Tiled Generated Map with Phaser 3
This is a series of posts that attempt to create more beginner friendly tutorials for phaser 3, inspired by Modular Game Worlds in Phaser 3.
By the end of this post, you will have something like this.
Download the Assets
You can download the assets you need from the codepen below. Check the folder named assets
in the root directory.
The Basics
Read Modular Game Worlds in Phaser 3 (Tilemaps #1) — Static Maps to learn the basics of Tilemap API. The article is written by the guy who developed the API.
The TypeScript Approach
In this section, you will learn how to achieve a similar result from the Modular game tutorial using TypeScript. I will assume that you are using the Phaser 3 TypeScript template from the previous post. First, create a TilemapScene
class.
You will pack all the tilemap related logic into this class. A little bit of recap on the scene’s life cycle: When the game starts, Phaser calls the constructors of the scenes in the scene list configured in main.ts
. init()
is called every time the scene starts, which is a good place to initialize the member variables. Then preload()
is called to load all the assets needed. Then create()
is called to create the game objects to render. Finally, the scene enters the game loop, where update()
is called every frame to update the states of the game objects.
First we need to tell TilemapScene
where to load the tilemap json file and tileset image file.
tilemapKey
is defined such that the tilemap json file is located at <tilemapKey>.json
. Similarly, tilesetKey
is defined such that the tileset image file is located at <tilesetKey>.png
. You will see why you want to do this in a second. In this case, you should have your tilemap json at assets/map.json
and tileset image at assets/tiles.png
.
Now preload the tilemap and tileset.
The first argument of this.load.image()
is the key for the image to load. You will use this key to refer to the image later in the program. The second argument is the file path to the asset. By default, it is <key>.png
, which is why we defined the keys the way we did earlier. Throwing away a little bit of flexibility, this makes the code short and clean. Similarly for this.load.tilemapTiledJSON()
, the default second argument is <key>.json
.
Next, create the layers.
Note the layer names BottomLayer
, MiddleLayer
, andTopLayer
must match how you named them in Tiled. By default, Tiled uses the file name (excluding the extension) as the tileset name when you import a tileset to a tilemap. So if you import a tileset located at path/to/tileset/foo.png
, then the default tileset name is foo
. getDefaultTilesetName()
is just a helper function that extracts the file name from file path. Now if you refresh your browser, you should see the tilemap!
Animated Tile
If you look at the tileset, you see there is a tile animation for the lava.
To learn how to place an animated tile, check this tutorial out. Unfortunately, Phaser 3’s Tilemap API does not support tile animation, so we need to create our own. The good news is it’s not that hard if we assume the duration of the animation is the same across all the frames. The animation data is stored in tileset.tileData
like this.
tileData
is a key-value pairs where the key is the tileid
and the value is the animation data. The screenshot above says the tile with id 9 has animation, where the first frame is a tile with id 9, the second frame is tile with id 10, the third frame is a tile with id 11, and each frame should last 100ms.
To change the tile to render, it’s as simple as setting the id
property of the tile object. The question is when to change it. Recall a scene’s update()
method takes two arguments — time
and delta
. time
is current time since the game starts and delta
is the elapsed time since previous frame. Here’s the idea: We can make use of delta
and create a variable that loops from 0 ms to 300 ms. If the value is between 0 ms and 100 ms, we render the first frame; If the value is between 100 ms and 200 ms, we render the second frame; and so on. We create a AnimatedTile
class that implements this logic.
tileset.firstgid
is the first tile id that belongs the tileset. firstgid
of the first tileset is always 1 while the first tile id in Tiled is always 0. That’s why the tile id is offset by firstgid
in the code above.
In TilemapScene
, we are going to check every tile and see if the tile has animation data associated with it.
Notice we have changed the layers from static layer to dynamic layer because we are changing the tile id. Finally, update the animatable tiles every frame.
Now you should see the lava is animating!
Adding a Player (Optional)
Let’s create a player that explores the fantastic world we just created. First, we need to preload the asset.
Just like all other loading methods, the first argument of this.load.atlas
is the key of the asset. The second argument is the texture whose default value is <key>.png
. The third argument is the atlas json file of the texture whose default value is <key>.json
. So make sure you have assets/elf_f.png
and assets/elf_f.json
.
Here’s the player class.
You are probably already familiar with what’s going on in these code if you have followed the Phaser 3 Official Tutorial.
Insert the player right after you created the middle layer, but before the top layer. This makes sure top layer always appears on top of the player.
this.physics.add.collider()
adds the collision detection between the first argument and the second argument, making sure they are separated when they collide.
Finally add these configuration code at the end of the create()
function.
Set the bounds of the world and the camera so that the player can never go off the screen. If your game becomes blurry, you need to configure Phaser and disable antialiasing.
Add this code snippet to the game config in main.ts
.
And that is it! Now you can start adding more features to your game.
Next Up
In the next part, you will learn how to do scene transition. Cheers!