Introduction to CocosBuilder

This is a blog post by iOS Tutorial Team member Ali Hafizji, an iOS and Android developer working at Tavisca Solutions. CocosBuilder is a free Interface Builder-like tool for Cocos2D that allows you to quickly and easily layout the sprites, layers, and scenes for your game. CocosBuilder is ideal for quickly laying out menus and […] By Ali Hafizji.

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.

Difficult Is Not An Option!

Like the Main Menu, the Options scene will have three buttons, and creating this scene will be just as painless.

In the case of the Options scene, the buttons will allow the user to select a difficulty level of Easy, Medium or Hard. There will also be a back button to return to the Main Menu.

Open CocosBuilder and create the new scene by selecting File\New\New File (follow the same steps as when you created the MainMenuScene), name it OptionsScene and save it in the Scenes directory.

Add three buttons to the scene and set their titles to Easy, Medium, and Hard. Then set their tags as 1, 2, and 3, respectively.

To get events when the user taps these buttons, you need to register a method to be called. Just as you did with MainMenuScene, set the selector for each of the buttons to difficultyButtonPressed: and the Target to Document Root.

Note: Wondering what Document Root means? It means the root node in the “Default Timeline” tree. Soon you will set the root node (CCLayer) to a custom class – OptionsLayer. That means that OptionsLayer will be the Document Root.

The layout should look something like this:

Now for that back button. This time, instead of adding a CCControlButton, add a CCMenu with the back button as a menu item.

Press the CCMenu button on the tool bar.

This will create a CCMenu node and add it to the OptionsScene layer. Now, add a CCMenuItemImage by pressing the CCMenuItemImage button on the tool bar.

Set the Normal and Selected sprites for this CCMenuItem as btn-back-0.png and btn-back-1.png. These properties can be set from the CCMenuItemImage subsection on the right pane.

Place the back button in the top-left corner of the scene and set the selector as backButtonPressed:. Don’t forget to set the Target as Document root.

That’s it! The scene should now look like this:

Just as you did with the MainMenuScene, add a custom class for the OptionsScene. Name it OptionsLayer.

As before, save your changes, publish the scene, and add the CCBI file to the Xcode project.

Switch to Xcode, create a new class under the Layers group and name it OptionsLayer (make sure it extends CCLayer) – just as you did before.

Next, add the following import and declare statements to the top of OptionsLayer.m:

#import "CCBReader.h"
#import "CCControlButton.h"

#define DIFFICULTY_EASY_BUTTON_TAG 1
#define DIFFICULTY_MEDIUM_BUTTON_TAG 2
#define DIFFICULTY_HARD_BUTTON_TAG 3

Also add the following methods:

-(void)backButtonPressed:(id)sender {
    [[CCDirector sharedDirector] replaceScene:[CCTransitionFlipAngular transitionWithDuration:1.0 scene:[CCBReader sceneWithNodeGraphFromFile:@"MainMenuScene.ccbi"]]];
}

-(void)difficultyButtonPressed:(id)sender {
    CCControlButton *button = (CCControlButton*) sender;
    NSString *difficultyLevel = @"Hard";
    if (button.tag == DIFFICULTY_EASY_BUTTON_TAG) {
        difficultyLevel = @"Easy";
    } else if(button.tag == DIFFICULTY_MEDIUM_BUTTON_TAG) {
        difficultyLevel = @"Medium";
    }
    NSLog(@"Difficulty is set to %@", difficultyLevel);
}

All of this should be familiar to you by now. :] backButtonPressed: will take the user back to the Main Menu scene.

difficultyButtonPressed: in its current form doesn’t actually set a difficulty level, but it will log the user’s selection. Feel free to implement the difficulty level for the game later, on your own – will you accept the challenge? :]

Build and run the game, and now you have two fully functional scenes. You’re halfway to a full game interface!

It’s About Time For… Fire!

About scenes exist to give users additional information about your game or app – how to play or use the app, who made it and what version number it is, and so on.

Your About scene will be special: it will have a burning inferno! Not only will it look cool, but it will serve the extra purpose of teaching you how to add a particle system (i.e. special effect) using CocosBuilder. :]

Switch to CocosBuilder, create a new file named AboutScene and save it in the Scenes directory.

To begin the layout, press the CCParticleSystemQuad button on the tool bar.

This will create a fire particle system. Select the particle system and change the Particle texture property to cat_leap_1.png. Play around with the CCParticleSystemQuad properties until you’re happy with what you have. Then place the particle system as shown below:

Now you need some text. Add a CCLabelBMFont by pressing the following button on the tool bar:

Set the Font file property of the label to Arial.fnt (present in the Resources directory). Then add two more labels, one below the other, and give them the same font.

Break up the following text between the three labels: Help the cat jump over all the obstacles trying to run him over. You’re not going for anything nuanced here!

At this stage, your About scene should look something like this:

You’re almost done with this scene! All that’s left is the back button – make sure the user can get back home!

Add a back button in the top-left corner, just as you did earlier. Set its selector as backButtonPressed: and the Target as Document root.

The final step is to add a custom class for this scene’s layer, just like for the previous two scenes. Name the custom class AboutLayer, as shown below:

Following the now-familiar routine, save your changes, publish the scene, and add it to the Xcode project. Then switch to Xcode and create a new Cocos2D class under the Layers group. Name it AboutLayer and make sure it extends CCLayer.

Now open AboutLayer.m and add the following import statement:

#import "CCBReader.h"

Also add the following method:

-(void)backButtonPressed:(id)sender {
    [[CCDirector sharedDirector] replaceScene:[CCTransitionFlipAngular transitionWithDuration:1.0 scene:[CCBReader sceneWithNodeGraphFromFile:@"MainMenuScene.ccbi"]]];
}

This method will be invoked when the user presses the back button and it replaces the current scene with the MainMenu scene.

Build and run the game again. You should see all the functionality you expect: tapping on the About button displays the AboutScene, and the back button returns you to the Main Menu. Not bad for a few minutes of your time, eh?

Game On!

Finally, time to the introduce the star of the game – the cat having a rough day! All you need to do here is place all the sprites that the game requires into the correct spots, and you’ll do the rest via code. So, let’s get started!

Switch to CocosBuilder and create a new file. Name it GameScene and save it in the Scenes directory.

Next, press the CCSprite button on the toolbar to create a new sprite. Set the frame of this sprite as bg.png and set the position as the center of the screen. Your scene should resemble the one shown below:

Now you need to add the game’s main character – the cat! Add another sprite to the layer by pressing the CCSprite button again. Set the frame of this sprite to cat_stand_1.png and give it a fixed position of X:75 and Y:75.

You also need to add two CCLabelBMFonts to the layer. These will show the number of lives the cat has left and the number of dodges made.

Press the CCLabelBMFont button twice and set the text of the labels to Dodges: and Lives:, respectively. Also set the font for each to Arial.fnt. Place the labels in the top left and right corners of the screen, as shown below:

Excellent! You’re all set.

Or are you? While you’ve placed all the sprites on the layer, you don’t have a way to reference them in code. For example, how would you refer to the cat in the game logic? Don’t get your tail in a knot – CocosBuilder gives you an easy way.

Let’s start with your hero, the cat. Select the cat sprite and in the right pane, under Code Connections, you will see a dropdown below the Custom class label.

Select Doc root var from the dropdown and set the text box next to it to cat. This will associate a variable named “cat” with a reference to this sprite. “Doc root var” specifies that this variable will be present in the document root, in this case the layer class.

That’s all there is to it! Now repeat the same for the labels, and name their reference variables livesLabel and dodgesLabel.

You can guess what comes next, right? Just like the previous scenes, this scene also requires a custom layer. So set the custom class property for the root layer of this scene to GameLayer:

Save your changes, publish the scene, and add the CCBI file to the project. Then switch to Xcode and create a new class in the Layers group, naming it GameLayer and making sure it extends CCLayer.

Now would be the time in your game development process to write the game logic. But since the focus of this tutorial is CocosBuilder (not game logic), simply replace GameLayer.m with this long code block:

#import "GameLayer.h"
#import "CCBReader.h"
#import "SimpleAudioEngine.h"

#define kVehicleTypeNone -1
#define kVehicleTypeRedCar 0
#define kVehicleTypeYellowCar 1
#define kVehicleTypeDog 2
#define kVehicleTypeKid 3

@interface GameLayer() {
    CCLabelBMFont *livesLabel;
    CCLabelBMFont *dodgesLabel;
    CCSprite *cat;
    
    CCNode *_vehicles;
    BOOL _invincible;
    BOOL _jumping;
    double _nextSpawn;
    int _lives;
    int _dodges;
    CCSpriteBatchNode *_catJumpBatchNode;
    CCAnimation *_catJumpAnimation;
}
@end

@implementation GameLayer

- (id) init {
    self = [super init];
    if (self) {
        
        [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"CatJumpAtlas.plist"];
        
        _catJumpBatchNode = [CCSpriteBatchNode batchNodeWithFile:@"CatJumpAtlas.png"];
        [self addChild:_catJumpBatchNode z:1];
        
        _catJumpAnimation = [CCAnimation animation];
        [_catJumpAnimation addSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"cat_leap_1.png"]];
        [_catJumpAnimation addSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"cat_leap_2.png"]];
        [_catJumpAnimation setDelayPerUnit:0.625f];
        [_catJumpAnimation retain];
        
        // If you want to add this to the AnimationCache instead of retaining
        //[[CCAnimationCache sharedAnimationCache] addAnimation:catJumpAnimation name:@"catJumpAnim"];
        
        // Dog Animation
        CCAnimation *dogAnimation = [CCAnimation animation];
        [dogAnimation addSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"dog_1.png"]];
        [dogAnimation addSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"dog_2.png"]];
        [dogAnimation addSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"dog_3.png"]];
        [dogAnimation addSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"dog_4.png"]];
        [[CCAnimationCache sharedAnimationCache] addAnimation:dogAnimation name:@"dogAnimation"];
        
        // Kid Animation
        CCAnimation *kidAnimation = [CCAnimation animation];
        [kidAnimation addSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"kidontrike_1.png"]];
        [kidAnimation addSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"kidontrike_2.png"]];
        [kidAnimation addSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"kidontrike_3.png"]];
        [kidAnimation addSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"kidontrike_4.png"]];
        [[CCAnimationCache sharedAnimationCache] addAnimation:kidAnimation name:@"kidAnimation"];
        
        
        self.isTouchEnabled = YES;
        
        [self scheduleUpdate];
        
        _vehicles = [CCNode node];
        [self addChild:_vehicles];
        
        _lives = 9;
        _dodges = 0;
        
        double curTime = [[NSDate date] timeIntervalSince1970];
        _nextSpawn = curTime + 4;

    }
    return self;
}

- (void) didLoadFromCCB { 
    [self setLives:_lives];
    [self setDodges:_dodges];
}

- (void) setDodges:(int) noOfDodges {
    dodgesLabel.string = [NSString stringWithFormat:@"Dodges:%d", noOfDodges];
}

- (void) setLives:(int) noOfLives {
    livesLabel.string = [NSString stringWithFormat:@"Lives:%d", noOfLives];
}

- (void)carDone:(id)sender {
    
    CCSprite *vehicle = (CCSprite *)sender;
    [vehicle removeFromParentAndCleanup:YES];
    
    _dodges++;
    [self setDodges:_dodges];
}

- (void)doneInvincible {
    _invincible = FALSE;
}

- (void)update:(ccTime)dt {
    CGSize winSize = [CCDirector sharedDirector].winSize;
    CCSprite *vehicleSprite;
    // Spawn Vehicles (new)
    double curTime = [[NSDate date] timeIntervalSince1970];
    if (curTime > _nextSpawn) {
        
        int randomVehicle = arc4random() % 4;
        
        if (randomVehicle == kVehicleTypeRedCar) {
            // Red Car
            vehicleSprite = [CCSprite spriteWithSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"car1_1.png"]];
            [vehicleSprite setUserData:[NSNumber numberWithInt:kVehicleTypeRedCar]];
            CCSprite *wheel1 = [CCSprite spriteWithSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"car1_tire.png"]];
            CCSprite *wheel2 = [CCSprite spriteWithSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"car1_tire.png"]];
            id tireRotateAction1 = [CCRotateBy actionWithDuration:1.0f angle:360.0f]; // Ray, these are backwards on purpose as a lab exercise.
            id tireRotateAction2 = [CCRotateBy actionWithDuration:1.0f angle:360.0f];
            [wheel1 runAction:[CCRepeatForever actionWithAction:tireRotateAction1]];
            [wheel2 runAction:[CCRepeatForever actionWithAction:tireRotateAction2]];
            [vehicleSprite addChild:wheel1];
            [vehicleSprite addChild:wheel2];
            [wheel1 setPosition:ccp(65,18)];
            [wheel2 setPosition:ccp(212,18)];
            
        } else if (randomVehicle == kVehicleTypeYellowCar) {
            // Yellow Car (Same code as Red Car except for wheel placement, re-listed for clarity. Consilidate in your own games)
            vehicleSprite = [CCSprite spriteWithSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"car2_1.png"]];
            [vehicleSprite setUserData:[NSNumber numberWithInt:kVehicleTypeYellowCar]];
            CCSprite *wheel1 = [CCSprite spriteWithSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"car2_tire.png"]];
            CCSprite *wheel2 = [CCSprite spriteWithSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"car2_tire.png"]];
            id tireRotateAction1 = [CCRotateBy actionWithDuration:1.0f angle:-360.0f];
            id tireRotateAction2 = [CCRotateBy actionWithDuration:1.0f angle:-360.0f];
            [wheel1 runAction:[CCRepeatForever actionWithAction:tireRotateAction1]];
            [wheel2 runAction:[CCRepeatForever actionWithAction:tireRotateAction2]];
            [vehicleSprite addChild:wheel1];
            [vehicleSprite addChild:wheel2];
            [wheel1 setPosition:ccp(62,15)];
            [wheel2 setPosition:ccp(195,15)];
            
        } else if (randomVehicle == kVehicleTypeDog) {
            // Dog
            vehicleSprite = [CCSprite spriteWithSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"dog_1.png"]];
            [vehicleSprite setUserData:[NSNumber numberWithInt:kVehicleTypeDog]];
            
            // In your code, check that the animationByName did not return nil (due to memory warnings)
            CCAnimation *vehicleAnimation = [[CCAnimationCache sharedAnimationCache] animationByName:@"dogAnimation"];
            
            vehicleAnimation.restoreOriginalFrame = NO;
            vehicleAnimation.delayPerUnit = 0.5f/ vehicleAnimation.frames.count;
            id animationAction = [CCAnimate actionWithAnimation:vehicleAnimation];
            
            [vehicleSprite runAction:[CCRepeatForever actionWithAction:animationAction]];
            
        } else {
            // Kid on Bike (Same code as Dog, re-listed for clarity. Consilidate in your own games)
            vehicleSprite = [CCSprite spriteWithSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"kidontrike_1.png"]];
            [vehicleSprite setUserData:[NSNumber numberWithInt:kVehicleTypeKid]];
            
            // In your code, check that the animationByName did not return nil (due to memory warnings)
            CCAnimation *vehicleAnimation = [[CCAnimationCache sharedAnimationCache] animationByName:@"kidAnimation"];
            
            vehicleAnimation.restoreOriginalFrame = NO;
            vehicleAnimation.delayPerUnit = 0.5f/ vehicleAnimation.frames.count;
            id animationAction = [CCAnimate actionWithAnimation:vehicleAnimation];
            
            [vehicleSprite runAction:[CCRepeatForever actionWithAction:animationAction]];
            
        }
        
        // Common placement and movement code for all vehicles
        
        vehicleSprite.position = ccp(winSize.width + vehicleSprite.contentSize.width/2, 75);
        [_catJumpBatchNode addChild:vehicleSprite];
        
        [vehicleSprite runAction:[CCSequence actions:
                                  [CCMoveBy actionWithDuration:1.25 position:ccp(-winSize.width-vehicleSprite.contentSize.width, 0)],
                                  [CCCallFuncN actionWithTarget:self selector:@selector(carDone:)],
                                  nil]];
        
        float randomInterval = arc4random() % 3 + 1.5;
        _nextSpawn = curTime + randomInterval;
        
        
    }
    
    
    // Check for collisions
    if (!_invincible) {
        float insetAmtX = 10;
        float insetAmtY = 10;
        BOOL isCatColliding;
        CGRect catRect = CGRectInset(cat.boundingBox, insetAmtX, insetAmtY);
        CGRect vehicleRect;
        for (CCSprite *vehicle in _catJumpBatchNode.children) {
            if ([vehicle tag] == 1) {
                continue;  // No need to check if the Cat collides with itself
            }
            isCatColliding = NO;
            NSNumber *vehicleTypeNumber = (NSNumber*)[vehicle userData];
            int vehicleType = [vehicleTypeNumber intValue];
            
            if (vehicleType == kVehicleTypeRedCar) {
                CGPoint boundingBoxOrigin = vehicle.boundingBox.origin;
                CGRect carHood = CGRectMake(boundingBoxOrigin.x+10,boundingBoxOrigin.y , 40,80);
                insetAmtX = 50;
                insetAmtY = 10;
                vehicleRect = CGRectInset(vehicle.boundingBox,insetAmtX,insetAmtY);
                
                if ((CGRectIntersectsRect(catRect,carHood)) ||
                    (CGRectIntersectsRect(catRect, vehicleRect))) {
                    isCatColliding = YES;
                    CCLOG(@"Collided with Red Car");
                }
                
            } else if (vehicleType == kVehicleTypeYellowCar) {
                CGPoint boundingBoxOrigin = vehicle.boundingBox.origin;
                CGRect carHood = CGRectMake(boundingBoxOrigin.x+10,boundingBoxOrigin.y , 68,65);
                insetAmtX = 68;
                insetAmtY = 10;
                vehicleRect = CGRectInset(vehicle.boundingBox,insetAmtX,insetAmtY);
                
                if ((CGRectIntersectsRect(catRect,carHood)) ||
                    (CGRectIntersectsRect(catRect, vehicleRect))) {
                    isCatColliding = YES;
                    CCLOG(@"Collided with Yellow Car");
                } 
                
                
            } else {
                // Dog or Kid
                CGRect vehicleRect = CGRectInset(vehicle.boundingBox, insetAmtX, insetAmtY);
                if (CGRectIntersectsRect(catRect, vehicleRect)) {
                    isCatColliding = YES;
                }
            }
            
            
            if (isCatColliding == YES) {
                // Play sound, take a hit, invincible, break out of the loop
                [[SimpleAudioEngine sharedEngine] playEffect:@"squish.wav"];            
                _invincible = TRUE;
                [cat runAction:[CCSequence actions:
                                [CCBlink actionWithDuration:1.0 blinks:6],
                                [CCCallFunc actionWithTarget:self selector:@selector(doneInvincible)],
                                nil]];     
                _lives--;
                [self setLives:_lives];
                
                if (_lives <= 0) {
                    [[CCDirector sharedDirector] replaceScene:[CCTransitionJumpZoom transitionWithDuration:1.0 scene:[CCBReader sceneWithNodeGraphFromFile:@"GameOver.ccbi"]]];
                }
                break;
            }
        }
        
    }
}

- (void)doneJump {
    _jumping = FALSE;
}

- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    if (!_jumping) {
        
        _jumping = TRUE;
        [[SimpleAudioEngine sharedEngine] playEffect:@"meow.wav"];
        
        CCLOG(@"Making the Cat Jump");
        _catJumpAnimation.restoreOriginalFrame = YES;
        CCAnimate *jumpAnimation = [CCAnimate actionWithAnimation:_catJumpAnimation];
        
        CCJumpBy *jumpAction = [CCJumpBy actionWithDuration:1.25 position:ccp(0,0) height:200 jumps:1];
        CCCallFunc *doneJumpAction = [CCCallFunc actionWithTarget:self selector:@selector(doneJump)];
        CCSequence *sequenceAction = [CCSequence actions:jumpAction,doneJumpAction, nil];
        
        
		[cat runAction:[CCSpawn actions:jumpAnimation,sequenceAction, nil]];
    }
    
}
@end

And that’s your work done for you! :] Feel free to look through the code to get an idea of what's going on with the game logic, but don't worry about it too much - again our focus is CocosBuilder here.

Pay attention to the following variables set up at the top, right after the #defines:

    CCLabelBMFont *livesLabel;
    CCLabelBMFont *dodgesLabel;
    CCSprite *cat;

These variables, as I'm sure you remember, correspond to the variables you set up in CocosBuilder. They will be automatically initialized to the cat sprite, lives label, and dodges label.

Build and run the game. Now when you press the Play button, you can actually play the game.

HOORAY! You’ve successfully implemented the gameplay scene. That was a lot easier that dodging kids on tricycles.

Ali Hafizji

Contributors

Ali Hafizji

Author

Over 300 content creators. Join our team.