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 3 of 4 of this article. Click here to view the first page.

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