RSS/Atom and Twitter Feeds

An Asteroids-style game in CoreAnimation, Part One.

How would you write an arcade-style 2D game in CoreAnimation? Over the next few weeks, I'll show you how to write a resolution independent, high-speed, model-view-controller designed, Asteroids-style arcade game using CoreAnimation as the screen renderer. In this first of four parts, I'll detail the concept for the overall application and show you how to create a resolution independent window for the game.

A screenshot of the finished game

CoreAnimation as a game renderer

Drawing on the Mac is normally done through either CoreGraphics or OpenGL. OpenGL is fast, supports 2D and 3D and is already used in many games. However, OpenGL is a low-level, pure-C API, so even simple rendering and animating of 2D surfaces can require a whole library of code to accomplish.

CoreGraphics and the NSView hierarchy are a whole library of well-written code. They are clean to use and support a wide range of features without needing to write them yourself. Sadly, CoreGraphics is not ideal for games since it does not easily render 30 fullscreen frames per second (the minimum standard for action games) — even with Quartz Extreme and the other improvements Apple has added since Mac OS X 10.0.

This is where CoreAnimation comes in. Apple introduced CoreAnimation in Mac OS X 10.5 (Leopard) to dramatically accelerate the animation of 2D rectangular textures. Since it integrates so well into the existing CoreGraphics and NSView hierarchy on the Mac, it suddenly gives CoreGraphics a way of delivering 30 (or more) fullscreen frames per second and makes a writing a game possible using nothing more than the Foundation, AppKit and QuartzCore libraries.

Quartzeroids One

The application I'll present over the next few weeks will be similar to an experiment I wrote about 8 years ago. Back then, the application was named Quartzeroids and it was an experiment to see if NSViews were fast enough to use for a 2D Asteroids game. The code was not good — if you find it out in the wild, keep your distance and call animal control.

Were NSViews fast enough for this type of animation? No, not really. At 640 by 480, the game played at 15 frames-per-second on my iMac G3 500Mhz. Computer and operating-system improvements have improved this (newer Intel Macs can now render this old 640 by 480 screen size at more than 30 frames per second) but even newer machines cannot maintain good frame rates at proper fullscreen resolutions of 1920 by 1200 using this approach.

Quartzeroids Two

Since Apple released CoreAnimation 15 months ago, I had been wanting to revisit the idea. To further increase the difficulty, I decided the project should also contain the following features:

Proper model-view-controller implementation where the "game-state" is the model and the view is free to reinterpret the state for the purposes of display.

Fullscreen or windowed mode for display.

Rough design

A rough block diagram of the program would look like this:

The decisions I think were most significant are:

There is not a one-to-one relationship between objects in the Game Data and Layers in the Window.

The updates to the Game Data are triggered by the Game Data's own timer, not directly by the controller (which can start and stop but nothing further).

The second point is most significant when the Game Data is compared to a normal application document. A normal document never updates itself — it only changes when instructions are sent from a controller. In a game however, choosing when to update is a part of the game logic (which I have included in the model) so the controller shown here is left with only the simplest level of control.

I spent some time deliberating about whether to make the GameData a normal document object or a singleton. A normal document style object can be swapped with another document or recreated. A singleton is lazier and simpler since it doesn't need to be connected to every object which needs to access it (since it is globally accessible). Ultimately, the convenience of a singleton won over the hypothetical flexibility of a normal document object since the game will only ever be strictly single instance.

A resolution independent, constant aspect-ratio view

To get the code started for the project, I'll show you how the top level of the CALayer hierarchy (named backgroundLayer in the application since it shows the background for the game) is implemented inside the contentView for the window.

The two hardest constraints for the backgroundView are maintaining a constant aspect ratio and maintaining a constant internal coordinates when the surrounding window resizes or reshapes.

The GameController adds this method as an observer of the contentView's NSViewFrameDidChangeNotification. The gameWidth and gameHeight methods on the GameData objecct are programmed to return values with a 16:10 ratio (matching common widescreen computer aspect ratios). The aspectSize calculated here is then the largest size that obeys this ratio and can fit inside the contentView.

Finally, the CATransform3DMakeScale transform, applied to the backgroundLayer before the frame is set, causes the backgroundLayer to always treat its internal coordinates as though they are gameWidth by gameHeight. This will mean that any layers our game adds to the backgroundLayer's subview hierarchy will always render at a constant fraction of the backgroundLayer's size. Order is important here: apply the transform after the frame is set and it will behave differently.

The other required behavior of the window is switching to fullscreen and back. This is another task which became extremely simple in Mac OS X 10.5:

2 comments:

Core animation is great, but i still prefer OpenGl cause it is cross plateform.

To my opinion, making the model into a singleton is not a mvc idiom anymore. You can call it, anywhere and that's not what we want. Moreover it's seems that the implementation rely too much on the gameModel interface. I always stay in touch with my model via a custom observer pattern (and not by NSNotification even if there are made for it). But i think i'm a little too psycho-rigid.

As Alex said, i'm wondering the best way to implement a mvc philosophy in a game

Nice tutorial. This example demonstrates straight forward way to present a game content. However, it is interesting how the game design would look like if there were a separate menu mvc (with xib interface). Matt, do you have any ideas how should the clean design look like, made of three MVSs: main app delegate, menu mvc and game content controller (mvc)? It is obvious that all games need some general methods for loading/saving game data, restarting/resuming and etc. Everything is clear when the game is a straight forward - main app delegate with window and game mvc. The main app delegate handles such events as applicationDidBecomeActive: and applicationWillResignActive: and uses all game prep methods there. Now, the situation is a little bit different if we have a menu mvc (for example built with interface builder). Both, main app delegate and menu class needs to point to the same game controller object. But the question for me is, how and where should the game be restarted, resetting the game content object and both pointers (app main delegate and menu)?