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
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

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