Multi Demo

class Boing extends Phaser.Scene {

    constructor (handle, parent)
    {
        super(handle);

        this.parent = parent;

        this.ball;
        this.shadow;
    }

    create ()
    {
        const bg = this.add.image(0, 0, 'boing', 'boing-window').setOrigin(0);

        this.cameras.main.setViewport(this.parent.x, this.parent.y, Boing.WIDTH, Boing.HEIGHT);

        this.physics.world.setBounds(10, 24, 330, 222);

        this.ball = this.physics.add.sprite(100, 32, 'boing', 'boing1').play('boing');
        this.shadow = this.add.image(this.ball.x + 62, this.ball.y - 2, 'boing', 'shadow');

        this.ball.setVelocity(Phaser.Math.Between(40,80), 110);
        this.ball.setBounce(1, 1);
        this.ball.setCollideWorldBounds(true);

        this.events.on('postupdate', this.postUpdate, this);
    }

    postUpdate ()
    {
        this.shadow.setPosition(this.ball.x + 44, this.ball.y - 2);
    }

    refresh ()
    {
        this.cameras.main.setPosition(this.parent.x, this.parent.y);

        this.scene.bringToTop();
    }

}

Boing.WIDTH = 344;
Boing.HEIGHT = 266;
                        
class Juggler extends Phaser.Scene {

    constructor (handle, parent)
    {
        super(handle);

        this.parent = parent;
    }

    create ()
    {
        const bg = this.add.image(0, 0, 'jugglerWindow').setOrigin(0);

        this.cameras.main.setViewport(this.parent.x, this.parent.y, Juggler.WIDTH, Juggler.HEIGHT);

        this.add.sprite(100, 22, 'juggler').setOrigin(0).play('juggler');
    }

    refresh ()
    {
        this.cameras.main.setPosition(this.parent.x, this.parent.y);

        this.scene.bringToTop();
    }

}

Juggler.WIDTH = 328;
Juggler.HEIGHT = 226;
                        
class Invaders extends Phaser.Scene {

    constructor (handle, parent)
    {
        super(handle);

        this.parent = parent;

        this.left;
        this.right;

        this.ship;

        this.invaders;
        this.mothership;
        this.bullet;

        this.topLeft;
        this.bottomRight;

        this.bulletTimer;
        this.mothershipTimer;

        this.isGameOver = false;

        this.invadersBounds = { x: 12, y: 62, right: 152 };
    }

    create (config)
    {
        this.left = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.LEFT);
        this.right = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.RIGHT);

        this.physics.world.setBounds(4, 22, 400, 300);

        this.cameras.main.setViewport(this.parent.x, this.parent.y, Invaders.WIDTH, Invaders.HEIGHT);
        this.cameras.main.setBackgroundColor('#000');

        this.createInvaders();

        this.bullet = this.physics.add.image(200, 290, 'invaders.bullet2');

        this.mothership = this.physics.add.image(500, 40, 'invaders.mothership');

        this.ship = this.physics.add.image(200, 312, 'invaders.ship');

        var bg = this.add.image(0, 0, 'invadersWindow').setOrigin(0);

        this.ship.setCollideWorldBounds(true);

        this.physics.add.overlap(this.bullet, this.invaders, this.bulletHit, null, this);
        this.physics.add.overlap(this.bullet, this.mothership, this.bulletHitMothership, null, this);

        this.launchBullet();

        this.mothershipTimer = this.time.addEvent({ delay: 10000, callback: this.launchMothership, callbackScope: this, repeat: -1 });

        this.invaders.setVelocityX(50);
    }

    launchMothership ()
    {
        this.mothership.setVelocityX(-100);
    }

    bulletHit (bullet, invader)
    {
        this.launchBullet();

        invader.body.enable = false;

        this.invaders.killAndHide(invader);

        this.refreshOutliers();
    }

    bulletHitMothership (bullet, mothership)
    {
        this.launchBullet();

        this.mothership.body.reset(500, 40);
    }

    refreshOutliers ()
    {
        const list = this.invaders.getChildren();

        let first = this.invaders.getFirst(true);
        let last = this.invaders.getLast(true);

        for (let i = 0; i < list.length; i++)
        {
            const vader = list[i];

            if (vader.active)
            {
                if (vader.x < first.x)
                {
                    first = vader;
                }
                else if (vader.x > last.x)
                {
                    last = vader;
                }
            }
        }

        if (this.topLeft === null && this.bottomRight === null)
        {
            this.gameOver();
        }

        this.topLeft = first;
        this.bottomRight = last;
    }

    launchBullet ()
    {
        this.bullet.body.reset(this.ship.x, this.ship.y);

        this.bullet.body.velocity.y = -400;
    }

    createInvaders ()
    {
        this.invaders = this.physics.add.group();

        let x = this.invadersBounds.x;
        let y = this.invadersBounds.y;

        for (let i = 0; i < 10; i++)
        {
            this.invaders.create(x, y, 'invaders.invader1').setTint(0xff0000).play('invader1');

            x += 26;
        }

        x = this.invadersBounds.x;
        y += 28

        for (let i = 0; i < 16; i++)
        {
            this.invaders.create(x, y, 'invaders.invader2').setTint(0x00ff00).play('invader2');

            x += 33;

            if (i === 7)
            {
                x = this.invadersBounds.x;
                y += 28;
            }
        }

        x = this.invadersBounds.x;
        y += 28

        for (let i = 0; i < 14; i++)
        {
            this.invaders.create(x, y, 'invaders.invader3').setTint(0x00ffff).play('invader3');

            x += 38;

            if (i === 6)
            {
                x = this.invadersBounds.x;
                y += 28;
            }
        }

        //  We can use these markers to work out where the whole Group is and how wide it is
        this.topLeft = this.invaders.getFirst(true);
        this.bottomRight = this.invaders.getLast(true);
    }

    refresh ()
    {
        this.cameras.main.setPosition(this.parent.x, this.parent.y);

        this.scene.bringToTop();
    }

    gameOver ()
    {
        this.invaders.setVelocityX(0);

        this.ship.setVisible(false);

        this.bullet.setVisible(false);

        this.isGameOver = true;
    }

    update ()
    {
        if (this.isGameOver || (this.bottomRight === null && this.topLeft === null))
        {
            return;
        }

        if (this.left.isDown)
        {
            this.ship.body.velocity.x = -400;
        }
        else if (this.right.isDown)
        {
            this.ship.body.velocity.x = 400;
        }
        else
        {
            this.ship.body.velocity.x = 0;
        }

        //  Bullet bounds
        if (this.bullet.y < -32)
        {
            this.launchBullet();
        }

        //  Invaders bounds

        let moveDown = false;

        if (this.bottomRight.body.velocity.x > 0 && this.bottomRight.x >= 390)
        {
            this.invaders.setVelocityX(-50);
            moveDown = true;
        }
        else if (this.topLeft.body.velocity.x < 0 && this.topLeft.x <= 12)
        {
            this.invaders.setVelocityX(50);
            moveDown = true;
        }

        if (moveDown)
        {
            const list = this.invaders.getChildren();
            let lowest = 0;

            for (let i = 0; i < list.length; i++)
            {
                const vader = list[i];

                vader.body.y += 4;

                if (vader.active && vader.body.y > lowest)
                {
                    lowest = vader.body.y;
                }
            }

            if (lowest > 240)
            {
                this.gameOver();
            }
        }
    }

}

Invaders.WIDTH = 408;
Invaders.HEIGHT = 326;
                        
class Stars extends Phaser.Scene {

    constructor (handle, parent)
    {
        super(handle);

        this.parent = parent;

        this.blitter;

        this.width = 320;
        this.height = 220;
        this.depth = 1700;
        this.distance = 200;
        this.speed = 6;

        this.max = 300;
        this.xx = [];
        this.yy = [];
        this.zz = [];
    }

    create ()
    {
        this.cameras.main.setViewport(this.parent.x, this.parent.y, Stars.WIDTH, Stars.HEIGHT);
        this.cameras.main.setBackgroundColor(0x000000);

        this.blitter = this.add.blitter(0, 0, 'star');

        for (let i = 0; i < this.max; i++)
        {
            this.xx[i] = Math.floor(Math.random() * this.width) - (this.width / 2);
            this.yy[i] = Math.floor(Math.random() * this.height) - (this.height / 2);
            this.zz[i] = Math.floor(Math.random() * this.depth) - 100;

            const perspective = this.distance / (this.distance - this.zz[i]);
            const x = (this.width / 2) + this.xx[i] * perspective;
            const y = (this.height / 2) + this.yy[i] * perspective;
            const a = (x < 0 || x > 320 || y < 20 || y > 260) ? 0 : 1;

            this.blitter.create(x, y);
        }

        const bg = this.add.image(0, 0, 'starsWindow').setOrigin(0);
    }

    update (time, delta)
    {
        const list = this.blitter.children.list;

        for (let i = 0; i < this.max; i++)
        {
            const perspective = this.distance / (this.distance - this.zz[i]);

            const x = (this.width / 2) + this.xx[i] * perspective;
            const y = (this.height / 2) + this.yy[i] * perspective;

            this.zz[i] += this.speed;

            if (this.zz[i] > this.distance)
            {
                this.zz[i] -= (this.distance * 2);
            }

            list[i].x = x;
            list[i].y = y;
            list[i].a = (x < 0 || x > 320 || y < 20 || y > 260) ? 0 : 1;
        }
    }

    refresh ()
    {
        this.cameras.main.setPosition(this.parent.x, this.parent.y);

        this.scene.bringToTop();
    }

}

Stars.WIDTH = 328;
Stars.HEIGHT = 266;
                        
const config = {
    type: Phaser.WEBGL,
    width: window.innerWidth,
    height: window.innerHeight,
    backgroundColor: '#0055aa',
    parent: 'phaser-example',
    scene: Controller,
    physics: {
        default: 'arcade',
        arcade: {
            debug: false,
            gravity: { y: 0 }
        }
    }
};

const game = new Phaser.Game(config);

window.addEventListener('resize', function (event) {

    // game.resize(window.innerWidth, window.innerHeight);

}, false);
                        
class Clock extends Phaser.Scene {

    constructor (handle, parent)
    {
        super(handle);

        this.parent = parent;

        this.graphics;
        this.clockSize = 120;
    }

    create ()
    {
        const bg = this.add.image(0, 0, 'clockWindow').setOrigin(0);

        this.cameras.main.setViewport(this.parent.x, this.parent.y, Clock.WIDTH, Clock.HEIGHT);
        this.cameras.main.setBackgroundColor(0x0055aa);

        this.graphics = this.add.graphics();
    }

    update ()
    {
        const graphics = this.graphics;
        const timer = this.timerEvent;
        const clockSize = this.clockSize;
        const x = Clock.WIDTH / 2;
        const y = 8 + Clock.HEIGHT / 2;

        graphics.clear();

        //  Progress is between 0 and 1, where 0 = the hand pointing up and then rotating clockwise a full 360

        //  The frame
        graphics.fillStyle(0xffffff, 1);
        graphics.lineStyle(3, 0x000000, 1);
        graphics.fillCircle(x, y, clockSize);
        graphics.strokeCircle(x, y, clockSize);

        let date = new Date;
        let seconds = date.getSeconds() / 60;
        let mins = date.getMinutes() / 60;
        let hours = date.getHours() / 24;

        //  The hours hand
        let size = clockSize * 0.9;

        let angle = (360 * hours) - 90;
        let dest = Phaser.Math.RotateAroundDistance({ x: x, y: y }, x, y, Phaser.Math.DegToRad(angle), size);

        graphics.fillStyle(0x000000, 1);

        graphics.beginPath();

        graphics.moveTo(x, y);

        let p1 = Phaser.Math.RotateAroundDistance({ x: x, y: y }, x, y, Phaser.Math.DegToRad(angle - 5), size * 0.7);

        graphics.lineTo(p1.x, p1.y);
        graphics.lineTo(dest.x, dest.y);

        graphics.moveTo(x, y);

        let p2 = Phaser.Math.RotateAroundDistance({ x: x, y: y }, x, y, Phaser.Math.DegToRad(angle + 5), size * 0.7);

        graphics.lineTo(p2.x, p2.y);
        graphics.lineTo(dest.x, dest.y);

        graphics.fillPath();
        graphics.closePath();

        //  The minutes hand
        size = clockSize * 0.9;

        angle = (360 * mins) - 90;
        dest = Phaser.Math.RotateAroundDistance({ x: x, y: y }, x, y, Phaser.Math.DegToRad(angle), size);

        graphics.fillStyle(0x000000, 1);

        graphics.beginPath();

        graphics.moveTo(x, y);

        p1 = Phaser.Math.RotateAroundDistance({ x: x, y: y }, x, y, Phaser.Math.DegToRad(angle - 5), size * 0.7);

        graphics.lineTo(p1.x, p1.y);
        graphics.lineTo(dest.x, dest.y);

        graphics.moveTo(x, y);

        p2 = Phaser.Math.RotateAroundDistance({ x: x, y: y }, x, y, Phaser.Math.DegToRad(angle + 5), size * 0.7);

        graphics.lineTo(p2.x, p2.y);
        graphics.lineTo(dest.x, dest.y);

        graphics.fillPath();
        graphics.closePath();

        //  The seconds hand
        size = clockSize * 0.9;

        angle = (360 * seconds) - 90;
        dest = Phaser.Math.RotateAroundDistance({ x: x, y: y }, x, y, Phaser.Math.DegToRad(angle), size);

        graphics.fillStyle(0xff0000, 1);

        graphics.beginPath();

        graphics.moveTo(x, y);

        p1 = Phaser.Math.RotateAroundDistance({ x: x, y: y }, x, y, Phaser.Math.DegToRad(angle - 5), size * 0.3);

        graphics.lineTo(p1.x, p1.y);
        graphics.lineTo(dest.x, dest.y);

        graphics.moveTo(x, y);

        p2 = Phaser.Math.RotateAroundDistance({ x: x, y: y }, x, y, Phaser.Math.DegToRad(angle + 5), size * 0.3);

        graphics.lineTo(p2.x, p2.y);
        graphics.lineTo(dest.x, dest.y);

        graphics.fillPath();
        graphics.closePath();

    }

    refresh ()
    {
        this.cameras.main.setPosition(this.parent.x, this.parent.y);

        this.scene.bringToTop();
    }

}

Clock.WIDTH = 275;
Clock.HEIGHT = 276;
                        
class Controller extends Phaser.Scene {

    constructor ()
    {
        super();

        this.count = 0;

        this.workbench;
        this.workbenchTitle;
        this.workbenchIcons;
    }

    preload ()
    {
        this.load.setBaseURL('https://cdn.phaserfiles.com/v355');
        this.load.image('disk', 'assets/phaser3/disk.png');

        this.load.image('workbenchTitle', 'assets/phaser3/workbench-title.png');
        this.load.image('workbenchIcons', 'assets/phaser3/workbench-icons.png');
        this.load.image('demosWindow', 'assets/phaser3/demos-window.png');
        this.load.image('eyesIcon', 'assets/phaser3/eyes-icon.png');
        this.load.image('starsIcon', 'assets/phaser3/stars-icon.png');
        this.load.image('jugglerIcon', 'assets/phaser3/juggler-icon.png');
        this.load.image('twistIcon', 'assets/phaser3/twist-icon.png');
        this.load.image('invadersIcon', 'assets/phaser3/invaders-icon.png');
        this.load.image('clockIcon', 'assets/phaser3/clock-icon.png');
        this.load.image('boingIcon', 'assets/phaser3/boing-icon.png');

        this.load.image('starsWindow', 'assets/phaser3/stars-window.png');
        this.load.image('sineWindow', 'assets/phaser3/sinewave-window.png');
        this.load.image('eyesWindow', 'assets/phaser3/eyes-window.png');
        this.load.image('jugglerWindow', 'assets/phaser3/juggler-window.png');
        this.load.image('invadersWindow', 'assets/phaser3/invaders-window.png');
        this.load.image('clockWindow', 'assets/phaser3/clock-window.png');

        this.load.atlas('boing', 'assets/phaser3/boing.png', 'assets/phaser3/boing.json');

        this.load.spritesheet('juggler', 'assets/phaser3/juggler.png', { frameWidth: 128, frameHeight: 184 });
        this.load.image('star', 'assets/phaser3/star2.png');
        this.load.image('eye', 'assets/phaser3/eye.png');

        this.load.image('invaders.boom', 'assets/games/multi/boom.png');
        this.load.spritesheet('invaders.bullet', 'assets/games/multi/bullet.png', { frameWidth: 12, frameHeight: 14 });
        this.load.image('invaders.bullet2', 'assets/games/multi/bullet2.png');
        this.load.image('invaders.explode', 'assets/games/multi/explode.png');
        this.load.spritesheet('invaders.invader1', 'assets/games/multi/invader1.png', { frameWidth: 16, frameHeight: 16 });
        this.load.spritesheet('invaders.invader2', 'assets/games/multi/invader2.png', { frameWidth: 22, frameHeight: 16 });
        this.load.spritesheet('invaders.invader3', 'assets/games/multi/invader3.png', { frameWidth: 24, frameHeight: 16 });
        this.load.image('invaders.mothership', 'assets/games/multi/mothership.png');
        this.load.image('invaders.ship', 'assets/games/multi/ship.png');
    }

    create ()
    {
        //  Create animations

        this.anims.create({
            key: 'juggler',
            frames: this.anims.generateFrameNumbers('juggler'),
            frameRate: 28,
            repeat: -1
        });

        this.anims.create({
            key: 'boing',
            frames: this.anims.generateFrameNames('boing', { prefix: 'boing', start: 1, end: 14 }),
            frameRate: 28,
            repeat: -1
        });

        this.anims.create({
            key: 'bullet',
            frames: this.anims.generateFrameNumbers('invaders.bullet'),
            frameRate: 8,
            repeat: -1
        });

        this.anims.create({
            key: 'invader1',
            frames: this.anims.generateFrameNumbers('invaders.invader1'),
            frameRate: 2,
            repeat: -1
        });

        this.anims.create({
            key: 'invader2',
            frames: this.anims.generateFrameNumbers('invaders.invader2'),
            frameRate: 2,
            repeat: -1
        });

        this.anims.create({
            key: 'invader3',
            frames: this.anims.generateFrameNumbers('invaders.invader3'),
            frameRate: 2,
            repeat: -1
        });

        this.workbench = this.add.graphics({ x: 16, y: 21 });

        this.workbench.fillStyle(0xffffff);
        this.workbench.fillRect(0, 0, this.sys.game.config.width - 105, 20);

        this.workbenchTitle = this.add.image(16, 21, 'workbenchTitle').setOrigin(0);
        this.workbenchIcons = this.add.image(this.sys.game.config.width - 87, 21, 'workbenchIcons').setOrigin(0);

        const disk = this.add.image(16, 64, 'disk').setOrigin(0).setInteractive();

        const demosWindow = this.add.image(0, 0, 'demosWindow').setOrigin(0);
        const eyesIcon = this.add.image(32, 34, 'eyesIcon', 0).setOrigin(0).setInteractive();
        const jugglerIcon = this.add.image(48, 110, 'jugglerIcon', 0).setOrigin(0).setInteractive();
        const starsIcon = this.add.image(230, 40, 'starsIcon', 0).setOrigin(0).setInteractive();
        const invadersIcon = this.add.image(120, 34, 'invadersIcon', 0).setOrigin(0).setInteractive();
        const clockIcon = this.add.image(240, 120, 'clockIcon', 0).setOrigin(0).setInteractive();
        const boingIcon = this.add.image(146, 128, 'boingIcon', 0).setOrigin(0).setInteractive();

        const demosContainer = this.add.container(32, 70, [ demosWindow, eyesIcon, jugglerIcon, starsIcon, invadersIcon, clockIcon, boingIcon ]);

        demosContainer.setVisible(false);

        demosContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, demosWindow.width, demosWindow.height), Phaser.Geom.Rectangle.Contains);

        this.input.setDraggable(demosContainer);

        demosContainer.on('drag', function (pointer, dragX, dragY) {

            this.x = dragX;
            this.y = dragY;

        });

        disk.once('pointerup', function () {

            demosContainer.setVisible(true);

        });

        eyesIcon.on('pointerup', function () {

            this.createWindow(Eyes);

        }, this);

        jugglerIcon.on('pointerup', function () {

            this.createWindow(Juggler);

        }, this);

        starsIcon.on('pointerup', function () {

            this.createWindow(Stars);

        }, this);

        invadersIcon.on('pointerup', function () {

            this.createWindow(Invaders);

        }, this);

        clockIcon.on('pointerup', function () {

            this.createWindow(Clock);

        }, this);

        boingIcon.on('pointerup', function () {

            this.createWindow(Boing);

        }, this);


        this.events.on('resize', this.resize, this);
    }

    createWindow (func)
    {
        const x = Phaser.Math.Between(400, 600);
        const y = Phaser.Math.Between(64, 128);

        const handle = 'window' + this.count++;

        const win = this.add.zone(x, y, func.WIDTH, func.HEIGHT).setInteractive().setOrigin(0);

        const demo = new func(handle, win);

        this.input.setDraggable(win);

        win.on('drag', function (pointer, dragX, dragY) {

            this.x = dragX;
            this.y = dragY;

            demo.refresh()

        });

        this.scene.add(handle, demo, true);
    }

    resize (width, height)
    {
        if (width === undefined) { width = this.game.config.width; }
        if (height === undefined) { height = this.game.config.height; }

        this.cameras.resize(width, height);

        this.workbench.clear();
        this.workbench.fillStyle(0xffffff);
        this.workbench.fillRect(0, 0, width - 105, 20);

        this.workbenchIcons.x = (width - 87);
    }

}
                        
class Eyes extends Phaser.Scene {

    constructor (handle, parent)
    {
        super(handle);

        this.parent = parent;

        this.left;
        this.right;

        this.leftTarget;
        this.rightTarget;

        this.leftBase;
        this.rightBase;

        this.mid = new Phaser.Math.Vector2();
    }

    create ()
    {
        const bg = this.add.image(0, 0, 'eyesWindow').setOrigin(0);

        this.cameras.main.setViewport(this.parent.x, this.parent.y, Eyes.WIDTH, Eyes.HEIGHT);

        this.left = this.add.image(46, 92, 'eye');
        this.right = this.add.image(140, 92, 'eye');

        this.leftTarget = new Phaser.Geom.Line(this.left.x, this.left.y, 0, 0);
        this.rightTarget = new Phaser.Geom.Line(this.right.x, this.right.y, 0, 0);

        this.leftBase = new Phaser.Geom.Ellipse(this.left.x, this.left.y, 24, 40);
        this.rightBase = new Phaser.Geom.Ellipse(this.right.x, this.right.y, 24, 40);
    }

    update ()
    {
        this.leftTarget.x2 = this.input.activePointer.x - this.parent.x;
        this.leftTarget.y2 = this.input.activePointer.y - this.parent.y;

        //  Within the left eye?
        if (this.leftBase.contains(this.leftTarget.x2, this.leftTarget.y2))
        {
            this.mid.x = this.leftTarget.x2;
            this.mid.y = this.leftTarget.y2;
        }
        else
        {
            Phaser.Geom.Ellipse.CircumferencePoint(this.leftBase, Phaser.Geom.Line.Angle(this.leftTarget), this.mid);
        }

        this.left.x = this.mid.x;
        this.left.y = this.mid.y;

        this.rightTarget.x2 = this.input.activePointer.x - this.parent.x;
        this.rightTarget.y2 = this.input.activePointer.y - this.parent.y;

        //  Within the right eye?
        if (this.rightBase.contains(this.rightTarget.x2, this.rightTarget.y2))
        {
            this.mid.x = this.rightTarget.x2;
            this.mid.y = this.rightTarget.y2;
        }
        else
        {
            Phaser.Geom.Ellipse.CircumferencePoint(this.rightBase, Phaser.Geom.Line.Angle(this.rightTarget), this.mid);
        }

        this.right.x = this.mid.x;
        this.right.y = this.mid.y;
    }

    refresh ()
    {
        this.cameras.main.setPosition(this.parent.x, this.parent.y);

        this.scene.bringToTop();
    }

}

Eyes.WIDTH = 183;
Eyes.HEIGHT = 162;