BIT-101 [2003-2017]

Cocos2d – Part 3


Let’s make things move.

If you’ve read my books or seen me speak, you’ll know I love balls. By which, I mean that I love to make circular objects for the purpose of demonstrating basic motion concepts. So I made a ball named “Ball.png” and added this to my Resources.

ball

Now I’m thinking that this ball should be in its own layer. So I created a MotionLayer class. Interface:

[c]#import <foundation/Foundation.h>
#import “Layer.h”

@interface MotionLayer : Layer {

}

@end[/c]

We’ll get to the implementation soon. But first lets add the layer to the MainScene:

[c]#import “MainScene.h”
#import “Sprite.h”
#import “cocos2d.h”
#import “PictureLayer.h”
#import “MotionLayer.h”

@implementation MainScene

– (id) init
{
self = [super init];
if (self != nil) {
Sprite *background = [Sprite spriteWithFile:@“background.png”];
background.position = ccp(240, 160);
[self addChild:background];

PictureLayer *pictureLayer = [PictureLayer node];;
[self addChild:pictureLayer];

MotionLayer *motionLayer = [MotionLayer node];
[self addChild:motionLayer];
}
return self;
}

@end[/c]

Simple enough. The project should compile at this point, but won’t do anything different, since the MotionLayer doesn’t add anything to the picture. Let’s move on to the ball itself. We could just make a new sprite with the ball image and move that around. But let’s actually give the ball some behavior of its own. For that, we’ll need a Ball class. Create a new class Named “Ball” that extends Sprite, and add the following to its interface:

[c]#import <foundation/Foundation.h>
#import “Sprite.h”

@interface Ball : Sprite {
float vx;
float vy;
float gravity;
float bounce;
}

@property float vx;
@property float vy;

– (void)update:(ccTime)dt;

@end[/c]

Now, if you’ve read Making Things Move, or seen my talks or read my tutorials, or even if you’re just wicked smart, you’ll see where this is going. vx and vy are velocity on the x and y axes. bounce is going to be used to reverse velocity when the ball hits an edge, and gravity is going to pull the ball “down”. vx and vy are properties so they can be set from outside, and there’s an update method, which we’ll get to shortly. Here’s the implementation:

[c]#import “Ball.h”
#import “cocos2d.h”

@implementation Ball

@synthesize vx;
@synthesize vy;

– (id) init
{
self = [super init];
if (self != nil) {
gravity = -20.0;
bounce = -0.7;
Sprite *ball = [Sprite spriteWithFile:@“Ball.png”];
[self addChild:ball];
[self schedule:@selector(update:)];
}
return self;
}

– (void)update:(ccTime)dt
{
float x = self.position.x;
float y = self.position.y;
vy += gravity;
x += vx * dt;
y += vy * dt;
if(x + 20 > 480)
{
x = 460;
vx *= bounce;
}
else if(x < 20) { x = 20; vx *= bounce; } if(y + 20 > 320)
{
y = 300;
vy *= bounce;
}
else if(y < 20) { y = 20; vy *= bounce; } self.position = ccp(x, y); } @end[/c] In the init method we are setting values for gravity and bounce, and calling this schedule method. schedule is kind of like enterFrame and a timer, all in one. If you just call schedule with a selector, that selector will be called repeatedly, at a rate set by the Director. This defaults to 60 times per second. There is an alternate version of the method with the signature: schedule:(SEL)s interval:(ccTime)seconds This allows you to set your own interval rate for the selector to get called. Here we are passing in our update method. Now let’s look at the signature of that again: - (void)update:(ccTime)dt You’ll see that it gets passed a value called dt, of type ccTime, which is just a typedef for float. This represents the amount of time that has elapsed since the last update. This is very useful for accurate timing of motion. Multiply your velocities by that value each time, and if the frame rate lags for some reason, your motion will continue along at the same perceived speed. This is because a lower frame rate will result in a higher elapsed time between frames and make each frame’s velocity exactly that much faster to compensate. The other side of this is that you have to change the size of the numbers you use for velocity. If you are just adding velocity straight to position on each frame, then your velocity is basically in terms of “pixels per frame”. If you are multiplying it by the elapsed time before adding it to position, then you are dealing with “pixels per second”. 5 pixels per frame is the same as 300 pixels per second at 60 fps (60 x 5 = 300). I’m not going to say any more about the update code. If you don’t understand it, buy my book. Actually, just buy my book anyway. 🙂 Though I should note that true to OpenGL, coco2d’s y coordinates are upside down from what you might be used to in Flash or even just plain Cocoa. In other words, positive y is up, negative y is down. The orgin is in the lower left corner, not the upper left. In other words, it’s like regular Cartesian Coords. OK… now back to the MotionLayer where we create this ball. Here’s the implementation, finally: [c]- (id) init { self = [super init]; if (self != nil) { Ball *ball = [Ball node]; ball.position = ccp(200, 200); ball.vx = 300; ball.vy = 200; [self addChild:ball]; } return self; } @end[/c] All we are doing here is creating a ball, positioning it, setting its initial velocity, and adding it to the layer. Run this baby and behold the bouncing ball! Now we talked a bit about frame rate, so I want to show you something cool. Go back to the main app delegate, and after all the other Director setup calls, add this line: [[Director sharedDirector] setDisplayFPS:YES]; And look what we have here:fps

A nice little fps meter down in the lower left corner! Sweet. And that explains why we needed to add that fps_image.png to the project. This will show you the exact frame rate of your game at any given time. If you start adding too much motion and too many objects and you feel it’s getting a little choppy, this will tell you without guesswork.

Also, what if you don’t want to run at the default 60 fps? You set that in the Director as well. Say you wanted 30 fps. Add this line:

[[Director sharedDirector] setAnimationInterval:1.0 / 30.0];

And you’ll see in the fps meter, that’s what it’s running at.

Another neat thing about using schedule instead of NSTimer or some other animation method, is that you can now control ALL animation in the game directly through the Director, via the pause, resume, stopAnimation, and startAnimation methods. I’ll just show you the declarations for those methods to give you and idea of what they do:

[c]/** Pauses the running scene.
The running scene will be _drawed_ but all scheduled timers will be paused
While paused, the draw rate will be 4 FPS to reduce CPU consuption
*/
-(void) pause;

/** Resumes the paused scene
The scheduled timers will be activated again.
The “delta time” will be 0 (as if the game wasn’t paused)
*/
-(void) resume;

/** Stops the animation. Nothing will be drawn. The main loop won’t be triggered anymore.
If you wan’t to pause your animation call [pause] instead.
*/
-(void) stopAnimation;

/** The main loop is triggered again.
Call this function only if [stopAnimation] was called earlier
@warning Dont’ call this function to start the main loop. To run the main loop call runWithScene
*/
-(void) startAnimation;[/c]

Neato, huh?

Well that covers lesson 3. And that really is it for today. I’ll see what I can dig up for tomorrow.

« Previous Post
Next Post »