TweetFollow Us on Twitter

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

Volume Number: 26
Issue Number: 03
Column Tag: Game Development for iPad, iPhone and iPod Touch

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

Tools for building 2D games

by Rich Warren

Let's Get These Engines Running!

Last time, we looked at the cocos2d for iPhone graphics framework, and the Chipmunk Physics Engine. If you followed the instructions from Part 1, you have already installed both libraries and played around with the examples. If not, please check out Part 1, because we're going to hit the ground running.

In this article, we will build a simple pachinko game. When the user taps the screen, a ball will fall from the top, and bounce off a number of pins. If it goes into the target, a bell will ring. Otherwise, the ball simply exits off the bottom of the screen. We will try to keep this simple, and focused on the graphics and physics engines. There's no score. No ability to affect the ball once it is launched, and only very simple sound effects. Still, by the time we're done you should have a good handle on how to use cocos2d and Chipmunk in your own projects. So, without further introduction, let's jump right into the code.

Building a New 2D Game App

Launch Xcode, and create a new cocos2D Chipmunk application: File--> New Project...--> iPhone OS--> Application--> cocos2d-0.8.2 Chipmunk Application. Name the project Pachinko, and click the Save button. As I mentioned in Part 1, the current release of cocos2d defaults to the iPhone 2.2 SDK. Unfortunately, this version is not included in recent versions of Xcode. The simplest solution is to change the Active SDK in the Overview drop-down menu, and select an existing SDK. Usually, I select the most recent simulator release (as of writing, 3.1.3).


Create a New Cocos2D Chipmunk Application

Next, any game needs graphics and sound effects. Open up the Resources group. As you can see, the template already has four PNG files. Default.png is the splash screen that's displayed while the application launches. Icon.png is the application icon that appears on the iPhone's home screen. For simplicity's sake, we will leave these alone; however, you will want to replace them in your own projects. The fps_images.png image is used to display the frame rate while testing the application. We can use that feature as a quick and dirty performance test, so leave that image alone. However, the grossini_dance_atlas.png image is only used by the template's sample application. You can safely delete that file.

Creating artwork and sound effects is beyond the scope of this article. Instead, you should download the article's source code from ftp://ftp.mactech.com/src/mactech/volume26_2010/Warren-Pachinko_iPhone_Source.zip. Now copy the following files to the resource folder: bell.wav, tick1.wav, tick2.wav, right_bumper.png, left_bumper.png, back-ground.png, ball.png, pin.png and target.png. To add these files, right-click on the Resource group, then click Add Existing Files.... Select the desired files, and then click the Add button.

Wrapping Up our Entities

Our pachinko game will have a number of balls that can interact with other things on the screen: other balls, pins, bumpers and the target. As described in Part 1, a single game entity is a combination of graphical elements and physical attributes. These entities are represented by a number of cocos2d objects and Chipmunk structures. All of these need to be created, initialized and (when the time is right) destroyed properly. That's a lot of code just to get something on the screen. Fortunately, most of this code is identical from entity to entity. We'll take advantage of this by wrapping all the objects and structures in our own class, Entity. This class will encapsulate all the common creation and destruction code, properly handling the memory management for the underlying objects and structures.

A classic Object Oriented approach would involve encapsulating the common elements in a base class, and then create a number of subclasses to represent each of the different entity types. While that would work here, I've chosen a slightly different approach. We will use the Entity class for all our entities, and encapsulate the differences between the various types using a configuration source object. To do this, we first create the EntityConfigurationSource protocol. This protocol is similar to the DataSource protocols used throughout in the Cocoa Framework. If you've ever used a UITableViewDataSource to fill the contents of a table, you have the basic idea. There is one small, technical difference. Typically, to avoid reference loops, a Cocoa class does not retain its data source. However, following that convention would only add unnecessary complications to our code. Therefore, to avoid any confusion, we will call our protocol a ConfigSource, not a DataSource.

EntityConfigSource

Let's build the EntityConfigSource. Right click on the Classes group and then select Add New Group. Create a new subgroup named Entities. Now, right click on the Entities group and select Add New File... iPhone OS Cocoa Touch Class Objective-C Protocol. Name this protocol EntityConfigSource.h, and click the Finish button. Now modify the file as shown below:

EntityConfigSource.h

This protocol encapsulates the differences between different types of Entities.

#import <Foundation/Foundation.h>
#import "cocos2d.h"
#import "chipmunk.h"
#import "Utility.h"
@protocol EntityConfigSource <NSObject>
-(enum SpriteType) spriteType;
-(int) depth;
-(BOOL) isStatic;
-(cpBody*) createBody;
-(NSArray*) createShapesGivenBody:(cpBody*)body;
@optional
-(CGPoint) initialLocation;
-(CGFloat) initialSpriteRotationForLocation:(CGPoint)location;
-(CGFloat) updatedSpriteRotationForLocation:(CGPoint)location;
@end

This protocol contains a mixture of required and optional methods. spriteType returns a value that can be used to look up the appropriate image file name. depth determines the image's z-depth (higher numbers are drawn overtop lower numbers). isStatic returns YES if the entity is static (non-moving), or NO if it is mobile. createBody creates the entity's cpBody structure, and createShapesGivenBody builds an array of shapes associated with that body. As described in Part 1, the body defines the entity's mass and moment of inertia, while the shapes define how the entity interacts with other entities (typically by defining its surface geometry and characteristics that determine the effects of collisions).

The optional methods can be used to set the object's initial location and its initial rotation. Finally, the updateSpriteRotationForLocation: method is used to set the sprite's rotation based on its current location. We will use this to create fake 3D lighting effects later on.

Note: this class imports the Utility.h file. This defines the enumerations and a number of short helper functions used throughout this project. For the sake of saving time and space, I will not spend much time on those functions here. Simply copy Utility.h and Utility.m from the article's source code.

Entity Class

OK, let's look at the Entity class. Create a new Objective-C class in the Entities subgroup (right click Entities and select Add New File... iPhone OS Cocoa Touch Class Objective-C Class), and name it Entity. Now open Entity.h and modify it as shown below:

Entity.h

#import <Foundation/Foundation.h>
#import "EntityConfigSource.h"
@interface Entity : NSObject {
    
    id <EntityConfigSource> configSource;
    // cocos2D Graphical Features
    Layer* layer;
    Sprite* sprite;
    
    // Chipmunk Physics Features
    cpSpace* space;
    cpBody* body;
    NSArray* shapes;
}
@property (readonly, nonatomic) CGPoint position;
@property (readonly, nonatomic) NSArray* shapes;
// Init
 -(id)initInSpace:(cpSpace*)theSpace 
        forLayer:(Layer*)theLayer 
      atLocation:(CGPoint)location 
 withConfigSource:(id <EntityConfigSource>)theConfigSource;
// Update
-(void) updateSprite;
// Convenience methods for creating different Entities
+(id) pinInSpace:(cpSpace*)space 
        forLayer:(Layer*)layer 
      atLocation:(CGPoint)location;
+(id) leftBumperInSpace:(cpSpace*)space 
               forLayer:(Layer*)layer;
+(id) rightBumperInSpace:(cpSpace*)space 
                forLayer:(Layer*)layer;
+(id) targetInSpace:(cpSpace*)space 
           forLayer:(Layer*)layer 
         atLocation:(CGPoint)location;
 
+(id) ballInSpace:(cpSpace*)space 
         forLayer:(Layer*)layer 
       atLocation:(CGPoint)location;
@end

We'll look at the methods in more detail below. For now, focus on the instance variables. Notice how the Entity's appearance is described using a Sprite placed within a Layer, while the physical attributes are defined using a cpBody and one or more cpShapes placed within a cpSpace.

Note: cpShape is a C structure, not an Objective-C Object. As a result, we cannot place it directly into an NSArray. There are a few possible solutions to this problem. In this case, I chose to create a simple object called Shape that wraps the cpShape structure. We then place the Shape object into the NSArray. I'll leave the actual implementation of Shape as a homework assignment (or just grab a copy from the source code).

So far, everything looks simple enough. Lets move on to the implementation. We'll go slow and take this in bite-size chunks.

Defining an extension for Entity

#import "Entity.h"
#import "PinConfigSource.h"
#import "LeftBumperConfigSource.h"
#import "RightBumperConfigSource.h"
#import "TargetConfigSource.h"
#import "BallConfigSource.h"
#import "Shape.h"
@interface Entity()
-(void)setConfigSource:(id<EntityConfigSource>)theConfigSource;
 -(void) setPosition:(CGPoint)thePosition;
    -(void) setSpace:(cpSpace*)theSpace;
    -(void) setLayer:(Layer*)theLayer;
@end

We start by creating an extension on Entity that declares a number of private methods. We will use these methods to initialize our Entitiy instances. We will look at the actual definitions in just a moment.

init and dealloc

@implementation Entity
@synthesize shapes;
-(id)initInSpace:(cpSpace*)theSpace 
        forLayer:(Layer*)theLayer 
      atLocation:(CGPoint)location 
withConfigSource:(id <EntityConfigSource>)theConfigSource {
    
    if( (self=[super init])) {
        
        // these methods must be called in this order.
        [self setConfigSource: theConfigSource];
        [self setPosition:location];    
        [self setSpace: theSpace];
        [self setLayer: theLayer];   
        
    }
    
    return self;
 }
-(id) init {
    
    [NSException raise:NSInternalInconsistencyException 
                format:@"Cannot initialize using init, use %@ instead",
    NSStringFromSelector(
        @selector(initInSpace:forLayer:atLocation:withConfigSource:))];
    
    // we will never get here
    return nil;    
}
// remember: do not release during chipmunk callbacks (e.g. collisions).
-(void) dealloc {
    
    [layer removeChild:sprite cleanup:NO];
    [layer release];
    
    [sprite release];
    
    for (Shape* shape in shapes) {
        cpSpaceRemoveShape(space, shape.pointer);
    }
        
    [shapes release];
    
    if (![configSource isStatic]) {
        cpSpaceRemoveBody(space, body);
    }
        
    cpBodyFree(body);
    
    [configSource release];
    [super dealloc];
}

Next we synthesize the reader method for our shapes property. Notice, we do not synthesize the position property. We will provide a custom implementation instead. While synthesizing position won't cause an error, I prefer to use @synthesize only on properties actually managed by the compiler.

Next we define the initialization and deallocation methods. initInSpace:forLayer:atLocation:with-ConfigSource: is our default initialization method. It simply calls the private methods defined in our extension. Additionally, we want to prevent people from calling NSObject's init method by mistake, so our implementation overrides init and throws an exception. Notice that we dynamically generate the string for our method name, rather than hard coding it in our exception. This helps ensure that our message will still be relevant, even if we refactor our initialization methods.

The dealloc method is somewhat more complex. We remove our entity from the Layer and the cpSpace, then release or free all the components. Notice that we must handle the memory management of Chipmunk structures differently than standard Objective-C objects. Instead of calling retain and release, we must use Chipmunk's functions to allocate and free those structures.

Correctly managing both the Objective-C objects and the C structures is one of the more complicated aspects of using Chipmunk on the iPhone. Fortunately, our Entity class will hide all this complexity from us.

There is one small wrinkle with this code. We cannot release any Entities during a Chipmunk callback (e.g. when processing collisions). I believe this restriction may be removed in later versions of Chipmunk. Still, it is not hard to work around. Either make a list of objects to release, and then release them all once the callbacks have finished, or use performSelector:withObject:afterDelay: to schedule the release. Even setting the delay value to 0 will cause the method to be delayed until the run loop is empty, and you've moved safely outside the callback code.

Now lets look at the implementation of our private methods. Again, these do the bulk of the work involved in setting up our Entities. Notice that these must be called in the correct order. First set the config source. Then set the position. Finally set the space and the layer.

setConfigSource:

-(void) setConfigSource:(id<EntityConfigSource>)theConfigSource {
    
    // store the config source
    configSource = theConfigSource;
    [configSource retain];
    
    // and setup the sprite FILE_NAMES[[delegate spriteType]]
    sprite = [Sprite spriteWithFile: 
                 FILE_NAMES[[configSource spriteType]]];
    [sprite retain];
}

This method stores and retains the config source. Then it queries the source for the sprite type and uses that to determine the correct file name. It then initializes and retains the sprite. Note: FILE_NAMES is an array of NSStrings defined in Utility.h.

setPosition

-(void) setPosition:(CGPoint)thePosition {
    
    sprite.position = thePosition;
    
    if ([configSource respondsToSelector:
             @selector(initialSpriteRotationForLocation:)]) {
        
         sprite.rotation = [configSource 
             initialSpriteRotationForLocation:thePosition];
    }
    
}

The setPosition: method sets the sprite's position. Then we check to see if our config source has implemented the optional initialSpriteRotationForLocation: method. If it has, we call that method and use the result to set our rotation.

setSpace:

-(void) setSpace:(cpSpace*)theSpace {
    
    space = theSpace;
    
    body = [configSource createBody];
    body->p = sprite.position;
    
    if (![configSource isStatic]) {
        cpSpaceAddBody(space, body);
    }
    
    shapes = [configSource createShapesGivenBody:body];
    [shapes retain];
    
    for (Shape* shape in shapes) {
        
        // save the InteractiveObject in the cpShape's data field.
        [shape setData: self];
        
        if ([configSource isStatic]) {
            
            // only add the static shape
            cpSpaceAddStaticShape(space, shape.pointer);
        }
        else {
            
            // add the non-static shape
            cpSpaceAddShape(space, shape.pointer);
            
        }
    }
}

This method is a bit more complex than the rest. As mentioned previously, the cpSpace structure holds references to a number of bodies and shapes. When we step the space forward in time, Chipmunk will calculate the new position and rotation for all the bodies, as well as checking for collisions among any shapes within that space. This method creates the body and the shapes needed for our Entity and add them to the given space.

First we store a reference to the cpSpace structure. Then we ask our config source to build our cpBody structure. We then set the cpBody's position equal to the Sprite's position. As mentioned in Part 1, both the cpBody and Sprite store the entity's position, and we need to keep them in sync. Finally, we query the config source to see if this entity is static (non-moving). If it is not static, we add the body to the space.

Note: Do not add the bodies of static objects to the cpSpace structure. Any bodies in the space will have the force of gravity applied to them each time we iterate the space. While the static objects won't move, the accumulated force will cause very odd bugs during collisions.

Next, we query the config source for a list of shapes associated with our entity. We store and retain the list of Shapes. Remember, we are wrapping the cpShape structure inside a simple Objective-C class, named Shape. The cpShape structure also has a data field that can be used to store pointers to arbitrary user data. In our case, we will store a reference to this Entity. That will allow us to find the correct Entity for a given cpShape.

Iterate over all the Shapes and call setData: to set the cpSpace->data field (see the source code for implementation details). Then add the shape to the space. Note: if the shape is a static shape, you will add it using the cpSpaceAddStaticShape() function. Otherwise add it using cpSpaceAddShape().

-(void) setLayer:(Layer*)theLayer {
    
    layer = [theLayer retain];
    [theLayer addChild:sprite z:[configSource depth]];
    
}

Finally, we add the Layer. First, we store a reference to the Layer and retain it. We then add the Sprite to the Layer. In many ways, the Layer is to cocos2D objects what the cpSpace is to Chipmunk structures.

Also note, we query our config source for our entity's z-value. Objects with a higher z-value will be drawn over objects with a lower z-value. In Utility.h we define an enumeration for all the z-values in ascending order for the background, bumpers, ball, target and pins. The background is drawn on the bottom, and the pins are drawn on the top.

Careful attention to the z-values is important for making the game look right. For example, we have designed the target's artwork so that when the ball goes into the target, it disappears behind the target. Getting the effect we want requires coordination between the art design, the z-values and the target's shape.

Update

-(void) updateSprite {
    
    if (![configSource isStatic]) {
        
        sprite.position = body->p;
        
        if ([configSource
                respondsToSelector:@selector(updatedSpriteRotation)]) {
            
            sprite.rotation = [configSource 
                               updatedSpriteRotationForLocation:body->p];
        }
    }
}

The next method is used to update our sprite position and rotation. As mentioned previously, the Chipmunk physics engine will automatically update the position of our entitiy's cpBody every time we iterate the space. We then need to call this method to keep our sprite's position in sync with the cpBody.

First, we check to make sure our object is not static. Static objects will not move, so their Sprite positions never needs updating. Then we simply copy the cpBody's position over to the Sprite.

Typically, you would want to copy the cpBody's rotation as well, but we're going to do something a little different. We only have one type of moving object, the balls, and they are round, reflective, and largely featureless. Rotating them does not make a lot of sense. However, we have placed a highlight at the top of the ball image. If we assume this is the reflection from a single light source somewhere off the top of the screen, then we should to rotate the ball so that the highlight points towards this light source. This allows us to fake 3D lighting effects within our 2D game.

First, we check to see if the config source implements the optional upatedSpriteRotation method. If it does, we set the Sprite's rotation by calling this method. If not, we do not change the rotation at all (though, we could set it equal to the body's rotation if we had other moving objects where rotating the image made sense).

Next we implement the reader for our position property. This simply returns the body's position.

Entity Accessor Methods

-(CGPoint) position {
    return body->p;
}

Now we start building convenience functions. Remember, the goal of building the Entity class was to hide as much of the complexity as possible. By creating a convenience method for each Entity type, we further simplify the interface. Our code only needs to access the Entity class itself. It does not need to know about the different EntityConfigSources.

Below are the convenience methods for the ball and the left bumper. I leave the rest as homework.

Entity Public Convenience Methods

+(id) ballInSpace:(cpSpace*)space 
         forLayer:(Layer*)layer 
       atLocation:(CGPoint)location {
    
    id <EntityConfigSource> configSource = 
        [[[BallConfigSource alloc] init] autorelease];
    
    return [[[Entity alloc] initInSpace:space 
                               forLayer:layer 
                             atLocation:location
                       withConfigSource:configSource] autorelease];
}
+(id) leftBumperInSpace:(cpSpace*)space 
               forLayer:(Layer*)layer  {
    
    id <EntityConfigSource> configSource = 
        [[[LeftBumperConfigSource alloc] init] autorelease];
    
    return [[[Entity alloc] initInSpace:space 
                               forLayer:layer 
                             atLocation:[configSource initialLocation]
                       withConfigSource:configSource] autorelease];
}

These methods simply create the correct config source, and then instantiate the Entity. Note, that ballInSpace:forLayer:atLocation: takes a location parameter, which is used to set the ball's initial location. leftBumperInSpace:forLayer: does not. The location is set by the config source's initialLocation method.

There is a design issue here worth discussing. This is not a truly general approach to creating cocos2d/Chipmunk objects. It works well for this particular game, but it would be easy to come up with situations where this approach is simply not appropriate. Partially, I did that to keep the Entity code relatively simple for the purpose of this tutorial. Writing truly general code is both complicated and hard. But, part of it is also just good software engineering. You shouldn't over-engineer your classes by adding a lot of features that you may never use. Instead, add new features as and when they are needed, refactoring your existing code as you go.

Config Sources

Of course, we're not quite done yet. We still need our config sources. I'll show you two. One for the ball and one for the target. Again, I leave the rest as homework.

BallConfigSource.m

#import "BallConfigSource.h"
#import "Shape.h"
@implementation BallConfigSource
-(id) init {
   
   if ((self = [super init])) {
      
      CGSize size = getSpriteSize(BALL);
      radius = size.width / 2.0f;
      
   }
   
   return self;
}
-(enum SpriteType) spriteType {
   return BALL;
}
-(int) depth {
   return BALL_DEPTH;
}
 
-(BOOL) isStatic {
   return NO;
}
-(cpBody*) createBody {
   
   return cpBodyNew(10, cpMomentForCircle(10, 0.0, radius, cpv(0,0)));
}
-(NSArray*) createShapesGivenBody:(cpBody*)body {
   
   cpShape* shape = cpCircleShapeNew(body, radius, cpv(0.0, 0.0));
   
   shape->e = 0.75f;
   shape->u = 0.5f;
   shape->collision_type = BALL_COLLISION;
   
   return [NSArray arrayWithObject:[Shape shapeFromPointer:shape]];
}
-(CGFloat) initialSpriteRotationForLocation:(CGPoint)location {
   return getLightRotation(location);
}
-(CGFloat) updatedSpriteRotationForLocation:(CGPoint)location {
   return getLightRotation(location);
}
@end

Most of this should be relatively straightforward. The init method simply calculates the ball's radius based on the sprite size. Both the BALL enum and the getSpriteSize() method are defined in Utility.h.

The createBody method creates a cpBody structure with a mass of 10.0 and a moment of inertia based on Chipmunk's cpMomentForCircle() function. The CreateShapesGivenBody: method creates a single circular shape, and then sets the elasticity to 0.75 and the friction to 0.5. It then sets the collision type. We will use the collision types later to correctly dispatch different collisions.

Finally, as mentioned earlier, the balls should be rotated so that their highlight points towards our imaginary light source. The initialSpriteRotationForLocation: and updatedSpriteRotationForLocation: methods handle this by calling the getLightRotation() function, also from Utility.h.

TargetConfigSource.m

#import "TargetConfigSource.h"
#import "Shape.h"
Shape* buildShape(cpBody* body, cpVect start, cpVect end) {
    
    cpShape* shape = cpSegmentShapeNew(body, start, end, 0.0f);
    
    shape->e = 0.5f;
    shape->u = 0.5F;
    shape->collision_type = TARGET_WALL_COLLISION;
    
    return [Shape shapeFromPointer:shape];
} 
@implementation TargetConfigSource
-(enum SpriteType) spriteType {
    return TARGET;
}
-(int) depth {
    return TARGET_DEPTH;
}
-(BOOL) isStatic {
    return YES;
}
-(cpBody*) createBody {
    return cpBodyNew(INFINITY, INFINITY);
}
-(NSArray*) createShapesGivenBody:(cpBody*)body {
    
    CGSize size = getSpriteSize(TARGET);
    CGFloat halfWidth = size.width / 2.0f;
    
    cpVect upperLeft  = cpv(-halfWidth, halfWidth);
    cpVect lowerLeft  = cpv(11.0f - halfWidth, -halfWidth);
    cpVect lowerRight = cpv(halfWidth - 11.0f, -halfWidth);
    cpVect upperRight = cpv(halfWidth, halfWidth);
    
    // These ball will bounce off these.
    Shape* left = buildShape(body, upperLeft, lowerLeft);
    Shape* bottom = buildShape(body, lowerLeft, lowerRight);
    Shape* right = buildShape(body, lowerRight, upperRight);
    
    // But this shape will trigger the target.
    Shape* goal = buildShape(body, 
                             cpv(15.0f - halfWidth, 4.0f - halfWidth), 
                             cpv(halfWidth - 15.0f, 4.0f - halfWidth));
    
    // Need to change the collision type
    goal.pointer->collision_type = TARGET_COLLISION;
    
    return [NSArray arrayWithObjects:left, bottom, right, goal, nil];
}
@end

The left bumper is a little more complex, but not by much. Since this is a static object, we create a cpBody with an infinite mass and moment of inertia. We then define a complex shape. This starts with three lines (left, bottom and right) that define an open cup shape. We then define a fourth goal line at the bottom of the cup. Notice, fast-moving balls might penetrate the target's wall by a few pixels before a collision is detected. To prevent the ball from accidentally triggering the goal, we create a 4-pixel padding between it and the cup. Also notice that the coordinates for the shapes are all relative to the cpBody's position (or the center of the Sprite's icon). The target's walls are given a TARGET_WALL_COLLISION, while the goal is given a TARGET_COLLISION. This will allow us to process the collisions separately. When a ball hits the wall, it will just bounce off. But, when it hits the goal, we will ring a bell and delete the ball. We also gave the target's shapes a relatively low elasticity. When a ball goes into the cup, we don't want it to just bounce out again.

Believe it or not, most of the heavy work is now done. We just need to make our main layer, and then link everything together in our app delegate.

Fake It Till You Make It

The actual implementation of the Main Layer will have to wait until the next installation. Still, we want to see our Entities in action. So, lets make a few quick modifications to the existing HelloWorldScene.m file.

First, import our Entity class at the top of the file. Then modify the eachShape() function as shown below. This simply extracts the Entity from the shape data and calls our updateSprite function.

eachShape()

static void
eachShape(void *ptr, void* unused)
{
    cpShape *shape = (cpShape*) ptr;
    Entity *entity = shape->data;
    
    [entity updateSprite];
}

Now delete the following lines from the init method.

Init

AtlasSpriteManager *mgr = [AtlasSpriteManager 
    spriteManagerWithFile:@"grossini_dance_atlas.png" capacity:100];
[self addChild:mgr z:0 tag:kTagAtlasSpriteManager];

Now modify the addNewSpriteX:y: method as shown below.

addNewSpriteX:y:

-(void) addNewSpriteX: (float)x y:(float)y
{
    
   double offx = (CCRANDOM_0_1() * 2.0 - 1.0);
   double offy = (CCRANDOM_0_1() * 2.0 - 1.0);
    [[Entity ballInSpace:space 
                forLayer:self 
              atLocation:ccp(x+offx, y + offy)] retain];
}

Here, we simply create a ball at the given position. We add a slight random nudge to the position, to avoid adding multiple balls in the exact same location. The collision detection system doesn't work properly if their positions are exactly the same.

Notice how much simpler addNewSpriteX:y: has become. Our Entity class hides much of the complexity that the previous implementation had to manage manually. Of course, we're creating a memory leak here by retaining and never releasing all these Entity objects. But, that's OK for a quick test.

Compile and run the application. It should add a ball whenever you tap the screen.


Sample Ball Entities

Once everything works properly, go ahead and start playing around. Look through the HelloWorldScene.m file. Try changing the gravity vector. Add different Entity types. Play around with the code and get a feel for how things work. Don't worry about messing up the HelloWorldScene class. We won't be using it at all in Part 3. Instead, we will write our own layer, MainLayer, and use that to wrap up our Pachinko game.

Most importantly, until next time, have fun. This is supposed to be a game.


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.

 
AAPL
$116.47
Apple Inc.
+0.16
MSFT
$47.98
Microsoft Corpora
-0.72
GOOG
$537.50
Google Inc.
+2.67

MacTech Search:
Community Search:

Software Updates via MacUpdate

Cobook 3.0.7 - Intelligent address book....
Cobook Contacts is an intuitive, engaging address book. Solve the problem of contact management with Cobook Contacts and its simple interface and powerful syncing and integration possibilities.... Read more
StatsBar 1.9 - Monitor system processes...
StatsBar gives you a comprehensive and detailed analysis of the following areas of your Mac: CPU usage Memory usage Disk usage Network and bandwidth usage Battery power and health (MacBooks only)... Read more
Cyberduck 4.6 - FTP and SFTP browser. (F...
Cyberduck is a robust FTP/FTP-TLS/SFTP browser for the Mac whose lack of visual clutter and cleverly intuitive features make it easy to use. Support for external editors and system technologies such... Read more
Maya 2015 - Professional 3D modeling and...
Maya is an award-winning software and powerful, integrated 3D modeling, animation, visual effects, and rendering solution. Because Maya is based on an open architecture, all your work can be scripted... Read more
Evernote 6.0.1 - Create searchable notes...
Evernote allows you to easily capture information in any environment using whatever device or platform you find most convenient, and makes this information accessible and searchable at anytime, from... Read more
calibre 2.11 - Complete e-library manage...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital... Read more
Herald 5.0.1 - Notification plugin for M...
Note: Versions 2.1.3 (for OS X 10.7), 3.0.6 (for OS X 10.8), and 4.0.8 (for OS X 10.9) are no longer supported by the developer. Herald is a notification plugin for Mail.app, Apple's Mac OS X email... Read more
Firetask 3.7 - Innovative task managemen...
Firetask uniquely combines the advantages of classical priority-and-due-date-based task management with GTD. Stay focused and on top of your commitments - Firetask's "Today" view shows all relevant... Read more
TechTool Pro 7.0.6 - Hard drive and syst...
TechTool Pro is now 7, and this is the most advanced version of the acclaimed Macintosh troubleshooting utility created in its 20-year history. Micromat has redeveloped TechTool Pro 7 to be fully 64... Read more
PhotoDesk 3.0.1 - Instagram client for p...
PhotoDesk lets you view, like, comment, and download Instagram pictures/videos! (NO Uploads! / Image Posting! Instagram forbids that! AND you *need* an *existing* Instagram account). But you can do... Read more

Latest Forum Discussions

See All

Ubisoft Gives Everyone Two New Ways to E...
Ubisoft Gives Everyone Two New Ways to Earn In-Game Stuff for Far Cry 4 Posted by Jessica Fisher on November 21st, 2014 [ permalink ] | Read more »
Golfinity – Tips, Tricks, Strategies, an...
Dig this: Would you like to know what we thought of being an infinite golfer? Check out our Golfinity review! Golfinity offers unlimited ways to test your skills at golf. Here are a few ways to make sure your score doesn’t get too high and your... | Read more »
Dark Hearts, The Sequel to Haunting Meli...
Dark Hearts, The Sequel to Haunting Melissa, is Available Now Posted by Jessica Fisher on November 21st, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Meowza! Toyze Brings Talking Tom to Life...
Meowza! | Read more »
Square Enix Announces New Tactical RPG f...
Square Enix Announces New Tactical RPG for Mobile, Heavenstrike Rivals. Posted by Jessica Fisher on November 21st, 2014 [ permalink ] With their epic stories and gorgeous graphics, | Read more »
Quest for Revenge (Games)
Quest for Revenge 1.0.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.0 (iTunes) Description: The great Kingdom of the west has fallen. The gods ignore the prayers of the desperate. A dark warlord has extinguished... | Read more »
Threadz is a New Writing Adventure for Y...
Threadz is a New Writing Adventure for You and Your Friends Posted by Jessica Fisher on November 21st, 2014 [ permalink ] In the tradition of round-robin storytelling, | Read more »
SteelSeries Stratus XL Hardware Review
Made by: SteelSeries Price: $59.99 Hardware/iOS Integration Rating: 4 out of 5 stars Usability Rating: 4.5 out of 5 stars Reuse Value Rating: 4.25 out of 5 stars Build Quality Rating: 4.5 out of 5 stars Overall Rating: 4.31 out of 5 stars | Read more »
ACDSee (Photography)
ACDSee 1.0.0 Device: iOS iPhone Category: Photography Price: $1.99, Version: 1.0.0 (iTunes) Description: Capture, perfect, and share your photos with ACDSee. The ACDSee iPhone app combines an innovative camera, a powerful photo... | Read more »
ProTube for YouTube (Entertainment)
ProTube for YouTube 2.0.2 Device: iOS Universal Category: Entertainment Price: $1.99, Version: 2.0.2 (iTunes) Description: ProTube is the ultimate, fully featured YouTube app. With it's highly polished design, ProTube offers ad-free... | Read more »

Price Scanner via MacPrices.net

Save up to $400 with Apple refurbished 2014 1...
The Apple Store has restocked Apple Certified Refurbished 2014 15″ Retina MacBook Pros for up to $400 off the cost of new models. An Apple one-year warranty is included with each model, and shipping... Read more
New 13-inch 1.4GHz MacBook Air on sale for $8...
 Adorama has the 2014 13″ 1.4GHz/128GB MacBook Air on sale for $899.99 including free shipping plus NY & NJ tax only. Their price is $100 off MSRP. B&H Photo has the 13″ 1.4GHz/128GB MacBook... Read more
Apple Expected to Reverse Nine-Month Tablet S...
Apple and Samsung combined accounted for 62 percent of the nearly 36 million branded tablets shipped in 3Q 2014, according to early vendor shipment share estimates from market intelligence firm ABI... Read more
Stratos: 30 Percent of US Smartphone Owners t...
Stratos, Inc., creator of the Bluetooth Connected Card Platform, has announced results from its 2014 Holiday Mobile Payments Survey. The consumer survey found that nearly one out of three (30 percent... Read more
2014 1.4GHz Mac mini on sale for $449, save $...
 B&H Photo has lowered their price on the new 1.4GHz Mac mini to $449.99 including free shipping plus NY tax only. Their price is $50 off MSRP, and it’s the lowest price available for this new... Read more
Check Apple prices on any device with the iTr...
MacPrices is proud to offer readers a free iOS app (iPhones, iPads, & iPod touch) and Android app (Google Play and Amazon App Store) called iTracx, which allows you to glance at today’s lowest... Read more
64GB iPod touch on sale for $249, save $50
Best Buy has the 64GB iPod touch on sale for $249 on their online store for a limited time. Their price is $50 off MSRP. Choose free shipping or free local store pickup (if available). Sale price for... Read more
15″ 2.2GHz Retina MacBook Pro on sale for $17...
 B&H Photo has the 2014 15″ 2.2GHz Retina MacBook Pro on sale for $1799.99 for a limited time. Shipping is free, and B&H charges NY sales tax only. B&H will also include free copies of... Read more
New Logitech AnyAngle Case/Stand Brings Flexi...
Logitec has announced the newest addition to its suite of tablet products — the Logitech AnyAngle. A protective case with an any-angle stand for iPad Air 2 and all iPad mini models, AnyAngle is the... Read more
Notebook PC Shipments Rise Year-Over-Year as...
According to preliminary results from the upcoming DisplaySearch Quarterly Mobile PC Shipment and Forecast Report, the global notebook PC market grew 10 percent year-over-year in Q3’14 to 49.4... Read more

Jobs Board

*Apple* Solutions Consultant (ASC)- Retail S...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Project Manager, *Apple* Financial Services...
**Job Summary** Apple Financial Services (AFS) offers consumers, businesses and educational institutions ways to finance Apple purchases. We work with national and Read more
*Apple* Store Leader Program - College Gradu...
Job Description: Job Summary As an Apple Store Leader Program agent, you can continue your education as you major in the art of leadership at the Apple Store. You'll 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
Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.