How To Use SpriteHelper and LevelHelper Tutorial

If you’ve ever manually laid out a level in Cocos2D, you know how painful it can be. Without tool support, you’ll find yourself guessing a position, testing to see how it looks, tweaking and testing again, and repeating over and over. Well, thanks to some tools that are starting to become available, life has become […] By .

Leave a rating/review
Save for later
Share
Create a simple game with SpriteHelper and LevelHelper!

Create a simple game with SpriteHelper and LevelHelper!

If you’ve ever manually laid out a level in Cocos2D, you know how painful it can be.

Without tool support, you’ll find yourself guessing a position, testing to see how it looks, tweaking and testing again, and repeating over and over.

Well, thanks to some tools that are starting to become available, life has become a lot easier!

In this tutorial, I’m going to show you how to use two of these tools (SpriteHelper and LevelHelper) to create a simple platformer game with Cocos2D and Box2D.

To go through this tutorial, you need a copy of LevelHelper and SpriteHelper. However, if you don’t have a copy you might like to just read this tutorial anyway to learn about the tools – and check out my personal review at the end.

You should also be familiar with the basics of using Cocos2D and Box2D. If you are new to Cocos2D or Box2D, please review some of the other other Cocos2D and Box2D tutorials on this site first.

Full disclosure: I have received a free review copy of LevelHelper and SpriteHelper, and these tools are advertised on my site. However, my review and comments are my honest opinions as a game developer.

Game Overview

In this tutorial, we’re going to make a very basic game where you’re a little angel that is just trying to get from one end of the level to the other without falling in any pits.

This will demonstrate the basics of using LevelHelper and SpriteHelper and creating a scrolling level with player movement, and in future tutorials we’ll add on some extra features.

To make the game, we’re going to use some art made by my lovely wife, so go ahead and download the resources for this project that she made us (along with some sound effects made by yours truly!)

This game is going to be called Raycast, because later on we’ll be integrating Box2D Raycasting into the game, and also I think the name is just totally awesome (and no I’m not biased at all!) ;]

Getting Started

OK, let’s get started!

Start up Xcode, go to File\New\New Project, choose iOS\cocos2d\cocos2d_box2d, and click Next. Name the new project Raycast, click Next, choose a folder to save your project in, and click Create.

Update 3/1/2012: Instead of using the cocos2d_box2d template, you should use the LevelHelper Xcode 4 template. Follow the instructions in this tutorial to download and install the template. Name it Raycast and continue along.

From the resources for this project that you downloaded, drag the Fonts and Sounds folders into your project. Verify that “Copy items into destination group’s folder (if needed)” is selected, and click Finish.

Right now the project contains some sample Box2D code in HelloWorldLayer.h and HelloWorldLayer.mm. Let’s remove this and create a new layer to get to a blank slate.

Control-click on HelloWorldLayer.h and HelloWorldLayer.mm, click Delete, and click Delete again.

Next go to File\New\New File, choose iOS\Cocoa Touch\Objective-C class, and click Next. Enter NSObject for Subclass of, click Next, name the new class ActionLayer.mm, and click Save.

Replace ActionLayer.h with the following:

#import "cocos2d.h"

@interface ActionLayer : CCLayer {
}

+ (id)scene;

@end

This declares a subclass of CCLayer, and a static method to create a new CCScene.

Switch to ActionLayer.mm and replace it with the following:

#import "ActionLayer.h"

@implementation ActionLayer

+ (id)scene {
    CCScene *scene = [CCScene node];    

    ActionLayer *layer = [ActionLayer node];
    [scene addChild:layer];    
    
    return scene;
}

@end

This implements the static method to create a new default CCScene and add the layer as the only child.

Finally, switch to AppDelegate.mm and make one final set of changes:

// Replace #import "HelloWorldLayer.h" with this:
#import "ActionLayer.h"

// Replace call to runWithScene with this:
[[CCDirector sharedDirector] runWithScene: [ActionLayer scene]];

Compile and run, and you should have a blank scene that will serve as our starting point!

A blank scene with Cocos2D

SpriteHelper and Sprite Sheet Generation

SpriteHelper is a tool that contains some aspects of Texture Packer (sprite sheet generation) and Physics Editor (physics shape definitions).

If you compare SpriteHelper’s feature set to Texture Packer and Physics Editor, Texture Packer and Physics Editor are easily superior due to many neat features such as dithering, command line support, built-in Cocos2D integration, auto-shape tracing, and more.

However, SpriteHelper truly shines with its integration with LevelHelper. Once you create the sprite sheets and shapes with SpriteHelper, you can then use LevelEditor to do scene layout, something that would be a manual process with the above tools.

So let’s try it out! Start up SpriteHelper and the following window will appear:

Default screen in SpriteHelper

Open up Finder and navigate to where you downloaded the resources for this project, and drag all of the images inside the Raw directory into the gridded area inside SpriteHelper.

The images will appear all on top of each other. To get SpriteHelper to auto-layout the sprites, set the following in the top toolbar:

  • Make sure Crop is unchecked. If it was checked, it would remove transparent space around sprites, but we don’t want this because the transparent space is actually important for our animations.
  • Make sure Save SD is checked. Our art is HD, so checking this will automatically create a copy scaled by 50% for the SD version when we go to save.
  • Make sure NPOT is checked. This will save the sprite sheets as a non power of two (NPOT) texture. This saves texture memory on the iPad, iPhone 4, and iPod Touch 4th, which can read non-power of two textures without having to expand them to the closest power of two like older devices. Note you have to set CC_TEXTURE_NPOT_SUPPORT in ccConfig.h to enable this in Cocos2D.

Finally, click Pack Sprites and SpriteHelper will automatically layout the sprites:

Packing sprites with SpriteHelper

At this point you’re done with the sprite sheet generation part of SpriteHelper – let’s move on to the physics shape definition aspect!

SpriteHelper and Physics Shape Definitions

For each sprite you’ve added into SpriteHelper, you can configure the physics body that maps to the sprite.

By default, it maps a rectangular body that matches to the bounds of the sprite. You can make the rectangle smaller with the “Shape Border” setting, set it to a circle with the “Is Circle” setting, or create your own custom polygon with the “Create Shape” buttons.

You can also set other physics properties such as:

  • Is Sensor: If this is true, the object can detect collisions but can move through other objects.
  • Shape type: Static means “does not move”, kinematic means “moved by setting velocity, does not respond to forces or collisions”, and dynamic means “normal movement via Box2D”. Usually you’ll use static for ground objects, and dynamic for most everything else.
  • Density: How hard the body is to move
  • Friction: How slippery the body is
  • Restitution: How bouncy the body is
  • Mask Bit: A bitfield indicating what categories the body collides with.
  • Category Bit: A bitfield indicating the categories the body belongs to.
  • Group Index: Any bodies with the same group index always collide (if the index is positive) or never collide (if the index is negative), regardless of category/mask settings. Personally I use mask/category bits more often over group indices, as I find them easier to work with.

I find setting the mask bits and category bits is difficult unless you draw out the categories on paper first. Here’s the category and masks we’re going to need for this game (note categories must all be powers of 2 since they are stored in a bitfield):

  • Laser: Category 1. Should be able to collide with heros (2), so its mask bit is 2.
  • Hero: Category 2. Should be able to collide with lasers (1) and clouds (4), so its mask bit is 5.
  • Cloud: Category 4. Should be able to collide with heros (2) and monsters (8), so its mask is 10.
  • Monster: Category 8. Should be able to collide with clouds (4), so its mask bit is 4.

With this all set, setting up our shapes and properties will be easy! Go through each shape and set up the settings as specified below.

Monster

LevelHelper Monster Settings

Here we set the body as dynamic, with a circle shape with a slight border so it better matches the monster’s shape.

Cloud

LevelHelper Cloud Settings

Here we set the body as static (since it does not move), with a square shape with a large border to match the cloud better. We also set the friction up a bit so the hero doesn’t slide around on it too much.

Laserbeam

LevelHelper Laser Settings

This time we set the body as kinematic, because we will be moving the laser by setting the velocity manually and don’t want any other forces (such as gravity) to interfere with it. We also set it up as a sensor so it doesn’t get stopped by any other bodies.

Hero (select all 4 sprites at once)

LevelHelper Hero Settings

This is a dynamic body with a shape border to better fit the main body of the character. Friction is set high to avoid sliding, and bounciness is decreased. We also set the body to fixed rotation, because falling on your face isn’t very angelic! :]

Done!

You’re finally done with SpriteHelper! Go to File\Save, click Yes if the popup appears, and save it somewhere on your drive as “Sprites” (SpriteHelper will add the .png extension automatically). Another Save As dialog will appear – enter “Sprites” again (SpriteHelper will add the .pshs extension automatically).

If you didn’t have LevelHelper, you could use SpriteHelper to generate some code that would let you use the generated sprite sheet and physics data in your game, but we do have LevelHelper, so let’s move on to that!

Getting Started with LevelHelper

Start up LevelHelper and the following screen will appear:

LevelHelper Default Screen

Start by clicking the + button next to Project. In the dialog that appears, on the left hand side enter Raycast for New Project Name, select “iPhone Landscape (480×320)” in the dropdown, and click “Create New Project.

Creating a new project in LevelHelper

You can have more than one level associated with the same Xcode project, so here you’re saying what project this level belongs to. The reason why this is important is because you generate code specific to each Xcode project that includes some constants that you’ll need to refer to in both LevelHelper and code.

Next in the area at the bottom of the screen, set these values:

Setting Box2D World Settings in LevelHelper

  • Game World Size: Set to (0,0,968,320). This is the area that should be visible to the player. I set it to about double the width of an iPhone screen to keep the level small and easy to manage, but feel free to tweak!
  • Can Drag Outside World: Set to checked. We’re going to need to drag an object outside the world boundaries later on.
  • Physic Boundaries: Set to (0,0,968,450). Although as you know Box2D has no actual boundaries, you can direct LevelHelper to create a border to keep objects inside by setting this. For this game this is useful to prevent the player from going out of the world to the left, top, and right. Note I made it lower at the bottom since the player should be allowed to fall through a hole in the level!
  • Graivty: Set to (0, -10). This is roughly the same as Earth’s gravity (-9.8 m/s^2).

By the way, one thing that wasn’t clear to me when I started is how to scroll the Game World. To do this, hold down control and drag in the editor, and it will let you pan the view.

Now, it’s time to import the sprites! Go to File\Import SpriteHelper Scene, and select the Sprites.pshs file that you made with SpriteHelper earlier. You’ll see the list of sprites appear on the right (if you don’t, click the first tab at the top to get it selected):

Importing SpriteHelper scene in LevelHelper

By the way, be aware that once you import a scene from SpriteHelper, it’s very difficult to go back and add another sprite into the SpriteHelperScene, re-layout, and start using the new sprite in your LevelHelper scene. I tried this and it messed up my existing level (incorrect sprites appeared in the game). LevelHelper works best if you have your art made and ready to go, and aren’t making any changes in the future. Considering in practice game art changes quite freqently as you’re developing, this is a big drawback so hopefully will be addressed in a future update as well.

OK – now we can start laying out the level! For now let’s make a simple row of clouds going along the bottom – we’ll come back later and finish the design of the level.

Drag a single cloud from the list over to the far left bottom of the screen. Then with the cloud selected, click the little arrow button in the bottom toolbar:

Repeating a sprite in LevelHelper

This will let you automatically repeat the cloud a number of times at a set offset (rather than requiring to repetitively drag them across a number of times). Enter 15 for number of copies, 68 for the X offset, and 0 for the Y offset, and click Make Clones:

LevelHelper Clone and Align dialog

An array of clouds should appear on the bottom of the level. Next drag a Monster from the list of sprites over into the level, somewhere to the upper right:

Add a monster to the scene with LevelHelper

This should be enough that we can start trying this out in code, so let’s save this out! Go to File\Save, navigate to the directory your Raycast Xcode project is saved in, and save it as TestLevel. LevelHelper will automatically append a plhs extension.

Next, we’re going to use LevelHelper to generate the code we’ll need to read in this file.

In the main toolbar, click Download code. This will download the latest code templates from a central server. Then go to File\Generate Code\Cocos2D with Box2D, browse to your Raycast Xcode project directory, and click Choose directory to generate files.

This will generate two files – LevelHelperLoader.h and LevelHelperLoader.mm in your project directory. Now it’s time to use them!

Integrating LevelHelper with Cocos2D

First, let’s add the files we generated with SpriteHelper and LevelHelper into your Xcode project.

Find wherever you saved your Sprites.png and Sprites-hd.png with SpriteHelper, and drag them into your Xcode project. Make sure “Copy items into destination group’s folder” is selected, and click Finish.

Then find TestLevel.plhs, LevelHelperLoader.h, and LevelHelperLoader.mm in your project directory, and drag them into your Xcode project as well. We don’t want these moved at all, so uncheck “Copy items into destination group’s folder”, and click Finish.

Open up ActionLayer.h and make the following changes:

// Add to top of file
#import "Box2D.h"
#import "GLES-Render.h"
#import "LevelHelperLoader.h"

// Add inside @interface
b2World * _world;
GLESDebugDraw * _debugDraw;
LevelHelperLoader * _lhelper;

Here we import the headers we’ll need, and predeclare variables for the Box2D world, the debug drawing helper class, and the level helper loader class (generated by LevelHelper itself).

Next switch to ActionLayer.mm and replace it with the following:

#import "ActionLayer.h"
#import "SimpleAudioEngine.h"

@implementation ActionLayer

+ (id)scene {
    CCScene *scene = [CCScene node];    

    ActionLayer *layer = [ActionLayer node];
    [scene addChild:layer];    
    
    return scene;
}

- (void)setupWorld {    
    b2Vec2 gravity = b2Vec2(0.0f, 0.0f);
    bool doSleep = false; 
    _world = new b2World(gravity);
    _world->SetAllowSleeping(doSleep);
}

- (void)setupLevelHelper {
    _lhelper = [[LevelHelperLoader alloc] initWithContentOfFile:@"TestLevel"];
    [_lhelper addObjectsToWorld:_world cocos2dLayer:self];
    [_lhelper createWorldBoundaries:_world];
    [_lhelper createGravity:_world];
}

- (void)setupDebugDraw {    
    _debugDraw = new GLESDebugDraw();
    _world->SetDebugDraw(_debugDraw);
    _debugDraw->SetFlags(b2Draw::e_shapeBit | b2Draw::e_jointBit);
}

- (void)setupAudio {
    [[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"Raycast.m4a"];
    [[SimpleAudioEngine sharedEngine] preloadEffect:@"ground.wav"];
    [[SimpleAudioEngine sharedEngine] preloadEffect:@"laser.wav"];
    [[SimpleAudioEngine sharedEngine] preloadEffect:@"wing.wav"];
    [[SimpleAudioEngine sharedEngine] preloadEffect:@"whine.wav"];
    [[SimpleAudioEngine sharedEngine] preloadEffect:@"lose.wav"];
    [[SimpleAudioEngine sharedEngine] preloadEffect:@"win.wav"];
}

- (id)init {
    if ((self = [super init])) {
        [self setupWorld];
        [self setupLevelHelper];
        [self setupDebugDraw];
        [self setupAudio];
        [self scheduleUpdate];
    }
    return self;
}

- (void)updateLevelHelper:(ccTime)dt {
    [_lhelper update:dt];
}

- (void)updateBox2D:(ccTime)dt {
    _world->Step(dt, 1, 1);
    _world->ClearForces();
}

- (void)updateSprites:(ccTime)dt {
    
     for (b2Body* b = _world->GetBodyList(); b; b = b->GetNext())
   {
      if (b->GetUserData() != NULL) 
        {
         CCSprite *myActor = (CCSprite*)b->GetUserData();            
            if(myActor != 0)
            {
                //THIS IS VERY IMPORTANT - GETTING THE POSITION FROM BOX2D TO COCOS2D
                myActor.position = [LevelHelperLoader metersToPoints:b->GetPosition()];
                myActor.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());      
            }            
        }   
   } 
}

- (void)update:(ccTime)dt {
    [self updateLevelHelper:dt];
    [self updateBox2D:dt];
    [self updateSprites:dt];
}

-(void) draw {  
    
    glClearColor(98.0/255.0, 183.0/255.0, 214.0/255.0, 255.0/255.0);
    glClear(GL_COLOR_BUFFER_BIT);	
    
    glDisable(GL_TEXTURE_2D);
    glDisableClientState(GL_COLOR_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    
    _world->DrawDebugData();
        
    glEnable(GL_TEXTURE_2D);
    glEnableClientState(GL_COLOR_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);	
}

- (void)dealloc {
    [_lhelper release];
    _lhelper = nil;
    delete _world;
    [super dealloc];
}

@end

Wow – there’s a lot of code there! If you’re familiar with Cocos2D and Box2D, most of this should be review though. I’ll just point out the LevelHelper specific areas here:

  • setupLevelHelper contains the initial setup code. You create a new instance of LevelHelperLoader, passing in the name of the level file you want to use (without the plhs extension). You then call a method to populate the Box2D world you created earlier with the shapes you defined with SpriteHelper/LevelHelper. You then optionally call methods to create the world boundaries and set the gravity specified in LevelHelper.
  • Notice that usually when you make a Box2D game you specify the PTM_RATIO somewhere in a header file. With LevelHelper, you can get it from the LevelHelperLoader class instead with the pixelsToMeterRatio method. This defaults to 32.0 but you can manually set it if you would prefer a different value.
  • Before updating Box2D, you should give LevelHelperLoader time to process by calling the update method. LevelHelperLoader uses this to make objects move along paths, or handle parallax scrolling. It won’t make a difference for this game since we’re not using those features, but it’s good practice to have that call in place.
  • It’s our responsibility to update each Box2D body’s associated sprite to the same position (hence the updateSprites method). LevelHelperLoader automatically stores a reference to a Box2D body’s associated sprite in the body’s userData.

Compile and run the code, and you should see the level you made with LevelHelper, and the monster fall down and drop down onto the clouds!

A simple level made with LevelHelper

Creating a Simple Level with LevelHelper

Now that we know things are working, let’s go back to LevelHelper to flesh out our level.

We’ve already covered how to add objects to the level with LevelHelper, so go ahead and drag sprites from the list to create a level that looks something like this (but feel free to make yours different!)

LevelHelper level sample layout

Important: Also drag a laserbeam from the library somewhere offscreen. You won’t need it for this tutorial, but it will be necessary for the next.

Once you’re done, you can just save your level in LevelHelper and re-run your game, and you should see the updated layout!

Level Helper sample level in-game

However this isn’t very exciting, because nothing moves! So let’s add some character movement and scrolling logic into the game.

Before we can do that though, we need to make some changes in LevelHelper. You see, we’re going to need some way to retrieve references to objects that we made in LevelHelper programatically. There are two ways to do so – by setting tags on objects (kind of like a CSS “class”), or by setting unique names for objects (kind of like a CCS “id”).

Let’s start with the tags. At the bottom of the screen in LevelHelper, under the Sprite Properties section, click the + button next to Tag to create a new Tag:

Creating a tag in LevelHelper

Type in PLAYER for the name, and click Add. Then repeat this process for MONSTER, LASER, and GROUND.

Then click each of the sprites you added to the level and set them to their appropriate tag. Note that you can select multiple at once (i.e. all of the clouds) by holding down the option key and clicking or dragging, and then set the tag to save time.

Next, find the sprite you added into the scene for the hero in the list under “Sprites in level”, double click the entry under “Unique Name”, and change it to “hero”:

Setting the unique name for a sprite in LevelHelper

Save your file. Also, whenever you modify tags you have to re-generate the code (because it puts the tags in the generated header file for you), so when you’re done go to File\Generate Code\Cocos2D with Box2D. Choose your Raycast Xcode project directory again, and click Choose directory to generate files.

You can verify it worked by going to LevelHelperLoader.h – you should see something like this toward the top:

enum LevelHelper_TAG 
{ 
    DEFAULT_TAG     = 0,
    PLAYER          = 1,
    MONSTER         = 2,
    LASER           = 3,
    GROUND          = 4,
    NUMBER_OF_TAGS  = 5
};

You know how Cocos2D sprites have a tag value? LevelHelperLoader sets the tag to these constants, based on what you set up in LevelHelper.

OK, now we can finally add the character movement and scrolling logic code! Make the following changes to ActionLayer.h:

// Add inside @interface
double _playerVelX;
CCSprite * _hero;
b2Body * _heroBody;
BOOL _gameOver;

And the following changes to ActionLayer.mm:

// Add to top of file
#define MOVE_POINTS_PER_SECOND 80.0

// Add to bottom of setupLevelHelper
_hero = [_lhelper spriteWithUniqueName:@"hero"];
NSAssert(_hero!=nil, @"Couldn't find hero");
_heroBody = [_hero body];
NSAssert(_heroBody!=nil, @"Couldn't find hero body");

// Add at bottom of init
self.isTouchEnabled = YES;

// Add after init
-(void)setViewpointCenter:(CGPoint) position {
    
    CGSize winSize = [[CCDirector sharedDirector] winSize];
    CGRect worldRect = [_lhelper gameWorldSize];
    
    int x = MAX(position.x, worldRect.origin.x + winSize.width / 2);
    int y = MAX(position.y, worldRect.origin.y + winSize.height / 2);
    x = MIN(x, (worldRect.origin.x + worldRect.size.width) - winSize.width / 2);
    y = MIN(y, (worldRect.origin.y + worldRect.size.height) - winSize.height/2);
    CGPoint actualPosition = ccp(x, y);
    
    CGPoint centerOfView = ccp(winSize.width/2, winSize.height/2);
    CGPoint viewPoint = ccpSub(centerOfView, actualPosition);
    
    self.position = viewPoint;
    
}

- (void)loseGame {
    [_hero runAction:[CCSequence actions:
                      [CCScaleBy actionWithDuration:0.35 scale:2.0],
                      [CCDelayTime actionWithDuration:0.75],
                      [CCScaleTo actionWithDuration:0.35 scale:0],
                      nil]];
    [_hero runAction:[CCRepeatForever actionWithAction:
                      [CCRotateBy actionWithDuration:0.5 angle:360]]]; 
    _gameOver = YES;
}

- (void)winGame {
    _gameOver = YES;
    [[SimpleAudioEngine sharedEngine] playEffect:@"win.wav"];
}

- (void)updateHero:(ccTime)dt {
    if (_playerVelX != 0) {
        b2Vec2 b2Vel = _heroBody->GetLinearVelocity();
        b2Vel.x = _playerVelX / [LevelHelperLoader pixelsToMeterRatio];
        _heroBody->SetLinearVelocity(b2Vel);                    
    }
}

- (void)updateViewpoint:(ccTime)dt {
    [self setViewpointCenter:_hero.position];
}

- (void)updateGameOver:(ccTime)dt {
    
    if (_gameOver) return;
    
    CGRect worldRect = [_lhelper gameWorldSize];
    if (_hero.position.x > (worldRect.origin.x + worldRect.size.width) * 0.95) {
        [self winGame];
    }
    
    if (_hero.position.y < 0.5) {
        [self loseGame];
    }
    
}

// Add in update, at beginning
[self updateHero:dt];

// Add in update, at end
[self updateViewpoint:dt];
[self updateGameOver:dt];

// Add after draw
- (void)handleTouchAtPoint:(CGPoint)touchLocation {        
    if (touchLocation.x < _hero.position.x) {
        _playerVelX = -MOVE_POINTS_PER_SECOND;
        _hero.flipX = YES;
    } else {
        _playerVelX = MOVE_POINTS_PER_SECOND;
        _hero.flipX = NO;
    }    
}

- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    
    
    if (_gameOver) return;
    
    UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [self convertTouchToNodeSpace:touch];
    
    [self handleTouchAtPoint:touchLocation];    
    
    if (touch.tapCount > 1) {
        _heroBody->ApplyLinearImpulse(b2Vec2(_playerVelX/[_lhelper pixelsToMeterRatio], 1.25), _heroBody->GetWorldCenter());
        [[SimpleAudioEngine sharedEngine] playEffect:@"wing.wav"];
    }
    
}

- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    
    if (_gameOver) return;
    
    UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [self convertTouchToNodeSpace:touch];
    [self handleTouchAtPoint:touchLocation];
}

- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    _playerVelX = 0;
}

- (void)ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    _playerVelX = 0;
}

Again, there’s a lot of code here but most of this is simple stuff and review if you’re familiar with Cocos2D and Box2D. Here’s some notes on the above:

  • At the bottom of setupLevelHelper, we use spriteWithUniqueName and bodyWithUniqueName to retrieve the sprite and body with the unique name “hero” (set up in the “Sprites in level” section in LevelHelper).
  • setViewpointCenter is a helper method to scroll the layer to keep the hero centered (or the closest to the center it can, without going offscreen). For more details on how this works, check out How To Drag and Drop Sprites with Cocos2D or our Learning Cocos2D Book.
  • We move the player left and right by calling SetLinearVelocity to manually set the velocity based on whether the user’s touch is to the left or right of the player. Note we use convertTouchToNodeSpace to get the touch coordinates within the layer (even if the layer is scrolled).
  • If the player double taps, we apply a small impulse to the player with ApplyLinearImpulse for a weak flight effect.
  • If the player gets to the far right of the screen, we say the player has won, and if he falls down a pit we say he’s lost. Right now there’s a little animation on lose, and a sound effect on both win and lose, but that’s it.

Compile and run, and see if you can beat the game! And if you can’t, you can always use LevelHelper to edit the level and make it easier ;]

Complete game made with SpriteHelper and LevelHelper

LevelHelper and SpriteHelper: My Review

At this point, you should know the basics of using SpriteHelper and LevelHelper to create a game, so we’re going to bring this tutorial to a close.

But I wanted to leave you off with my overall thoughts on SpriteHelper and LevelHelper, in case you are still trying to decide if these tools are for you. Let’s start with a list of pros:

  • Makes it very easy to create or modify a level, especially when compared to manually guesstimating and fixing up sprite positions.
  • There’s some nice features I didn’t even cover here like creating animations, paths, parallax scrolling, and joints which can save even more time!
  • The API is straightforward and easy to use.

And here’s a list of cons:

  • Adding new art halfway through a project is problematic. As mentioned earlier, I tried this and it ended up corrupting my level and I had to remake it.
  • There doesn’t seem to be a way to assign a subclass of CCSprite to a particular sprite. I often like to subclass CCSprite to add special behavior to objects, so this is annoying.

    Update: Actually this is possible, and a tutorial on how to do this is here!

  • Various UI frustrations due to general lack of polish.

Overall, I think the pros outweigh the cons, especially when you compare it to manual sprite placement or taking the time and energy to build your own editor.

So if you are looking for a premade editor for Cocos2D and can put up with the above limitations, I think SpriteHelper and LevelHelper are great additions to your tool library that can save you some significant development time.

Where To Go From Here?

Here is a sample project with all of the code from the above tutorial.

Keep reading for the next tutorial, where we’ll expand this game and make the monsters a lot more interesting – by applying some Intermediate Box2D physics tricks and techniques! :]

In the meantime, if you have any questions or comments about SpriteHelper, LevelHelper, or this tutorial, please join the forum discussion below!