Tag Archives: ARC

Cocos2D, Box2D and MVC

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:

  1. How do you use automatic reference counting (ARC) with Cocos2D? The approach I’ve taken is to follow this blog by Steffen Itterheim.
  2. 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.
  3. 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:

GameController

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.

GameView

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:

GameLayer

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 stopAllActions instead.

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.

GameModel

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).

GameElement

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 GameElements_Private.h.  Then GameElements.m and Animal.m both import GameElements_Private.h instead of GameElements.h, so that they can refer to body.

Further ideas

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…

  

Time for sound

Time for sound.  In iOS it looks like sound can be played by AVAudioPlayer, and recorded by AVAudioRecorder.  The broader framework is called AVFoundation.  However, at a very low level there is also the Core Audio framework, which includes the Audio Toolbox and Audio Unit frameworks.

There are some great sample projects available at Apple’s Audio & Visual Starting Point:

  • AddMusic shows how to pick songs from the iTunes library.  This also includes setting up an AVAudioSession, which I’m not sure is necessary.
  • avTouch shows how to play sounds using AVAudioPlayer.
  • SpeakHere shows how to record sounds using AQRecord.  On closer inspection this looks extremely daunting.
  • AVAudioRecorder is another approach that looks a lot easier, as documented in this Stack Overflow post, and this Techotopia post.  However, there is a subtlety when you try to play the recorded sound in an AVAudioPlayer – for some reason using
    self.recordingPlayer = [[AVAudioPlayer alloc] 
       initWithContentsOfURL:self.audioRecorder.url error:&error];

    does not ever call the delegate routines when the URL is that of the recorded sound (in either the simulator or the device). So instead, I used

    NSData* recordedData = [NSData 
       dataWithContentsOfFile:[self.audioRecorder.url path]];
    self.recordingPlayer = [[AVAudioPlayer alloc] 
       initWithData:recordedData error:&error];

    - which worked fine.  Another gotcha is to make sure the AVAudioPlayer object is not disposed of at the end of the method, but before the playing actually happens. My solution was to make it an instance variable (as in the sample code above).

  • Audio UI Sounds (SysSound) shows how to play alert sound-effects, but on closer inspection comes with the warning this should not be used for other sound effects, e.g. in games.
  • OpenAL is one solution, with sample code called oalTouch.  This comes with some C code (MyOpenALSupport.c) which looks handy, if a little daunting.  Note that to call this with Automatic Reference Counting (ARC), you need to use (__bridge CFURLRef) and then delete the line CFRelease(fileURL);. I forgot this last part and it took a while to realise what was going on.  There is also a great basic tutorial by Ben Britten.

The Multimedia Programming Guide – Audio has more context.  And as usual, Ray Wenderlich has a great summary and tutorial.

I found strange behaviour with AVAudioSession.

  • You can provide a category to the session, and I chose AVAudioSessionCategorySoloAmbient, which is meant to silence sound from other apps.  However it seems to also silence OpenAL and other AVAudioPlayer sounds from the same app. Switching to AVAudioSessionCategoryAmbient solved this problem for me.
  • I had set my category to AVAudioSessionCategoryAmbient while setting up the audio recorder, but changed it to AVAudioSessionCategoryRecord just before the  record command; I changed it back again after recording stops.  This worked on the simulator, but on the device the call to record simply returned NO with no explanation, and nothing was recorded.  I spent most of a day trying to work out why.  The answer: use the category AVAudioSessionCategoryPlayAndRecord while setting up the recorder.