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.
Files and Modules
Many Files in Development, Concat in Production
I recommend having something like this in your index.html:
1 2 3 4 5 6 7 8 9 10 11 12 13
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.
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:
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
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
Then, another file could access this information:
1 2 3 4 5 6 7
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
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
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.
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.
script element in your HTML:
1 2 3 4 5
Then, in your
main.js file, you use
require to load other scripts that you need:
1 2 3
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.
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
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
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
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…
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
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.
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.
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!