Upgrade to Pro — share decks privately, control downloads, hide ads and more …

An educational retro game using only Python? Challenge Accepted!

An educational retro game using only Python? Challenge Accepted!

How to make a game using OOP Python concepts for reuse of entities.

Roberto Rosario

April 19, 2015
Tweet

More Decks by Roberto Rosario

Other Decks in How-to & DIY

Transcript

  1. “A video game is just modern day storytelling, pixel perfect

    graphics, pitch perfect music are not requirements for that. Tell a story someone will want to spend time experiencing.”
  2. #1 Title screen #2 Introduction / Storyline #3 Progressive stages

    #4 Player #5 Bosses #6 Game over #7 Game win
  3. #1 Title screen - Setting (static) #2 Introduction / Storyline

    - Setting (scripted) #3 Progressive stages - Setting (interactive) #4 Player - Entity (controllable) #5 Bosses - Entity (autonomous) #6 Game over - Setting (scripted) #7 Game win - Setting (scripted)
  4. #1 Title screen - Setting (static) #2 Introduction / Storyline

    - Setting (scripted) #3 Progressive stages - Setting (interactive) #6 Game over - Setting (scripted) #7 Game win - Setting (scripted)
  5. Simplified monomyth #1 Major crisis #2 Choosen one #3 Behind

    enemy lines #4 Insurmountable odds #5 Return home victorious
  6. The main loop VBlank Interrupt service: • Process input events

    • Update bad guys • Update good guys • Update environment • Paint screen • Update screen • Release control
  7. The main loop BEGIN: • Process input events • Update

    bad guys • Update good guys • Update environment • Paint screen • Update screen • GOTO BEGIN
  8. The main loop BEGIN: • Process input events • Update

    bad guys • Update good guys • Update environment • Paint screen • Update screen • GOTO BEGIN
  9. The main loop class Game(object): def on_setup(self): # Configure screen,

    resolution, sound, fonts def on_events(self): # Handle input events def on_update(self): # Update entities, movement, behaviour def on_blit(self): # Paint scene and actor in canvas def on_render(self): # Display canvas on screen
  10. The main loop class Game(object): ... def run(self): self.on_setup() while

    not self.finish: self.on_events() self.on_update() self.on_blit() # Render pygame.display.flip()
  11. The main loop def on_setup(self) self.clock = pygame.time.Clock() screen_mode =

    pygame.HWSURFACE | pygame.DOUBLEBUF if self.fullscreen: screen_mode |= pygame.FULLSCREEN self._screen = pygame.display.set_mode(DEFAULT_SCREENSIZE, screen_mode) self.surface = pygame.Surface(self._screen.get_size()) self.running = True self.font = pygame.font.Font(GAME_FONT, 15) self.pause_sound = pygame.mixer.Sound('assets/sounds/pause.wav') self.player = ActorPlayer(game=self) pygame.display.set_caption(GAME_TITLE) self._current_stage = None self.can_be_paused = self._can_be_paused = False self._exit_prompt = False self.ask_exit_confirm = False self.initial_stage = self.module.initial_stage post_event(event=EVENT_CHANGE_STAGE, stage=self.initial_stage)
  12. The main loop def on_event(self) for event in pygame.event.get(): try:

    self.on_event(event) except SwallowEvent: pass else: if self._current_stage: self.module.stages[self._current_stage].on_event(event)
  13. The main loop def on_event(self) for event in pygame.event.get(): try:

    self.on_event(event) except SwallowEvent: pass else: if self._current_stage: self.module.stages[self._current_stage].on_event(event)
  14. The main loop class Game(object): ... def run(self): self.on_setup() while

    not self.finish: self.on_events() self.module.stages[self._current_stage].on_update() self.on_blit() # Render pygame.display.flip()
  15. The main loop class Game(object): ... def run(self): self.on_setup() while

    not self.finish: self.on_events() self.module.stages[self._current_stage].on_update() self.on_blit() # Render pygame.display.flip()
  16. The main loop def on_blit(self): self.module.stages[self._current_stage].on_blit() if self._exit_prompt: self.exit_confirmation(self.font) if

    self.paused: self.display_pause_label(self.font) if self.shake_screen: self._screen.blit(self.surface, (randint(0, 10), randint(0, 10))) else: self._screen.blit(self.surface, (0, 0))
  17. The main loop class Game(object): ... def run(self): self.on_setup() while

    not self.finish: self.on_events() self.module.stages[self._current_stage].on_update() self.on_blit() # Render pygame.display.flip()
  18. The main loop class Game(object): ... def run(self): self.on_setup() while

    not self.finish: self.on_events() self.module.stages[self._current_stage].on_update() self.on_blit() # Render pygame.display.flip()
  19. Game code Screen Memory page 1 Memory page 2 Video

    generator pointer Draw on inactive page
  20. Game code Screen Memory page 1 Memory page 2 Video

    generator pointer Draw on inactive page
  21. Stage class Stage(object): def __init__(self, next_stage=None): def add_actor(self, actor): def

    on_blit(self): def on_event(self, event): def on_level_exit(self): def on_setup(self, game): def on_start(self): def on_update(self):
  22. Stage class Stage(object): def __init__(self, next_stage=None): def add_actor(self, actor): def

    on_blit(self): def on_event(self, event): def on_level_exit(self): def on_setup(self, game): def on_start(self): def on_update(self):
  23. class Stage(object): def add_actor(self, actor): if actor not in self.actors:

    actor.on_setup(stage=self) self.actors.append(actor) def on_blit(self): self.on_draw_background() for actor in self.actors: actor.on_blit() def on_update(self): if not self.game.paused: for actor in self.actors: actor.on_update(time_passed=self.game.time_passed) for time, entry in self._runtime_script.items(): if pygame.time.get_ticks() > self._start_time + time: entry = self._runtime_script.pop(time) entry.on_execute() Stage
  24. class StageScripted(Stage): def on_setup(self, game): ... self.script = {} def

    on_start(self): self._start_time = pygame.time.get_ticks() self._runtime_script = self.script.copy() def on_update(self): if not self.game.paused: for actor in self.actors: actor.on_update(time_passed=self.game.time_passed) for time, entry in self._runtime_script.items(): if pygame.time.get_ticks() > self._start_time + time: entry = self._runtime_script.pop(time) entry.on_execute() Stage
  25. class StageTitle(StageScripted): def on_setup(self, *args, **kwargs): super(...) text_press_enter = DisplayText(string=,

    position=, font_file=, size=, effect=, horizontal_align=, vertical_align=) text_credit = text_version = self.game.can_be_pause = False self.game.ask_exit_confirm = False self.script = { 0000: Background(image_file=, fit=), 0001: PlayMusic(filename=, loop=True), 0002: ActorCommand(text_press_enter, lambda x: x.show()), 0003: ActorCommand(text_credit, lambda x: x.show()), 0004: ActorCommand(text_version, lambda x: x.show()), } Stage
  26. class StoryStage(StageScripted): def on_setup(self, *args, **kwargs): ... evil_spaceship = ActorSpaceship(game=self.game)

    tractor_beam = ActorTracktorBeam(game=self.game) book_01 = ActorBook01(game=self.game) book_02 = ActorBook02(game=self.game) book_03 = ActorBook03(game=self.game) book_04 = ActorBook04(game=self.game) book_05 = ActorBook05(game=self.game) human_ship = ActorHumanShip(game=self.game) text_time = DisplayText(string=TEXT_YEAR, position=(0, 400), effect=TypeWriter (150, 'assets/sounds/08.ogg'), horizontal_align=CenterAlign) text_book_1 = DisplayText(string=TEXT_STEAL_BOOKS_1, position=(0, 350), effect=TypeWriter(50), horizontal_align=CenterAlign) text_book_2 = DisplayText(string=TEXT_STEAL_BOOKS_2, position=(0, 380), effect=TypeWriter(50), horizontal_align=CenterAlign) text_hero = DisplayText(string=TEXT_HERO, position=(0, 350), effect=TypeWriter (50), horizontal_align=CenterAlign) Stage
  27. class StoryStage(StageScripted): def on_setup(self, *args, **kwargs): ... self.script = {

    0000: Background(image_file='assets/backgrounds/earth.png'), 0001: PlayMusic('assets/music/LongDarkLoop.ogg'), 1000: ActorCommand(text_time, lambda x: x.show()), 6000: ActorCommand(text_time, lambda x: x.hide()), 6500: ActorCommand(text_book_1, lambda x: x.show()), 9000: ActorCommand(text_book_2, lambda x: x.show()), 15000: ActorCommand(text_book_1, lambda x: x.hide()), 15001: ActorCommand(text_book_2, lambda x: x.hide()), 16500: ActorCommand(evil_spaceship, lambda x: x.set_position(220, -65)), 16501: ActorCommand(evil_spaceship, lambda x: x.show()), 16502: ActorCommand(evil_spaceship, lambda x: x.set_destination(235, 30, 0.04)), 17500: ActorCommand(evil_spaceship, lambda x: x.set_destination(235, 30, 0.03)), 19000: ActorCommand(evil_spaceship, lambda x: x.set_destination(235, 30, 0.01)),
  28. class StoryStage(StageScripted): def on_setup(self, *args, **kwargs): ... self.script = …

    21000: ActorCommand(tractor_beam, lambda x: x.set_position(253, 70)), 21001: ActorCommand(tractor_beam, lambda x: x.show()), 21200: ActorCommand(tractor_beam, lambda x: x.strobe_start()), 22100: ActorCommand(book_01, lambda x: x.set_destination(240, 40, 0.02)), 22101: ActorCommand(book_01, lambda x: x.set_position(253, 170)), 22102: ActorCommand(book_01, lambda x: x.show()),
  29. Actors class Actor(object): animation_death_file = None animation_death_fps = 8 animation_loop

    = True animation_normal_files = [] attack_points = 1 total_hit_points = 100 invincible = False fps = 1 sound_hit_file = None sound_die_file = None speed = 0.05 team = None scale = 1 rotation = 0 flip_x = False flip_y = False can_overflow = True
  30. Actors def __init__(self, game): def change_animation(self, images): def destroy(self): def

    hide(self): def is_alive(self): def set_animation_fps(self, fps): def set_destination(self, x_position, y_position, speed): def set_direction(self, vector): def set_flip_x(self, flip): def set_flip_y(self, flip): def set_invincible(self, offset): def set_position(self, x_position, y_position): def set_rotation(self, degrees): def set_scale(self, scale): def show(self): def strobe_start(self): def strobe_stop(self):
  31. Actors def set_animation_fps(self, fps): self._animation_delay = 1000 / fps def

    on_animate(self): if self.animation_active or force: t = pygame.time.get_ticks() if t - self._last_update > self._animation_delay: self._frame += 1 if self._frame >= len(self.images): if self.animation_loop: self._frame = 0 else: self._frame -= 1 self.on_animation_finished() self.image = self.images[self._frame] self._last_update = t
  32. Actors def is_alive(self): return self.hit_points > 0 def change_animation(self, images):

    self.images = images self._frame = -1 self._last_update = 0 self.rect = self.images[0].get_rect() self.size = self.images[0].get_size() self.image = self.images[0]
  33. Actors def on_hit(self, attack_points): self.hit_points -= attack_points if self.sound_hit: self.sound_hit.play()

    if self.hit_points <= 0: self.hit_points = 0 self.on_death() def on_death(self): self.hit_points = 0 self.direction = vec2d(0, 0) if self.sound_die: self.sound_die.play() self.animation_loop = False if self.animation_death_fps: self.set_animation_fps(self.animation_death_fps) if self.animation_death: self.change_animation(self.animation_death)
  34. Actors def on_animate(self, time_passed, force=False): def on_animation_finished(self): def on_blit(self): def

    on_calculate_movement(self): def on_collision(self, foreign_actor): def on_death(self): def on_execute(self): def on_event(self, event): def on_hit(self, attack_points): def on_setup(self, stage): def on_update(self, time_passed, force=False):
  35. Actors def on_animate(self, time_passed, force=False): def on_animation_finished(self): def on_blit(self): def

    on_calculate_movement(self): def on_collision(self, foreign_actor): def on_death(self): def on_execute(self): def on_event(self, event): def on_hit(self, attack_points): def on_setup(self, stage): def on_update(self, time_passed, force=False):
  36. class Actor(object): def set_direction(self, vector): self.direction = vector def on_update(self,

    time_passed, force=False): if self.is_alive(): self.on_calculate_movement() displacement = vec2d( self.direction.x * self.speed * time_passed, self.direction.y * self.speed * time_passed ) self.pos += displacement
  37. class ActorPlayer(Actor): def on_calculate_movement(self): super(ActorPlayer, self).on_calculate_movement() if self.game.running and self.is_alive():

    keys_pressed = pygame.key.get_pressed() direction_y = 0 direction_x = 0 if keys_pressed[pygame.K_LEFT]: direction_x = -1 if keys_pressed[pygame.K_RIGHT]: direction_x = 1 if keys_pressed[pygame.K_UP]: direction_y = -1 if keys_pressed[pygame.K_DOWN]: direction_y = 1 self.set_direction(vec2d(direction_x, direction_y).normalized())
  38. from libraries.vec2d import vec2d >>> vec2d(1, 0) * vec2d(-1, 1)

    # Moves right, invert (x) vec2d(-1, 0) # Now moves left >>> vec2d(-1, 0) * vec2d(-1, 1) # Moves left, invert (x) vec2d(1, 0) # Now moves right >>> vec2d(0, -1) * vec2d(-1, 1) # Moves up, invert (x) vec2d(0, -1) # Keeps moving up
  39. class ActorBoss(Actor): def on_calculate_movement(self): bounds_rect = self.game.surface.get_rect() if self.rect.left <=

    bounds_rect.left: self.direction.x *= -1 elif self.rect.right >= bounds_rect.right: self.direction.x *= -1 elif self.rect.top <= bounds_rect.top: self.direction.y *= -1 elif self.rect.bottom >= bounds_rect.bottom: self.direction.y *= -1