Game Demo Tutorial #3: Collisions & Particle Systems
In this entry I am going to cover using Chipmunk's collision pair function. This function is used to set up a collision test between two objects. If Chipmunk detects that two predefined objects have collided, the callback function that was passed to the collision pair function gets called. To show this we are going to extend the code from the last tutorial, adding a rock and a particle system.
The first thing we are going to do is add rock.png to our resources. Rock.png can be found in the source code for this entry. Next, we're going to add a method called makeSpaceRock to our GameLayer class within GameScene.m. Here is the code you need to add:
-(void) makeSpaceRockX: (float) x y:(float)y { Sprite *rock = [[Sprite spriteWithFile:@"rock.png"] retain]; rock.position = cpv(x,y); [self addChild: rock]; cpVect verts[] = { cpv(-54,-43), cpv(-54, 43), cpv(54, 43), cpv(54,-43), }; cpBody *rockBody = cpBodyNew(200.0f, INFINITY); rockBody->p = cpv(x, y); rockBody->v = cpv(0, 0); cpSpaceAddBody(space, rockBody); cpShape * rockShape = cpPolyShapeNew(rockBody, 4, verts, cpvzero); rockShape->e = 0.9f; rockShape->u = 0.9f; rockShape->data = rock; rockShape->collision_type = 0; //New! cpSpaceAddShape(space, rockShape); cpSpaceAddCollisionPairFunc(space, 0, 1, &bulletCollision, self); //new! }
As you can see this method contains to lines of code we haven't seen before. We are setting a property called collision_type on rockShape and then calling cpSpaceAddCollisionPairFunc. collision_type is how we identify a particular object for the collision pair function. In order for everything to work properly you'll need to start numbering your collision_types from 0. rockShape will be numbered with a 0 and our bullets will be numbered with a 1. cpSpaceAddCollisionPairFunc takes the following arguments: the current simulation space, object A (as specified by collision_type), object B, C callback function to be called when a collision is detected, and then a data parameter. The last parameter is optional and could be left as null, but you can pass self in to get access to your GameLayer object during runtime.
Next we need to modify our method that creates bullets, makeBulletX. Here is the line you need to add:
laserShape->data = laser; laserShape->collision_type = 1; //this is new! cpSpaceAddShape(space, laserShape);
Now we need to see up our callback function, which we declared as bulletCollision in cpSpaceAddCollisionPairFunc. This C function will be located in GameScene.m, but outside of our classes, just like the function eachShape(void *ptr, void* unused). After the eachShape function add the following code:
static int bulletCollision(cpShape *a, cpShape *b, cpContact *contacts, int numContacts, cpFloat normal_coef, void *data) { NSLog(@"Collision Detected"); return 0; }
Now we need to add a rock to the screen to test out our collision. In the init method of GameLayer add the following line:
[self makeSpaceRockX:200 y:200];
If you build and run the application you should now see a rock on screen. You should be able to shoot that rock with laser bullets and have the collision show up in the console. Hopefully everything worked for you. If not you can always grab the fully working source code at the end of this entry.
Next up we are going to flesh out our bullet collisions a little bit more. We want the rock to disappear when a bullet collides with it and we want an explosion to occur. First we want to add the following method to our GameLayer class:
-(void) createExplosionX: (float) x y: (float) y { ParticleSystem *emitter = [ParticleExplosion node]; emitter.position = cpv(x,y); [self addChild: emitter]; }
Now we want to go back to our collision callback function (bulletCollision) and modify its code to this:
GameLayer *game = (GameLayer*) data; [game createExplosionX:200 y:200]; return 0;
Now if you run the application you should be able to shoot the rock and generate an explosion. This is great, but as you can see the explosion doesn't really look that great. It runs a little slow and the colors are wacky. To fix this we're going to create our own particle system. Right click on the Classes folder in X-Code and select Add->New File. Choose NSObject subclass and name it RockExplosion. Change RockExplosion.h to this:
#import "PointParticleSystem.h" #import "QuadParticleSystem.h" @interface RockExplosion : PointParticleSystem { } @end
As you can see we are going to be extending Cocos2D's PointParticleSystem class.
In the implementation file add the following code:
#import "RockExplosion.h" #import "TextureMgr.h" #import "Director.h" @implementation RockExplosion -(id) init { return [self initWithTotalParticles:2]; } -(id) initWithTotalParticles:(int)p { if( !(self=[super initWithTotalParticles:p]) ) return nil; // duration duration = 0.1f; // gravity gravity.x = 10; gravity.y = 10; // angle angle = 90; angleVar = 360; // speed of particles speed = 200; speedVar = 40; // radial radialAccel = 0; radialAccelVar = 0; // tagential tangentialAccel = 0; tangentialAccelVar = 0; // emitter position position.x = 160; position.y = 240; posVar.x = 0; posVar.y = 0; // life of particles life = 10.0f; lifeVar = 2; // size, in pixels startSize = 40.0f; startSizeVar = 20.0f; // emits per second emissionRate = totalParticles/duration; // color of particles startColor.r = 0.99f; startColor.g = 0.99f; startColor.b = 0.99f; startColor.a = 1.0f; startColorVar.r = 0.0f; startColorVar.g = 0.0f; startColorVar.b = 0.0f; startColorVar.a = 0.0f; endColor.r = 0.0f; endColor.g = 0.0f; endColor.b = 0.0f; endColor.a = 1.0f; endColorVar.r = 0.0f; endColorVar.g = 0.0f; endColorVar.b = 0.0f; endColorVar.a = 0.0f; self.texture = [[TextureMgr sharedTextureMgr] addImage: @"rock.png"]; // additive blendAdditive = NO; return self; } @end
Now we need to go back and modify our createExplosion method in GameLayer. The ParticleSystem should now be created from RockExplosion instead of ParticleExplosion. The line of code should be:
ParticleSystem *emitter = [RockExplosion node];
Also, you will need to go into GameScene.h and add #import RockExplosion.h.
Great! Now we have a nice little explosion when the lasers hit our rock. Let's go back and actually get rid of the space rock when a laser hits it. In GameScene.m, change the code inside the bulletCollision function to this:
GameLayer *game = (GameLayer*) data; a->body->p = cpv(800,800); b->body->p = cpv(800,800); [game createExplosionX:200 y:200]; return 0;
This code will move the space rock and bullet that hit it off screen, leaving only the exploding rock chunks. Compile and run your code, looks cool, huh?
SOURCE CODE: Game Demo Tutorial Entry 3 Source Code