Bonus Maze Game - WebGL Textures: Introduction to Mipmaps, Sub Images & Atlases (2015)

WebGL Textures: Introduction to Mipmaps, Sub Images & Atlases (2015)

Bonus Maze Game

Maze Game

WebGL Textures: Introduction to Mipmaps, Sub Images & Atlases includes a bonus simple maze game developed with WebGL sub images. The game displays the two dimensional maze used with the previous two projects. The player's tile displays in red. The player moves the tile through the maze with taps along the brightly colored edges. The game keeps score based on the number of steps the player takes to find their way out of the maze. If the player reaches the destination with the shortest path, then the score equals 100%.

Maze Game Web Page

The maze game's Web page includes styles and elements to display brightly colored strips around the game board. Tap on the left strip to move the player left. Tap on the right strip to move the player right. Tap on the top strip to move the player up. Tap on the bottom strip to move the player down. Other methods exist to select and move elements on a WebGL canvas. This technique provides perhaps the most simple method to interact with WebGL elements.

The following listing includes the styles to color the top, bottom, left, and right colored elements. The eDebug element serves as the top red colored strip. The eBoard element encloses eContent which encloses the canvas with id of cv. We use the elements to provide a yellow strip along the left edge of the canvas, a violet strip along the right edge, a red strip along the top, and an aqua strip along the bottom. The strips provide navigation.

#eBoard{

display:block;

width:650px;

height:650px;

margin-left:auto;

margin-right:auto;

background-color:green;

}

#eContent{

width:512px;

height:650px;

float:left;

}

#cv{

background-color:blue;

display:inline;

float:left;

margin-left:0px;

margin-right:0px;

clear:none;

}

#eDebug,#eBottom{

width:512px;

height:69px;

font-weight:bold;

}

#eDebug{

background-color:#ff1111;

font-weight:bold;

}

#eBottom{

background-color:#00ffff;

font-weight:bold;

}

#eLeft,#eRight{

display:inline;

width:69px;

height:650px;

float:left;

}

#eLeft{

background-color:#ffff00;

}

#eRight{

background-color:#ff00ff;

}

#animStop,#animStart{

visibility:hidden;

height:0px;

width:0px;

}

Listing 40: Maze Game Styles

The following listing demonstrates how the colored strips are arranged around the center canvas with HTML.

<div id="eBoard">

<div id="eLeft"></div>

<div id="eContent">

<div id="eDebug">

Tap colored rectangles to the

left, right, top, and bottom

of the game board.

</div>

<canvas id="cv"

width="512" height="512"

>

Your browser doesn't support canvas.

</canvas>

<div id="eBottom"></div>

</div>

<div id="eRight"></div>

</div>

</div>

Listing 41: Maze Game HTML Elements

Initialize the Maze Game

The file GLMazeGame.js declares the GLMazeGame class. The maze game initializes the following instance variables identical to the Sub Image's project. Create GLMaze array, nX, nY sub image coordinates. Finish initialization after the image instance variable's Image file downloads. The onload event listener named imgLoaded() completes maze game initialization.

However the GLMazeGame constructor first adds a few instance variables for game play. A String named S_PLAY_AGAIN contains To play again, reload the Web page.. The instance variable nMoves tracks the number of times the player moves. The instance variables nLastX and nLastY maintain the sub image's previous coordinates. Last the instance variable idx saves the player's current location as an index into the maze array.

Method imgLoaded() activates after the image file downloads. imgLoaded() then completes initialization. imgLoaded() creates an index element array and array of vertices with texels identical to the Sub Image project's imgLoaded() method. Method imgLoaded() creates a controller, then calls methods getAudio() and setListeners(). We cover getAudio() and setListeners() next.

Maze Game getAudio()

The maze game saves references to audio tags from the Web page. Sound effects play when the user makes a right move, makes a wrong move, or completes the game. The following listing shows the audio tags declared within the maze game's Web page. Notice each audio tag includes an id attribute. Method getAudio() obtains references to the audio elements by id. See the Downloads section to download the audio files.

<audio id="aZap">

<source

src="audio/dink.mp3"

type="audio/mpeg"

/>

<source

src="audio/dink.ogg"

type="audio/ogg"

/>

</audio>

<audio id="aChime">

<source

src="audio/chime.mp3"

type="audio/mpeg"

/>

<source

src="audio/chime.ogg"

type="audio/ogg"

/>

</audio>

<audio id="aTap">

<source

src="audio/water-bubble.mp3"

type="audio/mpeg"

/>

<source

src="audio/water-bubble.ogg"

type="audio/ogg"

/>

</audio>

Listing 42: Web Page Audio Tags

Method getAudio() saves instance variables referencing the Web page's audio elements. The formal parameter to getAudio() is a reference to the GLMazeGame class named glMaze. The following listing includes the entiregetAudio() method. The game uses instance variables aChime, aTap, and aZap, in response to player moves.

getAudio: function(glMaze){

glMaze.aChime = document.getElementById

(

"aChime"

);

glMaze.aTap = document.getElementById

(

"aTap"

);

glMaze.aZap = document.getElementById

(

"aZap"

);

},

Listing 43: Maze Game getAudio() Method

Maze Game setListeners()

Method setListeners() assigns click event listeners for elements which display as colored strips around the canvas.

Method setListeners() has one parameter. The parameter references the GLControl controller. The controller always saves a reference to the current example project in it's glDemo property. First setListeners() saves this instance of GLMazeGame to the local variable glMaze. Then setListeners() removes the default click event from the canvas. We don't want to render the scene when the player taps the canvas, as we have with most of the other projects.

Next setListeners() assigns method tapTop() to the eDebug element, method tapBottom() to the eBottom element, method tapLeft() to the eLeft element, and method tapRight() to the eRight element. Method setListeners creates a property named controller on each element. Each element's controller property receives a reference to the current GLControl class. Each click event listener has one parameter. The parameters is an event object. When tapTop(), tapBotom(), tapLeft(), or tapRight() trigger, then the event object's currentTarget.controller property references our GLControl class. The game needs the controller reference to process tap events for the maze.

See the setListeners() method.

Maze Game's Tap Events

Methods tapTop(), tapBottom(), tapLeft(), and tapRight() respond when the player taps a colored rectangle around the canvas. Each event first calls method setPlayAgain() which returns true if the game's over, and falseotherwise. If the game's over setPlayAgain() tells the player to reload the Web page, if they want to play again. Otherwise the tap event handlers process the player's current move.

Tap events activate when the player taps the colored strips which display along the sides of the maze. The only parameter is an event object. The event object's currentTarget.controller property is a reference to the controllerclass.

Each tap event saves a local reference to the controller. Each tap event saves a local reference to the GLMazeGame. The controller's glDemo property always contains a reference to the current project. The following line saves aGLMazeGame reference to the local variable glMaze.

var glMaze = controller.glDemo;

Each tap event increments the maze game's nMoves instance variable. The score is based on the number of moves it takes exit the maze. A score of 100% means the player took the shortest route out of the maze.

Each tap event first determines if the maze has an open tile in the direction the player wishes to travel. If not, then call the method setWrong(). If so, then call the method setRight().

Maze Game's setWrong() Method

Method setWrong() only has one parameter, which is a reference to this GLMazeGame class. setWrong() simply tells the player they tried to move off the path, then plays the audio file associated with the audio element aZap. The following line plays the zap sound.

glMaze.aZap.play()

The eDebug and eBottom colored strips around the canvas, both display the following message to the user.

You tried to move off the path.

See the setWrong() method.

Maze Game's setRight() Method

Method setRight() has two parameters. The first parameter is a reference to the GLControl controller. The second parameter is an index into the maze array, named idx. Method setRight() calculates X and Y coordinates from the index into the maze array. Then setRight() saves the coordinates to the nX and nY properties of the GLMazeGame reference. setRight() saves the index into the maze array to the GLMazeGame's idx property. Method setRight() call's the controller's animOne() method which renders the new tile location.

Method setRight() follows the same sequence we outlined within the Sub Image's getRandom() method, to find the row, column, and coordinates of the upper left hand corner to display the next sub image. Calculate the column as follows col = idx % 16;. Calculate the X coordinate to display the tile sub image as follows. glMaze.nX = col * 32; The following listing demonstrates how to determine the Y coordinate for the sub image, from an index into the maze array.

// Index into the

// array, divide by

// sixteen to find

// the row.

var row = Math.floor(idx/16);

// Rows start with

// row zero at

// the bottom.

row = 15 - row;

// Each tile is

// 32 pixels square.

glMaze.nY = row * 32;

Listing 44: Maze Game Y Coordinate from Index

If the current index is less than 255 then the game's not over. setRight() plays the tap sound, and displays the number of moves to the player, with the following listing.

glMaze.aTap.play();

s = new String(

"Great move! You moved "+glMaze.nMoves+" times."

);

Listing 45: Maze Game: Correct Move

If the current index equals 255, then the player has completed the maze. In other words the player is on the last tile which leads out of the maze. Therefore setRight() plays the chime sound. setRight() calculates the score as a percentage based on the minimum number of moves. If the player made it to the last tile in the minimum number of moves then the score equals 100%. Otherwise setRight() decrements the score based on the number of extra moves the player took. The following listing demonstrates calculating information for the final move.

// This array's

// shortest path

// takes 38 moves.

var nMinMoves = Number(38);

// Play the game

// over sound.

glMaze.aChime.play();

// Percentage of moves

// for each tile

// on the shortest

// path.

var nTilePercent = Number(1/nMinMoves * 100);

// Percentage

// over.

var nScore = (glMaze.nMoves - nMinMoves) * nTilePercent;

// Score in

// percentage.

nScore = Math.floor(100 - nScore);

// Prepare

// to tell the

// player their

// score.

if (nScore == 100){

// 100%

s = new String("You win!! Score: "+nScore+"%");

}

else

{

// Less

// than 100%.

if (nScore < 0)

nScore = 0;

s = new String("Game Over!! Score: "+nScore+"%");

s += "
Number of moves: "+glMaze.nMoves+".";

s += " Quickest path: "+nMinMoves+".";

}

Listing 46: Maze Game Calculate Score

Last setRight() displays information to the player. See the setRight() method.

Maze Game's tapTop() Method

tapTop() activates when the player taps the colored strip which displays along the top of the maze.

tapTop() finds the index within the maze array, which corresponds to one tile directly above the player's location. If the player's on the top row, they can't move any higher. In that case glMaze.idx index equals 15 or less. The top row's indices range from 0 to 15. Any row below the top row has an index greater than 15. If the current index is less than 16 then call method setWrong() and return.

Otherwise check to see if the tile above the player's tile, is on the path. The following listing demonstrates determining if the tile above the current tile is on the path.

// The index above

// the player's current

// location.

idx = glMaze.idx - 16;

// Is the tile

// above on

// the path?

v = glMaze.aMaze[idx];

if (v != 0){...

Listing 47: Maze Game Find Tile Above

If the tile above the current tile is on the path, then call setRight(). Pass the index of the tile above the current tile to method setRight(). See the tapTop() method.

Maze Game's tapBottom() Method

tapBottom() activates when the player taps the colored strip which displays along the bottom of the maze.

tapBottom() finds the index within the maze array, which corresponds to one tile directly below the player's location. If the player's on the bottom row, they can't move any lower. In that case glMaze.idx index equals 240 or more. The bottom row's indices range from 240 to 255. If the current index is greater than 240 then call method setWrong() and return.

Otherwise check to see if the tile below the player's tile, is on the path. If the tile below the current tile is on the path, then call setRight(). Pass the index of the tile below the current tile to method setRight(). See the tapBottom()method.

Maze Game's tapLeft() Method

tapLeft() activates when the player taps the colored strip which displays along the left side of the maze.

tapLeft() finds the index within the maze array, which corresponds to one tile directly to the left of the player's current location. If the player's on farthest left row, they can't move any farther to the left. In that case glMaze.idx % 16equals zero. Call method setWrong() and return.

Otherwise check to see if the tile to the left of the player's tile, is on the path. The following line obtains the index of the tile to the left of the current tile.
idx = glMaze.idx - 1;

Check the maze to determine if glMaze.aMaze[idx] ! = 0. If the tile to the left of the current tile is on the path, then call setRight(). Pass the index of the tile to the left of the current tile to method setRight(). See the tapLeft()method.

Maze Game's tapRight() Method

tapRight() activates when the player taps the colored strip which displays along the right side of the maze.

tapRight() finds the index within the maze array, which corresponds to one tile to the right of the player's current location. This method simply increments the index of the player's current location. If the next tile in the array is not on the path, then call method setWrong() and return. Notice in this case if the player's tile is on the right most tile of a row, then the next tile in the array wraps around to the left most tile one row down.

Check to see if the tile to the right of the player's tile, is on the path. If the tile to the right of the current tile is on the path, then call setRight(). Pass the index of the tile to the right of the current tile to method setRight(). See thetapRight() method.

Render the Maze Game

The render() method has one parameter, a reference to the GLControl controller class. First obtain a reference to the WebGLContext with the following line.

var gl = controller.gl;

Second obtain a reference to the GLMazeGame class with the following line. The controller always saves a reference of the current project to the property named glDemo.

var glMaze = controller.glDemo;

Third call the WebGL API method texSubImage2D(). The X and Y coordinates for the upper left hand corner of the sub image were saved to the glMaze properties nX and nY. The glMaze property image contains the color data for the sub image. The following listing demonstrates calling texSubImage2D() in the maze game's render() method.

gl.texSubImage2D

(

gl.TEXTURE_2D,

0,

glMaze.nX,

glMaze.nY,

gl.RGBA,

gl.UNSIGNED_BYTE,

glMaze.image

);

Listing 48: Maze Game Render with texSubImage2D()

Last call the WebGL API method drawElements() as follows.

gl.drawElements

(

gl.TRIANGLES,

controller.nBufferLength,

gl.UNSIGNED_SHORT,

0

);

Listing 49: Maze Game Render with drawElements()

See the render() method.

Game Ideas for Improvement

We designed the maze game as a simple example. However, configurable arrays offer a number of possibilities. You might generate 3D mazes from an array. Of course the game could benefit from a number of improvements. For example highlight tiles with a different color when the player back tracks over the same section of the path a second time. Currently the player can't determine which square they're on, when they double back. When retracing steps along a path every square they've already covered is red.

Other options include levels, and interactivity. Add a Play Again button to reset the display and game state. Perhaps include levels of difficulty with different maze graphics and maze arrays. Interactive response usually makes a more enjoyable game. For example highlight the player's tile when they move. Allow the player to tap where they want to move the tile.

Maze Game Summary

This simple game demonstrated how to use a configurable array to move a player along a path within a maze. The game keeps score based on the number of moves the player takes to find their way out of the maze. We used the array and maze graphic from the previous sub image projects.

See the GLMazeGame.js JavaScript file.