/ collar

Understand how game works by using collar.js

collar.js game: spaceship

Play it: (support both desktop and mobile browsers, use desktop chrome for best play experience)

slow motion

just for fun

challenge yourself

fast & furious

god like

Source code could be found at github


How to build a game?

You probably knows how to build a game with unity3D, how to build a game with Love2D, or how to build a game with some other game engines. Each of these game engines has its own APIs and ways to organise the code. Understanding one of the framework doesn't give you the capability to understand the others.

In this post, I will demonstrate how 2D game works by modelling a general 2D game model with collar.js. By using this general 2D game model, you can easily build your own 2D game with your favourite game engine, or migrate your game with a different one. At the end of this post, a simple spaceship game is built with the game model we made in this post.

Model a 2D game with collar.js

Game is quite different from other desktop/mobile/web applications. Here is definition from wikipedia:

A video game is an electronic game that involves human interaction with a user interface to generate visual feedback on a video device

In developer's point of view, a game is about: 1. interaction and 2. visual feedback

Your game program takes in charge of refreshing the screen and update your game states at a high refresh rate (usually 60 frame per second (fps)). In collar.js, we can model this with an "update" event (or any name you want to call it).

collar.enableDevtool();
var ns = collar.ns("com.collarjs.game.2d.spaceship");

// game module input 
var gameModuleInput = ns.input("game module input");

gameModuleInput
  // this event will be received 60 times per second
  .when("update event") 
  .do("update game states")

game step 1

game module input is a passthrough node that makes our flow independent from other modules.

You don't need to generate the update event by yourself, all game engines provide a way to handle it. What you need to do here is to create a game engine sensor and connect it to the game module input node.

var gameEngineSensor = ns.sensor("game engine sensor");
gameEngineSensor.to(gameModuleInput);

game step 2

As game is a multimedia application, it requires lots of multimedia resources (images, textures, musics, etc). Loading them is a time consuming job. To make your game faster, almost all the game engines will load the resources before the game starts. Remember the progress bar at the beginning of a game?

So we need another event "load resources" to handle it.

gameModuleInput
  .when("load resources")
  .do("load resources")

Once the resources are loaded in memory, you need to initialise your game scene: where is your player? his hp? mp? or other properties? where are the enemies? and what are their properties? what the world looks like?

So we add new event "init game scene" to handle it

gameModuleInput
  .when("init game scene")
  .do("init the game scene")

For 2D games, the game scene is rendered by layer. Think about a Z axis pointing to you from your screen. The farthest layer (let's call it layer 0) will be rendered first, the closest layer (layer N) will be rendered last. The elements on closer layer will cover the farther one. It it important to arrange your visual elements on the right layer, otherwise, it will be covered by other elements.

Usually we will put the background at the farthest layer, and the UI element (button, score board) the closest layer.

To model that in collar.js, we replace the "init game scene" actuator with multiple layer rendering actuators

gameModuleInput
  .when("init game scene")
  .do("init world settings")
  .do("render layer 0")
  .do("render layer 1")
  .do("render layer 2")
  .do("render layer 3")

Before we render the different layers, we add a new actuator to initiate the world settings, for example, the gravity, the physic system.

game step 3

We have finished modelling the visual feedback part, now it's time to model the interaction part.

According to different game, the user inputs could be mouse click, joystick input, touch point, multiple touch points, etc. To make the model for general purpose, we create a general "user input" event to accept all of these inputs, and we will replace it with a specific input event when implementing a specific game.

gameModuleInput
  .when("user input")
  .do("react to user input")

Different game scene could have different kind of user inputs, so the "user input" event will be captured at each scene. Game engine could not give us such event, so we need to initialise a game scene sensor when we initialise the game scene.

var gameSceneSensor = ns.sensor("game scene sensor");

gameModuleInput
  .when("init game scene")
  .do("init world settings")
  .do("render layer 0")
  .do("render layer 1")
  .do("render layer 2")
  .do("render layer 3")
  .do("init game scene sensor")

gameSceneSensor.to(gameModuleInput);

game step 4

The graph above is the general data flow of a 2D game. It is not perfect, for example, it does not demonstrate how game state is modelled. To complete the model, you can use collar.js' model and variable node to represent your game state and update them according to the user input.

To make this post short, we use this simplified model here to build a 2D web game.

Escape! spaceship! Escape!

collar.js game: spaceship

Last sunday, I made a simple 2D web game with this model. You are a spaceship pilot, you accidentally entered an enemy territory. They fired several self guided missiles to shoot you down. Your mission is to do turn maneuver with your mouse to escape from these missiles. You can play with it from this link:

Run Escape! spaceship! Escape!.

See how long you can last, and good luck! pilot :p

Implementation

Here is how to implement it with the game model we just made:

First, let's see how to represent the game state:

Both the spaceship and missiles have two properties, speed and rotation speed. Missile flies faster than spaceship, otherwise it will never catch the spaceship. But missile turns slower than spaceship, otherwise you never gonna escape from it.

Both spaceship and missile have coordinates (x, y), the game logic is to calculate their coordinates and display them on screen. Both of them have a direction vector, to show the current direction it heads to.

To make the user easier to play, we fix the spaceship at the center of the screen. It is the background and missiles who move.

In this post, we will use Phaser.js as the game engine. You can use any javascript game engine you like.

Phaser.js has several callbacks to handle resources, create scene, and update. We simply send a signal with different "event" property in these callbacks.

The false parameter in send() method tells collar.js that the signal handling should be atomic, collar.js will handle it without interruption.

var gameEngineSensor = collar.sensor("game engine sensor", function() {
  game.state.add(
    'Game', {
      preload : () => {
        this.send({
          event : 'load resources'
        }, false);
      },
      create : () => {
        this.send({
          event : 'init game scene'
        }, false);
      },
      update : () => {
        this.send({
          event : 'update'
        }, false);
      }
    }
  );
});

Next, we implement the load resources branch.

gameModuleInput
  .when("load resources", signal => signal.get("event") === "load resources")
  .do("load resources", signal => {
    game.load.image('missile', 'images/missile.png');
    game.load.image('background', 'images/background.png');
    game.load.image('spaceship', 'images/spaceship.png');
    game.load.spritesheet('explosion', 'images/explode.png', 128, 128);
  });

For the initiation of game scene, we will render the background image at layer 0, spaceship at layer 1, missile at layer 2, explosion at layer 3, and UI elements at layer 4.

For example the following code initiates the spaceship

.do("render spaceship", signal => {
  spaceship = game.add.sprite(
    game.world.centerX,game.world.centerY, 'spaceship');
  spaceship.anchor.setTo(0.5, 0.5);
  game.physics.enable(spaceships, Phaser.Physics.ARCADE);
  spaceship._flySpeed = spaceshipSpeed;
  spaceship._rotateSpeed = spaceshipRotateSpeed;
  spaceship._direction = new Phaser.Point(
    spaceship.x, 
    spaceship.y - player._flySpeed
  );
})

Find the complete code at my github repo :

game.js

There are 3 kinds of input for this game:

  1. when user gives a new direction: when you click on screen, and move you mouse, your mouse position gives the spaceship a new direction to head to.
  2. when new missile fires: this is controlled by a timer, every 5 seconds a new missile is fired
  3. restart, when you are unfortunately hit by a missile, game over. You can click the screen to restart the game

We replace the user input branch with this 3 new branches.

gameModuleInput
  .when("new spaceship target direction", signal => {
    return signal.get("event") === "new target direction";
  })
  .do("update spaceship's target direction", signal => {
    var target = new Phaser.Point(signal.get("x"), signal.get("y"));
    spaceship._target = target;
  })

gameModuleInput
  .when("enemy fires", signal => {
    return signal.get("event") === "enemy fires";
  })
  .do("create a new missile")

gameModuleInput
  .when("restart", signal => {
    signal.get("event") === "restart" && isStarted == false
  })
  .do("restart the game", signal => {
    isStarted = true;
    gameoverText.visible = false;
    time = game.time.now;
  });

The last part is to implement the "update" event branch. When we update the scene, we will do the following procedure :

  1. update spaceship's position (by moving the background)
  2. update missile's position
  3. update current score (time)
  4. check if collision happens

We modify our flow as following:

gameModuleInput
  // this event will be received 60 times per second
  .when("update event", signal => signal.get("event") === "update")
  .do("update spaceship position")
  .do("update missile position")
  .do("update time score")
  .do("collision detection")

The implementation of these nodes involves some math work, the idea here is to calculate the new positions of each elements and update them. To know more details, please check the github repo:

source code of collar.js game demo : spaceship

Finally, your game flow looks like the following graph:

game flow final

show big picture

Understand how it works on runtime

To understand how it works on runtime, you can record signal sequence with collar dev tool.

1.-Install collar dev server

sudo npm install collar-dev-server -g

2.-Run collar dev server

collar-dev-server

3.-Open collar dev tool

http://localhost:7500

4.-Run the game with parameter dev=1

http://game.url?dev=1&otherparams...

5.-Go back to collar dev tool, and start recording

6.-Play the game, and stop recording in collar dev tool when you finished playing

7.-Check the signal sequence to understand how your game works