Source

pygame-tutorial / side_scroller.py

Richard Jones 7e2b9e2 












































































Richard Jones 039df47 
Richard Jones 7e2b9e2 
Richard Jones 039df47 







Richard Jones 7e2b9e2 

































































Richard Jones 039df47 
Richard Jones 7e2b9e2 
Richard Jones 039df47 

Richard Jones 7e2b9e2 














Richard Jones 039df47 


Richard Jones 7e2b9e2 



































'''Basic side-scolling shooter game.

Developed for the Intro to Game Programming tutorial at US PyCon 2012.

Copyright 2012 Richard Jones <richard@mechanicalcat.net>
This code is placed in the Public Domain.
'''
import pygame
import tmx

#
# Our enemies appear on the right-hand side of the screen when their triggers
# become exposed. They move slowly left until they are no longer on the or die.
#
class Enemy(pygame.sprite.Sprite):
    image = pygame.image.load('enemy.png')
    def __init__(self, location, *groups):
        super(Enemy, self).__init__(*groups)
        self.rect = pygame.rect.Rect(location, self.image.get_size())

    def update(self, dt, game):
        # move the enemy by 50 pixels per second
        self.rect.x += -50 * dt

        # check for collision with the player; on collision mark the flag on the
        # player to indicate game over (a health level could be decremented here
        # instead)
        if self.rect.colliderect(game.player.rect):
            game.player.is_dead = True

#
# Bullets fired by the player move in one direction until their lifespan runs
# out or they hit an enemy. This could be extended to allow for enemy bullets.
#
class Bullet(pygame.sprite.Sprite):
    image = pygame.image.load('bullet.png')
    def __init__(self, location, *groups):
        super(Bullet, self).__init__(*groups)
        self.rect = pygame.rect.Rect(location, self.image.get_size())
        # time this bullet will live for in seconds
        self.lifespan = 1

    def update(self, dt, game):
        # decrement the lifespan of the bullet by the amount of time passed and
        # remove it from the game if its time runs out
        self.lifespan -= dt
        if self.lifespan < 0:
            self.kill()
            return

        # move the enemy by 400 pixels per second
        self.rect.x += 400 * dt

        # check for collision with any of the enemy sprites; we pass the "kill
        # if collided" flag as True so any collided enemies are removed from the
        # game
        if pygame.sprite.spritecollide(self, game.enemies, True):
            game.explosion.play()
            # we also remove the bullet from the game or it will continue on
            # until its lifespan expires
            self.kill()

#
# Our player of the game represented as a sprite with many attributes and user
# control.
#
class Player(pygame.sprite.Sprite):
    image = pygame.image.load('player-right.png')
    def __init__(self, location, *groups):
        super(Player, self).__init__(*groups)
        self.rect = pygame.rect.Rect(location, self.image.get_size())
        # is the player dead?
        self.is_dead = False
        # time since the player last shot
        self.gun_cooldown = 0

    def update(self, dt, game):
        # handle the player movement keys
        key = pygame.key.get_pressed()
        dx = 100
        dy = 0
        if key[pygame.K_LEFT]: dx -= 200
        if key[pygame.K_RIGHT]: dx += 200
        if key[pygame.K_UP]: dy -= 200
        if key[pygame.K_DOWN]: dy += 200
        self.rect.x += int(dx * dt)
        self.rect.y += int(dy * dt)

        # keep the player from moving off-screen
        viewport = game.tilemap.viewport
        if self.rect.bottom > viewport.bottom:
            self.rect.bottom = viewport.bottom
        elif self.rect.top < viewport.top:
            self.rect.top = viewport.top
        if self.rect.left < viewport.left:
            self.rect.left = viewport.left
        elif self.rect.right > viewport.right:
            self.rect.right = viewport.right

        # handle the player shooting key
        if key[pygame.K_LSHIFT] and not self.gun_cooldown:
            Bullet(self.rect.midright, game.sprites)
            # set the amount of time until the player can shoot again
            self.gun_cooldown = .2
            game.shoot.play()

        # decrement the time since the player last shot to a minimum of 0 (so
        # boolean checks work)
        self.gun_cooldown = max(0, self.gun_cooldown - dt)

        # if the player touches any of the map blockers they die
        if game.tilemap.layers['triggers'].collide(self.rect, 'blockers'):
            self.is_dead = True

        if game.tilemap.layers['triggers'].collide(self.rect, 'exit'):
            game.won = True

#
# Our game class represents one loaded level of the game and stores all the
# actors and other game-level state.
#
class Game(object):
    def main(self, screen):
        self.screen = screen

        # grab a clock so we can limit and measure the passing of time
        clock = pygame.time.Clock()

        # we draw the background as a static image so we can just load it in the
        # main loop
        background = pygame.image.load('background.png')

        # load our tilemap and set the viewport for rendering to the screen's
        # size
        self.tilemap = tmx.load('scroller.tmx', screen.get_size())

        # add a layer for our sprites controlled by the tilemap scrolling
        self.sprites = tmx.SpriteLayer()
        self.tilemap.layers.append(self.sprites)
        # fine the player start cell in the triggers layer
        start_cell = self.tilemap.layers['triggers'].find('player')[0]
        # use the "pixel" x and y coordinates for the player start
        self.player = Player((start_cell.px, start_cell.py), self.sprites)

        # add a separate layer for enemies so we can find them more easily later
        self.enemies = tmx.SpriteLayer()
        self.tilemap.layers.append(self.enemies)

        # load the sound effects used in playing a level of the game
        self.jump = pygame.mixer.Sound('jump.wav')
        self.shoot = pygame.mixer.Sound('shoot.wav')
        self.explosion = pygame.mixer.Sound('explosion.wav')

        # flag to allow us to detect when the player completes the level
        self.won = False

        # start the view focus at the player spawn point
        view_x = start_cell.px
        while 1:
            # limit updates to 30 times per second and determine how much time
            # passed since the last update; convert the milliseconds value to
            # seconds
            dt = clock.tick(30)/1000.

            # handle basic game events; terminate this main loop if the window
            # is closed or the escape key is pressed
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    return
                if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                    return

            # move the view at a fixed rate; round to int so we don't introduce
            # any floating-point accumulation problems
            view_x += int(100 * dt)
            self.tilemap.set_focus(view_x, start_cell.py)

            # update the tilemap and everything in it passing the elapsed time
            # since the last update (in seconds) and this Game object
            self.tilemap.update(dt, self)

            # add an enemy for each "enemy" trigger that has been exposed
            for cell in self.tilemap.layers['triggers'].collide(self.tilemap.viewport, 'enemy'):
                # delete the enemy trigger so we don't make another one
                del cell['enemy']
                Enemy((cell.px+32, cell.py), self.enemies)

            # construct the scene by drawing the background and then the rest of
            # the game imagery over the top
            screen.blit(background, (0, 0))
            self.tilemap.draw(screen)
            pygame.display.flip()

            # terminate this main loop if the player dies; a simple change here
            # could be to replace the "print" with the invocation of a simple
            # "game over" scene
            if self.player.is_dead:
                print 'YOU DIED'
                return

            # terminate this main loop if the player wins
            if self.won:
                print 'YOU WIN'
                return

if __name__ == '__main__':
    # if we're invoked as a program then initialise pygame, create a window and
    # run the game
    pygame.init()
    screen = pygame.display.set_mode((640, 480))
    Game().main(screen)