You can read other articles in this series here:
Part 1: Design & Prototyping
Part 3: Balancing & Polishing
Part 4: Testing, Release, and Marketing
In the first article, we covered how Catch the Monkey started from initial simple concept, to the technology we chose, through the prototyping phase. At the end of prototyping we had a greatly increased design, but despite knowing better, we didn't document it thoroughly. We knew we had 12 tools to create, 10 types of monkeys, and some vague concept of a store which would allow the purchase of upgrades. How many upgrades and what they would do was not finalized. It was time to start coding!
This article is longer than the previous, I have attempted to keep it of reasonable length by highlighting only the most interesting aspects from the core construction phase. If you have a specific question, just post a comment and I'll respond.
We Going to Do this or Not?!
As mentioned in the first article, the artist was working full time but I as the programmer was only able to work part time as I was required by other aspects of the business. The project dragged. It finally reached a point where the project would be cancelled due to lack of progress. Instead, I mapped out the time remaining to build the game. About 6 weeks (50hrs x 6 = 300hrs) should do it. I made an extreme decision: I booked a 6 week hiatus from work to go to my cottage and focus 100% on the game. While my wife was less than thrilled, she was supportive of seeing me get the game done. It was time to go "all in". Hind sight confirms this was the right way to recover the project.
Our Single Biggest Mistake
Not having a properly defined design document would appear to be our largest mistake, but we made one that completely dwarfed it.
If you study the zombies in Plants vs Zombies, you will see there are many types of zombies, but they are made up of several graphical parts (head, body, arm, arm, legs) and several optional decorators (pylon, helmet, paper). By reusing and varying these components you can have many different types of zombie with minimal memory requirements. We wanted a similar approach with many kinds of monkeys each with varying abilities and weaknesses.
However, and we painfully learned this later, if you want to have this kind of reuse, you have to lay down very specific rules of what the characters can and cannot do. Notice in plants verses zombies that the zombies always face the camera (like the 2D South Park animation). No matter what they do, they never turn away from the camera to a profile view.
Well, early on in our animation and prototyping we decided when the monkey arrives at a plant he will plop down, TURN HIS BACK, and begin digging. Then when he gets a potato, he will TURN BACK and proceed to eat it. We completed all the artwork for the regular monkey before we discovered what a problem this was. When we wanted to have a hat monkey, we thought we would just create a separate hat object, attach it to the monkey, and off we go. Well as we did it we realized the hat (or vest, or sunglasses) has to turn with the monkey as he turns away from the camera. This requires one decorator frame per monkey frame and pixel perfect alignment. This means a whole host of painstakingly researched coordinates per frame to get it all to look right. It was so much work, and we didn't want to redraw the digging animation, so we made an expedient decision: just duplicate all the frames for the regular monkey to the hat monkey with the hat pasted right into the frame. The artist went ahead and did this for each of the 6 additional types of monkeys.
Here is the math of why this was such a problem later:
1 monkey has a set of interaction sprite sheets (fear, ducky, laughing, walking, climbing, etc.) taking about 20mb of VRAM memory.
7 monkey types x 20mb = 140mb VRAM
The iPhone 3GS (iPod 3+) only has ~55MB of VRAM available (with a 15MB heap) before it starts crashing.
We had initially wanted to target the iPod Touch 2+, but it has only 30MB of VRAM and it became impossible. So we increased the system requirements to iPod 3+ and scrambled to get the VRAM down. We'll talk more about this in the next article.
So the lesson is always map out memory requirements during the design phase, before you build it, rather than in the middle, or after. Had we of known the ramifications of the monkey turning away from the camera we would have gone a different direction with the art and the game wouldn't be noticeably different.
Cute Monkeys in a Nasty Real-Time World
Many business developers I know avoid writing multi-threaded solutions when they can avoid it. Why? Because the race conditions that can occur between two separate threads doing their own thing are a nightmare for testing. There are so many permutations of what could be happening simultaneously in the application that if it crashes, it is difficult to reproduce never mind fix permanently.
When it comes to games, they are already real-time in that the Update() loop is executed every so many milliseconds not matter what. There is no concept of "blocking" calls like there is in Windows Forms development. This is just the way games are, and this is not what I'm referring to.
I'm talking about a real-time game verses a turn based game. A turn based game waits for user input, then responds accordingly; while waiting for user interaction there may be things happening on screen, nice effects and such, but the actual state of the game doesn't change. In a real-time system the game state is constantly changing regardless of player interaction.
For our first time game, we NEVER should have chosen to do a real-time game.
Catch the Monkey was an incredible amount of effort to make everything work in a constantly changing environment. The number of testing scenarios is probably 20 times greater than a turn based system. The ability to replicate scenarios is extremely difficult, even when programming specific unit tests to occur. There was a point late the construction phase I wasn't sure I could ever get it to stop crashing. Fortunately Marmalade has some amazing memory monitoring tools built into it I was able to find all the issues (I think!).
We learned this lesson so bitterly the next title we are currently working on is turn based.
Obviously the power of OOP is the ability to build small, focused, encapsulated objects and then work with them at a higher level. My goal was to create an object hierarchy that knew how to instantiate, move, and render itself.
There was a time in my career where I didn't do modelling. Once someone showed me Rational Rose, UML, and modelling I never went back. I always model my code, even personal projects no one will ever see, because I find it the best way to think through the problems before the code gets in the way. Rational Rose (or any proper modelling tool) helps you think through the design as you design. I used Rational Rose for several years, but when I went out on my own I couldn't afford the $2,000/seat license. Fortunately the Open Source community came to the rescue with StarUML. StarUML is a powerful free object modeling tool. It is virtually identical to Rational Rose (at least to the last version I used in 2003).
Looking at the class design diagram, notice the two fundamental objects: GameObject and UIObject. Both of these inherit from Graphic. Graphic encapsulates all the Marmalade 2D API interaction, and therefore is necessary for rendering whether it is a monkey, a story slide, or a text object.
A GameObject is an object used in a GameScene (which is a level you play). It manages its own state, sprite sheets, depth calculation, scaling (based on depth), click handling, and hit detection. All play objects inherit from GameObject. UIObject is similar to GameObject, but is more lightweight and designed for non-play scenes, such as text, buttons, and images in the store or tool selection screens.
We used GoF design patterns as necessary. For example:
- We used the Factory pattern for our Level class; feed in a week and day, and it spits out a formatted level object, complete with any necessary tutorials.
- We used two singletons for caching image files and sound files called GraphicManager and SoundManager, so even though each object is responsible for loading/unloading it's assets, it does it through these caches to minimize the actual memory used.We used a singleton for player state (number of stars, current progress, which tutorials have fired, upgrades purchased). This made it extremely simple to serialize/deserialize player progress.
- We used the Decorator pattern for adding graphical effects to any GameObject, such as fade in, fade out, flashing, etc.
One of the early conceptual struggles I had was how to bring all the different types of screens (a store, a tool selection, story modes, title screens, option/menu screens, game modes) together into a nice organized OOP paradigm. While researching I found two excellent articles by iPhone game maker rivermanmedia:
The Scene System
The GUI Stack
I knew this paradigm was the way forward not just for this game, but probably all future games.
The Scene system breaks down the game into a series of scenes. In Catch the Monkey I ended up with 19, like SceneTitle and SceneDialog. Each of these inherit a common interface from Scene such as: Init(), Update(), Render(), Shutdown(). I created a SceneManager singleton that contains all the logic related to scene creation, shutdown, and transition. Now my code can be blissfully unaware of what else is going on at a higher level. If I want a scene to end and begin a new scene, I call:
If I want the new scene to be focused and on top of the current scene, I call:
The SceneManager knows if there currently are other scenes involved, winding them down appropriately, removing their assets from memory, doing a fading transition, then initializing and firing up the new scene. With this in place the real-time game now behaves more like a Windows Form application, with dialogs able to call dialogs and just let the OS worry about sorting it all out.
The second key concept is the GUIStack. The GUI Stack sits inside the SceneManager and replicates "focus" of a scene just like how Windows does for forms and dialogs. By pushing and popping scenes onto the stack, I can control which scene has its Update() and Render() code called. If a scene doesn't receive the Update() call, it is effectively frozen in time (paused). In pure form, the top scene is the only one to have its Update() called, while all in the stack have their Render() called. Later in testing I removed calling Render() to every scene in the stack for performance improvement. For scenes that require a background scene (such as a dialog window appearing over top of the game screen) I Instead take a screenshot of the current state, then display that as a backdrop to whatever the current scene is.
Using Marmalade in 2D
As previously mentioned, we were targeting both iPhone and Android simultaneously with C++ in Visual Studio 2008. While Marmalade is a 3D framework, we knew we were making a 2D title and therefore focused on the Iw2D APIs. I'll highlight the fundamentals of 2D animation with Marmalade's 2D API.
As you you'll see, Marmalade works at a pretty low level. This isn't GameSalad here, and that is one of the reasons I chose it. Given the choice, I prefer the flexibility and power of a low level API rather than being limited to what a framework designer decided I should be able to do (or not!).
Marmalade works it magic by using a custom make file called an MKB. This file allows the user to define Marmalade libraries to pull into the project, source code, assets (sounds), fonts, and texture groups.
Marmalade has a resource manager that allows the management of image groups (texture groups) by defining them like this in the MKB file:
# Provide access to resource objects via IDE
You then define all your images in custom group files:
useTemplate "image" "image_template"
Within the code you can test if resource groups are already loaded into memory, and then load/unload them through two simple function calls:
if (IwGetResManager()->GetGroupNamed("farm", IW_RES_PERMIT_NULL_F) != NULL)
Images are loaded (and automatically uploaded to OpenGL VRAM) by asking for the image by name (without the .png extension) by using:
CIw2DImage* img = Iw2DCreateImageResource(name);
Once you have an image in memory, it can be rendered simply by calling the image drawing routine with the image you want, and the 2D vector position.
Marmalade automatically queues up all of the drawing calls in the order in which you called them, so this way you can control layering by doing your background draw calls first. So before I would run through my Render() routine I would sort all my objects by depth (lowest to highest) and then draw them in that sequence.
To complete the rendering I call these two routines, which tells Marmalade I'm finished, show it to the world:
That's it. Call those drawing routines each frame and you've got yourself a game.
Simplifying Sprite Sheets
The game comprises over 4,000 frames of hand drawn animation, most is for the monkeys interacting with their world. To manage all these images, we put them into spritesheets. Two issues needed to be considered:
- No dimension of the sprite sheet could be larger than 1024 (iPhone doesn't like textures bigger than this and Marmalade started fuzzing them)
- Sprite sheet dimensions needed to be to the power of 2 (32,64,128,256,512,1024) for the graphics card. If they weren't, the graphics card would pad them out to make them to the power of 2 anyway.
An example of a GameMaker strip.
Photoshop does not have an easy way to make a sprite sheet where each frame is universal in height/width. So we discovered a trick that saved us dozens of hours:
- Save each frame in PNG from photoshop
- Create a sprite in Game Maker to drag and drop each PNG frame from the file system for a given animation
- Export the sprite from Game Maker as an animation strip, which is each frame appended into one long horizontal PNG with the number of frames appended to the file name.
- Run our custom sprite sheet program on the strip, which would break it out into a rectangle of the smallest power of 2 dimension as a PNG.
While it sounds involved, we could go from a collection of png frames to a squared sprite sheet in under 2 minutes. Just for its ability to create sprite strips GameMaker is well worth having!
The final spritesheet, power of 2 sized.You can't tell, but we also dropped the color depth from 32bit to 16bit for memory.
Who is the harshest critic, the audience or the musician? The musician, for they have the double burden of knowing every note they missed and how much better they played during practice. So while the creator is extremely biased and forgiving of their creation, there is a harsh reality of what they intended and what they ended up making. I would say the music is always much sweeter in the imagination than on the page.
At the end of the 6 weeks I had finished building the core of the game. I came in around 340hrs. Knowing I have a personal biased, having played the game over a thousand times during build cycles, I concluded this game is actually fun. There was something magical about trying to entertain 3-5 monkeys simultaneously. Because I was away at the cottage, the artist had to take my word for it. And since I hadn't yet figured out how to deploy it, he had no way to play it other than on my laptop. But, knowing we had a good core, something we were proud of, gave us the determination for the toughest fight yet: Polishing.
In the next article we'll cover how we took a core game through polishing, feature cuts/additions, and game balancing. We had no idea what we were in for! See you next Wednesday!
- No dimension of the sprite sheet could be larger than 1024 (iPhone doesn't like textures bigger than this and Marmalade started fuzzing them)