Exemplo n.º 1
0
class Game:
    def __init__(self):
        # pg.init()
        pg.display.init()
        pg.font.init()
        self.screen = pg.display.set_mode((WIDTH, HEIGHT))
        pg.display.set_caption(TITLE)
        self.clock = pg.time.Clock()
        pg.key.set_repeat(250, 50)  # Allows us to hold dowW keys to move, etc.
        self.load_data()
        self.total_carrots = 0
        self.go_screen = False

    def load_data(self):
        game_folder = path.dirname(__file__)
        map_folder = path.join(game_folder, "maps")
        self.map = TiledMap(path.join(map_folder, "AI-map.tmx"))
        self.map_img = self.map.make_map()
        self.map_rect = self.map_img.get_rect()
        #self.map = Map(path.join(game_folder, "map.txt"))

    def new(self):
        # initialize all variables and do all the setup for a new game
        self.all_sprites = pg.sprite.Group()
        self.walls = pg.sprite.Group()
        self.chest = pg.sprite.Group()
        self.items = pg.sprite.Group()
        self.carrots = pg.sprite.Group()
        for tile_object in self.map.tmxdata.objects:
            if tile_object.name == "player":
                print("player location: ", tile_object.x, tile_object.y)
                self.player = AIPlayer(self, tile_object.x, tile_object.y)
            # self.player = Player(self, tile_object.x, tile_object.y)

            if tile_object.name == "wall":
                Obstacle(self, tile_object.x, tile_object.y, tile_object.width,
                         tile_object.height)
            if tile_object.name == "carrot":
                Carrot(self,
                       tile_object.x,
                       tile_object.y,
                       groups=[self.all_sprites, self.carrots, self.items])
                self.total_carrots += 1
            if tile_object.name == "chest":
                Chest(self,
                      tile_object.x,
                      tile_object.y,
                      groups=[self.all_sprites, self.chest, self.items])

        self.camera = Camera(self.map.width, self.map.height)

    def reset(self):
        # initialize all variables anddo all the setup for a new game

        self.chest = pg.sprite.Group()
        for chest in self.chest:
            self.all_sprites.remove(chest)
            chest.kill()

        for carrot in self.carrots:
            self.all_sprites.remove(carrot)
            carrot.kill()
        self.carrots.empty()
        self.carrots = pg.sprite.Group()

        for item in self.items:
            self.all_sprites.remove(item)
            item.kill()
        self.items = pg.sprite.Group()
        self.total_carrots = 0

        for tile_object in self.map.tmxdata.objects:
            if tile_object.name == "player":
                print("player location: ", tile_object.x, tile_object.y)
                #self.player = AIPlayer(self, tile_object.x, tile_object.y)
                self.player.reset(tile_object.x, tile_object.y)
            if tile_object.name == "carrot":
                Carrot(self,
                       tile_object.x,
                       tile_object.y,
                       groups=[self.all_sprites, self.carrots, self.items])
                self.total_carrots += 1
            if tile_object.name == "chest":
                Chest(self,
                      tile_object.x,
                      tile_object.y,
                      groups=[self.all_sprites, self.chest, self.items])

    def run(self):
        # game loop - set self.playing = False to end the game
        # useful for ending a training session
        self.playing = True
        while self.playing:
            self.dt = self.clock.tick(FPS) / 1000
            self.events()
            self.update()
            self.draw()

    def quit(self):
        self.playing = False
        pg.quit()
        pg.display.quit()
        sys.exit()

    def update(self):
        # update portion of the game loop
        self.all_sprites.update()
        self.camera.update(self.player)

    def draw_grid(self):
        for x in range(0, WIDTH, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (x, 0), (x, HEIGHT))
        for y in range(0, HEIGHT, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (0, y), (WIDTH, y))

    def draw(self):
        self.screen.fill(BGCOLOR)
        self.draw_grid()
        self.screen.blit(self.map_img, self.camera.apply_rect(self.map_rect))
        for sprite in self.all_sprites:
            self.screen.blit(sprite.image, self.camera.apply(sprite))

        self.screen.blit(self.score_surface(self.player), (0, 0))

        # Yuck
        try:
            if isinstance(self.player, AIPlayer) and DEBUG == True:
                if not self.player.controller.debug_queue.empty():
                    (self.episode, self.state_values
                     ) = self.player.controller.debug_queue.get()
                if self.episode % DEBUG_BLIT_RATE == 0:
                    for state in self.state_values:
                        val = self.state_values.get(state, 0)
                        val = round(val, 2)
                        x, y = (state[0][0] + TILESIZE / 2,
                                state[0][1] + TILESIZE / 2)
                        if len(state) == 3 and state[2] == True:
                            y = state[0][1] + TILESIZE / 4
                        self.screen.blit(self.state_value_surface(str(val)),
                                         (x, y))
        except (NameError, AttributeError) as e:
            pass
        pg.display.flip()

    def events(self):
        # catch all events here
        for event in pg.event.get():
            if event.type == pg.QUIT:
                if isinstance(self.player, AIPlayer):
                    self.player.controller.quit()
                self.quit()

            self.player.events(event)

            if self.go_screen == True and event.type == pg.KEYDOWN and event.key == pg.K_SPACE:
                self.go_screen = False

    def score_surface(self, player):
        template = "Score: {}, Remaining Moves: {}, Pos: ({}, {})"
        template = template.format(player.score,
                                   MAX_ACTIONS - player.total_actions,
                                   player.rect.x, player.rect.y)

        default_font = pg.font.get_default_font()
        return pg.font.Font(default_font, 18).render(template, True,
                                                     (0, 0, 255))

    def show_start_screen(self):
        pass

    def game_over_surface(self):
        text = "Game Over! Press Space To Play Again"
        default_font = pg.font.get_default_font()
        return pg.font.Font(default_font, 24).render(text, True, (0, 0, 255))

    def state_value_surface(self, V):
        default_font = pg.font.get_default_font()
        return pg.font.Font(default_font, 8).render(V, True, (0, 0, 240))

    def show_go_screen(self):
        self.go_screen = True
        while self.go_screen:
            go_surf = self.game_over_surface()
            self.screen.blit(go_surf, (WIDTH / 2 -
                                       (go_surf.get_width() / 2), HEIGHT / 2))

            pg.display.flip()

            self.events()
            self.update()
class Game:
    def __init__(self):
        self.logger = logging.getLogger(f"{__name__}.Game")
        self.logger.debug("Initializing game object")
        pg.mixer.pre_init(
            44100, -16, 1,
            1024)  # increase sound buffer to minise lag when playing sounds
        pg.init()
        self.screen = pg.display.set_mode((WIDTH, HEIGHT))
        pg.display.set_caption(TITLE)
        self.clock = pg.time.Clock()
        # pg.key.set_repeat(10, 100)  # Lets you hold down a key to keep the move going
        self.game_folder = path.dirname(__file__)
        self.load_images()
        self.load_sounds()
        self.player = None

    def draw_text(self, text, font_name, size, color, x, y, align="nw"):
        font = pg.font.Font(font_name, size)
        text_surface = font.render(text, True, color)
        text_rect = text_surface.get_rect()
        if align == "nw":
            text_rect.topleft = (x, y)
        if align == "ne":
            text_rect.topright = (x, y)
        if align == "sw":
            text_rect.bottomleft = (x, y)
        if align == "se":
            text_rect.bottomright = (x, y)
        if align == "n":
            text_rect.midtop = (x, y)
        if align == "s":
            text_rect.midbottom = (x, y)
        if align == "e":
            text_rect.midright = (x, y)
        if align == "w":
            text_rect.midleft = (x, y)
        if align == "center":
            text_rect.center = (x, y)
        self.screen.blit(text_surface, text_rect)

    def load_images(self):
        self.logger.debug("loading images...")
        # Define game folders
        image_folder = path.join(self.game_folder, 'data/images')
        # Load images from files
        self.title_font = path.join(image_folder, 'ZOMBIE.TTF')
        self.hud_font = path.join(image_folder, 'Impacted2.0.TTF')
        self.dim_screen = pg.Surface(self.screen.get_size()).convert_alpha()
        self.dim_screen.fill((0, 0, 0, 180))
        self.player_imgs = {}
        for img in PLAYER_IMGS:
            self.player_imgs[img] = pg.image.load(
                path.join(image_folder, PLAYER_IMGS[img])).convert_alpha()
        self.wall_img = pg.image.load(path.join(image_folder,
                                                WALL_IMG)).convert_alpha()
        self.wall_img = pg.transform.scale(self.wall_img, (TILESIZE, TILESIZE))
        self.mob_img = pg.image.load(path.join(image_folder,
                                               MOB_IMG)).convert_alpha()
        self.bullet_images = {}
        self.bullet_images['lg'] = pg.image.load(
            path.join(image_folder, BULLET_IMG)).convert_alpha()
        self.bullet_images['lg'] = pg.transform.scale(self.bullet_images['lg'],
                                                      (5, 5))
        self.splat = pg.image.load(path.join(image_folder,
                                             SPLAT)).convert_alpha()
        self.splat = pg.transform.scale(self.splat, (64, 64))
        self.gun_smoke = []
        for img in MUZZLE_SMOKE:
            self.gun_smoke.append(
                pg.image.load(path.join(image_folder, img)).convert_alpha())
        self.item_images = {}
        for item in ITEM_IMAGES:
            self.item_images[item] = pg.image.load(
                path.join(image_folder, ITEM_IMAGES[item])).convert_alpha()
        # Lighting
        self.fog = pg.Surface((WIDTH, HEIGHT))
        self.fog.fill(NIGHT_COLOUR)
        self.light_mask = pg.image.load(path.join(image_folder,
                                                  LIGHT_MASK)).convert_alpha()
        self.light_mask = pg.transform.scale(self.light_mask, LIGHT_RADIUS)
        self.light_rect = self.light_mask.get_rect()

    def load_sounds(self):
        self.logger.debug("loading sounds...")
        sound_folder = path.join(self.game_folder, 'data/sounds')
        self.music_folder = path.join(self.game_folder, 'data/music')
        # Load initial music - define user event to trigger when it ends, picked up in events()
        self.SONG_END = pg.USEREVENT + 1
        self.current_song = random.choice(BG_MUSIC)
        pg.mixer.music.set_endevent(self.SONG_END)
        pg.mixer.music.load(path.join(self.music_folder, self.current_song))
        pg.mixer.music.set_volume(BG_MUSIC_LEVEL)
        # Effects
        self.effects_sounds = {}
        for eff in EFFECTS_SOUNDS:
            self.effects_sounds[eff] = pg.mixer.Sound(
                path.join(sound_folder, EFFECTS_SOUNDS[eff]['file']))
            self.effects_sounds[eff].set_volume(EFFECTS_SOUNDS[eff]['volume'])

        # Weapons
        self.weapon_sounds = {}
        for weapon in WEAPON_SOUNDS:
            self.weapon_sounds[weapon] = []
            for sound in WEAPON_SOUNDS[weapon]:
                s = pg.mixer.Sound(path.join(sound_folder, sound))
                s.set_volume(GUN_SOUND_LEVEL)
                self.weapon_sounds[weapon].append(s)
        #  Zombies
        self.zombie_moan_sounds = []
        for sound in ZOMBIE_MOAN_SOUNDS:
            s = pg.mixer.Sound(path.join(sound_folder, sound))
            s.set_volume(ZOMBIE_MOAN_LEVEL)
            self.zombie_moan_sounds.append(s)
        self.zombie_hit_sounds = []
        for sound in ZOMBIE_HIT_SOUNDS:
            s = pg.mixer.Sound(path.join(sound_folder, sound))
            s.set_volume(ZOMBIE_HIT_LEVEL)
            self.zombie_hit_sounds.append(s)
        # Player
        self.player_hit_sounds = []
        for sound in PLAYER_HIT_SOUNDS:
            s = pg.mixer.Sound(path.join(sound_folder, sound))
            s.set_volume(PLAYER_HIT_LEVEL)
            self.player_hit_sounds.append(s)
        self.player_pain_sounds = []
        for sound in PLAYER_PAIN_SOUNDS:
            s = pg.mixer.Sound(path.join(sound_folder, sound))
            s.set_volume(PLAYER_PAIN_LEVEL)
            self.player_pain_sounds.append(s)
        self.player_panic_sounds = []
        for sound in PLAYER_PANIC_SOUNDS:
            s = pg.mixer.Sound(path.join(sound_folder, sound))
            s.set_volume(PLAYER_PANIC_LEVEL)
            self.player_panic_sounds.append(s)

    def new(self):
        """For things that should happen once, at the start of a new game"""
        # Load map from file
        self.map_folder = path.join(self.game_folder, 'data/maps')
        # self.map = Map(path.join(game_folder, 'map.txt'))
        self.map = TiledMap(self, path.join(self.map_folder, MAP_NAME))
        self.map_img = self.map.make_map()
        self.map_rect = self.map_img.get_rect()
        # self.paths = breadth_first_search(self.map, self
        # initialize all variables and do all the setup for a new game
        self.all_sprites = pg.sprite.LayeredUpdates()
        self.walls = pg.sprite.Group()
        self.mobs = pg.sprite.Group()
        self.bullets = pg.sprite.Group()
        self.items = pg.sprite.Group()

        # For Loading map from tmx data
        for tile_object in self.map.tmxdata.objects:
            obj_center = vec(tile_object.x + tile_object.width / 2,
                             tile_object.y + tile_object.height / 2)
            if tile_object.name == 'player':
                self.player = Player(self, obj_center.x, obj_center.y)
            if tile_object.name == 'zombie':
                Mob(self, obj_center.x,
                    obj_center.y)  # For EM all entities must have unique id
            if tile_object.name == 'wall':
                Obstacle(self, tile_object.x, tile_object.y, tile_object.width,
                         tile_object.height)
            if tile_object.name in ['health', 'shotgun', 'pistol']:
                Item(self, obj_center, tile_object.name)

#        self.EM = AI.EntityManager()
        for mob in self.mobs:
            # self.EM.add_entity(mob.id) #All entities must have a unique id
            mob.SM.current_state = zombie_states.Idle(self, mob)
            mob.SM.global_state = zombie_states.ZombieGlobalState(self, mob)

        assert self.player is not None
        self.camera = Camera(self.map.width,
                             self.map.height)  # Give camera total size of map
        # Flags
        self.draw_debug = False
        self.paused = False
        self.night = False
        self.effects_sounds['level_start'].play()

    def run(self):
        pg.mixer.music.play()
        # game loop - set self.playing = False to end the game
        self.playing = True
        while self.playing:
            self.dt = self.clock.tick(FPS) / 1000
            self.events()
            if not self.paused:
                self.update()
            self.draw()

    def quit(self):
        pg.quit()
        sys.exit()

    def update(self):
        """For things that should happen every frame"""
        # update portion of the game loop
        self.all_sprites.update()
        self.camera.update(
            self.player
        )  # Call camera.update - give player as target to follow

        # Game over condition - No more zombies
        #        if len(self.mobs) == 0:
        #            self.playing = False

        # Mobs hit player
        hits = pg.sprite.spritecollide(self.player, self.mobs, False,
                                       collide_hit_rect)
        for hit in hits:
            now = pg.time.get_ticks()
            if now - hit.last_hit > MOB_HIT_TIMEOUT and random.random(
            ) < MOB_HIT_CHANCE:
                hit.last_hit = now
                # self.player.health -= MOB_DAMAGE
                hit.vel = vec(0, 0)
                random.choice(self.player_hit_sounds).play()
                if self.player.health <= 0:
                    self.playing = False
                self.player.got_hit()
                self.player.pos += vec(MOB_KNOCKBACK, 0).rotate(-hits[0].rot)

        # Bullets hit mobs
        hits = pg.sprite.groupcollide(self.mobs, self.bullets, False, True)
        for mob in hits:
            # hit.health -= WEAPONS[self.player.weapon]['damage'] * len(hits[hit])
            # hit.draw_health()
            for bullet in hits[mob]:
                mob.health -= bullet.damage
            mob.vel = vec(0, 0)

        # Player hits items
        hits = pg.sprite.spritecollide(self.player, self.items, False)
        for hit in hits:
            if hit.type == 'health' and self.player.health < PLAYER_HEALTH:
                hit.kill()
                self.effects_sounds['health_up'].play()
                self.player.add_health(HEALTH_PACK_AMOUNT)

            if hit.type in WEAPONS.keys():
                hit.kill()
                self.player.pickup(hit.type)
                # print(hit.type)

    def draw_grid(self):
        for x in range(0, WIDTH, TILESIZE):
            pg.draw.line(self.screen, GREEN, (x, 0), (x, HEIGHT))
        for y in range(0, HEIGHT, TILESIZE):
            pg.draw.line(self.screen, GREEN, (0, y), (WIDTH, y))

    def render_fog(self):
        # draw light mask (gradient) onto fog image
        self.fog.fill((NIGHT_COLOUR))
        self.light_rect.center = self.camera.apply(self.player).center
        self.fog.blit(self.light_mask, self.light_rect)
        self.screen.blit(self.fog, (0, 0), special_flags=pg.BLEND_MULT)

    def draw(self):
        pg.display.set_caption("{:.2f}".format(self.clock.get_fps()))
        # self.screen.fill(BGCOLOR)
        self.screen.blit(self.map_img, self.camera.apply_rect(self.map_rect))
        # self.draw_grid()
        # self.all_sprites.draw(self.screen)
        for sprite in self.all_sprites:
            if isinstance(sprite, Mob):
                sprite.draw_health()
            self.screen.blit(
                sprite.image, self.camera.apply(sprite)
            )  # blit all sprites to screen in location of where the camera says the sprite is


#            if self.draw_debug:
#                pg.draw.rect(self.screen, CYAN, self.camera.apply_rect(sprite.hit_rect), 1)

        if self.night:
            self.render_fog()
        # DEBUG HUD
        if self.draw_debug:
            for wall in self.walls:
                pg.draw.rect(self.screen, CYAN,
                             self.camera.apply_rect(wall.rect), 1)
            for sprite in self.all_sprites:
                if isinstance(sprite, Mob):
                    sprite.draw_health()
                    pg.draw.circle(self.screen, CYAN,
                                   (self.camera.apply(sprite).centerx,
                                    self.camera.apply(sprite).centery),
                                   sprite.detect_radius, 1)
                pg.draw.rect(self.screen, CYAN,
                             self.camera.apply_rect(sprite.hit_rect), 1)

        # HUD functions
        draw_player_stats(self.screen, 10, 10,
                          (self.player.health / PLAYER_HEALTH))
        draw_player_stats(self.screen, 10, 40,
                          (self.player.fatigue / PLAYER_FATIGUE), BEIGE)
        self.draw_text("Zombies: {}".format(len(self.mobs)),
                       self.hud_font,
                       30,
                       WHITE,
                       WIDTH - 10,
                       10,
                       align="ne")
        if self.player.weapon is not None:
            self.draw_text("{}:{}".format(self.player.weapon.name,
                                          self.player.weapon.remaining_shots),
                           self.hud_font,
                           30,
                           WHITE,
                           10,
                           HEIGHT - 50,
                           align="nw")

        # Paused
        if self.paused:
            self.screen.blit(self.dim_screen, (0, 0))
            self.draw_text("Paused",
                           self.title_font,
                           105,
                           RED,
                           WIDTH / 2,
                           HEIGHT / 2,
                           align='center')

        pg.display.flip()

    def play_new_song(self):
        next_song = random.choice(BG_MUSIC)
        self.current_song = next_song
        # pg.mixer.music.load(next_song)
        pg.mixer.music.load(path.join(self.music_folder, next_song))
        pg.mixer.music.play()

    def events(self):
        # catch all events here
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.quit()
            if event.type == pg.MOUSEBUTTONDOWN:
                # print(event.button)
                if event.button == 1:
                    self.player.shoot()
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_ESCAPE:
                    self.quit()
                if event.key == pg.K_h:
                    self.draw_debug = not self.draw_debug
                if event.key == pg.K_p:
                    self.paused = not self.paused
                if event.key == pg.K_n:
                    self.night = not self.night
                if event.key == pg.K_r:
                    self.player.reload()
                if event.key == pg.K_SPACE:
                    self.player.cycle_weapon()
                if event.key == pg.K_g:
                    self.player.print_inventory()
            if event.type == self.SONG_END:
                self.play_new_song()

    def show_start_screen(self):
        pass

    def show_go_screen(self):
        self.screen.fill(BLACK)
        pg.mixer.fadeout(5000)
        self.draw_text("GAME OVER",
                       self.title_font,
                       180,
                       RED,
                       WIDTH / 2,
                       HEIGHT / 2,
                       align='center')
        self.draw_text("Press any key to start",
                       self.title_font,
                       75,
                       WHITE,
                       WIDTH / 2,
                       HEIGHT * 3 / 4,
                       align='center')
        pg.display.flip()
        self.wait_for_key()

    def wait_for_key(self):
        pg.event.wait()  # Clears event queue
        waiting = True
        while waiting:
            self.clock.tick(FPS)
            for event in pg.event.get():
                if event.type == pg.QUIT:
                    waiting = False
                    self.quit()

                if event.type == pg.KEYUP:
                    waiting = False
Exemplo n.º 3
0
class Game:
    """
    Blueprint for game objects
    """

    def __init__(self):
        # initialize game window, etc
        pg.init()
        pg.mixer.pre_init(44100, -16, 1, 1024)
        pg.mouse.set_visible(False)
        self.screen = pg.display.set_mode((WIDTH, HEIGHT))
        self.screen.set_alpha(None)
        self.screen_width = WIDTH
        self.screen_height = HEIGHT
        pg.display.set_caption(TITLE)
        self.clock = pg.time.Clock()

        # Resource folders
        self.game_folder = path.dirname(__file__)
        self.img_folder = path.join(self.game_folder, 'img')
        self.snd_folder = path.join(self.game_folder, 'snd')
        self.music_folder = path.join(self.snd_folder, 'Music')
        self.crosshair_folder = path.join(self.img_folder, 'Crosshairs')
        self.item_folder = path.join(self.img_folder, 'Items')
        # Loads game assets
        self.load_data()
        self.running = True
        # Debugging flags
        self.debug = False
        self.hardcore_mode = False

    def load_data(self):
        """
        Loads the necessary assets for the game. 
        :return: None
        """
        # Game map
        self.map = Map(path.join(self.game_folder, 'map3.txt'))

        self.pause_screen_effect = pg.Surface(self.screen.get_size()).convert()
        self.pause_screen_effect.fill((0, 0, 0, 225))

        # Fog of war
        self.fog = pg.Surface(self.screen.get_size()).convert()
        self.fog.fill(NIGHT_COLOR)

        # Light on the player
        self.light_mask = pg.image.load(path.join(self.img_folder, LIGHT_MASK)).convert_alpha()
        self.light_mask = pg.transform.smoothscale(self.light_mask, (LIGHT_RADIUS, LIGHT_RADIUS))
        self.light_rect = self.light_mask.get_rect()

        # HUD Elements
        self.mag_img = pg.transform.smoothscale(pg.image.load(path.join(self.img_folder, CLIP_IMG)),
                                                (40, 40)).convert_alpha()
        # Crosshairs
        self.crosshairs = [
            pg.transform.smoothscale(pg.image.load(path.join(self.crosshair_folder, crosshair)),
                                     (32, 32)).convert_alpha()
            for crosshair in CROSSHAIRS]
        # todo - have a menu decide the player's crosshair
        self.crosshair = choice(self.crosshairs)

        # Item pickups
        self.pickup_items = {}
        for item in ITEM_IMAGES:
            self.pickup_items[item] = []
            self.pickup_items[item] = pg.image.load(path.join(self.item_folder, ITEM_IMAGES[item])).convert_alpha()

        # Fonts
        self.hud_font = path.join(self.img_folder, 'Fonts\Impacted2.0.ttf')

        # Sound loading
        self.music_tracks = {"main menu": MAIN_MENU_MUSIC, 'Game over': GAME_OVER_MUSIC, 'background music': BG_MUSIC}
        pg.mixer.music.load(path.join(self.music_folder, self.music_tracks['background music']))
        pg.mixer.music.set_volume(.5)

        self.swing_noises = {}
        for weapon in PLAYER_SWING_NOISES:
            noise = pg.mixer.Sound(path.join(self.snd_folder, PLAYER_SWING_NOISES[weapon]))
            noise.set_volume(.25)
            self.swing_noises[weapon] = noise

        self.player_hit_sounds = []
        for snd in PLAYER_HIT_SOUNDS:
            self.player_hit_sounds.append(pg.mixer.Sound(path.join(self.snd_folder, snd)))

        self.weapon_sounds = {}
        for weapon in WEAPONS['sound']:
            self.weapon_sounds[weapon] = {}
            sound_list = {}
            for snd in WEAPONS['sound'][weapon]:
                noise = pg.mixer.Sound(path.join(self.snd_folder, WEAPONS['sound'][weapon][snd]))
                noise.set_volume(.9)
                sound_list[snd] = noise
            self.weapon_sounds[weapon] = sound_list

        self.zombie_moan_sounds = []
        for snd in ZOMBIE_MOAN_SOUNDS:
            noise = pg.mixer.Sound(path.join(self.snd_folder, snd))
            noise.set_volume(.25)
            self.zombie_moan_sounds.append(noise)

        self.zombie_hit_sounds = {}
        for type in ENEMY_HIT_SOUNDS:
            self.zombie_hit_sounds[type] = []
            for snd in ENEMY_HIT_SOUNDS[type]:
                snd = pg.mixer.Sound(path.join(self.snd_folder, snd))
                snd.set_volume(.75)
                self.zombie_hit_sounds[type].append(snd)

        self.player_foot_steps = {}
        for terrain in PLAYER_FOOTSTEPS:
            self.player_foot_steps[terrain] = []
            for snd in PLAYER_FOOTSTEPS[terrain]:
                snd = pg.mixer.Sound(path.join(self.snd_folder, snd))
                snd.set_volume(.5)
                self.player_foot_steps[terrain].append(snd)

        # Bullets
        self.bullet_images = {}
        self.bullet_images['lg'] = pg.transform.smoothscale(pg.image.load(path.join(self.img_folder, RIFLE_BULLET_IMG)),
                                                            (10, 3)).convert_alpha()
        self.bullet_images['med'] = pg.transform.smoothscale(
            pg.image.load(path.join(self.img_folder, HANDGUN_BULLET_IMG)), (5, 3)).convert_alpha()
        self.bullet_images['sm'] = pg.transform.smoothscale(pg.image.load(
            path.join(self.img_folder, SHOTGUN_BULLET_IMG)).convert_alpha(), (7, 7))

        # Effects
        self.gun_flashes = [pg.image.load(path.join(self.img_folder, flash)).convert_alpha() for flash in
                            MUZZLE_FLASHES]

        # Load enemy animations
        self.enemy_imgs = [pg.transform.smoothscale(pg.image.load(path.join(self.game_folder, name)),
                                                    (96, 96)).convert_alpha() for name in
                           ENEMY_IMGS]
        # Load player animations
        self.default_player_weapon = 'knife'
        self.default_player_action = 'idle'
        self.player_animations = {'handgun': {}, 'knife': {}, 'rifle': {}, 'shotgun': {}}

        # Create all hand gun animations
        self.player_animations['handgun']['idle'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                     for name in HANDGUN_ANIMATIONS['idle']]
        self.player_animations['handgun']['melee'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                      for name in HANDGUN_ANIMATIONS['melee']]
        self.player_animations['handgun']['move'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                     for name in HANDGUN_ANIMATIONS['move']]
        self.player_animations['handgun']['reload'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                       for name in HANDGUN_ANIMATIONS['reload']]
        self.player_animations['handgun']['shoot'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                      for name in HANDGUN_ANIMATIONS['shoot']]

        # Create all knife animations
        self.player_animations['knife']['idle'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha() for
                                                   name in KNIFE_ANIMATIONS['idle']]
        self.player_animations['knife']['melee'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha() for
                                                    name in KNIFE_ANIMATIONS['melee']]
        self.player_animations['knife']['move'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha() for
                                                   name in KNIFE_ANIMATIONS['move']]

        # Create all rifle animations
        self.player_animations['rifle']['idle'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha() for
                                                   name in RIFLE_ANIMATIONS['idle']]
        self.player_animations['rifle']['melee'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha() for
                                                    name in RIFLE_ANIMATIONS['melee']]
        self.player_animations['rifle']['move'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha() for
                                                   name in RIFLE_ANIMATIONS['move']]
        self.player_animations['rifle']['reload'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                     for name in RIFLE_ANIMATIONS['reload']]
        self.player_animations['rifle']['shoot'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha() for
                                                    name in RIFLE_ANIMATIONS['shoot']]

        # Create all shotgun animations
        self.player_animations['shotgun']['idle'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                     for name in SHOTGUN_ANIMATIONS['idle']]
        self.player_animations['shotgun']['melee'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                      for name in SHOTGUN_ANIMATIONS['melee']]
        self.player_animations['shotgun']['move'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                     for name in SHOTGUN_ANIMATIONS['move']]
        self.player_animations['shotgun']['reload'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                       for name in SHOTGUN_ANIMATIONS['reload']]
        self.player_animations['shotgun']['shoot'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                      for name in SHOTGUN_ANIMATIONS['shoot']]

    def new(self):
        """
        Creates a new game
        :return: None
        """
        self.all_sprites = pg.sprite.LayeredUpdates()
        self.walls = pg.sprite.Group()
        self.bullets = pg.sprite.Group()
        self.mobs = pg.sprite.Group()
        self.items = pg.sprite.Group()
        self.swingAreas = pg.sprite.Group()
        self.camera = Camera(self.map.width, self.map.height)
        self.paused = False
        self.running = True
        self.pathfinder = Pathfinder()
        self.game_graph = WeightedGraph()
        mob_positions = []
        wall_positions = []

        for row, tiles in enumerate(self.map.data):
            for col, tile in enumerate(tiles):
                if tile == '1':
                    wall_positions.append((col * TILESIZE, row * TILESIZE))
                if tile == 'P':
                    self.player = Player(self, col * TILESIZE, row * TILESIZE)
                if tile == 'E':
                    mob_positions.append((col * TILESIZE, row * TILESIZE))
                if tile == 'W':
                    WeaponPickup(self, (col * TILESIZE, row * TILESIZE))

        for position in wall_positions:
            Obstacle(self, position[0], position[1])

        for position in mob_positions:
            Mob(self, position[0], position[1])

        self.game_graph.walls = [(int(wall[0] // TILESIZE), int(wall[1] // TILESIZE)) for wall in wall_positions]
        self.mob_idx = 0
        self.last_queue_update = 0
        g.run()

    def find_path(self, predator, prey):
        """
        Finds a path for the predator to reach its prey
        :param predator: The entity who seeks
        :param prey: The unknowning target
        :return: A list of Vector2 objects to guide the predator
        """
        return self.pathfinder.a_star_search(self.game_graph,
                                             vec(predator.pos.x // TILESIZE, predator.pos.y // TILESIZE),
                                             vec(prey.pos.x // TILESIZE, prey.pos.y // TILESIZE))

    def run(self):
        """
        Runs the game
        :return: None
        """
        self.playing = True
        pg.mixer.music.play(loops=-1)
        while self.playing:
            pg.display.set_caption("{:.1f}".format(self.clock.get_fps()))
            self.dt = self.clock.tick(FPS) / 1000
            self.events()
            if not self.paused:
                self.update()
            self.draw()

    def update(self):
        """
        Updates game state
        :return: None
        """
        self.impact_positions = []
        for sprite in self.all_sprites:
            if sprite == self.player:
                sprite.update(pg.key.get_pressed())
            else:
                sprite.update()
        self.camera.update(self.player)
        self.swingAreas.update()
        self.update_pathfinding_queue()

        # Player hits mobs
        hit_melee_box = pg.sprite.groupcollide(self.mobs, self.swingAreas, False, True, collide_hit_rect)
        for hit in hit_melee_box:
            choice(self.zombie_hit_sounds['bash']).play()
            hit.health -= hit.health
            self.impact_positions.append(hit.rect.center)

        # Enemy hits player
        hits = pg.sprite.spritecollide(self.player, self.mobs, False, collide_hit_rect)
        for hit in hits:
            if random() < .7:
                choice(self.player_hit_sounds).play()
            if hit.can_attack:
                self.impact_positions.append(self.player.rect.center)
                self.player.health -= hit.damage
                hit.vel.normalize()
                hit.pause()
                if self.player.health <= 0:
                    self.playing = False
        if hits:
            self.player.pos += vec(ENEMY_KNOCKBACK, 0).rotate(-hits[0].rot)

        # Bullet collisions
        hits = pg.sprite.groupcollide(self.mobs, self.bullets, False, False, collide_hit_rect)
        for mob in hits:
            for bullet in hits[mob]:
                self.impact_positions.append(bullet.rect.center)
                mob.health -= bullet.damage
                mob.pos += vec(WEAPONS[self.player.weapon]['damage'] // 10, 0).rotate(-self.player.rot)
            # Drowns out blood gushing noises the further the collision is from the player
            dist = self.player.pos.distance_to(mob.pos)
            ratio = 1
            if dist > 0:
                ratio = round(200 / dist, 2)
                if ratio > 1:
                    ratio = 1
            snd = choice(self.zombie_hit_sounds['bullet'])
            snd.set_volume(ratio)
            if snd.get_num_channels() > 2:
                snd.stop()
            snd.play()

        # Item collisions
        hits = pg.sprite.spritecollide(self.player, self.items, True, collide_hit_rect)
        for hit in hits:
            self.player.pickup_item(hit)
            if isinstance(hit, WeaponPickup):
                snd = self.weapon_sounds[hit.type]['pickup']
                snd.play()
            else:
                pass

    def events(self):
        """
        Game loop event handling
        :return: None
        """
        for event in pg.event.get():
            if event.type == pg.QUIT:
                if self.playing:
                    self.playing = False
                self.running = False
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_b:
                    self.debug = not self.debug
                if event.key == pg.K_p:
                    self.paused = not self.paused
                if event.key == pg.K_h:
                    self.hardcore_mode = not self.hardcore_mode

    def draw_grid(self):
        """
        Used for debugging
        :return: None
        """
        for x in range(0, WIDTH, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (x, 0), (x, HEIGHT))
        for y in range(0, HEIGHT, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (0, y), (WIDTH, y))

    def render_fog(self):
        self.fog.fill(NIGHT_COLOR)
        self.light_rect.center = self.camera.apply_rect(self.player.hit_rect.copy()).center
        self.fog.blit(self.light_mask, self.light_rect)
        self.screen.blit(self.fog, (0, 0), special_flags=pg.BLEND_RGBA_MULT)

    def draw(self):
        """
        Draws the updated game state onto the screen
        :return: None
        """
        self.screen.fill(DARKGREY)
        if self.debug:
            self.draw_grid()
        self.draw_blood_splatters()
        # Draw all sprites to the screen
        for sprite in self.all_sprites:
            # if isinstance(sprite, Mob):
            #     sprite.draw_health()
            self.screen.blit(sprite.image, self.camera.apply(sprite))
            if self.debug:
                pg.draw.rect(self.screen, (0, 255, 255), self.camera.apply_rect(sprite.hit_rect), 1)
        self.render_fog()
        x, y = pg.mouse.get_pos()
        self.screen.blit(self.crosshair, (x - self.crosshair.get_rect().width // 2,
                                          y - self.crosshair.get_rect().height // 2))
        # draw hud information
        if not self.hardcore_mode:
            self.update_hud()
        if self.paused:
            self.screen.blit(self.pause_screen_effect, (0, 0))
            self.draw_text('Paused', self.hud_font, 105, RED, WIDTH / 2, HEIGHT / 2, align='center')
        pg.display.flip()

    def update_hud(self):
        """
        Updates the HUD information for the player to see
        :return: None
        """
        self.draw_player_stats()
        if self.player.weapon != 'knife':
            self.draw_current_clip(self.screen, 10, 70, self.player.arsenal[self.player.weapon]['clip'], 3, 15)

    def draw_player_stats(self):
        """
        Gives the player visual indication of their health & stamina
        :return: None
        """
        self.draw_player_health(self.screen, 10, 10, self.player.health // PLAYER_HEALTH)
        self.draw_text(str(self.player.health) + "%", self.hud_font, 15, (119, 136, 153),
                       BAR_LENGTH // 2 - 5, 10)
        self.draw_player_stamina(self.screen, 10, 40, self.player.stamina / PLAYER_STAMINA)
        self.draw_text("{0:.0f}".format(self.player.stamina) + "%", self.hud_font, 15, (119, 136, 153),
                       BAR_LENGTH // 2 - 5, 40)

    def draw_current_clip(self, surface, x, y, bullets, bullet_length, bullet_height):
        """
        Used to give the player visual indication of how much ammunition they have at 
        the moment in their weapon
        :param surface: The location in which to draw this information
        :param x: The x location to draw
        :param y: The y location to draw
        :param bullets: How many bullets to draw
        :param bullet_length: How wide the bullet figure will be
        :param bullet_height: How tall the bullet figure will be
        :return: None
        """
        if self.player.weapon != 'knife':
            temp = x
            # Draws how many bullets are in the player's magazine in GOLD with the difference between
            # maximum mag capacity - current bullet count is in LIGHTGREY
            for j in range(0, WEAPONS[self.player.weapon]['clip size']):
                bullet_outline = pg.Surface((bullet_length, bullet_height)).convert()
                if j < bullets:
                    bullet_outline.fill(GOLD)
                else:
                    bullet_outline.fill(LIGHTGREY)
                surface.blit(bullet_outline, (temp, y))
                temp += 2 * bullet_length
            surface.blit(self.mag_img, (temp, y - 10))
            self.draw_text('x', self.hud_font, 15, WHITE,
                           temp + 32, y)
            self.draw_text(str(self.player.arsenal[self.player.weapon]['reloads']), self.hud_font, 20, WHITE,
                           temp + 40, y - 5)

    @staticmethod
    def draw_player_health(surface, x, y, pct):
        """
        Draws the player's health bar
        :param surface: The surface on which to draw the health bar
        :param x: x location
        :param y: y location
        :param pct: How much health is left in %
        :return: None
        """
        if pct < 0:
            pct = 0
        fill = pct * BAR_LENGTH
        static = BAR_LENGTH

        outline_rect = pg.Rect(x, y, BAR_LENGTH, BAR_HEIGHT)
        filled_rect = pg.Rect(x, y, fill, BAR_HEIGHT)
        static_rect = pg.Rect(x, y, static, BAR_HEIGHT)

        pg.draw.rect(surface, LIMEGREEN, static_rect)
        pg.draw.rect(surface, GREEN, filled_rect)
        pg.draw.rect(surface, WHITE, outline_rect, 2)

    @staticmethod
    def draw_player_stamina(surface, x, y, pct):
        """
        Draws the player's stamina bar
        :param surface: The surface to draw on
        :param x: x location
        :param y: y location
        :param pct: How much stamina is left in %
        :return: None
        """
        if pct < 0:
            pct = 0

        fill = pct * BAR_LENGTH
        static = BAR_LENGTH
        outline_rect = pg.Rect(x, y, BAR_LENGTH, BAR_HEIGHT)
        filled_rect = pg.Rect(x, y, fill, BAR_HEIGHT)
        static_rect = pg.Rect(x, y, static, BAR_HEIGHT)

        pg.draw.rect(surface, DODGERBLUE, static_rect)
        pg.draw.rect(surface, DEEPSKYBLUE, filled_rect)
        pg.draw.rect(surface, WHITE, outline_rect, 2)

    def draw_text(self, text, font_name, size, color, x, y, align='nw'):
        """
        Draws informative text.
        :param text: The text to draw.
        :param font_name: The font to use.
        :param size: The size of the font.
        :param color: The colour of the font.
        :param x: The x-coordinate of the location
        :param y: The y-coordinate of the location 
        :param align: Compass location
        :return: None
        """
        font = pg.font.Font(font_name, size)
        text_surface = font.render(text, True, color)
        text_rect = text_surface.get_rect()
        if align == "nw":
            text_rect.topleft = (x, y)
        if align == "ne":
            text_rect.topright = (x, y)
        if align == "sw":
            text_rect.bottomleft = (x, y)
        if align == "se":
            text_rect.bottomright = (x, y)
        if align == "n":
            text_rect.midtop = (x, y)
        if align == "s":
            text_rect.midbottom = (x, y)
        if align == "e":
            text_rect.midright = (x, y)
        if align == "w":
            text_rect.midleft = (x, y)
        if align == "center":
            text_rect.center = (x, y)

        self.screen.blit(text_surface, text_rect)

    def draw_blood_splatters(self):
        if self.impact_positions:
            for pos in self.impact_positions:
                impact = True
                while impact:
                    self.events()
                    for magnitude in range(1, randrange(35, 65)):
                        exploding_bit_x = pos[0] + randrange(-1 * magnitude, magnitude) + self.camera.camera.x
                        exploding_bit_y = pos[1] + randrange(-1 * magnitude, magnitude) + self.camera.camera.y
                        pg.draw.circle(self.screen, choice(BLOOD_SHADES), (exploding_bit_x, exploding_bit_y),
                                       randrange(1, 5))
                    impact = False
            self.impact_positions.clear()

    def update_pathfinding_queue(self):
        """
        Gives each mob on the level the opportunity to find a path
        to its prey (the player). Each mob will be given this
        opportunity in order according to their list position into
        the mobs group.
        :return: None
        """
        now = pg.time.get_ticks()
        if now - self.last_queue_update > 5000:
            self.last_queue_update = now
            if self.mob_idx == len(self.mobs):
                self.mob_idx = 0
            count = 0
            for mob in self.mobs:
                mob.can_find_path = False
                if count == self.mob_idx:
                    mob.can_find_path = True
                count += 1
            self.mob_idx += 1

    def show_start_screen(self):
        """
        Displays the start screen for the game
        :return: None
        """
        pass

    def show_gameover_screen(self):
        """
        Displays the gameover screen for the game
        :return: None
        """
        pass

    def show_pause_screen(self):
        """
        Displays the pause screen for the game
        :return: None
        """
        pass
Exemplo n.º 4
0
class Game:
    def __init__(self):
        # Initializes PyGame buffer=256
        pg.mixer.pre_init(frequency=22050, size=-16, channels=2, buffer=256)
        pg.init()

        # Create screen and title
        self.screen = pg.display.set_mode((WIDTH, HEIGHT))
        pg.display.set_caption(TITLE)

        # Clock speed and key delay
        self.clock = pg.time.Clock()
        pg.key.set_repeat(500, 100)

        # Loads data..
        self.load_data()

    def load_data(self):
        # Asset folders
        game_folder = path.dirname(__file__)
        img_folder = path.join(game_folder, 'assets', 'textures')
        snd_folder = path.join(game_folder, 'assets', 'sounds', 'snd')
        music_folder = path.join(game_folder, 'assets', 'sounds', 'music')
        self.map_folder = path.join(game_folder, 'assets', 'maps')

        # Font
        self.title_font = path.join(img_folder, 'ZOMBIE.TTF')
        self.hud_font = path.join(img_folder, 'Impacted2.0.ttf')

        # Object images
        self.dim_screen = pg.Surface(self.screen.get_size()).convert_alpha()
        self.dim_screen.fill((0, 0, 0, 150))
        self.player_img = pg.image.load(
            path.join(img_folder, 'player', PLAYER_IMG)).convert_alpha()

        self.bullet_images = {}
        self.bullet_images['lg'] = pg.image.load(
            path.join(img_folder, 'bullet', BULLET_IMG)).convert_alpha()
        self.bullet_images['sm'] = pg.transform.scale(self.bullet_images['lg'],
                                                      (10, 10))

        self.mob_img = pg.image.load(path.join(img_folder, 'mob',
                                               MOB_IMG)).convert_alpha()
        self.splat = pg.image.load(path.join(img_folder, 'mob',
                                             SPLAT)).convert_alpha()
        self.splat = pg.transform.scale(self.splat, (64, 64))

        # Particles
        self.gun_flashes = []
        for img in MUZZLE_FLASHES:
            self.gun_flashes.append(
                pg.image.load(path.join(img_folder, 'particles',
                                        img)).convert_alpha())

        # Items
        self.item_images = {}
        for item in ITEM_IMAGES:
            self.item_images[item] = pg.image.load(
                path.join(img_folder, 'items',
                          ITEM_IMAGES[item])).convert_alpha()

        # Lighting effects
        self.fog = pg.Surface((WIDTH, HEIGHT))
        self.fog.fill(NIGHT_COLOR)
        self.light_mask = pg.image.load(path.join(img_folder,
                                                  LIGHT_MASK)).convert_alpha()
        self.light_mask = pg.transform.scale(self.light_mask, LIGHT_RADIUS)
        self.light_rect = self.light_mask.get_rect()

        # Sounds
        pg.mixer.music.load(path.join(music_folder, BG_MUSIC))

        self.effects_sounds = {}
        for type in EFFECTS_SOUNDS:
            s = pg.mixer.Sound(path.join(snd_folder, EFFECTS_SOUNDS[type]))
            s.set_volume(0.5)
            self.effects_sounds[type] = s

        self.weapon_sounds = {}
        for weapon in WEAPON_SOUNDS:
            self.weapon_sounds[weapon] = []
            for snd in WEAPON_SOUNDS[weapon]:
                s = pg.mixer.Sound(path.join(snd_folder, snd))
                if weapon == 'shotgun':
                    s.set_volume(0.2)
                else:
                    s.set_volume(0.5)
                self.weapon_sounds[weapon].append(s)

        self.zombie_moan_sounds = []
        for snd in ZOMBIE_MOAN_SOUNDS:
            s = pg.mixer.Sound(path.join(snd_folder, snd))
            s.set_volume(0.2)
            self.zombie_moan_sounds.append(s)

        self.player_hit_sounds = []
        for snd in PLAYER_HIT_SOUNDS:
            self.player_hit_sounds.append(
                pg.mixer.Sound(path.join(snd_folder, snd)))

        self.zombie_hit_sounds = []
        for snd in ZOMBIE_HIT_SOUNDS:
            s = pg.mixer.Sound(path.join(snd_folder, snd))
            s.set_volume(0.3)
            self.zombie_hit_sounds.append(s)

    def new(self):
        # initialize all variables and do all the setup for a new game
        self.draw_debug = False
        self.paused = False
        self.night = True

        # Sprite groups
        self.all_sprites = pg.sprite.LayeredUpdates()
        self.obstacles = pg.sprite.Group()
        self.mobs = pg.sprite.Group()
        self.bullets = pg.sprite.Group()
        self.items = pg.sprite.Group()

        # Map
        self.map = TiledMap(path.join(self.map_folder, '01.tmx'))
        self.map_img = self.map.make_map()
        self.map_rect = self.map_img.get_rect()

        # Map Objects
        for tile_object in self.map.tmxdata.objects:
            # For spawning in center of tiled object
            obj_center = vec(tile_object.x + tile_object.width / 2,
                             tile_object.y + tile_object.height / 2)
            if tile_object.name == 'player':
                self.player = Player(self, obj_center.x, obj_center.y)
            if tile_object.name == 'zombie':
                Mob(self, obj_center.x, obj_center.y)
            if tile_object.name == 'wall':
                Obstacle(self, tile_object.x, tile_object.y, tile_object.width,
                         tile_object.height)
            if tile_object.name in ['health', 'shotgun']:
                Item(self, obj_center, tile_object.name)

        # Camera object
        self.camera = Camera(self.map.width, self.map.height)

        # Start a new level sound
        self.effects_sounds['level_start'].play()

    def run(self):
        # game loop - set self.playing = False to end the game
        self.playing = True

        # Starts music
        pg.mixer.music.play(loops=-1)

        while self.playing:
            # dt - How much time did the last frame take in ms (For frame-independent movement)
            self.dt = self.clock.tick(FPS) / 1000

            self.events()

            if not self.paused:
                self.update()

            self.draw()

    def events(self):
        # catch all events here
        for event in pg.event.get():
            # Closing window
            if event.type == pg.QUIT:
                self.quit()

            # Keyboard press
            if event.type == pg.KEYDOWN:
                # ESC
                if event.key == pg.K_ESCAPE:
                    self.quit()
                if event.key == pg.K_h:
                    self.draw_debug = not self.draw_debug
                if event.key == pg.K_p:
                    self.paused = not self.paused
                if event.key == pg.K_n:
                    self.night = not self.night

    def update(self):
        # Update portion of the game loop
        self.all_sprites.update()
        self.camera.update(self.player)

        # Game over?
        if len(self.mobs) == 0:
            self.playing = False

        # Player and item
        hits = pg.sprite.spritecollide(self.player, self.items, False)
        for hit in hits:
            if hit.type == 'health' and self.player.health < PLAYER_HEALTH:
                hit.kill()  # Destroy item
                self.effects_sounds['health_up'].play()  # Play sound
                self.player.add_health(HEALTH_PACK_AMOUNT)  # Add health
            if hit.type == 'shotgun':
                hit.kill()
                self.effects_sounds['gun_pickup'].play()
                self.player.weapon = 'shotgun'

        # Player and mobs
        hits = pg.sprite.spritecollide(self.player, self.mobs, False,
                                       collide_hit_rect)
        for hit in hits:
            # Play sound
            if random() < 0.7:
                choice(self.player_hit_sounds).play()

            # Take damage
            self.player.health -= MOB_DAMAGE
            hit.vel = vec(0, 0)
            if self.player.health <= 0:
                self.playing = False
        if hits:
            self.player.hit()
            self.player.pos += vec(MOB_KNOCKBACK, 0).rotate(-hits[0].rot)

        # Bullets and Mobs
        hits = pg.sprite.groupcollide(self.mobs, self.bullets, False, True)
        for mob in hits:
            for bullet in hits[mob]:
                mob.health -= bullet.damage
            mob.vel = vec(0, 0)

    def draw(self):
        # Shows window title and FPS counter
        pg.display.set_caption("{} FPS - {:.2f}".format(
            TITLE, self.clock.get_fps()))

        # Fill background
        # self.screen.fill(BGCOLOR)

        # Draws grid
        # self.draw_grid()

        # Draws map
        self.screen.blit(self.map_img, self.camera.apply_rect(self.map_rect))

        # Draws all sprites ant hit rectangles
        for sprite in self.all_sprites:
            if isinstance(sprite, Mob):
                sprite.draw_health()
            self.screen.blit(sprite.image, self.camera.apply(sprite))
            if self.draw_debug:
                pg.draw.rect(self.screen, CYAN,
                             self.camera.apply_rect(sprite.hit_rect), 1)
        if self.draw_debug:
            for obstacle in self.obstacles:
                pg.draw.rect(self.screen, CYAN,
                             self.camera.apply_rect(obstacle.rect), 1)

        # Lighting effects
        if self.night:
            self.render_fog()

        # Draws HUD
        draw_player_health(self.screen, 10, 10,
                           self.player.health / PLAYER_HEALTH)
        self.draw_text('Zombies: {}'.format(len(self.mobs)),
                       self.hud_font,
                       30,
                       WHITE,
                       WIDTH - 10,
                       10,
                       align="ne")

        # Draws PAUSED
        if self.paused:
            self.screen.blit(self.dim_screen, (0, 0))
            self.draw_text("Paused",
                           self.title_font,
                           105,
                           RED,
                           WIDTH / 2,
                           HEIGHT / 2,
                           align="center")

        # Flips/Updates screen
        pg.display.flip()

    def render_fog(self):
        self.fog.fill(NIGHT_COLOR)
        self.light_rect.center = self.camera.apply(self.player).center
        self.fog.blit(self.light_mask, self.light_rect)
        self.screen.blit(self.fog, (0, 0), special_flags=pg.BLEND_MULT)

    def draw_text(self, text, font_name, size, color, x, y, align="nw"):
        font = pg.font.Font(font_name, size)
        text_surface = font.render(text, True, color)
        text_rect = text_surface.get_rect()
        if align == "nw":
            text_rect.topleft = (x, y)
        if align == "ne":
            text_rect.topright = (x, y)
        if align == "sw":
            text_rect.bottomleft = (x, y)
        if align == "se":
            text_rect.bottomright = (x, y)
        if align == "n":
            text_rect.midtop = (x, y)
        if align == "s":
            text_rect.midbottom = (x, y)
        if align == "e":
            text_rect.midright = (x, y)
        if align == "w":
            text_rect.midleft = (x, y)
        if align == "center":
            text_rect.center = (x, y)
        self.screen.blit(text_surface, text_rect)

    def draw_grid(self):
        for x in range(0, WIDTH, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (x, 0), (x, HEIGHT))
        for y in range(0, HEIGHT, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (0, y), (WIDTH, y))

    def show_start_screen(self):
        pass

    def show_go_screen(self):
        self.screen.fill(BLACK)
        self.draw_text("GAME OVER",
                       self.title_font,
                       100,
                       RED,
                       WIDTH / 2,
                       HEIGHT / 2,
                       align="center")
        self.draw_text("Press a key to start",
                       self.title_font,
                       75,
                       WHITE,
                       WIDTH / 2,
                       HEIGHT * 3 / 4,
                       align="center")
        pg.display.flip()
        self.wait_for_key()

    def wait_for_key(self):
        pg.event.wait()
        waiting = True
        while waiting:
            self.clock.tick(FPS)
            for event in pg.event.get():
                if event.type == pg.QUIT:
                    waiting = False
                    self.quit()
                if event.type == pg.KEYUP:
                    waiting = False

    def quit(self):
        pg.quit()
        sys.exit()