Introduction To
Game Programming
     EuroPython 2010
       Richard Jones
The Basics
The Basics
• Displaying something
The Basics
• Displaying something
• Controlling animation
The Basics
• Displaying something
• Controlling animation
• User input
The Basics
• Displaying something
• Controlling animation
• User input
• Gameplay mechanics
The Basics
• Displaying something
• Controlling animation
• User input
• Gameplay mechanics
• Playing sound effects and music
The Basics
• Displaying something
• Controlling animation
• User input
• Gameplay mechanics
• Playing sound effects and music
• Game architecture
Displaying Something
Displaying Something

• Opening a window
Displaying Something

• Opening a window
• Reading images
Displaying Something

• Opening a window
• Reading images
• Displaying images
Displaying Something

• Opening a window
• Reading images
• Displaying images
• Organising the display of images (scene
  composition)
Sky
fixed
Distance
 parallax=.5
Midfield
 parallax=.8
Foreground 1
Foreground 2
Game Triggers
Opening a Window
   import cocos
   from cocos.director import director

   director.init()

   scene = cocos.scene.Scene()

   director.run(scene)
Drawing
import cocos
from cocos.director import director

director.init()

scene = cocos.scene.Scene()
sprite = cocos.sprite.Sprite('kitten.jpg')
scene.add(sprite)

director.run(scene)
Anchoring


              (256, 256)
512 px




            512 px
Anchoring




(256, 256)
Anchoring




(0,0)
Anchoring
import cocos
from cocos.director import director

director.init()

scene = cocos.scene.Scene()
sprite = cocos.sprite.Sprite('kitten.jpg', anchor=(0,0))
scene.add(sprite)

director.run(scene)
Using a Layer
import cocos
from cocos.director import director

director.init()

scene = cocos.scene.Scene()
layer = cocos.layer.Layer()
scene.add(layer)
sprite = cocos.sprite.Sprite('kitten.jpg', anchor=(0,0))
layer.add(sprite)

director.run(scene)
Animation
import cocos
from cocos import actions
from cocos.director import director
...
scene = cocos.scene.Scene()
layer = cocos.layer.Layer()
scene.add(layer)
sprite = cocos.sprite.Sprite('kitten.jpg', anchor=(0,0))
layer.add(sprite)

sprite.do(actions.MoveBy((100, 0)))

director.run(scene)
Getting Organised
import cocos
from cocos import actions
from cocos.director import director

class AsteroidsGame(cocos.scene.Scene):
    def __init__(self):
        super(AsteroidsGame, self).__init__()

       self.player = cocos.layer.Layer()
       self.add(self.player)
       self.ship = cocos.sprite.Sprite('data/ship.png', (width/2, height/2))
       velocity = (100, 0)
       self.ship.do(actions.MoveBy(velocity))
       self.player.add(self.ship)

director.init()
width, height = director.get_window_size()
director.run(AsteroidsGame())
Adding Asteroids
import random

import cocos
from cocos import actions
from cocos.director import director

def create_asteroid(layer, size, position, speed):
    '''Create an asteroid of a certain size and speed.
    '''
    s = cocos.sprite.Sprite('data/%s_asteroid.png' % size, position)
    s.size = size
    layer.add(s)
    velocity = (random.randint(-speed, speed), random.randint(-speed, speed))
    dr = random.randint(-speed, speed)
    s.do(actions.Repeat(actions.MoveBy(velocity, 1) | actions.RotateBy(dr, 1)))

...
Adding Asteroids
...
super(AsteroidsGame, self).__init__()

# place the asteroids in a layer by themselves
self.asteroids = cocos.layer.Layer()
self.add(self.asteroids)
for i in range(3):
    # place asteroids at random positions around the screen
    position = (random.randint(0, width), random.randint(0, height))
    create_asteroid(self.asteroids, 'big', position, 100)

# place the player ship in another layer
...
Better action
...
s = cocos.sprite.Sprite('data/%s_asteroid.png' % size, position)
s.size = size
layer.add(s)
s.velocity = (random.randint(-speed, speed), random.randint(-speed, speed))
s.dr = random.randint(-speed, speed)
s.do(actions.Move())
...
Screen Wrapping
...
s.velocity = (random.randint(-speed, speed),
    random.randint(-speed, speed))
s.dr = random.randint(-speed, speed)
s.do(actions.WrappedMove(width, height))
...
    self.ship.velocity = (100, 0)
    # move the ship kinematically and wrapped to the screen size
    self.ship.do(actions.WrappedMove(width, height))
    self.player.add(self.ship)
    ...
Gameplay Mechanics
Gameplay Mechanics
• Player Controls
Gameplay Mechanics
• Player Controls
• Timed rules (when objects appear; moving
  the play field)
Gameplay Mechanics
• Player Controls
• Timed rules (when objects appear; moving
  the play field)
• Interaction of game objects
Gameplay Mechanics
• Player Controls
• Timed rules (when objects appear; moving
  the play field)
• Interaction of game objects
• Detecting important events (game won or
  game over)
User Input
User Input


• Getting events
User Input


• Getting events
• Distinct keyboard events vs. keyboard state
Control
import math
import random

from pyglet.window import key
import cocos
from cocos import actions
from cocos.director import director
Control




                                               y = speed x sin(rotation)
                                         ng)
                                  ho oti
                                rs
                            n, o
                     er atio
                   l
             a cce
     d (or
s pee
        rotation
          x = speed x cos(rotation)
Control
         x = speed x cos(rotation)




                                                y = speed x sin(-rotation)
        rotation
spe
   ed
        (or
              acc
                    ele
                       rat
                          ion
                             , or
                                    sho
                                       oti
                                          ng)
Control
class MoveShip(actions.WrappedMove):
    def step(self, dt):
        super(MoveShip, self).step(dt)

       # set the rotation using the left and right arrow keys
       self.target.dr = (keys[key.RIGHT] - keys[key.LEFT]) * 360

       # figure the x and y heading components from the rotation
       rotation = math.radians(self.target.rotation)
       rotation_x = math.cos(rotation)
       rotation_y = math.sin(-rotation)
       # accelerate along the heading
       acc = keys[key.UP] * 200
       self.target.acceleration = (acc * rotation_x, acc * rotation_y)
Control

...
self.ship = cocos.sprite.Sprite('data/ship.png', (width/2, height/2))
self.ship.velocity = (0, 0)
# animate the ship with our custom action
self. ship.do(MoveShip(width, height))
self.player.add(self. ship)
...
Control

...
# open the window, get dimensions, watch the keyboard and run our scene
director.init()
width, height = director.get_window_size()
keys = key.KeyStateHandler()
director.window.push_handlers(keys)
director.run(AsteriodsGame())
Detecting Collisions
Detecting Collisions

• Pixel-Perfect
Detecting Collisions

• Pixel-Perfect
• Axis-Aligned Bounding Box
Detecting Collisions

• Pixel-Perfect
• Axis-Aligned Bounding Box
• Circle-Circle
Detecting Collisions

• Pixel-Perfect
• Axis-Aligned Bounding Box
• Circle-Circle
• Hash Map
Detecting Collisions
      Pixel-Perfect
Detecting Collisions
   Axis-Aligned Bounding Box
Detecting Collisions
   Axis-Aligned Bounding Box
Detecting Collisions
   Axis-Aligned Bounding Box
Detecting Collisions
      Circle-Circle
Detecting Collisions
      Circle-Circle
Detecting Collisions
      Circle-Circle
Detecting Collisions
      Circle-Circle
Detecting Collisions
                       Hash Map




d = {(42,42): [ship], (43, 42): [ship], (44, 42): ship, ...
      (52, 45): [asteroid], (53, 45): [asteroid], ...}
Collision Detection
Collision Detection
Collision Detection
Collision Detection
Collision Detection

       distance
...
                       Collision
        self.target.acceleration = (acc * rotation_x, acc * rotation_y)



def collide(a, b):
    '''Determine whether two objects with a center point and width
    (diameter) are colliding.'''
    distance = math.sqrt((a.x-b.x)**2 + (a.y-b.y)**2)
    return distance < (a.width/2 + b.width/2)



class MoveAsteroid(actions.WrappedMove):
    def step(self, dt):
        super(MoveAsteroid, self).step(dt)

        # detect collision between this asteroid and the ship
        if collide(self.target, director.scene.ship):
            quit('GAME OVER')



def create_asteroid(layer, size, position, speed):
    ...
Collision

def create_asteroid(layer, size, position, speed):
    '''Create an asteroid of a certain size and speed.
    '''
    s = cocos.sprite.Sprite('data/%s_asteroid.png' % size, position)
    s.size = size
    layer.add(s)
    s.velocity = (random.randint(-speed, speed), random.randint(-speed, speed))
    s.dr = random.randint(-speed, speed)
    s.do(MoveAsteroid(width, height))

...
Shooting
      ...
      self.target.acceleration = (acc * rotation_x, acc * rotation_y)

      if self.target.gun_cooldown:
          # still limiting the gun rate of fire
          self.target.gun_cooldown = max(0, self.target.gun_cooldown - dt)
      elif keys[key.SPACE]:
          # fire a bullet from the ship
          b = cocos.sprite.Sprite('data/bullet.png', (self.target.x,
              self.target.y))
          # send it in the same heading as the ship
          b.velocity = (rotation_x * 400, rotation_y * 400)
          # the bullet has a lifespan of 1 second
          b.life = 1
          director.scene.player.add(b)
          # move the bullet with its custom action
          b.do(MoveBullet(width, height))
          # ship may only shoot twice per second
          self.target.gun_cooldown = .5

...
Shooting
class MoveBullet(actions.WrappedMove):
    def step(self, dt):
        super(MoveBullet, self).step(dt)

       # age the bullet
       self.target.life -= dt
       if self.target.life < 0:
           # remove from play if it's too old
           self.target.kill()
           return

       # see if the bullet hits any asteroids
       for asteroid in director.scene.asteroids.get_children():
           if collide(self.target, asteroid):
               # remove the bullet and asteroid
               self.target.kill()
               asteroid.kill()
               return
... and winning

...
super(MoveShip, self).step(dt)

# if there's no asteroids left then the player has won
if not director.scene.asteroids.children:
    quit('YOU WIN')

# set the rotation using the left and right arrow keys
self.target.dr = (keys[key.RIGHT] - keys[key.LEFT]) * 360
...
Creating chunks
def create_asteroid(layer, size, position, velocity, rotation, speed):
    '''Create an asteroid of a certain size, possibly inheriting its
    parent's velocity and rotation.
    '''
    s = cocos.sprite.Sprite('data/%s_asteroid.png' % size, position)
    s.size = size
    dx, dy = velocity
    s.velocity = (dx + random.randint(-speed, speed),
        dy + random.randint(-speed, speed))
    s.dr = rotation + random.randint(-speed, speed)
    layer.add(s)
    s.do(MoveAsteroid(width, height))
Creating chunks

for asteroid in director.scene.asteroids.get_children():
    if collide(self.target, asteroid):
        # remove the bullet and asteroid, create new smaller
        # asteroid
        self.target.kill()
        create_smaller_asteroids(asteroid)
        asteroid.kill()
        return
Creating chunks
            ...
            position = (random.randint(0, width), random.randint(0, height))
            create_asteroid(self.asteroids, 'big', position, (0, 0), 0, 100)



def create_smaller_asteroids(asteroid):
    asteroids = director.scene.asteroids
    if asteroid.size == 'big':
        # if it's a big asteroid then make two medium asteroids in
        # its place
        for i in range(2):
            create_asteroid(asteroids, 'medium', asteroid.position,
                asteroid.velocity, asteroid.dr, 50)
    elif asteroid.size == 'medium':
        # if it's a medium asteroid then make two small asteroids in
        # its place
        for i in range(2):
            create_asteroid(asteroids, 'small', asteroid.position,
                asteroid.velocity, asteroid.dr, 50)
Sound
Sound


• Reading sound files
Sound


• Reading sound files
• Playing sounds and background music
Sound Effects
import math
import random

import pyglet
from pyglet.window import key
import cocos
from cocos import actions
from cocos.director import director



# load our sound effects
explosion_sound = pyglet.media.load('data/explosion.wav', streaming=False)
bullet_sound = pyglet.media.load('data/bullet.wav', streaming=False)

...
Sound Effects


       ...
       # ship may only shoot twice per second
       self.target.gun_cooldown = .5
       bullet_sound.play()

...
Sound Effects

 ...
 create_smaller_asteroids(asteroid)
 asteroid.kill()
 explosion_sound.play()
 return
Game Architecture
Game Architecture

• Player lives
Game Architecture

• Player lives
• Game won / game over screen
Game Architecture

• Player lives
• Game won / game over screen
• Opening screen
Game Architecture

• Player lives
• Game won / game over screen
• Opening screen
• Options menu
Invulnerability
class Ship(cocos.sprite.Sprite):
    gun_cooldown = 0
    velocity = (0, 0)
    is_invulnerable = False

   def set_invulnerable(self):
       self.do(actions.Blink(10, 1) +
           actions.CallFunc(self.set_vulnerable))
       self.is_invulnerable = True

   def set_vulnerable(self):
       self.is_invulnerable = False
Invulnerability

self.player = cocos.layer.Layer()
self.add(self.player)
self.ship = Ship('data/ship.png', (width/2, height/2))
self.player.add(self.ship)

# animate the ship with our custom action
self.ship.do(MoveShip(width, height))
self.ship.set_invulnerable()
Invulnerability

super(MoveAsteroid, self).step(dt)

if director.scene.ship.is_invulnerable:
    return

# detect collision between this asteroid and the ship
if collide(self.target, director.scene.ship):
    quit('GAME OVER')
Lives

 self.ship.set_invulnerable()
 self.player.add(self.ship)



# another layer for the "HUD" or front informational display
self.hud = cocos.layer.Layer()
self.add(self.hud)
self.lives = cocos.text.Label('Lives: 3', font_size=20, y=height, anchor_y='top')
self.lives.counter = 3
self.hud.add(self.lives)
Lives

# detect collision between this asteroid and the ship
if collide(self.target, director.scene.ship):
    lives = director.scene.lives
    lives.counter -= 1
    director.scene.ship.set_invulnerable()
    if not lives.counter:
        quit('GAME OVER')
    else:
        lives.element.text = 'Lives: %d' % lives.counter
Scenes
class MessageScene(cocos.scene.Scene):
    def __init__(self, text):
        super(MessageScene, self).__init__()
        class UserActivity(cocos.layer.Layer):
            is_event_handler = True
            def on_mouse_press(*args): director.pop()
            def on_text(*args): director.pop()
        layer = UserActivity()
        self.add(layer)
        t = cocos.text.Label(text, font_size=40,
            x=width/2, anchor_x='center',
            y=height/2, anchor_y='center')
        layer.add(t)
Scenes
      # if there's no asteroids left then the player has won
      if not director.scene.asteroids.children:
          director.replace(MessageScene('You Win'))

      # set the rotation using the left and right arrow keys
      self.target.dr = (keys[key.RIGHT] - keys[key.LEFT]) * 360
...
          lives.counter -= 1
          director.scene.ship.set_invulnerable()
          if not lives.counter:
              director.replace(MessageScene('Game Over'))
          else:
              lives.element.text = 'Lives: %d' % lives.counter
Menu
class MenuScene(cocos.scene.Scene):
    def __init__(self):
        super(MenuScene, self).__init__()

        # opening menu
        menu = cocos.menu.Menu("Asteroids!!!!")
        menu.create_menu([
            cocos.menu.MenuItem('Play', lambda: director.push(AsteroidsGame())),
            cocos.menu.MenuItem('Quit', pyglet.app.exit),
        ])
        menu.on_quit = pyglet.app.exit
        self.add(menu)

# open the window, get dimensions, watch the keyboard and run our scene
director.init()
width, height = director.get_window_size()
keys = key.KeyStateHandler()
director.window.push_handlers(keys)
director.run(MenuScene())
Special Effects
Special Effects


• Simple image animations
Special Effects


• Simple image animations
• Use libraries like lepton
Controlling Animation
Controlling Animation

• Image position on screen
Controlling Animation

• Image position on screen
• Updates over time
Controlling Animation

• Image position on screen
• Updates over time
• Accounting for frame rate
Controlling Animation

• Image position on screen
• Updates over time
• Accounting for frame rate
• Frame to use from multiple images
Animation from
multiple images
Animation from
multiple images
Special Effects
Special Effects

...
explosion_sound = pyglet.media.load('data/explosion.wav', streaming=False)
bullet_sound = pyglet.media.load('data/bullet.wav', streaming=False)

# load our explosion animation
explosion = pyglet.image.load('data/explosion.png')
explosion_grid = pyglet.image.ImageGrid(explosion, 2, 8)
explosion = explosion_grid.get_animation(.05, False)

...
Special Effects

# remove the bullet and asteroid, create new smaller
# asteroid
self.target.kill()
s = cocos.sprite.Sprite(explosion, self.target.position)
s.velocity = asteroid.velocity
s.do(actions.WrappedMove(width, height) |
    (actions.Delay(.05 * 16) +
    actions.CallFuncS(lambda s: s.kill())))
self.target.parent.add(s)
create_smaller_asteroids(asteroid)
asteroid.kill()
Other Game Types

• minesweeper
• car driving
• platformer
• tower defence
Minesweeper

• rendering a grid of cells
• detecting mouse clicks on cells
• exposing contents
Car Driving

• different movement model
• detecting correct circuit
• detecting collision with edge of track
  (pixel-based collision detection)
Platformer

• rendering game layers
• handling triggers
• platformer physics
Tower Defence


• tower placement (selection & position)
• movement solution for creeps
Other Things

• kytten
  GUI controls for pyglet / cocos2d
• cocograph
  map editing
Where To From Here?

• http://los-cocos.org
• http://pyglet.org
• http://pygame.org
• http://inventwithpython.com/
• http://pyweek.org

Intro to Game Programming

  • 1.
    Introduction To Game Programming EuroPython 2010 Richard Jones
  • 2.
  • 3.
  • 4.
    The Basics • Displayingsomething • Controlling animation
  • 5.
    The Basics • Displayingsomething • Controlling animation • User input
  • 6.
    The Basics • Displayingsomething • Controlling animation • User input • Gameplay mechanics
  • 7.
    The Basics • Displayingsomething • Controlling animation • User input • Gameplay mechanics • Playing sound effects and music
  • 8.
    The Basics • Displayingsomething • Controlling animation • User input • Gameplay mechanics • Playing sound effects and music • Game architecture
  • 9.
  • 10.
  • 11.
    Displaying Something • Openinga window • Reading images
  • 12.
    Displaying Something • Openinga window • Reading images • Displaying images
  • 13.
    Displaying Something • Openinga window • Reading images • Displaying images • Organising the display of images (scene composition)
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
    Opening a Window import cocos from cocos.director import director director.init() scene = cocos.scene.Scene() director.run(scene)
  • 21.
    Drawing import cocos from cocos.directorimport director director.init() scene = cocos.scene.Scene() sprite = cocos.sprite.Sprite('kitten.jpg') scene.add(sprite) director.run(scene)
  • 22.
    Anchoring (256, 256) 512 px 512 px
  • 23.
  • 24.
  • 25.
    Anchoring import cocos from cocos.directorimport director director.init() scene = cocos.scene.Scene() sprite = cocos.sprite.Sprite('kitten.jpg', anchor=(0,0)) scene.add(sprite) director.run(scene)
  • 26.
    Using a Layer importcocos from cocos.director import director director.init() scene = cocos.scene.Scene() layer = cocos.layer.Layer() scene.add(layer) sprite = cocos.sprite.Sprite('kitten.jpg', anchor=(0,0)) layer.add(sprite) director.run(scene)
  • 27.
    Animation import cocos from cocosimport actions from cocos.director import director ... scene = cocos.scene.Scene() layer = cocos.layer.Layer() scene.add(layer) sprite = cocos.sprite.Sprite('kitten.jpg', anchor=(0,0)) layer.add(sprite) sprite.do(actions.MoveBy((100, 0))) director.run(scene)
  • 28.
    Getting Organised import cocos fromcocos import actions from cocos.director import director class AsteroidsGame(cocos.scene.Scene): def __init__(self): super(AsteroidsGame, self).__init__() self.player = cocos.layer.Layer() self.add(self.player) self.ship = cocos.sprite.Sprite('data/ship.png', (width/2, height/2)) velocity = (100, 0) self.ship.do(actions.MoveBy(velocity)) self.player.add(self.ship) director.init() width, height = director.get_window_size() director.run(AsteroidsGame())
  • 29.
    Adding Asteroids import random importcocos from cocos import actions from cocos.director import director def create_asteroid(layer, size, position, speed): '''Create an asteroid of a certain size and speed. ''' s = cocos.sprite.Sprite('data/%s_asteroid.png' % size, position) s.size = size layer.add(s) velocity = (random.randint(-speed, speed), random.randint(-speed, speed)) dr = random.randint(-speed, speed) s.do(actions.Repeat(actions.MoveBy(velocity, 1) | actions.RotateBy(dr, 1))) ...
  • 30.
    Adding Asteroids ... super(AsteroidsGame, self).__init__() #place the asteroids in a layer by themselves self.asteroids = cocos.layer.Layer() self.add(self.asteroids) for i in range(3): # place asteroids at random positions around the screen position = (random.randint(0, width), random.randint(0, height)) create_asteroid(self.asteroids, 'big', position, 100) # place the player ship in another layer ...
  • 31.
    Better action ... s =cocos.sprite.Sprite('data/%s_asteroid.png' % size, position) s.size = size layer.add(s) s.velocity = (random.randint(-speed, speed), random.randint(-speed, speed)) s.dr = random.randint(-speed, speed) s.do(actions.Move()) ...
  • 32.
    Screen Wrapping ... s.velocity =(random.randint(-speed, speed), random.randint(-speed, speed)) s.dr = random.randint(-speed, speed) s.do(actions.WrappedMove(width, height)) ... self.ship.velocity = (100, 0) # move the ship kinematically and wrapped to the screen size self.ship.do(actions.WrappedMove(width, height)) self.player.add(self.ship) ...
  • 33.
  • 34.
  • 35.
    Gameplay Mechanics • PlayerControls • Timed rules (when objects appear; moving the play field)
  • 36.
    Gameplay Mechanics • PlayerControls • Timed rules (when objects appear; moving the play field) • Interaction of game objects
  • 37.
    Gameplay Mechanics • PlayerControls • Timed rules (when objects appear; moving the play field) • Interaction of game objects • Detecting important events (game won or game over)
  • 38.
  • 39.
  • 40.
    User Input • Gettingevents • Distinct keyboard events vs. keyboard state
  • 41.
    Control import math import random frompyglet.window import key import cocos from cocos import actions from cocos.director import director
  • 42.
    Control y = speed x sin(rotation) ng) ho oti rs n, o er atio l a cce d (or s pee rotation x = speed x cos(rotation)
  • 43.
    Control x = speed x cos(rotation) y = speed x sin(-rotation) rotation spe ed (or acc ele rat ion , or sho oti ng)
  • 44.
    Control class MoveShip(actions.WrappedMove): def step(self, dt): super(MoveShip, self).step(dt) # set the rotation using the left and right arrow keys self.target.dr = (keys[key.RIGHT] - keys[key.LEFT]) * 360 # figure the x and y heading components from the rotation rotation = math.radians(self.target.rotation) rotation_x = math.cos(rotation) rotation_y = math.sin(-rotation) # accelerate along the heading acc = keys[key.UP] * 200 self.target.acceleration = (acc * rotation_x, acc * rotation_y)
  • 45.
    Control ... self.ship = cocos.sprite.Sprite('data/ship.png',(width/2, height/2)) self.ship.velocity = (0, 0) # animate the ship with our custom action self. ship.do(MoveShip(width, height)) self.player.add(self. ship) ...
  • 46.
    Control ... # open thewindow, get dimensions, watch the keyboard and run our scene director.init() width, height = director.get_window_size() keys = key.KeyStateHandler() director.window.push_handlers(keys) director.run(AsteriodsGame())
  • 47.
  • 48.
  • 49.
  • 50.
    Detecting Collisions • Pixel-Perfect •Axis-Aligned Bounding Box • Circle-Circle
  • 51.
    Detecting Collisions • Pixel-Perfect •Axis-Aligned Bounding Box • Circle-Circle • Hash Map
  • 52.
    Detecting Collisions Pixel-Perfect
  • 53.
    Detecting Collisions Axis-Aligned Bounding Box
  • 54.
    Detecting Collisions Axis-Aligned Bounding Box
  • 55.
    Detecting Collisions Axis-Aligned Bounding Box
  • 56.
    Detecting Collisions Circle-Circle
  • 57.
    Detecting Collisions Circle-Circle
  • 58.
    Detecting Collisions Circle-Circle
  • 59.
    Detecting Collisions Circle-Circle
  • 60.
    Detecting Collisions Hash Map d = {(42,42): [ship], (43, 42): [ship], (44, 42): ship, ... (52, 45): [asteroid], (53, 45): [asteroid], ...}
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
    ... Collision self.target.acceleration = (acc * rotation_x, acc * rotation_y) def collide(a, b): '''Determine whether two objects with a center point and width (diameter) are colliding.''' distance = math.sqrt((a.x-b.x)**2 + (a.y-b.y)**2) return distance < (a.width/2 + b.width/2) class MoveAsteroid(actions.WrappedMove): def step(self, dt): super(MoveAsteroid, self).step(dt) # detect collision between this asteroid and the ship if collide(self.target, director.scene.ship): quit('GAME OVER') def create_asteroid(layer, size, position, speed): ...
  • 67.
    Collision def create_asteroid(layer, size,position, speed): '''Create an asteroid of a certain size and speed. ''' s = cocos.sprite.Sprite('data/%s_asteroid.png' % size, position) s.size = size layer.add(s) s.velocity = (random.randint(-speed, speed), random.randint(-speed, speed)) s.dr = random.randint(-speed, speed) s.do(MoveAsteroid(width, height)) ...
  • 68.
    Shooting ... self.target.acceleration = (acc * rotation_x, acc * rotation_y) if self.target.gun_cooldown: # still limiting the gun rate of fire self.target.gun_cooldown = max(0, self.target.gun_cooldown - dt) elif keys[key.SPACE]: # fire a bullet from the ship b = cocos.sprite.Sprite('data/bullet.png', (self.target.x, self.target.y)) # send it in the same heading as the ship b.velocity = (rotation_x * 400, rotation_y * 400) # the bullet has a lifespan of 1 second b.life = 1 director.scene.player.add(b) # move the bullet with its custom action b.do(MoveBullet(width, height)) # ship may only shoot twice per second self.target.gun_cooldown = .5 ...
  • 69.
    Shooting class MoveBullet(actions.WrappedMove): def step(self, dt): super(MoveBullet, self).step(dt) # age the bullet self.target.life -= dt if self.target.life < 0: # remove from play if it's too old self.target.kill() return # see if the bullet hits any asteroids for asteroid in director.scene.asteroids.get_children(): if collide(self.target, asteroid): # remove the bullet and asteroid self.target.kill() asteroid.kill() return
  • 70.
    ... and winning ... super(MoveShip,self).step(dt) # if there's no asteroids left then the player has won if not director.scene.asteroids.children: quit('YOU WIN') # set the rotation using the left and right arrow keys self.target.dr = (keys[key.RIGHT] - keys[key.LEFT]) * 360 ...
  • 71.
    Creating chunks def create_asteroid(layer,size, position, velocity, rotation, speed): '''Create an asteroid of a certain size, possibly inheriting its parent's velocity and rotation. ''' s = cocos.sprite.Sprite('data/%s_asteroid.png' % size, position) s.size = size dx, dy = velocity s.velocity = (dx + random.randint(-speed, speed), dy + random.randint(-speed, speed)) s.dr = rotation + random.randint(-speed, speed) layer.add(s) s.do(MoveAsteroid(width, height))
  • 72.
    Creating chunks for asteroidin director.scene.asteroids.get_children(): if collide(self.target, asteroid): # remove the bullet and asteroid, create new smaller # asteroid self.target.kill() create_smaller_asteroids(asteroid) asteroid.kill() return
  • 73.
    Creating chunks ... position = (random.randint(0, width), random.randint(0, height)) create_asteroid(self.asteroids, 'big', position, (0, 0), 0, 100) def create_smaller_asteroids(asteroid): asteroids = director.scene.asteroids if asteroid.size == 'big': # if it's a big asteroid then make two medium asteroids in # its place for i in range(2): create_asteroid(asteroids, 'medium', asteroid.position, asteroid.velocity, asteroid.dr, 50) elif asteroid.size == 'medium': # if it's a medium asteroid then make two small asteroids in # its place for i in range(2): create_asteroid(asteroids, 'small', asteroid.position, asteroid.velocity, asteroid.dr, 50)
  • 74.
  • 75.
  • 76.
    Sound • Reading soundfiles • Playing sounds and background music
  • 77.
    Sound Effects import math importrandom import pyglet from pyglet.window import key import cocos from cocos import actions from cocos.director import director # load our sound effects explosion_sound = pyglet.media.load('data/explosion.wav', streaming=False) bullet_sound = pyglet.media.load('data/bullet.wav', streaming=False) ...
  • 78.
    Sound Effects ... # ship may only shoot twice per second self.target.gun_cooldown = .5 bullet_sound.play() ...
  • 79.
    Sound Effects ... create_smaller_asteroids(asteroid) asteroid.kill() explosion_sound.play() return
  • 80.
  • 81.
  • 82.
    Game Architecture • Playerlives • Game won / game over screen
  • 83.
    Game Architecture • Playerlives • Game won / game over screen • Opening screen
  • 84.
    Game Architecture • Playerlives • Game won / game over screen • Opening screen • Options menu
  • 85.
    Invulnerability class Ship(cocos.sprite.Sprite): gun_cooldown = 0 velocity = (0, 0) is_invulnerable = False def set_invulnerable(self): self.do(actions.Blink(10, 1) + actions.CallFunc(self.set_vulnerable)) self.is_invulnerable = True def set_vulnerable(self): self.is_invulnerable = False
  • 86.
    Invulnerability self.player = cocos.layer.Layer() self.add(self.player) self.ship= Ship('data/ship.png', (width/2, height/2)) self.player.add(self.ship) # animate the ship with our custom action self.ship.do(MoveShip(width, height)) self.ship.set_invulnerable()
  • 87.
    Invulnerability super(MoveAsteroid, self).step(dt) if director.scene.ship.is_invulnerable: return # detect collision between this asteroid and the ship if collide(self.target, director.scene.ship): quit('GAME OVER')
  • 88.
    Lives self.ship.set_invulnerable() self.player.add(self.ship) #another layer for the "HUD" or front informational display self.hud = cocos.layer.Layer() self.add(self.hud) self.lives = cocos.text.Label('Lives: 3', font_size=20, y=height, anchor_y='top') self.lives.counter = 3 self.hud.add(self.lives)
  • 89.
    Lives # detect collisionbetween this asteroid and the ship if collide(self.target, director.scene.ship): lives = director.scene.lives lives.counter -= 1 director.scene.ship.set_invulnerable() if not lives.counter: quit('GAME OVER') else: lives.element.text = 'Lives: %d' % lives.counter
  • 90.
    Scenes class MessageScene(cocos.scene.Scene): def __init__(self, text): super(MessageScene, self).__init__() class UserActivity(cocos.layer.Layer): is_event_handler = True def on_mouse_press(*args): director.pop() def on_text(*args): director.pop() layer = UserActivity() self.add(layer) t = cocos.text.Label(text, font_size=40, x=width/2, anchor_x='center', y=height/2, anchor_y='center') layer.add(t)
  • 91.
    Scenes # if there's no asteroids left then the player has won if not director.scene.asteroids.children: director.replace(MessageScene('You Win')) # set the rotation using the left and right arrow keys self.target.dr = (keys[key.RIGHT] - keys[key.LEFT]) * 360 ... lives.counter -= 1 director.scene.ship.set_invulnerable() if not lives.counter: director.replace(MessageScene('Game Over')) else: lives.element.text = 'Lives: %d' % lives.counter
  • 92.
    Menu class MenuScene(cocos.scene.Scene): def __init__(self): super(MenuScene, self).__init__() # opening menu menu = cocos.menu.Menu("Asteroids!!!!") menu.create_menu([ cocos.menu.MenuItem('Play', lambda: director.push(AsteroidsGame())), cocos.menu.MenuItem('Quit', pyglet.app.exit), ]) menu.on_quit = pyglet.app.exit self.add(menu) # open the window, get dimensions, watch the keyboard and run our scene director.init() width, height = director.get_window_size() keys = key.KeyStateHandler() director.window.push_handlers(keys) director.run(MenuScene())
  • 93.
  • 94.
  • 95.
    Special Effects • Simpleimage animations • Use libraries like lepton
  • 96.
  • 97.
  • 98.
    Controlling Animation • Imageposition on screen • Updates over time
  • 99.
    Controlling Animation • Imageposition on screen • Updates over time • Accounting for frame rate
  • 100.
    Controlling Animation • Imageposition on screen • Updates over time • Accounting for frame rate • Frame to use from multiple images
  • 101.
  • 102.
  • 103.
  • 104.
    Special Effects ... explosion_sound =pyglet.media.load('data/explosion.wav', streaming=False) bullet_sound = pyglet.media.load('data/bullet.wav', streaming=False) # load our explosion animation explosion = pyglet.image.load('data/explosion.png') explosion_grid = pyglet.image.ImageGrid(explosion, 2, 8) explosion = explosion_grid.get_animation(.05, False) ...
  • 105.
    Special Effects # removethe bullet and asteroid, create new smaller # asteroid self.target.kill() s = cocos.sprite.Sprite(explosion, self.target.position) s.velocity = asteroid.velocity s.do(actions.WrappedMove(width, height) | (actions.Delay(.05 * 16) + actions.CallFuncS(lambda s: s.kill()))) self.target.parent.add(s) create_smaller_asteroids(asteroid) asteroid.kill()
  • 106.
    Other Game Types •minesweeper • car driving • platformer • tower defence
  • 107.
    Minesweeper • rendering agrid of cells • detecting mouse clicks on cells • exposing contents
  • 108.
    Car Driving • differentmovement model • detecting correct circuit • detecting collision with edge of track (pixel-based collision detection)
  • 109.
    Platformer • rendering gamelayers • handling triggers • platformer physics
  • 110.
    Tower Defence • towerplacement (selection & position) • movement solution for creeps
  • 111.
    Other Things • kytten GUI controls for pyglet / cocos2d • cocograph map editing
  • 112.
    Where To FromHere? • http://los-cocos.org • http://pyglet.org • http://pygame.org • http://inventwithpython.com/ • http://pyweek.org

Editor's Notes

  • #20 A Sprite is an image that knows how to draw itself on the screen.
  • #21 The default anchor point in cocos2d is the center of the sprite image.
  • #40 Unfortunately cocos2d rotates clockwise around Z, whereas pyglet (and basic trigenometry) rotates anti-clockwise, so we must negate the rotation to determine the correct y value.
  • #93 Make life easier for yourself - make all your animation frames the same size!
  • #94 Make life easier for yourself - make all your animation frames the same size!
  • #96 pyglet will sequence a grid of images from the bottom left corner taking each row, and then each cell in turn