Collision detection

Welcome to the part on Collision Detection. Great that you made it this far! In this section we're going use Tiled again to create a collision detection layer. Using Tiled we can add values and properties to tiles. What this can do for us is that we can give certain tiles the property "blocked" (or any name you want to give it). We can then use that property and see if our spaceship is touching a tile with that specific property and have it crash (You're dead). Because the tiles in our tile sets are 32 x 32 pixels a bunch of the tiles that have areas where the ship should crash and some areas where the collision detection should not be triggered. This means that basically the tiles are to big to use for collision detection. To solve this issue I've created a second tile map with a Tile Layer consisting of tiles that are 2 x 2 pixels that will serve as a collision layer. To give you a better understanding of what I mean I will show you how I did it.

Lets go back to Tiled and open up the level1.tmx map. First save the map as a .png file by going to File -> Save as Image. Now we want to create a new tile map that has the same dimensions as this tile map, but now consisting out of 2 x 2 pixel tiles. We can create a new tile map under File -> New but when we do that we have to tell Tiled the dimension of our new tilemap in width and height (x tiles). We don't now that yet so let's calculate that first.

So before you create a new tilemap let's stay with the original one first and click on Map -> Resize Map. Tiled should now tell you that the map is 149 Tiles in width and 8 Tiles in height. We now that the tiles are squares that have a width and height of 32 pixels (you could check this by clicking on Map -> New Tilest f.e.). This would mean that the map has a height of 8 x 32 = 256 pixels and a width of 149 x 32 = 4768 pixels.
If we divide both these numbers by two we know that our new map with 2 x 2 tiles should then have a height of 128 and a width of 2384 tiles.

Adding the .png image of the map can be done as followed:
Once you've created the new map add a new Image layer to it by clicking on either Layer -> Add Image Layer or right-clicking in the Layer Pane. The way to add the Image is to select the Image Layer and go to Layer -> Layer Properties. The properties window will now pop up.

Click on the browse button on the right side of the window and browse to where you stored the .png file of the map and select it. Once you've selected it you should immediately see it in Tiled. Now that we have our background image we still need a tile set (1 tile actually) that we will use to draw the collision layer. For this I am using this .jpg file:


Click on the link to download it and drag and drop it into your assets folder in Eclipse. Now from Eclipse you can either drag and drop it into the tile set pane in Tiled or import it by clicking on Map -> New Tileset. Leave the settings as they are by default. When you've done that you probably want to zoom in on the tile (it's very small and hard to click on otherwise). In the right bottom corner of Tiled you can adjust the zoom level of the tileset. Right click on the red tile and choose Tile Properties. Now when the properties window pops up click on the + button and give the tile the property "blocked". Now draw the red tile on the Tile Layer over the background anywhere that you want the player to collide with the map. Like this:

To color in the collision layer faster once you've put a couple of red tiles in the Tile Layer you can select a region by selecting Rectangular Select from the tool bar at the top of the screen (or press R button). Select the area you want to use, press CTRL + C & CTRL + V to copy/paste it and now you can "stamp" bigger chunks of tiles on the map. Once you're done drawing the collision layer UNCHECK THE IMAGE LAYER(!) and save the file. The reason for unchecking this layer is so that the LibGDX OrhtogonalTileMapRenderer class renders the first visible layer on screen and if we would like to render the Tile Layer (for debugging purposes for example) instead of the Image Layer the easiest way to do that is to uncheck the visibility of the Image Layer.

Here is the .tmx file for the collision layer in case you want to use mine instead of going through the process of creating your own. I've only done a small part of the map though....:


Place this file into your project's assets folder (along with red_tile2x2.jpg) and let's start adding some code to use this collision layer for collision detection.

Add the following changes to your starting class:

Code Analysis

On line 21 + 22 we create a Collision Map loading the data from the .tmx file and a new OrthogonalTiledMapRenderer to which we pass the collisionMap.
On line 3 a new LibGDX class gets introduced - TiledMapTileLayer - that can hold information of specific tile layers (in this case our collision layer). We'll use this later when we get to collision detection.
On line 46/47 we set the projection matrix of the TileMapRenderer to that of our camera.

Alternatively you could also render a specific layer using the commented out code of lines 50 - 52. Note that if you want to use the .renderTileLayer() method - as opposed to collisionMapRenderer.render(); - you have to(!) call .getBatch().begin() and .getBatch().end().

Passing the collision layer to the Player class

Now we know that the collision map fits right over our tile map you can comment out (//) lines 46 + 47. What we're going to do now is pass the collision layer to the Player class and add code to the Player class that will make use of the collision layer to check for collision detection with the map. To do this move line 15 to line 25 (just after we've created our TileMapTileLayer instance) and pass the collisionLayer to the Player class. Like this:

You'll get an error "The constructor Player(TiledMapTileLayer) is undefined" because we haven't added any parameters to the Player class' constructor yet. Let's do that by going to the Player class:

First create a field within the Player class for a TiledMapTileLayer (line 2). You can give it any name but I've given it the same name here as the one in our MambowGame class. On line 4 we've added a parameter to the Player class' constructor telling our code that we need a TileMapTileLayer object to be passed to our Player when we create it. Line 5 basically says that the TileMapTileLayer private to the Player class (the one from line 2) - this.collisionLayer is the collisionLayer that got passed to the constructor (line 4). I can thereafter use collisionLayer in the other methods of my Player class.

Now at the end of the update() method add a call to the checkCollisionMap() method.

We haven't created that method yet so let's add this method to the Player class:

Code Analysis

So what's happening here? When the checkCollisionMap() method is called I'm creating two variables - xWorld & yWorld - on line 3 + 4 to determine the spaceship worldcoordinates instead of it's screencoordinates. What's the difference you might ask? The screencoordinates are the spaceships coordinates on the screen (using the viewport's dimensions). This means that the screencoordinate's x component will always be between 0 - 312 and the y component will be between 0 - 192 (which if you remember are the viewport's dimensions). As our camera moves - and the background scrolls - our spaceship's screencoordinate could always stay the same while it's worldcoordinates constantly changes. And it's these worldcoordinates that we need when we check for collision with the map. To get the ship's worldcoordinates I take it's screencoordinates (x + y) and add the value of SCROLLTRACKER_X/Y. We haven't created these variables yet, but we'll get to that soon. The value of SCROLLTRACKER_X/Y is determined by how much the camera has moved since the start of the game. So when the camera has moved 100 pixels on the horizontal plane and the ship's x component of the screencoordinates is 100, the x component's value of the worldcoordinates is 200.

On line 7 we create a boolean variable collisionWithMap which by default is set to false.
On line 9 we call another method called isCellBlocked to which we pass our worldcoordinates. This method's return type is a boolean which means when we call that method it will return us a boolean value (most methods return types so far have been void meaning they don't return any value).

What's happening in the isCellBlocked() method is that we get a cell (e.g. Tile) from our collision layer depending on the worldcoordinates we passed to the method. And then on line 23 we get the properties from that Tile and when it contains the value "blocked" (which we added to the red tile in our collision layer) the method returns the value true and otherwise it returns false. If it returns true the value of collisionWithMap changes to true and executes the code from the if statement (lines 12 - 14) printing out "player-map collision!!!" in Eclipse's Console Pane. Obviously instead of this print out you would normally want to trigger events like display the crash animation and sound, etc. but we won't be doing that in this "mini" tutorial.

Okay, so now let's add the SCROLLTRACKER_X/Y variables and take care of updateing it's value depending on how much the camera has moved. In the Variables class add the following lines:

And now go back to the cameraUpdate() method in the MambowGame class and make the following changes:

Now whenever the camera moves it updates the SCROLLTRACKER values (line 9, 16 & 22) so that the checkCollisionMap() method of the Player class can calculate the right worldcoordinates. Now run your Game and see if the collision detection is working!

Final Thoughts

And that's it for this "mini" Tutorial. Of course we could go on to discus many many more things about Mam Bow 2, but I just wanted to create a tutorial that would cover some of the basics like project setup, viewport, camera movement, tilemap usage etc. If you liked this tutorial, would like to have more sections on Mam Bow 2, have feedback or any other thought please leave a comment in the Disqus section.

Follow Norakomi on Google+ and/or Facebook to keep updated on new tutorials and apps.