Sunday, April 19, 2009

Dropping a Dimension: Cocos2D and Box2D on iPhone

Over Easter I decided to drop a dimension in my iPhone project to go from a 3D evolution game simulator to 2D.

I started out playing with the Chipmunk physics engine, just out of curiosity. I watched some videos of the engine in action, read the forums and downloaded the code base and example moon buggy project. The code comes with an Xcode project and the 8 demos make working with the engine look easy.

I started playing around with cocos2d for the iphone because it came with Chipmunk already ported. I hacked on the chipmunk demos (also ported to cocos2d) and found the lack of OO-design frustrating and the speed terrible, even after reading tweaks from the cocos2d forum and blog.

I kept seeing references to the Box2d physics engine (elements of which Chipmunk is based) and eventually dropped what I was working on and grabbed a copy of the code base. The code also had an Xcode project and I was impressed by the number and variety of demos. I dug deeper and came across the box2d port to iphone donated by Simon Oliver who built Rolando using Box2D. I graped the port from the box2D SVN server and started developing my game. I started fleshing out a basic game engine with physics and rendering call backs each frame, tweaked based on some game tutorial screencasts on 71^2.

The speed of the demos was still not good enough, but the object-oriented design sat right and I assumed I could optimize my application into submission (I have so far). While googling fixes for the issues and questions I was having in my basic game engine I kept coming back to the cocos2d forums and codebase. It was obvious that cocos2d had already solved the problem of a simple game engine, and then some.

I was decided. The rapid progress I was able to affect in 2D with both chipmunk and box2d as well as the ready-to-exploit and well documented cocos2d game engine convinced me.

The following is the dead-simple procedure I wrote down for manually integrating the box2D physics engine into the cocos2d game engine for the iPhone:

  1. Check-out cocos2d-iphone from SVN. This is because the downloadable archive from the site does not allow one to decouple Chipmunk as easily.
  2. Check-out box2d physics engine from SVN. This is because the downloadable archive is outdated and does not include the iPhone port (and subsequent changes to the codebase?).
  3. Create a new Xcode project for cocos2d based on Monocle Studios - Introduction to Cocos2d iPhone Whitepaper. I only included the cocos2d directory (no Chipmunk for example), and I tweaked a few things a long the way (OpenGL-based project as a starting point, names of things, etc).
  4. Because Chipmunk is no longer in my codebase I used the new replacement macros whenever the old Chipmunk macros were referred to (such as in forum posts and in the new cocos2d project tutorial in the previous step).
  5. Add the box2d source to the project, specifically the 'include' and 'sources' directories.
  6. The first build had some compile issues (a 'FALSE' rather than a 'false' and missing ';') that required minor fixes.
  7. The first thing I did was create a custom Box2D cocos2d layer that ran the physics engine and generated a basic world with cute 2-box creature (big box with a little box flipper).
  8. I used the rendering code listed in the box2D iPhone port (GLES-Render.mm) for drawing shapes, segments and points, and used the debug-drawing engine in the box2D core (b2World.cpp) as the basis for traversing world elements for drawing each frame. I considered using the cocos2d primitives (ChipmunkDemo/main.m) as is done in the ported Chipmunk examples, but I figured I am going to need custom drawing routines anyway, so the simple custom OpenGL drawing routines were a good base. I also ripped the mouse/touch code from the iPhone port of Box2D which works great.
  9. I had some initial problems importing Box2D.h into my Objective-C++ header files. The solution was simple enough, involving #ifdef __cplusplus/#endif around the cpp header.
  10. Finally, when compiling for iPhone hardware I had some additional errors in Box2D ('finite' was not declared in this scope), that were easily fixed.
I'm sure there are easier/better ways to get up and running with Box2D in cocos2d-iPhone (tell me please!) and millage may vary as the code bases change (I checked out the working copies of each project), although it has worked well enough for me thus far. If there is sufficient demand, I'll create an opensource project with the bare-bones project, or even just open up my current codebase. Let me know.

I'm currently struggling with the fabled "codesign failed with exit code 1" while trying to push my application to my iPhone device, specifically "object file format invalid or unsuitable". I've dropped 6 hours into this problem already and am still nowhere, although I'm reasonably sure it is not project specific. I will get there, but it is so damn frustrating!

Reading has turned up an array of interesting snippets. Cocos2d game engine was originally written in Python and ported to Objective-C for the iPhone. Notes on Cocos2d iPhone Development provide a great intro to cocos2d and specifically the arrangement of scenes, layers and nodes. Box2D has been ported all over the place including to Flash, and there are lots of useful box2d-flash related posts that are directly useful (like this lot that helped me figure out Revolute Joints). I have also seen lots of discussion about tuning box2d for performance (like 1m bodies are the sweet spot) and interesting physics engine comparisons and speed tests.

My current plan is to continue to realize the procedural creature generation and evolutionary engine for my project in 2D and play-test the hell out of it. If something fun drops out I will polish and push it to the app store and/or consider a port to 3D. The raw computation required for any meaningful morphology/controller evolutionary process is still my biggest technical risk. I am still not convinced it is viable on this hardware. I've got some thoughts regarding work-arounds (pre-evaluated strata in the search space, amazon EC2 compute servers, etc), although I am also thinking about a creature app without evolution.

11 comments:

Dougal said...

Jason, most of this goes over my head technically but what's really interesting is the thought processes behind your development process plus the fact you go to such trouble putting I down on paper. It would take ages (especially after oftentimes disenheartening coding). It's very generous of you and I'm sure you've made many a would-be app developer's day. Keep it up. 2D or bust. Dougal

Jason said...

@Dougal Thanks mate.

For anyone else out there developing iPhone apps on PPC hadware, I solved my codesign problem with a handly little script by "Tiku" (search the page) on iPhone SDK CodeSign Error.

Bauerpauer said...

Jason,

Did you have to do anything special to get Box2D's debug shape drawing to work? I'm close, but it looks like cocos2d is tweaking the OpenGL ES settings enough to really skew the scale of the entire world. For example, I'm trying to get Box2D's "Chain" demo running alongside cocos2d, and the chain renders about 50-pixels tall starting at the left side of the screen. If the answer is obvious, go easy please, I'm a complete no0b at the game programming stuff ;)

Jason said...

@Bauerpauer I didn't use Box2d's debug drawing facility directly. I wrote custom cocosnodes that rendered themselves based on the logic from debug drawing.

For example, I have a block class that extends cocosnode and that manages a box2d body and shape. The draw method for the block class uses similar logic (copy-paste) as is used to render body-shapes in the box2d debug draw logic (specifically enumeration from b2world and opengl from the iphone port of box2d). If you do this, you will needed to ensure you setup the gl drawing state yourself in each draw function.

Regarding scale - I started out using the 1:1 scale for the iphone screen. This worked fine, but is not so good for the default box2d configuration that prefers bodies between 0.1 and 10 unit lengths. Scaling is built into cocosnodes, simply determine an appropriate scale factor and use that to translate between the box2d and cocos2d worlds (touching, drawing, etc).

Bauerpauer said...

@Jason

Thanks for the tips. Now I've got some more problems... I basically just started over, using the Box2D iPhone Testbed as a starting point. I ripped out all of the demo code, dropped in the cocos2d project, and started to build a blank-slate app. Everything cocos2d-related compiles properly, but as soon as I add

#import "Box2D.h"

to GameLayer.h (GameLayer is going to be a Cocos2d Layer that manages the b2World instance, etc...), I get build errors on all of the float32 const's in b2Settings.h:

error: initializer element is not a constant

Any ideas?

Jason said...

@Bauerpauer yeah I had the same problems and listed the solution in my blog post (step 9 and the referenced link).

Make sure you only include box2d.h where you actually use types (implementation or public interface) and make sure anything that does import the header is an objective-C++ file (.mm)

Sam said...

Do you think you could upload the source? I'm coming from a Flash background (using Box2D too :D) so any help getting in to the rhythm of iPhone development is greatly appreciated. Specifically, I'm curious to see exactly how you did the Box2D debug drawing stuff.

Cheers.

Jason said...

@Sam I didn't (as mentioned in the post and in an above comment). I did custom drawing stuff inspired by the debug draw code.

For an example of Box2d debug drawing on the iPhone please see the most excellent post and code (n the box2d SVN) by HandCircus: iPhone port of Box2D Testbed now available

Sam said...

Yeah i saw that - I was referring more to how you did the drawing through Cocos2D. I'll have a look at that HandCircus code now.

Hans Pinckaers said...

Thanks for your post it really helped me. I'm currently playing with cocos2d and box2d. I found a problem with calling the world step, the box2d step advices to use a fixed timestamp but when using the schedule functionality of cocos2d with a fixed timestamp the simulation is not drawn at the same speed for different fps. So I use the fps as a timestamp, but when the fps is low you will sometimes see object react differently then normal(such as a bounce). So my question is, where do you put your world step call?

(sorry for my bad english btw, I hope you can understand it)

su said...

I'm making a list of game-ready model shops, if you know another ones please tell me ^^ here's what I've found so far:
(alphabetically sorted)

[b]3dbud.com[/b]
[img]http://img118.imageshack.us/img118/8018/83767809.jpg[/img]
(fantasy characters - animals - monters) -
http://3dbud.com

[b]3drt.com[/b]
[img]http://3drt.com/3dm/characters/real-zombies/characters-real-zombies-3d_01.jpg[/img]
(fantasy - scifi characters - animals - monters)
http://3drt.com