Tiled Level Sample

Overview

This sample has code for a very basic 2D map of tiles that the player can move and jump around. Platforms are randomly created on a 64x64 grid of tiles. Either an XBox 360 gamepad or the keys A, S and Space can be used to navigate the player around. The code base consists of 5 classes split into individual files: The map used in this sample is build up of 64x64 tiles, each one 32 pixels wide and high, with the player's size exactly matching one of the tiles. GameObjects' positions specify the top left of the object's position in whole tiles. So a position of (12.5, 62) means the player is at the bottom of the map half-way between the tiles at x co-ordinates 12 and 13. Objects use a rectangular bounding box for collision detection.

Rendering the map

The Map class contains a method called DrawOnMap() which will draw a given texture at the correct position on screen based on the supplied world (or map) position. It does this by utilizing the WorldToScreen() method which converts a world co-ordinate into a screen co-ordinate, there is also a ScreenToWorld() method which does exactly the opposite.

Since we store a reference to which tile should be drawn in the centre of the screen the first thing we do in WorldToScreen() is adjust the world co-ordinate given so it is relative to the centre of the screen (subtract centre). Then we need to scale the distance in the world space to match screen space. Since world space uses units of whole tiles and screen space is in pixels we multiply by the size in pixels of the tiles (32 in this example) and also the scale of the map (currently fixed to 1). This gives us a screen co-ordinate relative to the centre of the screen. To finish off, we find the absolute screen position relative to the top-left of the screen by adding the co-ordinates of the centre of the screen in pixels.


fig 1 - Tiles which need to be rendered given the position of the screen.

We render all the visible tiles of the map in the Draw() method. This uses the DrawOnMap() method for each tile that appears on screen. To calculate which tiles appear on screen we use the ScreenToworld() method to find the world co-ordinates at the top-left and bottom- right of the screen. Once we have this information we can simply loop through all the tiles inbetween and call DrawOnMap() to render them.

Collision detection

Every frame, when Update() is called on a GameObject we want to move the object horizontally and vertically. The derived method, in this case the Player, sets up the 'vector' variable to indicate how the object will move. The Update() method of the Player class shows what we do for the player object. For example, the Player's Update() method always adds to vector.Y to simulate gravity. When the player successfully jumps we set vector.Y to a large negative number. This makes the player initially move very quickly but then slow down and get pulled back to earth due to gravity.


fig 2 - Area we want to move the object through.

The Move() method within GameObject is called to move the object once the derived class has set up the 'vector' variable appropriately. In this method we calculate what area of the map the object needs to move through for both the horizontal and vertical movement. Fig 2 shows the player about to move horizontally with the blue area indicating the area we will try and move the player though. We can calculate the bounds of the blue rectangle using simple maths as everything either lines up with the player position or is given by the amount we're trying to move. This area is passed to the map's Collide() method along with the direction we're trying to move.


fig 3 - Which tiles we need to check in order to move.

In the Collide() method within the Map class we deal with whole tiles so we need to convert the bounding area passed in to whole tiles as shown in fig 2. We then check each of the tiles highlighted to see if it's solid. The order we check each of the tiles depends on the movement direction passed in. For moving to the right we need to check the tiles on the left before the tiles on the right, if we were moving upwards through this area then we'd need to check the two tiles at the bottom before the two at the top. The reason for this is that as soon as we encounter a solid tile we calculate the position an object would get stopped at, in this case it would be the left edge of the brick as we're trying to move right.


fig 4 - Player moved to hit the wall.

The x-coordinate of the left edge of the brick gets returned to the GameObject so that we can then adjust the position of the object so it is flush against the surface of the brick as shown in fig 3. We'd then do the same for the y direction. If we were falling then we'd only need to check the tile directly below us since we now fit exactly in one tile horizontally.