#GameDev at Python Vigo:

Game development basics
(in Python)

2015/05/21

Slides powered by Reveal.js

Player 1:

  • Name: Adam
  • Class: Developer
  • Skills: PHP, Java, Python and Javascript

Components

Story + Graphics + Music/effects + Mechanics

Story + Graphics + Music/effects + Mechanics

Bioshock © 2007 2K Boston and 2K Australia

Bioshock Infinite © 2013 Irrational Games

The Last of Us © 2013 Naughty Dog

Monkey Island © (1990/2009) Lucasfilm Games

Story + Graphics + Music/effects + Mechanics

Destiny © 2014 Bungie

Crysis 3 © 2013 Crytek UK and Crytek Frankfurt

Call of Duty: Advanced Warfare © 2014 Sledgehammer Games

Story + Graphics + Music/effects + Mechanics

Guitar Hero © 2007 Neversoft

Story + Graphics + Music/effects + Mechanics

Portal © 2007 Valve

Isn't all about the money or team size

Minecraft @ 2011 Mojang AB

Fez @ 2013 Polytron

Bastion @ 2011 Supergiant Games

Dust: An Elysian Tail @ 2012 Humble Hearts

Spelunky @ 2009 Derek Yu

Motivation

Communities

Communities (II)

Resources

Resources (II)

Resources (III)

Tools

So what we can do/use in Python?

Pygame

Release: 2000-10 / Stable: 1.9.1 (2009-08)

Pyglet

Pyglet

Alpha Release: 2007-06 / Stable: 1.1.4 (2010-01), 1.2.2 (2015-03)

Cocos2d

Release: 2008-02 / Stable: 0.6.3 (2015-04)

Kivy

Kivy

Release: 2012-09 / Stable: 1.9.0 (2015-04)

Blender

Game Engine

Release: 2000 / Stable: 2.74 (2015-03)

Cocos2d

Python 3

Low learning curve

Pyglet API access

Development based on Nodes, Scenes and Layers

Actions and transformations effects

Basic Collision engine

...

Cocos2d Basics

Node: Base class that exposes Cocos2d core characteristics and functionalites:

  • Parent-child
  • Spatial placement
  • Events
  • Rendering
  • Time and actions management

[Cocos2d - More about Nodes]

Cocos2d Basics (II)

Scenes

Director: Singleton object that manages the main window and scenes workflow

[Cocos2d - Director]

Cocos2d Basics (III)

Layers

Cocos2d Basics (IV)


import cocos

# structured style
def create_layer():
    a_layer = cocos.layer.Layer()
    a_label = cocos.text.Label('structured')
    a_label.position = 320, 240 # x, y
    a_layer.add(a_label)
    return a_layer
    
    
# object oriented style
class MyLayer(cocos.layer.Layer):
    def __init__(self):
        super(MyLayer, self).__init__()        
        a_label = cocos.text.Label('Object oriented')
        a_label.position = 320, 100 # x, y
        self.add(a_label)
    
    
if __name__ == '__main__': 
    cocos.director.director.init(width=640, height=480)
    a_scene = cocos.scene.Scene()
    a_scene.add(create_layer(), z = 1) 
    a_scene.add(myLayer(), z = 0)
    cocos.director.director.run(a_scene)

                        

Actions and transformations

Actions are grouped orders received by a node resulting in a specific attribute transformation (positional, rotational, scale, visibility).

Actions and transformations (II)

  • MoveBy((x, y))
  • Jump(y, x, jumps, duration)
  • Beizer(bezier, duration, forward)
  • RotateTo(angle , duration)
  • FadeOut(duration)
  • ....

Actions and transformations (III)

One action can be composed within an other action:

    Chained actions: "action1 + action2"
    parallel actions: "action1 | action2"

Actions and transformations (IV)

We can apply modifiers to the actions:

  • Looping actions: "action * 10"
  • Infinite looping actions: "Repeat(action)"
  • Accelerate actions: "Accelerate(action)"
  • ...

[Cocos2d - more about actions..]

Actions and transformations (V)


#...
label.do(Repeat(JumpTo(label.position, height=100, duration=2)))
#...
label2.do(Repeat(ScaleBy(1.1, 0.2) + Reverse(ScaleBy(1.1, 0.2))))
#...
label3.do(Repeat(FadeTo(0, 1) + FadeTo(255, 1)))
#...
sprite = cocos.sprite.Sprite('sunglasses.png')
sprite.do(JumpTo((window_width*0.4, window_height*0.6), duration=1) +
                  Repeat(RotateBy(10, 0.6) + Reverse(RotateBy(10, 0.6))))
                            

Event Handling

Pyglet events fallback on Cocos2D layer


import cocos
class MyLayer(cocos.layer.Layer):
    is_event_handler = True
    #...
    def on_key_press(self, key, modifiers):
        pass
        
    def on_key_release(self, key, modifiers):
        pass
        
    def on_key_press(self, key, modifiers):
        pass
        
    def on_mouse_motion(self, x, y, dx, dy):
        pass
    #...
					

[Pyglet - more event methods..]

Event Handling (II)

Pyglet key events fallback on Cocos2D's window handlers


from pyglet.window import key
from cocos import director

kb = key.KeyStateHandler()
director.window.push_handlers(kb)
					       

[Snipped based on a JPWright blog's entry]

kb is an dictionary that saves keyboard keys states.

[Pyglet - more about key handling..]

Event Handling (III)


# scheduled update handler for sprite position management
self.schedule(self.__update__)
# ...
def __update__(self, dt):
    turbo = (4 * kb[key.LSHIFT]) | 1
    
    new_x = self.sprite.position[0] + 
            (dt * 200 * turbo * kb[key.RIGHT] - kb[key.LEFT])
            
    new_y = self.sprite.position[1] + 
            (dt * 200 * turbo * kb[key.UP] - kb[key.DOWN])

    self.sprite.position = new_x, new_y
					       

[Delta time on gamedev explained]

Animations

Based on spritesheet images (row x columns)

Running sprite sheet

[Side Scroller Sprite Base Animation by Wind astella | CC-By 3.0 License]

Animations (II)


from pyglet import image

img = image.load('/path/to/the/image') # Image load
                        
# We build a matrix were each element is a sprite sheet partition 
img_grid = image.ImageGrid(img, rows, columns, 
                           item_height, item_width, 
                           column_padding, row_padding)

# We make a texture grid from the image grid for memory optimization
texture_grid = image.TextureGrid(img_grid)

# Animation building from a set of elements of the grid
animation_01 = image.Animation.
               from_image_sequence(sequence=texture_grid[begin:end]
                                   period=animation_rate,
                                   Loop=True|False)

                        

[Animaton snippet based on cocos-discuss forum post]

Music & Sounds

By default we can play uncompressed "RIFF/WAV" files without any extra dependencies.

To play MP3/AU/OGG files, we must install AVBin.

Music & Sounds (II)



music = pyglet.resource.media('/path/to/the/file')
# Using Player wrapper
player = pyglet.media.Player()
player.queue(music)
player.volume = 0.2
player.play()

# playing directly from source
sound_media_src = pyglet.media.load('/path/to/the/file')
sound_media_src = pyglet.media.StaticSource(sound_media)
sound_media_src.play().volume = 0.5
                            

[ More about Pyglet media management ]

Collisions

Cocos2d have a module for simple collision and proximity checking, based on geometrical distance and overlapping calculations.

Collisions (II)

Collision shapes

A collidable object must have a "cshape" property (CircleShape or AARectShape).

Collisions and distance checking are handled by the collision manager.

Collisions (III)


import cocos.collision_model as cm

# ...
collision_mgr = cm.CollisionManagerGrid(xmin, xmax, 
                                        ymin, ymax, 
                                        cell_width, 
                                        cell_height)
# ...                                            
for item in collision_mgr.iter_colliding(target):
    # react to the collision 
# ...
near_list = collision_mgr.objs_near(obj, near_distance)
# ...
check_a_b_collision = collision_mgr.they_collide(a, b)
# ...
items_in_zone = collision_mgr.objs_into_box(minx, maxx, miny, maxy)

                           

Collisions (IV)


import cocos.collision_model as cm
import cocos.euclid as eu

#...
obj_center = eu.Vector2(cx, cy)
obj.cshape = cm.AARectShape(center=obj_center, half_width, half_height)
                            
collision_mgr.add(obj, name)
                           

[ More about collision module ]

Extra: spawning objects


    class Entity(cocos.sprite.Sprite):
        def __init__(self, image, x=0.0, y=0.0):
        # ...

        def draw(self):
            if self.x + self.center[0] < self.boundaries[0] or \ 
               self.x - self.center[0] > self.boundaries[2]:
                self.parent.remove(self)
                return

            if self.y + self.center[1] < self.boundaries[1] or \ 
               self.y - self.center[1] > self.boundaries[3]:
                self.parent.remove(self)
                return

            super(Entity, self).draw()

                           

Extra: spawning objects (II)



import random as rnd

# ...
class SpawnerLayer(cocos.layer.ColorLayer):

    def __init__(self, image):
        super(SpawnerLayer, self).__init__(r=255, g=255, b=255, a=255)
        self.schedule_interval(callback=self.__spawn__, interval=1)
        self.image = image
        
    def __spawn__(self, _):
        y = rnd.uniform(10, 400)
        spawned = Entity(self.image, -100, y)
        spawned.scale = rnd.uniform(0.5, 1.5)
        spawned.do(MoveTo((800, y), rnd.randint(1, 8)))
        self.add(spawned)

                           

Sharing the code

Complete code examples used in the slides

RunnerBase: A Cocos2d based runner skeleton that eases the building of simple runner games.

Other things to check:

Adam DKC

Twitter: @adoankim

Github: adoankim

E-Mail: dkc.adam [at] gmail [dot] com