Build a Cross-Platform Puzzle Game: Game Geometry and Setup

In the first part of this series, the concept for the game was introduced and described in detail. The cross-platform game development process via Shiva3D Suite was also described in detail. This installment will focus on the underlying geometric aspects of the game and lay the background for code development. It will also introduce the step-by-step instructions for building the game with the ShiVa Editor.

HUD Coordinate System

The HUD coordinate system represents a normalized view of the screen where the screen width and height are always 100 units. Let us call those HUD units. Because this application is developed for deploying into different devices with different screen resolutions, the HUD coordinate system will help us visualize the geometric relationships with respect to a fixed unit system without worrying about actual screen dimensions, which are measured in pixels. Eventually, we will obtain the screen dimensions of the device and perform transformations from the HUD units into pixels, however, that is relatively simple math.

In the above picture, observe the following:

The lower left corner of the screen corresponds to the origin (0,0) of the HUD coordinate system.

The lower right corner of the screen corresponds to (100,0) of the HUD coordinate system.

The upper right corner of the screen corresponds to (100,100) of the HUD coordinate system.

The upper left corner of the screen corresponds to (0,100) of the HUD coordinate system.

In the HUD coordinate system, points greater than 100 along the X-axis are not allowed. Similarly, points greater than 100 along the Y-axis are not allowed. Hence, the HUD coordinate system is defined by the rectangle (0,0), (100,0), (0,100), and (100,100).

Touchscreen Coordinate System

The touchscreen coordinate system is useful in order to capture the user's interaction with the screen. When the user's finger touches the screen the position of the finger is defined with respect to the touchscreen coordinates. The origin (0,0) of the touchscreen coordinate system coincides with the point (50,50) of the HUD coordinate system. In addition,

The point (-1,0) of the touchscreen coordinate system coincides with the point (0,50) of the HUD coordinate system.

The point (0,1) of the touchscreen coordinate system coincides with the point (50,100) of the HUD coordinate system.

The point (1,0) of the touchscreen coordinate system coincides with the point (100,50) of the HUD coordinate system.

The point (0,-1) of the touchscreen coordinate system coincides with the point (50,0) of the HUD coordinate system.

In the touchscreen coordinate system, points greater than 1 or less than -1 along the X-axis are not allowed. Similarly, points greater than 1 or less than -1 along the Y-axis are not allowed.

Transformation Between Device Screen And HUD Coordinates

The picture below shows the relationship between the device screen width xd, height yd and the HUD coordinates.

Figure 19. HUD Coordinate System And Screen Dimensions

The xd and yd are measured in pixels. For example, for iPad, xd=768 and yd=1024. Because 100 HUD units corresponds to xd pixels along X-axis and 100 HUD units correspond to yd pixels along Y-axis,

x pixels along the X-axis corresponds to xh=100*x/xd HUD units

y pixels along the Y-axis corresponds to yh=100*y/yd HUD units

Puzzle Image

The puzzle image is xp pixels in width and yp pixels in height, as shown below.

Figure 20. Puzzle Image

The puzzle image needs to be scaled up or down in order to fully fit into the screen. We define a variable called scale as follows:

scale = min (xd/xp,yd/yp)

We will revisit the scale variable in a moment. When broken down into pieces, the puzzle consists of Xp columns and Yp rows. Hence, there are a total of Xp * Yp puzzle pieces. For example, below, Xp=4, Yp=6. Note: In our Puzzle game, Xp=6, Yp=9.

Centered Image

We define a variable named xOffset as the distance between the Y axis and the puzzle image when the image is horizontally centered on the screen. This is shown below. Note that xOffset is in HUD units.

Figure 23. Offset

Fixed Positions

Let us define Pij to be the (i,j)-th puzzle piece, where i=1,...,Xp, j=1,...,Yp. Assuming the puzzle image is horizontally centered in the screen, we use fij to indicate the fixed center position of the (i,j)-th puzzle piece when all pieces are correctly positioned (i.e. the puzzle is solved). In the example below, Xp=4, Yp=6.

Figure 24. Fixed Points

Note that because puzzle pieces can be in arbitrary locations during the course of the game, fij does not necessarily represent the center position of the puzzle piece Pij at all times.

Button Positions

The following diagram shows the position of toolbar elements where all distances are given in pixels.

Figure 25. Toolbar Elements

The toolbar is placed on top the screen, i.e. its upper left and right corners coincide with the upper left and right corners of the screen.

The toolbar is xd units wide, i.e. it is as wide as the screen itself.

The toolbar is ym1 + ym2 + yb pixels high.

The distance between Start button and left side of the screen is xlm pixels.

The distance between the Start and New buttons is xlm pixels.

The distance between the Solve button and the right side of the screen is xrm pixels.

The distance between the Timer and Solve buttons is xrm pixels.

The Start, New, Solve buttons and the Timer are all ym1 pixels below the top edge of the toolbar.

The Start, New, Solve buttons and the Timer are all ym2 pixels above the bottom edge of the toolbar.

Each of the Start, New, Solve buttons and the Timer is yb pixels high.

The Start button is x1 pixels wide, the New button is x1 pixels wide, the Timer is x3 pixels wide, and the Solve button is x4 pixels wide.

Because our application supports multiple devices with varying screen sizes, we cannot predefine the variables above. In our code, we will define those variables as a function of the screen width of the device. Because the API method calls we will be using are in terms of HUD units, below we will define some variables corresponding to the pixel distances described above.

Variable in Pixel Units

Corresponding Variable in HUD Units

x1

x1h=100*x1/xd

x2

x2h=100*x2/xd

x3

x3h=100*x3/xd

x4

x4h=100*x4/xd

xlm

xlmh=100*xlm/xd

xrm

xrmh=100*xrm/xd

yb

ybh=100*yb/yd

ym1

ym1h=100*ym1/yd

ym2

ym2h=100*ym2/yd

We had introduced a variable named scale previously. Because the top section of the screen will be used by the toolbar that is ym1 + ym2 + yb units high, there is less screen height available for the puzzle image to fit. Therefore, we redefine the scale as follows:

scale = min (xd/xp,(yd - (ym1+ym2+yb))/yp)

Developing Puzzle with the ShiVa Editor

In this section, we will describe how to develop the game using the ShiVa Editor.

ShiVa Editor Modules

Let us first give a quick introduction to various modules in the ShiVa Editor. We borrow most of this information from the ShiVa Editor help documentation.

Data Explorer is a resource manager to provide access to various elements of the game, including the game itself, AIModels, HUD etc. Its user interface presents those resources in a hierarchical folder structure.

Game Editor is the main module of the editor letting developers edit the game resource. For example, with game editor, a developer can define the AIModel associated with the game.

AIModel Editor allows developer to edit AIModels. With AIModel editor, you can define variables, functions, states, and handlers for an AIModel.

Script Editor is used to edit scripts of an AIModel.

HUD Editor is used to create and edit a HUD. In particular, the HUD editor can be used to define event handlers to process actions on the UI components in the HUD, such as the handling of a user pressing a button.

Soundbank Editor is used to create and edit a "Soundbank", or a storage area for sounds in the game.

AIModel

In Shiva3D, an AIModel represents the behavior of a model, typically a 3D model. An AIModel can have variables, states, handlers and functions. In Puzzle, we do not have a 3D model. However, in order to define the scripts of the game, we create a single AIModel called PuzzleAI. We will review below the variables, states, handlers, and functions of PuzzleAI.

Variables

buttonOrigXSize, buttonOrigYSize: Those variables store the original width and height, respectively, of the currently pressed button. When pressed, the button shrinks. When unpressed, using those variables, it expands to its original size.

canPlay: This variable is Boolean and indicates if user can interact with the game. During certain states, the variable is set to false as the application needs to process a previously initiated user action.

cFx, cFy, cPPx, cPPy, cPx, cPy, cTPx, cTPy: Those data structures are of the Lua table type. See the section named "Table Data Structures" below for a description.

curMovCompI, curMovCompJ: Those integer variables store the indices of the puzzle piece currently being moved by the user. For example, if curMovCompI=3, curMovCompJ=4 then the user is moving P34.

currentX, currentY: Those store the HUD coordinates of a puzzle piece moved by the user at the moment the user ends the touch sequence, i.e., unpresses the currently moved piece.

currTime: This variable represents the number of seconds passed since the timer started, or since this variable is reset. currTime is a decimal number and, as such, only its integer part is displayed in the timer. When currTime reaches 60, it is reset and the minute of the timer is incremented.

disabledBackgroundColor: An integer variable that represents each of the background RGB color values for a button in a disabled state.

disabledForegroundColor: An integer variable that represents each of the foreground RGB color values for a button in a disabled state.

enabledBackgroundColor: An integer variable that represents each of the background RGB color values for a button in an enabled state.

enabledForegroundColor: An integer variable that represents each of the foreground RGB color values for a button in an enabled state.

isButtonPressed: This is a Boolean variable and is true if and only if one of the buttons is pressed.

isInitialized: This is a Boolean variable and is set to true when the game enters Initialized state until all the puzzle pieces reach their random locations.

isMouseDown: This is a Boolean variable that is used when the application is being tested in the development environment via a mouse to emulate user touch actions.

isMultitouchEnabled: This is a Boolean variable that is true if and only if the device is multitouch enabled (this variable must be true for any device where our game is deployed).

isPuzzleReplaced: This is a Boolean variable and is set to true when the game enters the Replaced state until the game leaves that state.

isSolved: This is a Boolean variable and is set to true when the game enters the Solved state until the game leaves that state.

numHr, numMin, numSec: Those are integer variables that store the hour, minute and second, respectively, of the timer displayed to the user.

optionValue: This is an integer variable used to represent the current viewport rotation of the device. It assumes one of four values: 0 (no rotation), 1 (-90 degrees), 2 (180 degrees), or 3 (90 degrees).

pressedButtonName: This is a string variable that stores the name of the currently pressed button.

PuzzleHUD: This is a string constant that is equal to "PuzzleHUD.p".

puzzlePieceHeight, puzzlePieceWidth: Those are integer constants that store the height and width of each puzzle piece in pixels.

puzzlePieceXind, puzzlePieceYind: Those integer variables store the indices of the puzzle piece currently being moved or the puzzle piece that is to be replaced.

puzzleTempXPos, puzzleTempYPos: Those integer variables store the temporary position in HUD coordinates of the puzzle piece that is currently being replaced.

puzzleXind, puzzleYind: Those integer variables store the indices of the fixed position a puzzle piece is being moved to in Replaced state.

puzzleXPos, puzzleYPos: Those integer variables store HUD coordinates of the fixed position a puzzle piece is being moved to in Replaced state.

randX, randY: Those are data structures of Lua table type and store the randomly ordered column and row indices, respectively of the fixed positions. They are used to randomly place the puzzle pieces in Initialized state.

scale: Introduced earlier, this variable is a number used to scale up or down the puzzle in order to fully fit into the screen.

textHeight: This integer variable stores the percentage of text height inside the buttons.

tLastTouchState: This is a data structure of Lua table type and stores information regarding the touch sequence on the screen.

tolerance: This number defines a range in terms of HUD dimensions to automatically move a puzzle piece to the closest fixed position on the screen. When the user releases his/her finger from the screen, if the distance between the nearest fixed position and the center of the puzzle piece moved by the user is less than the tolerance, then the piece automatically snaps into the fixed position. Otherwise, the piece moves back to its original position automatically.

underCursorComponent: This string variable stores the name of the component currently touched by the user's finger.

Some of the important data structures we will be using in our code are of the Lua table type.

The x and y coordinates of the fixed position fij are stored in cFx[q], cFy[q], respectively, where q=(j-1)*Xp+i, i=1 ... Xp, j=1 ... Yp. The x, y are in HUD coordinates.

The x and y coordinates of the current position of Pij are stored in cPPx[q], cPPy[q], respectively, where q=(j-1)*Xp+i, i=1 ... Xp, j=1 ... Yp. The x, y are in HUD coordinates.

The tables cPx[q], cPy[q] store the indices k, l, respectively, where Pkl is the puzzle piece at fixed position k, l and q=(j-1)*Xp+i, i=1 ... Xp, k=1 ... Xp, j=1 ... Yp, l=1 ... Yp. For example, let Xp be 6 and assume that the (1,3)-th puzzle piece P13 is currently at fixed position of 2nd column and 4th row. Then i=2, j=4, q=(j-1)*Xp+i=20 and cPx[20]=1, cPy[20]=3.

We had previously mentioned that when the game is initialized (at application startup or when the user presses the New button) the puzzle pieces fly around and get into random locations. Similarly, when user presses the Solve button, the puzzle pieces fly around to get into their correct locations. During those periods when pieces fly from one location to another, we need to keep track of their current locations. The x and y coordinates of the temporary position of puzzle piece Pij are stored in cTPx[q], cTPy[q], respectively, where q=(j-1)*Xp+i, i=1 ... Xp, j=1 ... Yp. The x, y are in HUD coordinates.

States

Shiva3D provides a framework by which a developer can easily implement a state machine. As previously mentioned, the Puzzle has 5 states: Initialized, Started, Solved, Paused, and Replaced. To represent those, we have five states in our code: gameInitialized, gameStarted, gameSolved, gamePaused, and gameReplaced. A state in Shiva3D can have three possible scripts:

onEnter is executed once when the state is entered.

onLoop is executed each time the frame is entered within the current state. In Shiva3D, a game consists of loops and each frame is a loop.

onLeave is executed once when the state is exited.

Handlers

In Shiva3D, handlers consume events. Those events may be initiated by the user, system, or other elements in the code, such as functions, states, and handlers. PuzzleAI has the following handlers.

onInit is a built-in handler and is called every time the game is started.

onEnterFrame is a built-in handler. In Shiva3D, a game consists of loops and each frame is a loop. This handler is called in every loop of the game.

onJoypadMove handles the event resulting from the user rotating the device. The code of Puzzle is written such that the game toolbar is always positioned at the top of the screen, regardless of if the device is positioned in a vertical or horizontal manner. As we will see below, in onJoypadMove we detect the device orientation and rotate the viewport to display toolbar always at the top.

onMouseButtonDown, onMouseButtonUp, onMouseMove: Those handlers are used only during unit testing of the game in Preview or Animation mode in ShiVa Editor where the user touch actions are emulated via mouse clicks.

onTouchSequenceBegin, onTouchSequenceChange, onTouchSequenceEnd: Those handlers are used together to process user touch events.

onRequestRestart is called when user presses the Restart (New) button.

onStartPause is called when user presses the StartPause (Start or Pause) button.

onSolve is called when user presses the Solve button.

Functions

buttonPressed reduces the size of a button 90% when pressed in order to provide visual feedback to the user.

buttonReleased restores the original dimensions of a button when released.

getCurrentIndexAt returns the row and column indices (vi,vj) of the puzzle piece that is in the (i,j)-th fixed position.

getCurrentPositionOf. Recall that the x and y coordinates of the current position of Pij are stored in cPPx[q], cPPy[q], respectively, where x, y are in HUD coordinates. This function accepts i, j as input parameters and returns the x, y coordinates, current position, of the puzzle piece Pij.

getCurrentTempPositionOf. Recall that x and y coordinates of the temporary position of a puzzle piece Pij are stored in cTPx[q], cTPy[q], respectively, where x, y are in HUD coordinates. This function accepts i, j as input parameters and returns the x, y coordinates, temporary position, of the puzzle piece Pij.

getFixedPositionAt. Recall that x and y coordinates of the fixed position fij are stored in cFx[q], cFy[q] , respectively, where x, y are in HUD coordinates. This function accepts i, j as input parameters and returns the x, y coordinates of the fixed position fij.

getIndexOf. This function iterates through all fixed positions to find the particular fixed position that currently has the puzzle piece with indices ih, jh.

initializePositionTables. This function simply initializes all the table data structures by inserting a dummy 0 into the corresponding entries.

initializeRandomTables. Recall that randX, randY are data structures of Lua table type and store the randomly ordered column and row indices, respectively of the fixed positions. They are used to randomly place the puzzle pieces in Initialized state. This function populates randX, randY with random values.

positionControls. This function defines the dimensions and screen position of various HUD elements, the buttons StartPause, Restart (New), Solve, the labels Toolbar and Time.

reset. This function resets several variables. The reset is necessary in puzzleReplaced state and also when the touch sequence begins.

setBorderColors. This function goes through all puzzle pieces and redefines their border colors. If a puzzle piece is in correct location then its borders are set to green. Otherwise, no border color is defined.

setButtonActive sets various attributes of a button to indicate it is active or inactive.

setCurrentPosition puts the (ih,jh)-th puzzle piece in xCoord, yCoord, which corresponds to the fixed screen position at (i,j).

setFixedPosition defines the fixed position for the (i,j)-th pair stored in tables cFx and cFy based on the input parameters.

updateRotation Puzzle is written such that the game toolbar is always positioned at the top of the screen, regardless of the device is positioned in a vertical or horizontal manner. In onJoypadMove handler we detect the device orientation and rotate the viewport to display toolbar always at the top. This function is called by onJoypadMove in order to recalculate xu and yu variables based on new viewport dimensions. It also recalculates several variables to reset positions and dimensions of screen elements accordingly.

Head-Up Display

The Head-Up Display (HUD) is a basic element of the Puzzle as all visible components of Puzzle belong to the HUD. Please recall that Shiva3D is a 3D game development framework and HUD is normally used to provide UI controls such as buttons, labels etc. for letting user interact with the game. In Puzzle, however, there are no 3D UI elements and the entire game is implemented with two types of UI controls, buttons and labels. In addition, there are 'Actions' that dispatch user events to handlers.

Buttons

Restart: This is the button with the label New. Pressing this buttons starts a new puzzle game.

Solve: This is the button with the label Solve. Pressing this button automatically solves the Puzzle.

StartPause: This is the button with the Start or Pause labels. In Initialized and Paused states, the Start label is displayed. In Started state, the Pause label is displayed.

Labels

In our current implementation, Xp=6, Yp=9, and hence there are 54 puzzle pieces. Each of those puzzle pieces is represented as a label with no text where the background is the image of the corresponding puzzle piece. The labels representing the puzzle pieces are named p11 through p69.

Background is the name of the label that is used to provide background color. It covers the entire screen and puzzle pieces are laid over it. Background has no text and is colored black.

Toolbar is the name of the label that represents the Toolbar. It has no text and its color is RGB=48,48,48 (dark grey).

Time is the label that displays the timer. It has 0:00:00 as its display format. Its background color is RGB=48,48,48 (dark grey) and its foreground color is RGB=127,127,127 (light grey).

Actions

In Shiva3D, a HUD action is mainly used to dispatch an event to a handler when the user takes an action such as pressing a button. For each of the three buttons defined above there is a corresponding action.

restartAction is called when the user presses the Restart (New) button. This action invokes the custom onRequestRestart handler with no parameters. Note that this is also designated as the initial action and is called when the game starts.

solveAction is called when the user presses the Solve button. This action invokes the custom onSolve handler with no parameters.

startPauseAction is called when user presses the StartPause (Start or Pause) button. This action invokes the custom onStartPause handler with no parameters.

Pre-requisite Files From Download Archive

In order to follow the step-by-step instructions below you will need PuzzleAI.ste, PuzzleHUD.ste, PuzzleSoundBank.ste, PuzzleTextures.ste that are part of the download archive accompanying this article.

Step-by-Step Instructions for Building Puzzle

Create A Project

From the ShiVa toolbar, select Main > Projects.

Figure 27. Selecting Project

Click the Add button and select a project path, e.g. C:\Projects\Puzzle\. Make sure there is a green dot next to the project folder. It should look like below. Press Close to dismiss the dialog.

Figure 28. Selecting Project Path

Create A Game

In the Data Explorer menu, right click on Puzzle - Games and select Create -> Game.

Figure 29. Creating A Game

Type Puzzle as name of the game as shown below. Press OK to dismiss the dialog.

Figure 30. Naming The Game

Import Textures

Then, we need to import all the textures that contain the puzzle pieces. In the Data Explorer menu, select Import > Archive.

Figure 31. Importing Archive

Proceed to your local directory where you have stored the download archive accompanying this article. Select PuzzleTextures.ste and press Import. When the import is completed, in Data Explorer under Puzzle - Resources - Textures, you will see the textures named p11 through p69. A partial list is shown below.

Figure 32. Imported Textures

Import AI Model

The next step is to import the AI model. In Data Explorer menu, select Import > Archive. Proceed to your local directory where you have stored the download archive accompanying this article. Select PuzzleAI.ste and press Import.

Figure 33. Importing AI

To verify the import has succeeded, first look at the Data Explorer under Puzzle - Resources - AIModels. You should see PuzzleAI.

Figure 34. Verify PuzzleAI

Then, bring up the AIModel Editor. You should also have Data Explorer open. In Data Explorer under Puzzle - Resources - AIModels, double click on PuzzleAI. You should see variables, functions, states and handlers associated with the PuzzleAI.

Figure 35. Variable List I

Figure 36. Variable List II

Figure 37. Function List

Figure 38. States and Handlers List

In addition, if you look at the Data Explorer, Puzzle - Resources - Scripts, you will the scripts associated with the functions, states, and handlers (a partial list is shown below).

Figure 39. Scripts

Import Sound Bank

The next step is to import a sound bank for the game. In the Data Explorer menu, select Import > Archive. Proceed to your local directory where you have stored the download archive accompanying this article. Select PuzzleSoundBank.ste and press Import.

Figure 40. Importing SoundBank

After the import is completed, in Data Explorer you can go to Puzzle - Resources - SoundBanks and see that PuzzleSoundBank has been imported.

Figure 41. Verifying SoundBank

In addition, two sounds, st_punch_sound and Boing, must be visible in Data Explorer under Puzzle - Resources - Sounds.

Figure 42. Verifying Sounds

Concluding Remarks For Part 2

Part 2 of this tutorial started with "Game Geometry", which was focused on the underlying geometric aspects of the game and laid the background for code development. In "Developing The Puzzle with ShiVa Editor", we introduced main constructs of the code base that implement the game functionality, including the variables, functions, and event handlers. This section also gave step-by-step instructions on how to develop the game in the ShiVa Editor. Here, we described creating the project and the game, and importing textures, the AI model, and the sound bank from the download archive accompanying the tutorial.

Part 3 will continue the discussion in "Developing The Puzzle In the ShiVa Editor". Here, we will describe importing the HUD components and make the final adjustments to the game developed so far. Then, we will conclude "Developing The Puzzle In ShiVa Editor" via a unit test. In "Authoring Tool", we will show how to use the ShiVa Authoring Tool to convert the Puzzle into two different applications, one for Android OS and one for iPad. We will then wrap up the article with a "Conclusions" section.