Saturday, September 5, 2015

Phaser tutorial: Breaking the (z-order) law!

 





Previous Phaser tutorials:
Phaser tutorial: Phaser and Spriter skeletal animation
Phaser tutorial: DronShooter - simple game in Typescript - Part 3
Phaser tutorial: DronShooter - simple game in Typescript - Part 2
Phaser tutorial: adding 9-patch image support to Phaser
Phaser tutorial: DronShooter - simple game in Typescript - Part 1
Phaser tutorial: custom easing functions for tweening and easing functions with parameters
Phaser tutorial: sprites and custom properties for atlas frames
Phaser tutorial: manage different screen sizes
Phaser tutorial: How to wrap bitmap text


 When building scene tree in Phaser engine the sprites added as last are rendered on top. Unlike, for example, in Unity, you cannot change order of sprites with setting its z position. You can only set z property and sort sprites within one group.

 Now, take a look at following example:


You can try to achieve this with:
  1.  making two groups (walls and gems) and tween them. But, in such case all gems will be under or above walls, depending on groups order,
  2. making one group with all the walls and gems, but to move them you will have to create 8 tweens.
 There is third way which allows you to put all sprites in one group (like in 2.) and transform gems and walls at once (like in 1.).

 To achieve it you have to create 3 groups: one for all the sprites and two empty. In group with sprites you will maintain your z order and empty groups will be tweened. Now comes the trick - sprites will not be transformed with their parent group, but walls will be transformed with on of the empty groups and gems with the second. In other words: when engine is transforming sprite, you give it group parent different from its original one.

 Source code for this example is short and self explanatory. Sprites are created and added to spritesGroup. All sprites are MySprite class objects and they are given another group they will use as their parent when updating transform. Two empty groups are named group1 and group2. These groups are then tweened.

 While the code is simple, the final effect looks like complicated pattern of sprites and groups.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
class Game extends Phaser.Game {
    // -------------------------------------------------------------------------
    constructor() {
        // init game
        super(640, 400, Phaser.CANVAS, "content", State);
    }
}

// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
class MySprite extends Phaser.Sprite {

    private _parentTransform: Phaser.Group;

    // -------------------------------------------------------------------------
    constructor(aGame: Phaser.Game, aX: number, aY: number, aKey: string, aParentTransform: Phaser.Group) {
        super(aGame, aX, aY, aKey);
        this._parentTransform = aParentTransform;
    }

    // -------------------------------------------------------------------------
    public updateTransform(): void {
        if (!this.visible) {
            return;
        }

        this.displayObjectUpdateTransform(this._parentTransform);
    }
}

// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
class State extends Phaser.State {

    // -------------------------------------------------------------------------
    preload() {
        // sprites
        this.load.image("Wall", "assets/Wall.png");
        this.load.image("Gem0", "assets/Gem0.png");
        this.load.image("Gem1", "assets/Gem1.png");
        this.load.image("Gem2", "assets/Gem2.png");
        this.load.image("Gem3", "assets/Gem3.png");
    }

    // -------------------------------------------------------------------------
    create() {
        this.stage.backgroundColor = 0x0F3043;

        var group1 = this.add.group();
        var group2 = this.add.group();

        var spritesGroup = this.add.group();

        for (var i = 0; i < 4; i++) {
            // wall sprite
            var wall = new MySprite(this.game, 50, 90 + i * 70, "Wall", group1);
            wall.width = 440;
            wall.z = i * 2;
            spritesGroup.add(wall);

            // gem sprite
            var gem = new MySprite(this.game, 170 + 100 * i, 50, "Gem" + i, group2);
            gem.anchor.setTo(0.5, 0.5);
            gem.z = i * 2 + 1;
            spritesGroup.add(gem);
        }

        // move groups
        this.add.tween(group1).to({ x: 100 }, 1000, Phaser.Easing.Sinusoidal.InOut, true, 0, -1, true);
        this.add.tween(group2).to({ y: 320 }, 3000, Phaser.Easing.Linear.None, true, 0, -1, true);
    }
}

// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
window.onload = () => {
    new Game();
};