Creating a simple game with PhaserJS
Tags: typescript, javascript, gamedev, phaserjs
Published: 2021-04-22 13:54:46

![Phaser header](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6bb2umhm1us8yq741v99.png)

## Introduction

We are about to talk about how we did create a simple game with **PhaserJS** and also why we did it. 

We will talk only about front-end part (the game here) and  skip the backend.

Back to the game, what we want to create is a 2D game where our player must avoid and/or catch falling items. We will use a Typescript version boilerplate for this tutorial.
Side advice, keep this docs near you : [https://newdocs.phaser.io/docs/3.54.0](https://newdocs.phaser.io/docs/3.54.0)

## Overview
- Step 1 : Game settings and main scene 🎛
- Step 2 : Player and basic interactions 🤾‍♂️
- Step 3 : Enemies ans collectibles 👾
- Step 4 : Boss 😱
- Step 5 : Game difficulties 😌 -> 🤬

## TLDR;
- Repository at the end
- Playable version at the end

## Step 1 : Game settings and main scene 🎛

First things first, we need to setup our game based on PhaserIO docs. Use index.html (or whatever file you want as entry point for your project) 
Inside this file, you'll only need to import inside your `<head>` your game file (javascript): 
```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="user-scalable=0"/>
    <script src="game.js"></script>
    <style>
        body {
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body>
</body>
</html>
```
Game config is quite simple as we are just putting basic settings, like the kind of physics, background color and gravity. Start scene is set to Main (it will be our main file for next steps).
```Typescript
import 'phaser';
import Main from './scenes/Main';

const config = {
    backgroundColor: '#f4f4f6',
    dom: {
        createContainer: true
    },
    height: window.innerHeight,
    parent: 'gameContainer',
    physics: {
        default: 'arcade',
        arcade: {
            debug: true,
            gravity: { y: 150 }
        }
    },
    scene: Main,
    type: Phaser.AUTO,
    width: window.innerWidth, 
};

new Phaser.Game(config);
```
For this tutorial we want to create a game that will be designed to work on smartphone. The main purpose is to move our player (a spaceship) from left and right in order to avoid enemies and catch collectibles. We will set up : 
- enemy 1 : spam (looks like virus)
- enemy 2 : boss like (looks like evil)
- collectible 1 : bike
- collectible 2 : computer
- collectible 3 : smartphone
- collectible 4 : shoes
- collectible 5 : camera
- collectible 6 : Shield (will protect our player from enemy 1)
Obviously all sprites are found on internet for free and/or created by ourself. Please be indulgent on graphics quality! 

## Step 2 : Player and basic interactions 🤾‍♂️

Here we will set our main scene on which we will insert a move our player but also our enemies and collectibles.
As declared in step 1, we make our game start on Main scene.
Our player is supposed to move from left to right but not able to move forward/backward. Game play will be quite easy as it's based on keyboard left/right arrows or on screen touch (if in left half, then move to the left).
Player will start with 3 lives and a score of 0 point. We choose to display texts on bottom corners to summarize this data.

We've also set a circle "hit box" around the player object equivalent to half its width as a radius. It means that every collision happening between player and other object will be done on a "circle".

This is our Main scene start :
```typescript
export default class Main extends Phaser.Scene {
    activePointer: Phaser.Input.Pointer;
    height: number;
    levelText: Phaser.GameObjects.Text;
    livesText: Phaser.GameObjects.Text;
    player: Phaser.Physics.Arcade.Image;
    scaleRatio: number;
    scoreText: Phaser.GameObjects.Text;
    width: number;

    constructor() {
        super('main');
        this.height = window.innerHeight;
        this.scaleRatio = window.devicePixelRatio;
        this.width = window.innerWidth;
    }

    preload() {
        this.preload.image('ship', 'assets/sprites/ship.png');
    }

    create() {
        this.cameras.main.setBackgroundColor('#0d0d21');
        this.cursors = this.input.keyboard.createCursorKeys();

        this.initData();
        this.initPlayer();
        this.initText();
    }

    update() {
        if (this.data.get('lives') === 0) {
            alert('Game over.');
        }

        this.handlePlayerUpdate();
        this.scoreText.setText(`Score: ${this.data.get('score')}`);
        this.livesText.setText(`Lives: ${this.data.get('lives')}`);
        this.levelText.setText(`Level: ${this.data.get('level')}`);
    }

    /**
     * Init scene data
     */
    initData(): void {
        this.data.set('score', 0);
        this.data.set('lives', 3);
        this.data.set('level', 1);
    }

    /**
     * Init player with hitbox and movable attributes
     */
    initPlayer(): void {
        this.player = this.physics.add.image(this.width / 2, this.height - 64, 'ship');
        this.player.setCircle(38);

        this.player.setImmovable();
        this.player.setCollideWorldBounds(true);
    }

    /**
     * Init all texts on screen that displays scene data
     */
    initText(): void {
        this.scoreText = this.add.text(20, this.height - 60, `Score: ${this.data.get('score')}`);
        this.livesText = this.add.text(this.width - 100, this.height - 40, `Lives: ${this.data.get('lives')}`);
        this.levelText = this.add.text(20, this.height - 40, `Level: ${this.data.get('level')}`);
    }

    /**
     * Handle player mouvements
     */
    handlePlayerUpdate(): void {
        this.activePointer = this.input.activePointer;
        this.player.setVelocityX(0);

        if (this.cursors.left.isDown || (this.activePointer.isDown && (this.activePointer.x < (this.width / 2)))) {
            this.player.setVelocityX(-500);
            this.player.setRotation(-0.2);
        } else if (this.cursors.right.isDown || (this.activePointer.isDown && (this.activePointer.x > (this.width / 2)))) {
            this.player.setVelocityX(500);
            this.player.setRotation(0.2);
        } else {
            this.player.setRotation(0);
        }
    }
}
```
Update loop is used to run update on scene on every game tick. 

## Step 3 : Enemies and collectibles 👾

At the end of step 2, we are able to get our player spawned in our Main scene and able to move from left to right. But we do agree, it's quite useless if we don't add any goal, and also... enemies 😈

In opposition to our player, we want to spawn collectibles and enemies every X seconds. Enemies will make player loose one life and collectibles make him win points.

We will create several timer that will perform an action every defined time. One time to make an enemy spawn, one to make a collectible appear. We will play later on this timer to increase difficulty (step 5!)

** **THIS IS THE BIGGEST STEP** **
We are only showing new code to append to Main scene (see repo at the end to full source code)

Basically, we are adding 3 timers : 
1. Collectibles spawn (earn points)
2. Enemies spawn (loose lives)
3. Buyzooka item (earn Buyzooka's shield to be protected against spam)

As you can see it's an allegory of online shopping... Buying one time = getting spammed life time. And our product protects you against that.

In every timer, we call one creation method, one per kind of game object. We do a random choose about collectibles for appearance in order to make it more realistic.

As as last step, in update loop we need to take care of every collision happening inside the scene. Like : 
- Spam hitting player (loose 1 life)
- Collectible hitting player (win 50 points)
- Buyzooka item hitting player (set shied to true)
- Spam hitting protected player (win 5 points)
Collision are managed by colliders.

We also destroy items since they are supposed to disappear visually from scene.

```typescript
export default class Main extends Phaser.Scene {
    // See step 1 attribute and append those ones
    buyzookaObjectsGroup: Phaser.Physics.Arcade.Group;
    buyzookaItemTimedEvent: Phaser.Time.TimerEvent;
    productKeys: string[] = ['bike', 'camera', 'computer', 'shoes', 'smartphone'];
    productObjectsGroup: Phaser.Physics.Arcade.Group;
    productTimedEvent: Phaser.Time.TimerEvent;
    spamObjectsGroup: Phaser.Physics.Arcade.Group;
    spamTimedEvent: Phaser.Time.TimerEvent;
    playerHasShield: boolean = false;

    constructor() {
        // See Step 1
    }

    preload() {
        // Step 1 preload + those new onees
        this.productKeys.forEach(k => {
            this.load.image(k, `assets/sprites/${k}.png`);
        });

        this.load.image('buyzooka', 'assets/sprites/buyzooka.png');
        this.load.image('spam', 'assets/sprites/spam.png');
    }

    create() {
        // See step 1 and add this method
        this.startLevel1();
    }

    update() {
        // See step 1 update method and add this
        this.checkIfSpamHitsBoundaries();
        this.checkIfBuyzookaItemHitsGround();
        this.checkIfProductHitsGround();
    }

    initData(): void {
        // See step 1
    }
    initPlayer(): void {
        // Step 1
    }
    initText(): void {
        // Step 1
    }

    /**
     * Level 1 start
     */
    startLevel1(): void {
        this.initSpamSpawn();
        this.initBuyzookaSpawn();
        this.initProductSpawn();
    }

    /**
     * Handle player mouvements
     */
     handlePlayerUpdate(): void {
        this.activePointer = this.input.activePointer;
        this.player.setVelocityX(0);

        if (this.cursors.left.isDown || (this.activePointer.isDown && (this.activePointer.x < (this.width / 2)))) {
            this.player.setVelocityX(-500);
            this.player.setRotation(-0.2);
        } else if (this.cursors.right.isDown || (this.activePointer.isDown && (this.activePointer.x > (this.width / 2)))) {
            this.player.setVelocityX(500);
            this.player.setRotation(0.2);
        } else {
            this.player.setRotation(0);
        }
    }

    /**
     * Init spam spawn 
     */
    initSpamSpawn(): void {
        this.spamObjectsGroup = this.physics.add.group({
            defaultKey: 'spam',
            collideWorldBounds: false
        });

        this.spamTimedEvent = this.time.addEvent({ delay: 1000, callback: this.createSpam, callbackScope: this, loop: true });
        this.physics.add.collider(this.spamObjectsGroup, this.player, (o1, o2) => this.spamHitsPlayer(o1, o2), null, this);
    }

    /**
     * Init buyzooka item spawn
     */
    initBuyzookaSpawn(): void {
        this.buyzookaObjectsGroup = this.physics.add.group({
            defaultKey: 'buyzooka',
            collideWorldBounds: true
        });

        this.buyzookaObjectsGroup.scaleXY(this.scaleRatio, this.scaleRatio);
        this.buyzookaItemTimedEvent = this.time.addEvent({ delay: 10200, callback: this.createBuyzookaItem, callbackScope: this, loop: true });
        this.physics.add.collider(this.buyzookaObjectsGroup, this.player, (o1, o2) => this.buyzookaItemHitsPlayer(o1, o2), null, this);
    }

    /**
     * Init product spawn
     */
    initProductSpawn(): void {
        this.productObjectsGroup = this.physics.add.group({
            defaultKey: 'shoes'
        });

        this.productObjectsGroup.scaleXY(this.scaleRatio, this.scaleRatio);
        this.productTimedEvent = this.time.addEvent({ delay: 2100, callback: this.createProduct, callbackScope: this, loop: true });
        this.physics.add.collider(this.productObjectsGroup, this.player, (o1, o2) => this.productHitsPlayer(o1, o2), null, this);
    }

    /**
     * Create a spam in scene
     */
     createSpam(): void {
        const spam = this.spamObjectsGroup.create(this.getRandomX(), 0);
        spam.setCircle(spam.width / 2);
        spam.body.bounce.set(1);
    }

    /**
     * Create buyzooka item in scene
     */
    createBuyzookaItem(): void {
        if (this.playerHasShield) {
            this.buyzookaItemTimedEvent.remove();
            return;
        }

        const item = this.buyzookaObjectsGroup.create(this.getRandomX(), 0);
        item.setCircle(item.width / 2);
    }

    /**
     * Create product in scene
     */
    createProduct(): void {
        const product = this.productObjectsGroup.create(this.getRandomX(), 0, this.getRandomProductKey());
        product.setCircle(product.width / 2);
    }

    /**
     * Handle spam mouvement and destroy when hitting scene boudaries
     */
    checkIfSpamHitsBoundaries(): void {
        const spams = this.spamObjectsGroup.getChildren();
        spams.forEach(spam => {
            const spamObj = (spam as Phaser.GameObjects.Image);
            if (
                (spamObj.y + spamObj.height) < this.height
                && spamObj.x > spamObj.width 
                && spamObj.x + spamObj.width < this.width
            ) {
                return;
            }

            this.spamObjectsGroup.remove(spam, true, true);
        });
    }

    /**
     * Remove buyzooka's item when hit the ground
     */
    checkIfBuyzookaItemHitsGround(): void {
        const items = this.buyzookaObjectsGroup.getChildren();
        items.forEach(item => {
            const itemObj = (item as Phaser.GameObjects.Image);
            if ((itemObj.y + itemObj.height) < this.height) {
                return;
            }

            this.buyzookaObjectsGroup.remove(item, true, true);
        });
    }

    /**
     * Remove product when hit the ground
     */
    checkIfProductHitsGround(): void {
        const products = this.productObjectsGroup.getChildren();
        products.forEach(product => {
            const productObj = (product as Phaser.GameObjects.Image);
            if ((productObj.y + productObj.height) < this.height) {
                return;
            }

            if (this.playerHasShield) {
                this.decrementsLives();
            }

            this.productObjectsGroup.remove(product, true, true);
        });
    }

    /**
     * Triggered when spam hits player
     * 
     * @param player 
     * @param spam 
     */
     spamHitsPlayer(player: Phaser.Types.Physics.Arcade.GameObjectWithBody, spam: Phaser.Types.Physics.Arcade.GameObjectWithBody): void {
        if (this.playerHasShield) {
            this.addScore(5);
        } else {
            this.decrementsLives();
            this.spamObjectsGroup.remove(spam, true, true);
        }
    }

    /**
     * Triggered when buyzooka's item hits player
     * 
     * @param player 
     * @param item 
     */
    buyzookaItemHitsPlayer(player: Phaser.Types.Physics.Arcade.GameObjectWithBody, item: Phaser.Types.Physics.Arcade.GameObjectWithBody): void {
        if (this.playerHasShield) {
            return;
        } else {
            this.playerHasShield = true;
            this.player.setTexture('shielded_ship');
        }

        this.buyzookaObjectsGroup.remove(item, true, true);
    }

    /**
     * Triggered when product hits player
     * 
     * @param player 
     * @param product 
     */
    productHitsPlayer(player: Phaser.Types.Physics.Arcade.GameObjectWithBody, product: Phaser.Types.Physics.Arcade.GameObjectWithBody): void {
        this.addScore(50);
        this.productObjectsGroup.remove(product, true, true);
    }

    /**
     * Add points to player's score
     * 
     * @param points 
     */
    private addScore(points: number): void {
        this.data.inc('score', points);
    }

    /**
     * Decrement player's remaining lives
     */
    private decrementsLives(): void {
        this.data.inc('lives', -1);
    }
}
```

## Step 4 : Boss 😱

The game is now running in a very basic way : catch items and avoid spams.
We want now to add some difficulty by adding a boss (or several 😈). Again we are doing a parallel with real life.

Our boss character is supposed to act like a big marketplace (that everyone knows). He will "eat" literally all collectibles (so you won't get points). But Buyzooka's shield can help you to make war to this and give back power to small/local retailers.

Here we will add a loop, as for enemies and collectibles/item to make it appears and manage every new collision on Main scene :
- boss hits player
- boss hits collectible
Side info, we've called the boss "Amazin". He's not supposed to appear only one time, but in loop and probably several occurence at a time if you're not fast enough.

As a summary at step 3 you must :
- catch collectible to win point
- avoid collectible to hit the ground (loose lives)
- kill boss to avoid collectible destruction

This is what we've added in code for step 3. (We've skipped step 1 & 2 code to stay focus on new code)

```typescript
export default class Main extends Phaser.Scene {
    // See step 1 attributes and append those ones
    // See also step 2 attributes and append new ones
    amazinObjectsGroup: Phaser.Physics.Arcade.Group;
    amazinItemTimedEvent: Phaser.Time.TimerEvent;

    constructor() {
        // See Step 1
    }

    preload() {
        // Step 1 preload + those new onees
        // Step 2 preload + new ones 
        this.load.image('amazin', 'assets/sprites/amazin.png');
    }

    create() {
        // See step 2
    }
    update() {
        // See step 2
    }
    initData(): void {
        // See step 1
    }
    initPlayer(): void {
        // Step 1
    }
    initText(): void {
        // Step 1 and append 
        this.initAmazinSpawn();
    }
    startLevel1(): void {
        // Step 2
    }

    /**
     * Init Amazin spawn
     */
     initAmazinSpawn(): void {
        this.amazinObjectsGroup = this.physics.add.group({
            defaultKey: 'amazin'
        });

        this.amazinItemTimedEvent = this.time.addEvent({ delay: 30 * 1000, callback: this.startLevel2, callbackScope: this, loop: false});
        this.physics.add.collider(this.amazinObjectsGroup, this.player, (o1, o2) => this.amazinHitsPlayer(o1, o2), null, this);
        this.physics.add.collider(this.amazinObjectsGroup, this.productObjectsGroup, (o1, o2) => this.amazinHitsProduct(o1, o2), null, this);
    }

    /**
     * Start level 2
     */
    startLevel2(): void {
        this.data.set('level', 2);

        this.spamTimedEvent.remove();
        this.buyzookaItemTimedEvent.remove();

        this.time.addEvent({ delay: 0, callback: this.createAmazin, callbackScope: this });
        this.amazinItemTimedEvent = this.time.addEvent({ delay: 20 * 1000, callback: this.createAmazin, callbackScope: this, loop: true });
        this.productTimedEvent = this.time.addEvent({ delay: 800, callback: this.createProduct, callbackScope: this, loop: true });
        this.spamTimedEvent = this.time.addEvent({ delay: 400, callback: this.createSpam, callbackScope: this, loop: true });
    }
    handlePlayerUpdate(): void {
        // Step 2
    }
    initSpamSpawn(): void {
        // Step 2
    }
    initBuyzookaSpawn(): void {
        // Step 2    
    }
    initProductSpawn(): void {
        // Step 2
    }
    createSpam(): void {
        // Step 2
    }
    createBuyzookaItem(): void {
        // Step 2
    }
    createProduct(): void {
        // Step 2
    }
    checkIfSpamHitsBoundaries(): void {
        // Step 2
    }
    checkIfBuyzookaItemHitsGround(): void {
        // Step 2
    }
    checkIfProductHitsGround(): void {
        // Step 2
    }
    spamHitsPlayer(player: Phaser.Types.Physics.Arcade.GameObjectWithBody, spam: Phaser.Types.Physics.Arcade.GameObjectWithBody): void {
        // Step 2
    }
    buyzookaItemHitsPlayer(player: Phaser.Types.Physics.Arcade.GameObjectWithBody, item: Phaser.Types.Physics.Arcade.GameObjectWithBody): void {
        // Step 2
    }
    productHitsPlayer(player: Phaser.Types.Physics.Arcade.GameObjectWithBody, product: Phaser.Types.Physics.Arcade.GameObjectWithBody): void {
        // Step 2
    }
    
    /**
     * Triggered when Amazin hits the player
     * 
     * @param player 
     * @param amazin 
     */
     amazinHitsPlayer(player: Phaser.Types.Physics.Arcade.GameObjectWithBody, amazin: Phaser.Types.Physics.Arcade.GameObjectWithBody): void {
        if (this.playerHasShield) {
            const newLives = +(amazin.getData('lives')) - 1;
            amazin.setData('lives', newLives);

            if (newLives <= 0) {
                amazin.body.bounce.set(0);
                this.amazinObjectsGroup.remove(amazin, true, true);
                this.addScore(135);
            }
        } else {
            this.decrementsLives();
        }
    }

    /**
     * Triggered when Amazin hits a product 
     * 
     * @param product 
     * @param amazin 
     */
    amazinHitsProduct(amazin: Phaser.Types.Physics.Arcade.GameObjectWithBody, product: Phaser.Types.Physics.Arcade.GameObjectWithBody): void {
        this.productObjectsGroup.remove(product, true, true);
    }

    private addScore(points: number): void {
        // Step 2
    }
    private decrementsLives(): void {
        // Step 2
    }
}
```

## Step 5 : Game difficulties 😌 -> 🤬

On the last step we are only playing on a difficulty factor that will reduce time needed to make spawn appear. It will drive players crazy to make them loose :p

This is the new code 

```typescript
export default class Main extends Phaser.Scene {
    // See step 1 attributes and append those ones
    // See also step 2 attributes and append new ones
    // See step step 4 and add 
    difficulty: number;

    constructor() {
        // See Step 2 and add 

        this.difficulty = 1;
    }

    preload() {
        // Step 2 preload + those new onees
        // Step 3 preload + new ones 
        // Step 4 peaload
    }

    create() {
        // See step 2
    }
    update() {
        // See step 2
    }

    initData(): void {
        // See step 1
    }
    initPlayer(): void {
        // Step 2
    }
    initText(): void {
        // Step 4
    }
    startLevel1(): void {
        // Step 3
    }
    initAmazinSpawn(): void {
        // Step 4 
    }

    /**
     * Start level 2
     */
    startLevel2(): void {
        // Step 4 and add : 
        this.time.addEvent({ delay: 30 * 1000, callback: this.nextLevel, callbackScope: this });
    }

    /**
     * Start next level
     */
    nextLevel(): void {
        this.data.inc('level');
        this.difficulty++;
        this.spamTimedEvent.remove();
        this.productTimedEvent.remove();
        this.amazinItemTimedEvent.remove();

        this.time.addEvent({ delay: 0, callback: this.createAmazin, callbackScope: this });
        this.amazinItemTimedEvent = this.time.addEvent({ delay: (20 * 1000) / this.difficulty, callback: this.createAmazin, callbackScope: this, loop: true });
        this.productTimedEvent = this.time.addEvent({ delay: 800 / this.difficulty, callback: this.createProduct, callbackScope: this, loop: true });
        this.spamTimedEvent = this.time.addEvent({ delay: 400 / this.difficulty, callback: this.createSpam, callbackScope: this, loop: true });
    }
    
    handlePlayerUpdate(): void {
        // Step 3
    }
    initSpamSpawn(): void {
        // Step 3
    }
    initBuyzookaSpawn(): void {
        // Step 3    
    }
    initProductSpawn(): void {
        // Step 3
    }
    createSpam(): void {
        // Step 3
    }
    createBuyzookaItem(): void {
        // Step 3
    }
    createProduct(): void {
        // Step 3
    }
    checkIfSpamHitsBoundaries(): void {
        // Step 3
    }
    checkIfBuyzookaItemHitsGround(): void {
        // Step 3
    }
    checkIfProductHitsGround(): void {
        // Step 3
    }
    spamHitsPlayer(player: Phaser.Types.Physics.Arcade.GameObjectWithBody, spam: Phaser.Types.Physics.Arcade.GameObjectWithBody): void {
        // Step 3
    }
    buyzookaItemHitsPlayer(player: Phaser.Types.Physics.Arcade.GameObjectWithBody, item: Phaser.Types.Physics.Arcade.GameObjectWithBody): void {
        // Step 3
    }
    productHitsPlayer(player: Phaser.Types.Physics.Arcade.GameObjectWithBody, product: Phaser.Types.Physics.Arcade.GameObjectWithBody): void {
        // Step 3
    }
     amazinHitsPlayer(player: Phaser.Types.Physics.Arcade.GameObjectWithBody, amazin: Phaser.Types.Physics.Arcade.GameObjectWithBody): void {
        // Step 4
    }
    amazinHitsProduct(amazin: Phaser.Types.Physics.Arcade.GameObjectWithBody, product: Phaser.Types.Physics.Arcade.GameObjectWithBody): void {
        // Step 4
    }
    private addScore(points: number): void {
        // Step 3
    }
    private decrementsLives(): void {
        // Step 3
    }
}
```

## Addendum

We are exposing on this article the big pictures of our game mechanisms. You'll find out that in the final repository, some things have changed, mostly because of design problem (assets size etc). But if you understand the whole article, then the source code will look crystal clear to you!

In final repo you'll see that we've added several other scenes that are explanation about the game mechanism. No purpose here.

### Why?

Here at Buyzooka we are looking for some ways to promote our product and get some users or leads with the less money possible.
As every startup, we count every dollar spent 😅.
Our idea was to create a game that will explain the features of our product in a simple, and very imaged way (a game). Doing some Growth Hacking was obviously our best choice, because classic marketing for acquisition is something we did by the past without any success (even if we found the best way to communicate with people). But as we are creating a web-extension, we think that it's quite complicated to make them instal, signup and use our product easily. Web-extensions are unfortunately not a common use.

That's why the game will be released on mobile in order to get users in our database (like "leads"). We will be able to tell them to install the real application (under development), later that Spring. We will use Ionic to embed the game inside and iframe.

### How?

In order to get some viral effect we do provide to our game player, a promise of getting our next premium version. I'll not explain the content of that premium version here.
In order to get that premium version we need our player to give us their e-mail address and do some referral

## Conclusion 

Game is fully playable online here : [https://play.buyzooka.io](https://play.buyzooka.io?ref=devto)

## Repository and credits

Every feedback is welcome!
Give us some stars ⭐️ and spread the word! :p
{% github Buyzooka/initiator-game no-readme %}

PhaserJS : https://phaser.io/