How To Make A Side-Scrolling Beat Em Up Game Like Scott Pilgrim with Cocos2D – Part 1

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. In this two-part tutorial series, you’ll learn how to make a cool Beat Em Up Game for the iPhone, using Cocos2D 2.0! Lately, Beat Em Up games […] By Allen Tan.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Loading the Tiled Map

It’s time to put some content into the scene. You will start with the item that appears behind everything else: the Tiled Map. I’m going to assume you already know your way around creating Tiled Maps. If this isn’t the case, please refer to the How to Make a Tile-Based Game with Cocos2D tutorial before proceeding.

Ready? First, download the resource kit for the tutorial from here and extract it, which includes some lovely pixel art made by Hunter Russell!

Drag the Sprites folder into the Resources group of your project. Make sure that Copy items into destination group’s folder is checked, Create groups for any added folders is selected, and that the PompaDroid target is selected under Add to targets. Tap Finish when you’re sure everything is set correctly.

Xcode

The Sprites folder contains several files, but you’ll mainly be working with two files:

  • pd_tiles.png: the tile piece images used in the tiled map.
  • pd_tilemap.tmx: the actual tile map loaded into Cocos2D.

Open up pd_tilemap.tmx using Tiled, and you will see the entire map for the game.

Tiled Map Editor

There are a few important things to keep in mind here:

  1. There are two layers: Wall and Floor. Turn off the Opacity for each layer to see what each one is composed of, and you will notice that the fourth row of tiles from the bottom is comprised of tiles from both layers. Layers can be useful for a lot of things, and in this case, they’re being used to create variations of tiles by combining two tiles in one spot.
  2. Each tile is 32×32 in size.
  3. The walkable floor tiles are in the first 3 rows from the bottom.

Let’s get coding again. Go to GameLayer.h and add the following variable inside the curly braces after the @interface:

CCTMXTiledMap *_tileMap;

Switch to GameLayer.m and add these methods inside the @implementation:

-(id)init {
    if ((self = [super init])) {
        [self initTileMap];
    }
    return self;
}

-(void)initTileMap {
    _tileMap = [CCTMXTiledMap tiledMapWithTMXFile:@"pd_tilemap.tmx"];
    for (CCTMXLayer *child in [_tileMap children]) {
        [[child texture] setAliasTexParameters];
    }
    [self addChild:_tileMap z:-6];
}

initTileMap creates the tiled map based on the .tmx file you provided. It then iterates over all members of the tiled map and calls setAliasTexParameters. This method ensures that anti-aliasing is turned off for all the textures used in the tiled map, so that you can retain the pixel-like style of the textures even when the map is scaled. For more info on this, please refer to the Introduction to Pixel Art for Games tutorial.

Finally, you add the tile map to the layer. Keep in mind that the z-value of the tiled map is -6. You want to make sure that the tiled map is always behind everything, so from now on, there shouldn’t be any children added to GameLayer with a z-value lower than -6.

Before you build and run, visit AppDelegate.m and search for this line:

if( ! [director_ enableRetinaDisplay:YES] )

And change it to this:

if( ! [director_ enableRetinaDisplay:NO] )

You’re disabling Retina support, since there isn’t a separate set of images for the Retina display. This doesn’t mean that the game won’t work on Retina devices, but rather, it will simply scale up all images you provided for bigger resolutions. Without this, the tiled map would have just taken up half of the screen on Retina devices.

Now, build and run, and you should see the tiled map displayed on the screen:

The Tiled Map

Creating the Pompadoured Hero

In most 2D side-scrolling games, characters have different animations to represent different types of actions. This presents us with a problem: how do you know which animation to play at which time?

For this Beat Em Up game, you are going to implement one possible solution to this problem using a state machine.

A state machine, as the name may or may not imply, is simply something that changes behavior by switching states. A single state machine can have only one state at a time, but it can transition from one state to another.

To understand this better, imagine the base character for the game, and list the things he can do:

  1. Stay Put/Idle
  2. Walk
  3. Punch

Then list the requirements for each:

  1. If he is idle, then he cannot be walking or punching.
  2. If he is walking, then he cannot be idle or punching.
  3. If he is punching, then he cannot be idle or walking.

Expand this list to include two more actions: getting hurt, and dying, and you have five actions in total:

Action States

Given the above, you could say that the character, if he were a state machine, would switch between an Idle State, a Walking State, a Punching State, a Hurt State, and a Dead State.

To have a complete flow between the states, each state should have a requirement and a result. The walking state cannot suddenly transition to the dead state, for example, since your hero must first go through being hurt in order to reach the state of being dead.

The result of each state also helps solve the problem presented earlier. For the game’s implementation, when you switch between states, the character will also switch between animations.

OK, that’s enough theory for now. It’s time to continue coding!

The first step is to create a base template for a sprite that is able to switch between actions.

Note: For the purposes of this tutorial, since each action represents a state, the words action and state will be used interchangeably.

Hit Command-N and create a new file with the iOS\Cocos2D v2.x\CCNode Class template. Make it a subclass of CCSprite, and name it ActionSprite.

Go to ActionSprite.h, and add the following before the @end:

//actions
@property(nonatomic,strong)id idleAction;
@property(nonatomic,strong)id attackAction;
@property(nonatomic,strong)id walkAction;
@property(nonatomic,strong)id hurtAction;
@property(nonatomic,strong)id knockedOutAction;

//states
@property(nonatomic,assign)ActionState actionState;

//attributes
@property(nonatomic,assign)float walkSpeed;
@property(nonatomic,assign)float hitPoints;
@property(nonatomic,assign)float damage;

//movement
@property(nonatomic,assign)CGPoint velocity;
@property(nonatomic,assign)CGPoint desiredPosition;

//measurements
@property(nonatomic,assign)float centerToSides;
@property(nonatomic,assign)float centerToBottom;

//action methods
-(void)idle;
-(void)attack;
-(void)hurtWithDamage:(float)damage;
-(void)knockout;
-(void)walkWithDirection:(CGPoint)direction;

//scheduled methods
-(void)update:(ccTime)dt;

The above code declares the basic variables and methods you need to create an ActionSprite. Separated by section, these are:

  • Actions: these are the CCActions (Cocos2D actions) to be executed for each state. The CCActions will be a combination of executing sprite animations and other events triggered when the character switches states.
  • States: holds the current action/state of the sprite, using a type named ActionState that you will define soon.
  • Attributes: contains values for the sprite’s walk speed for movement, hit/health points for getting hurt, and damage for attacking.
  • Movement: will be used later to calculate how the sprite moves around the map.
  • Measurements: holds useful measurement values regarding the actual image of the sprite. You need these values because the sprites you will use have a canvas size that is much larger than the image contained inside.
  • Action methods: you won’t call the CCActions (from the actions section above) directly. Instead, you will be using these methods to trigger each state.
  • Scheduled methods: anything that needs to run constantly at certain intervals, such as updates to the Sprite’s position and velocity, will be put here.

Before going to ActionSprite.m, you need to define the ActionState type used above. For the sake of uniformity and ease-of-use, I personally like to keep all my definitions in one file. You will be doing just that in this tutorial.

Hit Command-N and create a new file with the iOS\C and C++\Header File template and name it Defines.

Open Defines.h, and add the following after #define PompaDroid_Defines_h:

// 1 - convenience measurements
#define SCREEN [[CCDirector sharedDirector] winSize]
#define CENTER ccp(SCREEN.width/2, SCREEN.height/2)
#define CURTIME CACurrentMediaTime()

// 2 - convenience functions
#define random_range(low,high) (arc4random()%(high-low+1))+low
#define frandom (float)arc4random()/UINT64_C(0x100000000)
#define frandom_range(low,high) ((high-low)*frandom)+low

// 3 - enumerations
typedef enum _ActionState {
    kActionStateNone = 0,
    kActionStateIdle,
    kActionStateAttack,
    kActionStateWalk,
    kActionStateHurt,
    kActionStateKnockedOut
} ActionState;

// 4 - structures
typedef struct _BoundingBox {
    CGRect actual;
    CGRect original;
} BoundingBox;

To explain briefly:

  1. These are some convenience macros so that you don’t need to type the long versions later on. For example, instead of having to type [[CCDirector sharedDirector] winSize] every time you want to check the screen’s dimensions, you just need to type SCREEN.
  2. These are a few convenience functions that return a random integer or float value, and will be useful later on.
  3. Here you define ActionState, which is an enumeration of the different states that the ActionSprite can have. These are simply integers, with values from 0 to 5, but the enumeration names make your code much more readable.
  4. Here you define a structure named BoundingBox, which will be used later for collision handling. You add it now so that you don’t need to go back to Defines.h ever again during the course of this tutorial.

You want Defines.h to be present in every file that you work on from now on, but it’s a hassle to have to enter #import “Defines.h” in each of these files. The quick solution is to include it in the pre-compiled header file.

In your Project Navigator, expand Supporting Files, open Prefix.pch, and then do the following:

//add after #import <Foundation/Foundation.h>
#import "Defines.h"

That’s it. Build and run your code, and you should see this:

PompaDroid!

No? Oh, that’s right. Just teasing. :]

You probably just got the same blank result as before, but with a few warnings about having incomplete implementations because you haven’t yet put anything in ActionSprite.m. Leave it as is for now, and skip forward to getting your hero up on the screen.

Switch to GameLayer.h and add the following instance variable:

CCSpriteBatchNode *_actors;

Now switch to GameLayer.m and add the following to init:

//add inside if ((self = [super init]))
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"pd_sprites.plist"];
        _actors = [CCSpriteBatchNode batchNodeWithFile:@"pd_sprites.pvr.ccz"];
[_actors.texture setAliasTexParameters];
[self addChild:_actors z:-5];

You load all the frames in the Sprite Sheet you added earlier into your Resources, and create a CCSpriteBatchNode out of it. This batch node will later on be the parent of all your sprites. It has a higher z-value than the CCTMXTiledMap object, since you want it to appear in front of the tiled map.

Note: Batch nodes are very effective when drawing multiple animated sprites. You can skip the usage of batch nodes if you want, but it may have an impact on performance, especially when there are a lot of sprites on the scene.

Time to create the Hero class. 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 Hero.

Add this at the top of Hero.h:

#import "ActionSprite.h"

Switch to Hero.m, and add the following inside the @implementation:

-(id)init {
    if ((self = [super initWithSpriteFrameName:@"hero_idle_00.png"])) {
        int i;
        
        //idle animation
        CCArray *idleFrames = [CCArray arrayWithCapacity:6];
        for (i = 0; i < 6; i++) {
            CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:@"hero_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]];
        
        self.centerToBottom = 39.0;
        self.centerToSides = 29.0;
        self.hitPoints = 100.0;
        self.damage = 20.0;
        self.walkSpeed = 80;
    }
    return self;
}

You create the hero character with an initial idle sprite frame, prepare a CCArray containing all of the other sprite frames belonging to the idle animation, and create the CCAction that plays this animation.

This animation will simply change the displayed sprite frame for the hero at the chosen interval, in this case 1.0/12.0, which means 12 frames per second.

After that, you place some initial values for the hero's attributes, including the measurements from the center of the sprite to the sides and bottom. To better understand what these two measurements are for, take a look at the following diagram:

Content Size

Each frame of the hero sprite was created on a 280x150 pixel canvas, but the actual hero sprite only takes up a fraction of that space. Each CCSprite has a property named contentSize, which gives you the size of the frame. It's useful for cases wherein the sprite takes up the whole frame, but very useless for this case, so it's a good idea to store these two measurements so that you can position the sprite (or the hero in this case) easily.

If you're wondering why the sprites have all that extra empty space, it's because the sprites might be drawn in different ways for each animation, and some might require more space than the others. This way, you have the extra space to draw each sprite as needed, instead of having the different sprites for an animation (or sprites from different animations) be different sizes.

Switch to GameLayer.h and add the following:

//add to top of file
#import "Hero.h"

//add inside @interface
Hero *_hero;

Next, switch to GameLayer.m and do the following:

//add inside if ((self = [super init])), right after [self addChild:_actors z:-5];
[self initHero];

//add this method inside the @implementation
-(void)initHero {
    _hero = [Hero node];
    [_actors addChild:_hero];
    _hero.position = ccp(_hero.centerToSides, 80);
    _hero.desiredPosition = _hero.position;
    [_hero idle];
}

You create an instance of Hero, add it to the batch node, and set its position. You call idle so that the hero goes to the idle state and runs the idle animation. Here you can see one use of the _centerToSides property: to place the hero at the edge of the screen to be fully visible.

But you haven't yet created the idle method. Go to ActionSprite.m and add the following inside the @implementation:

-(void)idle {
    if (_actionState != kActionStateIdle) {
        [self stopAllActions];
        [self runAction:_idleAction];
        _actionState = kActionStateIdle;
        _velocity = CGPointZero;
    }
}

The idle method can only run if the ActionSprite isn't already in the idle state. When it is triggered, it executes the idle action, changes the current action state to kActionStateIdle, and zeroes out the velocity.

Build and run, and you should now see your hero doing his idle animation:

Idle Hero

Allen Tan

Contributors

Allen Tan

Author

Over 300 content creators. Join our team.