class Sword: """ Main sword weapon. """ def __init__(self, parent_rect: pygame.Rect, depth: int = 20, slash_range: int = 40, extent: int = 10): # TODO: These Default parameters should be set in the configuration file self.parent_rect = parent_rect self.depth = depth self.slash_range = slash_range self.extent = extent self.horizontal_rect = pygame.Rect(0, 0, self.slash_range, self.depth) self.vertical_rect = pygame.Rect(0, 0, self.depth, self.slash_range) self.current_rect = self.horizontal_rect self.image = None self.action = Action(PlayerSprite.ATTACK_FRAME_DURATION * PlayerSprite.FRAME_NUMBER) self._update_current_rect_and_direction(Direction.DOWN) def attack(self, direction: Direction): if self.action.in_progress(): return self._update_current_rect_and_direction(direction) self.action.restart() def has_hit(self, enemy: Enemy): if self.action.in_progress(): return self.current_rect.colliderect(enemy.hitbox.rect) else: return False def update(self): self.action.update() def _update_current_rect_and_direction(self, direction: Direction): if direction in (Direction.DOWN, Direction.UP): self.current_rect = self.horizontal_rect else: self.current_rect = self.vertical_rect relative_position = self.extent * direction.value self.current_rect.center = self.parent_rect.center + relative_position self.set_image(BLUE) def set_image(self, color): self.image = pygame.Surface(self.current_rect.size) self.image.fill(color) self.image.set_alpha(100)
class Bow: """ Bow weapon and arrows """ SHOOT_COOLDOWN = 20 def __init__(self, parent_rect: pygame.Rect, parent_group: pygame.sprite.Group): self.parent_rect = parent_rect self.action = Action(self.SHOOT_COOLDOWN) self.quiver = pygame.sprite.Group() self.parent_group = parent_group self.proyectile_image = pygame.image.load('assets/sprites/arrow.png').convert_alpha() def attack(self, direction: Direction): if self.action.in_progress(): return arrow = Arrow(self.parent_rect.center, direction, self.proyectile_image) arrow.add(self.parent_group, self.quiver) self.action.restart() def update(self, delta: float, physical_obstacles: List[pygame.Rect]): self.action.update() self.quiver.update(delta, physical_obstacles)
class Enemy: THINK_TIME = 70 INVISIBLE_TIME = 25 MAX_VISUAL_FIELD = 6000 MAX_VELOCITY = cfg.VELOCITY * 0.5 MAX_FRAME_WAIT = 10 IDLE_TIME = 25 def __init__(self, position: Vector2, direction: Direction, life: int, sprite: EnititySprite): self.group = None self.life = life self.position = Vector2(position) self.velocity = Vector2(0, 0) self.direction = direction self.sprite = sprite self.state = sprite.state self.hitbox = Hitbox(sprite.rect) self.cooldown = Action(self.INVISIBLE_TIME) # self.think_counter = self.THINK_TIME self.force_frame_count = self.MAX_FRAME_WAIT # TODO: Move to AI self.idle_counter = 0 # TODO: Move to Action self.hit_sound = pygame.mixer.Sound("assets/sounds/hit.wav") def update(self, delta, physical_objects, player_position: Vector2): self.cooldown.update() if self.cooldown.is_idle(): # self.animation.next_frame("walk") # self.image = self.animation.current_sprite self.handle_ai(player_position) # elif self.cooldown.in_progress(): # self.blink() # self.cooldown.update() # if self.idle_counter > 0: # self.idle_counter -= 1 # self.velocity[:] = 0, 0 self.handle_collisions_with_objects(delta, physical_objects) self.move(delta) def get_hit(self, direction: Direction): if self.cooldown.is_idle(): self.cooldown.restart() self.hit_sound.play() self.velocity = cfg.VELOCITY * 2 * direction.value self.life -= 1 if self.life == 0: self.kill() def handle_ai(self, player_position: Vector2): distance_to_player = self.position.distance_squared_to(player_position) if distance_to_player < self.MAX_VISUAL_FIELD: vec_difference = player_position - self.position desired_direction = Direction.closest_direction(vec_difference) if desired_direction != self.direction: self.force_frame_count -= 1 if self.force_frame_count == 0: self.force_frame_count = self.MAX_FRAME_WAIT self.direction = desired_direction self.velocity = self.MAX_VELOCITY * self.direction.value else: self.velocity[:] = 0, 0 # elif self.think_counter == 0: # self.think_counter = choice((self.THINK_TIME, 5)) # if self.think_counter == self.THINK_TIME: # self.direction.py = self.DIRECTION_VECTOR[randint(0, 3)] # self.velocity = cfg.VELOCITY * 0.5 * self.direction.py # self.think_counter -= 1 def handle_collisions_with_objects(self, delta: float, physical_objects): for idx, vec in enumerate((Vector2(1, 0), Vector2(0, 1))): self.hitbox.move_with_respect_to_parent( self.position + delta * (self.velocity.elementwise() * vec)) if self.hitbox.has_collided_with_rects(*physical_objects): self.velocity[idx] = 0 def stay_idle(self): self.idle_counter = self.IDLE_TIME def move(self, delta): self.position.update(self.position + delta * self.velocity) self.sprite.update(self.direction, self.position, self.velocity, self.state) def kill(self): self.group.remove_enemy(self) def add(self, group): """Add to an enemy group""" self.group = group
class Player: HITBOX_DEFLATION = -16 DAMAGE_MOMENTUM = 2.5 COOLDOWN_TIME = 10 def __init__(self): self.life = 10 self.position = Vector2((cfg.DIS_WIDTH // 2, cfg.DIS_HEIGHT // 2)) self.velocity = Vector2(0, 0) self.state = State.IDLE self.direction = Direction.DOWN self.sprite_group = pygame.sprite.Group() self.sprite = PlayerSprite(self.position, self.direction, self.state, self.sprite_group) self.hitbox = Hitbox(self.sprite.rect, self.HITBOX_DEFLATION, self.HITBOX_DEFLATION) self.cooldown = Action(self.COOLDOWN_TIME) self.sword = Sword(self.sprite.rect) self.bow = Bow(self.sprite.rect, self.sprite_group) def update(self, delta: float, control: Control, objects_group: List[pygame.Rect], enemy_group: EnemyGroup): self.bow.update(delta, objects_group) self.sword.update() self.cooldown.update() if self.cooldown.is_idle() and self.sword.action.is_idle( ) and self.bow.action.is_idle(): self.handle_input(control) self.handle_collision_with_enemy(enemy_group) self.handle_collision_with_objects(delta, objects_group) self.move(delta) def handle_input(self, control: Control): self.velocity[:] = 0, 0 if control.attack: self.sword.attack(self.direction) self.state = State.ATTACK return if control.shoot: self.bow.attack(self.direction) self.state = State.IDLE return if control.up: self.velocity.y = -cfg.VELOCITY if control.down: self.velocity.y = cfg.VELOCITY if control.left: self.velocity.x = -cfg.VELOCITY if control.right: self.velocity.x = cfg.VELOCITY if self.velocity.elementwise() != 0: self.velocity = 0.7071 * self.velocity if self.velocity.length_squared() == 0: self.state = State.IDLE else: self.state = State.WALK self.update_direction() def update_direction(self): if self.velocity.y == 0: if self.velocity.x > 0: self.direction = Direction.RIGHT else: self.direction = Direction.LEFT elif self.velocity.x == 0: if self.velocity.y > 0: self.direction = Direction.DOWN else: self.direction = Direction.UP def handle_collision_with_objects(self, delta: float, physical_objects: List[pygame.Rect]): for idx, vec in enumerate((Vector2(1, 0), Vector2(0, 1))): self.hitbox.move_with_respect_to_parent( self.position + delta * (self.velocity.elementwise() * vec)) if self.hitbox.has_collided_with_rects(*physical_objects): self.velocity[idx] = 0 def handle_collision_with_enemy(self, enemy_group: EnemyGroup): enemy: Enemy for enemy in enemy_group: if self.cooldown.is_idle() and self.hitbox.has_collided_with( enemy.hitbox): self.get_hit(enemy.position) enemy.stay_idle() if self.sword.has_hit(enemy): enemy.get_hit(self.direction) for arrow in self.bow.quiver: if arrow.has_hit(enemy): enemy.get_hit(arrow.direction) arrow.kill() def get_hit(self, enemy_position: Vector2): self.cooldown.restart() self.life -= 1 vec_difference = self.position - enemy_position direction = Direction.closest_direction(vec_difference) self.velocity = cfg.VELOCITY * self.DAMAGE_MOMENTUM * direction.value def move(self, delta: float): self.position.update(self.position + delta * self.velocity) self.sprite.update(self.direction, self.position, self.velocity, self.state)