The game

As we discussed at the beginning of this chapter, there are several considerations that must be kept in mind when designing and building a game to be played on a mobile device as well as on a desktop browser. As we write this final game in this book, we will apply those principles and best practices making a game to be playable on both a mobile device and an HTML5-ready browser.

The approach taken in this particular project is to design for desktop first then add mobile-specific APIs and functionality later. The number one reason for this decision was because it is much easier to test and debug an application on a desktop browser using existing tools and common practices, then add the things needed to make the code run smoothly on mobile as well.

The final game is a traditional two-dimensional space shooter, where the player controls a single ship that can move around the screen and always shoots up in the same direction. Random enemy space ships come from all sides of the screen, attempting to hit the player's ship, thus giving damage to the player's ship until it explodes.

The game

Code structure

Given the complexity of this game, the structure of the code has to be carefully considered. For simplicity, we will use a component-based approach so that adding feature upon feature is much easier, especially when it comes down dynamically adding the input handling mechanism. Since the game needs to be played equally well on a mobile device as well as on a desktop (in other words, the game needs to take and handle mouse and keyboard input as well as touch input, depending on the environment in which it is played), being able to add a specific component to the game on the fly is a very important feature.

If you're not familiar with component-based game development, don't worry too much about it. The general idea of component-based development is to separate each piece of functionality from a class and make that functionality its own class. What this allows us to do is to create individual objects that represent individual pieces of functionality, such as moving, rendering, and so on.

The final project structure for this game is as follows, where the list of files and directories shows the root of the project folder:

/css

This is where the single stylesheet file is stored. This stylesheet defines all the styling for both the desktop and mobile versions, although there are very few differences between the two. The way to add CSS features to a version of the game is to declare these features inside CSS classes then assign those classes to DOM elements when appropriate.

The first thing we want to declare in this stylesheet is the viewport, making sure that every pixel within the screen is part of the document, so that we can capture input events everywhere on our document. We also want to keep the document from somehow growing larger than the viewport which would introduce scrollbars to the game, which in this case is not desired.

body, html {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
}

body {
  background: url("../img/space-bg-01.jpg") repeat;
}

Two features that we want in our game, if the device running it supports it, are transitioned effects of DOM elements as well as animating the background image. In order to add that functionality only where appropriate (for example, adding this feature to some mobile devices might slow down the game play to unplayable levels, given the amount of processing the mobile browser would need to do in order to produce the CSS animation and transitions), we create the CSS animation and add it to a custom class. When we determine that the device can handle the animation, we simply add the class to the document.

/**
 * Make the background image continually move up and to the left,
 * giving the illusion that the game world is scrolling at anangle.
 */
@-webkit-keyframes NebulaBg {
  from {
    background-position: 0 0;
  }
  to {
    background-position: 1300% 600%;
  }
}

/**
 * Add the animation to this class, and add a transition
 * to any box-shadow applied to whatever element this class isattached to.
 */
.animBody {
  -webkit-transition: box-shadow 8s;

  -webkit-animation: NebulaBg;
  -webkit-animation-duration: 500s;
  -webkit-animation-timing-function: linear;
  -webkit-animation-iteration-count: infinite;
}

Finally, in order to simplify some of the game user interface, we create some of the game elements as ordinary DOM elements, as opposed to rendering equivalent elements directly in the canvas where the rest of the game is rendered.

The only game element we're building as a DOM element is the energy bar for the player's ship, which indicates how much energy the ship has left. This energy bar is composed of a container element containing a div element inside of it. The width of this nested div represents the amount of energy the player has left, which can be a value between 0-100%.

.energyBar {
  position: absolute;
  top: 2%;
  left: 4%;
  z-index: 99999;
  width: 92%;
  height: 25px;
  border: 1px solid #ff5;
  background: #c00;
  overflow: hidden;
}

.energyBar div {
  background: #ff5;
  height: 100%;
  width: 100%;
  -webkit-transition: width 0.2s;
}

/img

Inside this folder we store all the image assets used in the game. Since all of these images are rendered inside a canvas, we could have very well combined all of the images into a single image atlas. This would be a very nice optimization, especially as the game grows and the number of image assets grows. Since most browsers limit the number of parallel HTTP requests that an application makes to the same server, we can only fetch a limited amount of images at the same time. This means that if there are too many individual image files being fetched from the same server, the first 4-8 requests are processed (the default number of parallel connections to the same server varies from browser to browser but is usually around 6 or so) while the rest of them wait in a queue.

Thus, it is easy to see how creating a single image atlas asset is a much better choice over downloading multiple individual image files. Even if the total image file size of the atlas is greater than the total size of all the other images combined, the big gain is in the transfer latency. Even if the game doubles in individual image assets at some point in time, we would still only have to download a single image atlas (or a few separate atlases which can all be downloaded simultaneously).

Note

Since not everybody is highly talented when it comes to creating awesome looking graphics for your games and even fewer people have the time to create each image to be used in the game. Many game developers find it worthwhile to buy graphics from digital artists.

In this game, all of the graphics were downloaded from websites where artists share their creations for free or very low costs. The website address to this wonderful community is http://opengameart.org.

/js

As mentioned earlier, this game is built on a component-based model. The file structure is divided into four main categories; namely components, entities, widgets, and general purpose code. Each of these pieces of code are meant to be somewhat generic and reusable. The gluing together of all these pieces is done in a file called main.js.

/components

The components directory is where we store all of the components used in the game. In the context of game development, a component is a very specific class that may contain its own data and performs a very specific function. For example, when designing a class to represent the player, instead of having this class handle the rendering of the player's ship, moving, performing collision detection, and so on, we can break down each piece of functionality from this class into many individual smaller classes—or components.

Commonly, each component in your game implements a common interface so we can take advantage of object-oriented techniques. Although classical inheritance and other object-oriented techniques can be simulated in JavaScript, we simply copy the same base interface for each component and make the assumption in our client code that every component follows the same interface.

// Namespace the component in order to keep the global namespaceclean
var Packt = Packt || {};
Packt.Components = Packt.Components || {};

Packt.Components.Component = function(entity) {
  var entity = entity;

  this.doSomething = function() {
};

Every component in this game has two things in common. They all live within the Pack.Components object simulating a package-based structure and they all hold a reference to the parent entity that uses the service provided by the component.

The first component that we'll create will be the sprite component, which is responsible for rendering an entity. As we'll see in our discussion about entities, an entity only keeps track of its own position in the game world and has no notion of its width and height. For this reason, the sprite component also keeps track of the entity's physical size as well as the image that represents the entity visually.

var Packt = Packt || {};
Packt.Components = Packt.Components || {};
Packt.Components.Sprite = function(pEntity, pImgSrc, pWidth, pHeight) {
  var entity = pEntity;
  var img = new Image();
  img.src = pImgSrc;

  var width = pWidth;
  var height = pHeight;
  var sWidth = pWidth;
  var sHeight = pHeight;
  var sX = 0;
  var sY = 0;
  var ctx = null;

  // Inject the canvas context where the rendering of the entity
  // managed by this component is done
  function setCtx(context) {
    ctx = context;
  }

  // Public access to the private function setCtx
  this.setCtx = setCtx;

  // If the image used to render the entity managed by thiscomponent
  // is part of an atlas, we can specify the specific region
  // within the atlas that we want rendered
  this.setSpriteCoords = function(x, y, width, height) {
    sX = x;
    sY = y;
    sWidth = width;
    sHeight = height;
  };

  // Render the entity
  this.update = function() {
    if (ctx && entity.isActive()) {
      var pos = entity.getPosition();
      ctx.drawImage(img, sX, sY, sWidth, sHeight, pos.x, pos.y,width, height);
    }
  };

  // Return both values at once, instead of using two getterfunctions
  this.getSize = function() {
    return {
      width: width,
      height: height
    };
  };
};

Once the functionality to render an entity is in place, we can now move on to adding a component to allow the player to move the entity about the screen. Now, the whole point of using components is to allow for maximum code reuse. In our case, we want to reuse the component that makes a player move so that we can have each enemy ship move about the game world using the same functionality.

To make the entity move, we use a very standard Move component which moves the entity based on its direction vector and a constant speed at which the entity is to move in this given direction. The Vec2 data type is a custom general purpose class discussed later in the chapter. Basically, this class represents a vector where it holds two variables representing the two components of a vector and defines a very handy function to normalize the vector when needed.

var Packt = Packt || {};
Packt.Components = Packt.Components || {};

Packt.Components.Move = function(entity, speed) {
  var entity = entity;
  var speed = speed;
  var direction = new Packt.Vec2(0, 0);

  // Move the entity in the direction it is facing by a constantspeed
  this.update = function() {
    var pos = entity.getPosition();
    direction.normalize();

    var newPos = {
      x: pos.x + direction.get("x") * speed,
      y: pos.y + direction.get("y") * speed
    };

    entity.setPosition(newPos);
  };

  // Allow the input mechanism to tell the entity where to move
  this.setDirection = function(x, y) {
    direction.set(x, y);
  };
};

Now, the way that both a player and an enemy can use this same Move component to move their entities is slightly different. In the case of an enemy, we can simply create some raw artificial intelligence to set the direction of the enemy's entity every so often and the Move component takes care of updating the entity's position as needed.

In order to make the player's ship move, however, we want the player himself or herself to tell the entity where to go. To accomplish this, we simply create an input component that listens for human input. However, since the player might be playing this game from a device that may support either mouse events or direct touch events, we need to create two separate components to handle each case.

These components are identical in every way, except for the fact that one registers for mouse events and the other for touch events. While this could have been done inside a single component, and conditional statements dictated which events to listen for instead, we opted for separate components in order to make the code less coupled to any particular device.

var Packt = Packt || {};
Packt.Components = Packt.Components || {};

Packt.Components.TouchDrag = function(entity, canvas) {
  var entity = entity;
  var canvas = canvas;
  var isDown = false;
  var pos = entity.getPosition();

  canvas.getCanvas().addEventListener("touchstart", doOnTouchDown);
  canvas.getCanvas().addEventListener("touchend", doOnTouchUp);
  canvas.getCanvas().addEventListener("touchmove", doOnTouchMove);

  // Set a isDown flag on the entity, indicating that the playeris currently
  // touching the entity that is to be controlled
  function doOnTouchDown(event) {
    event.preventDefault();
    var phy = entity.getComponent("physics");
    var touch = event.changedTouches;

    if (phy) {
      isDown = phy.collide(touch.pageX, touch.pageY, 0, 0);
    }
  }

  // Whenever the player releases the touch on the screen,
  // we must unset the isDown flag
  function doOnTouchUp(event) {
    event.preventDefault();
    isDown = false;
  }

  // When the player drags his/her finger across the screen,
  // store the new touch position if and only if the player
  // is actually dragging the entity
  function doOnTouchMove(event) {
    event.preventDefault();
    var touch = event.changedTouches;

    if (isDown) {
      pos.x = touch.pageX;
      pos.y = touch.pageY;
    }
  }

  // Reposition the player's entity so that its center is placed
  // right below the player's finger
  this.centerEntity = function() {
    if (isDown) {
      var sprite = entity.getComponent("sprite");
  
      if (sprite) {
        var size = sprite.getSize();
        var x = pos.x - size.width / 2;
        var y = pos.y - size.height / 2;

        entity.setPosition({x: x, y: y});
      }
    }
  };

  this.getPosition = function() {
    return pos;
  };
};

Next, let's look at a very crucial component of any game with moving entities, namely the physics components whose sole responsibility in life is to tell if two entities collide. This is done in a very simple and efficient fashion. In order for an entity to be able to use the physics component, it must also have a sprite component since the physics component needs to know where each entity is located as well as how tall and wide each entity is. With a sprite component, we're able to extract both pieces of information about each entity.

The way to check whether two entities collide is very simple. The component itself stores a reference to an entity so the function that performs the check needs to know the position and size of the entity we're checking against for collision. Once we have the location and dimensions of both entities, we simply check if one entity's right side is to the left of the other's left side, if one's left side is to the right of the other's right side, or if one's bottom is above the other's top, and if one's top is below the other entity's bottom. If any of these tests passes (in other words, the conditional check returns positive) then we know that there is no collision, since it is impossible for two rectangles to be intersecting each other, and yet any of those four statements are true about them. Similarly, if all of those tests fail, we know that the entities are intersecting each other and a collision has occurred.

var Packt = Packt || {};
Packt.Components = Packt.Components || {};

Packt.Components.Physics = function(entity) {
  var entity = entity;

  // Check if these two rectangles are intersecting
  this.collide = function(x, y, w, h) {
    var sprite = entity.getComponent("sprite");
    if (sprite) {
      var pos = entity.getPosition();
      var size = sprite.getSize();

      if (pos.x > x + w) {
        return false;
      }

      if (pos.x + size.width < x) {
        return false;
      }

      if (pos.y > y + h) {
        return false;
      }

      if (pos.y + size.height < y) {
        return false;
      }

      return true;
    }

    return false;
  };

  // Return the entity's location and dimensions
  this.getBodyDef = function() {
    var pos = entity.getPosition();
    var sprite = entity.getComponent("sprite");
    var size = sprite.getSize() || {width: 0, height: 0};

    return {
      x: pos.x,
      y: pos.y,
      width: size.width,
      height: size.height
    };
  };
};

The last two components used in the game are very simple and are slightly more unique to this particular type of game than the other components. These components are the Strength component and the LaserGun component, which gives entities the ability to shoot laser beams at other entities.

The Strength component isolates the management of the player's own energy as well as all enemy ships and everybody's lasers. This component is used to determine if an entity is still alive and how much damage it can cause on other entities upon contact. If an entity is no longer alive (if its strength has gotten below zero) then it is removed from the game altogether, as is the case with lasers every time they collide with another entity.

var Packt = Packt || {};
Packt.Components = Packt.Components || {};
Packt.Components.Strength = function(pEntity, pHP, pEnergy) {
  var entity = pEntity;
  var hp = pHP;
  var energy = pEnergy;

  // This is how much damage the entity causes to other entities
  // upon collision between the two
  this.getHP = function() {
    return hp;
  };

  // This represents how much energy the entity has left. When
  // the energy gets to or below zero, the entity dies
  this.getEnergy = function() {
    return energy;
  };

  // Update the entity's energy levels
  this.takeDamage = function(damage) {
    energy -= damage;
    return energy;
  };
};

The LaserGun component is slightly more involved because it contains a collection of entities that it manages. Each time a laser beam is fired by the entity containing the laser gun, a new entity is created to represent that laser beam. This entity is similar to all the other entities in the game since it also contains a sprite component to draw itself a Move component and a physics component as well.

Each time the laser gun updates itself, it needs to move all of its lasers forward and remove any of its laser beams from its control if the laser has gone outside the screen area.

var Packt = Packt || {};
Packt.Components = Packt.Components || {};

Packt.Components.LaserGun = function(entity, canvas, maxShots) {
  var entity = entity;
  var MAX_SHOTS = maxShots;
  var canvas = canvas;
  var shots = new Array();
  var shotsPerSec = 1000 / 15;
  var timeLastShot = 0;

  // Move all lasers forward, and remove any lasers outsidethe screen
  this.update = function() {
    for (var i = 0, len = shots.length; i < len; i++) {
      try {
        shots[i].update();
        var shotPos = shots[i].getPosition();
  
        if (shotPos.y < -100) {
          shots.splice(i, 1);
        }
      } catch (e) {}
    }
  };

  // Create a new laser entity, and assign all of the components
  // it will need in order to actually destroy other ships
  this.add = function(x, y) {
    var time = Date.now();

    // Don't add a new laser until at least some time has passed,
    // so that we don't fire too many lasers at once
    if (time - timeLastShot >= shotsPerSec) {

      // Restrict the amount of lasers that can be on the screenat once
      if (shots.length < MAX_SHOTS) {
        var shot = new Packt.Entity(Packt.ENTITY_TYPES.BULLET, x, y);
        var spriteComp = new Packt.Components.Sprite(
          shot, "./img/laser-blue.png", 8, 32);
        spriteComp.setCtx(canvas.getContext());
        var strengthComp = new Packt.Components.Strength(shot, 10, 0);
        var physComp = new Packt.Components.Physics(shot);
        var mockMove = new Packt.Components.Move(shot, 15);

        shot.addComponent("sprite", spriteComp);
        shot.addComponent("move", mockMove);
        shot.addComponent("physics", physComp);
        shot.addComponent("strength", strengthComp);

        shot.setOnUpdate(function() {
          mockMove.setDirection(0, -1);
          mockMove.update();
        });
  
        shots.push(shot);
      }

      timeLastShot = time;
    }
  };

  // Return a list of active shots
  this.getShots = function() {
    return shots;
  };
};

With all of our major components in place, we're ready to take a look at the other classes in the game. Remember, though, that the whole purpose of using components is to simplify development and to loosen the coupling between individual pieces of functionality. Thus, if we wanted to add more components to the game, say, an explosion effect component, for example, all we'd need to do is follow the same basic structure of a component and we'd be ready to simply plug it into the main game logic script.

/entities

Entities are the main building blocks of the game. They are the generalized representation of anything that we can interact with—the player's ship, enemy ships, or laser beams. Some people call their entities objects, characters, or actors, but the idea behind them is the same.

In our game, we don't extend the base entity class in order to create a distinction between ships and lasers. The only thing that sets them apart are the components they use and how those components are used.

The structure of our game entities is basic and to the point. Each entity keeps track of its own position within the game world, a flag that indicates its state (whether the entity is active or not—dead or alive), a list of components, and an update function. Also, for simplicity, each entity declares a draw function which delegates the actual drawing to the sprite component, if the entity happens to have one. We also define a few general purpose functions inside each entity so as to make adding, removing, and using components a bit easier. Finally, each entity allows for a custom update function, so that each instantiated entity can update itself differently.

var Packt = Packt || {};
Packt.ENTITY_TYPES = {
  SHIP: 0,
  BULLET: 1
};

Packt.Entity = function(type, x, y) {
  var type = type;
  var pos = {
    x: x,
    y: y
  };

  var isActive = true;
  var components = new Object();

  // Make this function empty by default, and allow the user tooverride it
  var update = function(){};

  // Add a component to this entity if one by this name has notyet been added
  function addComponent(key, component) {
    if (!components[key]) {
      components[key] = component;
    }

    return component;
  }

  // Attempt to remove an entity by its name
  function removeComponent(key) {
    if (components[key]) {
      return delete components[key];
    }

    return false;
  }

  // Return a reference to a component
  function getComponent(key) {
    return components[key] || null;
  }

  // Draw this component
  function draw() {
    if (components.sprite) {
      components.sprite.update();
    }
  }

  // Expose these functions through a public interface
  this.addComponent = addComponent;
  this.removeComponent = removeComponent;
  this.getComponent = getComponent;
  this.getPosition = function() {
    return pos;
  };

  this.setPosition = function(newPos) {
    pos = newPos;
  };

  this.isActive = function() {
    return isActive;
  };

  this.setActive = function(active) {
    isActive = active;
  };

  this.draw = draw;
  this.update = update;
  this.update = function() {
    update();
  };
  // Save a reference to a new update callback function
  this.setOnUpdate = function(cb){
    update = cb;
  };
};

As you can see, this entity class is really quite bare bones. It takes into account what our game needs to do, what is used in the game, and it encapsulates the most common functionality based on that. From here we can instantiate an entity and add components to it, making it a very unique entity, based on all that it potentially can and cannot do.

/widgets

The only widget used in this game is the EnergyBar widget. The whole point of widgets is to simplify the management of different user interface elements. The way in which each widget decides how to display the elements they represent is their own business and any client code using them should only be concerned about the interface through which it can communicate with the widget.

What the EnergyBar widget does is display a horizontal bar across the top of the page representing how much energy the player has left. Each time the player is hit by an enemy ship, its energy levels drop by some amount. When that energy meter goes to zero, the player dies, and the game is over.

One way this energy bar could have been rendered was through the canvas API, where the widget was rendered directly on the game canvas. While this is a very acceptable solution as well as a very common one, I decided to just use a plain old DOM element instead. This way the styling can be changed much more easily through CSS and nothing else would need to be changed within the code. In other words, while someone worked on the actual code, a second person could work on styling the widget and all they'd need to access would be the stylesheet associated with it.

var Packt = Packt || {};
Packt.Widgets = Packt.Widgets || {};

Packt.Widgets.EnergyBar = function(cssClass) {
  var energy = 100;

  // Create the DOM element to represent this widget on screen
  var container = document.createElement("div");
  container.classList.add(cssClass);

  var bar = document.createElement("div");
  bar.style.width = energy + "%";
  container.appendChild(bar);

  // Return the DOM element so it can be appended to the document
  this.getElement = function() {
    return container;
  };

  // Increase the player's energy level and update the DOM element
  // that represents it on screen. To decrease the energy level, simply
  // pass a negative number to this function
  this.addEnergy = function(amount) {
    energy += amount;
    bar.style.width = energy + "%";
  };

  // Set the energy level directly, instead of just adding to
  // or removing from it
  this.setEnergy = function(amount) {
    energy = amount;
    bar.style.width = energy + "%";
  };
};

When an EnergyBar widget is instantiated, it creates its own DOM element that represents the widget, adding any CSS classes and IDs associated with it. The member attribute energy represents the amount of energy that an entity has and the width of one of the DOM elements created by the widget matches to the percentage of energy it contains. After a widget's element has been added to the document, we can simply communicate with the widget class through its public interface and the DOM elements displayed on the document get updated accordingly.

Canvas.js

With the exception of the EnergyBar widget, everything else rendered to the screen in this game is rendered through a canvas, using the 2D rendering context. In order to keep things together and make the code more organized, we create a very simple abstraction over the canvas API. Instead of keeping track of a canvas variable referencing some DOM element, along with its accompanying context reference, we encapsulate the canvas element, the JavaScript reference to it, and the reference to the rendering context all inside a single object.

// Namespace the canvas abstraction
var Packt = Packt || {};

// Construct a canvas of an arbitrary size
Packt.Canvas = function(w, h) {
  var width = w;
  var height = h;
  var canvas = document.createElement("canvas");

  canvas.width = width;
  canvas.height = height;

  var ctx = canvas.getContext("2d");

  this.getCanvas = function() {
    return canvas;
  };

  this.getContext = function() {
    return ctx;
  };

  this.getWidth = function() {
    return width;
  };

  this.getHeight = function() {
    return height;
  };

  // Allow the client to clear the entire rendering buffer without
  // needing to know how things are done under the hood, andwithout
  // anyone needing to worry about the width and height of thecanvas
  this.clear = function() {
    ctx.clearRect(0, 0, width, height);
  };
};

We also hide some of the detailed functionality of the canvas API by adding a few helper functions, such as getWidth, getHeight, and clear, so that other areas in the code can interact with the canvas through this simplified interface.

One other reason that an abstraction such as this can be very handy is that it would greatly simplify things if we decided to use two or more canvases. Suppose we wanted to render a widget into its own canvas. Without an abstraction like this, we would now have four separate variables to keep track of in our code.

A common optimization pattern in HTML5 game rendering with the 2D canvas is to separate the rendering into layers. For example, things that don't change very frequently from frame to frame (such as the background graphics of a level) can be re-rendered much less frequently than dynamic objects that may need to be rendered at a different location each frame (the player and the enemies that are trying to kill the hero). Instead of redrawing each and every pixel of the background graphics each frame, since most of those pixels are the exactly same as the previous frame, we can draw the entire background scene onto its own canvas and absolutely position it behind another canvas that only draws on smaller parts of it, making it much easier to redraw every frame.

Since the background layer doesn't change too often, if at all, we can render more complex graphics onto it and not have to worry about redrawing anything there very often. Although the foreground layer normally needs to be cleared and redrawn every single frame, we can still maintain a good frame rate because we're normally only rendering on a small portion of the foreground canvas, which doesn't take as much processing as it would to redraw the background layer every frame.

Canvas.js

Now it becomes easy to see how valuable a simple canvas abstraction can be when using more advanced rendering techniques. In most cases, even if we're just rendering on a single canvas, being able to encapsulate all the loose variables associated with a canvas often makes things more efficient, especially when you need to pass the canvas and the canvas context around to other functions and classes.

EnemyManager.js

Since the player of our game will only control a single entity throughout the entire game, creating an instance of the entity class and letting the player control that entity is trivial. The challenge is finding a way to create enemy entities, move them around, and manage them as the game progresses. To solve this problem, we create an EnemyManager class, whose job is to create enemy entities when needed and manage their existence.

While this may seem like a complex task, it becomes more manageable if we break down the task into smaller pieces. The responsibilities that the EnemyManager class has include creating a new enemy entity and adding it to a list of active entities that it stores, updating each entity individually, and removing any dead entities from the entity list it manages.

// Namespace the enemy manager object
var Packt = Packt || {};

Packt.EnemyManager = function(canvas) {
  var entities = new Array();
  var canvas = canvas;
  var worldWidth = canvas.getWidth();
  var worldHeight = canvas.getHeight();

  // By returning the list of active enemies to the client code,
  // we can pass on the responsibility of rendering each entity,
  // as well as allow other components to interact with theentities
  this.getEntities = function() {
    return entities;
  };

  // Create a new entity at a certain screen location, along
  // with a list of components
  function addEnemies(x, y, components) {
    var entity = new Packt.Entity(Packt.ENTITY_TYPES.SHIP, x || 0,y || -100);
    for (var c in components) {
      entity.addComponent(c, components[c]);
    };

    var strengthComp = new Packt.Components.Strength(entity, 0.5, 25);
    var physComp = new Packt.Components.Physics(entity);
    var mockMove = new Packt.Components.Move(entity, (Math.random() * 5 >> 0) + 2);

    var enemySprite = "./img/enemy-red.png";

    // Randomly assign a different skin to the sprite component
    if (parseInt(Math.random() * 100) % 2 == 0) {
      enemySprite = "./img/spaceship.png";
    }

    var spriteComp = new Packt.Components.Sprite(entity, enemySprite, 64, 64);

    spriteComp.setCtx(canvas.getContext());
    spriteComp.setSpriteCoords(0, 0, 64, 64);
    entity.addComponent("sprite", spriteComp);
    entity.addComponent("move", mockMove);
    entity.addComponent("physics", physComp);
    entity.addComponent("strength", strengthComp);

    // Randomly assign a starting direction to each entity
    var randPathX = (Math.random() * 100 % 10) - 5;
    var randPathY = (Math.random() * 100 % 50) + 10;
    entity.setOnUpdate(function() {
      mockMove.setDirection(randPathX, 1);
      mockMove.update();
    });

    entities.push(entity);
  }

  this.add = addEnemies;

  // Remove dead entities from our management
  this.remove = function(entity) {
    for (var i = 0, len = entities.length; i < len; i++) {
      if (entities[i] === entity) {
        entities.splice(i, 1);
        return entity;
      }
    }

    return null;
  };

  // Update each entity's position, and remove dead entities
  this.update = function() {
    var enemiesDeleted = 0;
    for (var i = 0, len = entities.length; i < len; i++) {
      try {
        entities[i].update();

        var pos = entities[i].getPosition();

        if (pos.y > worldHeight + 100 || !entities[i].isActive())
        {
          entities.splice(i, 1);
          enemiesDeleted++;
        }

        if (pos.x < -100) {
          pos.x = worldWidth + 50;
          entities[i].setPosition(pos);
        } else if (pos.x > worldWidth + 100) {
          pos.x = -50;
          entities[i].setPosition(pos);
        }
      } catch (e) {}
    }

    if (enemiesDeleted > 0) {
      for (var i = 0; i < enemiesDeleted; i++) {
        var offset = (Math.random() * 100 >> 0) % (worldWidth / 75 >> 0);
        var x = 50 * offset + 25 + (25 * offset);
        var y = 0 - Math.random() * 100 - 100;
        addEnemies(x, y, {});
      }
    }
  };
};

Because we are using a component-based architecture, these three tasks aren't complex at all. In order to create a new entity, we simply instantiate the class and add the necessary components it needs to function. To add variety to the game, we can randomly assign a different sprite to each entity created as well as randomly tweak the properties of each entity, such as making it move faster, cause more damage, look bigger, and so on.

Removing dead entities is even easier. All we need to do is iterate over the list of active entities and remove them from the list if the active flag for an entity is unset. One thing we could also do is remove any entities that wander too far off the screen area, so that we don't need to manage entities that can't possibly be hit by the player's lasers.

Finally, the update function takes the job of updating each active entity's position (or rather, it tells each entity to update its own position based on the direction they're headed), simulate some basic artificial intelligence by moving each entity forward, then removing any dead entities.

GameLoop.js

The game loop class takes care of running the game's logic for each frame. The main added value that we get from using a class such as this is that we can encapsulate this boilerplate functionality and reuse it with different settings with minimal effort.

// Namespace the game loop class
var Packt = Packt || {};

Packt.GameLoop = function(fps) {
  var fps = fps;
  var frameDelay = 1000 / fps;
  var lastFrameTime = 0;
  var isRunning = true;

  // By default, the game tick is empty, indicating that we expect
    the client
  // to provide their own update function
  var update = function(){};

  // Once the game loop object is set to running, this functionwill be called
  // as close to the specified frame rate as it can, until theclient code
  // sets the object's running state to false
  function run(time) {
    if (isRunning) {
      var delta = time - lastFrameTime;

      if (delta >= frameDelay) {
        update();
        lastFrameTime = time;
      }

      requestAnimationFrame(run);
    }
  }

  // Allows client code to start/stop the game loop
  this.setRunning = function(running) {
    isRunning = running;
    return isRunning;
  };

  this.isRunning = function() {
    return isRunning;
  };

  this.run = run;

  // Allows client code to override default update function
  this.setOnUpdate = function(cb){
    update = cb;
  };
};

When we create an instance of this class, we tell it how fast we want the game loop to run in terms of frames per second and the class takes care of the rest. Once this is set up, the class will class its own update class at whatever frequency we tell it to. As an added bonus, we can also specify our own update function to be executed every time the game loop ticks.

PhysicsManager.js

Similar to the EnemyManager class, the PhysicsManager class is responsible for isolating complex functionality so that the client code is cleaner and the functionality can be reused elsewhere. Since this class is a bit more involved, we won't be showing the complete source code for it in the book. As with the other chapters, check out Packt's website for this book.

In summary, the PhysicsManager class takes a reference to all of the enemy entities (which it can get from the EnemyManager object), all of the player's laser beams, and the player's entity itself. Then, inside its update method, it checks for collision between all of those entities.

Vec2.js

Since the physics engine of this game makes extensive use of vector structures and since JavaScript doesn't provide native vector data types, we decided to create our own. This simple class represents a vector with two components and provides a function to normalize the vector. This is especially useful when we want to move an entity in whichever direction it faces.

main.js

Finally, we bring it all together in a file that we might as well call main.js. This file looks an awful lot like me when I go to a fast food restaurant: take one of everything and see how it all goes together. First we instantiate a canvas object, then the player entity, an EnemyManager object, a PhysicsManager object, and finally, a game loop object. After everything is wired up, we start the game loop and the game is set in motion.

(function main(){
  var WIDTH = document.body.offsetWidth;
  var HEIGHT = document.body.offsetHeight;
  var MAX_ENEMIES = 100;

  // The main canvas where the game is rendered
  var canvas = new Packt.Canvas(WIDTH, HEIGHT);
  document.body.appendChild(canvas.getCanvas());

  // The energy widget
  var playerEnergy = new Packt.Widgets.EnergyBar("energyBar");
  document.body.appendChild(playerEnergy.getElement());

  // The player entity, along with its required components
  var player = new Packt.Entity(Packt.ENTITY_TYPES.SHIP,
    canvas.getWidth() / 2, canvas.getHeight() - 100);

  var playerLaserGunComp = new Packt.Components.LaserGun(player, canvas, 10);
  var playerStrengthComp = new Packt.Components.Strength(player, 0, 100);
  var playerMoveComp = new Packt.Components.Drag(player, canvas);
  var playerPhysComp = new Packt.Components.Physics(player);
  var playerSpriteComp = new Packt.Components.Sprite(player, "./img/fighter.png", 64, 64);
  playerSpriteComp.setCtx(canvas.getContext());
  playerSpriteComp.setSpriteCoords(64 * 3, 0, 64, 64);
  player.addComponent("sprite", playerSpriteComp);
  player.addComponent("drag", playerMoveComp);
  player.addComponent("physics", playerPhysComp);
  player.addComponent("strength", playerStrengthComp);
  player.addComponent("laserGun", playerLaserGunComp);

  // Override the player's update function
  player.setOnUpdate(function() {
    var drag = player.getComponent("drag");
    drag.centerEntity();

    var pos = player.getPosition();
    var laserGun = player.getComponent("laserGun");
    laserGun.add(pos.x + 28, pos.y);
    laserGun.update();
  });


  // The enemy manager
  var enMan = new Packt.EnemyManager(canvas);
  for (var i = 0; i < MAX_ENEMIES; i++) {
    var offset = i % (WIDTH / 75 >> 0);
    var x = 50 * offset + 25 + (25 * offset);
    var y = -50 * i + 25 + (-50 * i);
    enMan.add(x, y, {});
  }

  // The physics manager
  var phy = new Packt.PhysicsManager();
  phy.setPlayer(player);

  // The game loop, along with its overriden update function
  var gameLoop = new Packt.GameLoop(60);
  gameLoop.setOnUpdate(function() {
    // Check if game is over
    if (playerStrengthComp.getEnergy() < 0) {
      document.body.classList.add("zoomOut");

      var ctx = canvas.getContext();
      ctx.globalAlpha = 0.01;

      gameLoop.setRunning(false);
    }

    // Add everyone to the physics manager to check for collision
    var enemies = enMan.getEntities();
    for (var i = 0, len = enemies.length; i < len; i++) {
      phy.addEnemy(enemies[i]);
    }

    var playerLasers = playerLaserGunComp.getShots();
    for (var i = 0, len = playerLasers.length; i < len; i++) {
      phy.addPlayerShots(playerLasers[i]);
    }

    // Update positions
    enMan.update();
    player.update();

    // Check for collisions
    phy.checkCollisions();

    // Draw
    canvas.clear();
    for (var i = 0, len = enemies.length; i < len; i++) {
      enemies[i].draw();
    }

    for (var i = 0, len = playerLasers.length; i < len; i++) {
      playerLasers[i].draw();
    }

    player.draw();
    playerEnergy.setEnergy(playerStrengthComp.getEnergy());
  });

  // Get the game going
  gameLoop.run();
})();

The main reason for the self-invoked main function is to privately scope all of the variables contained within the function is to prevent users from manipulating the game from a browser's JavaScript console. Had the game variables all been stored in the global scope, anyone with access to that would be able to manipulate the game state. Also, since this function is merely a setup function, this would be the perfect place to put any conditional logic to load alternate resources based on the user agent executing the script.

index.html

The host page for this game could not be any briefer. All that we do in this file is load all of our resources. Since different components sometimes depend on other components or other modules defined in our game (and since JavaScript provides no mechanism to load individual components into a script), the order in which our JavaScript resources are loaded is important.

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>2D Space Shooter</title>
    <link rel="stylesheet" href="./css/style.css" />
  </head>

  <body class="animBody">
    <script src="./js/Vec2.js"></script>
    <script src="./js/components/Sprite.js"></script>
    <script src="./js/components/Move.js"></script>
    <script src="./js/entities/Entity.js"></script>
    <script src="./js/Canvas.js"></script>
    <script src="./js/GameLoop.js"></script>
    <script src="./js/components/TouchDrag.js"></script>
    <script src="./js/components/Physics.js"></script>
    <script src="./js/components/Strength.js"></script>
    <script src="./js/components/LaserGun.js"></script>
    <script src="./js/PhysicsManager.js"></script>
    <script src="./js/EnemyManager.js"></script>
    <script src="./js/widgets/EnergyBar.js"></script>
    <script src="./js/main.js"></script>
  </body>
</html>

Mobile optimizations

In this final section, let's take a look at a few aspects of the game that we could (and should) optimize particularly for deployment on mobile devices. Although some of the following optimizations discussed also overlap in terms of desktop optimizations, they are especially impactful in mobile web development.

Combine resources

Although it is a good practice to write loose, modular code, we must not stop there. Before deploying the application to a production server, we would be wise to at least combine all of those files into a single file. The easiest way to do this would be by simply concatenating each file and serving up that larger file instead of multiple ones.

The reason this is preferred over sending multiple individual files to the client is because after a certain amount of concurrent connection to the same server, the browser will queue consequent connections and the total time it will take to load all of the files will increase.

Also, after all of the resources have been combined into a single resource, we should also use one of the many available tools that lets us compress, minify, obfuscate, and uglify our code. Anything we can do to reduce the code to the smallest possible amount of bytes is a big win for a mobile player. One particularly powerful tool for the job is the popular open source Closure Compiler developed by Google. Among its many features, the Closure Compiler also offers a function that analyzes the final code and removes any unreachable, dead code. Doing this will further reduce the final size of the application code making it especially handy for download over limited network connections, such as those found in most mobile devices today.

Track touches by IDs

The way we wrote our component to handle user input through touch makes the assumption that only one touch will be used at all times. While this assumption may hold true for most of the time in our game, it may not be the case in other games. The TouchDrag component always looks for the touch information on the very first touch object found within the changed touches list. The only issue with that is that the original touch may not always be the first array element within its parent array.

To change this, all we need to do is keep track of the touch ID of the finger that first touches the screen, then reference that touch based on its identification value.

Packt.Components.TouchDrag = function(entity, canvas) {
  var touchId = 0;

  // When a successful touch is first captured, cache the touch'sidentification
  function doOnTouchDown(event) {
    event.preventDefault();
    var phy = entity.getComponent("physics");
    var touch = event.changedTouches;

    if (phy) {
      touchId = touch.identifier;
      isDown = phy.collide(touch[touchId].pageX, touch[touchId].pageY, 0, 0);
    }
  }

  // Clear the touch flag on the entity, as well as the touch id
  function doOnTouchUp(event) {
    event.preventDefault();
    isDown = false;
    touchId = 0;
  }

  // Always move the entity based on the cached touch id
  function doOnTouchMove(event) {
    event.preventDefault();
    var touch = event.changedTouches;

    if (isDown) {
      pos.x = touch[touchId].pageX;
      pos.y = touch[touchId].pageY;
    }
  }
};

By tracking the original touch and only responding to it, we can guarantee fidelity in the touch input, even if multiple touches are initiated on the screen. This would also be the proper way of tracking separate touches for the purposes of implementing gestures or other input triggers based on an arbitrary combination of touches.

Use CSS animations with caution

There is a strange phenomenon that sometimes happens in mobile browsers when we are too generous with some of the newer CSS properties. For example, if we add a box shadow to an element, we can still get pretty strong performance. Optionally, if we add a CSS transition to some other element, performance could still be maintained. However, if both of these properties are assigned together then performance all of a sudden drops down to barely playable conditions.

Since there is no formula that describes which properties should and should not be used, and which combinations should be avoided, the advice here is to use the least amount of CSS properties possible and add them slowly. In the case of our game, where the desktop version makes heavy use of CSS animations to render the background, we need to consider the implications that this may have on a mobile device. After trying the effect on the two most popular mobile platforms today and seeing performance drop severely, we concluded that the particular animation we wanted, along with a constantly rendering canvas, was too much for a mobile processor to handle.

One way to determine if a particular CSS animation is too demanding on a mobile device is to use profiling tools such as Google Developer Tools and take note of the sort of work that the browser needs to do in order to achieve the desired animation. In cases such as this game, where a background detail was so computationally intensive to generate that it conflicted with the calculations required to simply play the game, we might well opt for a less demanding alternative. In this game, instead of loading the CSS animation onto the document body, we simply display a still background graphic.

Use separate canvases for each game layer

As briefly discussed earlier, one powerful optimization technique in HTML5 rendering is to use more than one canvas. The point is to render less frequently those things that only need to be rendered every once in a while. Those things that need to be rendered much more often, we render by themselves in a dedicated canvas context so that no CPU (or CPU) power is used to render details around these elements.

For example, the background scene of a game generally stays the same for several frames. Instead of clearing the entire canvas context, only to redraw those exact same pixels, on their exact same prior location, we can just render a background scene on a dedicated canvas and only render that scene again when the screen scrolls, or the scene otherwise changes. Until then, that canvas need not be bothered. Any movable objects and entities that need to be rendered multiple times per second can just be rendered on a second canvas, with a transparent background, so that the background layer can be seen through.

In this game, we could very well have rendered the image used as the background graphic onto a dedicated background layer, then provide the background animation to the canvas this way. However, since HTML5 provides a similar function that produces the same effect, we opted for that instead.

Use image atlases

The idea behind image atlases is really quite brilliant. Since the canvas API specifies a function that allows us to draw onto the canvas context from a source image specifying the area within the image that the pixel copying is to take place, we can simply use one master image from which all of our graphics assets can be drawn.

Instead of sending multiple loose images from the server down to the client, we can simply bundle all of our images into a single atlas file then draw each asset from a certain section of this larger collage.

Following is an image atlas with many smaller images inside it placed side by side, allowing us to retrieve each individual image from a single image asset. One of the main benefits of this technique is that we only need one HTTP request to the server in order to gain access to all of the images used within the atlas.

Use image atlases

Of course, the challenge in using this technique is that we would need a way to know where each particular image is located within the atlas file. While doing this by hand might not seem too cumbersome on a small project such as this one, the complexity of this task gets unruly very fast.

There exist many open source tools available to solve this very problem. These tools take individual loose image files, bundle them up in the smallest possible atlas that can be generated from the list of images provided, and also generate a JSON file that we can use to map each loose image to their new representation within the atlas.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.20.68