By Craig Robinson
Last year we had the opportunity to create a series of five mahjong link games (a variation on the standard) in HTML5 for Spil Games. The first game we built was Dream Pet Link. Given that we were tasked with building four additional games with the same mechanic but different themes, we knew we’d be able to reuse much of the same code. We could just write the first game and then swap in new art and sound assets for the subsequent titles!
Oh, only if things were so easy. A few issues made things more complicated:
- Desire to reuse some code in other games that didn’t have the same mechanic;
- Uncertainty about the code changes necessary to accommodate differences in the art and audio;
- Features and performance improvements we knew we would discover later in the cycle.
Start your engines
We decided that functionality would be organized into three distinct buckets: code and configuration files specific to a given game; code that could be shared by all the games with this mechanic (we called this LinkEngine); and code that could be used by any game (we called this the Absolute library).
It wasn’t always immediately clear which bucket we should place a particular functionality into. Sometimes we would build a feature, and only after working with it for a while, realize that it could be made more generally useful. We would then promote it to the next level of abstraction and move it to a different bucket.
We didn’t start to split out game specific code from LinkEngine until it was time to build the second game. By that time we felt like we had a pretty good handle on what needed to be in the Absolute library, but splitting the game specific code from LinkEngine was a process that, as I describe below, required some additional effort that we believe provided a better result than if we had tried to make this separation in advance.
What do you mean you want to use a larger font?
One thing we didn’t address in the first game was how changes to the graphic assets would affect the layout of onscreen items. Some of the graphics were positioned relatively, but others had fixed/hardcoded locations. This scheme broke down when we built the second game. The theme of each game was different, and our artists chose a different font (with different metrics) for text in each game. Other asset sizes changed as well, such as the game logo, buttons, dialog box frames, progress bars, etc.
In order to accommodate these different sizes, we changed some asset positioning from fixed to relative. In situations where this wasn’t appropriate, we created a game specific JSON layout file that provided coordinates for various assets. We wrapped this functionality in a Layout class that hides the complexities from the rest of the code.
One might argue that we should have foreseen the need to support more flexible layouts up-front and we could have saved ourselves some effort by planning for differently sized assets in advance. In fact, we did anticipate this issue, but we chose not to address it up-front. The game design was very fluid, we didn’t know which features and items would stay in the game and which would go, and we knew we could easily re-factor this later.
By putting off the work to build a more flexible layout mechanism we ended up with a more elegant solution, because we were able to collect more information about the type of features and functionality required across a number of scenarios.
Sounds about right
Each game had unique sound effects with different lengths, but we were able to use these without code changes. We stuck to the same structure; the same actions caused sound effects and we had a couple of different music tracks for different parts of the game. A JSON configuration file enabled us to easily remap existing actions to new audio assets.
We now have an audio library that provides scalable support across platforms by taking advantage of high-end capabilities where available, and falling back to less rich, but still acceptable functionality, where not.
Making it better
We introduced a number of bug fixes, optimizations, and feature improvements throughout the process. While each bug fix in the library code benefitted all of the games using the library, each feature addition and bug fix required testing across all of the games.
- Loose typing;
- Object literal notation;
- Ubiquity & immediacy;
Strongly typed languages enable the compiler to catch errors and perform optimizations that they can’t perform in a loosely typed language. Loose typing has been criticized for this reason; however, it enables for much more flexibility in type conversion and class/object definition. These attributes help with prototyping and refactoring, even at later stages of development.
Refactoring vs. prefactoring
Most experienced developers avoid pre-optimizing code; rather, they build functionality and then look for bottlenecks, spot-optimizing code where necessary to achieve desired performance. That doesn’t mean they don’t think about writing efficient code upfront, just that they don’t spend more time than warranted before they know they need to.
We ended up with five great HTML5 games with very little game-specific code, a common engine for all of the link games, a library to support audio playback across a variety of devices, and a general HTML5 game engine on which we can build any type of game.
Most important, the number of game plays is in the millions, and we’re seeing great loyalty in the game. Players have logged in about 1.4 million hours of play and the time they spend in the games has doubled in the last couple of months. We will definitely continue to use this methodology as we move on to our next titles.