JavaScript Game Code Organization

| by Greg Smith

At Bocoup, my colleagues and I often laze about in antique leather armchairs, sipping Mai Tais, waxing rhetoric about important issues-of-the-day including international politics and automatic semicolon insertion. One thing I find fascinating is how people working on different types of projects have different wisdom to share: best practices for jQuery plugins are different than those for Facebook apps, and tips for Backbone.js ecommerce sites may not be useful when developing real-time strategy games.

What I’d like to share in this article is some code organization tips and tricks I’ve learned while making HTML5 games. I’ve tried to keep them as generally useful as possible, but you’ll definitely get the most out of this if you make games like I do.

First I’ll discuss organizing JavaScript code into files and modules. Then I’ll talk about code sharing approaches such as component systems. Lastly I’ll share some ideas for writing data-driven code in games.

Files and Modules

It should go without saying that a large application all stuffed into one file is a maintenance nightmare. Still, even now, the logistics of organizing JavaScript in separate files are being ironed out. First let’s look at the file issue itself, then the more complex issue of what sorts of modules those files contain.

Many Files in Development, Concat in Production

When setting up a new JavaScript project, I recommend using grunt, a build tool that has built-in tasks for concatenating all your files into one, then minifying the result. You want users to end up downloading just one minified JavaScript file. This is great in production, since it reduces HTTP requests and download sizes. In development, it’s not so good: having readable code in separate files is necessary for efficient debugging.

I recommend having something like this in your index.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
<head>
  ...
  <!-- source files for development -->
  <!-- <script src="src/setup.js"></script> -->
  <!-- <script src="src/player.js"></script> -->
  <!-- <script src="src/enemy.js"></script> -->
  <!-- <script src="src/bullet.js"></script> -->
  <!-- <script src="src/explosion.js"></script> -->

  <!-- concatenated / minified source for production -->
  <script src="production/app.js"></script>
   ...
</head>

This way you can comment out app.js and uncomment the source files for easy development.

However, this isn’t ideal. One thing you might do is modify your build process to have options to build for development or for production, saving you the effort of manually commenting / uncommenting lines. No build tools do this out of the box as far as I know, but for some projects this extra effort of customizing your build process may be worthwhile.

Source Maps

Google Chrome has a cool new feature called Source Maps that solves this problem. When you use a JavaScript concatenator/minifier that supports Source Maps, the compiler will add a comment to the code that refers to the source map (a header can also be used). The source map contains all the necessary information for linking from the compiled code to the source file. As a result, development tools that support Source Maps will understand what file some given code originally came from. There is no longer any issue debugging / developing with minified code.

Right now the Closure compiler and Google Chrome are the only compiler and browser that support Source Maps, so it can only be used on certain projects. Still, if more tools start to support it, this may be the future of debugging JavaScript.

IIFEs and Namespaces

Now that you have a build process that intelligently handles multiple files, what actually goes in those files?

The first concern is global variables. Since global variables in separate files are still shared, we can use IIFEs to give each file its own local scope. Here is an example:

file1.js

1
2
3
4
5
6
7
8
9
10
(function() {
  
  var i = 0;

  setInterval(function() {
    i += 2;
    console.log("File 1 counter: " + i);
  }, 1000);

}());

file2.js

1
2
3
4
5
6
7
8
9
10
(function() {
  
  var i = 0;

  setInterval(function() {
    i += 5;
    console.log("File 2 counter: " + i);
  }, 1000);

}());

The first and last line of each file is the IIFE. The only purpose of those lines is to create a local scope for each file. As a result, the i variable in each file will not conflict.

This works to avoid conflicts and polluting the global scope, but what about when we do want to share state between files?

A global namespace is my favorite way to handle this. In this approach, your game will have one single variable in the global scope. It will contain a hierarchy of all the game’s global state. Here is an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(function() {
  
  // create global namespace
  window.game = {};

  // add some data
  game.player = {
    hp: 10,
    mp: 5
  };

  game.enemies = [];

  game.enemies.push({
    name: "zombie",
    hp: 10
  });

}());

Then, another file could access this information:

1
2
3
4
5
6
7
(function() {
  
  // set up the UI
  document.getElementById("hp").innerHTML = game.player.hp;
  document.getElementById("mp").innerHTML = game.player.mp;

}());

Often it’s convenient to use the IIFE to pass through global variables as local variables. The previous example could be modified like so:

1
2
3
4
5
6
7
(function(player) {
  
  // set up the UI
  document.getElementById("hp").innerHTML = player.hp;
  document.getElementById("mp").innerHTML = player.mp;

}(game.player));

A pattern called the Module Pattern is similar. In this pattern, the IIFE is modified to return a value that is put in the global scope:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
window.game.boss = (function() {
  
  var hp = 30;

  if (game.temperature < 5) {
    hp -= 10;
  }

  return {
    name: "elder red kitten",
    hp: hp
  };

})();

Notice in the last line that we’ve moved the () outside of the parentheses that wrap the IIFE’s function. This is not necessary but is often done to indicate that the return value is used.

Module Systems

With IIFEs and namespaces we have solved the global scope issue. However, we may still have issues in projects with complicated dependencies. In some games its reasonable to just include all your scripts in the order that they depend on each other. For projects with more complicated dependencies, especially multi-page apps with different script needs on each page, a module system may be helpful.

RequireJS is an advanced JavaScript module loader. With RequireJS, you can have just one script element in your HTML:

1
2
3
4
5
<head>
  ...
  <script data-main="scripts/main" src="scripts/require.js"></script>
  ...
</head>

Then, in your main.js file, you use require to load other scripts that you need:

1
2
3
require(["scripts/world"], function(world) {
    // world.js will load, then this code will run
});

Each other file loaded by require can have its own dependencies, and so on. You end up with a dependency graph that reflects your code’s actual needs, rather than a mysterious load order that just so happens to work. This makes it much easier to set up a separate page that needs some, but not all, of your scripts. Think of a save / load screen, or a high scores list, that needs information about loading player accounts, but not information about collecting powerups.

You need to follow the AMD specification to use require.js, so you should familiarize yourself with that if you intend to use it.

Now that we have these basics worked out, we can move on to the fun stuff: how to organize the code itself.

Code Sharing

A whole lot of code organization boils down to code sharing. How do you share code that is used in more than one place? It’s a big question, and ultimately the answer depends on your project, your language, your frameworks, and your personal preferences.

A common symptom of poor code sharing is super objects. A super object is an object that has too many responsibilities. It’s too big, and it contains a lot of code that would be better organized into different units. Often, super objects are a result of laziness. When writing new code, it’s easiest to just put the code in the nearest super object, rather than deciding where it really belongs. The best weapon in the fight against super objects is diligence.

Here’s an example. Let’s say you have player.js and enemy.js. You want to write the code wherein an enemy damages the player. At first, it seems convenient to put it in player.js, since the code concerns the player’s properties.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(function() {

  game.player = {

    receiveDamage(amount) {
      if (Math.random() < this.resist) {
        amount /= 2;
      }
      this.hp -= amount;
      if (this.hp <= 0) {
        this.die();
      }
    }

  };

}());

Later, we add a feature where an enemy can poison the player. Let’s add that code in there.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(function() {

  game.player = {

    receiveDamage(amount, enemy) {
      if (Math.random() < this.resist) {
        amount /= 2;
      }
      this.hp -= amount;
      if (this.hp <= 0) {
        this.die();
      }
      else if (enemy.poisonous) {
        this.setPoisoned(true);
      }
    }

  };

}());

Then we add enemies that can blind the player. Then enemies that can stun the player. receiveDamage is getting too big, and player is becoming a super object.

One problem is that receiveDamage is very entangled with both the enemy and player objects. To untangle this code, we should put the enemy’s responsibilities in one place, and the player’s responsibilities in another.

Another issue is that there is only one type of player, but many types of enemies. In this case, each type of enemy should describe its own unique interaction with the player, rather than having the player, or a common enemy object, describe every type of interaction.

Let’s rewrite this code in 3 different files.

Now we can extend this code in a more reasonable way. You may have noticed that the poisonous enemy uses explicit delegation to call enemy.damagePlayer. This is perhaps the simplest form of code sharing, and it works, but this is a clear example where implicit delegation via inheritance may be preferable. We could refactor this code to use inheritance, but instead, let’s discuss another option…

Components

Laziness isn’t the only cause of super objects. A more subtle problem is when your code sharing strategy doesn’t suit your object model. Many classically-trained programmers have been taught that inheritance is a one-size-fits-all code sharing strategy. In fact, many programming languages seem to have that assumption baked in. Let’s show an example where classical inheritance falls short, then discuss an alternative.

Here is some simple code to start with:

Maybe you wrote this code by writing methods as they became useful. But, stepping back, you realized this: all of these objects need to be able to do all of these things! Using inheritance, you might move all these methods up to a common parent, like so:

Now the common behavior is shared, but you can still add custom code for each type of entity.

There are two potential problems here. First, it’s possible that entity will end up being a super object. Second, it’s possible that while most enemies can move, some can’t! Or that there are rare indestructible crates! How do we deal with these exceptions?

For many applications, inheritance works great. It turns out, though, that the problems I just described are very common in game development. For this reason, many game developers use components.

With inheritance, objects get their behavior from their place in the inheritance hierarchy. With components, objects contain any number of components which determine their behavior. These components can be mixed and matched differently in each object. Let’s look at an example using components:

The first thing you’ll notice in this code is that a simple component system is implemented in componentSystem.js. Some game frameworks provide their own more robust component systems. Then there are files for each component that entities can contain. At the end there is some code demonstrating how to use the component system to create entities with different capabilities.

At first, it might seem strange to check if a method exists when you want to use it. You may not always need to do that, for example in code where you can safely assume a method will be present, or for methods that you know are always present. You could even create a component system where this check is not necessary. For my games, I tend to embrace duck typing. I’ve found that the test isn’t tedious because it’s important to consider what might need to go in the else block.

You could build a more complex component system where components remain encapsulated after they’ve been added to an entity, which may be useful if you want to achieve state that is private to each component. This may be seen as a “true” component system, where what I’ve built in this previous example may be closer to mixins, or abstract base classes with multiple inheritance. The lines between these concepts tend to get blurry in JavaScript, and either way, what we’ve built in this example is incredibly expressive as is.

Code and Data

Another aspect of code organization in games is data. Data-driven programming involves a separation of code and data, where the code reads in data files that describe objects and their states, then the code is responsible for describing how those objects behave and interact. A common example in games is world files: descriptions of maps and monsters read in and brought to life by the game code.

Let’s look at a simple example of some data-driven code:

This is great for two reasons. One, a separation of data and logic makes for more maintainable and more reusable code. Two, a team member without programming experience is better able to contribute to content creation.

That should be easy enough. What’s less clear is what happens when the line between code and data gets blurry. Let’s say that we want to create some code that is only relevant to dragons:

Now damage against dragons is reduced by half. Maybe lots of enemies have this “half damage” trait? You’d be better off making a halfDamage property in your data format. That’s pretty straightforward.

On the other hand, it depends on how your data will grow. The direction we just described will work well if you have a small number of traits that are frequently reused. Where this approach falls short is when you have a large number of enemies with unique properties. You’ll end up with a big, complicated data format that is mostly dedicated to describing exceptions to the rules.

What I advocate in these cases is data-like code. Rather than using JSON, we’ll use JavaScript that happens to be mostly data, but with the ability to add custom code at predefined locations. Here is an example:

We’ve added the ability for enemies to define a custom filter on the amount of damage they receive. We can easily add one enemy that always takes half damage, then another enemy that takes double damage. I added self as an argument to filterDamage because it’s very useful in these types of data formats: you may want to filter damage differently based on the enemy’s state. The best part is, you can create all kinds of unique enemies without modifying the data format. The code and data are still mostly separate, except where a little overlap is appropriate. And a non-programmer could still work with this data pretty easily, until they need to ask for help with a special enemy (which they would have had to do anyway).

Of course, if your game doesn’t need anything this complicated, it’s always best to stick with the simplest data format that works.

Conclusion

I hope these tips were helpful for you. If you want to play a game I developed where I learned some of these lessons, check out 91. If you want to see a game framework that uses a cool component system as described in this article, check out my boxbox project. Have fun making games!

in JavaScript .

Comments