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
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
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()