First Phaser 3 Game – Lessons & Code Pt. 2

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

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

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

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

 
Thank you so much for reading this! If you find any typos, mistakes, or have suggestions, please feel free to do so in the comments below or contact me on Twitter @thejamespierce.

If you want to try some of my other games, here are a few titles:

Leave a Reply