Having Fun with Browser Games & Web databases
Tags: javascript, kintone, gamedev, phaserio
Published: 2019-10-25 13:19:46

# Introduction
This post introduces how to spin up a phaser.io game integrated with a Kintone Web Database. 


![GIF of the invaders game on a Kintone Web Database](https://thepracticaldev.s3.amazonaws.com/i/lvkj9nqfkancgioqo4re.gif)

There's no need to set up a node.js environment, to get an AWS serverless environment ready, or to install the latest .NET framework.

If you have a browser and a text editor, you can follow along easily.
And no, you won't be needing a credit card, so you can put that away.

## What is Kintone?
Kintone is a cloud platform for teams with a visual web database builder. Kintone's REST APIs and JavaScript APIs enable further extensions to be built on top of the platform.
https://www.kintone.com/

## What is Phaser.io?
Phaser is a free 2D game framework for making HTML5 games for desktop and mobile.
https://phaser.io/

# Prerequisites
## A Kintone Cloud Environment
Kintone is a cloud service that has flexible web database features.
We will be creating a web database on Kintone, and then building a phaser.io game on top of that.

You can obtain a Kintone environment by either applying for a free [Kintone Developer License](https://developer.kintone.io/hc/en-us/articles/220582587-Get-a-Developer-License), or requesting for a free trial through [Kintone.com](https://www.kintone.com/). The former is more handy, as the developer license will be available to you indefinitely. 

## Phaser.io Files
In this example, we will be using the Phaser 2 library and an example game listed on their website, [Invaders](https://phaser.io/examples/v2/games/invaders). I understand that Phaser 3 is available, but to be honest, I couldn't find any good game examples that use Phaser 3.

### The Phaser Version 2 Library
This library can be obtained from the below page.
https://phaser.io/download/release/2.13.3
Download the js file to your local computer.

![A screenshot showing which js file to download from the phaser.io website to obtain the phaser2 library](https://thepracticaldev.s3.amazonaws.com/i/vsa2asjgi14pu3qa08rv.png)

### The Invaders Game Example Code
This code is available on the below page.
https://phaser.io/examples/v2/games/invaders#download
Save the zip file on to your local computer and extract the phaser-examples-master folder.

![A screenshot showing which zip file to download from the phaser.io website to obtain the invader game related files](https://thepracticaldev.s3.amazonaws.com/i/61s9rp2m2vrb76ec3h3k.png)

# Running the Invaders Game on Kintone
Follow these 5 steps to start running the Invaders game on Kintone

## 1. Create a Kintone App
Web databases in Kintone are called "Apps".
Once you log in, click on the + sign on the Apps widget to start creating a new App. Select "Create App from Scratch", and give a name to your App.
On the form editor, drag and drop 2 number fields into the form. Name them "Scale" and "Health", and also set their field codes as "Scale" and "Health". 

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/00w1cdro0m6qjptfsi1m.gif)

Save the form, and click on the blue Activate App button.
Your web database is now ready, and you will be directed to a page that holds a list of your data. Except that you don't have any data yet, so it will contain no data. 

![Screenshot of Kintone, with a list view and no data](https://thepracticaldev.s3.amazonaws.com/i/5cxeynxd72p0s0nhonrg.png)

Which is where we move on to our next step.

## 2. Input Some Data
On this List view page, click on the + button to start adding data to the App.
You'll notice that the input form is the form you created in the previous step. Let's place in the value *0.2* for the Scale field, and the value *5* for the Health field. Click on Save to save this input data.


![GIF of filling in record values into Kintone](https://thepracticaldev.s3.amazonaws.com/i/h9jtw4bqh03447wmaxj9.gif)



Click on the + button again to input some new data. Let's place in *1* and *10* for Scale and Health.
Repeat this process again, and place in *0.7* and *2* for Scale and Health.

OK, once we are done here, click on the name of your App to navigate to the List view. Previously, we saw this page had not data. Now that you've put new data inside, it should now look a bit more lively.

![Screenshot of Kintone, with 3 records of data inside the record list view](https://thepracticaldev.s3.amazonaws.com/i/okq6cyvbmtc7uump45rs.png)

We will be utilizing this data in a later step.

## 3. Create a Custom View
Access the App's setting page by clicking the cog wheel.
The view tab allows you to set new views by limiting the fields you want to see, and by setting filter conditions. Kintone also allows a "Calendar view and" a "Custom view".
In this example, we will be using a Custom view, which is a view defined by HTML elements.

Click on the + button to add a new view, and select "Custom view". In the "HTML Code" option, insert the following html:
```html
<div id="phaser-example"></div>
```

![Screenshot of Kintone, with HTML inserted into the HTML field](https://thepracticaldev.s3.amazonaws.com/i/xovbxqflfycyozutymcy.png)

Click Save, and then the Update App button to apply these new settings.

## 4. Make Edits to the Invaders JavaScript File
We need to modify the invaders.js file before applying it to the Kintone App.
Open up the invaders file in your local editor. This file can be found in the phaser-examples-master folder under *examples > games*.

### Update the preload() Function
By default, the Invaders code is loading images in this method by stating a local path. As we are activating this game on the browser, we need to change these paths to a valid path. Unfortunately, Kintone doesn't have a stable place to host image files (I hope this gets updated soon!), so upload the images onto an image hosting service such as https://imgur.com and reference these images in the preload() function:

```js
game.load.image('bullet','{path to bullet.png}');
game.load.image('enemyBullet', '{path to enemy-bullet.png}');
game.load.spritesheet('invader', '{path to invader32x32x4.png}', 32, 32);
game.load.image('ship', '{path to player.png}');
game.load.spritesheet('kaboom', '{path to explode.png}', 128, 128);
game.load.image('starfield', '{path to starfield.png}');
//game.load.image('background', '{path to background2.png}'); <-- this file is not used
```

If you are using imgur, the link should be in the format of ht<span>tps://</span>i.imgur.com/{randomstring}.png

The name and locations of the images within the downloaded folder may be a bit confusing - use the below table as reference:


| Image variable | Image | Location                                 |
|:--------------:|-------|------------------------------------------|
| bullet         |   ![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/8tlpdj3xdyxqbkniotr1.png)    | assets/games/invaders/bullet.png         |
| enemyBullet    |  ![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/u7kr38sr4e2d8cndpsn7.png)     | assets/games/invaders/enemy-bullet.png   |
| invader        |   ![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/gb77blp275lz2ft2u1jl.png)    | assets/games/invaders/invader32x32x4.png |
| ship           |   ![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/00mczgbpkv7t1n343zec.png)    | assets/games/invaders/player.png         |
| kaboom         | ![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/zb291o89a86voxrvafsr.png)      | assets/games/invaders/explode.png        |
| starfield      |   ![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/xev44l8upij60f1hqglm.png)    | assets/games/invaders/starfield.png      |

### Update the Phaser.Game method
State "Phaser.CANVAS" for the third parameter of the Phaser.Game method so that images can be loaded and used from an external server.

```js
var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render });

```

### Update the restart() Function
This Invaders example doesn't reset the score when you die and restart the game. It's not a large matter, but it was sort of bugging me, so add the following code at the end of the restart() function in the invaders.js file so that the score resets to 0 when you restart your Invaders game:
```js
score = 0;
scoreText.text = scoreString + score;
```

## 5. Add JavaScript files to Your Kintone App
Now this is the final step for getting the Invaders file to work on top of Kintone.

Access the App's settings page by clicking on the cog wheel. Click on the "App Settings" tab, and select the "JavaScript and CSS Customization" settings. This page allows you to upload JavaScript and CSS files that are to be run when pages of the Kintone App run.

Under the "Upload JavaScript for PC" settings, click on "Upload file", and upload the phaser.js library, and the Invaders file that we've been working on. Make sure that the phaser.js library is located above the Invaders file, as Kintone will read these files in order from the top, and the Invaders file will be using methods that exist within the phaser.js library. 

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/ecryq1a7plx45gopcz4a.png)


Once uploaded, click on Save, and Update changes to apply these new changes to the App.

You should be navigated to the Custom view page, where the Invaders game should load.


![A screenshot of the invaders game working on Kintone](https://thepracticaldev.s3.amazonaws.com/i/aiqqdwrwb6njoaq7wb24.png)

Use the arrow keys to move the player's ship, and the spacebar to shoot bullets! Pew pew!

# Further Configurations to the Invaders Game
Before integrating the game with the data in our Kintone database, we'll make some further updates to the code.

## Scaling the Alien Size
The createAliens() function contains the code for, well, creating the Aliens.
```js
for (var y = 0; y < 4; y++)
{
    for (var x = 0; x < 10; x++)
    {
        var alien = aliens.create(x * 48, y * 50, 'invader');
        alien.anchor.setTo(0.5, 0.5);
        alien.animations.add('fly', [ 0, 1, 2, 3 ], 20, true);
        alien.play('fly');
        alien.body.moves = false;
    }
}
```

Scale the alien size by adjusting the scale property of the aliens:

```js
for (var y = 0; y < 4; y++)
{
    for (var x = 0; x < 10; x++)
    {
        var alien = aliens.create(x * 48, y * 50, 'invader');
        alien.anchor.setTo(0.5, 0.5);
        alien.animations.add('fly', [ 0, 1, 2, 3 ], 20, true);
        alien.play('fly');
        alien.body.moves = false;
        alien.scale.x = 0.2; //<--- 
        alien.scale.y = 0.2; //<---
    }
}
```
This should make the aliens 1/5 of the default size.


## Adding HP to the aliens
Aliens in this Invaders game die with just one hit of the laser. This is due to alien.kill() method being called within the collisionHandler() function. Phaser has a health component (Phaser.Component.Health) that adds an HP component to the game's characters, so that the kill method is called when their HP reaches or goes below 0.

To configure the aliens to have HP, first replace the alien.kill() method with alien.damage() method within the collisionHandler() function:

```js
    //  When a bullet hits an alien we kill them both
    bullet.kill();
    //alien.kill(); //<---
    alien.damage(1); //<---
```

Next, configure the aliens' health parameters in the createAliens() function:

```js
for (var y = 0; y < 4; y++)
{
    for (var x = 0; x < 10; x++)
    {
        var alien = aliens.create(x * 48, y * 50, 'invader');
        alien.anchor.setTo(0.5, 0.5);
        alien.animations.add('fly', [ 0, 1, 2, 3 ], 20, true);
        alien.play('fly');
        alien.body.moves = false;
        alien.scale.x = 0.2;
        alien.scale.y = 0.2;
        alien.health = 5; //<--- 
    }
}
```

With this, aliens will start off with an HP of 5, which will be reduced by the integer stated in alien.damage() every time the player's bullet hits them. When the alien's HP reaches 0 or below, the alien's kill method will be called.

# Integrating Databases with the Invaders Game

OK so now that we've got our Invaders game running on the browser on top of Kintone, we'll get right to the question - why are we using a Kintone database to do this?
This is because we want to relate the data inside the database with the Invaders game that we are playing.

Let's add some final touches.

## Add an Immediate Function and a Kintone Event
As a [best practice](https://developer.kintone.io/hc/en-us/articles/360005109013) to writing code on Kintone, we will wrap the current code in an immediate function, and also set a Kintone List View event. This will ensure that the code will run only when the record list view (including the custom view) is displayed.

```js
(function () {
    "use strict";
    kintone.events.on('app.record.index.show', function (event) {

    /////////////////////////////////////
    //  The code we've written so far  //
    /////////////////////////////////////

    });
})();
```

When using "use strict", undeclared variables cause errors.
For some reason, *enemyBullets*, *live* and *bullet* are not declared in the original code, so declare them in the list of variables under the preload() function:

```js
var player;
var aliens;
var bullets;
var bulletTime = 0;
var cursors;
var fireButton;
var explosions;
var starfield;
var score = 0;
var scoreString = '';
var scoreText;
var lives;
var enemyBullet;
var firingTimer = 0;
var stateText;
var livingEnemies = [];
var enemyBullets; //<---
var live; //<---
var bullet; //<---
```

## Set the Code to Run Only on the Custom View
The Invaders game code will run in any view since we are currently using Kintone's [Record List View event](https://developer.kintone.io/hc/en-us/articles/212494758).

To ensure that the game code will only run when we are in the Custom view, add the following:

```js
(function () {
    "use strict";
    kintone.events.on('app.record.index.show', function (event) {
      
      if (event.viewType != "custom") {return;} // <---
```

## Update the createAliens() Function

Replace the double loop that created the aliens to the below code.

```js
for (var i=0; i<event.records.length;i++)
{
  var alien = aliens.create(i * 48,  50, 'invader');
  alien.anchor.setTo(0.5, 0.5);
  alien.animations.add('fly', [ 0, 1, 2, 3 ], 20, true);
  alien.play('fly');
  alien.body.moves = false;
  alien.scale.x = event.records[i].Scale.value;
  alien.scale.y = event.records[i].Scale.value;
  alien.health = event.records[i].Health.value;
}
```

The *event* variable is an object made available by Kintone when the Record List event was triggered. It includes data of the Record List, including an array of record data. As we have set the field codes for the two number fields to be Scale and Health, the values for these fields can be found in each content of the array. For example, *event.records[0].Scale.value* will have the numerical value stored in the Scale field of the 1st record.

The code above will create multiple aliens equal to the number of records in the record list (taken from event.records.length). Also, the size and health changed from being predefined numbers to values from the Kintone database records.

## Update the collisionHandler() Function
The explosion animation that is displayed when the player's bullets hit the enemies are a bit off when the aliens are scaled down/up.
To fix this bug, the explosion.reset(alien.body.x, alien.body.y); method in the collisionHandler() function needs to be altered:
```js
explosion.reset(alien.body.center.x, alien.body.center.y);
```

## Save the File and Re-Upload It to the Kintone App
Now that the code is further altered, save your invaders.js file, access the JavaScript and CSS Customization settings of the Kintone App, and replace the old file with the latest saved file.
Click Save, and Update App to apply the changes.

Try out the updated game in your Custom view

![Screenshot of the invaders game working on Kintone with several alines with different sizes](https://thepracticaldev.s3.amazonaws.com/i/658yk2tulcj3ujeu0tyc.png)

![Screenshot of the Kintone list view with 10 records with different values inside](https://thepracticaldev.s3.amazonaws.com/i/rfisddkvadk1aypdnmqn.png)


# Summary

This post went through how to set up an Invaders game related to a web database, just using the browser and a Kintone cloud environment. By doing this, users who are not able to code can join the game development process by updating the Kintone database properties and adding in new data, that will be reflected upon the Invaders file.

Feel free to copy the code and test it out on your environment - I will be happy to see enhanced versions of the example code in this post!

# The resulting invaders code used
```js
(function () {
    "use strict";
    kintone.events.on('app.record.index.show', function (event) {
      
      if (event.viewType != "custom") {return;}
      
      var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render });
      
      function preload() {
   
        game.load.image('bullet','{path to image}');
        game.load.image('enemyBullet', '{path to image}');
        game.load.spritesheet('invader', '{path to image}', 32, 32);
        game.load.image('ship', '{path to image}');
        game.load.spritesheet('kaboom', '{path to image}', 128, 128);
        game.load.image('starfield', '{path to image}');
        game.load.image('background', '{path to image}');
      
      }
      
      var player;
      var aliens;
      var bullets;
      var bulletTime = 0;
      var cursors;
      var fireButton;
      var explosions;
      var starfield;
      var score = 0;
      var scoreString = '';
      var scoreText;
      var lives;
      var enemyBullet;
      var firingTimer = 0;
      var stateText;
      var livingEnemies = [];
      var enemyBullets;
      var live;
      var bullet;
      
      function create() {
      
          game.physics.startSystem(Phaser.Physics.ARCADE);
      
          //  The scrolling starfield background
          starfield = game.add.tileSprite(0, 0, 800, 600, 'starfield');
      
          //  Our bullet group
          bullets = game.add.group();
          bullets.enableBody = true;
          bullets.physicsBodyType = Phaser.Physics.ARCADE;
          bullets.createMultiple(30, 'bullet');
          bullets.setAll('anchor.x', 0.5);
          bullets.setAll('anchor.y', 1);
          bullets.setAll('outOfBoundsKill', true);
          bullets.setAll('checkWorldBounds', true);
      
          // The enemy's bullets
          enemyBullets = game.add.group();
          enemyBullets.enableBody = true;
          enemyBullets.physicsBodyType = Phaser.Physics.ARCADE;
          enemyBullets.createMultiple(30, 'enemyBullet');
          enemyBullets.setAll('anchor.x', 0.5);
          enemyBullets.setAll('anchor.y', 1);
          enemyBullets.setAll('outOfBoundsKill', true);
          enemyBullets.setAll('checkWorldBounds', true);
      
          //  The hero!
          player = game.add.sprite(400, 500, 'ship');
          player.anchor.setTo(0.5, 0.5);
          game.physics.enable(player, Phaser.Physics.ARCADE);
      
          //  The baddies!
          aliens = game.add.group();
          aliens.enableBody = true;
          aliens.physicsBodyType = Phaser.Physics.ARCADE;
      
          createAliens();
      
          //  The score
          scoreString = 'Score : ';
          scoreText = game.add.text(10, 10, scoreString + score, { font: '34px Arial', fill: '#fff' });
      
          //  Lives
          lives = game.add.group();
          game.add.text(game.world.width - 100, 10, 'Lives : ', { font: '34px Arial', fill: '#fff' });
      
          //  Text
          stateText = game.add.text(game.world.centerX,game.world.centerY,' ', { font: '84px Arial', fill: '#fff' });
          stateText.anchor.setTo(0.5, 0.5);
          stateText.visible = false;
      
          for (var i = 0; i < 3; i++) 
          {
              var ship = lives.create(game.world.width - 100 + (30 * i), 60, 'ship');
              ship.anchor.setTo(0.5, 0.5);
              ship.angle = 90;
              ship.alpha = 0.4;
          }
      
          //  An explosion pool
          explosions = game.add.group();
          explosions.createMultiple(30, 'kaboom');
          explosions.forEach(setupInvader, this);
      
          //  And some controls to play the game with
          cursors = game.input.keyboard.createCursorKeys();
          fireButton = game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
          
      }
      
      function createAliens () {
      
        for (var i=0; i<event.records.length;i++)
        {
          var alien = aliens.create(i * 48,  50, 'invader');
          alien.anchor.setTo(0.5, 0.5);
          alien.animations.add('fly', [ 0, 1, 2, 3 ], 20, true);
          alien.play('fly');
          alien.body.moves = false;
          alien.scale.x = event.records[i].Scale.value;
          alien.scale.y = event.records[i].Scale.value;
          alien.health = event.records[i].Health.value;
        }
      
        aliens.x = 100;
        aliens.y = 50;
      
          //  All this does is basically start the invaders moving. Notice we're moving the Group they belong to, rather than the invaders directly.
          var tween = game.add.tween(aliens).to( { x: 200 }, 2000, Phaser.Easing.Linear.None, true, 0, 1000, true);
      
          //  When the tween loops it calls descend
          tween.onLoop.add(descend, this);
      }
      
      function setupInvader (invader) {
      
          invader.anchor.x = 0.5;
          invader.anchor.y = 0.5;
          invader.animations.add('kaboom');
      
      }
      
      function descend() {
      
          aliens.y += 10;
      
      }
      
      function update() {
      
          //  Scroll the background
          starfield.tilePosition.y += 2;
      
          if (player.alive)
          {
              //  Reset the player, then check for movement keys
              player.body.velocity.setTo(0, 0);
      
              if (cursors.left.isDown)
              {
                  player.body.velocity.x = -200;
              }
              else if (cursors.right.isDown)
              {
                  player.body.velocity.x = 200;
              }
      
              //  Firing?
              if (fireButton.isDown)
              {
                  fireBullet();
              }
      
              if (game.time.now > firingTimer)
              {
                  enemyFires();
              }
      
              //  Run collision
              game.physics.arcade.overlap(bullets, aliens, collisionHandler, null, this);
              game.physics.arcade.overlap(enemyBullets, player, enemyHitsPlayer, null, this);
          }
      
      }
      
      function render() {
      
          // for (var i = 0; i < aliens.length; i++)
          // {
          //     game.debug.body(aliens.children[i]);
          // }
      
      }
      
      function collisionHandler (bullet, alien) {
          //  When a bullet hits an alien we kill them both
          bullet.kill();
          //alien.kill();
          alien.damage(1);
      
          //  Increase the score
          score += 20;
          scoreText.text = scoreString + score;
      
          //  And create an explosion :)
          var explosion = explosions.getFirstExists(false);
          //explosion.reset(alien.body.x, alien.body.y);
          explosion.reset(alien.body.center.x, alien.body.center.y);
          explosion.play('kaboom', 30, false, true);
      
          if (aliens.countLiving() == 0)
          {
              score += 1000;
              scoreText.text = scoreString + score;
      
              enemyBullets.callAll('kill',this);
              stateText.text = " You Won, \n Click to restart";
              stateText.visible = true;
      
              //the "click to restart" handler
              game.input.onTap.addOnce(restart,this);
          }
      
      }
      
      function enemyHitsPlayer (player,bullet) {
          
          bullet.kill();
      
          live = lives.getFirstAlive();
      
          if (live)
          {
              live.kill();
          }
      
          //  And create an explosion :)
          var explosion = explosions.getFirstExists(false);
          explosion.reset(player.body.x, player.body.y);
          explosion.play('kaboom', 30, false, true);
      
          // When the player dies
          if (lives.countLiving() < 1)
          {
              player.kill();
              enemyBullets.callAll('kill');
      
              stateText.text=" GAME OVER \n Click to restart";
              stateText.visible = true;
      
              //the "click to restart" handler
              game.input.onTap.addOnce(restart,this);
          }
      
      }
      
      function enemyFires () {
      
          //  Grab the first bullet we can from the pool
          enemyBullet = enemyBullets.getFirstExists(false);
      
          livingEnemies.length=0;
      
          aliens.forEachAlive(function(alien){
      
              // put every living enemy in an array
              livingEnemies.push(alien);
          });
      
      
          if (enemyBullet && livingEnemies.length > 0)
          {
              
              var random=game.rnd.integerInRange(0,livingEnemies.length-1);
      
              // randomly select one of them
              var shooter=livingEnemies[random];
              // And fire the bullet from this enemy
              enemyBullet.reset(shooter.body.x, shooter.body.y);
      
              game.physics.arcade.moveToObject(enemyBullet,player,120);
              firingTimer = game.time.now + 2000;
          }
      
      }
      
      function fireBullet () {
      
          //  To avoid them being allowed to fire too fast we set a time limit
          if (game.time.now > bulletTime)
          {
              //  Grab the first bullet we can from the pool
              bullet = bullets.getFirstExists(false);
      
              if (bullet)
              {
                  //  And fire it
                  bullet.reset(player.x, player.y + 8);
                  bullet.body.velocity.y = -400;
                  bulletTime = game.time.now + 200;
              }
          }
      
      }
      
      function resetBullet (bullet) {
      
          //  Called if the bullet goes out of the screen
          bullet.kill();
      
      }
      
      function restart () {
      
          //  A new level starts
          
          //resets the life count
          lives.callAll('revive');
          //  And brings the aliens back from the dead :)
          aliens.removeAll();
          createAliens();
      
          //revives the player
          player.revive();
          //hides the text
          stateText.visible = false;
          
          score = 0;
          scoreText.text = scoreString + score;
      
      }
  });
})();
```