As I indicated in my last post, I am going to write my kids’ game in Cocos2D. After implementing the simple bear animation tutorial from Ray Wenderlich, I had a few questions:
- How do you use automatic reference counting (ARC) with Cocos2D? The approach I’ve taken is to follow this blog by Steffen Itterheim.
- How do you write code with Cocos2D (and maybe with Box2D) in an MVC (model-view-controller)-compliant way? The approach I’ve taken is to follow this blog and its sequel by Bartek Wilczyński.
- How do you add Box2D to the project once you’ve already started it (and continue to use ARC)? The approach I’ve taken is to make it a static library by following this blog by Red Glasses. This is a pretty involved procedure but does the job. I imagine you can do point (1) above using this approach too – it would be nice to be consistent – but I have not tried this.
I’ve done all this and got it all running, and I thought others may find it helpful, so I have put the result on github at https://github.com/RacingTadpole/Cocos2D-Box2D-MVC-example. You are welcome to download it as a starting point for your own projects, or just to poke holes in it (but please tell me what they are!). The frame rate seems to be only 20-30 frames per second on the simulator, but on a real device it is close to 60, and I have read elsewhere that this stat should be ignored on the simulator anyway. Edit – the project on GitHub was compiled using a slightly older version of XCode, and the latest XCode complains when I try to run it on a device (ld: file is universal (2 slices) but does not contain a(n) armv7s slice: …./libbox2d-lib.a for architecture armv7s ). For now I am solving this by setting “Build Active Architecture Only” to “Yes” in the target’s build settings (see this stackoverflow question), but in the long run it looks like I’ll need to redo step 3 above using the latest XCode.
The game as it stands (called Zambazi) simply involves a host of monkeys and bears falling from the sky onto a grassy foreground, and bouncing like rubber balls. When you touch anywhere, all the animals are hit with random forces. It’s not much, but my kids find it surprisingly engrossing! They decided you win if you can make all the bears run off the screen before the monkeys… Maybe I’m not so far from the App Store after all?
All images are from Ray & Vicki Wenderlich’s sites – thank you both for making these freely available!
Here’s the basic structure/flow:
When PLAY is pressed, a GameController is initialized. The gameController has a GameView and a GameModel, each of which is initialized. The gameController also schedules the updates.
The gameView has a delegate (actually the gameController) which currently does nothing, because it doesn’t need to know which sprite you’ve touched. This delegate may be useful if you do need to know – see Bartok’s blog for his vision here. In fact I’m planning instead to remove this delegate and simply register the controller with Cocos2D as a touch delegate, as described here.
The view creates the necessary layers:
The gameLayer is in charge of all the sprites. It knows nothing about the view or the controller, but does have a reference to the model. It also registers itself as an observer of notifications from the model. The notifications are:
- Model initialization – this is so that the gameLayer can get a reference to the model in the first place.
- Revise game elements – this is so that it can set up the sprites that correspond to the model.
The gameLayer starts by loading in all the sprites for the game, and setting up their actions (
CCAction). I am sure there is a much better way to do this – but this does the job for now.
It also keeps track of which actions are running. This seems an unfortunate complication, but as far as I know, you need to do this so that you can stop the action later. If you can get away with stopping all actions on a sprite – and maybe you can – then you could remove this stuff and use
The gameLayer does not know about Box2D – I see that as a model-level thing. The gameElements provide their own velocity, where, rotation etc methods. I have defined a
Point3D structure to pass around points – this was when I was thinking the model may have a 3D world even if the view is only 2D. However, with Box2D, the third dimension is irrelevant – so it would be simpler to just use
CGPoint for example. I am leaving Point3D nonetheless so that if you want to use this code with a 3D model (without Box2D), it shouldn’t be too hard to adapt it.
The other trick is that gameLayer has an
NSDictionary (called sprites), with the gameElements as keys and the sprites as the objects. The complication is that
NSDictionary copies its keys before it uses them, so that the key winds up being a different object to what you requested. The solution (implemented in my code) is to override
copyWithZone: to return self, without copying, as described in this stack overflow post. I am assured this is good practice if the object is immutable. All this may be too tricky by half, but it seemed sensible at the time, and works fine.
The game model is the Box2D world, and an array of the game elements. When
createGameObjects is called (by the Controller – this could equally well be part of the initialization), it sets up some default game elements. It has an
update: method which uses Box2D to update the physics; optionally each element may have its own additional update behaviour (I have adopted Bartok’s “Updatable” protocol).
The gameElements are the models of the platforms, the enemies, etc. They basically have a Box2D body and a name. The name is used by the gameLayer to work out what sprite to show. The gameElement uses Box2D to provide where, velocity and rotation methods, so the
body variable itself is kept private (i.e. in a class extension).
I am subclassing GameElement (e.g. Animal) to provide different behaviour for different models.
Technical note – so that subclasses can still access the
body variable, I have declared
body in a class extension header file called
Animal.m both import
GameElements_Private.h instead of
GameElements.h, so that they can refer to
As I start to turn this into a functioning game, I have found two further issues, one conceptual, one practical:
- It’s nice to cleanly separate the model from the view – but the image you are using is a particular size, and I’m finding the model sometimes needs to know this size (so you don’t have to scale the image). I’m solving this with another delegate pattern. I’ve introduced a
NaturalSizeProtocol, which the GameLayer and the GameView follow. The GameModel then has a
naturalSizeDelegate. When a gameElement needs to know its natural size (i.e. the size the view wants to make it), it asks its
naturalSizeDelegate. This returns the size in model co-ordinates (as a
Point3D). This feels like a contortion of MVC, so I’d love to hear if anyone has a better solution to this. It has left me wondering if MVC is more trouble than it’s worth after all for image-intensive games.
- Getting a background image to repeat in Cocos2D is hard. I have only managed it so far by loading the background image multiple times, which doesn’t seem right.
That’s it for now. Please let me know if you find this useful, or have any suggestions.
Edit – I have just come across Steffen Itterheim’s excellent post on exactly this subject, which inspired him to write KoboldTouch. Together with problems I am having getting Lua to compile in my Cocos2D/Box2D project, I am starting to wish I had used Kobold2D…