How To Make A Side-Scrolling Beat Em Up Game Like Scott Pilgrim with Cocos2D – Part 2
This is a post by iOS Tutorial Team Member Allen Tan, an iOS developer and co-founder at White Widget. You can also find him on Google+ and Twitter. Welcome back to the second (and final) part of our Beat Em Up game tutorial series! If you followed the first part, then you’ve already created the […] By Allen Tan.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How To Make A Side-Scrolling Beat Em Up Game Like Scott Pilgrim with Cocos2D – Part 2
40 mins
Bring on the Droids
While being able to walk around is fun, walking around a huge empty corridor can become pretty boring, pretty fast for your hero. It's up to you to give him some company. :]
You already have a base model for the sprite: ActionSprite. You can reuse that to make computer-controlled characters for the game.
This part of the tutorial will move rather quickly, since it's very similar to how the hero was created.
Hit Command-N and create a new file with the iOS\Cocos2D v2.x\CCNode Class template. Make it a subclass of ActionSprite and name it Robot.
Go to Robot.h and add this line at the top:
#import "ActionSprite.h"
Switch to Robot.m and add this method:
-(id)init {
if ((self = [super initWithSpriteFrameName:@"robot_idle_00.png"])) {
int i;
//idle animation
CCArray *idleFrames = [CCArray arrayWithCapacity:5];
for (i = 0; i < 5; i++) {
CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:@"robot_idle_%02d.png", i]];
[idleFrames addObject:frame];
}
CCAnimation *idleAnimation = [CCAnimation animationWithSpriteFrames:[idleFrames getNSArray] delay:1.0/12.0];
self.idleAction = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:idleAnimation]];
//attack animation
CCArray *attackFrames = [CCArray arrayWithCapacity:5];
for (i = 0; i < 5; i++) {
CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:@"robot_attack_%02d.png", i]];
[attackFrames addObject:frame];
}
CCAnimation *attackAnimation = [CCAnimation animationWithSpriteFrames:[attackFrames getNSArray] delay:1.0/24.0];
self.attackAction = [CCSequence actions:[CCAnimate actionWithAnimation:attackAnimation], [CCCallFunc actionWithTarget:self selector:@selector(idle)], nil];
//walk animation
CCArray *walkFrames = [CCArray arrayWithCapacity:6];
for (i = 0; i < 6; i++) {
CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:@"robot_walk_%02d.png", i]];
[walkFrames addObject:frame];
}
CCAnimation *walkAnimation = [CCAnimation animationWithSpriteFrames:[walkFrames getNSArray] delay:1.0/12.0];
self.walkAction = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:walkAnimation]];
self.walkSpeed = 80;
self.centerToBottom = 39.0;
self.centerToSides = 29.0;
self.hitPoints = 100;
self.damage = 10;
}
return self;
}
As with the hero, the above code creates a robot with three actions: idle, attack and walk. It also fills in the two measurement values: centerToBottom and centerToSides.
Notice that the robot's attributes are weaker compared to the hero. This is logical, since otherwise your hero will never be able to defeat the robots. :]
Let's jump straight to filling the game with a lot of these robots. You want a brawl, don’t you?
Switch to GameLayer.h and add the following property:
@property(nonatomic,strong)CCArray *robots;
Now switch to GameLayer.m and do the following:
//add to top of file
#import "Robot.h"
//add inside if ((self = [super init])) right after [self initHero];
[self initRobots];
//add this method inside the @implementation
-(void)initRobots {
int robotCount = 50;
self.robots = [[CCArray alloc] initWithCapacity:robotCount];
for (int i = 0; i < robotCount; i++) {
Robot *robot = [Robot node];
[_actors addChild:robot];
[_robots addObject:robot];
int minX = SCREEN.width + robot.centerToSides;
int maxX = _tileMap.mapSize.width * _tileMap.tileSize.width - robot.centerToSides;
int minY = robot.centerToBottom;
int maxY = 3 * _tileMap.tileSize.height + robot.centerToBottom;
robot.scaleX = -1;
robot.position = ccp(random_range(minX, maxX), random_range(minY, maxY));
robot.desiredPosition = robot.position;
[robot idle];
}
}
You just did the following:
- Created an array of 50 robots, and added them to the batch node.
- Used the random functions you created in Defines.h to randomly place the 50 robots across the tile map's floors. You also made sure that no robots are placed at the starting point by making the minimum random value bigger than the screen's width.
- Made each robot perform its idle action.
Build and run, and walk the hero around until you see robots on the map.
Try walking around a bit in an area with robots, and you'll notice that there's something really wrong with how the robots are drawn. According to the current perspective, if the hero is below a robot, then he should be drawn in front of the robot, not the other way around.
For things to be drawn in the right sequence, you need to explicitly tell the game which objects to draw first. You should already know how to do this – think back to how you made the tile map appear behind everything else.
Figured it out? If your answer was z-order/z-value, then you're 100% correct!
To refresh your memory, take a look at how the sprite batch node and tile map were added to the scene in GameLayer.m:
//you set the z-value of tileMap lower than actors, so anything drawn in actors appears in front of tileMap
[self addChild:_actors z:-5]; //this is your CCSpriteBatchNode
[self addChild:_tileMap z:-6]; //this is your CCTMXTiledMap
Now take a look at how the hero and the robots were added:
[_actors addChild:_hero];
[_actors addChild:robot];
There are two differences:
- The SpriteBatchNode and the CCTMXTiledMap were both added as direct children of GameLayer, while the hero and the robots were added as children of the CCSpriteBatchNode. GameLayer is responsible for drawing the CCSpriteBatchNode and CCTMXTiledMap in the proper sequence, while it is the CCSpriteBatchNode's responsibility to draw the hero and the robots in the correct sequence within itself.
- You didn't explicitly assign a z-value to the hero and the robots. By default, the object added last will have a higher z-value than the previous objects – and that's why all the robots are drawn in front of the hero.
To fix the broken drawing sequence, you need to handle the z-order dynamically. Every time a sprite moves across the screen vertically, its z-order should be changed. The higher a sprite is on the screen, the lower its z-value should be.
Still in GameLayer.m, do the following:
//add this method inside the @implementation
-(void)reorderActors {
ActionSprite *sprite;
CCARRAY_FOREACH(_actors.children, sprite) {
[_actors reorderChild:sprite z:(_tileMap.mapSize.height * _tileMap.tileSize.height) - sprite.position.y];
}
}
//add this method inside update, right after [self updatePositions]
[self reorderActors];
Every time the sprite positions are updated, this method makes the CCSpriteBatchNode reorder the z-value of each of its children, based on how far the child is from the bottom of the map. As the child goes higher, the resulting z-value goes down.
Note: Each CCNode has its own property named zOrder, but changing this won't give you the same effect as calling reorderChild from the parent. It's the parent's responsibility to draw its children in order, so it should also be the parent's responsibility to set the order of its children.
Note: Each CCNode has its own property named zOrder, but changing this won't give you the same effect as calling reorderChild from the parent. It's the parent's responsibility to draw its children in order, so it should also be the parent's responsibility to set the order of its children.
Build and run, and the drawing sequence should now be correct.