Barbarian Meets Coding
barbarianmeetscoding

WebDev, UX & a Pinch of Fantasy

17 minutes readgamedev

Wiki: Making HTML5 Games With Phaser.js

What is Phaser.js?

Phaser.js is a game development framework that lets you build cross platform HTML5 games using JavaScript. It is one of the most mature game dev frameworks available and comes with a ton of built-in functionality and helpers to make creating your own games a breeze.

There’s two actively maintained versions of Phaser: Phaser CE and Phaser 3. Phaser CE or community edition is a continuation of Phaser 2 which is now maintained by the open source community from v2.7 onwards. Phaser 3 is the latest version of Phaser being actively developed by the original Phaser team. Phaser CE vs Phaser 3? Which one to choose? I have had a hard time finding good learning resources for either versions of Phaser, but they are more readily available for Phaser CE than Phaser 3.

How is a Game Organized in Phaser?

This will give you an idea of the different elements that compose a game in Phaser:

  • Game: Phaser provides a Game object that represents the game itself and gives you access to different game-wide events, properties and utility functions.
  • Game States: A game is composed of different scenes (like the start scene, a map scene, the game scene, credits scene, etc) which in phaser are called Game States. This act as a natural division of the different parts of a game and a way to group game objects and game mechanics.
  • Assets Loader: Let’s you load assets within the game like images, sprite sheets, tilemaps, sound and music.
  • Game Objects: Game objects are any type of static or interactive object present within a game: background images, sprites like a game character or a monster, tilemaps, etc
  • Input Handling: Phaser provides helper classes for input handling via keyboard, mouse or gamepad. It even comes with built-in support for building HUDs and handling HUD user input.
  • Camera: Phaser comes with a built-in camera and useful helpers that let you define a camera that follows a player.
  • Physics: Phaser provides several physics engines that you can use to implement common game mechanics like movement, jumping, falling (through gravity), collisions, etc

In general, a game will be composed of different game scenes (Game states) where we will render different game objects (images, backgrounds, sprites, text, etc). The user will be able to interact with these game objects via an input that we define (like a keyboard) and their actions will make the game objects move and interact with other game objects in a world powered by one of the physics engine.

Getting Started

You can start developing a game in Phaser in many ways. For instance:

Game States

Game states have three methods where you do stuff:

  • preload where you preload your resources for that game scene
  • create where you create the game objects which will take part in that scene
  • update where you update the state of the game objects of the scene as the game progresses. This is the game loop for this particular scene.

There’s also a render method where sprites and such are rendered onto the screen. You don’t typically use drawing primitives in phaser (not as far as I have seen in tutorials and examples), but you can use this method to render debug information on top of the screen.

Adding States to the Game

Add a new state to the game by using game.state.add and associating a key to a state object:

game.state.add(stateKey, state)

State Transitions

Use the game.start method to jump into a new state:

game.start(stateKey)

Loading Resources

Resources are typically loaded in the preload method of a GameState object using phaser’s asset loader. For instance:

// load image
game.load.image(key, path)
// load spritesheet
game.load.spritesheet(key, path, width, height, opt numberOfCells)
// load tilemap
// where tilemapFormat can be for instance: Phaser.Tilemap.TILED_JSON
game.load.tilemap(key, path, null, tilemapFormat)
game.load.tilemap(key, null, tilemapObject, tilemapFormat)
// load sounds or music
game.load.audio(key, path)

Creating Game Objects

Creating Images

const image = game.add.image(x, y, key)
// if it is associated to a spritesheet
// it will show the first frame in the spritesheet

If the image is connected to a spritesheet (the key refers to a spritesheet that was loaded via game.load.spritesheet), then you can use the frame property to change the image being rendered in the screen:

// show second frame in spritesheet
image.frame = 2

Creating Text

// you can create text like this
const text = game.add.text(x, y, someText, textOptions)
// The text options can also be set via properties
text.font = 'bold 50px Arial' // style, size and font family.
text.fill = '#333333' // hex color
text.stroke = '#333333' // hex color
text.strokeThickness = 1
text.fontSize = 24 // pixels
text.align = 'center' // text-align
text.text = 'new text' // text

Creating Sprites

const sprite = game.add.sprite(x, y, key)

You can add animations to sprites by associating them with a spritesheet (as opopposed to a single image) and using the animations API as follows:

// You can add as many animations as you want using this method
sprite.animations.add(animationName, frames, framesPerSecond, isLoop)

This can then be triggered like this:

sprite.animations.play(animationName)

Here’s a complete example:

const ghost = game.add.sprite(0, 0, 'ghost')
ghost.animations.add('walk', [0, 1, 2, 3], 10, true)
ghost.animations.play('walk')

Working With Sprites

Here are some useful sprite methods:

// bring to top (z-index)
sprite.bringToTop()
// make sprite check world bounds. When they go out they will trigger an event
sprite.checkWorldBounds = true
// destroy sprite when it leaves the world bounds
// it works great in combination with the above
sprite.outOfBoundsKill = true
// mark sprite as killed (disabled, inactive)
sprite.kill()
// set sprite scale
sprite.scale.x = 0.5
sprite.scale.y = 0.5
// flip sprite horizontally
sprite.scale.x = -1
// flip sprite vertically
sprite.scale.y = -1

Creating TileSprites

A TileSprite is a Sprite that has a repeating texture. The texture can be scrolled and scaled and will automatically wrap on the edges as it does so. It is typically used for scrolling backgrounds within a game (like tile maps). You can create one like this:

// the key will reference an associated image
const tileSprite = game.add.tileSprite(x, y, width, height, key)

You can make a tilesprite scroll automatically by using the following function:

tileSprite.autoScroll(x, y)

Sets this TileSprite to automatically scroll in the given direction until stopped via TileSprite.stopScroll(). The scroll speed is specified in pixels per second. A negative x value will scroll to the left. A positive x value will scroll to the right. A negative y value will scroll up. A positive y value will scroll down.

Creating TileMaps

After having loaded a tilemap tiles image and data on preload:

// load tileset
game.load.image(tilesetKey, path)
// load tilemap
// where tilemapFormat can be for instance: Phaser.Tilemap.TILED_JSON
game.load.tilemap(key, path, null, tilemapFormat)
game.load.tilemap(key, null, tilemapObject, tilemapFormat)

We create a tilemap game object like this:

const map = game.add.tilemap(mapName)
map.addTilesetImage(tilesetKey)
// the layer name comes from the tilemap object
const layer = map.createLayer(layerName)

Working With Tilemaps

These are some of the things you can do with tilemaps:

// resize world based on layer dimensions
layer.resizeWorld()

// Make sure that game objects can collide with specific tiles:
// - set up which tilesets are enabled for collisions
// - enable collisions between game objects and layer
map.setCollisionBetween(minFrame, maxFrame)
game.physics.arcade.collide(gameObject, layer)

// be notified when a game object collides with a specific tile
// the callback is of type f(sprite, tile)
// for instance, you may wan to know when a character comes to a door
// within the game
map.setTileIndexCallback(index, callback, context)

// Removing a tile
map.removeTile(x, y, layer)

Creating Sounds and Music

// create sound game object
const sound = game.add.audio(key)
// set volume
sound.volume = 0.7
// loop sound (useful for the game background music)
sound.loop = true
// play sound
// it'll play the sound every time we call the play method
sound.play()
// stop sound
sound.stop()

Grouping Game Objects into Group

Phaser allows you to group game objects into groups so that you can operate on similar or related game objects in a simple and unified way. To create a group:

// create group
const group = game.add.group()

// add items to group
group.add(sprite)
group.add(anotherSprite)

// then interact with all items via group
// these changes are applied to every element within the group
group.setAll(key, value)

// The group itself also has properties that may affect the children
// for instance, x and y represent group bounds. Changing the group bounds
// will indirectly change the position of the children as the final position
// of the children will be offset by the group position
group.x = 100
group.y = 200

setAll: Quickly set the same property across all children of this group to a new value.

This call doesn’t descend down children, so if you have a Group inside of this group, the property will be set on the group but not its children. If you need that ability please see Group.setAllChildren.

The operation parameter controls how the new value is assigned to the property, from simple replacement to addition and multiplication.

Alternatively, you can use a group object as a factory for multiple objects by using the createMultiple method. That is, instead of creating objects and adding them to a group, you create a group and use createMultiple to create all of them at once:

const group = game.add.group()
group.createMultiple(number, key)
// then set properties for all of them

This is helpful when you want to use a group of game objects as a pool of objects that can be reused. As a pool, dead objects become available for reuse and can be added to the game again with new properties. This is a common game development technique to avoid GC thrashing (the creation and cleanup of many game objects). These are some helpful functions when using a group as an object pool:

// get first non active object that can be reused
const gameObject = group.getFirstDead()
// reset properties of object and position it in (x,y)
gameObject.reset(x, y)
// set it as active again
gameObject.enabled = true
// add more properties as you want

Physics

Phaser comes with several physics engine. The most basic one is called Arcade physics engine and you can start using it right away by enabling it like so:

game.physics.startSystem(Phaser.Physics.Arcade)

After that, we need to make the physics system aware of the game objects which will be affected by physics. We can do that using the following method:

game.physics.arcade.enable(gameObject)
// alternatively
game.physics.arcade.enable([gameObject1, gameObject2, etc])

After we have enabled physics for a given game object we can interact with its physics through its body property like this:

// Setting the speed of an object
sprite.body.velocity.setTo(x, y)
// Setting the gravity that affects an object
sprite.body.gravity.y = 100
// Setting an object as immovable
// its velocity is not affected by collisions with other objects
sprite.body.immovable = true
// Setting a bounce when an object collides with something else
sprite.body.bounce.set(0.2)
// Enable checking when the object reaches the bounds of the world
sprite.body.collideWorldBounds = true
// Check whether the object is on top of something
sprite.body.onFloor()
// Check whether the object collided left or right
sprite.body.blocked.left
sprite.body.blocked.right

The Arcade physics game engine comes with a plethora of helper functions that help you move objects around. For instance:

// moteToXY: move object to X,Y coordinates at given speed
// returns rotation of the game object so that we can make it point towards the
// target if that's what we want
const rotation = game.physics.arcade.moveToXY(
  sprite,
  destinationX,
  destinationY,
  speed
)
sprite.rotation = rotation

You can check whether two objects (with physics enabled) collide in different ways. For instance, to check collisions and keep objects separate you can use the collide method:

game.physics.arcade.collide(
  obj1,
  obj2,
  collideCallback,
  processCallback,
  context
)

An optional processCallback can be provided. If given this function will be called when two sprites are found to be colliding. It is called before any separation takes place, giving you the chance to perform additional checks. If the function returns true then the collision and separation is carried out. If it returns false it is skipped.

The collideCallback is an optional function that is only called if two sprites collide. If a processCallback has been set then it needs to return true for collideCallback to be called.

To check collisions but not separate objects you use overlap:

game.physics.arcade.overlap(
  obj1,
  obj2,
  collideCallback,
  processCallback,
  context
)

Game Utility Functions and properties

game.world.centerX // gives you center of the game in the X axis
game.world.centerY // gives you center of the game in the Y axis
game.world.height
game.world.width
game.stage.backgroundColor // lets you set the game background color
game.world.bringToTop(gameObject) // brings game object to top (z-index)

The game camera can be accessed via game.camera property. For instance, if you want the camera to follow your game character you can write this:

game.camera.follow(mainCharacter)

You can also have game objects maintain their position as the camera moves around the world using this approach:

// fixed to camera
gameObject.fixedToCamera = true
// position in relation to camera
gameObject.cameraOffset.setTo(x, y)

This is useful for the game HUD which typically accompanies the player throughout the world displaying useful information and UI controls.

Random Numbers

game.rnd.integerInRange(min, max)

Positioning Elements in the Game

You can use the x/y coordinates in combination with anchors. The x/y coordinates tell phaser where to put a game object in the screen while anchors tell phaser which part of an object to use as reference. For instance, the snippet below:

this.sprite.anchor.set(0.5, 0.5)

Tells phaser to use the center of a sprite as a reference when moving the sprite around. That means that if we were to place the sprite in the (0,0) coordinates (top/left corner), phaser would put the center of that sprite in those coordinates. Alternative setting the anchor to (0,0) would make the top/left corner of the sprite coincide with that of the screen.

Changing anchors makes it super easy to center sprites in the middle of the screen. Just combine a centered anchor with the utility properties game.world.centerX.

UI Game Elements

Buttons

Phaser has special support for UI buttons. You can load a spritesheet with the button look and feel:

game.load.spritesheet(key, spriteSheetPath, width, height, numberOfCells)

And then use it within phaser via the Button API:

game.add.button(
  x,
  y,
  key,
  onClick,
  context,
  hoverFrame,
  normalFrame,
  pressedFrame
)

Inputs

Keyboard

Phaser provides the game.input.keyboard property that gives you access to the keyboard input for your game. The easiest way to setup the keyboard for your game is using the createCursorKeys() method. This method creates an objec that allows you to check whether any of the cursor keys has been pressed and act accordingly

// setup cursors
const cursors = game.input.keyboard.createCursorKeys()

// check whether keys are pressed
if (cursors.left.isDown) {
  /* do something */
} else if (cursor.right.isDown) {
  /* do something else*/
}
// etc

If you want to add controls beyond the cursor keys you can use the addKeys API. This allows you to map arbitrary names to keyboard keys like this:

const keys = game.input.keyboard.addKeys({
  left: Phaser.KeyCode.H,
  down: Phaser.KeyCode.J,
  up: Phaser.KeyCode.K,
  right: Phaser.KeyCode.L,
  teleportRight: Phaser.KeyCode.W,
  teleportLeft: Phaser.KeyCode.B,
})

if (keys.teleportRight.isPressed) {
  /* do something */
}

Enabling Inputs for Sprites

By default sprites won’t react to user input. You can enable it like this:

sprite.inputEnabled = true

Once a sprite is enabled for input you can add event handlers to detect that it was clicked:

sprite.events.onInputDown.add(onClickEventHandler, scope)

You can also create references to inputs and check their isDown status. For intance:

if (game.input.activePointer.isDown) {
  /* do something */
}

Game Event Listeners

The Game object provides game-wide event listeners. For instance, you can listen to:

  • Clicks on the canvas: game.input.onUp.add(onUpEventHandler, context)

Time Utilities

Phaser comes with a series of time related utilities under the game.time property:

// setup a timer (happens once after delay)
game.time.events.add(delay, callback, context)
// setup up a interval (happens every period)
game.time.events.loop(period, callback, context)
// For the delay/period you can use Phaser.Timer.SECOND

For more information check the API documentation.

Debugging

Phaser comes with some handy methods that help you debug your game. A common practice is to use the render method within a game state to add the following snippet:

function render() {
  // Show sprite info
  game.debug.spriteInfo(gameObject, x, y)
  // Show physics info for gameObject
  game.debug.bodyInfo(gameObject, x, y)
  // etc
}

You can find more about the debug utility methods in the API docs.

Features for supporting mobile apps

// force orientation to landscape or portrait
// in this case we're forcing portrait
game.scale.forceOrientation(/*landscape*/ false, /*portrait*/ true)
// this doesn't force anything per se, but enables
// a couple of listeners that let you hook up into orientation changes:
// enterIncorrectOrientation and leaveIncorrectOrientation
// which you can use to handle when the game gets in the incorrect orientation
game.scale.enterIncorrectOrientation(callback, this)
game.scale.leaveIncorrectOrientation(callback, this)

Resources


Jaime González García

Written by Jaime González García , dad, husband, software engineer, ux designer, amateur pixel artist, tinkerer and master of the arcane arts. You can also find him on Twitter jabbering about random stuff.Jaime González García