Adventures in Phaser with TypeScript–Tilemaps Part 2 Now with more stuff!

 

 

In the previous tutorial we took a look at creating, loading and displaying a tilemap in Phaser and for many simple projects this may be enough.  That said, there are two very common scenarios this doesn’t really address, and we will look at those in this tutorial.  The first is positioning a sprite within a tilemap.  The second is collision detections between tiles and sprites.

 

Placing a sprite within a tilemap is a pretty common behavior, but the way you deal with it isn’t immediately obvious in Phaser.  To understand what I am going on about it’s good to understand how layers of tilemaps work.  You may recall in our previous example, our tilemap was composed on three layers, one for the background, one for the midground (a word I apparently made up) and one for the foreground.  Basically the background is where you would put the stuff that happens behind the scene, things like clouds or the blue sky for example.  The midground is generally where the bulk of the stuff in your world would go.  The foreground then is the stuff drawn on top of the midground.

 

Quite often your playersprite will then be drawn in front of the foreground.  That said, what happens when your player is behind something in the world, such as say… a shrubbery?  How do we deal with this?  Well first, let’s visualize how the layers of a tilemap come together.

 

No better way than an image, right?

 

Layers

 

Think of each layer of tiles as sheets laid one over the next.  The top most layer obscuring the contents behind it, and that layer obscuring the contents behind it.

 

So, in Phaser, what determines draw order?  Simple, the order they were added in.  Change the order layers are drawn, you change what draws on top of what.  Know this then, how do we could we for example draw our character sprite between the foreground and midground layer so it appears behind the chandelier, like so:

 

 

Layers2

 

 

The answer is actually quite simple but possibly a bit confusing, let’s look at the code:

 

/// <reference path="phaser.d.ts"/>  class SimpleGame {      game: Phaser.Game;      map: Phaser.Tilemap;      player: Phaser.Sprite;            constructor() {          this.game = new Phaser.Game(640, 480, Phaser.AUTO, 'content', {              create: this.create, preload:              this.preload, render: this.render          });      }      preload() {          this.game.load.tilemap("ItsTheMap", "map.json", null, Phaser.Tilemap.TILED_JSON);          this.game.load.image("Tiles", "castle_0.png");          this.game.load.image("Decepticon", "decepticonLarge.png");      }      render() {        }      create() {          this.map = this.game.add.tilemap("ItsTheMap", 32, 32, 50, 20);          this.map.addTilesetImage("castle_0", "Tiles");            this.map.createLayer("Background").resizeWorld();          this.map.createLayer("Midground");          this.map.createLayer("Foreground");            this.player = new Phaser.Sprite(this.game,200,50,"Decepticon");          this.game.world.addAt(this.player, 2);      }  }    window.onload = () => {      var game = new SimpleGame();  };

 

And when we run it:

image

 

Note the order we called createLayer matches the order we want the layers drawn, from back to front.  After that we load our sprite then do something a bit different from what we’ve done in the past.  Previously we just added Sprites using this.game.add().  Now however we want a bit more control of where they are added.  This is why we created our Sprite instead using new, then explicitly added it to the world using addAt().  By adding it at index 2 ( the 3rd item ) this means we will be between the mid and foreground in the world list.

 

You should be aware however that this is incredibly fragile an approach.  You can only rely on layers being where they are because they are the only other objects we’ve created.  In a non-trivial example, you will dozens or hundreds of other objects in your scene, this may not be as easy to figure out.  There are of course alternatives.  One of the easiest, and possibly crudest options, would be too iterate through the world, find the index of layer you want to insert before and then insert like so:

 

        var index: number;          this.game.world.forEach((child) => {              index++;              if (child.layer.name = "Foreground")                  index = child.index;          }, this, false);          this.game.world.addAt(this.player, index);

 

I suppose I should point out this particular solution iterates through every item in the world, even if the first child is what you are looking for. Also, it doesn’t take into account the possibility that the given child doesn’t even exist, which is probably a bad idea. That all said, I think you get the gist of how this works.  As you may notice, the world variable is pretty important, as essentially it is the scene graph, the source of all things to be rendered.  At the end of the day though, it’s just a group that we covered earlier.

 

Ok… that was a really long way of saying the order you add things to the world is really important… let’s move on, collisions.

 

Quite often you are going to want to test to see if “stuff” (scientific term there… ) hits other “stuff”.  In this particular case, we want to see if our game sprite collides with the “floor”.  First lets take a look at adding physics to our game sprite.  I’ve used physics in past tutorials, but havent gone into detail how these things work… and I am not going to here either.  For now just think of it as the work of moon wizards.  I will cover physics at a later point, I promise!

 

So, back to enabling physics and gravity for game sprites.  Here’s how:

 

    create() {          // Enable the physics system in Phaser          this.game.physics.startSystem(Phaser.Physics.ARCADE);          this.map = this.game.add.tilemap("ItsTheMap", 32, 32, 50, 20);          this.map.addTilesetImage("castle_0", "Tiles");            this.map.createLayer("Background").resizeWorld();          var world = this.map.createLayer("Midground");          this.map.createLayer("Foreground");            this.player = new Phaser.Sprite(this.game, 200, 50, "Decepticon");          // Apply physics to our game sprite          this.game.physics.enable(this.player, Phaser.Physics.ARCADE);          // Turn on gravity.  10pixels / second along the y axis          this.player.body.gravity.y = 10;            this.game.world.addAt(this.player, 3);      }

 

The comments explain the basics of what’s going on, like I said, I will go into a bit more detail later.  Now if you run the code with the above changes the player sprite ( the Decepticon logo ) will slowly fall to the bottom of the screen, to infinity and beyond.

 

Now what we would prefer to see is the sprite stop when it collided with the tiles composing the floor.  This takes a bit of understanding of how tilemaps are actually stored as data.  Open up map.json and you will see a layer contains an array called data full of tiles that make up the layer, like so:

 

image

 

Each of those indices present a location within your sprite image.  Let’s take a look back in Tiled for a second.

 

image

 

You can see the ID of each tile by simply clicking it in the Tilesets window.  In this case we are going to want the ID of each red topped brick.  Specifically we want the ID of the tiles that make up these particular tiles

 

image

 

In my particular case, the IDs were 32,33,34,35 and 36.  Now that we know the IDs of the tiles we want to collide with, lets set that up.  Code time!

 

/// <reference path="phaser.d.ts"/>  class SimpleGame {      game: Phaser.Game;      map: Phaser.Tilemap;      layer: Phaser.TilemapLayer;      player: Phaser.Sprite;            constructor() {          this.game = new Phaser.Game(640, 480, Phaser.AUTO, 'content', {              create: this.create, preload:              this.preload,update:this.update, render: this.render          });      }      preload() {          this.game.load.tilemap("ItsTheMap", "map.json", null, Phaser.Tilemap.TILED_JSON);          this.game.load.image("Tiles", "castle_0.png");          this.game.load.image("Decepticon", "decepticonLarge.png");      }      update() {          // You actually need to perform the collision test between the map and player sprite          this.game.physics.arcade.collide(this.player, this.layer);      }      render() {          // Display the outline of the physics body          this.game.debug.body(this.player);      }      create() {          // Enable the physics system in Phaser          this.game.physics.startSystem(Phaser.Physics.ARCADE);          this.map = this.game.add.tilemap("ItsTheMap", 32, 32, 50, 20);          this.map.addTilesetImage("castle_0", "Tiles");            this.map.createLayer("Background").resizeWorld();          this.layer = this.map.createLayer("Midground");          this.map.createLayer("Foreground");            this.player = new Phaser.Sprite(this.game, 200, 40, "Decepticon");          // Apply physics to our game sprite          this.game.physics.enable(this.player, Phaser.Physics.ARCADE);          // Turn on gravity.  10pixels / second along the y axis          this.player.body.gravity.y = 10;          this.player.body.collideWorldBounds = true;            // Now setup the tiles in our midground layer for collisions          // When using multiple layers it is critical you specify the layer the collisions occur on!          this.map.setCollisionBetween(32, 36,true,this.layer.index,true);                      this.game.world.addAt(this.player, 2);            // Add a keyboard handler on 'R' pressed the resets the sprite to it's starting position          this.game.input.keyboard.addKey(Phaser.Keyboard.R).onUp.add(() => {              this.player.position.set(200, 40);          });      }  }    window.onload = () => {      var game = new SimpleGame();  };

 

 

Here is the code running.

 

 

Click in the IFrame and press R to reset the sprite to it’s starting position.

 

Most of the important bits are commented in the source code, but a few things are worth noting.  The most important is how you add collision tests to individual tiles within the world.  As mentioned earlier, you need the tile ID of the tile within the layer.  You then use setCollision methods to mark which tiles perform collisions.  In this example we used setCollisionBetween() which allows you to specify a range of tiles to collide with.  There is also a version that takes an individual tile ID as well as one that takes an array.  The critical part of this call is you have to provide the layer where the collision takes place.  Also of importance is the call to physics.arcade.collide() which tells Phaser to check for a collision between that layer and your player sprite.

 

One other thing you may notice is in render(), I call game.debug.body(this.player).  This causes the renderer to draw a bounding box around the physics shape of our sprite.  This is important because it exposes a very common bug that I thought I would address.  Notice when you run the applicaion, once gravity is done, our image is resting above the ground like this:

 

image

 

This is because our source image has some empty pixels.  The physics bounding volume is going to be around the entire sprite, not just drawn pixels.  If you are using physics, be sure your image is cropped as much as possible!

 

So that illustrates how to insert a sprite between tilemap layers and how to perform collisions against tiles.  That should be the majority of what you need to get started using tilemaps.

 

Before we continue, I should answer a commonly asked question.  What about isometric maps?  Yes, Tiled can export isometric tilemaps, but currently Phaser does not support them.  There are some third party code samples to load them, but nothing in the core as of yet.

 

Programming


Scroll to Top