class Animation: def __init__(self, total_frames, columns, frame_duration): self.total_frames = total_frames self.columns = columns self.__frame_duration = frame_duration self.current_frame = 0 self.__timer = Timer(self.__frame_duration) self.__timer.start() def update(self, delta_time): self.__timer.update(delta_time) if self.__timer.done: self.current_frame = self.current_frame + \ 1 if self.current_frame + 1 < self.total_frames else 0 self.__timer.reset() self.__timer.start()
class Octopus(Kinetic): def __init__(self, x, y): super(Octopus, self).__init__(x, y, 16, 16, randint(10, 30)) self.timer = Timer(randint(1, 5) * 1000, True) self.sprite = Sprite(x - 16, y - 16, SpriteType.OCTOPUS) self.shadow = Sprite(x - 16 - 8, y - 16 + 16, SpriteType.OCTOPUS_SHADOW) self.i = 0 def set_location(self, x, y): super(Octopus, self).set_location(x, y) self.sprite.set_location(self.x - 16, self.y - 16) self.shadow.set_location(self.x - 16 - 8, self.y - 16 + 16) def __shoot(self, entities): entities.append(Bullet(self.x, self.y + self.height / 2, 200)) def __move(self, delta_time): self.i += 1 * delta_time self.set_location(self.x - self.move_speed, self.y + math.sin(self.i)) def update(self, delta_time, entities): self._calculate_scaled_speed(delta_time) self.__move(delta_time) self.timer.update(delta_time) if self.timer.done: if randint(1, 10) <= 7: self.__shoot(entities) self.timer.reset() self.timer.start() def draw(self, surface): if pygine.globals.debug: self._draw_bounds(surface, CameraType.DYNAMIC) else: self.shadow.draw(surface, CameraType.DYNAMIC) self.sprite.draw(surface, CameraType.DYNAMIC)
class BossCrab(Entity): def __init__(self): super(BossCrab, self).__init__(96, 128, 128, 8) self.body = Sprite(self.x - 64, self.y - 32, SpriteType.CRAB_BOSS_BODY) self.bandaid = Sprite(self.x + 2 * 16, self.y - 1 * 16, SpriteType.CRAB_BOSS_BANDAID) self.face = Sprite(self.x + 2 * 16, self.y + 2 * 16, SpriteType.CRAB_FACE_SLEEPING) self.emote = Sprite(self.x + 5 * 16, self.y - 2 * 16, SpriteType.CRAB_BOSS_EMOTE_SLEEPY) self.state_index = 0 self.total_flashes = 3 self.flashes = 0 self.flash_duration = 200 self.invinsibility_flash_timer = Timer(self.flash_duration) self.hurt = False self.flashing = False self.injured = False self.crab_smash = False self.sync_smash = 0 self.special_attack = False self.idle_timer = Timer(5000) def bop_on_head(self): if not self.hurt: self.state_index += 1 self.hurt = True self.invinsibility_flash_timer.start() if self.state_index < 5: self.face.set_sprite(SpriteType.CRAB_FACE_HURT) self.emote.set_sprite(SpriteType.CRAB_BOSS_EMOTE_MAD) def __update_ai(self, delta_time, scene_data): if self.state_index > 1 and self.state_index < 5: self.idle_timer.start() self.idle_timer.update(delta_time) if self.idle_timer.done: self.crab_smash = True self.idle_timer.reset() def __update_health(self, delta_time): if self.hurt: self.idle_timer.reset() self.invinsibility_flash_timer.update(delta_time) if self.invinsibility_flash_timer.done: self.flashes += 1 self.flashing = not self.flashing if self.flashes >= self.total_flashes * 2: self.hurt = False self.flashing = False self.flashes = 0 if self.state_index == 1: self.state_index += 1 if self.state_index < 5: self.face.set_sprite(SpriteType.CRAB_FACE_HAPPY) self.emote.set_sprite(SpriteType.NONE) self.crab_smash = True self.invinsibility_flash_timer.reset() self.invinsibility_flash_timer.start() def __update_expression(self): if self.state_index >= 5: self.emote.set_sprite(SpriteType.CRAB_BOSS_EMOTE_THIRSTY) self.face.set_sprite(SpriteType.CRAB_FACE_INJURED) self.injured = True def update(self, delta_time, scene_data): self.__update_ai(delta_time, scene_data) self.__update_health(delta_time) self.__update_expression() def draw(self, surface): if globals.debugging: draw_rectangle(surface, self.bounds, CameraType.DYNAMIC, self.color, 4) else: if not self.flashing: self.body.draw(surface, CameraType.STATIC) self.bandaid.draw(surface, CameraType.STATIC) self.face.draw(surface, CameraType.STATIC) self.emote.draw(surface, CameraType.STATIC)
class Player(Actor): def __init__(self, x, y): super(Player, self).__init__(x, y, 12, 28, 100) self.sprite = Sprite(self.x - 10, self.y - 16, SpriteType.PLAYER) self.walk_animation = Animation(6, 6, 100) self.direction = Direction.NONE self.area = None self.query_result = None self.default_jump_height = 16 * 4 self.jump_duration = 1 self.default_run_acceleration = 10 self.default_ground_friction = 7 self.default_air_friction = 1 self.jump_initial_velocity = 0 self.gravity = 0 self.lateral_acceleration = 0 self.ground_friction = 0 self.air_friction = 0 self.grounded = False self.jumping = False self.attempt_block_shift = False self.attacked = False self.restart = False self.pause = False self.transitioning = False self.restart_delay = Timer(2600) self.queue_restart = False def revive(self): self.grounded = False self.jumping = False self.attempt_block_shift = False self.attacked = False self.restart = False self.queue_restart = False self.velocity = Vector2(0, 0) self.sprite.set_frame(0, 6) def get_yeeted(self): self.__finessed_by_enemy() def _calculate_scaled_speed(self, delta_time): super(Player, self)._calculate_scaled_speed(delta_time) time = 1 / delta_time * self.jump_duration self.jump_initial_velocity = 4 * self.default_jump_height / time self.gravity = 8 * self.default_jump_height / time**2 self.lateral_acceleration = self.default_run_acceleration * delta_time self.ground_friction = self.default_ground_friction * delta_time self.air_friction = self.default_air_friction * delta_time def set_location(self, x, y): super(Player, self).set_location(x, y) self.sprite.set_location(self.x - 10, self.y - 16) def _apply_force(self, delta_time): if self.transitioning: return if not self.pause: self.velocity.y += self.gravity else: self.velocity.y += self.gravity * 0.6 self.set_location(self.x + self.velocity.x, self.y + self.velocity.y) def _update_input(self, delta_time): if self.attacked: return if not self.pause and pressing( InputType.LEFT) and not pressing(InputType.RIGHT): self.velocity.x -= self.lateral_acceleration if self.velocity.x < -self.move_speed: self.velocity.x = -self.move_speed if not self.jumping: self.sprite.set_frame(self.walk_animation.current_frame, 6) self.direction = Direction.LEFT elif not self.pause and pressing( InputType.RIGHT) and not pressing(InputType.LEFT): self.velocity.x += self.lateral_acceleration if self.velocity.x > self.move_speed: self.velocity.x = self.move_speed if not self.jumping: self.sprite.set_frame(self.walk_animation.current_frame, 6) self.direction = Direction.RIGHT elif ((not pressing(InputType.LEFT) and not pressing(InputType.RIGHT)) or (pressing(InputType.LEFT) and pressing(InputType.RIGHT))): if self.grounded: self.velocity.lerp(Vector2(0, self.velocity.y), self.ground_friction) else: self.velocity.lerp(Vector2(0, self.velocity.y), self.air_friction) if self.velocity.x > -0.1 and self.velocity.x < 0.1: self.velocity.x = 0 self.sprite.set_frame(0, 6) if not self.grounded: if self.velocity.y < 0: self.sprite.set_frame(6, 6) if self.velocity.y > 0: self.sprite.set_frame(7, 6) else: if self.velocity.x == 0: if pressing(InputType.UP): self.sprite.set_frame(8, 6) if pressing(InputType.DOWN): self.sprite.set_frame(9, 6) if self.direction == Direction.LEFT: self.sprite.flip_horizontally(True) elif self.direction == Direction.RIGHT: self.sprite.flip_horizontally(False) if pressed(InputType.A) and self.grounded and not self.jumping: self.__jump(delta_time) self.jumping = True if self.jumping and self.velocity.y < -self.jump_initial_velocity / 2 and not pressing( InputType.A): self.velocity.y = -self.jump_initial_velocity / 2 self.jumping = False if pressed(InputType.X): self.attempt_block_shift = True def __rectanlge_collision_logic(self, entity): # Bottom if self.velocity.y < 0 and self.collision_rectangles[0].colliderect( entity.bounds): self.set_location(self.x, entity.bounds.bottom) self.velocity.y = 0 # Top if self.velocity.y > 0 and self.collision_rectangles[1].colliderect( entity.bounds): self.set_location(self.x, entity.bounds.top - self.bounds.height) self.grounded = True self.jumping = False self.velocity.y = 0 # Right if self.velocity.x < 0 and self.collision_rectangles[2].colliderect( entity.bounds): self.set_location(entity.bounds.right, self.y) self.velocity.x = 0 # Left if self.velocity.x > 0 and self.collision_rectangles[3].colliderect( entity.bounds): self.set_location(entity.bounds.left - self.bounds.width, self.y) self.velocity.x = 0 def _collision(self, scene_data): if self.attacked: return if self.x < 3: self.set_location(3, self.y) if (globals.debugging): for e in scene_data.entities: e.set_color(Color.WHITE) self.area = Rect(self.x - 16, self.y - 32, self.width + 16 * 2, self.height + 32 * 2) self.grounded = False self.query_result = scene_data.entity_quad_tree.query(self.area) if self.attempt_block_shift: self.__shift_blocks(scene_data) self.attempt_block_shift = False for e in self.query_result: if e is self: continue if (globals.debugging): e.set_color(Color.RED) if isinstance(e, Block): self.__rectanlge_collision_logic(e) self._update_collision_rectangles() if isinstance(e, QBlock): if e.active: self.__rectanlge_collision_logic(e) self._update_collision_rectangles() if isinstance(e, BossCrab): if (not e.hurt and not self.grounded and self.velocity.y > 0 and self.collision_rectangles[1].colliderect( e.bounds)): e.bop_on_head() self.velocity.y = -self.jump_initial_velocity * 0.35 if not e.injured: play_sound("pain.wav", 0.4) else: play_sound("pain.wav", 0.2) self.query_result = scene_data.kinetic_quad_tree.query(self.area) for e in self.query_result: if e is self: continue if (globals.debugging): e.set_color(Color.RED) if isinstance(e, Crab): if not e.dead: if (not e.aggravated and not self.grounded and self.velocity.y > 0 and self.collision_rectangles[1].colliderect( e.bounds)): e.squish() self.velocity.y = -self.jump_initial_velocity * 0.35 play_sound("bop.wav") elif e.aggravated and self.bounds.colliderect(e.bounds): self.__finessed_by_enemy() def __jump(self, delta_time): self.velocity.y = -self.jump_initial_velocity play_sound("jump.wav") def __shift_blocks(self, scene_data): for e in self.query_result: if e is self: continue if isinstance(e, QBlock): if self.bounds.colliderect(e.bounds): play_sound("shift_fail.wav") return for e in scene_data.entities: if isinstance(e, QBlock): e.toggle() elif isinstance(e, Crab): e.toggle_aggravation() play_sound("shift.wav") def __finessed_by_enemy(self): self.attacked = True self.velocity.y = -self.jump_initial_velocity * 0.5 self.sprite.set_frame(10, 6) self.__play_mocking_music() def __play_mocking_music(self): if not self.queue_restart: play_song("fin.wav") self.queue_restart = True self.restart_delay.reset() self.restart_delay.start() def __update_death(self, delta_time, scene_data): if self.y + self.height > scene_data.scene_bounds.height: self.attacked = True self.__play_mocking_music() #if self.y + self.height > scene_data.scene_bounds.height + 64 + 128 + 64: # self.restart = True if self.queue_restart: self.restart_delay.update(delta_time) if self.restart_delay.done: self.restart = True self.queue_restart = False def __update_animation(self, delta_time): self.walk_animation.update(delta_time) def update(self, delta_time, scene_data): self._calculate_scaled_speed(delta_time) self._update_input(delta_time) self._apply_force(delta_time) self._update_collision_rectangles() self._collision(scene_data) self.__update_death(delta_time, scene_data) self.__update_animation(delta_time) def draw(self, surface): if globals.debugging: self._draw_collision_rectangles(surface) draw_rectangle(surface, self.bounds, CameraType.DYNAMIC, self.color) if self.area != None: draw_rectangle(surface, self.area, CameraType.DYNAMIC, Color.BLACK, 1) else: self.sprite.draw(surface, CameraType.DYNAMIC)
class Boat(Actor): def __init__(self, x, y, beans=50): super(Boat, self).__init__(x, y, 83, 16, 50) self.beans = beans self.playbounds = Rectangle(0, 16 * 3, Camera.BOUNDS.width, Camera.BOUNDS.height - 16 * 3) self.sprite = Sprite(x - 16, y - 48, SpriteType.BOAT) self.shadow = Sprite(x - 16 - 16, y - 16, SpriteType.BOAT_SHADOW) self.blinks = 5 self.invis_duration = 1600 self.invis_timer = Timer(self.invis_duration) self.blink_timer = Timer(self.invis_duration / self.blinks / 2) self.damaged = False self.flashing = False self.dead = False def set_location(self, x, y): super(Boat, self).set_location(x, y) self.sprite.set_location(self.x - 16, self.y - 48) self.shadow.set_location(self.x - 16 - 16, self.y - 16) def _collision(self, entities): for e in entities: if not self.damaged: if isinstance(e, Bullet) or isinstance(e, Octopus): if self.bounds.colliderect(e.bounds): e.dead = True self.__decrease_health(5) elif isinstance(e, Rock): if self.bounds.colliderect(e.bounds): self.__decrease_health(10) self.__bounds_collision() def __decrease_health(self, amount): self.damaged = True self.beans -= amount self.invis_timer.start() self.blink_timer.start() self.sprite.set_sprite(SpriteType.BOAT_OWO) def _move(self, direction=Direction.NONE): self.facing = direction if self.facing == Direction.UP: self.set_location(self.x, self.y - self.move_speed) self.velocity.y = -1 if self.facing == Direction.DOWN: self.set_location(self.x, self.y + self.move_speed) self.velocity.y = 1 if self.facing == Direction.LEFT: self.set_location(self.x - self.move_speed, self.y) self.velocity.x = -1 if self.facing == Direction.RIGHT: self.set_location(self.x + self.move_speed, self.y) self.velocity.x = 1 def _update_input(self, delta_time): self.input.update(delta_time) if self.input.pressing(InputType.UP): self._move(Direction.UP) if self.input.pressing(InputType.DOWN): self._move(Direction.DOWN) if self.input.pressing(InputType.LEFT): self._move(Direction.LEFT) if self.input.pressing(InputType.RIGHT): self._move(Direction.RIGHT) def __update_health(self, delta_time): if self.damaged: self.invis_timer.update(delta_time) self.blink_timer.update(delta_time) if self.blink_timer.done: self.flashing = not self.flashing self.blink_timer.reset() self.blink_timer.start() if self.invis_timer.done: self.damaged = False self.flashing = False self.invis_timer.reset() self.sprite.set_sprite(SpriteType.BOAT) def __bounds_collision(self): if self.x < self.playbounds.x: self.x = self.playbounds.x elif self.x + self.width > self.playbounds.x + self.playbounds.width: self.x = self.playbounds.x + self.playbounds.width - self.width if self.y < self.playbounds.y: self.y = self.playbounds.y elif self.y + self.height > self.playbounds.y + self.playbounds.height: self.y = self.playbounds.y + self.playbounds.height - self.height def __check_death(self): if self.beans <= 0: # TODO: death logic here, maybe display transition and change scene? self.dead = True def update(self, delta_time, entities): self._calculate_scaled_speed(delta_time) if self.dead: self.flashing = False self.set_location(self.x - self.move_speed / 2 + randint(-2, 0), self.y + self.move_speed) return self._update_input(delta_time) self._collision(entities) self.__update_health(delta_time) self.__check_death() def draw(self, surface): if pygine.globals.debug: self._draw_bounds(surface, CameraType.DYNAMIC) else: if not self.flashing: self.shadow.draw(surface, CameraType.DYNAMIC) self.sprite.draw(surface, CameraType.DYNAMIC)
class NPC(Kinetic): def __init__(self, x, y, type, can_move=True, horizontal=True, start_direction=1, walk_duration=5000): super(NPC, self).__init__(x, y, 10, 10, 25) self.type = type self.sprite = Sprite(self.x - 3, self.y - 22, SpriteType.NONE) self.shadow = Sprite(self.x - 3, self.y - 21, SpriteType.PLAYER_SHADOW) self.speech_bubble = SpeechBubble(self.x - 11, self.y - 32 - 11, self) self.radius = 32 self.show_prompt = False self.set_color(Color.RED) self.walk_direction = 1 if start_direction >= 0 else -1 self.horizontal = horizontal self.can_move = can_move self._walk_timer = Timer(walk_duration, True) self.animation_walk = Animation(6, 6, 100) self.walking = True self._set_walking_sprite() self._set_random_emotion() def set_location(self, x, y): super(NPC, self).set_location(x, y) self.sprite.set_location(self.x - 3, self.y - 22) self.shadow.set_location(self.x - 3, self.y - 21) self.speech_bubble.set_location(self.x - 11, self.y - 32 - 11) def _set_random_emotion(self): rand = randint(1, 4) if rand == 1: self.speech_bubble.set_content(SpriteType.FACE_HAPPY) elif rand == 2: self.speech_bubble.set_content(SpriteType.FACE_SAD) elif rand == 3: self.speech_bubble.set_content(SpriteType.FACE_MAD) elif rand == 4: self.speech_bubble.set_content(SpriteType.FACE_SURPRISED) def _stop_walking(self): if not self.horizontal: if self.type == NPCType.MALE: self.sprite.set_sprite(SpriteType.NPC_M_F) else: self.sprite.set_sprite(SpriteType.NPC_F_F) self._set_random_emotion() self.walking = not self.walking def _walk(self, delta_time): self._walk_timer.update(delta_time) if self._walk_timer.done: if random() < 0.25: self._stop_walking() if self.walking: self.walk_direction = -self.walk_direction self._set_walking_sprite() self._walk_timer.reset() self._walk_timer.start() if self.walking: if self.horizontal: self.set_location( self.x + self.move_speed * self.walk_direction, self.y) else: self.set_location( self.x, self.y + self.move_speed * self.walk_direction) def _set_walking_sprite(self): if self.can_move: if self.horizontal: if self.walk_direction > 0: if self.type == NPCType.MALE: self.sprite.set_sprite(SpriteType.NPC_M_R) else: self.sprite.set_sprite(SpriteType.NPC_F_R) else: if self.type == NPCType.MALE: self.sprite.set_sprite(SpriteType.NPC_M_L) else: self.sprite.set_sprite(SpriteType.NPC_F_L) else: if self.walk_direction > 0: if self.type == NPCType.MALE: self.sprite.set_sprite(SpriteType.NPC_M_F) else: self.sprite.set_sprite(SpriteType.NPC_F_F) else: if self.type == NPCType.MALE: self.sprite.set_sprite(SpriteType.NPC_M_B) else: self.sprite.set_sprite(SpriteType.NPC_F_B) else: if self.type == NPCType.MALE: self.sprite.set_sprite(SpriteType.NPC_M_F) else: self.sprite.set_sprite(SpriteType.NPC_F_F) def _within_radius(self, e): if distance_between(self.center, e.center) <= self.radius: self.show_prompt = True else: self.show_prompt = False def _update_conversation(self, entities): for e in entities: if isinstance(e, Player): last = self.show_prompt self._within_radius(e) if last != self.show_prompt: if self.show_prompt: entities.append(self.speech_bubble) else: entities.remove(self.speech_bubble) def _update_animation(self, delta_time): if self.walking: self.animation_walk.update(delta_time) self.sprite.set_frame(self.animation_walk.current_frame, self.animation_walk.columns) else: self.sprite.set_frame(0, self.animation_walk.columns) def _rectangle_collision_logic(self, entity): # Bottom if self.collision_rectangles[0].colliderect( entity.bounds) and self.velocity.y < 0: self.set_location(self.x, entity.bounds.bottom) # Top elif self.collision_rectangles[1].colliderect( entity.bounds) and self.velocity.y > 0: self.set_location(self.x, entity.bounds.top - self.bounds.height) # Right elif self.collision_rectangles[2].colliderect( entity.bounds) and self.velocity.x < 0: self.set_location(entity.bounds.right, self.y) # Left elif self.collision_rectangles[3].colliderect( entity.bounds) and self.velocity.x > 0: self.set_location(entity.bounds.left - self.bounds.width, self.y) def _collision(self, entities): for e in entities: if (isinstance(e, Building) or isinstance(e, Tree)): self._rectangle_collision_logic(e) def update(self, delta_time, entities): self._update_conversation(entities) self._calculate_scaled_speed(delta_time) if self.can_move: self._walk(delta_time) # self._update_collision_rectangles() # self._collision(entities) if self.can_move: self._update_animation(delta_time) def draw(self, surface): if pygine.globals.debug: self._draw_bounds(surface, CameraType.DYNAMIC) else: self.shadow.draw(surface, CameraType.DYNAMIC) self.sprite.draw(surface, CameraType.DYNAMIC)