March 2015

Volume 30 Number 3


Game Development - A Web Game in an Hour

By Michael Oneppo

You don’t need an entirely new skill set to develop games. In fact, your current Web development skills in HTML, JavaScript, CSS and so on are just fine for a wide range of games. When you build a game with Web technologies, it will run on pretty much any device with a browser.

To prove this, I’ll demonstrate building a game from scratch using Web technologies and just two external libraries, and I’ll do it in less than one hour. I’ll cover a variety of game development topics, from basic design and layout, controls and sprites, to arti­ficial intelligence (AI) for a simple opponent. I’m even going to develop the game so it works on PCs, tablets and smartphones. If you have some experience with programming as a Web developer or another development domain, but no experience writing games, this article will get you started. If you give me one hour, I promise to show you the ropes.

Get Up and Running

I’ll do all development in Visual Studio, which will allow fast execution of the Web app as I make changes. Be sure to have the latest version of Visual Studio (download at bit.ly/1xEjEnX) so you can follow along. I used Visual Studio 2013 Pro, but updated the code with Visual Studio 2013 Community.

This app will require no server code, so I start by creating a new, empty Web page project in Visual Studio. I’ll use the empty C# template for a Web site by selecting the Visual C# option after selecting File | New | ASP.NET Empty Web Site.

The index HTML file requires just three resources: jQuery, a main style sheet and a main JavaScript file. I add an empty CSS file to the project called style.css and an empty JavaScript file called ping.js to avoid errors when loading the page:

<!DOCTYPE html>
<html>
<head>
  <script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.1.min.js"></script>
  <script src="ping.js"></script>
  <link rel="stylesheet" href="style.css"></script>
</head>
<body>
</body>
</html>

Basic Design

The game I’m building is a variant of Pong that I call Ping. Ping has essentially the same rules as Pong, except that either player grabs the ball when it comes to them and can then fire the ball back either directly or at an angle up or down. It’s often best to draw how you would like the game to look before you build it. For this game, the overall layout I want to see is shown in Figure 1.

The Overall Design of Ping
Figure 1 The Overall Design of Ping

Once I’ve developed the game design layout, it’s just a matter of adding each element to HTML to build the game. One thing to note, though, is I’ll group the scoreboard and controls to ensure they sit together. So one by one, you can see I’ve added the elements, as shown in Figure 2.

Figure 2 The Initial HTML Layout

<div id="arena">
  <div id="score">
    <h1>
      <span id="playerScore">0</span>
      -
      <span id="opponentScore">0</span>
    </h1>
  </div>
  <div id="player"></div>
  <div id="opponent"></div>
  <div id="ball"></div>
  <div id="controls-left">
    <div id="up"></div>
    <div id="down"></div>
  </div>
  <div id="controls-right">
    <div id="left"></div>
    <div id="right"></div>
  </div>
</div>

Play with Style

If you were to load this page, you wouldn’t see anything because there’s no style applied. I’ve already set up a link to a main.css file in my HTML, so I’ll place all my CSS in a new file with that name. The first thing I’ll do is position everything on the screen. The body of the page needs to take up the whole screen, so I’ll set that up first:

body {
  margin: 0px;
  height: 100%;
}

Second, I need to have the arena fill the whole screen with the arena background image (see Figure 3) applied:

#arena {
  background-image: url(arena.png);
  background-size: 100% 100%;
  margin: 0px;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

The Arena Background Image
Figure 3 The Arena Background Image

Next, I’ll position the scoreboard. I want this to appear top and center, over the other elements. The command position: absolute lets me place it wherever I want and left: 50% places it halfway across the top of the window, but starting at the leftmost side of the scoreboard element. To ensure it’s perfectly centered, I use the transform property and the z-index property ensures it’s always at the top:

#score {
  position: absolute;
  z-index: 1000;
  left: 50%;
  top: 5%;
  transform: translate(-50%, 0%);
}

I also want the text font to be retro-themed. Most modern browsers let me include my own fonts. I found the appropriate Press Start 2P font from codeman38 (zone38.net). To add the font to the scoreboard, I have to create a new font face:

@font-face {
  font-family: 'PressStart2P';
  src: url('PressStart2P.woff');
}

Now, the scores are in an h1 tag, so I can set the font for all h1 tags. Just in case the font is missing, I’ll provide a few backup options:

h1 {
  font-family: 'PressStart2P', 'Georgia', serif;
}

For the other elements, I’ll use a sprite sheet of images. A sprite sheet contains all the images I need for the game in one file (see Figure 4).

The Sprite Sheet for Ping
Figure 4 The Sprite Sheet for Ping

Any element that has an image on this sheet will have a sprite class assigned. Then, for each element, I’ll use background-position to define what part of the sprite sheet I want to show:

.sprite {
  background-image: url("sprites.png");
  width: 128px;
  height: 128px;
}

Next, I’ll add the sprite class to all elements that will use the sprite sheet. I’ll have to briefly jump back to HTML to do this:

<div id="player" class="sprite"></div>
<div id="opponent" class="sprite"></div>
<div id="ball" class="sprite"></div>
<div id="controls-left">
  <div id="up" class="sprite"></div>
  <div id="down" class="sprite"></div>
</div>
<div id="controls-right">
  <div id="left" class="sprite"></div>
  <div id="right" class="sprite"></div>
</div>

Now I need to indicate the positions of each sprite on the sheet for each element. Again, I’ll do this using background-position, as shown in Figure 5.

Figure 5 Adding Offsets for the Sprite Sheet

#player {
  position: absolute;
  background-position: 0px 128px;
}
#opponent {
  position: absolute;
  background-position: 0px 0px;
}
#ball {
  position: absolute;
  background-position: 128px 128px;
}
#right {
  background-position: 64px 192px;
}
#left {
  background-position: 64px 0px;
}
#down {
  background-position: 128px 192px;
}
#up {
  background-position: 128px 0px;
}

The position: absolute property on the player, opponent and ball will let me move them around using JavaScript. If you look at the page now, you’ll see the controls and the ball have unnecessary pieces attached to them. This is because the sprite sizes are smaller than the default 128 pixels, so I’ll adjust these to the right size. There’s only one ball, so I’ll set its size directly:

#ball {
  position: absolute;
  width: 64px;
  height: 64px;
  background-position: 128px 128px;
}

There are four control elements (buttons the user can press to move the player about), so it behooves me to make a special class for them. I’ll also add a margin so they have a little space around them:

.control {
  margin: 16px;
  width: 64px;
  height: 64px;
}

After adding this class, the game has much better looking controls:

<div id="controls-left">
  <div id="up" class="sprite control"></div>
  <div id="down" class="sprite control"></div>
</div>
<div id="controls-right">
  <div id="left" class="sprite control"></div>
  <div id="right" class="sprite control"></div>
</div>

The last thing I need to do is position the controls so they’re by the user’s thumbs when the page is running on a mobile device. I’ll stick them to the bottom corners:

#controls-left {
  position: absolute;
  left: 0; bottom: 0;
}
#controls-right {
  position: absolute;
  right: 0; bottom: 0;
}

One nice thing about this design is everything is set with relative positions. This means the screen can be a number of different sizes while still making the game look good.

Follow the Bouncing Ball

Now I’ll make the ball move around. For the JavaScript code, I’ve referenced a file called ping.js in HTML, just as I did with the CSS. I’ll add this code to a new file with that name. I’m going to make objects for the ball and each of the players, but I’ll use the factory pattern for the objects.

This is a simple concept. The Ball function creates a new ball when you call it. There’s no need to use the new keyword. This pattern reduces some of the confusion around the this variable by clarifying the available object properties. And because I only have an hour to make this game, I need to minimize any confusing concepts.

The structure of this pattern, as I make the simple Ball class, is shown in Figure 6.

Figure 6 The Ball Class

var Ball = function( {
  // List of variables only the object can see (private variables).
  var velocity = [0,0];
  var position = [0,0];
  var element = $('#ball');
  var paused = false;
  // Method that moves the ball based on its velocity. This method is only used
  // internally and will not be made accessible outside of the object.
  function move(t) {
  }
  // Update the state of the ball, which for now just checks  
  // if the play is paused and moves the ball if it is not.  
  // This function will be provided as a method on the object.
  function update(t) {
    // First the motion of the ball is handled
    if(!paused) {
      move(t);
    }
  }
  // Pause the ball motion.
  function pause() {
    paused = true;
  }
  // Start the ball motion.
  function start() {
    paused = false;
  }
  // Now explicitly set what consumers of the Ball object can use.
  // Right now this will just be the ability to update the state of the ball,
  // and start and stop the motion of the ball.
  return {
    update:       update,
    pause:        pause,
    start:        start
}

To create a new ball, I simply call this function I’ve defined:

var ball = Ball();

Now I want to make the ball move and bounce around the screen. First, I need to call the update function at some interval to create an animation of the ball. Modern browsers provide a function meant for this purpose called requestAnimationFrame. This takes a function as an argument, and will call that passed-in function the next time it runs its animation cycle. This lets the ball move around in smooth steps when the browser is ready for an update. When it calls the passed-in function, it will give it the time in seconds since the page was loaded. This is critical for ensuring animations are consistent over time. In the game, the use of requestAnimationFrame appears as follows:

var lastUpdate = 0;
var ball = Ball();
function update(time) {
  var t = time - lastUpdate;
  lastUpdate = time;
  ball.update(t);
  requestAnimationFrame(update);
}
requestAnimationFrame(update);

Note that requestAnimationFrame is called again in the function, as the ball has finished updating. This ensures continuous animation.

While this code will work, there may be an issue where the script starts running before the page is fully loaded. To avoid this, I’ll kick off the code when the page is loaded, using jQuery:

var ball;
var lastUpdate;
$(document).ready(function() {
  lastUpdate = 0;
  ball = Ball();
  requestAnimationFrame(update);
});

Because I know the speed of the ball (velocity) and the time since its last update, I can do some simple physics to move the ball forward:

var position = [300, 300];
var velocity = [-1, -1];
var move = function(t) {
  position[0] += velocity[0] * t;
  position[1] += velocity[1] * t;  
  element.css('left', position[0] + 'px');
  element.css('top', position[1] + 'px');
}

Try running the code and you’ll see the ball move at an angle and off the screen. This is fun for a second, but once the ball goes off the edge of the screen, the fun stops. So the next step is to make the ball bounce off the edges of the screen, as implemented in Figure 7. Add this code and running the app will show a continuously bouncing ball.

Figure 7 Simple Ball Bouncing Physics

var move = function(t) {
  // If the ball hit the top or bottom, reverse the vertical speed.
  if (position[1] <= 0 || position[1] >= innerHeight) {
   velocity[1] = -velocity[1];
  }
  // If the ball hit the left or right sides, reverse the horizontal speed.
  if (position[0] <= 0 || position[0] >= innerWidth) {
   velocity[0] = -velocity[0];
  }
  position[0] += velocity[0] * t;
  position[1] += velocity[1] * t; 
  element.css('left', (position[0] - 32) + 'px');
  element.css('top', (position[1] - 32) + 'px');
}

A Moveable Player

Now it’s time to make the Player objects. The first step in fleshing out the player class will be to make the move function change the position of the player. The side variable will indicate which side of the court the player will reside, which will dictate how to position the player horizontally. The y value, passed into the move function, will be how much up or down the player will move:

var Player = function (elementName, side) {
  var position = [0,0];
  var element = $('#'+elementName);
  var move = function(y) {
  }
  return {
    move: move,
    getSide:      function()  { return side; },
    getPosition:  function()  { return position; }
  }
}

Figure 8 lays out player movement, stopping the motion if the player sprite reaches the top or bottom of the window.

I can now create two players and have them move to their appropriate side of the screen:

player = Player('player', 'left');
player.move(0);
opponent = Player('opponent', 'right');
opponent.move(0);

Figure 8 Movement Controls for the Player Sprite

var move = function(y) {
  // Adjust the player's position.
  position[1] += y;
  // If the player is off the edge of the screen, move it back.
  if (position[1] <= 0)  {
    position[1] = 0;
  }
  // The height of the player is 128 pixels, so stop it before any
  // part of the player extends off the screen.
  if (position[1] >= innerHeight - 128) {
    position[1] = innerHeight - 128;
  }
  // If the player is meant to stick to the right side, set the player position
  // to the right edge of the screen.
  if (side == 'right') {
    position[0] = innerWidth - 128;
  }
  // Finally, update the player's position on the page.
  element.css('left', position[0] + 'px');
  element.css('top', position[1] + 'px');
}

Keyboard Input

So in theory you can move the player, but it won’t move without instruction. Add some controls to the player on the left. You want two ways to control that player: using the keyboard (on PCs) and tapping the controls (on tablets and phones).

To ensure consistency between touch inputs and mouse inputs on various platforms, I’ll use the great unifying framework Hand.js (handjs.codeplex.com). First, I’ll add the script to HTML in the head section:

 

<script src="hand.minified-1.3.8.js"></script>

Figure 9 demonstrates using Hand.js and jQuery to control the player when you press keyboard keys A and Z, or when you tap the controls.

Figure 9 Adding Touch and Keyboard Controls

var distance = 24;  // The amount to move the player each step.
$(document).ready(function() {
  lastUpdate = 0;
  player = Player('player', 'left');
  player.move(0);
  opponent = Player('opponent', 'right');
  opponent.move(0);
  ball = Ball();
  // pointerdown is the universal event for all types of pointers -- a finger,
  // a mouse, a stylus and so on.
  $('#up')    .bind("pointerdown", function() {player.move(-distance);});
  $('#down')  .bind("pointerdown", function() {player.move(distance);});
  requestAnimationFrame(update);
});
$(document).keydown(function(event) {
  var event = event || window.event;
  // This code converts the keyCode (a number) from the event to an uppercase
  // letter to make the switch statement easier to read.
  switch(String.fromCharCode(event.keyCode).toUpperCase()) {
    case 'A':
      player.move(-distance);
      break;
    case 'Z':
      player.move(distance);
      break;
  }
  return false;
});

Catch the Ball

As the ball bounces around, I want to let players catch it. When it’s caught, the ball has an owner, and it follows the motion of that owner. Figure 10 adds functionality to the ball’s move method, allowing for an owner, which the ball will then follow.

Figure 10 Make the Ball Follow Its Owner

var move = function(t) {
  // If there is an owner, move the ball to match the owner's position.
  if (owner !== undefined) {
    var ownerPosition = owner.getPosition();
    position[1] = ownerPosition[1] + 64;
    if (owner.getSide() == 'left') {
      position[0] = ownerPosition[0] + 64;
    } else {
      position[0] = ownerPosition[0];
    }
  // Otherwise, move the ball using physics. Note the horizontal bouncing
  // has been removed -- ball should pass by a player if it
  // isn't caught.
  } else {
    // If the ball hits the top or bottom, reverse the vertical speed.
    if (position[1] - 32 <= 0 || position[1] + 32 >= innerHeight) {
      velocity[1] = -velocity[1];
    }
    position[0] += velocity[0] * t;
    position[1] += velocity[1] * t;  
  }
  element.css('left', (position[0] - 32) + 'px');
  element.css('top',  (position[1] - 32) + 'px');
}

Currently, there’s no way to get the position of a Player object, so I’ll add the getPosition and getSide accessors to the Player object:

return {
  move: move,
  getSide:      function()  { return side; },
  getPosition:  function()  { return position; }
}

Now, if the ball has an owner, it will follow that owner around. But how do I determine the owner? Somebody has to catch the ball. Figure 11 shows how to determine when one of the player sprites touches the ball. When that happens, I’ll set the owner of the ball to that player.

Figure 11 Collision Detection for the Ball and Players

var update = function(t) {
// First the motion of the ball is handled.
if(!paused) {
    move(t);
}
// The ball is under control of a player, no need to update.
if (owner !== undefined) {
    return;
}
// First, check if the ball is about to be grabbed by the player.
var playerPosition = player.getPosition();
  if (position[0] <= 128 &&
      position[1] >= playerPosition[1] &&
      position[1] <= playerPosition[1] + 128) {
    console.log("Grabbed by player!");
    owner = player;
}
// Then the opponent...
var opponentPosition = opponent.getPosition();
  if (position[0] >= innerWidth - 128 &&
      position[1] >= opponentPosition[1] &&
      position[1] <= opponentPosition[1] + 128) {
    console.log("Grabbed by opponent!");
    owner = opponent;
}

If you try playing the game now, you’ll find the ball bounces off the top of the screen, and you can move the player to catch it. Now, how do you throw it? That’s what the right-hand controls are for—aiming the ball. Figure 12 adds a “fire” function to the player, as well as an aim property.

Figure 12 Aim and Fire the Ball

var aim = 0;
var fire = function() {
  // Safety check: if the ball doesn't have an owner, don't not mess with it.
  if (ball.getOwner() !== this) {
    return;
  }
  var v = [0,0];
  // Depending on the side the player is on, different directions will be thrown.
  // The ball should move at the same speed, regardless of direction --
  // with some math you can determine that moving .707 pixels on the
  // x and y directions is the same speed as moving one pixel in just one direction.
  if (side == 'left') {
    switch(aim) {
    case -1:
      v = [.707, -.707];
      break;
    case 0:
      v = [1,0];
      break;
    case 1:
      v = [.707, .707];
    }
  } else {
    switch(aim) {
    case -1:
      v = [-.707, -.707];
      break;
    case 0:
      v = [-1,0];
      break;
    case 1:
      v = [-.707, .707];
    }
  }
  ball.setVelocity(v);
  // Release control of the ball.
  ball.setOwner(undefined);
}
// The rest of the Ball definition code goes here...
return {
  move: move,
  fire: fire,
  getSide:      function()  { return side; },
  setAim:       function(a) { aim = a; },
  getPosition:  function()  { return position; },
}

Figure 13 augments the keyboard function to set the player’s aim and fire functions. Aiming will work slightly differently. When the aiming key is released, the aim will return to straightforward.

Figure 13 Set the Player’s Aiming Function

$(document).keydown(function(event) {
  var event = event || window.event;
  switch(String.fromCharCode(event.keyCode).toUpperCase()) {
    case 'A':
      player.move(-distance);
      break;
    case 'Z':
      player.move(distance);
      break;
    case 'K':
      player.setAim(-1);
      break;
    case 'M':
      player.setAim(1);
      break;
    case ' ':
      player.fire();
      break;
  }
  return false;
});
$(document).keyup(function(event) {
  var event = event || window.event;
  switch(String.fromCharCode(event.keyCode).toUpperCase()) {
    case 'K':
    case 'M':
      player.setAim(0);
      break;
  }
  return false;
});

The final addition will be touch support on all controls. I’ll make the controls on the right change the aim of the player. I’ll also make it so touching anywhere on the screen fires the ball:

$('#left')  .bind("pointerdown", function() {player.setAim(-1);});
$('#right') .bind("pointerdown", function() {player.setAim(1);});
$('#left')  .bind("pointerup",   function() {player.setAim(0);});
$('#right') .bind("pointerup",   function() {player.setAim(0);});
$('body')   .bind("pointerdown", function() {player.fire();});

Keep Score

When the ball passes a player, I want to change the score and give the ball to that player. I’ll use custom events so I can separate scoring from any of the existing objects. The update function is getting long, so I’ll add a new private function called checkScored:

function checkScored() {
  if (position[0] <= 0) {
    pause();
    $(document).trigger('ping:opponentScored');
  }
  if (position[0] >= innerWidth) {
    pause();
    $(document).trigger('ping:playerScored');
  }
}

Figure 14 shows the code that reacts to those events to update the score and hand over the ball. Add this code to the bottom of the JavaScript document.

Figure 14 Update the Scoreboard

$(document).on('ping:playerScored', function(e) {
  console.log('player scored!');
  score[0]++;
  $('#playerScore').text(score[0]);
  ball.setOwner(opponent);
  ball.start();
});
$(document).on('ping:opponentScored', function(e) {
  console.log('opponent scored!');
  score[1]++;
  $('#opponentScore').text(score[1]);
  ball.setOwner(player);
  ball.start();
});

Now when the ball makes it past your opponent (which isn’t that difficult, as the opponent doesn't move) your score will go up, and the ball will be handed to the opponent. However, the opponent will just hold onto the ball.

Get Smart

You almost have a game. If only you had someone with whom to play. As a last step, I’ll show how to control the opponent with simple AI. The opponent will try to stay parallel with the ball as it moves about. If the opponent catches the ball, it will move randomly and fire the ball in a random direction. To make the AI feel a little more human, I’ll add delays in everything done. This isn’t highly intelligent AI, mind you, but it will be something to play the game against.

When designing this kind of system, it’s good to think in states. The opponent AI has three possible states: following, aiming/shooting and waiting. I’ll be the state between following actions to add a more human element. Start with just that for the AI object:

function AI(playerToControl) {
  var ctl = playerToControl;
  var State = {
    WAITING: 0,
    FOLLOWING: 1,
    AIMING: 2
  }
  var currentState = State.FOLLOWING;
}

Depending on the state of the AI, I’ll want it to do a different action. Just like the ball, I’ll make an update function I can call in requestAnimationFrame to have the AI act according to its state:

function update() {
  switch (currentState) {
    case State.FOLLOWING:
      // Do something to follow the ball.
      break;
    case State.WAITING:
      // Do something to wait.
      break;
    case State.AIMING:
      // Do something to aim.
      break;
  }
}

The FOLLOWING state is straightforward. The opponent moves in the vertical direction of the ball, and the AI transitions to the WAITING state to inject some slowed reaction time. Figure 15 shows these two states.

Figure 15 A Simple FOLLOWING AI

function moveTowardsBall() {
  // Move the same distance the player would move, to make it fair.
  if(ball.getPosition()[1] >= ctl.getPosition()[1] + 64) {
    ctl.move(distance);
  } else {
    ctl.move(-distance);
  }
}
function update() {
  switch (currentState) {
    case State.FOLLOWING:
      moveTowardsBall();
      currentState = State.WAITING;
    case State.WAITING:
      setTimeout(function() {
        currentState = State.FOLLOWING;
      }, 400);
      break;
    }
  }
}

With the code in Figure 15, the AI alternates between having to follow the ball and wait a split second. Now add the code to the game-wide update function:

function update(time) {
  var t = time - lastUpdate;
  lastUpdate = time;
  ball.update(t);
  ai.update();
  requestAnimationFrame(update);
}

When you run the game, you’ll see the opponent follow the ball’s movements—not a bad AI in less than 30 lines of code. Of course, if the opponent catches the ball, it won’t do anything. So for the last trick of the hour, it’s time to handle the actions for the AIMING state. I want the AI to move randomly a few times and then fire the ball in a random direction. Figure 16 adds a private function that does just that. Adding the aimAndFire function to the AIMING case statement makes a fully functional AI against which to play.

Figure 16 Aiming and Firing AI

function repeat(cb, cbFinal, interval, count) {
  var timeout = function() {
    repeat(cb, cbFinal, interval, count-1);
  }
  if (count <= 0) {
    cbFinal();
  } else {
    cb();
    setTimeout(function() {
      repeat(cb, cbFinal, interval, count-1);
    }, interval);
  }
}
function aimAndFire() {
  // Repeat the motion action 5 to 10 times.
  var numRepeats = Math.floor(5 + Math.random() * 5);
  function randomMove() {
    if (Math.random() > .5) {
      ctl.move(-distance);
    } else {
      ctl.move(distance);
    }
  }
  function randomAimAndFire() {
    var d = Math.floor( Math.random() * 3 - 1 );
    opponent.setAim(d);
    opponent.fire();
    // Finally, set the state to FOLLOWING.
    currentState = State.FOLLOWING;
  }
  repeat(randomMove, randomAimAndFire, 250, numRepeats);
}

Wrapping Up

By now, you have a full-fledged Web game that works on PCs, smartphones and tablets. There are many possible improvements to this game. It will look a little awkward in portrait mode on a smartphone, for example, so you need to make sure you’re holding the phone in landscape for it to work properly. This is just a small demonstration of the possibilities for game development for the Web and beyond.


Michael Oneppo *is a creative technologist and former program manager at Microsoft on the Direct3D team. His recent endeavors include working as CTO at the technology nonprofit Library For All and exploring a master’s degree at the NYU Interactive Telecommunications Program.*​

Thanks to the following technical expert for reviewing this article: Mohamed Ameen Ibrahim