In this recipe, we will look at the necessary steps required to create and implement a level. This will be done by implementing a level object and updating the games framework to handle the loading and drawing of modular 2D tile assets. This modular construction allows levels to be designed with varying environmental layouts and sizes.
In order to implement a level, we will need to modify both the
Player
andMain
objects as well as introduce a level manager object. Go ahead and open theMain
object and add the following declaration below where we declared and loaded our player sprite:var tile = new Image(); tile.src = "textures/tile.png";
This declaration creates a new image object, which will represent the terrain within the level as well as the path to the tiles texture. Next we will initialize a new instance of the level object we are yet to make as well as passing the level to our player object. Modify the contents of the
Initialise
function in ourMain
object as follows:this.level = new Level().InitLevel(); this.player = new Player().InitPlayer(this.level);
Next we will need to modify the
Player
object, which will be responsible for initializing the level and for performing any collision detection to determine if the player is standing on the terrain and whether or not the player has collided with any obstacles in the player's path. Insert the following variables into thePlayer
object and then modify thePlayer
constructor in order to retrieve and initialize a level object:this.level = null; this.InitPlayer = function(level) { this.InitAnimationManager(player_idle_left, 300, 600 - 48 - 48, 4, 6, 20); this.level = level; return this; }
The next stage is to implement some form of collision detection to determine whether or not the player is standing on the terrain or has collided with an obstacle. To do this we will modify the
Update
function within thePlayer
object. Insert the following below the code that moves the player in a given direction:var collision, position, curTile, terrainHeight, playerHeight; if ((this.right || this.left) && !(this.left && this.right)) { collision = false; do { position = this.left ? this.x : this.x + this.frameWidth; curTile = this.level.CurrentTile(position); terrainHeight = this.level.TerrainHeight(curTile); playerHeight = context.canvas.height - (this.y + this.texture.height); if (playerHeight < terrainHeight) { collision = true; if (this.right) this.x = this.level.tileWidth * curTile - this.frameWidth - 1; else this.x = this.level.tileWidth * (curTile + 1); } else collision = false; } while (collision) }
The final step is to implement the
Level
object. Create theLevel
object and insert the following code in it:function Level() { this.tiles = new Array(); this.tileWidth = 0; this.tileHeight = 0; this.InitLevel = function() { this.tileWidth = tile.width; this.tileHeight = tile.height; for(var i = 0; i < 50; i++) { this.tiles[i] = 1; } this.AddTiles(); return this; }; this.AddTiles = function() { for (var x = 0; x < this.tiles.length; ++x) { for (var y = 0; y < this.tiles[x]; ++y) new DrawableObject().InitDrawableObject(tile, x * this.tileWidth, 600 - (y + 1) * this.tileHeight, 4); } }; this.CurrentTile = function(x) { return parseInt( x / this.tileWidth); }; this.TerrainHeight = function(index) { if (index < 0 || index > this.tiles.length) return 0; return this.tiles[index] * this.tileHeight; }; }
We first start off by creating a new image object within the Main
object. This object represents a 2D tile module that the level is made up of. Similarly, to the player sprite this object loads an external texture into the application. We then declare and initialize a new instance of the Level
object. This Level
object is also passed into the Player
constructor so that it can be accessed and utilized within the Player
object.
Within the Player
object, we then assign a reference to the level object, passed into the Player
constructor, to a local level object. This reference is used to check whether or not the player is intersecting the levels terrain.
These collision detection checks are only employed when the player chooses to move left or right. When moving in either direction if the player collides with an obstacle they are pushed back to the point of intersection thus preventing them from passing through an obstacle.
The level object itself is made up of a constructor and a number of helper functions that are used to add tile modules into the level thus dictate the layout of the environment. The object also contains helper functions that are used to determine if the player is colliding with the terrain in their current position as well as a function for checking the height for each stack of tiles.
The constructor of the level object initializes the width and height of the tiles passed into it. This is done by retrieving the width and height of the tile image object that was previously loaded into the application, inside of the main object. The remainder of the constructor is used to determine the height of each stack of terrain tiles as well as how many stacks make up the level's terrain.
Each of these stacks of tiles are stored within an array that is passed into the AddTiles
function within the Level
object. This array is then looped through and each tile within a stack drawn on top of each other and each stack drawn next to the previous stack thus resulting a 2D terrain the player can interact with.