In part 1, I talked about the decisions I made early in the process of developing Clyde vs. the Maze. Here, I’ll get into the technology I used over the year-long development process, between 2010 and 2011.
Objective-C
I knew that even a relatively simple game like Clyde would involve a lot of code, and at the time I believed that OOP was the key to keeping a handle on the complexity. I’d recently become proficient with Objective-C, and more or less defaulted to it on the strength of its support in the Apple ecosystem.
Of course, the project’s reliance on Objective-C, as well as the associated Foundation libraries, meant that this casual decision locked the game into running on iOS and OS X only, which is problematic for the reasons I discussed last time.
cocos2d
cocos2d-iphone is a free game engine/library for iOS, with offshoots for other platforms. I chose cocos2d (version 0.99 at the time) because it seemed to be fairly well field-tested, with a robust community. Going my own way would’ve involved a lot of boilerplate code, and more use of OpenGL than I was comfortable with at the time.
At the time cocos2d wasn’t especially well-documented, and extending its functionality was intimidating. There are also performance problems I’ll talk about in a bit. Other than that, it was a positive experience. Its API is similar to Core Animation, which made it an easy transition, and putting things on the screen is just a matter of telling the engine where you want them.
The Actions system might be the biggest win: you can move sprites around, apply ease in/out curves to the movements, and most importantly chain movements and function calls together to create complex sequences. The slot machine bonus stage, which has animations of Clyde walking up and pulling the machine’s lever, would have otherwise required a bunch of timers and conditional logic or an unnecessarily sophisticated animation system.
Objects all the way down
Ultimately, it was OOP that gave me the biggest headaches. About halfway through the game’s development, I noticed that things were running slower than I expected. Surely a game designed to look like it ran on the NES should be able to run smoothly on an iPhone 3G!
After extensive profiling in Instruments, I eliminated some of the hot spots in my own code, but quite a few remained that were harder to tackle, and many of them were in cocos2d’s drawing code. Drilling down, I found that the delays were the result of cache misses and branch misprediction. This is how I learned about the importance of CPU caches.
Noel Llopis’ article on data-oriented design describes the above scenario exactly. CPUs are nothing more than machines for transforming data, and I had nearly ignored my data in favor of Objects, for no other reason than it seemed like the Right Approach. Of course, years ago I was taught not to optimize prematurely, but in a twist ending worthy of the Twilight Zone, I ended up writing code that was near-impossible to optimize.
I still like OOP, and will continue to use it where it’s appropriate, but I learned the hard way (or at least the thorough way) that it’s not always the best way. I’ve been experimentally rewriting the game’s core code in straight C, and while the performance might be better, I think it’s more noteworthy that the code is actually simpler. You do lose things like array bounds checking, but if you can handle a complex class hierarchy, you can handle passing a thing_count
parameter along with thing_t *thing
. I’ll write more about this approach in the future.
Lessons learned
Would I recommend the iOS/Obj-C/cocos2d stack to a new developer? I think I would, despite the issues I ran into, because it would’ve been easier to learn from them in a shorter project.
Back at the beginning of the article I said that the game was in development for a year. It was my first commercial game project, and it’s more complex than some, but should it really have taken a year? Probably not, and in part 3 I’ll get into why it did.
–Alex