Contents [show/hide]

A very basic Cocos2d tutorial, part 3

Now that we have the movement done, we need to set up collision detection so we can actually win or lose the game.

For this, we will use a simple brute force algorithm from Cocos2d. There are better options inside Cocos2d - and we can write even better ones ourselves - but we will keep this as simple as possible.

Collision manager

First, we need to create a collision manager. Once we create a collision manager and add some objects to it, we can "ask" it questions like:

  • Which known objects collide with selected object
  • Which known objects are nearer then 'X' pixels to selected object
  • ...

As we will use a simple brute force algorithm, we will use:

In []:
import cocos.collision_model as cm

collision_manager = cm.CollisionManagerBruteForce()

Adding sprites to the collision manager

If we want to add a sprite to the collision manager, we need to make sure that the sprite instance has a .cshape property, which is either cocos.collision_model.AARectShape (rectangle shape) or cocos.collision_model.AACircleShape (circle shape). If everything is ready, we can simply add it to the collision manager via it's .add() method.

Keep in mind that we cannot mix the two types of shapes inside a single collision manager, but we can have many collision managers (one for the player and walls, one for the player and enemies etc.).

In []:
import cocos.collision_model as cm

collision_manager = cm.CollisionManagerBruteForce()

sprite = cocos.sprite.Sprite(resources.image1)
sprite.position = 200, 200
sprite.cshape = cm.AARectShape(
    sprite.position,
    sprite.width//2,
    sprite.height//2
)

Checking for collisions on each frame

First of all, make sure to remember that the .cshape property does NOT move around with the sprite. This means that if we move the sprite, but forget to change it's .cshape property collision won't work as we wanted! As such, we need to make sure to update all .cshape properties before checking for collisions.

We will of course need to check for collisions on each frame. To do this, we will use the schedule() function, which calls the specified function each frame. We schedule a function simply by calling something like:

In []:
schedule(update)

In the update function, we will usually want to update the .cshape properties and check if anything is colliding. Any function called by the schedule() function needs to accept a dt argument, which is the time passed between the last and current call.

In this example, we will update the player and enemies .cshape properties and check if anything is colliding with the player - if it is, end the game (pop the Scene from the Director).

In []:
def update(dt):
    player.cshape.center = player.position
    for enemy in enemies:
        enemy.cshape.center = enemy.position

    collisions = collision_manager.objs_colliding(player)
    if collisions:
        cocos.director.director.pop()

Adding collision detection to our game

When creating the player, each of the enemies and the boss, we also need to create it's .cshape property (we will of course use rectangles (cm.AARectShape in our case) and add it to the collision manager. We will only use one collision manager.

We will also create an update() function which will be called on each frame via the schedule() function. In this function, we will first update all .cshape positions and then check for any collisions with the player. If we find any, we either print "You won!" and close the game (when hitting the boss) or just close the game (when hitting anyone else).

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 cocos.collision_model as cm
import resources


class Game(cocos.layer.ColorLayer):
    is_event_handler = True

    def __init__(self):
        super(Game, self).__init__(102, 102, 225, 255)

        self.collision_manager = cm.CollisionManagerBruteForce()

        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.player.cshape = cm.AARectShape(
            self.player.position,
            self.player.width//2,
            self.player.height//2
        )
        self.collision_manager.add(self.player)

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

        self.boss.cshape = cm.AARectShape(
            self.boss.position,
            self.boss.width//2,
            self.boss.height//2
        )
        self.collision_manager.add(self.boss)

        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]
            enem.cshape = cm.AARectShape(
                enem.position,
                enem.width//2,
                enem.height//2
            )
            self.collision_manager.add(enem)
            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))

        self.schedule(self.update)

    def update(self, dt):
        self.player.cshape.center = self.player.position
        for enem in self.enemies:
            enem.cshape.center = enem.position

        collisions = self.collision_manager.objs_colliding(self.player)
        if collisions:
            if self.boss in collisions:
                print("You won!")
            cocos.director.director.pop()

    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)

What comes next?

With collision detection, we can finally play our game and actually win or lose it (albeit with a very anti-climatic "You won!" print to the console while the game closes). Next on the list is adding menus with some simple options (FPS counter, fullscreen switch), which will allow us to nicely start (and re-start, as we just know everyone will want another crack at catching that pesky husband :) ) the game and give it a more "finished" feeling all over.

Related tutorials

This tutorial is part of a chain of tutorials:



Previous tutorial: Cocos2d - basic tutorial, part 2




blog comments powered by Disqus