Quantcast
Channel: Envato Tuts+ Code - Mobile Development
Viewing all articles
Browse latest Browse all 1836

Build a Monster Smashing Game with Cocos2D: Movement & Animations

$
0
0

This tutorial will teach you how to use the Cocos2D iOS framework in order to create simple yet advanced 2D games aimed at all iOS devices. It’s designed for both novice and advanced users. Along the way, you’ll learn about the Cocos2D core concepts, touch interaction, menus and transitions, actions, particles, and collisions. Read on!

This tutorial is the second entry in the three-part Monster Smashing series. Make sure you’ve completed the previous section before beginning.


Series Structure

In today’s tutorial we’ll program the game board for Monster Smashing. This part focuses on several things, including the MonsterRun class, movements, animations, touches, and so on. We’ll explain everything in the tutorial below.


1. Monsters

Monsters are the main objects of the game. Each monster has a few unique attributes like sprites, movement, velocity, and kill action. To represent an object we can use a specific class, so we’ve created a Monster class to represent a monster.

Usually, in Objective-C, we create a new NSObject subclass. In this case, we need to use the CCNode class, which is a class that includes the Foundation.h and the cocos2d.h headers. To create a new class in Xcode, choose File -> New -> New File…. In the left-hand table of the panel that appears, select Cocos2D from the iOS section. Select CCNode class from the upper panel and hit next. We’ll name the class “Monster”.

Figure 1: CCNode Class
Illustration of the Template Chooser (Xcode).

Now we need to declare the instance variables. In Monster.h, add five instance variables:

  • NSString *monsterSprite
  • NSString *splashSprite
  • int movement
  • float minVelocity
  • float maxVelocity
  • int killMethod

Now, as with every oriented object language, we need to implement the setters and getters. In objective-C, properties are a convenient alternative to writing out accessors for instance variables. It saves a lot of typing and it makes your class files easier to read. Add the above-mentioned properties on the .h file.

@property(nonatomic,readwrite,retain) NSString *monsterSprite;
@property(nonatomic,readwrite,retain) NSString *splashSprite;
@property(nonatomic, readwrite) int movement;
@property(nonatomic, readwrite) float minVelocity;
@property(nonatomic, readwrite) float maxVelocity;
@property(nonatomic, readwrite) int killMethod;

2. Initializing the Gameboard

Before continuing, you’ll need to initialize several things. we can add a background to our game just like we did in the first section of the tutorial. We can use the same one in the menu scene. If you don’t remember how to do that, you can use the following code on the -(id) init method (MonsterRun.m).

self.isTouchEnabled = YES;
// Add background
        winSize = [CCDirector sharedDirector].winSize;
        CCSprite *background = [CCSprite spriteWithFile:@"WoodRetroApple_iPad_HomeScreen.jpg"];
        background.position = ccp(winSize.width/2, winSize.height/2);
        [self addChild:background z:-2];

Now we need to initialize some “monsters”. Before you start adding monsters, we need to create a NSMutableArray in order to store them. Once again, the images for the monster sprites are available in the resources folder.

We’d also like to thank Rodrigo Bellão for providing us with the images for the monsters.

In MonsterRun.m add two NSMutableArray objects: _monsters and _monstersOnScreen. Then, in the init method, we must initialize those arrays in order to use them. The following snippet can help us achieve that.

        //initializing monsters
        _monsters = [[NSMutableArray alloc] init];
        _monstersOnScreen = [[NSMutableArray alloc] init];
        Monster *m1 = [[Monster alloc] init];
        [m1 setTag:1];
        [m1 setMonsterSprite:[[NSString alloc] initWithString:@"monsterGreen.png"]];
        [m1 setSplashSprite:[[NSString alloc] initWithString:@"splashMonsterGreen.png"]];
        [m1 setMinVelocity:2.0];
        [m1 setMaxVelocity:8.0];
        [m1 setMovement:1];
        [m1 setKillMethod:1];
        [_monsters addObject:m1];
        Monster *m2 = [[Monster alloc] init];
        [m2 setTag:2];
        [m2 setMonsterSprite:[[NSString alloc] initWithString:@"monsterBlue.png"]];
        [m2 setSplashSprite:[[NSString alloc] initWithString:@"splashMonsterBlue.png"]];
        [m2 setMinVelocity:2.0];
        [m2 setMaxVelocity:8.0];
        [m2 setKillMethod:2];
        [m2 setMovement:1];
        [_monsters addObject:m2];
        Monster *m3 = [[Monster alloc] init];
        [m3 setTag:3];
        [m3 setMonsterSprite:[[NSString alloc] initWithString:@"monsterRed.png"]];
        [m3 setSplashSprite:[[NSString alloc] initWithString:@"splashMonsterRed.png"]];
        [m3 setMinVelocity:3.0];
        [m3 setMaxVelocity:6.0];
        [m3 setKillMethod:1];
        [m3 setMovement:2];
        [_monsters addObject:m3];

These arrays should be used to store all the monsters for a given level.


3. Adding Monsters to the Screen

Now that we have some monsters on memory, the next step is to put them on screen. To do that, we just need a few lines.

First we need to create a scheduler to invoke a method to a specific action which, in this case, is the monster location and movement. Right next to the monster’s allocation, we can create the scheduler as the next line presents.

[self schedule:@selector(addMonster:) interval:1.0];

The selector indicates the method that will be called addMonster. At the moment, we don’t have that method, but it will be created soon. The scheduler can also receive a time interval that represents the time when a specific sprite is updated.

The addMonster method is the one responsible for the monster movement. Since we’ve added three different monsters, we will also create three different movement patterns. The addMonster method is depicted below.

- (void) addMonster:(ccTime)dt {
    //select a random monster from the _monsters Array
    int selectedMonster = arc4random() % [_monsters count];
    //get some monster caracteristics
    Monster *monster = [_monsters objectAtIndex:selectedMonster];
    int m = [monster movement];
    //!IMPORTANT -- Every Sprite in Screen must be an new CCSprite! Each Sprite can only be one time on screen
    CCSprite *spriteMonster = [[CCSprite alloc] initWithFile:[monster monsterSprite]];
    spriteMonster.tag = [monster tag];
    //BLOCK 1 - Determine where to spawn the monster along the Y axis
    CGSize winSize = [CCDirector sharedDirector].winSize;
    int minX = spriteMonster.contentSize.width / 2;
    int maxX = winSize.width - spriteMonster.contentSize.width/2;
    int rangeX = maxX - minX;
    int actualY = (arc4random() % rangeX) + minX;
    //BLOCK 2 - Determine speed of the monster
    int minDuration = [monster minVelocity];
    int maxDuration = [monster maxVelocity];
    int rangeDuration = maxDuration - minDuration;
    int actualDuration = (arc4random() % rangeDuration) + minDuration;
    if(m == 1){ //STRAIGHT MOVIMENT
        //BLOCK 3 - Create the monster slightly off-screen along the right edge,
        // and along a random position along the Y axis as calculated above
        spriteMonster.position = ccp( actualY,winSize.height + spriteMonster.contentSize.height/2);
        [self addChild:spriteMonster];
        //BLOCK 4 - Create the actions
        CCMoveTo * actionMove = [CCMoveTo actionWithDuration:actualDuration position:ccp( actualY,-spriteMonster.contentSize.height/2)];
        CCCallBlockN * actionMoveDone = [CCCallBlockN actionWithBlock:^(CCNode *node) {
            [_monstersOnScreen removeObject:node];
            [node removeFromParentAndCleanup:YES];
        }];
        [spriteMonster runAction:[CCSequence actions:actionMove, actionMoveDone, nil]];
        [_monstersOnScreen addObject:spriteMonster];
    }
    else if(m == 2){ //ZIGZAG-SNAKE MOVIMENT
        /* Create the monster slightly off-screen along the right edge,
         and along a random position along the Y axis as calculated above
         */
        spriteMonster.position = ccp( actualY,winSize.height + spriteMonster.contentSize.height/2);
        [self addChild:spriteMonster];
        CCCallBlockN * actionMoveDone = [CCCallBlockN actionWithBlock:^(CCNode *node) {
            [_monstersOnScreen removeObject:node];
            [node removeFromParentAndCleanup:YES];
        }];
        // ZigZag movement Start
        NSMutableArray *arrayBezier = [[NSMutableArray alloc] init];
        ccBezierConfig bezier;
        id bezierAction1;
        float splitDuration = actualDuration / 6.0;
        for(int i = 0; i< 6; i++){
            if(i % 2 == 0){
                bezier.controlPoint_1 = ccp(actualY+100,winSize.height-(100+(i*200)));
                bezier.controlPoint_2 = ccp(actualY+100,winSize.height-(100+(i*200)));
                bezier.endPosition = ccp(actualY,winSize.height-(200+(i*200)));
                bezierAction1 = [CCBezierTo actionWithDuration:splitDuration bezier:bezier];
            }
            else{
                bezier.controlPoint_1 = ccp(actualY-100,winSize.height-(100+(i*200)));
                bezier.controlPoint_2 = ccp(actualY-100,winSize.height-(100+(i*200)));
                bezier.endPosition = ccp(actualY,winSize.height-(200+(i*200)));
                bezierAction1 = [CCBezierTo actionWithDuration:splitDuration bezier:bezier];
            }
            [arrayBezier addObject:bezierAction1];
        }
        [arrayBezier addObject:actionMoveDone];
        id seq = [CCSequence actionsWithArray:arrayBezier];
        [spriteMonster runAction:seq];
        // ZigZag movement End
        [_monstersOnScreen addObject:spriteMonster];
    }
}

You may have noticed that there are comments within the code. The comments will guide us (both programmers and readers) to implement and learn each instruction. In this tutorial, the monsters are chosen randomly (however, in part three we’ll show how use pList to put the monsters on the screen).

The first line of our code does exactly that. We get the total number of monsters on our array and then we randomly choose which one we will put onscreen next. After the monster is selected, we define some properties such as movement, velocity, range over the XX axis and its tag.

We defined three Blocks that define specific properties.

  • Block 1 determines where on screen we will span the monsters. Note that the monster will only spawn in a visible area of screen. For that, we get the range of the XX axis and then randomly place the monster.
  • Block 2 determines the speed of the monster. Note that each monster’s type will have the minimum and maximum speed defined. The speed of the monster is the duration in seconds that the monster will pass through the screen. This duration will be between the minDuration and the maxDuration defined on the monster object on the init method
  • Block 3 creates the monster slightly off-screen along the right edge and along a random position along the XX axis, calculated above
  • Block 4 creates the actions for the straight movement. Two actions are created, the movement itself CCMoveTo * actionMove and one that is called when the sprite leaves the screen (CCCallBlockN * actionMoveDone). To create an action with straight movement, we use the CCMoveTo object, which receives the duration of the action and the final position. We also use the CCCallBlockN. This particular object will detect when a monster gets off the screen. Using the CCCallBlockN is advantageous because Cocos2D already has some mechanisms in place to detect if a particular resource leaves the screen.

We have defined two movements: straight and zig-zag. The straight one can be analyzed in Block 4, mentioned above. The zig-zag uses the bezier path to create the zig-zag effect and will be depicted below.


4. The Zig-Zag (Snake) Movement

To make the zig-zag movement we use the CCBezier. This class allow us to create an action that will move the target with a cubic bezier curve to a destination point.

The created curves will always move 100 pixels to the left or right, and at the same time the object will move 200 pixels below. The for loop will create the path of the monster. Since the screen, which is the normal iPad resolution, has 1024 pixels in the vertical position, we need to run the loop six times (200px * 6 = 1200px). The condition inside the for loop will determine if the iteration is odd or pair. When the number is odd, the monster move to the left. If the number is pair, the monster moves to the right.

Now each condition will calculate the controlPoint1 and controlPoint2, which are used to control and guide the curve on screen. The endPosition, as the name suggests, is the final position that each monster will end up in after each curve. We concatenate all these actions into an array and create a sequence from it. Just as we did with the straight movement, we’ll run the action and add the monster to the array.

It’s now time to build and run the project. Your game should look similar to the image below.

Figure 2: Monster on Screen
Illustration of the game with several monsters.

5. Touch Interaction

A game with monsters that cannot be killed is not a game, right?

It’s time to add some interaction between the screen and the user. We’ll follow the same philosophy we used above, code and explanation.

Cocos2D provides us with several mechanisms for touch interaction. For now, we’ll only focus on one touch. For that, we need to add a specific method called ccTouchesBegan. The method receives an event and will act accordingly to that event. Obviously, the event is a touch event. The snippet is presented below.

-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSMutableArray *monstersToDelete = [[NSMutableArray alloc] init];
    UITouch * touch = [[touches allObjects] objectAtIndex:0];
    CGPoint touchLocation = [self convertTouchToNodeSpace:touch];
    for (CCSprite *monster in _monstersOnScreen) {
        if (CGRectContainsPoint(monster.boundingBox, touchLocation)) {
            [monstersToDelete addObject:monster];
            //add animation with fade - splash
            Monster *m = [_monsters objectAtIndex:(monster.tag-1)];
            CCSprite *splashPool = [[CCSprite alloc] initWithFile:[m splashSprite]];
            if([m killMethod] == 1){
                splashPool.position = monster.position;
                [self addChild:splashPool];
                CCFadeOut *fade = [CCFadeOut actionWithDuration:3];  //this will make it fade
                CCCallFuncN *remove = [CCCallFuncN actionWithTarget:self selector:@selector(removeSprite:)];
                CCSequence *sequencia = [CCSequence actions: fade, remove, nil];
                [splashPool runAction:sequencia];
                //finish splash
            }
            if([m killMethod] == 2){
                // in Part 3 - Particles section
            }
            break;
        }
    }
     for (CCSprite *monster in monstersToDelete) {
         [monster stopAllActions];
         [_monstersOnScreen removeObject:monster];
         [self removeChild:monster cleanup:YES];

The first line of this method is the allocation of an auxiliary array monstersToDelete that defines what monsters will be deleted at each action.

The next step is to know the touch interaction location. With that location, we’ll loop all the sprites on the screen and test to see if the touch location is inside an invisible box that contains a monster. We’ll use f (CGRectContainsPoint(monster.boundingBox, touchLocation)). If it is true, we’ll add the monsters to the auxiliary array. Plus, we’ll add two splash animations when a monster is killed. The first one is a simple splash, while the second is a particle. We’ll discuss that in part three. To create the splash, we verify the monster type and then we add that monster to a splash pool. CCFadeOut will create a fade effect on the splash image, and the CCCallFuncN will call another method to remove the Monster sprite.

Now we just need one little thing. In the CCCallFuncN *remove, the call method removeSprite needs to be created and the objective is to remove that sprite. Add that method using the following code.

-(void) removeSprite:(id)sender {
    [self removeChild:sender cleanup:YES];
}

Finally, when the loop is over, the touch interaction is tested against all objects in the scene we have in another loop, for (CCSprite *monster in monstersToDelete). It will iterate over monstersToDelete and remove the object.

It’s now time to build and run the project. We should see some monsters popping up onscreen. Now we can start killing them! You will notice that the red monsters don’t have any splash when they die. This will be covered in the next part. The final image in this part is the following.

Figure 1: Monster on Screen with splash animation
Illustration of the game with several monsters with splash animation.

6. Conclusion

At this point you should be able to understand and perform the following tasks:

  • Add sprites to the screen.
  • Define sprite properties.
  • Define custom classes.
  • Initialize the game board.
  • Define custom movements and actions.
  • Know how to use touch interaction.

In the next tutorial we’ll learn about sound and game mechanics!


Viewing all articles
Browse latest Browse all 1836

Trending Articles