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.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All


Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.