def create_level(self): """ Creates a new player and restarts level sprites. """ self._paused = False self._game.ui.clear() self._player = PlayerCtrl() self._level = Level('level_1.tmx', self._player, self) self._timer = Timer()
def __init__(self, level_file, player, game_state): self._game_state = game_state self._score = 0 self.image = map_loader.make_map(level_file) self.rect = self.image.get_rect() self._groups = { 'all': pg.sprite.LayeredUpdates(), 'tanks': pg.sprite.Group(), 'damageable': pg.sprite.Group(), 'bullets': pg.sprite.Group(), 'obstacles': pg.sprite.Group(), 'items': pg.sprite.Group(), 'item_boxes': pg.sprite.Group() } self._ai_mobs = [] map_loader.init_sprites(self._groups, player, self._ai_mobs) self._player = player self._camera = Camera(player, self.rect.width, self.rect.height) player.camera = self._camera # Top/Bottom boundaries. BoundaryWall(0, 0, self.rect.width, 1, self._groups) BoundaryWall(0, self.rect.height, self.rect.width, 1, self._groups) # Left/Right boundaries. BoundaryWall(0, 0, 1, self.rect.height, self._groups) BoundaryWall(self.rect.width, 0, 1, self.rect.height, self._groups) self._item_timer = Timer() self.level_music = None
def __init__(self, x, y, dir, type, color, id, groups): self._layer = cfg.ITEM_LAYER SpriteW.__init__(self, x, y, _IMAGES[type][color], (groups['all'], groups['bullets'])) Movable.__init__(self, x, y) self.rotate_image(dir - Bullet.IMAGE_ROT) # Rotatable.rotate_image(self, self.image, dir - Bullet.IMAGE_ROT) self.hit_rect = self.rect self._damage = _STATS[type]["damage"] self.vel = cfg.Vec2(_STATS[type]["speed"], 0).rotate(-dir) self._lifetime = _STATS[type]["lifetime"] self._spawn_timer = Timer() self.id = id
def __init__(self, x, y, img, groups): self._layer = cfg.TANK_LAYER SpriteW.__init__( self, x, y, img, (groups['all'], groups['tanks'], groups['damageable'])) MovableNonlinear.__init__(self, x, y) Rotatable.__init__(self) Damageable.__init__(self, self.hit_rect) self.groups = groups self.MAX_ACCELERATION = 768 self._barrels = [] self._items = [] self.hit_rect.center = self.pos self._track_timer = Timer()
class Bullet(SpriteW, Movable): IMAGE_ROT = 90 def __init__(self, x, y, dir, type, color, id, groups): self._layer = cfg.ITEM_LAYER SpriteW.__init__(self, x, y, _IMAGES[type][color], (groups['all'], groups['bullets'])) Movable.__init__(self, x, y) self.rotate_image(dir - Bullet.IMAGE_ROT) # Rotatable.rotate_image(self, self.image, dir - Bullet.IMAGE_ROT) self.hit_rect = self.rect self._damage = _STATS[type]["damage"] self.vel = cfg.Vec2(_STATS[type]["speed"], 0).rotate(-dir) self._lifetime = _STATS[type]["lifetime"] self._spawn_timer = Timer() self.id = id @classmethod def range(self, type): return _STATS[type]["speed"] * (_STATS[type]["lifetime"] / 1000) @property def damage(self): return self._damage def update(self, dt): if self._spawn_timer.elapsed_time() > self._lifetime: self.kill() else: self.move(dt)
def __init__(self, color, type, parent, offset, image, groups): self._layer = cfg.BARREL_LAYER SpriteW.__init__(self, *parent.rect.center, image, (groups['all'], )) Rotatable.__init__(self) self.groups = groups # Bullet parameters. self._type = type self._color = color self._ammo_count = _STATS[self._type]["max_ammo"] # Parameters used for position. self._parent = parent self._offset = offset self._fire_delay = _STATS[self._type]["fire_delay"] self._fire_timer = Timer() self._fire_sfx = sfx_loader.get_sfx(Barrel._FIRE_SFX)
class DurationItem(Item): def __init__(self, x, y, duration, image, sound, groups): Item.__init__(self, x, y, image, sound, groups) self._effect_timer = Timer() self._item_duration = duration self._tank = None def apply_effect(self, tank): self._effect_timer.restart() self._tank = tank super().kill() def effect_subsided(self): if self._effect_timer.elapsed_time() > self._item_duration: self._remove_effect() return True return False def _remove_effect(self): pass
def pause(self): if self._paused: self._game.ui.pop_menu() Timer.unpause_timers() else: actions = [{ 'action': self.pause, 'text': 'Resume' }, { 'action': self.create_level, 'text': 'Restart' }, { 'action': lambda: self._game.set_state(GameNotPlayingState), 'text': 'Main Menu' }] self._game.ui.make_menu("Game Paused", actions, 24, cfg.WHITE) Timer.pause_timers() self._paused = not self._paused
class MuzzleFlash(SpriteW): IMG_ROT = -90 FLASH_DURATION = 25 IMAGE = 'shotLarge.png' def __init__(self, x, y, rot, groups): self._layer = cfg.EFFECTS_LAYER SpriteW.__init__(self, x, y, MuzzleFlash.IMAGE, groups['all']) self.rotate_image(rot - MuzzleFlash.IMG_ROT) # Rotatable.rotate_image(self, self.image, rot - MuzzleFlash.IMG_ROT) self._spawn_timer = Timer() def update(self, dt): if self._spawn_timer.elapsed_time() > MuzzleFlash.FLASH_DURATION: self.kill()
class AITankState: WALL_AVOID_DURATION = 1000 WALL_TURN_ANGLE = 15 _crash_timer = Timer() def __init__(self, ai): self._ai = ai # self._crash_time = -math.inf def check_for_walls(self): if self._ai.tank.collided_with_wall(): # self._crash_time = pg.time.get_ticks() AITankState._crash_timer.restart() self._ai.rotate_to(self._ai.tank.rot + AITankState.WALL_TURN_ANGLE) def is_avoiding_wall(self): # return cfg.time_since(self._crash_time) < AITankState.WALL_AVOID_DURATION return AITankState._crash_timer.elapsed_time() < AITankState.WALL_AVOID_DURATION
def __init__(self, x, y, duration, image, sound, groups): Item.__init__(self, x, y, image, sound, groups) self._effect_timer = Timer() self._item_duration = duration self._tank = None
class Level: MAX_ITEMS = 1 ITEM_RESPAWN_TIME = 5000 def __init__(self, level_file, player, game_state): self._game_state = game_state self._score = 0 self.image = map_loader.make_map(level_file) self.rect = self.image.get_rect() self._groups = { 'all': pg.sprite.LayeredUpdates(), 'tanks': pg.sprite.Group(), 'damageable': pg.sprite.Group(), 'bullets': pg.sprite.Group(), 'obstacles': pg.sprite.Group(), 'items': pg.sprite.Group(), 'item_boxes': pg.sprite.Group() } self._ai_mobs = [] map_loader.init_sprites(self._groups, player, self._ai_mobs) self._player = player self._camera = Camera(player, self.rect.width, self.rect.height) player.camera = self._camera # Top/Bottom boundaries. BoundaryWall(0, 0, self.rect.width, 1, self._groups) BoundaryWall(0, self.rect.height, self.rect.width, 1, self._groups) # Left/Right boundaries. BoundaryWall(0, 0, 1, self.rect.height, self._groups) BoundaryWall(self.rect.width, 0, 1, self.rect.height, self._groups) self._item_timer = Timer() self.level_music = None def _can_spawn_item(self): return self._item_timer.elapsed_time() > Level.ITEM_RESPAWN_TIME def update(self, dt): for ai in self._ai_mobs: ai.update(dt) self._groups['all'].update(dt) # Update list of ai mobs. self._camera.update() # Handle damage-causing bullets. hits = pg.sprite.groupcollide(self._groups['damageable'], self._groups['bullets'], False, True, bullet_collide_id) for sprite, bullets in hits.items(): for bullet in bullets: Explosion(bullet.pos.x, bullet.pos.y, self._groups) sprite.damage(bullet.damage) if sprite.health <= 0: sprite.kill() if not self._player.alive(): self._game_state.game_over() else: self._ai_mobs = [ ai for ai in self._ai_mobs if ai.alive() ] if len(self._ai_mobs) == 0: self._game_state.game_over() break # Handle bullets that destroy item boxes. for box in self._groups['item_boxes']: if not box.is_broken(): for bullet in self._groups['bullets']: if collide_hit_rect(box, bullet): bullet.kill() box.wear_out() if box.is_broken(): self._item_timer.restart() break # See if it's time to spawn a new item. if len(self._groups['item_boxes'] ) < Level.MAX_ITEMS and self._can_spawn_item(): spawn_box(self._groups) # Handle bullets that hit other obstacles. hits = pg.sprite.groupcollide(self._groups['bullets'], self._groups['obstacles'], True, False, bullet_collide_id) # Handle item pick-up. hits = pg.sprite.groupcollide(self._groups['tanks'], self._groups['items'], False, False) for tank, items in hits.items(): for item in items: tank.pickup(item) # item.apply_effect(tank) # Tank/tank collision. all_tanks = list(self._groups['tanks']) for i in range(0, len(all_tanks)): tank_a = all_tanks[i] for tank_b in all_tanks[i + 1:]: if collide_hit_rect(tank_a, tank_b): knockback_dir = tank_b.rot tank_a.vel += vec(tank_b.KNOCKBACK, 0).rotate(knockback_dir) tank_b.vel -= vec(tank_b.KNOCKBACK, 0).rotate(knockback_dir) def draw(self, screen): screen.blit(self.image, self._camera.apply(self.rect)) # self._groups['all'].draw(screen) for sprite in self._groups['all']: screen.blit(sprite.image, self._camera.apply(sprite.rect)) # pg.draw.rect(screen, (255, 255, 255), self._camera.apply(sprite.hit_rect), 1) for ai in self._ai_mobs: ai.draw_health(screen, self._camera) self._player.draw_hud(screen, self._camera)
def fire(self): ammo = self._tank.get_ammo_count() if ammo > 0: if ammo == 1: self._ammo_timer = Timer() self._tank.fire()
class Tank(SpriteW, MovableNonlinear, Rotatable, Damageable): KNOCKBACK = 100 _SPEED_CUTOFF = 100 _TRACK_DELAY = 100 def __init__(self, x, y, img, groups): self._layer = cfg.TANK_LAYER SpriteW.__init__( self, x, y, img, (groups['all'], groups['tanks'], groups['damageable'])) MovableNonlinear.__init__(self, x, y) Rotatable.__init__(self) Damageable.__init__(self, self.hit_rect) self.groups = groups self.MAX_ACCELERATION = 768 self._barrels = [] self._items = [] self.hit_rect.center = self.pos self._track_timer = Timer() def update(self, dt): self.rotate(dt) self.move(self.groups['obstacles'], dt) for item in self._items: if item.effect_subsided(): self._items.remove(item) can_spawn_track = self._track_timer.elapsed_time() > Tank._TRACK_DELAY if self.vel.length_squared() > Tank._SPEED_CUTOFF and can_spawn_track: self._spawn_tracks() @property def range(self): return self._barrels[0].range def pickup(self, item): item.apply_effect(self) if item.DURATION > 0: self._items.append(item) def equip_barrel(self, barrel): barrel.id = self.id self._barrels.append(barrel) def _spawn_tracks(self): Tracks(*self.pos, self.hit_rect.height, self.hit_rect.height, self.rot, self.groups) self._track_timer.restart() def rotate_barrel(self, dir): # pass for barrel in self._barrels: barrel.rot = dir barrel.rotate() def get_ammo_count(self): return self._barrels[0].get_ammo_count() def fire(self): for barrel in self._barrels: barrel.fire() def attempt_reload(self): if self._barrels[0].can_reload(): self.reload() def reload(self): for barrel in self._barrels: barrel.reload() def kill(self): for barrel in self._barrels: barrel.kill() super().kill()
class Barrel(SpriteW, Rotatable): _FIRE_SFX = 'shoot.wav' def __init__(self, color, type, parent, offset, image, groups): self._layer = cfg.BARREL_LAYER SpriteW.__init__(self, *parent.rect.center, image, (groups['all'], )) Rotatable.__init__(self) self.groups = groups # Bullet parameters. self._type = type self._color = color self._ammo_count = _STATS[self._type]["max_ammo"] # Parameters used for position. self._parent = parent self._offset = offset self._fire_delay = _STATS[self._type]["fire_delay"] self._fire_timer = Timer() self._fire_sfx = sfx_loader.get_sfx(Barrel._FIRE_SFX) def update(self, dt): self.rect.center = cfg.Vec2(*self._parent.rect.center) + \ self._offset.rotate(-self.rot) @property def range(self): return Bullet.range(self._type) @property def fire_delay(self): return self._fire_delay @fire_delay.setter def fire_delay(self, new_fire_delay): self._fire_delay = new_fire_delay def fire(self): if self._fire_timer.elapsed_time() > self._fire_delay: self._fire_timer.restart() self._spawn_bullet() self._fire_sfx.play() def _spawn_bullet(self): fire_pos = cfg.Vec2(*self.rect.center) + \ cfg.Vec2(self.hit_rect.height, 0).rotate(-self.rot) Bullet(*fire_pos, self.rot, self._type, self._color, self.id, self.groups) MuzzleFlash(*fire_pos, self.rot, self.groups) self._ammo_count -= 1 def can_reload(self): return self._ammo_count == 0 and self._fire_timer.elapsed_time( ) > RELOAD_DURATION def get_ammo_count(self): return self._ammo_count def reload(self): self._ammo_count = _STATS[self._type]["max_ammo"] def kill(self): self._parent = None super().kill()
def __init__(self, x, y, rot, groups): self._layer = cfg.EFFECTS_LAYER SpriteW.__init__(self, x, y, MuzzleFlash.IMAGE, groups['all']) self.rotate_image(rot - MuzzleFlash.IMG_ROT) # Rotatable.rotate_image(self, self.image, rot - MuzzleFlash.IMG_ROT) self._spawn_timer = Timer()
class GamePlayingState: def __init__(self, game): self._game = game self._paused = False self._game_over = False def enter(self): self.create_level() def create_level(self): """ Creates a new player and restarts level sprites. """ self._paused = False self._game.ui.clear() self._player = PlayerCtrl() self._level = Level('level_1.tmx', self._player, self) self._timer = Timer() def exit(self): # Save score or something self._game.ui.clear() def process_events(self, event): if not self._game_over: if event.type == pg.KEYDOWN: if event.key == pg.K_p: self.pause() def pause(self): if self._paused: self._game.ui.pop_menu() Timer.unpause_timers() else: actions = [{ 'action': self.pause, 'text': 'Resume' }, { 'action': self.create_level, 'text': 'Restart' }, { 'action': lambda: self._game.set_state(GameNotPlayingState), 'text': 'Main Menu' }] self._game.ui.make_menu("Game Paused", actions, 24, cfg.WHITE) Timer.pause_timers() self._paused = not self._paused def game_over(self): actions = [{ 'action': lambda: self._game.set_state(GamePlayingState), 'text': 'Play Again' }, { 'action': lambda: self._game.set_state(GameNotPlayingState), 'text': 'Main Menu' }, { 'action': self._game.quit, 'text': 'Quit' }] if self._player.alive(): title = "You win!" else: title = "Game Over!" self._game.ui.make_menu(title, actions, 24, cfg.WHITE) self._timer.pause() self._game_over = True def handle_input(self, active_bindings, mouse_state, mouse_x, mouse_y): if not (self._paused or self._game_over): self._player.handle_keys(active_bindings) self._player.handle_mouse(mouse_state, mouse_x, mouse_y) def update(self, dt): if not self._paused: self._level.update(dt) def draw(self): self._level.draw(self._game.screen) self._game.ui.draw(self._game.screen) total_secs = self._timer.elapsed_time() // 1000 mins = total_secs // 60 secs = total_secs % 60 gtext.render(self._game.screen, f"{mins:02d}:{secs:02d}", 24, cfg.WHITE, location='n')