Project Write-up

After many hours of work and more than 1.4k lines of code, the game is finally ready for submission! This write-up briefly explains how the game works. It also contains some of my own reflection on what I found to be most challenging as well as some of the things I have learnt.

Quick Start

To serve the game locally, clone the repository and install dependencies.

$ git clone https://github.com/lauweijie/nm2207-game.git
$ cd nm2207-game
$ sudo npm install -g bower
$ npm install && bower install
$ PORT=8000 node index.js

Project Structure

.
├── static                  # Static content served by Express.js
│   ├── audio
│   |   └── ...
│   ├── css                 # CSS compiled from scss directory
│   |   └── ...
│   ├── img
│   |   └── ...
│   ├── js
│   |   ├── controller.js   # Main script for controller page
│   |   ├── game.js         # Game logic
│   |   └── host.js         # Main script for host page
│   └── scss
│       └── ...
├── views
│   ├── controller.ejs      # EJS template for game controller
│   └── index.ejs           # EJS template for game host
├── bower.json
├── config.js               # Project config file
├── gulpfile.js             # Build script for use with gulp
├── index.js                # Project entry point
├── package.json
└── ...

Code

game.js

The main logic for the game is found in /static/js/game.js. For code maintainability, I followed the Google JavaScript Style Guide.

The obstacles in the game are created and destroyed dynamically. There are two types of obstacles in the game, one is a rotating square, and the other is a horizontal line. The obstacles alternate between the two types by keeping a counter of the number of obstacles rendered. The size of the square obstacle, its thickness and it direction of rotation are randomized.

To make the ball jump, a negative velocity is applied on the ball on the y-axis. The data received from the controller's gyroscope is normalized and is set as the world's x-axis gravity. This causes the ball to gravitate to the left or to the right when the controller is tilted to either direction.

The main game loop runs repeatedly while the physics engine is started. A viewBound variable keeps track of the game's viewport. The viewport moves upwards as the ball bounces up. However, when the ball falls below the viewBound, the viewport does not move downwards. To achieve that, viewBound is set to the minimum of itself and the ball's relative position. The game also ends when the ball falls below, out of the current viewport.

Also within the main game loop, the lastChallengePosition variable keeps track of the position of the last obstacle. Once the last challenge is within view, the next one will be dynamically created off-screen.

The game's collisionEventHandler is fired when a collision between any two bodies occur. If the ball collides with a jewel, a point is awarded and the jewel is removed from the world. The color of the ball also changes to match the color of the jewel. If the collision is between the ball and an obstacle, a sound effect is played and the game ends.

host.js

This file takes care of the socket connection between the game host and the web server. A random alphanumeric gameId is generated when the page loads. A QR code is then generated and displayed using the QRious JavaScript library. Once the socket connection is established, it sends the gameId to the server. It then waits until a controller-connected message from the server is received before starting the game.

controller.js

This script is loaded by the mobile controller. When EJS renders the controller page, the data-game-id attribute on the body tag is set to the gameId. When the socket connection is established, it sends the gameId to the server and it checks for a game host with the same gameId. If no such host is found, an error is sent back to the controller.

Challenges

One of the most challenging parts of the project was designing the game such that it would be fun and have just the right level of difficulty. I wanted the game to have some level of difficulty, but not to the extent where players would give up before becoming good at it. Of course, I played the game many times myself. But I also had a few of my friends test it. Based on their feedback, I made a few tweaks to make the game less difficult.

Another challenge I faced was due to the latency of the mobile controller. Since timing is critical for this game, it needs to be as responsive as possible to the controller. Initially, I had the controller send a message to the server every 100ms to report the current gyroscope values. It worked well initially while I was developing the game locally. However, after deploying it to a server, I noticed occasional delays due to the increased latency (the WebSocket frame had to travel from the controller to the server and back to the game host, even if both were on the same local network). Reducing the frequency of the gyroscope update helped a little.

Given more time, I would have explored using WebRTC to establish a peer-to-peer connection between the controller and the game host so that the round-trip to the server can be avoided.

What I Learnt

It's hard to reduce everything I have learnt into words. Among others, these are some of the things I have learnt.

  1. Game design
  2. Working with matter.js, a Physics engine written in JavaScript
  3. Building a remote game controller using socket.io
  4. Using gulp to build and run the game
  5. Deploying the game server using forever.js and setting up Apache as a reverse-proxy