Menu

First Phaser 3 Game – Lessons & Code Pt. 2

ATTENTION: This series was about the BETA version of Phaser 3! But Phaser 3 has long been released and received tons of updates.

There is a NEW series that shows you how to create games from scratch with Phaser 3! I highly suggest you check it out:

How to Create a Mobile Game from Scratch With Phaser 3


This dev blog posts series is about my first Phaser 3 game “Pixel Memory“.

The other posts in the series can be found here:
First Phaser 3 Game – Lessons & Code Pt. 1
First Phaser 3 Game – Lessons & Code Pt. 3


Overview

  1. Workaround: Passing Data Between Scenes
  2. Changing Width / Height of Phaser 3 Objects
  3. Tween Config
  4. Sprite Children: Buttons With Labels

1. Workaround: Passing Data Between Scenes


UPDATE 25.02.2018: Phaser 3.1.2 – Scenes Passing Data
This has been fixed. Please see this post for more details, especially regarding the use of global vars. 


As mentioned last time, there is a bug in Phaser 3 Beta 20 that prevents you from passing data between scenes.

For completion’s sake, here is how you would normally pass data between two scenes (Phaser 3 Lab Link):

// Here we are in the "Level" scene
// We start the "play" scene and send some data
this.scene.start('Play', { level: 3, difficulty: Medium });

// In the init or create method of the "Play" scene you receive the data as follows
PlayScene.init = function(data)
{
	this._LEVEL = data.level;
	this._DIFF  = data.difficulty;
};

Unfortunately, the data object that you will receive in the PlayScene is empty (Phaser 3 Beta 20).

In Pixel Memory, players can select the difficulty and also choose the color of their card decks. Since I couldn’t pass this data to the PlayScene, I decided to use global variables as a workaround.

Selecting a deck color in the DecksScene:

DecksScene.clickBlue = function()
{
	'use strict';
	
	if(this.flagClick() === false)
	{
		return;
	}
	
	this.sys.game._DECK = 0;
	
	this.hideUnselected();
	
	this.startTransitionOut(this.goPlay);
};

Selecting a difficulty in the DifficultyScene:

DifficultyScene.clickEasy = function()
{
	'use strict';
	
	if(this.flagClick() === false)
	{
		return;
	}
	
	this.sys.game._COLS = 4;
	this.sys.game._ROWS = 7;
	
	this.startTransitionOut(this.goDecks);
};

Saving all data when initializing the PlayScene:

PlayScene.init = function()
{
	'use strict';

	// ...

	this._COLS = this.sys.game._COLS;
	this._ROWS = this.sys.game._ROWS;
	this._DECK = this.sys.game._DECK;

	// ...

	this.sys.game._COLS = undefined;
	this.sys.game._ROWS = undefined;
	this.sys.game._DECK = undefined;
};

The concept is very easy and as long as you unset these values properly, it works perfectly fine.

Important: Notice that to save a variable in the global game space, you have to save it in this.sys.game.

Back to overview

2. Changing Width / Height of Phaser 3 Objects

In Phaser 2 you could directly set the width & height of a game object and it will take these values in the game.

Not so anymore in Phaser 3. In Phaser 3, you have to differentiate between sprite.width and sprite.displayWidth.

For example, I wanted to dynamically change the width of the XP bar on a player’s profile. It only works if you set the displayWidth property.

The width property is reserved for the width of the image, as it was loaded into the game. If it comes from a sprite sheet, it will show the width value of one frame in the sprite sheet.

You can also change the width of a sprite by setting its scale. However, remember that this scales the whole image! Since my XP bar has different edges, I cannot scale the whole image down. I needed to crop it dynamically, which means changing the displayWidth value.

// This does nothing
this.ui.profile.xpbar.width = Math.round(this.ui.profile.xpbar.width * ratio);

// This works
this.ui.profile.xpbar.displayWidth = Math.round(this.ui.profile.xpbar.width * ratio);

// This also changes the width, but it scales the full width of the image!!
this.ui.profile.xpbar.setScale(ratio, 1);

Back to overview

3. Tween Config

Tweening in Phaser 3 is absolutely amazing.

We now simply pass an object with all the tween config properties, such as duration, ease, but even callback & callbackScope.

I want to list this example because some properties are extremely useful to know about, such as callbackScope. Try it out!

var scope = this;

var tween = this.tweens.add({
        targets		: [ myImage, myGraphic, mySprite ],
        x		: 600,
        ease		: 'Linear',
        duration	: 3000,
        yoyo		: true,
        repeat		: 1, // -1 for infinite repeats
        onStart		: function () { console.log('onStart'); console.log(arguments); },
        onComplete	: function () { console.log('onComplete'); console.log(arguments); },
        onYoyo		: function () { console.log('onYoyo'); console.log(arguments); },
        onRepeat	: function () { console.log('onRepeat'); console.log(arguments); },
        callbackScope	: scope
    });

Sometimes you have tweens that are very similar and only slightly different. In that case, you can set up a method to return the tween config object for you:

function getTweenConfig(ctx, delay, col, row, pos)
{
	return {
		targets: ctx.decks[col][row],
		delay: delay,
		duration: 500,
		x: pos.x,
		y: pos.y,
		angle: -720,
		ease: 'Linear',
		// play sfx
		onStart: function() { ctx.time.delayedCall(delay, ctx.helper.playSfx, [ctx, 'tap_card'], ctx); },
		// normal callback
		onComplete: function() { completeIntro.call(ctx, col, row); },
		callbackScope: ctx
	}
}

Back to overview

4. Sprite Children: Buttons With Labels

I like to create helper methods for objects that I have to create many times in a game.

Such an object is a button with label. While the button sprite can be the same many times, the label on it will always be different.

In Phaser 2, you had the ability to create the button object and then add a text object as its child. Whatever property you changed on the button object thereafter, it also applied it to the child object (the text).

Unfortunately in Phaser 3 Beta 20, you cannot add children to an object. I hope this is something that will be added in a later release.

You still have the possibility to create Phaser Groups, though. However, the group object didn’t offer all the functionality I needed for my buttons.

So instead, I have added the text to the data object of my buttons.

I am pasting the full helper method for you below. Feel free to use it in your own Phaser 3 games & improve it further:

Helper.prototype.createBtnWithLabel = function(ctx, x, y, img, callback, label_config, frames, data)
{
	'use strict';
	
	var btn;
	var text;
	
	var label_config = label_config || { string: '[n/a]', size: 64, color: '0xFFFFFF', x: 0, y: 0 };
	
	// Label position
	if(!label_config.x)
	{
		label_config.x = 0;
	}
	if(!label_config.y)
	{
		label_config.y = 0;
	}
	
	// Create...
	// ...sprite
	btn = ctx.add.sprite(x, y, img);
	// ...label
	text = this.createText(ctx, x + label_config.x, y + label_config.y, label_config.string, label_config.size, label_config.color);
	// ...data
	btn.data = data || {};
	btn.data.label_obj = text;
	
	// Inputs...
	// ...activate
	btn.setInteractive();
	// ...callback
	btn.on('pointerup', function(e)
	{
		ctx.helper.playClickSfx(ctx);
		callback.call(ctx);
	});
	
	// Frames...
	// ...hover
	if(frames && frames.over)
	{
		btn.on('pointerover', function(e)
		{
			this.setFrame(frames.over);
		});
		
		btn.on('pointerout', function(e)
		{
			this.setFrame(0);
		});
	}
	// ...click
	if(frames && frames.down)
	{
		btn.on('pointerdown', function(e)
		{
			this.setFrame(frames.down);
		});
	}
	
	// Return group
	return btn;
};

As you can see, a beautiful thing about such helper methods is that you can easily add /change the “click” sounds for all buttons.

You could also add the callback for mobile players, where a touch input will not trigger the “out” state after clicking a button. For example when registering the “pointerdown” input, you run a callback method that resets the button’s frame back to the “out” frame after a short delay (maybe 250 ms).

Anything that you need all (or most) of your buttons to do, I hope that this helper method provides a good template for it.

Back to overview

The Ultimate Phaser 3 Guide

Everything you need to make real, polished, professional Phaser 3 games, you find it in this guide here: HTML5 Game Development Mini-Degree

I highly suggest you check it out!

2 comments

  1. blackhawx says:

    Great stuff! In your example of of Tween config, can you provide an example of using getTweenConfig(ctx, delay, col, row, pos). I’m trying to wrap my head around using ‘pos’ as a parameter in the function because you then use pos.x, and pos.y as values within the function. where or how is x and y defined when calling the function?

    Thanks!

  2. admin says:

    Hi blackhawx, thank you for commenting! I’m using the getTweenConfig() function as part of the transition when a new game starts. In this transition, all the cards are thrown onto the table. Each card is tweened with a different delay and is thrown to a different spot on the table.

    The arguments for the function are: ctx, delay, col, row, pos.
    (1) ctx is just the context, in other words the current scene. We need it for some of the callbacks where ctx replaces this, for example ctx.time.delayedCall().
    (2) delay is exactly what it means, the delay in ms until the tweening animation should start.
    (3) col and row are used to determine the correct target of the tween animation, which is a single card on the table. Each card can be identified by its col and row position on the table, thus that’s what I’m passing to the function.
    (4) The pos argument is the target position of the card in pixels, in other words the x and y coordinates where the card has to land when the tween animation is completed.

    As I’ve mentioned at the beginning of this comment, one example where I use it is during the transition of a new game where all the cards are thrown onto the table. Here is the loop that makes use of the getTweenConfig() function and grabs each card individually (col & row arguments) and tweens it to the correct position on the table (pos argument) in quick succession (delay argument):

    for(var col = 0; col < this.decks.length; col++)
    {		
        for(var row = 0; row < this.decks[col].length; row++)
            {
                num++;
                this.add.tween(getTweenConfig(this, num * 100, col, row, this.positions[col][row]));
            }
    }

    The getTweenConfig() function is just a helper function. I always try to keep my methods as concise as possible and prevent long code sections (I try, but I don't always find an elegant way to do it). Having the getTweenConfig() function helps me write on a single line what otherwise would take a dozen or more due to the Phaser Tween Config Object. Especially if there are tweens that have the same or very similar Tween Configurations, I can replace several multi-line objects with this short function caller.

Leave a Reply