Last year we had the opportunity to create a series of five Mahjong-link games 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 assumed we’d be able to reuse much of the same code, that 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.
There were a few issues that made things more complicated:
* Desire to reuse some code in other games that didn’t have the same mechanic
* Uncertainty about 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 organised into three distinct buckets;
1) Code and configuration files specific to a given game
2) Code that could be shared by all the games with this mechanic (we called this LinkEngine)
3) Code that could be used by any game (we called this the Absolute library).
It wasn’t always immediately clear into which bucket to place a particular functionality. Sometimes we would build a feature, and only after working with it for a while, realise 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 we 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 on screen 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 were changed as well such as the logo for the game, 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 that 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 allowed us to easily re-map 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, optimisations and feature improvements throughout the process. While each bug fix in the library code benefitted all the games using the library, each feature addition and bug fix required testing across all games.
* Loose typing
* Object literal notation
* Ubiquity & immediacy
Strongly typed languages allow the compiler to catch errors and perform optimisations that can’t be performed in a loosely typed language. Loose typing has been criticised for these reason, however, it allows 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. pre-factoring
Most experienced developers avoid pre-optimising code, rather, they build functionality and then look for bottlenecks, spot optimising where necessary to achieve desired performance. That doesn’t mean they don’t think about writing efficient code up-front, 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 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. Needless to say, we will continue to use this methodology as we move on to our next titles.