Creating an isometric world with Phaser.js, using the isometric plugin
PUBLISHED
Introduction
Have you ever wondered how to create an isometric world for your game? Is it difficult? What needs to be done in order to have a world like, for example, in the popular Zelda game series? The answer is easy. You can always write your own isometric game engine. But is it really necessary in times when the web is just bursting with web frameworks for HTML5 games?
No it isn’t. That is why in this article we present you the isometric plug-in for the Phaser game engine, which was earlier described in this article. We also show you how to use the plug-in with the Phaser.js library to create a simple wild west like world with a hero, some items to collect and an exit point. And it is covered in the sample application PhaserIso.wgt (fig. 1) attached to this article, which was developed using the Tizen SDK 2.3 (rev2).
Figure 1 – The PhaserIso.wgt sample application using Phaser.js with the Isometric plug-in
You will also learn how to setup the Isometric plug-in in Phaser.js. Create tiles for the world and obstacles. We describe also how to check collisions using the Phaser physics system and overlapping with collectible items.
As this article is based on Phaser.js and there is an existing article on developer.tizen.org about setting up Phaser.js. Then we will focus here only on the Isometric plug-in setup and features.
The Isometric plug-in setup in Phaser.js
The first thing to do is, as always, to import the library. In our case the Isometric plug-in into our project into the HTML document. Remember to put the Isometric plug-in import line after the Phaser.js import line.
[…] <script src="js/phaser.min.js"></script> <script src="js/phaser-plugin-isometric.min.js"></script> […]
Having done that, we can start writing our setup code in JavaScript. As you can see below, in the preload function of the Phaser game engine we have put the game.plugins.add() function which is used to actually import the isometric system into the Phaser game engine. As you can see as a parameter we pass a new instance of the Isometric engine, which in turn takes our Phaser game object as a parameter. Next we are setting the bounds of the world by using the game.world.setBounds() method. The first two parameters are the starting points of the world (x and y), so we want to keep them at the 0 value. The two last parameters are the width and height of the world. Here we put 1024 and 2048. Also we want to have some physics in our game, for example moving obstacles. That is why in the next line we are enabling the physics system. It is quite self-explanatory. The last line may not look like important, but it is. Setting the game.iso.achor.setTo() function gives us the ability to put the center of our isometric world in a specific point of the screen in relation to the Phaser camera.
[…] // Add the Isometric plug-in to Phaser game.plugins.add(new Phaser.Plugin.Isometric(game)); // Set the world size game.world.setBounds(0, 0, 2048, 1024); // Start the physical system game.physics.startSystem(Phaser.Plugin.Isometric.ISOARCADE); // set the middle of the world in the middle of the screen game.iso.anchor.setTo(0.5, 0); […]
The tile assets
In the preload section of the Phaser game engine we will also have to load up the graphical representations of the tiles in our isometric world. As in a standard Phaser.js setup we have two types of assets - animated and static assets. You upload them to the game engine like shown in the code snippet below. The most important are the internal names of the loaded assets – “cactus1”, “cactus2”, “rock”, ”characterAnim”, etc. Later they will be used to assign assets to their isometric representations.
[…] game.load.image('cactus1', 'images/tiles/obstacle1.png'); game.load.image('cactus2', 'images/tiles/obstacle2.png'); game.load.image('rock', 'images/tiles/obstacle3.png'); game.load.spritesheet('characterAnim', 'images/tiles/characterAnim.png', 70, 74); […]
That is all for the preload function of the Phaser game engine. Now we will move to the create function of the Phaser game engine. In this section as you probably remember from the first Phaser article on developer.tizen.org, we create our game scene. Nothing changes in that aspect here. We will just use the objects from the isometric library, instead of standard Phaser.js objects. So here it goes. In the snippet below, we create a sample object with the Isometric plug-in. But first you need to create a group for specific tile types. You may ask why, but the answer is quite simple. In an isometric world you have layers. The objects on the foreground can obscure the objects in the background. Groups in this scenario are used to differentiate groups of tiles, creating kind of layers, so you can have control and decide which objects are in front of the others. Beneath we create few groups and show how to put some tiles in them. Please note that the groups are stacking up, which means if you create first the floorGroup and after that you create a grassGroup then the first one will be under the second one. Also remember that this part should be placed in the create function of Phaser.
[…] // create groups for different tiles floorGroup = game.add.group(); itemGroup = game.add.group(); grassGroup = game.add.group(); obstacleGroup = game.add.group(); floorTile = game.add.isoSprite(xt, yt, 0, 'tile', 0, floorGroup); floorTile.anchor.set(0.5); grassTile = game.add.isoSprite(xt, yt, 0, 'grass1', 0, grassGroup); grassTile.anchor.set(0.5); cactus1 = game.add.isoSprite(xt, yt, 0, 'cactus1', 0, obstacleGroup); cactus1.anchor.set(0.5); […]
As for the isometric objects as you can see from the code above, for each tile we create an isoSprite which is our graphic tile representation in the isometric world. It takes several parameters while instantiating. The first three are the x, y and z positions of the isoSprite in the game world. The third one tells us which earlier loaded asset will be shown as the representation of the isoSprite. Here you just have to type in one of the names of the assets loaded in the preload function. The fourth one tells the isometric engine on which frame, if the asset is a spritesheet, the isoSprites’ animation should be set to. Finally, the last one tells us to which group should we assign our isoSprite. Here just choose the variable name of a chosen group.
You probably are wondering why we are setting for every isoSprite the anchor value to 0.5. This is very simple it just puts the anchor point in the middle of the x and y axis of the isoSprite. It is easier to place tiles with anchor point set to 0.5.
Also please note that for this tutorial we are only focusing on the essentials of creating an isometric world. We have omitted the creation of tile maps using for example arrays or JSONs. The way how you handle the data storage for your tile map layout really is up to you. It can be hardcoded, stored on a server or created randomly on the fly. This is totally up to you how your game will store your levels. In the example application provided along with this article we put the desert, rocks and cactuses with for loops, without using any layout data. As for the grass and collectibles, they have been put totally randomly on the isometric level.
Below is a very easy example of populating randomly our level with three grass types. The code is self-explanatory, thus we won’t make any special remarks about it. Also please note that you can always run the sample application attached to this article and play around instantly with the isometric world creation by changing some of the parameters and / or assets in the level generation for loops.
[…] // create the grass tiles randomly var grassTile; for (var xt = 1024; xt > 0; xt -= 35) { for (var yt = 1024; yt > 0; yt -= 35) { var rnd = rndNum(20); if (rnd == 0) { grassTile = game.add.isoSprite(xt, yt, 0, 'grass1', 0, grassGroup); grassTile.anchor.set(0.5); } else if (rnd == 1) { grassTile = game.add.isoSprite(xt, yt, 0, 'grass2', 0, grassGroup); grassTile.anchor.set(0.5); } else if (rnd == 2) { grassTile = game.add.isoSprite(xt, yt, 0, 'grass3', 0, grassGroup); grassTile.anchor.set(0.5); } } } […]
Setting up the physics
Earlier in the article we wrote about physics in the Isometric engine. We even turned on the isoArcade engine. So, how one implements the physical behavior in our isometric world?
Let’s look at the following example.
[…] rock = game.add.isoSprite(80, 80, 0, 'rock', 0, obstacleGroup); rock.anchor.set(0.5); // Let the physics engine do its job on this tile type game.physics.isoArcade.enable(rock); // This will prevent our physic bodies from going out of the screen rock.body.collideWorldBounds = true; // set the physics bounce amount on each axis (X, Y, Z) rock.body.bounce.set(0.2, 0.2, 0); // set the slow down rate on each axis (X, Y, Z) rock.body.drag.set(100, 100, 0); […]
As you may have already deducted we are creating a rock isoSprite. Then we enable physics on that isoSprite. Next, we have to set the collideWorldBounds boolean value of the rocks’ body to true. This will prevent the rock from falling through the floor towards eternity. Then we have some nice body properties like bounce and drag. Both can be set to give more natural effect on the rock obstacle object. They are very easy to understand. The bounce parameter tells the isometric engine how much the body should bounce if it collides with another colliding body. As for the drag parameter, it tells how much friction the body which has been pushed will have.
Of course sometimes you may want to have some bodies which are immovable. Like the cactuses in our example application or some kind of walls. If you’re wondering how to achieve that, then you just have to set the immovable property of the body of a desired object to true. Then you are sure that this obstacle will be not moved if it collides with any other physical bodies in the Phaser game engine.
The one important thing to remember after setting up the isoSprites and assigning them various physical behaviors is to actually check collisions using the isoArcade engine on a specific group, just like below. But remember that collision checking should be performed in the udpate function of the Phaser game engine. So the code you need might look something similar to the one below.
[…] game.physics.isoArcade.collide(obstacleGroup); […]
In this case the isoArcade physical engine will check for collision between all objects inserted into the obstacle group.
The last thing to do in the aspect of physics is to invoke the game.iso.topologicalSort() in the update function of the Phaser game engine. And as the parameter you need to use the group with colliding objects. In the case of our example application it is the obstacleGroup. Then you can be sure that the objects which should be behind other objects will be there and those which you want to be in front of other objects will also be in the right place.
[…] game.iso.topologicalSort(obstacleGroup); […]
Collectibles and overlapping
As in any game you would probably want to place some collectible items (fig. 2) in it or trigger points for some in game events. This can be also easily done utilizing the overlap check. Please note that the overlap checking is something different than the standard sprite overlap checking. The overlap checking in the Isometric engine checks if two isoSprites overlap in the isometric space, which means that until the moment they share the same isometric space, they are not overlapping. To be more precise, when you look at traditional 2D overlapping then it happens when pixels of one sprite share the same coordinate space of the pixels of the other sprite. In the Isometric engine objects can stand one in front of the other. So isoSprites overlap only in the moment when they occupy the same isometric pixel space.
Figure 2 – Collectible items - the gun and a gold nugget
Let’s take a closer look on the overlap function. Same as the collide function it needs to be put into the update function of the Phaser game. It takes three parameters. First two are the isoSprites we want to check for overlapping. The third parameter is the callback function to be run after checking the overlap. Let’s look at the example below.
[…] game.physics.isoArcade.overlap(marker1, player ,function(e){ e.destroy(); addItem(); }); […]
As you can see we have taken an example directly from the sample application attached to this article. What this overlap check does, it tests if the player and marker1 are overlapping. Please note that the object in the anonymous function is for real the marker1 isoSprite. So, in the anonymous function, after the player collects the marker1, we want the marker1 to disappear. That is why we invoke on the e object the destroy() function, like on any other isoSprite. Then with the addItem() function we change the number of collected items in the sample application.
Summary
In this article we have showed you how to setup the Isometric plug-in for Phaser.js. We have also described what is needed to create proper isometric objects. There have been explained methods on creating physical interactions between elements, like pushing and moving the isometric elements. Furthermore we showed how to create solid walls and how to handle overlapping for collectible items.
We hope that this article has broadened your knowledge about isometric game making and that it will encourage you to create isometric games. Also we encourage you to take a look into the sample application attached to this article and play around with the code inside. This is the best way to learn more in practice.
For more in depth information about the Isometric plug-in please visit the official documentation.