Creating an Isometric View in Phaser 3

Taylor Nodell
6 min readAug 27, 2019

Edit: Phaser 3.50 now supports isometric tilemaps. You should probably use that.

Full Repo: https://github.com/nodes777/flower-game-phaser3
Demo: http://www.spacegarden.xyz/

I’ve been working on a casual flower genetics game, in the Phaser 3 engine. I’d always imagined it having a psuedo 3D effect, and after building it to this point, I wanted to try to finally get the visuals in a workable place.

First we’ll make sure we have some assets that work in the isometric perspective. I’m a Patreon backer and big fan of SpriteStack. So that’ll be my tool of choice. If you’re interested in how this art technique works, I recommend reading this blog post by like100Bears. Basically we draw layers as 2D images. When we move in the z direction, we create a new layer and offset that in the y direction by one pixel. All of those layers stacked on top of each other, offset by one pixel give the illusion of depth.

Programmer art

I exported 3 objects from my project, a grass tile, a flower stem, and a flower head. Each has a png and a json file that we’ll be using. The grass tiles will be used to fill in the world, and I separate the stem from the flower so I can paint the flower any color I want, without affecting the stem’s color. I’m doing camera angle 60 (For the typical 120 ° isometric angle), 16 angles, 2x pixels.

Now we need to figure out how to include these images into the game world in a way that preserves the isometric view shown in SpriteStack, and behaves as expected in that view. The actual math for changing from Cartesian (standard 2D) coordinates to isometric coordinates is relatively simple, but I’ll be using a plugin to speed things along. Here’s a primer for those interested in the how this works.

Phaser 3 is still a little new to have a well documented fully featured isometric plugin, but luckily we have what we need. sebashwa forked the Phaser 2 isometric plugin and has a number of those features working for a Phaser 3 version. The interaction example on their github page, is close to what I’m looking for.

The interaction demo for the isometric plugin

Demos: https://sebashwa.github.io/phaser3-plugin-isometric/

Now to code! First step is to install the plugin

npm i phaser3-plugin-isometric --save

I’m adding this into my already existing project, so I’ve already got an index.js that kicks off the whole app. That calls my scene file, which is where our first change occurs. We’re creating a configuration object, that’s just telling Phaser that we’ll be using the isometric plugin.

//scene.js
import Phaser from "phaser";
import { preload } from "./preload";
import { create } from "./create";
import { update } from "./update";
class playGame extends Phaser.Scene {
constructor() {
const sceneConfig = {
key: "IsoInteractionExample",
mapAdd: { isoPlugin: "iso" }
};
super(sceneConfig);
}
preload() {
return preload.call(this);
}
create() {
return create.call(this);
}
update() {
return update.call(this);
}
}
export default playGame;

Next we’ll jump over to the preload function. We’re importing the plugin, and loading it with the same sceneKey as the one used in scene.js. For the images, I’m importing the json file to use as information to help slice my sprite sheet, and start with the correct frame.

//preload.js
import IsoPlugin from "phaser3-plugin-isometric";
import grassTileData from "../assets/spritestack/grassTile.json";
import flowerHeadData from "../assets/spritestack/flowerHead.json";
import stemData from "../assets/spritestack/stem.json";
export function preload() {
this.load.scenePlugin({
key: "IsoPlugin",
url: IsoPlugin,
sceneKey: "iso"
});
// load 3d assets
this.load.spritesheet("grassTile", "src/assets/spritestack/grassTile.png", {
frameWidth: grassTileData.width, // from json
frameHeight: grassTileData.height, // from json
startFrame: 0 // only using this frame, this could be a this.load.image
});
...

At this point we have our isometric assets ready to use, so lets add them in the scene. My create.js immediately calls an init function which provides the intial calls to setting up the scene. I pass in this as the parameter to retain context to the game. I’m grouping the grass tiles so I can create and refer to their behavior easily. I also make the first call to the iso plugin and place the camera, setting it center horizontal and slightly above center vertical.

//init.js
import { store } from "../index.js";
import { add3dFlower } from "./isometric/add3dFlower";
import { addTiles } from "./isometric/addTiles";

export function init(game) {
// create tile group
game.isoTiles = game.add.group();
// set camera placement
game.iso.projector.origin.setTo(0.5, 0.3);
// add Tiles
addTiles(game);
game.flowersOnScreen = [];
/* First Flower */
const storeFlowers = store.getState().flowers;
add3dFlower(storeFlowers.byId.flower1, "flower1", game);
game.flowerToFlyTo = game.flowersOnScreen[0];
}

Now I add the tiles with my addTiles function, passing in the game context again. I import the data about the grass tile to get the size of the tiles, then I adjust to get the fit right so that the tiles are spaced closely together. I create a nested loop which spaces each tile by that defined height.

//addTiles.js
import grassTileData from "../../assets/spritestack/grassTile.json";
export const addTiles = game => {
let tile;
const height = grassTileData.height - 2;
for (let xx = 0; xx < 256; xx += height) {
for (let yy = 0; yy < 256; yy += height) {
tile = game.add.isoSprite(xx, yy, 0, "grassTile", game.isoTiles);
tile.setInteractive();
tile.on("pointerover", function() {
this.setTint(0x86bfda);
this.isoZ += 5;
console.log(this);
if (this.flowerSprite) {
this.flowerSprite.isoZ += 5;
this.flowerSprite.stem.isoZ += 5;
}
});
tile.on("pointerout", function() {
this.clearTint();
this.isoZ -= 5;
if (this.flowerSprite) {
this.flowerSprite.isoZ -= 5;
this.flowerSprite.stem.isoZ -= 5;
}
});
}
}
};

I’m adding the tile as an isoSprite, which is the plugin’s extension for a regular sprite. Then I set it as interactive so I can provide some mouse over events. Here we can see our first interaction with the z axis. We raise the sprite up a bit when the mouse is over it. Additionally, if it has a flowerSprite, raise that too.

Isometric grass tiles with mouse over events

Where did that flowerSprite come from? We gotta make it. Back in our init, there’s another function that creates our flowers, add3dFlower.js. The first difference between adding an isosprite and regular sprite is in determining the position. I’m grabbing a random tile from the tile group we created in the previous step, and using that tile’s isometric position to place my flowers on top. The z position is 2 for the flowerhead, so that it can stay on top of the stem, which has a z position of 1, which stays on top of the grass with a z position of 0.

import { determineFlowerShape } from "../../determinants/determineFlowerShape";
import { determineStem } from "../../determinants/determineStem.js";
import { getHexColor } from "../../determinants/determineColor";
export function add3dFlower(currFlower, currFlowerId, game) {
const phenotype = currFlower.phenotype;
// determine position
const tile = game.isoTiles.children.entries[22];
const posX = tile._isoPosition.x;
const posY = tile._isoPosition.y;
// set position and shape
let newFlowerSprite = game.add.isoSprite(
posX,
posY,
2,
"flower3d" //flowerShape
);
// setFrame because ^ isoSprite doesn't set frame correctly
newFlowerSprite.setFrame(0);
// add stem
newFlowerSprite.stem = game.add.isoSprite(
newFlowerSprite._isoPosition.x,
newFlowerSprite._isoPosition.y,
1,
"straightStem3d" // stem Shape
);
newFlowerSprite.stem.setFrame(0);
// set color
newFlowerSprite.setTint(getHexColor(phenotype.color));
//add flower reference for the tile
tile.flowerSprite = newFlowerSprite;
// add the flower to the array of onscreen flowers for bee to fly to
game.flowersOnScreen.push(newFlowerSprite);
}

For whatever reason, I get an error when trying to use the 5th argument for creating an isoSprite. It should refer to setting the initial frame, but I get an error that h.add is not a function, despite this behavior being described in the docs. I just do it immediately after creating the sprite with setFrame(0). The process is repeated for the stem, and then I associate this sprite with the tile it was created on, so that the flower sprite is lifted up on mouse hover as well.

The finished product of the tutorial. An isometric view of a voxel flower on a grass field.

And with that we’ve got a little isometric scene started. Certainly a couple places to refactor and smooth out, but it’s looking much better that what I had previously. Let me know if you have any comments or suggestions or can sort out that bug above. Thanks for reading!

Before and after

Full Repo: https://github.com/nodes777/flower-game-phaser3

--

--

Taylor Nodell

Developer. Musician. Naturalist. Traveler. In any order. @tayloredtotaylor