TweetFollow Us on Twitter

Game Development for iPad, iPhone and iPod Touch, Part 3: Using the cocos2d and Chipmunk Frameworks

Volume Number: 24
Issue Number: 06
Column Tag: iPad

Game Development for iPad, iPhone and iPod Touch, Part 3: Using the cocos2d and Chipmunk Frameworks

Tools for building 2D games

by Rich Warren

Some Sort of Introduction

In the first article, we examined the cocos2D graphics engine and the Chipmunk physics engine, discussing how they could be used to simplify 2D game development on the iPhone. Unfortunately, using these two libraries requires a lot of boilerplate code to build and coordinate our on-screen objects. Last time we built an Entity class that would hide much of this complexity, letting us quickly and easily populate our game. This time, we're going to build our main scene and add code to handle collisions. We will also set up the games run loop This will complete our pachinko game.

If you've been following along, we've already installed the cocos2D and Chimpunk libraries. We set up our Pachinko project, and built and tested our Entity class. If this doesn't sound familiar to you, I'd highly recommend reviewing the previous two articles (Feb and Mar 2010).

Open the Pachinko project again. There's one small bit of housekeeping before we move forward. Delete the HelloWorldScene.h and HelloWorldScene.m files. We will be replacing this with our own layer code, and it is easiest to just start from scratch.

The Main Layer

Most games have multiple scenes, each composed of one or more layers. For simplicity's sake, our pachinko game only uses a single scene with a single layer, and most of the custom work is done in the layer itself. We won't even need to subclass the Scene.

So start by creating a new Objective-C Object named MainLayer. Edit MainLayer.h as shown below.

MainLayer.h

#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioServices.h>
#import "cocos2d.h"
#import "chipmunk.h"
@interface MainLayer : Layer {
    
    NSSet* pins;
    NSSet* bumpers;
    NSMutableSet* balls;
    
    id target;
    
    cpSpace* space;
    cpBody* walls;
    
    CGSize size;
    CGSize ballSize;
    
    // sound data
    CFURLRef tick1URLRef;
    CFURLRef tick2URLRef;
    CFURLRef bellURLRef;
    
    SystemSoundID tick1ID;
    SystemSoundID tick2ID;
    SystemSoundID bellID;
} 
@end

This defines the variables that will hold our pins, bumpers, balls and target Entities. We also store references to the cpSpace and the games side walls. Finally, we have a number of variables for our sound effects. We will use two different tick sounds to represent different collisions, and a bell sound for when a ball goes into the target.

Now lets look at the implementation. We start by defining a few global variables used to track the collisions during each time step. While we usually avoid global variables, we will need these to coordinate between a set of standard C callback functions and our Objective-C code. Using global variables is simple pragmatics; it is the path of least resistance.

MainLayer.m

#import "MainLayer.h"
#import "Utility.h"
#import "InteractiveObject.h"
id lastHit = NULL;
BOOL tick1 = NO;
BOOL tick2 = NO;

Next, we use an extension to define a number of private methods. These primarily break up our initialization code into smaller, more meaningful chunks; however, we also define our step: function, which will be called for each frame.

MainLayer extension

#pragma mark private methods
@interface MainLayer ()
-(void)setupEnvironment;
-(void)setupBackground;
-(void)setupPins;
-(void)setupBumper;
-(void)setupTargets;
-(void)setupCollisions;
-(void)setupSoundEffects;
-(void)step:(ccTime)delta;
@end

Next, the init method sets a few variables, and then calls the setup methods. dealloc simply releases all the memory. Note: this is a mixture of Objective-C objects and C-style structures, each has its own idiom for freeing memory.

init and dealloc

#pragma mark Constructors/Destructors
-(id) init {
    
    if( (self=[super init])) {
    
        ballSize = getSpriteSize(BALL);
        balls = [[NSMutableSet alloc] init];
        
        [self setupEnvironment];
        [self setupBackground];
        [self setupBumper];
        [self setupPins];
        [self setupTargets];
        [self setupCollisions];
        [self setupSoundEffects];
    }
    
    return self;
}
-(void)dealloc {
    
    [pins release];
    [bumpers release];
    [balls release];
    [target release];
    
    cpBodyFree(walls);
    cpSpaceFree(space);
    
    AudioServicesDisposeSystemSoundID(tick1ID);
    AudioServicesDisposeSystemSoundID(tick2ID);
    AudioServicesDisposeSystemSoundID(bellID);
    
    CFRelease(tick1URLRef);
    CFRelease(tick2URLRef);
    CFRelease(bellURLRef);
    
    [super dealloc];
}

Most of the real work goes into setting up the Layer. Once that is complete, the physics and graphics engines pretty much run everything automatically. So, lets go through the setup functions one at a time, starting with setupEnvironment.

setupEnvironment

-(void) setupEnvironment {
    
    // Select events to catch
    self.isTouchEnabled = YES;
    self.isAccelerometerEnabled = NO;
    
    // initialize the space
    space = cpSpaceNew();    
    space->gravity = cpv(0, -2000);
    space->elasticIterations = space->iterations;

We start by turning on touch notifications and turning off accelerometer notifications. We won't be using the accelerometer in this game. Next, we create our cpSpace structure for the Chipmunk physics engine. We set the space's gravity and set the elastic iterations equal to the regular iterations.

The number of iterations determines the accuracy of our collisions. The more iterations you have, the more accurate the results will appear, but at a cost of greater computational time. Elastic iterations cover other special cases, primarily preventing unwanted jitter when stacking objects. When you build a new cocos2D Chipmunk application, the HelloWorldScene template uses the default value for iterations, and sets the elastic iterations equal to the iterations. This is generally a good place to start, so we will follow that idiom here. You should tune your application further if necessary.

setupEnvironment continued

    // setup the space hashes
    // dim should equal the average shape size
    // count should be about 10 x the object count
    // static count can be much larger
    cpSpaceResizeActiveHash(space, 16.0f, 500);
    cpSpaceResizeStaticHash(space, 16.0f, 1000);
    

Next we setup the space's static and active hash. The first parameter is our cpSpace structure. The second is the size of the hash cell. The third is the number of hash cells. These values need to be tuned for each particular application. In general, if the hash size is too large, it will have too many objects inside it-and you will need to compare all the combinations for possible collisions. If the value is too small, then each object will need to be placed within several cells, which may also become computationally expensive. As a good rule of thumb, start with a size equal to the average Sprite size, and set the active hash count to approximately 10x the number of active objects. The static hash can be much larger. Then test and adjust to improve performance as necessary.

setupEnvironment continued

    // Setup bounding walls on the left and right
    walls = cpBodyNew(INFINITY, INFINITY);
    
    size = getScreenSize();
    cpShape *shape;
    
    // left
    shape = cpSegmentShapeNew(walls, cpv(0,0), 
                              cpv(0,size.height * 1.5f), 0.0f);
    
    shape->e = 0.75f; 
    shape->u = 0.25f;
    shape->collision_type = WALL_COLLISION;
    cpSpaceAddStaticShape(space, shape);
    
    // right
    shape = cpSegmentShapeNew(walls, cpv(size.width,0), 
                              cpv(size.width, size.height * 1.5f), 0.0f);
    
    shape->e = 0.75f; 
    shape->u = 0.25f;
    shape->collision_type = WALL_COLLISION;
    cpSpaceAddStaticShape(space, shape);

This portion simply sets up the side walls. You should recognize much of it from the Entity code we wrote last time. We create a static body with an infinite mass and moment of inertia. It's defaults to a (0,0) position (lower left corner of the screen). We then create line-segment shapes that run along the left and right side of the screen, extending well above the top, to prevent balls from bouncing out. We set the elasticity, friction and collision type, and then add them to the space.

So, why are we constructing the walls by hand when we spent all that time building our Entity class? Well, Entity was designed for objects that combine both a graphical representation with physical properties. Our walls do not have any graphical representation-they just correspond with the sides of our screen. We could combine them with our background image to form a single Entity, but the background might change during play-the walls should remain the same.

setupEnvironment continued

    // run the step method for each frame.
    [self schedule: @selector(step:)];
}

Finally, the setupEnvironment method ends by scheduling our step: method to be called each frame. Note: we never call step: directly. The cocos2D framework will automatically manage it.

Next we setup our background. This is a much simpler method.

setupBackground

-(void) setupBackground {
    
    Sprite* background = [Sprite spriteWithFile:@"background.png"];
    [background setPosition:ccp(160, 240)];
    [self addChild:background z:BACKGROUND_DEPTH];
    
}

Again, the background is not an Entity. It is simply a graphical element, and does not need to move or interact with anything on the screen. We simply create a Sprite object and set it to the middle of the screen. For simplicity's sake, I hard coded in the coordinates. That locks us into a given screen size; however, if we really want to support different screen sizes, we need to have different background images, and load the correct image for each screen. I'll leave that as homework (for those of you lucky enough to have an iPad).

Next, let's set up our pins.

setupBumper

-(void) setupPins {
    
    // calculate the ball size
    cpFloat pinSize = getSpriteSize(PIN).width;
    
    cpFloat maxDistance = 2.0f * (ballSize.width + pinSize);
    cpFloat halfMax = (ballSize.width + pinSize);
    
    cpFloat center = size.width / 2.0f;
    cpFloat top = size.height * 3.0f / 4.0f;
    cpFloat bottom = size.height / 4.0f + halfMax;
    cpFloat right = size.width - halfMax;
    
    NSMutableSet* temp = [[NSMutableSet alloc] init];
    
    cpFloat initialOffset = 0.0;
    for (cpFloat y = top; y > bottom; y -= halfMax) {
        
        cpFloat offset = initialOffset;
        while (center + offset < right) {
            
            [temp addObject:
                [InteractiveObject pinInSpace:space 
                                     forLayer:self 
                                   atLocation:cpv(center + offset, y)]];
            
            if (offset > 0.0f) {
                [temp addObject:
                    [InteractiveObject 
                         pinInSpace:space 
                           forLayer:self
                         atLocation:cpv(center - offset, y)]];
            }
            
            
            offset += maxDistance;
        }
        
        if (initialOffset == 0.0) {
            
            initialOffset = halfMax;
        }
        else {
            
            initialOffset = 0.0;
        }
        
    }
    
   pins = temp;
}

This looks intimidating, but most of the code just calculates the pin positions. Here we dynamically create a grid of pins to fill the center of the screen. The spacing is automatically calculated based on the icon size for the balls and pins. We also need to create enough pins to fill the space. If we build this application for a different screen size (for example, an iPad), the grid will automatically adjust to fill the space. Of course, the bumper size and background image would be wrong-but at least our code is partially future-proofed.

We also start to see the real benefit from all our work on the Entity class. The actual pin-creation code is a single method: pinInSpace:forLayer:atLocation:. This automatically loads the sprite image, then creates and adds all necessary cocos2D objects to the Layer, and all Chimpunk structures to the cpSpace.

All of the pins are placed in a mutable set, which is then assigned to the pins variable. Since the Entity class automatically handles the memory management, our pins will remain valid until this set is released.

The bumper and target methods are even simpler. The only difference is that the bumpers are stored in an NSSet collection, and they automatically calculate their own position based on the screen size. We manually set the target's position, and store it directly in an instance variable.

setBumpers and setTargets

-(void) setupBumper {
    
    bumpers = [NSSet setWithObjects:
                  [InteractiveObject leftBumperInSpace:space
                                              forLayer:self],
                  [InteractiveObject rightBumperInSpace:space
                                               forLayer:self],
                   nil];
    
    [bumpers retain];
    
}
-(void) setupTargets {
    
    target = [InteractiveObject targetInSpace:space 
                                     forLayer:self 
                                   atLocation:cpv(size.width / 2.0f, 
                                                  size.height / 4.0f)];
              
    [target retain];
    
}

Next we set up the collisions, as shown below.

setupCollisions

-(void) setupCollisions {
    
    // setup all collision functions.
    cpSpaceAddCollisionPairFunc(space, BALL_COLLISION, TARGET_COLLISION,
                                &goalHit, nil);
    cpSpaceAddCollisionPairFunc(space, BALL_COLLISION, PIN_COLLISION, 
                                &pinHit, nil);
    cpSpaceAddCollisionPairFunc(space, BALL_COLLISION, WALL_COLLISION, 
                                &wallHit, nil);
    cpSpaceAddCollisionPairFunc(space, BALL_COLLISION, 
                                TARGET_WALL_COLLISION, &defaultHit, nil);
    cpSpaceAddCollisionPairFunc(space, BALL_COLLISION, BUMPER_COLLISION, 
                                &defaultHit, nil);
    cpSpaceAddCollisionPairFunc(space, BALL_COLLISION, BALL_COLLISION, 
                                &defaultHit, nil);
}

cpSpaceAddCollisionPairFunc() defines the callback function for a given pair of collision types. To make the code more readable, we have defined a number of enums in Utility.h, providing meaningful names for the various collision types. For more information about Utility.h, check out the full source code at ftp://ftp.mactech.com/src/, if you haven't done so already.

In our case, the first call sets the goalHit() method for any collisions between balls and the target. The final argument allows us to pass arbitrary data to the callback function. We don't use that here, but it could be useful if you need to do more complex processing in the callbacks.

Note, the last three pairs all use the same default callback. We could have automatically set these using the cpSpaceSetDefault-CollisionPairFunc() method, but we would lose some control. For example, we won't know if a ball hit the wall, or if the wall was hit by the ball. Explicitly setting the collision pair allows us to force the order of the arguments. This can help simplify the callback code.

Finally, we need to set up our sound effects.

Sound effects

-(void) setupSoundEffects {
    
    CFBundleRef mainBundle;
    mainBundle = CFBundleGetMainBundle ();
    
    tick1URLRef = CFBundleCopyResourceURL (mainBundle,
                                           CFSTR ("tick1"),
                                           CFSTR ("wav"),
                                           nil);
    
    tick2URLRef = CFBundleCopyResourceURL (mainBundle,
                                           CFSTR ("tick2"),
                                           CFSTR ("wav"),
                                           nil);
    
    bellURLRef = CFBundleCopyResourceURL (mainBundle,
                                          CFSTR ("bell"),
                                          CFSTR ("wav"),
                                          nil);
    
    AudioServicesCreateSystemSoundID(tick1URLRef, &tick1ID);
    AudioServicesCreateSystemSoundID(tick2URLRef, &tick2ID);
    AudioServicesCreateSystemSoundID(bellURLRef, &bellID);
    
}

The iPhone has a number of sound libraries, and cocos2D adds an additional 3rd party options. For this application, we have chosen the simplest approach. Audio Services can play a single, short sound at a fixed volume. That's it. We have no other control. On the plus side, it's dead simple to up. Just create the URL reference, and then create the sound system ID. We will use this ID when responding to collisions. A real game would probably need a more-complex sound library. However, for this tutorial Audio Services works well enough.

Adding Events and Updates

Now we get to the meat of the application. Our layer must respond to touch events. Add the following method.

CcTouchesEnded:WithEvent:

#pragma mark Touch Events
-(BOOL)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    
    // shift left or right by up to 2 pixels
    CGFloat offset = CCRANDOM_MINUS1_1() * 2.0f;
    cpVect location = cpv(size.width / 2.0 + offset, size.height + 
        ballSize.height / 2.0f);
    [balls addObject:[InteractiveObject ballInSpace:space 
                                           forLayer:self
                                         atLocation:location]];
     
    return kEventHandled;
}

Basically, every time we tap the screen, we create a new ball. The ball is placed roughly in the center just off the top of the screen. We need to add a slight randomness to the ball's initial position, otherwise all the balls will follow the same exact path through the pins. And, if the ball is positioned exactly over a pin, it will bounce up and down on that pin, eventually balancing on top of it. Adding additional balls will just cause them to stack. So we need a bit of randomness to make things look and feel realistic

Note: this implementation creates and destroys a large number of balls. This could cause performance issues. In my testing, it did not have a significant effect, so I left the implementation as-is. However, if you're having performance problems, you may want to create a pool of balls, and recycle them, instead of constantly allocating new ones.

Finally, we get to the step function. The cocos2D framework will call this method once every frame. We will use it to update the balls' positions and play any needed sound effects.

step:

#pragma mark Timer Events 
-(void)step:(ccTime)delta {
    
    int steps = 2;
    //cpFloat dt = delta/(cpFloat)steps;
    cpFloat dt = 1.0f / (60.0f * (cpFloat) steps);
    
    for (int i = 0; i < steps; i++) {
        cpSpaceStep(space, dt);
    }

Here, we iterate the cpSpace twice. We could calculate the actual time that has passed between frames; however, the Chipmunk physics engine is optimized to take advantage of constant time steps. We will set the app to run at 60 frames per second, so we should move the space 1/120th of a second forward during each iteration. Of course, if the frame rate starts to lag, objects on the screen will visibly slow down. Hopefully the rest of our implementation is fast enough that this lag never becomes a problem.

Breaking the step into two cpSpace iterations helps catch collisions between fast objects with small shapes. When we only use a single iteration, the ball occasionally moved fast enough and at just the right positioning to pass completely through one of the pins or walls.

The cpSpaceStep() method automatically adjusts the body position of all the balls and calculates any collisions, and the physical effects of those collisions. It will also call our callback functions (which we will look at in a second). These callback functions then set the lastHit, tick1 and tick2 global variables, as needed. The rest of our step: method uses these values.

step: continued

    
    if (lastHit) {
        
        [balls removeObject:lastHit];
        lastHit = nil;
        
        AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
        AudioServicesPlaySystemSound(bellID);
        
    }
    
    if (tick1) {
        
        AudioServicesPlaySystemSound(tick1ID);
        tick1 = NO;
    }
    
    if (tick2) {
        
        AudioServicesPlaySystemSound(tick2ID);
        tick2 = NO;
    }

Here, our step: method checks the global variables to see if any of the events have been triggered. If a ball hit the goal, the ball is removed (and subsequently deallocated). We will also vibrate the phone and play the bell sound.

If either of the tick variables were set, it plays the appropriate tick sound. In any case, the variable is reset to nil or NO as appropriate.

Note: there appears to be a bug in the simulator. The call to vibrate the phone, AudioServicesPlaySystemSound-(kSystemSoundID_Vibrate), can cause the sound system to get kind of funky. This typically happens when several balls rapidly hit the target. If you have trouble with this during testing, simply comment out that line. Regardless, the code works fine on the phone itself.

step: continued

    NSMutableArray* outOfBounds = [[NSMutableArray alloc] init];
    
    for (id ball in balls) {
        
        [ball updateSprite];
        
        if ([ball position].y < - ballSize.height / 2.0f) {
            [outOfBounds addObject:ball];
        }
    }
    
    for (id ball in outOfBounds) {
        [balls removeObject:ball];
    }
    
    [outOfBounds release];
}

Finally, we iterate over all the balls. First, we update the ball's Sprite position by calling the updateSprite method we wrote in Part 2. Then we check to see if the ball has dropped off the bottom of the screen. If it has, we remove the ball, deallocating it. That's it. Chipmunk and coco2D handle everything else.

Of course, we still need our callback functions. These are standard C functions. You must either declare them, or simply write their implementation before the MainLayer implementation starts.

goalHit()

static int goalHit(cpShape *a, cpShape *b, cpContact *contacts, 
                   int numContacts, cpFloat normal_coef, void *data) {
    
    lastHit = a->data;
    return 1;
}

The callback template is pretty simple. The first two parameters are the shapes involved in the collision. They will be passed to this method in the order defined when setting the callback. In this case, a belongs to a ball, and b belongs to the target's goal.

cpContact, numContacts and normal_coef are set by Chipmunk, and contain potentially useful information about the current collision. Additionally, the data parameter will contain any the user defined data passed in when the callback was originally set.

In this callback, we set lastHit equal to the colliding ball's Entity object. Remember, when creating the balls, we set the cpShape->data pointer to the Entity object itself. Passing this to the lastHit global variable allows us to find and remove the ball during the later portion of our step: method.

Finally, we return a true value. This indicates that the collision should take place, and the object's velocity will be appropriately affected. If you return 0, the objects would proceed to pass right through one another.

The default callback is pretty much the same. Though we do a little more pre-processing.

defaultHit()

static int defaultHit(cpShape *a, cpShape *b, cpContact *contacts, 
                      int numContacts, cpFloat normal_coef, void *data) {
    
    cpVect velocity = a->body->v;
    cpVect normal = cpvmult(contacts[0].n, normal_coef);
    
    if (cpvdot(velocity, normal) > 50.0f) {
        tick1 = YES;
    }
    
    return 1;
}

Basically, we set the tick1 global variable to YES, thus triggering the appropriate sound effect later in the step method. However, balls might come to rest against other balls or the bumpers. In fact, balls often stop on the bumper, then roll down them. We don't want to have continuous ticking as the ball rolls. So, we do a quick check to estimate the speed of the impact.

First extract the velocity vector for our ball. Then we calculate the normal vector for the collision. However, the normal vector stored in cpContact.n may not be the correct orientation, if the shapes have been reversed. To ensure we have the correct vector, multiply it by normal_coef. Then we take the dot product of our velocity and normal vectors. This will give us the ball's velocity in the direction of the normal. If this is high enough, we trigger a tick sound.

Why don't we just check the velocity by itself? When the balls are moving fast, they might actually penetrate an object slightly before a collision is detected. Sometimes this results in a second collision on the way out. If we just use the raw velocity, we get occasional double ticks. However, by checking the projection of the velocity along the collision normal, the secondary collision has a negative number, and will be ignored.

The pin and wall callbacks are basically the same. The only difference is, we generate a tick sound for the walls, regardless of the approximate impact speed (because balls cannot balance on the side wall), and we use tick2 for the pins, to generate a slightly different sound.

That's almost it. We just need a few slight changes to our app delegate, and we're ready to go. Open PachinkoAppDelegate.m. First edit the list of imported files. Remove HelloWorldScene.h and add lines for MainLayer.h and chipmunk.h.

Now modify applicationDidFinishLaunching: as shown below.

applicationDidFinishLaunching:

- (void) applicationDidFinishLaunching:(UIApplication*)application
{
    // initialize random numbers
    srandom(time(nil));
    
    // Init chipmunk
    cpInitChipmunk();
    
    // Init the window
    window = [[UIWindow alloc] 
        initWithFrame:[[UIScreen mainScreen] bounds]];
    
    // cocos2d will inherit these values
    [window setUserInteractionEnabled:YES];    
    [window setMultipleTouchEnabled:NO];
    
    // Try to use CADisplayLink director
    // if it fails (SDK < 3.1) use Threaded director
    if( ! [Director setDirectorType:CCDirectorTypeDisplayLink] )
        [Director setDirectorType:CCDirectorTypeDefault];
    
    // Use RGBA_8888 buffers
    // Default is: RGB_565 buffers
    [[Director sharedDirector] setPixelFormat:kPixelFormatRGBA8888];
    
    // Create a depth buffer of 16 bits
    // Enable it if you are going to use 3D transitions or 3d objects
//    [[Director sharedDirector] setDepthBufferFormat:kDepthBuffer16];
    
    // Default texture format for PNG/BMP/TIFF/JPEG/GIF images
    // It can be RGBA8888, RGBA4444, RGB5_A1, RGB565
    // You can change anytime.
    [Texture2D 
        setDefaultAlphaPixelFormat:kTexture2DPixelFormat_RGBA8888];
    
    // before creating any layer, set the landscape mode
    [[Director sharedDirector] 
        setDeviceOrientation:CCDeviceOrientationPortrait];
    [[Director sharedDirector] setAnimationInterval:1.0/60];
    [[Director sharedDirector] setDisplayFPS:YES];
    
    // create an openGL view inside a window
    [[Director sharedDirector] attachInView:window];    
    [window makeKeyAndVisible];        
        
    // initialize the main scene
    // this should really be refactored out to support multiple scenes.
    Scene *mainScene = [Scene node];
    Layer *layer = [MainLayer node];
    [mainScene addChild:layer];
    
    // run the main scene
    [[Director sharedDirector] runWithScene: mainScene];
}

While the method itself is long, there are only a few, simple changes. We initialize both our random number seed and the Chipmunk physics engine. The template code chose to initialize Chipmunk in the HelloWorldScene; however, a real application may have multiple scenes all using Chipmunk. Rather than let each scene initialize its own version, let's just initialize it once here.

We also turn off multi touch. We don't need it for this application; all touch events just create a new ball. We also set the device to a fixed portrait orientation. While most games are landscape, portrait makes more sense in this instance.

Finally, we create a generic Scene instance, then create our MainLayer and add it to the scene. We then tell the shared director to run our scene. That's it. Compile it and see how it runs. Notice the number in the lower left corner shows the current frame rate. Try adding a lot of balls, and see how low the frame rate drops. This is an easy, early performance test.

You can turn off the frame rate by setting [[Director sharedDirector] setDisplayFPS:NO]; in the app delegate's applicationDidFinishLaunching: method.


Completed Pachinko App

Conclusion

As you can see, most of our code is involved in setting everything up-both setting up the Entities (in Part 2), and setting up the Scenes and Layers (as shown here). Once that's done, the actual run loop is very simple. Chipmunk and cocos2D handle most of the heavy lifting.

Of course, our Pachinko game is not particularly interactive. Things get more complicated when your entities begin maneuvering around and shooting at each other. Still, using a graphics and physics engine lets us strip down our development tasks and focus on the really interesting parts of our games. This lets us spend more time ensuring our games are engaging and fun, and less time debugging sprite movement or collision code.

Good luck, and remember, let's have fun out there.


Rich Warren lives in Honolulu, Hawaii with his wife, Mika, daughter, Haruko, and his son, Kai. He is a software engineer, freelance writer and part time graduate student. When not playing on the beach, he is probably writing, coding or doing research on his MacBook Pro. You can reach Rich at rikiwarren@mac.com, check out his blog at http://freelancemadscience.blogspot.com/ or follow him at http://twitter.com/rikiwarren.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Adobe Creative Cloud 2.2.0.129 - Access...
Adobe Creative Cloud costs $49.99/month (or less if you're a previous Creative Suite customer). Creative Suite 6 is still available for purchase (without a monthly plan) if you prefer. Introducing... Read more
Tower 2.2.3 - Version control with Git m...
Tower is a powerful Git client for OS X that makes using Git easy and more efficient. Users benefit from its elegant and comprehensive interface and a feature set that lets them enjoy the full power... Read more
Apple Java 2015-001 - For OS X 10.7, 10....
Apple Java for OS X 2015-001 installs the legacy Java 6 runtime for OS X 10.11 El Capitan, OS X 10.10 Yosemite, OS X 10.9 Mavericks, OS X 10.8 Mountain Lion, and OS X 10.7 Lion. This package is... Read more
Adobe Muse CC 2015 2015.0.1 - Design and...
Muse CC 2015 is available as part of Adobe Creative Cloud for as little as $14.99/month (or $9.99/month if you're a previous Muse customer). Muse CS6 is still available for purchase (without a... Read more
Adobe Illustrator CC 2015 19.1.0 - Profe...
Illustrator CC 2015 is available as part of Adobe Creative Cloud for as little as $19.99/month (or $9.99/month if you're a previous Illustrator customer). Illustrator CS6 is still available for... Read more
Corel Painter 14.1.0.1105 - Digital art...
Corel Painter helps you create astonishing art in a variety of media. Paint with vivid oil paints, fluid water colors, and earthy charcoals. Corel Painter flawlessly recreates the tones and textures... Read more
Pacifist 3.5.4 - Install individual file...
Pacifist opens up .pkg installer packages, .dmg disk images, .zip, .tar. tar.gz, .tar.bz2, .pax, and .xar archives and more, and lets you extract or install individual files out of them. This is... Read more
Merlin Project 3.1.0.40305 - Project man...
Merlin Project is for those of you who are responsible for complex projects. Simple lists of tasks won't suffice. Good planning raises questions about the dependencies of activities on each other,... Read more
DM1 2.0 - Advanced drum machine. (Commer...
DM1 is an advanced Drum Machine. It turns your computer into a fun and creative beat making machine. Easy and fast to use, loaded with 86 superb electronic drum kits and beautiful hyper-realistic... Read more
Posterino 3.2.1 - Create posters, collag...
Posterino offers enhanced customization and flexibility including a variety of new, stylish templates featuring grids of identical or odd-sized image boxes. You can customize the size and shape of... Read more

Mazes of Karradash (Games)
Mazes of Karradash 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: The city of Karradash is under attack: the monsters of the Shadow Realms are emerging from the depths.No adventurer is... | Read more »
Battle Golf is the Newest Game from the...
Wrassling was a pretty weird - and equally great - little wressling game. Now the developers, Folmer Kelly and Colin Lane, have turned their attention to a different sport: golfing. This is gonna be weird. [Read more] | Read more »
Qbert Rebooted has the App Store Going...
The weird little orange... whatever... is back, mostly thanks to that movie which shall remain nameless (you know the one). But anyway it's been "rebooted" and now you can play the fancy-looking Qbert Rebooted on iOS devices. [Read more] | Read more »
Giant Monsters Run Amok in The Sandbox...
So The Sandbox has just hit version number 1.99987 (seriously), and it's added a lot more stuff. Just like every other update, really. [Read more] | Read more »
Fish Pond Park (Games)
Fish Pond Park 1.0.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.0 (iTunes) Description: Nurture an idyllic slice of tourist's heaven into the top nature spot of the nation, furnishing it with a variety of... | Read more »
Look after Baby Buddy on your Apple Watc...
Parigami Gold is the new premium version of the match three puzzler that includes Apple Watch support and all new content. You won't simply be sliding tiles around on your wrist, the Apple Watch companion app is an all new mini-game in itself. You'... | Read more »
Swallow all of your opponents as the big...
Eat all of the opposition and become the largest ball in Battle of Balls now available in the App Store and Google Play. Battle of Balls pits you against other opponents in real time and challenges you to eat more balls and grow larger than all of... | Read more »
PAC-MAN Championship Edition DX (Games)
PAC-MAN Championship Edition DX 1.0.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.0 (iTunes) Description: It’s Your World. EAT IT! Get ready for more ghost chain gobbling and frantic action in PAC-MAN® CE-DX! The... | Read more »
incurve (Games)
incurve 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: Get ready for 2 different gravities Goal is to hit as many white dots on your way up.When you're touching the screen, the dots have a... | Read more »
Crossy Road has its Own Merch Store Now....
Do you like Crossy Road? I mean do you really like Crossy Road? Well then you're in luck! Hipster Whale has opened up a Crossy Road store, so you can show off your fandom via official T-shirts. [Read more] | Read more »

Price Scanner via MacPrices.net

Apple restocks refurbished Mac minis for up t...
The Apple Store has restocked Apple Certified Refurbished 2014 Mac minis, with models available starting at $419. Apple’s one-year warranty is included with each mini, and shipping is free: - 1.4GHz... Read more
13-inch 2.5GHz MacBook Pro on sale for $899,...
Best Buy has the 13″ 2.5GHz MacBook Pro available for $899.99 on their online store. Choose free shipping or free instant local store pickup (if available). Their price is $200 off MSRP. Price is... Read more
21-inch 2.9GHz iMac on sale for $1299, save $...
Best Buy has the 21″ 2.9GHz iMac on sale today for $1299.99 on their online store. Choose free shipping or free local store pickup (if available). Their price is $200 off MSRP, and it’s the lowest... Read more
Free Image Sizer 1.3 for iOS Offers Photo Edi...
Xi’An, China based G-Power has announced the release of Image Sizer 1.3 for the iPhone, iPad, and iPod touch, an important update to their free photo editing app. Image Sizer’s collection of easy to... Read more
Sale! 13″ 1.6GHz/128GB MacBook Air for $899,...
B&H Photo has the 13″ 1.6GHz/128GB MacBook Air on sale for $899 including free shipping plus NY tax only. Their price is $100 off MSRP, and it’s the lowest price available for this model. Read more
13-inch Retina MacBook Pros on sale for $100...
Best Buy has 13-inch Retina MacBook Pros on sale for $100 off MSRP on their online store. Choose free shipping or free local store pickup (if available). Prices are for online orders only, in-store... Read more
Will BMW’s i3 Electric Vehicle Be The Automo...
The German-language business journal Manager Magazin’s Michael Freitag reports that Apple and the German performance/luxury automaker Bayerishe Motoren Werke (BMW) are back at far-reaching... Read more
Sale! $250 off 15-inch Retina MacBook Pro, $2...
B&H Photo has lowered their price for the 15″ 2.2GHz Retina MacBook Pro to $1749, or $250 off MSRP. Shipping is free, and B&H charges NY sales tax only. They have the 27″ 3.3GHz 5K iMac on... Read more
Global Smartphone Market Posts 11.6% Year-Ove...
According to the latest preliminary data released from the International Data Corporation (IDC) Worldwide Quarterly Mobile Phone Tracker, smartphone vendors shipped a total of 337.2 million units... Read more
15-inch and 13-inch Retina MacBook Pros on sa...
B&H Photo has 15″ & 13″ Retina MacBook Pros on sale for up to $180 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.2GHz Retina MacBook Pro: $1819 save $180 - 15″ 2.... Read more

Jobs Board

*Apple* Customer Experience (ACE) Leader - A...
…management to deliver on business objectives Training partner store staff on Apple products, services, and merchandising guidelines Coaching partner store staff on Read more
Project Manager - *Apple* Pay Security - Ap...
**Job Summary** The Apple Pay Security team is seeking a highly organized, results-driven Project Manager to drive the development of Apple Pay Security. If you are Read more
*Apple* TV Product Design Internship (Spring...
…the mechanical design effort associated with creating world-class products with the Apple TV PD Group. Responsibilities will include working closely with manufacturing, Read more
*Apple* Watch SW Application Project Manager...
**Job Summary** The Apple Watch software team is looking for an Application Engineering Project Manager to work on new projects for Apple . The successful candidate Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.