Contents [show/hide]

A very basic Cocos2d tutorial, part 2

In this tutorial, we will add movement to all the henchmen. This means we will take a closer look at Actions and use them to produce different movements for different henchmen.

Actions

Actions are basically orders passed to a CocosNode object, and they usually modify one or more of the object's attributes like position, rotation, scale etc.

Actions are split in two basic types - actions that are instant (InstantActions) and actions that modify the attributes over a period of time (IntervalActions).

We have already used an Action in the previous tutorial; the Move action, which moves the target based on the parameters velocity, acceleration and gravity. The gravity parameter is basically acceleration on the y axis. As such, you will usually want your gravity to be negative, so the character will fall down, not up.

Instant actions

Actions like Hide (hides the object), Show (shows the object) and Place ("teleports" the object to the specified position) are instant actions.

Programmatically speaking, InstantActions aren't all that different from IntervalActions - they just promise us that any changes that are performed on the target will be done in the .start method and their methods .step, .update and .stop do not do anything. This makes sure that InstantActions remain "compatible" with IntervalActions, meaning we can combine them in various ways.

Interval actions

Interval actions are actions that change the target's parameters over a fixed duration. For instance, the actions MoveTo((50, 50), 10) would move the target to the position (50, 50) in 10 seconds.

Interval actions can also be degenerated with a duration of 0, practically turning them into instant actions. The player would not see a difference between MoveTo((50, 50), 0) and `Place((50, 50)).

Combining actions

Actions can easily be combined together to build a chain of subsequent actions. To chain actions together, you can use math operators + and *, which do exactly what you would expect them to - add and multiply actions.

Let's say that we want a character to move 50px to the right in one second and then 50px up in two seconds. We could do this with:

In []:
MoveBy((50, 0), 1) + MoveBy((0, 50), 2)

Now we want the character to move 50px to the right in half a second, wait for half a second and repeat those two actions five times. We can do this with:

In []:
(MoveBy((50, 0), 0.5) + Delay(0.5)) * 5

With combining actions like this, we can easily come up with fun and complex movements for our characters.

Repeating actions

Sometimes you would want to repeat an action indefinitely - for instance, you might want to create a guard "patrolling" the hallway by moving from left to right, waiting a bit on each side. When we want to repeat an action indefinitely, we use the Repeat action.

We could create a patrolling guard like this:

In []:
Repeat(Delay(0.5) + MoveBy((200, 0), 2) + Delay(0.5) + MoveBy((-200, 0), 2))

Reversing actions

Many times you might want to reverse an action. To do this, use the Reverse action. If an action is complex (made out of more actions), the actions will be run from last to first, with each of the actions reversed.

For instance, the reverse of an action that would move our character 50px right, then 50px up, then wait 2 seconds would be an action that would first wait 2 seconds, then move 50px down and then 50px left.

In []:
movement = MoveBy((50, 0), 1) + MoveBy((0, 50), 1) + Delay(2)
movement_reversed = Reverse(movement)
movement_manual_reversed = Delay(2) + MoveBy((0, -50), 1) + MoveBy((-50, 0), 1)

movement_reversed and movement_manual_reversed do the exact same thing.

Adding movement to the NPC's

We will use a combination of actions to make the henchmen move. Since their positions are mirrored on the x axis, we will make each of the bottom, middle and top two henchmen have the same (but reversed) movement. All of them will repeat the same movement indefinitely.

Full code

In []:
from __future__ import division

from cocos.actions import AccelDeccel
from cocos.actions import Delay
from cocos.actions import JumpBy
from cocos.actions import Move
from cocos.actions import MoveBy
from cocos.actions import Repeat
from cocos.actions import Reverse
from cocos.actions import RotateBy
from pyglet.window import key

import cocos
import resources


class Game(cocos.layer.ColorLayer):
    is_event_handler = True
    def __init__(self):
        super(Game, self).__init__(102, 102, 225, 255)

        self.player = cocos.sprite.Sprite(resources.player)
        self.player.position = 400, 25
        self.player.velocity = 0, 0
        self.player.speed = 150
        self.add(self.player, z=2)

        self.boss = cocos.sprite.Sprite(resources.boss)
        self.boss.position = 400, 600
        self.boss.scale = 0.4
        self.add(self.boss, z=1)

        self.batch = cocos.batch.BatchNode()
        self.enemies = [cocos.sprite.Sprite(resources.enemy)
                   for i in range(6)]
        positions = ((250, 125), (550, 125), (300, 325), (500, 325),
                     (150, 475), (650, 475))
        for num, enem in enumerate(self.enemies):
            enem.position = positions[num]
            self.batch.add(enem)

        self.add(self.batch, z=1)
        self.player.do(Move())

        move_basic = MoveBy((120, 0), 1)
        self.enemies[0].do(Repeat(move_basic + Reverse(move_basic)))
        self.enemies[1].do(Repeat(Reverse(move_basic) + move_basic))

        move_complex = (MoveBy((-75, 75), 1) +
                        Delay(0.5) +
                        MoveBy((-75, -75), 1) +
                        Delay(0.5) +
                        MoveBy((75, -75), 1) +
                        Delay(0.5) +
                        MoveBy((75, 75), 1) +
                        Delay(0.5))
        self.enemies[2].do(Repeat(move_complex))
        self.enemies[3].do(Repeat(Reverse(move_complex)))

        move_jump = AccelDeccel(JumpBy((200, 0), 75, 3, 3))
        move_jump_rot = AccelDeccel(RotateBy(360, 3))
        self.enemies[4].do(Repeat(move_jump + Reverse(move_jump)))
        self.enemies[4].do(Repeat(move_jump_rot + Reverse(move_jump_rot)))
        self.enemies[5].do(Repeat(Reverse(move_jump) + move_jump))
        self.enemies[5].do(Repeat(Reverse(move_jump_rot) + move_jump_rot))

    def on_key_press(self, symbol, modifiers):
        if symbol == key.LEFT:
            self.player.velocity = -self.player.speed, 0
        elif symbol == key.RIGHT:
            self.player.velocity = self.player.speed, 0
        elif symbol == key.UP:
            self.player.velocity = 0, self.player.speed
        elif symbol == key.DOWN:
            self.player.velocity = 0, -self.player.speed
        elif symbol == key.SPACE:
            self.player.velocity = 0, 0

if __name__ == '__main__':
    cocos.director.director.init(
        width=800,
        height=650,
        caption="Catch your husband!"
    )

    game_layer = Game()
    game_scene = cocos.scene.Scene(game_layer)

    cocos.director.director.run(game_scene)

Bottom henchmen movement

In []:
move_basic = MoveBy((120, 0), 1)
self.enemies[0].do(Repeat(move_basic + Reverse(move_basic)))
self.enemies[1].do(Repeat(Reverse(move_basic) + move_basic))

The bottom two henchmen will move in a very simple way - they will just run 120px left and right.

Middle henchmen movement

In []:
move_complex = (MoveBy((-75, 75), 1) +
                Delay(0.5) +
                MoveBy((-75, -75), 1) +
                Delay(0.5) +
                MoveBy((75, -75), 1) +
                Delay(0.5) +
                MoveBy((75, 75), 1) +
                Delay(0.5))
self.enemies[2].do(Repeat(move_complex))
self.enemies[3].do(Repeat(Reverse(move_complex)))

The movement of the middle henchmen is a little more complex. They will move in a square, stopping for half a second on each of the square's vertices.

Top henchmen movement

In []:
move_jump = AccelDeccel(JumpBy((200, 0), 75, 3, 3))
move_jump_rot = AccelDeccel(RotateBy(360, 3))
self.enemies[4].do(Repeat(move_jump + Reverse(move_jump)))
self.enemies[4].do(Repeat(move_jump_rot + Reverse(move_jump_rot)))
self.enemies[5].do(Repeat(Reverse(move_jump) + move_jump))
self.enemies[5].do(Repeat(Reverse(move_jump_rot) + move_jump_rot))

Here, we want to show off some of the more fancy movements. We will also run two actions at the same time on each henchmen, making them jump around while rotating.

We use a few actions that we haven't seen before here.

The first one is JumpBy, which makes the sprite jump around. We can specify by how much we want to move, the height and number of jumps and the duration. We used JumpBy((200, 0), 75, 3, 3), which makes our henchman jump 200px to the right, jump to the height of 75px and jump three times, all of this in 3 seconds.

The second new action we use is RotateBy, which rotates by an angle in degrees (clockwise, if you want to rotate counter-clockwise, pass a negative angle) in the specified duration. In our case, we rotate a full circle clockwise in 3 seconds.

Both actions are wrapped in the AccelDeccel which can be applied to any action to change the movement so it is not linear any more, but rather starts slow, gets fast in the middle and ends slow.

We then run two actions on each henchmen - one for jumping and one for rotating. Since they both have the same duration, they will be merge to look like a single action. Of course, the duration can be different, creating a mix of different actions.

What comes next?

With Actions, we created a dynamic game, but we still do not have collision detection, which will finally turn our application into a real game that can be won or lost. As you can probably guess, this is exactly what we will do in the next tutorial.

Related tutorials

This tutorial is part of a chain of tutorials:



Previous tutorial: Cocos2d - basic tutorial, part 1 Next tutorial: Cocos2d - basic tutorial, part 3




blog comments powered by Disqus