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.
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:
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
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.).
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 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).
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).
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.do(Repeat(move_basic + Reverse(move_basic))) self.enemies.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.do(Repeat(move_complex)) self.enemies.do(Repeat(Reverse(move_complex))) move_jump = AccelDeccel(JumpBy((200, 0), 75, 3, 3)) move_jump_rot = AccelDeccel(RotateBy(360, 3)) self.enemies.do(Repeat(move_jump + Reverse(move_jump))) self.enemies.do(Repeat(move_jump_rot + Reverse(move_jump_rot))) self.enemies.do(Repeat(Reverse(move_jump) + move_jump)) self.enemies.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.
This tutorial is part of a chain of tutorials:
- Pyglet - basic tutorial
- Cocos2d - basic tutorial, part 1
- Cocos2d - basic tutorial, part 2
- Cocos2d - basic tutorial, part 3 - current
Previous tutorial: Cocos2d - basic tutorial, part 2
blog comments powered by Disqus