Home > Uncategorized > Game Demo Tutorial #2: Hello Space

Game Demo Tutorial #2: Hello Space

April 26th, 2009

I've decided to move away from the screencasting approach for this part of the game demo tutorial. Anyways, in this part of our Cocos2d Game Demo tutorial we will be extending the code we developed in Hello World to include some images and movement. By the end of this you will end up with a space background and a moving spaceship that can fire lasers.

The first thing you want to do is to add the art resources for this project. You can find them with the source code (here). Within the project view in X-Code you want to locate the Resources folder, which should be on the the left side of the screen. Right click on Resources, and choose Add->Existing Files...

In the pop-up window select all of the art files included with this source code for the project. Next, go to GameScene.m. We're going to get rid of our label that we used previously, so change the code from this:

-(id) init {
	self = [super init];
	if(self != nil) {
		Label *test = [Label labelWithString:@"Hello World" fontName: @"Helvetica" fontSize: 24];
		test.position = cpv(160, 240);
		[self addChild: test];
	}
	return self;
}

To this:

-(id) init {
	self = [super init];
	if(self != nil) {
	//Add the background:
		Sprite *bg = [[Sprite spriteWithFile:@"bg.png"] retain];
		bg.position = cpv(160,240);
		[self addChild: bg];
 
	}
	return self;
}

This new code should replace the Hello World text with a background image of outer space. Now we're going to add a ship and make it move. So let's update that init method to this:

-(id) init {
	self = [super init];
	if(self != nil) {
	//Add the background:
		Sprite *bg = [[Sprite spriteWithFile:@"bg.png"] retain];
		bg.position = cpv(160,240);
		[self addChild: bg];
 
//Add the ship:
		ship = [[Sprite spriteWithFile:@"ship.png"] retain];
		ship.position = cpv(50,50);
		[self addChild: ship];
 
		//Make it move:
		[self schedule: @selector(moveShip:)];
 
	}
	return self;
}

We loaded another Sprite, called ship, and we set up a scheduler. Schedulers are a nice function provided by Cocos2D that allow you to call a given method over and over again. You can also pass an argument to it called interval to specify how often it should be called. If you don't specify this argument it will be called as often as possible.

After your init method you need to add the moveShip method. The code is as follows:

-(void) moveShip: (ccTime) dt
{
	t += dt; //Specify ccTime t in header
	ship.position = cpv(120*sin(t)+160, 50);
}

Like I mentioned in the comment above, you'll need to switch back to the header for GameScene and then within your GameLayer add a property for ccTime t and Sprite * ship.

Hopefully if you run this code you will see a ship oscillating back and forth over a space background.

Next we want to set up Chipmunk for use in our game. First, go to GameScene.m and after your import statement and before @implementation GameScene add the following C function:

static void
eachShape(void *ptr, void* unused)
{
	cpShape *shape = (cpShape*) ptr;
	Sprite *sprite = shape->data;
	if( sprite ) {
		cpBody *body = shape->body;
		[sprite setPosition: cpv( body->p.x, body->p.y)];
		[sprite setRotation: (float) CC_RADIANS_TO_DEGREES( -body->a )];
	}
}

This function is used to update our physics bodies. Next go to your init method in GameLayer and add this code after the scheduler we added earlier:

//Initialize Chipmunk:
		cpInitChipmunk();
 
		space = cpSpaceNew();
		cpSpaceResizeStaticHash(space, 400.0f, 40);
		cpSpaceResizeActiveHash(space, 100, 600);
 
		space->gravity = cpv(10, 0);
		space->elasticIterations = space->iterations;
 
		//Update Chipmunk
		[self schedule: @selector(step:)];

As you can see we scheduled another method to called, named step. Step will be used to run our physics simulation. We can go ahead and add this method to GameLayer:

-(void) step: (ccTime) delta
{
	int steps = 2;
	cpFloat dt = delta/(cpFloat)steps;
 
	for(int i=0; iactiveShapes, &eachShape, nil);
	cpSpaceHashEach(space->staticShapes, &eachShape, nil);
}

Now, we're going to create bullets for our ship to shoot. Since we want the bullets to be updated by Chipmunk we're going to have create a wrapper object for something called cpBody. cpBody is the Chipmunk structure for modeling our objects in the simulation space. Since cpBody is not an object we cannot store it in an NSArray or NSMutableArray, but if we wrap it, we can store it.

To get started writing the bullet wrapper click on the Classes folder, right click, and select Add->New File. In the prompt, choose NSObject subclass. Click Next and name the class Bullet.

The bullet header file is as follows:

#import
#import "chipmunk.h"
 
@interface Bullet : NSObject {
	cpBody *bulletBody;
	bool ready;
}
-(id) initWithCPBody: (cpBody *) bodyIn;
-(void) fireFromX: (float) x y:(float)y;
-(float) getY;
-(void) resetPosition;
 
@property(readwrite) bool ready;
@end

The implementation for bullet is as follows:

#import "Bullet.h"
 
@implementation Bullet
-(id) initWithCPBody: (cpBody *) bodyIn
{
	self = [super init];
	if(self != nil){
		bulletBody = bodyIn;
		ready = YES;
	}
 
	return self;
}
-(void) fireFromX: (float) x y:(float)y
{
	bulletBody->p = cpv(x,y);
	bulletBody->v = cpv(0, 500);
}
-(float) getY
{
	return bulletBody->p.y;
}
-(void) resetPosition
{
	bulletBody->p = cpv(-50,-50);
	bulletBody->v = cpv(0,0);
}
@synthesize ready;
@end

Now, switch to GameScene.h and add the following property to GameLayer:

NSMutableArray *bullets;

This NSMutableArray will be used to manage the bullets our ship will fire. We will use it to create a seemingly infinite stream of bullets from only 10 actually instantiations of our Bullet class.

Switch over to the GameScene implementation file and scroll down to GameLayer's init method. We're going to add some code to manage our bullets. After the schedule call to step add the following code:

//Bullet Manager, loads 10 bullets
		bullets = [[NSMutableArray alloc] init];
		for(int i = 0; i < 10; i++)
		{
			Bullet *b = [[Bullet alloc] initWithCPBody:[self makeBulletX:-50 y:-50]];
			[bullets addObject: b];
			[b release];
		}

This code sets up the NSMutableArray we declared in the header and creates ten instances of our Bullet class.

Next we want to be able to accept a touch to use to fire the ship's bullets. After the code above add the following line:

//Make it shoot:
		isTouchEnabled = YES;

Now we need to implement that method that will listen for a touch. In GameLayer, somewhere after our init method, add the following method:

- (BOOL)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
	for(Bullet *b in bullets)
	{
		if([b ready])
		{
			[b fireFromX:ship.position.x y:ship.position.y+12];
			[b setReady: NO];
			break;
		}
	}
 
	return kEventHandled;
}

ccTouchesBegan is the method that Cocos2D uses to alert you of touches. There are also similar methods for TouchesMoved and TouchesEnded. In the above code we are iterating through our bullets array, looking for the first ready bullet. If a ready bullet is found, we shoot it, set it to not being ready, and break the loop so no more bullets are fired. After our loop we return kEventHandled, this tells Cocos2d that the Touch event was handled.

Now we need to add some code that will keep track of our bullets and reset them when they go off screen. Add the following method to GameLayer to do just that:

-(void) updateBullets
{
	for(Bullet *b in bullets)
	{
		if([b getY] > 500)
		{
			[b resetPosition];
			[b setReady: YES];
			break;
		}
	}
}

In order for this method to be called we will go back to the steps method that we created for Chipmunk. At the very end of the method add this line:

[self updateBullets];

And that's it. Hopefully if you build and run the code you have create you will now see a spaceship oscillating over a background of stars. If you touch the screen you should see some bullets being fired from the ship. If you come across any problems just check out the following source code, it works: Game Demo Tutorial: Entry 2

rjett Uncategorized , ,

  1. May 2nd, 2009 at 02:44 | #1

    Hi Ronald,
    Awesome set of tutorials, thanks for putting them together. Just some notes I needed to get this one running (mostly fixes found in your code download):

    When adding the property ccTime t, Sprite *ship also needs to be declared.

    In GameScene.m, the definition of step: should read:
    for (int i = 0; i activeShapes, &eachShape, nil);
    cpSpaceHashEach(space->staticShapes, &eachShape, nil);

    The #import lines of Bullet.h should read:

    #import
    #import “chipmunk.h”

    In GameScene.m, the Bullet Manager code should read:

    for (int i = 0; i p = cpv(x, y);
    laserBod->v = cpv(0, 0);

    cpSpaceAddBody(space, laserBod);

    cpShape *laserShape = cpPolyShapeNew(laserBod, 4, verts, cpvzero);
    laserShape->e = 0.9f;
    laserShape->u = 0.9f;
    laserShape->data = laser;
    cpSpaceAddShape(space, laserShape);

    return laserBod;
    }

    In GameScene.h, add the following:

    #import “Bullet.h”

    Then in GameLayer a property:

    cpSpace *space;

    and:

    -(cpBody *) makeBulletX: (float) x y:(float) y;
    -(void) updateBullets;

    All the best,
    James

  2. May 2nd, 2009 at 02:46 | #2

    Ok, well that didn’t paste in exactly as planned :(

    Can’t find your address on the site, but drop me an email and I’ll send through my notes.

    Cheers,
    James

  3. slushe
    May 3rd, 2009 at 13:10 | #3

    When I tried this it says ship and t are both undeclared…

  4. rjett
    May 3rd, 2009 at 21:52 | #4

    In GameScene.h, within the GameLayer class, you need to add the properties: ccTime t and Sprite * ship. Sorry this wasn’t clearer.

  5. May 8th, 2009 at 13:46 | #5

    I think moving away from the screencast is not good. A better solution is providing both (if you have the time). Some concepts are better explained when “spelled out”. I’d love to see some more of your screencasts and I believe that mistakes are part of a learning curve and it feels so close when you were trying to figure out where things have gone wrong. Good job!

  6. May 15th, 2009 at 12:47 | #6

    Ronald, thanks a ton for taking the time to share such a great tutorial. It’s been immensely helpful.

    A quick question: can you explain your logic in the -step: method? I don’t understand why we want to call cpSpaceStep() twice for each game step.

    Thanks again!

  7. Austin
    May 18th, 2009 at 19:17 | #7

    Does anyone know how to make it so that you have to touch a specific object (button) to shoot instead of the whole screen. I already have my button on the screen but I’m having trouble what to do next.

  8. rjett
    June 3rd, 2009 at 21:55 | #8

    Sorry I don’t understand.. where do you see cpSpaceStep()?

  9. John D.
    June 14th, 2009 at 16:32 | #9

    Thanks for the Tutorials, but you should modify the post to include the changes to GameScene.h It takes someone to read the comments, or know what they are missing, to work with the tutorial.

    Thanks again.

  10. July 31st, 2009 at 02:51 | #10

    What a great trio of tutorials. The screen cast was a pain for me, so sticking with the text n graphics gets my vote.

    In the tut, you seem to miss out the definition of [-(cpBody *) makeBulletX: (float) x y:(float) y;] – i had to dive into the source code to fix that bug.

    There are a few other missing bits too, but perhaps fixing those is a good exercise for the programmer huh!

  11. izzy
    August 7th, 2009 at 03:28 | #11

    Hi, i’m a total newbie to programming with obj-C and this tutorial really helped me get going on my game project. One question though.

    if([b ready])
    {
    [b fireFromX:ship.position.x y:ship.position.y+12];
    [b setReady: NO];
    break;
    }

    I see you use setReady to set the bool “ready” in the bullet object.

    what is setReady? is it some method you defined yourself? how are setReady and ready connected?? or is it a native of cocos2d, cocoa or objective-c?

  12. September 2nd, 2009 at 00:54 | #12

    Hi Ronald,

    I am a student of Fukui-NCT in Japan.

    Your tutorials are very helpful.
    So, I translated it from English to Japanese and introduced it to Japanese Developer. (here : http://profo.jp/wiki/index.php?hello_space)

    Sorry for ex post facto report.
    If you have any problem, please let me.

    Thank you.

  13. rjett
    September 6th, 2009 at 00:41 | #13

    It’s a method I defined. I haven’t looked at the code in a long time, but if I remember right it just set the ready boolean for that Bullet instance.

  14. rjett
    September 6th, 2009 at 00:51 | #14

    Nice work! :)

  1. No trackbacks yet.