Bank Panic

Hot
export default class Boot extends Phaser.Scene
{
    constructor ()
    {
        super('Boot');
    }

    preload ()
    {
        this.load.setBaseURL('https://cdn.phaserfiles.com/v385');
        this.load.image('loading', 'assets/games/bank-panic/loading.png');
    }

    create ()
    {
        this.scene.start('Preloader');
    }
}
                        
export default class MainMenu extends Phaser.Scene
{
    constructor ()
    {
        super('MainMenu');
    }

    create ()
    {
        this.add.image(512, 384, 'title');

        let sign = this.add.image(512, -400, 'logo');

        this.tweens.add({
            targets: sign,
            y: 180,
            ease: 'Bounce.easeOut',
            duration: 2000
        });

        let cactus1 = this.add.image(150, 680, 'https://labs.phaser.io/assets', 'cactus');
        let cactus2 = this.add.image(880, 680, 'https://labs.phaser.io/assets', 'cactus').setFlipX(true);

        this.tweens.add({
            targets: cactus1,
            props: {
                scaleX: { value: 0.9, duration: 250 },
                scaleY: { value: 1.1, duration: 250 },
                angle: { value: -20, duration: 500, delay: 250 },
                y: { value: 660, duration: 250 }
            },
            ease: 'Sine.easeInOut',
            repeat: -1,
            yoyo: true
        });

        this.tweens.add({
            targets: cactus2,
            props: {
                scaleX: { value: 0.9, duration: 250 },
                scaleY: { value: 1.1, duration: 250 },
                angle: { value: 20, duration: 500, delay: 250 },
                y: { value: 660, duration: 250 }
            },
            ease: 'Sine.easeInOut',
            repeat: -1,
            yoyo: true
        });

        this.music = this.sound.play('music', { loop: true });

        this.input.once('pointerdown', () => {

            this.sound.stopAll();

            this.sound.play('shot');

            this.scene.start('MainGame');

        });
    }
}
                        
export default class Preloader extends Phaser.Scene
{
    constructor ()
    {
        super('Preloader');
    }

    preload ()
    {
        this.load.setBaseURL('https://cdn.phaserfiles.com/v385');
        this.loading = this.add.image(512, 384, 'loading');

        this.load.setPath('assets/games/bank-panic/');

        this.load.image('start');
        this.load.image('title');
        this.load.image('logo');
        this.load.image('background');
        this.load.image('bulletHole', 'bullet-hole.png');
        this.load.atlas('https://labs.phaser.io/assets', 'bank-panic.png', 'bank-panic.json');

        this.load.audio('shot', [ 'shot.ogg', 'shot.m4a', 'shot.mp3' ]);
        this.load.audio('banditShot', [ '50cal.ogg', '50cal.m4a', '50cal.mp3' ]);
        this.load.audio('money', [ 'money.ogg', 'money.m4a', 'money.mp3' ]);
        this.load.audio('levelComplete', [ 'complete.ogg', 'complete.m4a', 'complete.mp3' ]);
        this.load.audio('gameOver', [ 'gameover.ogg', 'gameover.m4a', 'gameover.mp3' ]);
        this.load.audio('music', [ 'music.ogg', 'music.m4a', 'music.mp3' ]);
        this.load.audio('door', [ 'door.ogg', 'door.m4a', 'door.mp3' ]);
        this.load.audio('scream1', [ 'scream1.ogg', 'scream1.m4a', 'scream1.mp3' ]);
        this.load.audio('scream2', [ 'scream2.ogg', 'scream2.m4a', 'scream2.mp3' ]);
        this.load.audio('scream3', [ 'scream3.ogg', 'scream3.m4a', 'scream3.mp3' ]);
    }

    create ()
    {
        //  Create our global animations

        this.anims.create({
            key: 'doorOpen',
            frames: this.anims.generateFrameNames('https://labs.phaser.io/assets', { prefix: 'door', start: 1, end: 5 }),
            frameRate: 20
        });

        this.anims.create({
            key: 'doorClose',
            frames: this.anims.generateFrameNames('https://labs.phaser.io/assets', { prefix: 'door', start: 5, end: 1 }),
            frameRate: 20
        });

        this.loading.setTexture('start');

        this.loading.setInteractive();

        this.loading.once('pointerdown', () => {
            this.scene.start('MainMenu');
        });
    }
}
                        
import Door from './Door.js';

export default class MainGame extends Phaser.Scene
{
    constructor ()
    {
        super('MainGame');

        this.hats;
        this.goals;
        this.gold;
        this.doors;

        this.isPaused = false;
        this.goalsComplete = 0;
        this.sign;

        this.level = 1;
        this.levelImage;

        this.killDelay = 0.7;
        this.closeDurationLow = 2000;
        this.closeDurationHigh = 4000;
    }

    create ()
    {
        this.add.image(512, 384, 'background');

        //  Level text
        this.add.image(450, 650, 'https://labs.phaser.io/assets', 'levelText');

        this.levelImage = this.add.image(600, 650, 'https://labs.phaser.io/assets', '1');

        this.createGoals();
        this.createDoors();

        this.hats = this.add.group({
            defaultKey: 'https://labs.phaser.io/assets',
            defaultFrame: 'hat',
            key: 'https://labs.phaser.io/assets',
            frame: 'hat',
            active: false,
            visible: false,
            repeat: 32,
            maxSize: 32
        });

        this.gold = this.add.group({
            defaultKey: 'https://labs.phaser.io/assets',
            defaultFrame: 'gold',
            key: 'https://labs.phaser.io/assets',
            frame: 'gold',
            active: false,
            visible: false,
            repeat: 11,
            maxSize: 12
        });

        this.isPaused = false;

        this.level = 1;
        this.killDelay = 0.8;
        this.closeDurationLow = 2000;
        this.closeDurationHigh = 4000;

        this.doors.forEach((door) => {
            door.start(this.game.getTime());
        });
    }

    createGoals ()
    {
        this.goals = [];
        this.goalsComplete = 0;

        for (let i = 1; i <= 12; i++)
        {
            this.goals.push(this.add.image(0, 0, 'https://labs.phaser.io/assets', i));
        }

        Phaser.Actions.GridAlign(this.goals, {
            width: 12,
            height: 1,
            cellWidth: 80,
            cellHeight: 36,
            x: 80,
            y: 86
        });
    }

    createDoors ()
    {
        this.doors = [];

        let doorWidth = 200;
        let doorSpace = Math.floor((1024 - (doorWidth * 4)) / 5);

        let x = 100 + doorSpace;
        let y = 352;

        for (let i = 1; i <= 4; i++)
        {
            this.doors.push(new Door('Door' + i, this, x, y))

            x += doorWidth + doorSpace;
        }
    }

    addGold (x, y)
    {
        let target = this.goals[this.goalsComplete];

        let gold = this.gold.get(x + 50, y + 100);

        gold.setActive(true).setVisible(true);

        this.sound.play('money');

        this.tweens.add({
            targets: gold,
            x: target.x,
            y: target.y,
            duration: 600,
            ease: 'Quad.easeOut',
            onComplete: () => {
                target.setVisible(false);
            }
        });

        this.goalsComplete++;

        if (this.goalsComplete === 12)
        {
            this.levelComplete();
        }
    }

    addHat (x, y, stackPosition)
    {
        y = 180 + (30 * (5 - stackPosition));

        let hat = this.hats.get(x, y);

        hat.setActive(true).setVisible(true);
        hat.setScale(1).setAlpha(1);

        const destX = Phaser.Math.RND.between(x - 400, x + 400);
        const destY = y - 400;

        this.tweens.add({
            targets: hat,
            x: destX,
            y: destY,
            angle: 960,
            duration: 1000,
            ease: 'Quad.easeOut',
            onComplete: () => {
                hat.setActive(false);
                hat.setVisible(false);
            }
        });
    }

    levelFail ()
    {
        this.isPaused = true;

        this.sign = this.add.image(512, -200, 'https://labs.phaser.io/assets', 'gameOver');

        this.sound.play('gameOver');

        this.tweens.add({
            targets: this.sign,
            y: 384,
            ease: 'Bounce.easeOut',
            duration: 1500,
            onComplete: () => {
                this.input.once('pointerdown', () => this.scene.start('MainMenu'));
            }
        });
    }

    levelComplete ()
    {
        this.isPaused = true;

        this.sign = this.add.image(512, -200, 'https://labs.phaser.io/assets', 'levelComplete');

        this.sound.play('levelComplete');

        this.tweens.add({
            targets: this.sign,
            y: 384,
            ease: 'Bounce.easeOut',
            duration: 1500,
            onComplete: () => {
                this.input.once('pointerdown', () => this.nextLevel());
            }
        });
    }

    nextLevel ()
    {
        this.goals.forEach((goal, index) => {
            goal.setFrame((index + 1).toString());
            goal.setVisible(true);
        });

        this.gold.getChildren().forEach((gold) => {
            gold.setVisible(false);
            gold.setActive(false);
        });

        //  Reset everything
        this.doors.forEach((door) => {
            door.reset(this.game.getTime());
        });

        this.goalsComplete = 0;

        //  Change difficulty

        if (this.level < 5)
        {
            this.killDelay -= 0.1;
        }

        if (this.level < 10)
        {
            this.closeDurationLow -= 100;
            this.closeDurationHigh -= 200;
        }

        //  Change level counter
        this.level++;

        this.levelImage.setFrame(this.level);

        this.sign.setVisible(false);

        this.isPaused = false;
    }

    killed (x, y)
    {
        //  Bullet holes on the screen

        let offsetX = 100;

        for (let i = 0; i < 3; i++)
        {
            let x = Phaser.Math.RND.between(offsetX, offsetX + 200);
            let y = Phaser.Math.RND.between(200, 600);

            let hole = this.add.image(x, y, 'bulletHole').setAlpha(0);

            this.tweens.add({
                targets: hole,
                alpha: 1,
                duration: 30,
                delay: 200 * i
            });

            offsetX += 340;
        }

        this.levelFail();
    }

    update (time)
    {
        if (!this.isPaused)
        {
            this.doors.forEach((door) => {
                door.update(time);
            });
        }
    }
}
                        
import Boot from './Boot.js';
import Preloader from './Preloader.js';
import MainMenu from './MainMenu.js';
import MainGame from './Game.js';

const config = {
    type: Phaser.AUTO,
    width: 1024,
    height: 768,
    backgroundColor: '#2e91f3',
    parent: 'phaser-example',
    scene: [ Boot, Preloader, MainMenu, MainGame ]
};

let game = new Phaser.Game(config);
                        
export default class Door extends Phaser.GameObjects.Container
{
    constructor (name, scene, x, y)
    {
        super(scene, x, y);

        this.name = name;

        this.background = scene.add.image(0, 0, 'https://labs.phaser.io/assets', 'doorBackground');
        this.door = scene.add.sprite(0, 0, 'https://labs.phaser.io/assets', 'door1');
        this.character = scene.add.image(0, 0, 'https://labs.phaser.io/assets', 'bandit1');
        this.characterFrame = 'bandit1';

        this.isOpen = false;
        this.isBandit = false;
        this.isHats = false;
        this.isDead = false;
        this.wasBandit = (scene.doors.length % 2) ? true : false;

        this.hats = 0;
        this.timeToOpen = Number.MAX_SAFE_INTEGER;
        this.timeToClose = Number.MAX_SAFE_INTEGER;
        this.timeToKill = 0;

        this.characters = [
            'bandit1',
            'bandit2',
            'cowboy1',
            'cowboy2',
            'hat'
        ];

        this.add([ this.background, this.character, this.door ]);

        this.setSize(200, 400);
        this.setInteractive();

        this.on('pointerup', this.shoot, this);

        scene.add.existing(this);
    }

    destroy ()
    {
        this.off('pointerup');
    }

    start (time)
    {
        this.timeToOpen = time + Phaser.Math.RND.between(500, 4000);
    }

    reset (time)
    {
        this.isOpen = false;
        this.isBandit = false;
        this.isHats = false;
        this.isDead = false;

        this.door.play('doorClose');

        this.timeToOpen = time + Phaser.Math.RND.between(500, 4000);
    }

    openDoor (time)
    {
        this.isOpen = true;
        this.isBandit = false;
        this.isHats = false;
        this.isDead = false;

        this.characterFrame = Phaser.Utils.Array.GetRandom(this.characters);

        //  When should this door close again?
        const duration = Phaser.Math.RND.between(this.scene.closeDurationLow, this.scene.closeDurationHigh);

        this.timeToClose = time + duration;

        if (this.characterFrame === 'bandit1' || this.characterFrame === 'bandit2')
        {
            this.isBandit = true;
        }
        else if (this.characterFrame === 'hat')
        {
            this.isHats = true;

            //  Pick random number of hats
            this.hats = Phaser.Math.RND.between(2, 5);

            this.characterFrame += this.hats.toString();
        }
        else
        {
            this.timeToClose = time + (duration / 2);
        }

        //  If we had a citizen or hats on our last go, we have to have a bandit now
        if (!this.wasBandit && !this.isBandit)
        {
            this.isHats = false;
            this.hats = 0;
            this.isBandit = true;
            this.characterFrame = (Math.random() > 0.5) ? 'bandit1' : 'bandit2';
            this.timeToClose = time + duration;
        }

        this.character.setFrame(this.characterFrame);
        this.character.setScale(1).setAlpha(1);

        if (this.isBandit)
        {
            this.timeToKill = time + (duration * this.scene.killDelay);
        }

        this.scene.sound.play('door');

        this.door.play('doorOpen');
    }

    closeDoor (time)
    {
        this.door.play('doorClose');

        this.isOpen = false;
        this.wasBandit = this.isBandit;

        if (!this.isBandit && !this.isHats && !this.isDead)
        {
            this.scene.addGold(this.x, this.y);
        }

        //  When should this door open again?
        this.timeToOpen = time + Phaser.Math.RND.between(2000, 4000);
    }

    shoot ()
    {
        if (!this.isOpen || this.scene.isPaused)
        {
            return;
        }

        this.scene.sound.play('shot');

        if (this.isDead)
        {
            //  We will want to hear the gunshot, but not actually do anything with it
            return;
        }

        if (this.isBandit)
        {
            this.shootCharacter(true);
        }
        else
        {
            if (this.isHats)
            {
                this.shootHat();
            }
            else
            {
                this.shootCharacter(false);
            }
        }
    }

    shootCharacter (closeDoor)
    {
        this.isDead = true;

        this.characterFrame += 'Dead';

        this.character.setFrame(this.characterFrame);

        this.scene.sound.play('scream' + Phaser.Math.RND.between(1, 3));

        this.scene.tweens.add({
            targets: this.character,
            scaleX: 0.5,
            scaleY: 0.5,
            duration: 300,
            onComplete: () => {
                if (closeDoor)
                {
                    this.closeDoor(this.scene.game.getTime());
                }
                else
                {
                    this.scene.levelFail();
                }
            }
        });

        //  No more shots at this door
        if (!closeDoor)
        {
            this.off('pointerup');
            this.scene.isPaused = true;
        }
    }

    shootHat ()
    {
        if (this.hats > 0)
        {
            this.scene.addHat(this.x, this.y, this.hats);

            this.hats--;

            this.characterFrame = 'hat' + this.hats;
        }

        this.character.setFrame(this.characterFrame);
    }

    shootYou ()
    {
        this.off('pointerup');

        this.scene.isPaused = true;

        //  Shots

        let shot1 = this.scene.add.image(this.x, this.y, 'https://labs.phaser.io/assets', this.characterFrame + 'shot1');
        let shot2 = this.scene.add.image(this.x, this.y, 'https://labs.phaser.io/assets', this.characterFrame + 'shot2');

        this.scene.sound.play('banditShot');
        this.scene.sound.play('banditShot', { delay: 0.25 });
        this.scene.sound.play('banditShot', { delay: 0.5 });

        this.scene.tweens.add({
            targets: shot1,
            alpha: 0,
            duration: 200,
            ease: 'Power2'
        });

        this.scene.tweens.add({
            targets: shot2,
            alpha: 0,
            duration: 200,
            delay: 200,
            ease: 'Power2',
            onComplete: () => {
                this.scene.killed(this.x, this.y);
            }
        });

        //  Gun smoke rising from the bandit

        let smoke1 = this.scene.add.image(this.x, this.y, 'https://labs.phaser.io/assets', this.characterFrame + 'smoke1');
        let smoke2 = this.scene.add.image(this.x, this.y, 'https://labs.phaser.io/assets', this.characterFrame + 'smoke2');

        this.scene.tweens.add({
            targets: smoke1,
            props: {
                y: { value: 150, duration: 1000, ease: 'Sine.easeInOut' },
                alpha: { value: 0, duration: 250, ease: 'Power2', delay: 750 }
            }
        });

        this.scene.tweens.add({
            targets: smoke2,
            props: {
                y: { value: 150, duration: 1000, ease: 'Sine.easeInOut', delay: 500 },
                alpha: { value: 0, duration: 250, ease: 'Power2', delay: 1250 }
            }
        });
    }

    update (time)
    {
        if (!this.isOpen && time >= this.timeToOpen)
        {
            this.openDoor(time);
        }
        else if (this.isOpen && time >= this.timeToClose)
        {
            this.closeDoor(time);
        }
        else if (this.isOpen && this.isBandit && !this.isDead && time >= this.timeToKill)
        {
            this.shootYou();
        }
    }
}