Intro to Game Programming

3,628 views
3,509 views

Published on

The slides to accompany the 3-hour introduction to game programming tutorial that I presented at EuroPython 2010 in Birmingham.

Published in: Education
5 Comments
7 Likes
Statistics
Notes
No Downloads
Views
Total views
3,628
On SlideShare
0
From Embeds
0
Number of Embeds
6
Actions
Shares
0
Downloads
142
Comments
5
Likes
7
Embeds 0
No embeds

No notes for slide


















  • A Sprite is an image that knows how to draw itself on the screen.
  • The default anchor point in cocos2d is the center of the sprite image.


















  • 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.




















































  • Make life easier for yourself - make all your animation frames the same size!
  • Make life easier for yourself - make all your animation frames the same size!

  • pyglet will sequence a grid of images from the bottom left corner taking each row, and then each cell in turn








  • Intro to Game Programming

    1. 1. Introduction To Game Programming EuroPython 2010 Richard Jones
    2. 2. The Basics
    3. 3. The Basics • Displaying something
    4. 4. The Basics • Displaying something • Controlling animation
    5. 5. The Basics • Displaying something • Controlling animation • User input
    6. 6. The Basics • Displaying something • Controlling animation • User input • Gameplay mechanics
    7. 7. The Basics • Displaying something • Controlling animation • User input • Gameplay mechanics • Playing sound effects and music
    8. 8. The Basics • Displaying something • Controlling animation • User input • Gameplay mechanics • Playing sound effects and music • Game architecture
    9. 9. Displaying Something
    10. 10. Displaying Something • Opening a window
    11. 11. Displaying Something • Opening a window • Reading images
    12. 12. Displaying Something • Opening a window • Reading images • Displaying images
    13. 13. Displaying Something • Opening a window • Reading images • Displaying images • Organising the display of images (scene composition)
    14. 14. Sky fixed
    15. 15. Distance parallax=.5
    16. 16. Midfield parallax=.8
    17. 17. Foreground 1
    18. 18. Foreground 2
    19. 19. Game Triggers
    20. 20. Opening a Window import cocos from cocos.director import director director.init() scene = cocos.scene.Scene() director.run(scene)
    21. 21. 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)
    22. 22. Anchoring (256, 256) 512 px 512 px
    23. 23. Anchoring (256, 256)
    24. 24. Anchoring (0,0)
    25. 25. 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)
    26. 26. 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)
    27. 27. 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)
    28. 28. 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())
    29. 29. 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))) ...
    30. 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. 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. 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. 33. Gameplay Mechanics
    34. 34. Gameplay Mechanics • Player Controls
    35. 35. Gameplay Mechanics • Player Controls • Timed rules (when objects appear; moving the play field)
    36. 36. Gameplay Mechanics • Player Controls • Timed rules (when objects appear; moving the play field) • Interaction of game objects
    37. 37. 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)
    38. 38. User Input
    39. 39. User Input • Getting events
    40. 40. User Input • Getting events • Distinct keyboard events vs. keyboard state
    41. 41. Control import math import random from pyglet.window import key import cocos from cocos import actions from cocos.director import director
    42. 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. 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. 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. 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. 46. 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())
    47. 47. Detecting Collisions
    48. 48. Detecting Collisions • Pixel-Perfect
    49. 49. Detecting Collisions • Pixel-Perfect • Axis-Aligned Bounding Box
    50. 50. Detecting Collisions • Pixel-Perfect • Axis-Aligned Bounding Box • Circle-Circle
    51. 51. Detecting Collisions • Pixel-Perfect • Axis-Aligned Bounding Box • Circle-Circle • Hash Map
    52. 52. Detecting Collisions Pixel-Perfect
    53. 53. Detecting Collisions Axis-Aligned Bounding Box
    54. 54. Detecting Collisions Axis-Aligned Bounding Box
    55. 55. Detecting Collisions Axis-Aligned Bounding Box
    56. 56. Detecting Collisions Circle-Circle
    57. 57. Detecting Collisions Circle-Circle
    58. 58. Detecting Collisions Circle-Circle
    59. 59. Detecting Collisions Circle-Circle
    60. 60. Detecting Collisions Hash Map d = {(42,42): [ship], (43, 42): [ship], (44, 42): ship, ... (52, 45): [asteroid], (53, 45): [asteroid], ...}
    61. 61. Collision Detection
    62. 62. Collision Detection
    63. 63. Collision Detection
    64. 64. Collision Detection
    65. 65. Collision Detection distance
    66. 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. 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. 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. 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. 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. 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. 72. 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
    73. 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. 74. Sound
    75. 75. Sound • Reading sound files
    76. 76. Sound • Reading sound files • Playing sounds and background music
    77. 77. 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) ...
    78. 78. Sound Effects ... # ship may only shoot twice per second self.target.gun_cooldown = .5 bullet_sound.play() ...
    79. 79. Sound Effects ... create_smaller_asteroids(asteroid) asteroid.kill() explosion_sound.play() return
    80. 80. Game Architecture
    81. 81. Game Architecture • Player lives
    82. 82. Game Architecture • Player lives • Game won / game over screen
    83. 83. Game Architecture • Player lives • Game won / game over screen • Opening screen
    84. 84. Game Architecture • Player lives • Game won / game over screen • Opening screen • Options menu
    85. 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. 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. 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. 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. 89. 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
    90. 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. 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. 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. 93. Special Effects
    94. 94. Special Effects • Simple image animations
    95. 95. Special Effects • Simple image animations • Use libraries like lepton
    96. 96. Controlling Animation
    97. 97. Controlling Animation • Image position on screen
    98. 98. Controlling Animation • Image position on screen • Updates over time
    99. 99. Controlling Animation • Image position on screen • Updates over time • Accounting for frame rate
    100. 100. Controlling Animation • Image position on screen • Updates over time • Accounting for frame rate • Frame to use from multiple images
    101. 101. Animation from multiple images
    102. 102. Animation from multiple images
    103. 103. Special Effects
    104. 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. 105. 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()
    106. 106. Other Game Types • minesweeper • car driving • platformer • tower defence
    107. 107. Minesweeper • rendering a grid of cells • detecting mouse clicks on cells • exposing contents
    108. 108. Car Driving • different movement model • detecting correct circuit • detecting collision with edge of track (pixel-based collision detection)
    109. 109. Platformer • rendering game layers • handling triggers • platformer physics
    110. 110. Tower Defence • tower placement (selection & position) • movement solution for creeps
    111. 111. Other Things • kytten GUI controls for pyglet / cocos2d • cocograph map editing
    112. 112. Where To From Here? • http://los-cocos.org • http://pyglet.org • http://pygame.org • http://inventwithpython.com/ • http://pyweek.org

    ×