def __init__(self, start_pos, initial_heading_vector, current_target, target_position, damage, speed, explosions_sprite_sheet, image_atlas, homing_radius, collision_grid, *groups): super().__init__(*groups) self.explosions_sprite_sheet = explosions_sprite_sheet self.original_image = image_atlas.subsurface((0, 256, 6, 12)) self.current_vector = [ initial_heading_vector[0], initial_heading_vector[1] ] self.target_vector = self.current_vector self.position = [float(start_pos[0]), float(start_pos[1])] facing_angle = math.atan2(-self.current_vector[0], -self.current_vector[1]) * 180 / math.pi self.image = pygame.transform.rotate(self.original_image, facing_angle) self.rect = self.image.get_rect() self.rect.center = start_pos self.collision_grid = collision_grid handlers_by_type = { GameCollisionType.MONSTER: self.collision_grid.no_handler } self.collision_shape = CollisionRect( self.original_image.get_rect(), math.atan2(-self.current_vector[0], -self.current_vector[1]), handlers_by_type, GameCollisionType.TURRET_PROJECTILE, [GameCollisionType.MONSTER]) self.collision_shape.set_owner(self) self.collision_grid.add_new_shape_to_grid(self.collision_shape) self.drawable_collision_rect = DrawableCollisionRect( self.collision_shape) self.should_die = False self.projectile_speed = speed self.damage = damage self.target_position = target_position x_diff = self.target_position[0] - self.position[0] y_diff = self.target_position[1] - self.position[1] total_target_dist = math.sqrt(x_diff**2 + y_diff**2) self.target_distance = total_target_dist + 16.0 self.shot_range = 600.0 self.rotate_speed = 10.0 self.time_to_home_in = False self.homing_time_acc = 0.0 self.homing_time = 0.2 self.homing_radius = homing_radius self.current_target = current_target
def __init__(self, start_pos, initial_heading_vector, damage, collision_grid, arrow_image, *groups): super().__init__(*groups) self.collision_grid = collision_grid self.original_image = arrow_image self.image = self.original_image.copy() self.rect = self.image.get_rect() self.rect.center = start_pos self.current_vector = [ initial_heading_vector[0], initial_heading_vector[1] ] self.position = [ float(self.rect.center[0]), float(self.rect.center[1]) ] self.world_position = [ float(self.rect.center[0]), float(self.rect.center[1]) ] self.should_die = False self.bullet_speed = 300.0 self.damage = damage self.shot_range = 1000.0 self.collision_rect = CollisionRect( self.rect, 0, { GameCollisionType.MONSTER: CollisionNoHandler(), GameCollisionType.TILE: CollisionNoHandler() }, GameCollisionType.PLAYER_WEAPON, [GameCollisionType.MONSTER, GameCollisionType.TILE]) self.collision_rect.set_owner(self) self.collision_grid.add_new_shape_to_grid(self.collision_rect) self.drawable_rect = DrawableCollisionRect(self.collision_rect)
def update_melee_attacking(self, time_delta): if self.active_anim.running: if 3 < self.active_anim.frame_index < 10: if self.x_facing_direction == "left": self.move_speed -= self.attack_slide_acceleration * time_delta if self.move_speed < -self.attack_slide_top_speed: self.move_speed = -self.attack_slide_top_speed self.velocity[0] = self.move_speed elif self.x_facing_direction == "right": self.move_speed += self.attack_slide_acceleration * time_delta if self.move_speed > self.attack_slide_top_speed: self.move_speed = self.attack_slide_top_speed self.velocity[0] = self.move_speed if self.active_anim.frame_index == 3 and self.melee_collision_shape is None: attack_position = [ self.world_position[0], self.world_position[1] ] if self.x_facing_direction == "left": attack_position[0] -= 64 attack_dimensions = [64, 16] game_types_to_collide_with = [CollisionType.AI] handlers_to_use = { CollisionType.AI: self.collision_grid.no_handler } self.melee_collision_shape = CollisionRect( pygame.Rect(attack_position[0], attack_position[1], attack_dimensions[0], attack_dimensions[1]), 0, handlers_to_use, CollisionType.PLAYER_ATTACKS, game_types_to_collide_with) self.collision_grid.add_new_shape_to_grid( self.melee_collision_shape) self.melee_collision_shape.owner = self self.melee_attack_collision_drawable_rectangle = DrawableCollisionRect( self.melee_collision_shape) elif self.active_anim.frame_index > 3 and self.melee_collision_shape is not None: self.collision_grid.remove_shape_from_grid( self.melee_collision_shape) self.melee_collision_shape = None else: self.motion_state = "idle"
def __init__(self, moving_sprites_group, collision_grid, knife_surface, start_position, throwing_direction, projectile_drawable_rects, camera, is_player_weapon=True, speed=1020.0): super().__init__(moving_sprites_group) self.collision_grid = collision_grid self.moving_sprites_group = moving_sprites_group self.original_image = knife_surface self.image = self.original_image.copy() self.rect = self.image.get_rect() self.knife_embed_length = (self.rect.width/9)*4 self.facing_direction = throwing_direction[:] self.facing_angle = math.degrees(math.atan2(self.facing_direction[1], self.facing_direction[0])) self.world_position = [start_position[0], start_position[1]] self.screen_position = [0.0, 0.0] self.throw_speed = speed + random.normalvariate(0, 40) self.initial_throw_velocity = [self.facing_direction[0]*self.throw_speed, self.facing_direction[1]*self.throw_speed] self.velocity = [self.initial_throw_velocity[0], self.initial_throw_velocity[1]] game_types_to_collide_with = [CollisionType.WORLD_SOLID, CollisionType.WORLD_PLATFORM_EDGE, CollisionType.WORLD_JUMP_THROUGH, CollisionType.WORLD_JUMP_THROUGH_EDGE] handlers_to_use = {CollisionType.WORLD_SOLID: self.collision_grid.rub_handler, CollisionType.WORLD_PLATFORM_EDGE: self.collision_grid.rub_handler, CollisionType.WORLD_JUMP_THROUGH: self.collision_grid.rub_handler, CollisionType.WORLD_JUMP_THROUGH_EDGE: self.collision_grid.rub_handler} if is_player_weapon: self.type = "player_projectile" collision_type = CollisionType.PLAYER_PROJECTILES game_types_to_collide_with.extend([CollisionType.AI,CollisionType.AI_PROJECTILES]) handlers_to_use[CollisionType.AI] = self.collision_grid.no_handler handlers_to_use[CollisionType.AI_PROJECTILES] = self.collision_grid.no_handler else: self.type = "enemy_projectile" collision_type = CollisionType.AI_PROJECTILES game_types_to_collide_with.extend([CollisionType.PLAYER, CollisionType.PLAYER_PROJECTILES]) handlers_to_use[CollisionType.PLAYER] = self.collision_grid.no_handler handlers_to_use[CollisionType.PLAYER_PROJECTILES] = self.collision_grid.no_handler self.collision_shape = CollisionRect(pygame.Rect(self.world_position[0], self.world_position[1], self.rect.width, int(self.rect.height*0.6)), 0, handlers_to_use, collision_type, game_types_to_collide_with) self.collision_shape.owner = self self.collision_grid.add_new_shape_to_grid(self.collision_shape) self.collision_shape.set_position(self.world_position) self.projectile_drawable_rects = projectile_drawable_rects self.drawable_rectangle = DrawableCollisionRect(self.collision_shape) self.projectile_drawable_rects.append(self.drawable_rectangle) self.frozen = False self.life_time = 15.0 self.hit_something = False self.update_screen_position(camera) self.rect.centerx = int(self.screen_position[0]) self.rect.centery = int(self.screen_position[1]) self.should_kill = False
class ThrowingKnife(Projectile): def __init__(self, moving_sprites_group, collision_grid, knife_surface, start_position, throwing_direction, projectile_drawable_rects, camera, is_player_weapon=True, speed=1020.0): super().__init__(moving_sprites_group) self.collision_grid = collision_grid self.moving_sprites_group = moving_sprites_group self.original_image = knife_surface self.image = self.original_image.copy() self.rect = self.image.get_rect() self.knife_embed_length = (self.rect.width/9)*4 self.facing_direction = throwing_direction[:] self.facing_angle = math.degrees(math.atan2(self.facing_direction[1], self.facing_direction[0])) self.world_position = [start_position[0], start_position[1]] self.screen_position = [0.0, 0.0] self.throw_speed = speed + random.normalvariate(0, 40) self.initial_throw_velocity = [self.facing_direction[0]*self.throw_speed, self.facing_direction[1]*self.throw_speed] self.velocity = [self.initial_throw_velocity[0], self.initial_throw_velocity[1]] game_types_to_collide_with = [CollisionType.WORLD_SOLID, CollisionType.WORLD_PLATFORM_EDGE, CollisionType.WORLD_JUMP_THROUGH, CollisionType.WORLD_JUMP_THROUGH_EDGE] handlers_to_use = {CollisionType.WORLD_SOLID: self.collision_grid.rub_handler, CollisionType.WORLD_PLATFORM_EDGE: self.collision_grid.rub_handler, CollisionType.WORLD_JUMP_THROUGH: self.collision_grid.rub_handler, CollisionType.WORLD_JUMP_THROUGH_EDGE: self.collision_grid.rub_handler} if is_player_weapon: self.type = "player_projectile" collision_type = CollisionType.PLAYER_PROJECTILES game_types_to_collide_with.extend([CollisionType.AI,CollisionType.AI_PROJECTILES]) handlers_to_use[CollisionType.AI] = self.collision_grid.no_handler handlers_to_use[CollisionType.AI_PROJECTILES] = self.collision_grid.no_handler else: self.type = "enemy_projectile" collision_type = CollisionType.AI_PROJECTILES game_types_to_collide_with.extend([CollisionType.PLAYER, CollisionType.PLAYER_PROJECTILES]) handlers_to_use[CollisionType.PLAYER] = self.collision_grid.no_handler handlers_to_use[CollisionType.PLAYER_PROJECTILES] = self.collision_grid.no_handler self.collision_shape = CollisionRect(pygame.Rect(self.world_position[0], self.world_position[1], self.rect.width, int(self.rect.height*0.6)), 0, handlers_to_use, collision_type, game_types_to_collide_with) self.collision_shape.owner = self self.collision_grid.add_new_shape_to_grid(self.collision_shape) self.collision_shape.set_position(self.world_position) self.projectile_drawable_rects = projectile_drawable_rects self.drawable_rectangle = DrawableCollisionRect(self.collision_shape) self.projectile_drawable_rects.append(self.drawable_rectangle) self.frozen = False self.life_time = 15.0 self.hit_something = False self.update_screen_position(camera) self.rect.centerx = int(self.screen_position[0]) self.rect.centery = int(self.screen_position[1]) self.should_kill = False def update(self, time_delta, gravity, camera): self.life_time -= time_delta if self.life_time <= 0.0: self.should_kill = True if self.collision_shape is not None: if len(self.collision_shape.collided_shapes_this_frame) > 0: for shape in self.collision_shape.collided_shapes_this_frame: if shape.game_type == CollisionType.AI or shape.game_type == CollisionType.PLAYER or shape.game_type == CollisionType.PLAYER_PROJECTILES or shape.game_type == CollisionType.AI_PROJECTILES: self.should_kill = True else: if not self.hit_something: self.hit_something = True # A bunch of code here to make sure daggers stick into things in a meaty fashion self.world_position[0] = self.collision_shape.x self.world_position[1] = self.collision_shape.y self.velocity = [0.0, 0.0] embed = random.normalvariate(self.knife_embed_length, self.knife_embed_length/8) self.world_position[0] += self.facing_direction[0] * embed self.world_position[1] += self.facing_direction[1] * embed self.collision_shape.set_position(self.world_position) self.frozen = True if not self.frozen: vel_length = math.sqrt(self.velocity[0] ** 2 + self.velocity[1] ** 2) if vel_length == 0.0: vel_length = 0.0001 self.facing_direction = [self.velocity[0] / vel_length, self.velocity[1] / vel_length] self.facing_angle = math.degrees(math.atan2(self.facing_direction[1], self.facing_direction[0])) self.image = pygame.transform.rotate(self.original_image, -self.facing_angle) self.rect = self.image.get_rect() self.collision_shape.set_rotation(math.radians(-self.facing_angle)) self.drawable_rectangle.on_rotation() self.velocity[1] += gravity * time_delta self.world_position[0] += self.velocity[0] * time_delta self.world_position[1] += self.velocity[1] * time_delta # set the position of the collision shape self.collision_shape.set_position(self.world_position) self.update_screen_position(camera) self.rect.centerx = int(self.screen_position[0]) self.rect.centery = int(self.screen_position[1]) if self.frozen and self.collision_shape is not None: self.collision_grid.remove_shape_from_grid(self.collision_shape) self.projectile_drawable_rects.remove(self.drawable_rectangle) self.collision_shape = None if self.should_kill: self.kill() if self.collision_shape is not None: self.collision_grid.remove_shape_from_grid(self.collision_shape) self.projectile_drawable_rects.remove(self.drawable_rectangle) self.collision_shape = None def update_screen_position(self, camera): view_top_left_position = (camera.position[0] - camera.half_width, camera.position[1] - camera.half_height) self.screen_position[0] = self.world_position[0] - view_top_left_position[0] self.screen_position[1] = self.world_position[1] - view_top_left_position[1]
class Arrow(Projectile): def __init__(self, start_pos, initial_heading_vector, damage, collision_grid, arrow_image, *groups): super().__init__(*groups) self.collision_grid = collision_grid self.original_image = arrow_image self.image = self.original_image.copy() self.rect = self.image.get_rect() self.rect.center = start_pos self.current_vector = [ initial_heading_vector[0], initial_heading_vector[1] ] self.position = [ float(self.rect.center[0]), float(self.rect.center[1]) ] self.world_position = [ float(self.rect.center[0]), float(self.rect.center[1]) ] self.should_die = False self.bullet_speed = 300.0 self.damage = damage self.shot_range = 1000.0 self.collision_rect = CollisionRect( self.rect, 0, { GameCollisionType.MONSTER: CollisionNoHandler(), GameCollisionType.TILE: CollisionNoHandler() }, GameCollisionType.PLAYER_WEAPON, [GameCollisionType.MONSTER, GameCollisionType.TILE]) self.collision_rect.set_owner(self) self.collision_grid.add_new_shape_to_grid(self.collision_rect) self.drawable_rect = DrawableCollisionRect(self.collision_rect) def remove_from_grid(self): self.collision_grid.remove_shape_from_grid(self.collision_rect) def react_to_collision(self): if not self.should_die: if len(self.collision_rect.collided_shapes_this_frame) > 0: self.should_die = True for shape in self.collision_rect.collided_shapes_this_frame: if shape.game_type == GameCollisionType.MONSTER: shape.owner.take_damage( Damage(self.damage, DamageType.PHYSICAL)) def draw_collision_shape(self, screen): self.drawable_rect.on_rotation() self.drawable_rect.update_collided_colours() self.drawable_rect.draw(screen) def calc_facing_angle_rad(self): direction_magnitude = math.sqrt(self.current_vector[0]**2 + self.current_vector[1]**2) unit_dir_vector = [0, 0] if direction_magnitude > 0.0: unit_dir_vector = [ self.current_vector[0] / direction_magnitude, self.current_vector[1] / direction_magnitude ] facing_angle = math.atan2(-unit_dir_vector[0], -unit_dir_vector[1]) # *180/math.pi return facing_angle def update(self, tiled_level, time_delta): if not self.should_die: self.shot_range -= time_delta * self.bullet_speed self.world_position[0] += (self.current_vector[0] * time_delta * self.bullet_speed) self.world_position[1] += (self.current_vector[1] * time_delta * self.bullet_speed) self.position[ 0] = self.world_position[0] - tiled_level.position_offset[0] self.position[ 1] = self.world_position[1] - tiled_level.position_offset[1] self.rect.center = self.position # calc facing angle & convert to degrees facing_angle = self.calc_facing_angle_rad() * 180 / math.pi bullet_centre_position = self.rect.center self.image = pygame.transform.rotate(self.original_image, facing_angle) self.rect = self.image.get_rect() self.rect.center = bullet_centre_position # update collision shape position and rotation self.collision_rect.set_rotation(facing_angle * math.pi / 180.0) self.collision_rect.set_position(self.world_position) if self.shot_range <= 0.0: self.should_die = True if self.should_die: self.collision_grid.remove_shape_from_grid(self.collision_rect) self.kill()
def __init__(self, start_position, moving_sprites_group, ui_sprites_group, collision_grid, archer_image): super().__init__(moving_sprites_group) self.type = "enemy" self.moving_sprites_group = moving_sprites_group self.ui_sprites_group = ui_sprites_group self.collision_grid = collision_grid self.atlas_image = archer_image self.anim_sets = {"idle_right": AnimSet(atlas_surface=self.atlas_image, start_pos=[0, 0], frame_size=[64, 96], num_frames=8, base_speed=8, centre_offset=[0, -2]), "idle_left": AnimSet(atlas_surface=self.atlas_image, start_pos=[0, 0], frame_size=[64, 96], num_frames=8, base_speed=8, centre_offset=[0, -2], x_flip=True), "walk_right": AnimSet(atlas_surface=self.atlas_image, start_pos=[0, 96], frame_size=[64, 96], num_frames=6, base_speed=8, centre_offset=[0, -2]), "walk_left": AnimSet(atlas_surface=self.atlas_image, start_pos=[0, 96], frame_size=[64, 96], num_frames=6, base_speed=8, centre_offset=[0, -2], x_flip=True), "attack_right": AnimSet(atlas_surface=self.atlas_image, start_pos=[0, 192], frame_size=[96, 96], num_frames=6, base_speed=8, centre_offset=[0, -2]), "attack_left": AnimSet(atlas_surface=self.atlas_image, start_pos=[0, 192], frame_size=[96, 96], num_frames=6, base_speed=8, centre_offset=[32, -2], x_flip=True) } self.active_anim = self.anim_sets["idle_left"] self.image = self.active_anim.current_frame self.rect = self.image.get_rect() self.start_position = start_position[:] self.world_position = [coord for coord in self.start_position] self.screen_position = [self.world_position[0], self.world_position[1]] game_types_to_collide_with = [CollisionType.WORLD_SOLID, CollisionType.WORLD_JUMP_THROUGH, CollisionType.WORLD_PLATFORM_EDGE, CollisionType.WORLD_JUMP_THROUGH_EDGE, CollisionType.PLAYER_ATTACKS, CollisionType.PLAYER_PROJECTILES] handlers_to_use = {CollisionType.WORLD_SOLID: collision_grid.rub_handler, CollisionType.WORLD_JUMP_THROUGH: collision_grid.rub_handler, CollisionType.WORLD_PLATFORM_EDGE: collision_grid.rub_handler, CollisionType.WORLD_JUMP_THROUGH_EDGE: collision_grid.rub_handler, CollisionType.PLAYER_ATTACKS: collision_grid.no_handler, CollisionType.PLAYER_PROJECTILES: collision_grid.no_handler} self.collision_shape_offset = [0, 0] self.collision_shape = CollisionRect(pygame.Rect(self.world_position[0], self.world_position[1], self.rect.width, self.rect.height), 0, handlers_to_use, CollisionType.AI, game_types_to_collide_with) self.collision_grid.add_new_shape_to_grid(self.collision_shape) self.collision_shape.owner = self self.base_health = 100 self.current_health = self.base_health self.rect.centerx = int(self.screen_position[0]) self.rect.centery = int(self.screen_position[1]) self.velocity = [0.0, 0.0] self.motion_state = "idle" self.frames_falling = 0 self.touching_ground = False self.move_speed = 0.0 self.x_facing_direction = "left" self.changed_direction_recently = False self.health_ui = UIEnemyHealthBar(self, self.ui_sprites_group) # debug draw self.drawable_collision_rectangle = DrawableCollisionRect(self.collision_shape) # view cone params if self.x_facing_direction == "right": facing_vec = [1.0, 0.0] else: facing_vec = [-1.0, 0.0] self.view_cone = ViewCone(self.world_position, facing_vec, fov=30.0, length=400.0) self.collision_grid.add_new_shape_to_grid(self.view_cone.collision_circle) self.visible_enemies = None self.closest_visible_enemy = None arrow_image_position = [576, 0] arrow_image_dimensions = [46, 7] arrow_image_rect = pygame.Rect(arrow_image_position, arrow_image_dimensions) self.arrow_image = self.atlas_image.subsurface(arrow_image_rect).convert_alpha() self.time_since_last_saw_enemy = 0 self.enemy_spotted_timeout = 3.0 self.is_time_to_fire = False self.fire_time = 1.0 self.fire_timer = 0.0 self.active_arrows = [] self.projectile_drawable_rects = [] self.has_moved = False self.disable_movement = False self.sprite_flash_acc = 0.0 self.sprite_flash_time = 0.25 self.should_flash_sprite = False self.active_flash_sprite = False self.last_impact_direction_vec = None self.direction_change_time = 1.0 self.direction_change_timer = 0.0 self.impact_velocity = None self.impact_time = 0.1 self.impact_timer = 0.0 self.last_frame_x_pos = self.world_position[0] self.last_collision_shape_x = self.collision_shape.x self.collision_print_time = 1.0 self.collision_print_timer = 0.0 # debuggers self.start_frame_collision_shape_x = self.collision_shape.x self.pre_collision_gets_moved_x = self.collision_shape.x self.post_collision_gets_moved_x = self.collision_shape.x self.alive = True
class EnemyArcher(pygame.sprite.Sprite): """ An enemy type for the Vania game. This guy is going to stroll about from left to right and back again on a platform, then, if he sees the player, shoot an arrow in that general direction. """ def __init__(self, start_position, moving_sprites_group, ui_sprites_group, collision_grid, archer_image): super().__init__(moving_sprites_group) self.type = "enemy" self.moving_sprites_group = moving_sprites_group self.ui_sprites_group = ui_sprites_group self.collision_grid = collision_grid self.atlas_image = archer_image self.anim_sets = {"idle_right": AnimSet(atlas_surface=self.atlas_image, start_pos=[0, 0], frame_size=[64, 96], num_frames=8, base_speed=8, centre_offset=[0, -2]), "idle_left": AnimSet(atlas_surface=self.atlas_image, start_pos=[0, 0], frame_size=[64, 96], num_frames=8, base_speed=8, centre_offset=[0, -2], x_flip=True), "walk_right": AnimSet(atlas_surface=self.atlas_image, start_pos=[0, 96], frame_size=[64, 96], num_frames=6, base_speed=8, centre_offset=[0, -2]), "walk_left": AnimSet(atlas_surface=self.atlas_image, start_pos=[0, 96], frame_size=[64, 96], num_frames=6, base_speed=8, centre_offset=[0, -2], x_flip=True), "attack_right": AnimSet(atlas_surface=self.atlas_image, start_pos=[0, 192], frame_size=[96, 96], num_frames=6, base_speed=8, centre_offset=[0, -2]), "attack_left": AnimSet(atlas_surface=self.atlas_image, start_pos=[0, 192], frame_size=[96, 96], num_frames=6, base_speed=8, centre_offset=[32, -2], x_flip=True) } self.active_anim = self.anim_sets["idle_left"] self.image = self.active_anim.current_frame self.rect = self.image.get_rect() self.start_position = start_position[:] self.world_position = [coord for coord in self.start_position] self.screen_position = [self.world_position[0], self.world_position[1]] game_types_to_collide_with = [CollisionType.WORLD_SOLID, CollisionType.WORLD_JUMP_THROUGH, CollisionType.WORLD_PLATFORM_EDGE, CollisionType.WORLD_JUMP_THROUGH_EDGE, CollisionType.PLAYER_ATTACKS, CollisionType.PLAYER_PROJECTILES] handlers_to_use = {CollisionType.WORLD_SOLID: collision_grid.rub_handler, CollisionType.WORLD_JUMP_THROUGH: collision_grid.rub_handler, CollisionType.WORLD_PLATFORM_EDGE: collision_grid.rub_handler, CollisionType.WORLD_JUMP_THROUGH_EDGE: collision_grid.rub_handler, CollisionType.PLAYER_ATTACKS: collision_grid.no_handler, CollisionType.PLAYER_PROJECTILES: collision_grid.no_handler} self.collision_shape_offset = [0, 0] self.collision_shape = CollisionRect(pygame.Rect(self.world_position[0], self.world_position[1], self.rect.width, self.rect.height), 0, handlers_to_use, CollisionType.AI, game_types_to_collide_with) self.collision_grid.add_new_shape_to_grid(self.collision_shape) self.collision_shape.owner = self self.base_health = 100 self.current_health = self.base_health self.rect.centerx = int(self.screen_position[0]) self.rect.centery = int(self.screen_position[1]) self.velocity = [0.0, 0.0] self.motion_state = "idle" self.frames_falling = 0 self.touching_ground = False self.move_speed = 0.0 self.x_facing_direction = "left" self.changed_direction_recently = False self.health_ui = UIEnemyHealthBar(self, self.ui_sprites_group) # debug draw self.drawable_collision_rectangle = DrawableCollisionRect(self.collision_shape) # view cone params if self.x_facing_direction == "right": facing_vec = [1.0, 0.0] else: facing_vec = [-1.0, 0.0] self.view_cone = ViewCone(self.world_position, facing_vec, fov=30.0, length=400.0) self.collision_grid.add_new_shape_to_grid(self.view_cone.collision_circle) self.visible_enemies = None self.closest_visible_enemy = None arrow_image_position = [576, 0] arrow_image_dimensions = [46, 7] arrow_image_rect = pygame.Rect(arrow_image_position, arrow_image_dimensions) self.arrow_image = self.atlas_image.subsurface(arrow_image_rect).convert_alpha() self.time_since_last_saw_enemy = 0 self.enemy_spotted_timeout = 3.0 self.is_time_to_fire = False self.fire_time = 1.0 self.fire_timer = 0.0 self.active_arrows = [] self.projectile_drawable_rects = [] self.has_moved = False self.disable_movement = False self.sprite_flash_acc = 0.0 self.sprite_flash_time = 0.25 self.should_flash_sprite = False self.active_flash_sprite = False self.last_impact_direction_vec = None self.direction_change_time = 1.0 self.direction_change_timer = 0.0 self.impact_velocity = None self.impact_time = 0.1 self.impact_timer = 0.0 self.last_frame_x_pos = self.world_position[0] self.last_collision_shape_x = self.collision_shape.x self.collision_print_time = 1.0 self.collision_print_timer = 0.0 # debuggers self.start_frame_collision_shape_x = self.collision_shape.x self.pre_collision_gets_moved_x = self.collision_shape.x self.post_collision_gets_moved_x = self.collision_shape.x self.alive = True def shutdown(self): if self.alive: self.kill() self.health_ui.kill() self.collision_grid.remove_shape_from_grid(self.collision_shape) self.collision_grid.remove_shape_from_grid(self.view_cone.collision_circle) self.drawable_collision_rectangle = None self.alive = False def draw_debug_info(self, screen, camera): if self.drawable_collision_rectangle is not None: self.drawable_collision_rectangle.update_collided_colours() self.drawable_collision_rectangle.draw(screen, camera.position, (camera.half_width, camera.half_height)) self.view_cone.draw(screen, camera) def update_screen_position(self, camera): view_top_left_position = (camera.position[0] - camera.half_width, camera.position[1] - camera.half_height) self.screen_position[0] = self.world_position[0] - view_top_left_position[0] self.screen_position[1] = self.world_position[1] - view_top_left_position[1] def update_animation(self, time_delta): # set the animation name based on motion and direction anim_name = self.motion_state + "_" anim_name += self.x_facing_direction # if this is a new animation, run the start function if self.active_anim != self.anim_sets[anim_name]: self.active_anim = self.anim_sets[anim_name] self.active_anim.start() # update the animation & set the sprite image to the current animation frame self.active_anim.update(time_delta) self.image = self.active_anim.current_frame def update_movement(self, time_delta, camera): self.has_moved = False if self.closest_visible_enemy is not None and self.touching_ground: self.velocity[0] = 0.0 on_left = (self.closest_visible_enemy.world_position[0] - self.world_position[0]) < 0.0 if on_left: self.motion_state = "attack" self.x_facing_direction = "left" else: self.motion_state = "attack" self.x_facing_direction = "right" if self.is_time_to_fire: self.is_time_to_fire = False self.fire_timer = 0.0 if self.x_facing_direction == "left": firing_direction = [-0.90, -0.1] else: firing_direction = [0.90, -0.1] arrow = ThrowingKnife(self.moving_sprites_group, self.collision_grid, self.arrow_image, self.world_position, firing_direction, self.projectile_drawable_rects, camera, is_player_weapon=False, speed=1500.0) self.active_arrows.append(arrow) else: self.fire_timer += time_delta if self.fire_timer >= self.fire_time: self.is_time_to_fire = True elif self.touching_ground: self.has_moved = True if self.x_facing_direction == "left": self.motion_state = "walk" self.velocity[0] = -100.0 elif self.x_facing_direction == "right": self.motion_state = "walk" self.velocity[0] = 100.0 else: self.motion_state = "idle" def in_range_and_in_front(self, sprite): x_dist = sprite.world_position[0] - self.world_position[0] y_dist = sprite.world_position[1] - self.world_position[1] distance_squared = x_dist ** 2 + y_dist ** 2 if distance_squared >= self.view_cone.length_squared: return False if self.x_facing_direction == "right": facing_vec = [1.0, 0.0] else: facing_vec = [-1.0, 0.0] dot = facing_vec[0] * x_dist + facing_vec[1] * y_dist if dot < 0: return False vector_to_point = [x_dist, y_dist] cross_1 = self.view_cone.cone_extent_facings[0][0] * vector_to_point[1] - self.view_cone.cone_extent_facings[0][1] * vector_to_point[0] cross_2 = self.view_cone.cone_extent_facings[1][0] * vector_to_point[1] - self.view_cone.cone_extent_facings[1][1] * vector_to_point[0] if cross_1 < 0 < cross_2 or cross_1 > 0 > cross_2: return True else: return False def update_visible_enemies(self, time_delta): if self.closest_visible_enemy is not None: self.time_since_last_saw_enemy += time_delta if self.time_since_last_saw_enemy > self.enemy_spotted_timeout: self.closest_visible_enemy = None enemies = [sprite for sprite in self.moving_sprites_group.sprites() if sprite.type == "player" and self.in_range_and_in_front(sprite)] if len(enemies) > 0 and self.has_moved: self.view_cone.set_position(pygame.math.Vector2(self.world_position)) if self.x_facing_direction == "right": facing_vec = [1.0, 0.0] else: facing_vec = [-1.0, 0.0] self.view_cone.set_facing_direction(pygame.math.Vector2(facing_vec)) self.view_cone.update() self.visible_enemies = [sprite for sprite in enemies if self.view_cone.is_subject_visible(pygame.math.Vector2(sprite.world_position)) and sprite.current_health > 0] if len(self.visible_enemies) > 0: self.closest_visible_enemy = self.find_closest(self.visible_enemies) self.time_since_last_saw_enemy = 0 else: self.view_cone.clear() def find_closest(self, sprites): closest_distance = 9999999999999999.0 closest_sprite = None for sprite in sprites: x_dist = self.world_position[0] - sprite.world_position[0] y_dist = self.world_position[1] - sprite.world_position[1] squared_dist = x_dist ** 2 + y_dist ** 2 if squared_dist < closest_distance: closest_distance = squared_dist closest_sprite = sprite return closest_sprite def update(self, time_delta, gravity, camera): floor_collided_this_frame = False impact_velocity = None hit_wall = False mtv_vector = None if len(self.collision_shape.collided_shapes_this_frame) > 0: for shape in self.collision_shape.collided_shapes_this_frame: if shape.game_type == CollisionType.PLAYER_PROJECTILES: if not self.should_flash_sprite: # invincible for flash time secs (0.5) after taking damage self.take_damage(15) # turn to face damage source self.last_impact_direction_vec = pygame.math.Vector2(shape.owner.velocity[0], shape.owner.velocity[1]) self.last_impact_direction_vec.normalize_ip() impact_velocity = self.apply_knockback(shape) if shape.game_type == CollisionType.PLAYER_ATTACKS: if not self.should_flash_sprite: # invincible for flash time secs (0.5) after taking damage self.take_damage(15) # turn to face damage source self.last_impact_direction_vec = pygame.math.Vector2(self.world_position[0] - shape.owner.world_position[0], self.world_position[1] - shape.owner.world_position[1]) if self.last_impact_direction_vec.length_squared() > 0: self.last_impact_direction_vec.normalize_ip() impact_velocity = self.apply_knockback(shape) if shape.game_type == CollisionType.WORLD_PLATFORM_EDGE or shape.game_type == CollisionType.WORLD_JUMP_THROUGH_EDGE: # slightly tricksy bit of code here to detect when are at a left or right platform edge # relies on the edges being tagged up using a special tile type and some poking at the normals # to figure out if it is a left or right platform edge. This method won't work on single block # platforms but that's probably OK, AI can just fall off those. if not self.changed_direction_recently and self.motion_state == "walk": if shape.y > self.collision_shape.aabb_rect.bottom: # test we are walking on top of this edge if self.x_facing_direction == "left": if self.collision_shape.x < shape.x and not shape.normals['left'].should_skip: self.collision_shape.set_position([shape.x, self.collision_shape.y]) self.changed_direction_recently = True self.x_facing_direction = "right" elif self.x_facing_direction == "right": if self.collision_shape.x > shape.x and not shape.normals['right'].should_skip: self.collision_shape.set_position([shape.x, self.collision_shape.y]) self.changed_direction_recently = True self.x_facing_direction = "left" if shape.game_type == CollisionType.WORLD_SOLID or \ shape.game_type == CollisionType.WORLD_PLATFORM_EDGE or \ shape.game_type == CollisionType.WORLD_JUMP_THROUGH or \ shape.game_type == CollisionType.WORLD_JUMP_THROUGH_EDGE: # see if we have an upwards facing mtv vector mtv_vector = self.collision_shape.get_frame_mtv_vector(shape) if mtv_vector is not None: if abs(mtv_vector[1]) > abs(mtv_vector[0]) and mtv_vector[1] < 0: floor_collided_this_frame = True self.velocity[1] = 0.0 self.frames_falling = 0 self.touching_ground = True # see if we have a sideways facing mtv vector if abs(mtv_vector[0]) > abs(mtv_vector[1]): hit_wall = True self.velocity[0] = 0.0 # makes AI change direction if they hit a wall. if not self.changed_direction_recently: self.changed_direction_recently = True if self.x_facing_direction == "left" and mtv_vector[0] > 0: self.x_facing_direction = "right" elif self.x_facing_direction == "right" and mtv_vector[0] < 0: self.x_facing_direction = "left" #if not self.touching_ground: # print("mid air collision") # self.velocity[0] = 0.0 # self.velocity[1] = 0.0 # self.collision_shape.x = self.world_position[0] # self.collision_shape.y = self.world_position[1] if not floor_collided_this_frame: if self.touching_ground and self.frames_falling > 10: self.frames_falling = 0 self.touching_ground = False self.frames_falling += 1 if len(self.collision_shape.collided_shapes_this_frame) > 0: self.world_position[0] = self.collision_shape.x - self.collision_shape_offset[0] self.world_position[1] = self.collision_shape.y - self.collision_shape_offset[1] if not floor_collided_this_frame: self.velocity[1] += gravity * time_delta self.update_impact_reactions(hit_wall, impact_velocity, time_delta) velocity_delta_vec = pygame.math.Vector2(self.velocity[0] * time_delta, self.velocity[1] * time_delta) if velocity_delta_vec.length() < 32.0: self.world_position[0] += self.velocity[0] * time_delta self.world_position[1] += self.velocity[1] * time_delta else: print("clamping velocity") velocity_delta_vec = velocity_delta_vec.normalize_ip() * 32.0 self.velocity[0] = velocity_delta_vec.x / time_delta self.velocity[1] = velocity_delta_vec.y / time_delta if not self.disable_movement: self.update_movement(time_delta, camera) self.update_animation(time_delta) self.update_sprite(time_delta) self.update_visible_enemies(time_delta) if self.changed_direction_recently: if self.direction_change_timer < self.direction_change_time: self.direction_change_timer += time_delta else: self.changed_direction_recently = False self.direction_change_timer = 0.0 # set the position of the collision shape self.collision_shape.set_position([self.world_position[0] + self.collision_shape_offset[0], self.world_position[1] + self.collision_shape_offset[1]]) self.update_screen_position(camera) self.rect.centerx = int(self.screen_position[0] - self.active_anim.centre_offset[0]) self.rect.centery = int(self.screen_position[1] - self.active_anim.centre_offset[1]) if self.current_health <= 0: self.shutdown() def update_impact_reactions(self, hit_wall, impact_velocity, time_delta): if impact_velocity is not None: self.touching_ground = False # self.disable_movement = True self.velocity[0] = 0.0 self.velocity[1] = 0.0 self.impact_velocity = impact_velocity self.impact_timer = 0.0 if not hit_wall and not self.touching_ground and self.impact_velocity is not None: self.velocity[0] += self.impact_velocity.x * time_delta self.velocity[1] += self.impact_velocity.y * time_delta if self.impact_timer < self.impact_time: self.impact_timer += time_delta else: self.impact_velocity[0] *= 0.8 self.impact_velocity[1] *= 0.8 if self.touching_ground and self.last_impact_direction_vec is not None: if not self.changed_direction_recently: self.turn_to_face_direction(self.last_impact_direction_vec) self.last_impact_direction_vec = None # self.disable_movement = False self.impact_velocity = None def debug_knockback(self, post_collision_move_x, pre_collision_move_x): if abs(self.world_position[0] - self.last_frame_x_pos) > 4.0: print("LARGE x MOVE:", abs(self.world_position[0] - self.last_frame_x_pos)) print("self.velocity:", self.velocity) print("collision shape x move:", abs(self.collision_shape.x - self.last_collision_shape_x)) print("pre_collision_move_world_pos_x:", pre_collision_move_x) print("post_collision_move_world_pos_x", post_collision_move_x) # print("mtv_vector:", mtv_vector) for shape in self.collision_shape.collided_shapes_this_frame: print("shape:", shape.x, shape.y) print("\n") print("self.start_frame_collision_shape_x", self.start_frame_collision_shape_x) print("self.pre_collision_gets_moved_x", self.pre_collision_gets_moved_x) print("self.post_collision_gets_moved_x", self.post_collision_gets_moved_x) def apply_knockback(self, shape): impact_velocity = pygame.math.Vector2(self.last_impact_direction_vec.x * 500.0, (self.last_impact_direction_vec.y * 300.0) - 1200.0) return impact_velocity def turn_to_face_direction(self, direction): if self.x_facing_direction == "right": facing_vec = [1.0, 0.0] else: facing_vec = [-1.0, 0.0] dot = facing_vec[0] * direction.x + facing_vec[1] * direction.y if dot > 0 and self.x_facing_direction == "right": self.x_facing_direction = "left" self.has_moved = True elif dot > 0 and self.x_facing_direction == "left": self.x_facing_direction = "right" self.has_moved = True def update_sprite(self, time_delta): if self.should_flash_sprite and not self.current_health <= 0: self.sprite_flash_acc += time_delta if self.sprite_flash_acc > self.sprite_flash_time: self.sprite_flash_acc = 0.0 self.should_flash_sprite = False else: lerp_value = self.sprite_flash_acc / self.sprite_flash_time flash_alpha = self.lerp(255, 0, lerp_value) flash_image = self.active_anim.current_frame.copy() flash_sprite = self.active_anim.current_frame.copy() flash_image.fill((255, 255, 255), None, pygame.BLEND_RGB_ADD) flash_image.fill((255, 255, 255, flash_alpha), None, pygame.BLEND_RGBA_MULT) flash_sprite.blit(flash_image, (0, 0)) self.image = flash_sprite if not self.active_flash_sprite: self.active_flash_sprite = True else: self.image = self.active_anim.current_frame def take_damage(self, damage): self.current_health -= damage if self.current_health < 0: self.current_health = 0 self.should_flash_sprite = True self.sprite_flash_acc = 0.0 @staticmethod def lerp(a, b, c): return (c * b) + ((1.0 - c) * a)
class Missile(Projectile): def __init__(self, start_pos, initial_heading_vector, current_target, target_position, damage, speed, explosions_sprite_sheet, image_atlas, homing_radius, collision_grid, *groups): super().__init__(*groups) self.explosions_sprite_sheet = explosions_sprite_sheet self.original_image = image_atlas.subsurface((0, 256, 6, 12)) self.current_vector = [ initial_heading_vector[0], initial_heading_vector[1] ] self.target_vector = self.current_vector self.position = [float(start_pos[0]), float(start_pos[1])] facing_angle = math.atan2(-self.current_vector[0], -self.current_vector[1]) * 180 / math.pi self.image = pygame.transform.rotate(self.original_image, facing_angle) self.rect = self.image.get_rect() self.rect.center = start_pos self.collision_grid = collision_grid handlers_by_type = { GameCollisionType.MONSTER: self.collision_grid.no_handler } self.collision_shape = CollisionRect( self.original_image.get_rect(), math.atan2(-self.current_vector[0], -self.current_vector[1]), handlers_by_type, GameCollisionType.TURRET_PROJECTILE, [GameCollisionType.MONSTER]) self.collision_shape.set_owner(self) self.collision_grid.add_new_shape_to_grid(self.collision_shape) self.drawable_collision_rect = DrawableCollisionRect( self.collision_shape) self.should_die = False self.projectile_speed = speed self.damage = damage self.target_position = target_position x_diff = self.target_position[0] - self.position[0] y_diff = self.target_position[1] - self.position[1] total_target_dist = math.sqrt(x_diff**2 + y_diff**2) self.target_distance = total_target_dist + 16.0 self.shot_range = 600.0 self.rotate_speed = 10.0 self.time_to_home_in = False self.homing_time_acc = 0.0 self.homing_time = 0.2 self.homing_radius = homing_radius self.current_target = current_target def update_sprite(self, all_bullet_sprites): all_bullet_sprites.add(self) return all_bullet_sprites def draw_collision_rect(self, screen): self.drawable_collision_rect.update_collided_colours() self.drawable_collision_rect.draw(screen) def react_to_collision(self): for shape in self.collision_shape.collided_shapes_this_frame: if shape.game_type == GameCollisionType.MONSTER: self.should_die = True def update_movement_and_collision(self, monsters, time_delta, new_explosions, explosions): if self.homing_time_acc < self.homing_time: self.homing_time_acc += time_delta else: self.time_to_home_in = True if self.time_to_home_in: self.homing_time_acc = 0.0 self.time_to_home_in = False if self.current_target is None or self.current_target.should_die: self.current_target, self.target_distance = self.get_closest_monster_in_radius( monsters) if self.current_target is not None: self.target_distance = self.calc_distance_to_target( self.current_target) self.target_vector = self.calculate_aiming_vector( self.current_target, self.target_distance) relative_angle = self.rotate_current_angle_to_target(time_delta) self.shot_range -= time_delta * self.projectile_speed self.position[0] += (self.current_vector[0] * time_delta * self.projectile_speed) self.position[1] += (self.current_vector[1] * time_delta * self.projectile_speed) self.rect.center = (int(self.position[0]), int(self.position[1])) self.collision_shape.set_position(self.position) if self.shot_range <= 0.0: self.should_die = True if relative_angle != 0.0: direction_magnitude = math.sqrt(self.current_vector[0]**2 + self.current_vector[1]**2) unit_dir_vector = [0, 0] if direction_magnitude > 0.0: unit_dir_vector = [ self.current_vector[0] / direction_magnitude, self.current_vector[1] / direction_magnitude ] facing_angle = math.atan2(-unit_dir_vector[0], -unit_dir_vector[1]) * 180 / math.pi bullet_centre_position = self.rect.center self.image = pygame.transform.rotate(self.original_image, facing_angle) self.rect = self.image.get_rect() self.rect.center = bullet_centre_position self.collision_shape.set_rotation( math.atan2(-unit_dir_vector[0], -unit_dir_vector[1])) if self.should_die: self.collision_grid.remove_shape_from_grid(self.collision_shape) # explode at front of missile explosion_pos = [0.0, 0.0] explosion_pos[0] = self.position[0] + (self.current_vector[0] * 7.0) explosion_pos[1] = self.position[1] + (self.current_vector[1] * 7.0) new_explosion = Explosion(self.position, self.explosions_sprite_sheet, 12, self.damage, DamageType.MISSILE, self.collision_grid) new_explosions.append(new_explosion) explosions.append(new_explosion) def calc_distance_to_target(self, target): x_dist = self.position[0] - target.position[0] y_dist = self.position[1] - target.position[1] current_dist = math.sqrt((x_dist**2) + (y_dist**2)) # re-adjust distance to our anticipated position when projectiles reach target time_to_reach_target = current_dist / self.projectile_speed guess_position = target.guess_position_at_time(time_to_reach_target) x_dist = guess_position[0] - self.position[0] y_dist = guess_position[1] - self.position[1] guess_dist = math.sqrt((x_dist**2) + (y_dist**2)) return guess_dist def get_closest_monster_in_radius(self, monsters): closest_monster_distance = 100000.0 closest_monster_in_radius = None for monster in monsters: guess_dist = self.calc_distance_to_target(monster) if guess_dist < self.homing_radius: if guess_dist < closest_monster_distance: closest_monster_distance = guess_dist closest_monster_in_radius = monster return closest_monster_in_radius, closest_monster_distance def calculate_aiming_vector(self, target, distance): time_to_reach_target = distance / self.projectile_speed guess_position = target.guess_position_at_time(time_to_reach_target) x_direction = guess_position[0] - self.position[0] y_direction = guess_position[1] - self.position[1] distance = math.sqrt((x_direction**2) + (y_direction**2)) return [x_direction / distance, y_direction / distance] def rotate_current_angle_to_target(self, time_delta): relative_angle = 0.0 if self.target_vector[0] != self.current_vector[ 0] or self.target_vector[0] != self.current_vector[0]: current_angle = math.atan2(self.current_vector[1], self.current_vector[0]) target_angle = math.atan2(self.target_vector[1], self.target_vector[0]) relative_angle = target_angle - current_angle if abs(relative_angle) < 0.01: self.current_vector[0] = self.target_vector[0] self.current_vector[1] = self.target_vector[1] else: if relative_angle > math.pi: relative_angle = relative_angle - (2 * math.pi) if relative_angle < -math.pi: relative_angle = relative_angle + (2 * math.pi) if relative_angle > 0: current_angle += (time_delta * self.rotate_speed) else: current_angle -= (time_delta * self.rotate_speed) self.current_vector[0] = math.cos(current_angle) self.current_vector[1] = math.sin(current_angle) return relative_angle
def update_jumping(self, time_delta, camera): if self.motion_state == "jump_attack": if self.active_anim.frame_index == 3 and self.melee_collision_shape is None: attack_position = [ self.world_position[0], self.world_position[1] ] if self.x_facing_direction == "left": attack_position[0] -= 64 attack_dimensions = [64, 16] game_types_to_collide_with = [CollisionType.AI] handlers_to_use = { CollisionType.AI: self.collision_grid.no_handler } self.melee_collision_shape = CollisionRect( pygame.Rect(attack_position[0], attack_position[1], attack_dimensions[0], attack_dimensions[1]), 0, handlers_to_use, CollisionType.PLAYER_ATTACKS, game_types_to_collide_with) self.collision_grid.add_new_shape_to_grid( self.melee_collision_shape) self.melee_collision_shape.owner = self self.melee_attack_collision_drawable_rectangle = DrawableCollisionRect( self.melee_collision_shape) elif self.active_anim.frame_index > 3 and self.melee_collision_shape is not None: self.collision_grid.remove_shape_from_grid( self.melee_collision_shape) self.melee_collision_shape = None if not self.active_anim.running: self.motion_state = "jump" elif self.motion_state == "jump_throw": if self.active_anim.frame_index == 3 and not self.thrown_knife: self.thrown_knife = True if self.closest_visible_enemy is not None: x_dir = self.closest_visible_enemy.world_position[ 0] - self.world_position[0] y_dir = self.closest_visible_enemy.world_position[ 1] - self.world_position[1] distance = math.sqrt(x_dir**2 + y_dir**2) throwing_direction = [x_dir / distance, y_dir / distance] # knives travel at roughly 1000.0 pixels a second, gravity is 800 pixels down per second # so we need to correct the direction vector based on how far away the target is. # after 1000 distance we will have descended roughly 800 from our target position # calculated by assuming traveling in a straight line to the right to a point 1000.0 away gravity_offset_vec = [0.0, -0.625 * distance / 1000.0] throwing_direction = [ throwing_direction[0] + gravity_offset_vec[0], throwing_direction[1] + gravity_offset_vec[1] ] # renormalise direction throwing_dir_len = math.sqrt(throwing_direction[0]**2 + throwing_direction[1]**2) throwing_direction = [ throwing_direction[0] / throwing_dir_len, throwing_direction[1] / throwing_dir_len ] else: if self.x_facing_direction == 'left': throwing_direction = [ -0.98, -0.19 + random.normalvariate(0, 0.02) ] else: throwing_direction = [ 0.98, -0.19 + random.normalvariate(0, 0.02) ] knife = ThrowingKnife(self.moving_sprites_group, self.collision_grid, self.knife_image, self.world_position, throwing_direction, self.projectile_drawable_rects, camera) self.active_knives.append(knife) if not self.active_anim.running: self.thrown_knife = False self.motion_state = "jump" else: self.motion_state = "jump" if self.move_left: self.move_speed -= self.air_acceleration * time_delta if self.move_speed < -self.air_top_speed: self.move_speed = -self.air_top_speed self.velocity[0] = self.move_speed self.x_facing_direction = "left" speed_factor = abs(self.move_speed / self.air_top_speed) self.active_anim.set_speed_factor(speed_factor) elif self.move_right: self.move_speed += self.air_acceleration * time_delta if self.move_speed > self.air_top_speed: self.move_speed = self.air_top_speed self.velocity[0] = self.move_speed self.x_facing_direction = "right" speed_factor = abs(self.move_speed / self.air_top_speed) self.active_anim.set_speed_factor(speed_factor)
def __init__(self, moving_sprites_group, ui_sprites_group, fonts, collision_grid, camera): super().__init__(moving_sprites_group) self.type = "player" self.moving_sprites_group = moving_sprites_group self.ui_sprites_group = ui_sprites_group self.fonts = fonts self.collision_grid = collision_grid self.camera = camera self.atlas_image = pygame.image.load("images/player.png") self.anim_sets = { "idle_right": AnimSet(self.atlas_image, [0, 96], [64, 96], 10, 10, [0, -1]), "idle_left": AnimSet(self.atlas_image, [0, 96], [64, 96], 10, 10, [0, -1], looping=True, x_flip=True), "run_right": AnimSet(self.atlas_image, [0, 0], [64, 96], 10, 30, [0, -1]), "run_left": AnimSet(self.atlas_image, [0, 0], [64, 96], 10, 30, [0, -1], looping=True, x_flip=True), "jump_right": AnimSet(self.atlas_image, [0, 192], [64, 96], 10, 8, [0, -1], looping=False, x_flip=False), "jump_left": AnimSet(self.atlas_image, [0, 192], [64, 96], 10, 8, [0, -1], looping=False, x_flip=True), "jump_attack_right": AnimSet(self.atlas_image, [0, 770], [96, 96], 10, 40, [0, -1], looping=False, x_flip=False), "jump_attack_left": AnimSet(self.atlas_image, [0, 770], [96, 96], 10, 40, [32, -1], looping=False, x_flip=True), "jump_throw_right": AnimSet(self.atlas_image, [0, 866], [96, 96], 10, 40, [0, -1], looping=False, x_flip=False), "jump_throw_left": AnimSet(self.atlas_image, [0, 866], [96, 96], 10, 40, [32, -1], looping=False, x_flip=True), "throw_right": AnimSet(self.atlas_image, [0, 288], [64, 96], 10, 40, [0, -1], False, False), "throw_left": AnimSet(self.atlas_image, [0, 288], [64, 96], 10, 40, [0, -1], False, True), "attack_right": AnimSet(self.atlas_image, [0, 384], [96, 96], 10, 60, [0, -1], False, False), "attack_left": AnimSet(self.atlas_image, [0, 384], [96, 96], 10, 60, [32, -1], False, True), "die_right": AnimSet(self.atlas_image, [0, 480], [96, 96], 10, 20, [0, -1], False, False), "die_left": AnimSet(self.atlas_image, [0, 480], [96, 96], 10, 20, [32, -1], False, True), "slide_right": AnimSet(self.atlas_image, [0, 576], [96, 96], 10, 15, [0, -1], False, False), "slide_left": AnimSet(self.atlas_image, [0, 576], [96, 96], 10, 15, [32, -1], False, True), "climb_up": AnimSet(self.atlas_image, [0, 672], [64, 96], 10, 30, [0, -1]), "climb_down": AnimSet(self.atlas_image, [0, 672], [64, 96], 10, 30, [0, -1], True, True) } # load the knife image knife_image_position = [0, 960] knife_image_dimensions = [30, 6] knife_image_rect = pygame.Rect(knife_image_position, knife_image_dimensions) self.knife_image = self.atlas_image.subsurface( knife_image_rect).convert_alpha() self.active_anim = self.anim_sets["idle_right"] self.image = self.active_anim.current_frame self.rect = self.image.get_rect() self.start_position = (316.0, 596.0) self.world_position = [coord for coord in self.start_position] self.screen_position = [self.world_position[0], self.world_position[1]] game_types_to_collide_with = [ CollisionType.WORLD_SOLID, CollisionType.WORLD_PLATFORM_EDGE, CollisionType.WORLD_JUMP_THROUGH, CollisionType.WORLD_JUMP_THROUGH_EDGE, CollisionType.AI_ATTACKS, CollisionType.AI_PROJECTILES ] handlers_to_use = { CollisionType.WORLD_SOLID: collision_grid.rub_handler, CollisionType.WORLD_PLATFORM_EDGE: collision_grid.rub_handler, CollisionType.WORLD_JUMP_THROUGH: collision_grid.no_handler, CollisionType.WORLD_JUMP_THROUGH_EDGE: collision_grid.no_handler, CollisionType.AI_ATTACKS: collision_grid.no_handler, CollisionType.AI_PROJECTILES: collision_grid.no_handler } self.collision_shape_offset = [0, 0] self.collision_shape = CollisionRect( pygame.Rect(self.world_position[0], self.world_position[1], self.rect.width, self.rect.height), 0, handlers_to_use, CollisionType.PLAYER, game_types_to_collide_with) self.collision_grid.add_new_shape_to_grid(self.collision_shape) self.collision_shape.owner = self self.ladder_collider_offset = [0.0, 0.0] self.triggers_collision_shape = CollisionRect( pygame.Rect(self.world_position[0], self.world_position[1], self.rect.width / 3, self.rect.height), 0, { CollisionType.LADDERS: collision_grid.no_handler, CollisionType.DOOR: collision_grid.no_handler, CollisionType.WATER: collision_grid.no_handler }, CollisionType.PLAYER_LADDER, [CollisionType.LADDERS, CollisionType.DOOR, CollisionType.WATER]) self.collision_grid.add_new_shape_to_grid( self.triggers_collision_shape) self.velocity = [0.0, 0.0] self.move_speed = 0.0 # player speed values self.ground_top_speed = 500.0 self.ground_acceleration = 1000.0 self.slide_top_speed = 700.0 self.slide_acceleration = 1500.0 self.attack_top_speed = 300.0 self.attack_acceleration = 3000.0 self.air_top_speed = 250.0 self.air_acceleration = 500.0 self.attack_slide_top_speed = 1400.0 self.attack_slide_acceleration = 3000.0 self.speed_multiplier = 1.0 self.climb_acceleration = 600.0 self.climb_top_speed = 300.0 self.climb_speed = 0.0 self.jump_height = -600.0 self.base_health = 100 self.current_health = self.base_health self.action_to_start = "" self.motion_state = "idle" self.x_facing_direction = "right" self.y_facing_direction = "up" # directions self.move_left = False self.move_right = False self.climb_up = False self.climb_down = False # world position information self.in_climb_position = True self.touching_ground = False # weapons self.thrown_knife = False self.active_knives = [] # collision shape drawing self.projectile_drawable_rects = [] self.player_drawable_rectangle = DrawableCollisionRect( self.collision_shape) self.player_ladder_drawable_rectangle = DrawableCollisionRect( self.triggers_collision_shape) self.melee_attack_collision_drawable_rectangle = None self.frames_falling = 0 self.found_ladder_position = [0.0, 0.0] self.rect.centerx = int(self.screen_position[0]) self.rect.centery = int(self.screen_position[1]) self.respawning = False self.respawn_time = 5.0 self.respawn_timer = self.respawn_time self.should_respawn = False if self.x_facing_direction == "right": facing_vec = [1.0, 0.0] else: facing_vec = [-1.0, 0.0] self.view_cone = ViewCone(self.world_position, facing_vec, fov=145.0, length=450.0) self.collision_grid.add_new_shape_to_grid( self.view_cone.collision_circle) self.visible_enemies = None self.closest_visible_enemy = None # melee attack self.melee_collision_shape = None # handling level exit doors self.in_exit_door_position = False self.has_exited_level = False self.exit_door_hint = ExitDoorHint(self.fonts, self)
class Player(pygame.sprite.Sprite): def __init__(self, moving_sprites_group, ui_sprites_group, fonts, collision_grid, camera): super().__init__(moving_sprites_group) self.type = "player" self.moving_sprites_group = moving_sprites_group self.ui_sprites_group = ui_sprites_group self.fonts = fonts self.collision_grid = collision_grid self.camera = camera self.atlas_image = pygame.image.load("images/player.png") self.anim_sets = { "idle_right": AnimSet(self.atlas_image, [0, 96], [64, 96], 10, 10, [0, -1]), "idle_left": AnimSet(self.atlas_image, [0, 96], [64, 96], 10, 10, [0, -1], looping=True, x_flip=True), "run_right": AnimSet(self.atlas_image, [0, 0], [64, 96], 10, 30, [0, -1]), "run_left": AnimSet(self.atlas_image, [0, 0], [64, 96], 10, 30, [0, -1], looping=True, x_flip=True), "jump_right": AnimSet(self.atlas_image, [0, 192], [64, 96], 10, 8, [0, -1], looping=False, x_flip=False), "jump_left": AnimSet(self.atlas_image, [0, 192], [64, 96], 10, 8, [0, -1], looping=False, x_flip=True), "jump_attack_right": AnimSet(self.atlas_image, [0, 770], [96, 96], 10, 40, [0, -1], looping=False, x_flip=False), "jump_attack_left": AnimSet(self.atlas_image, [0, 770], [96, 96], 10, 40, [32, -1], looping=False, x_flip=True), "jump_throw_right": AnimSet(self.atlas_image, [0, 866], [96, 96], 10, 40, [0, -1], looping=False, x_flip=False), "jump_throw_left": AnimSet(self.atlas_image, [0, 866], [96, 96], 10, 40, [32, -1], looping=False, x_flip=True), "throw_right": AnimSet(self.atlas_image, [0, 288], [64, 96], 10, 40, [0, -1], False, False), "throw_left": AnimSet(self.atlas_image, [0, 288], [64, 96], 10, 40, [0, -1], False, True), "attack_right": AnimSet(self.atlas_image, [0, 384], [96, 96], 10, 60, [0, -1], False, False), "attack_left": AnimSet(self.atlas_image, [0, 384], [96, 96], 10, 60, [32, -1], False, True), "die_right": AnimSet(self.atlas_image, [0, 480], [96, 96], 10, 20, [0, -1], False, False), "die_left": AnimSet(self.atlas_image, [0, 480], [96, 96], 10, 20, [32, -1], False, True), "slide_right": AnimSet(self.atlas_image, [0, 576], [96, 96], 10, 15, [0, -1], False, False), "slide_left": AnimSet(self.atlas_image, [0, 576], [96, 96], 10, 15, [32, -1], False, True), "climb_up": AnimSet(self.atlas_image, [0, 672], [64, 96], 10, 30, [0, -1]), "climb_down": AnimSet(self.atlas_image, [0, 672], [64, 96], 10, 30, [0, -1], True, True) } # load the knife image knife_image_position = [0, 960] knife_image_dimensions = [30, 6] knife_image_rect = pygame.Rect(knife_image_position, knife_image_dimensions) self.knife_image = self.atlas_image.subsurface( knife_image_rect).convert_alpha() self.active_anim = self.anim_sets["idle_right"] self.image = self.active_anim.current_frame self.rect = self.image.get_rect() self.start_position = (316.0, 596.0) self.world_position = [coord for coord in self.start_position] self.screen_position = [self.world_position[0], self.world_position[1]] game_types_to_collide_with = [ CollisionType.WORLD_SOLID, CollisionType.WORLD_PLATFORM_EDGE, CollisionType.WORLD_JUMP_THROUGH, CollisionType.WORLD_JUMP_THROUGH_EDGE, CollisionType.AI_ATTACKS, CollisionType.AI_PROJECTILES ] handlers_to_use = { CollisionType.WORLD_SOLID: collision_grid.rub_handler, CollisionType.WORLD_PLATFORM_EDGE: collision_grid.rub_handler, CollisionType.WORLD_JUMP_THROUGH: collision_grid.no_handler, CollisionType.WORLD_JUMP_THROUGH_EDGE: collision_grid.no_handler, CollisionType.AI_ATTACKS: collision_grid.no_handler, CollisionType.AI_PROJECTILES: collision_grid.no_handler } self.collision_shape_offset = [0, 0] self.collision_shape = CollisionRect( pygame.Rect(self.world_position[0], self.world_position[1], self.rect.width, self.rect.height), 0, handlers_to_use, CollisionType.PLAYER, game_types_to_collide_with) self.collision_grid.add_new_shape_to_grid(self.collision_shape) self.collision_shape.owner = self self.ladder_collider_offset = [0.0, 0.0] self.triggers_collision_shape = CollisionRect( pygame.Rect(self.world_position[0], self.world_position[1], self.rect.width / 3, self.rect.height), 0, { CollisionType.LADDERS: collision_grid.no_handler, CollisionType.DOOR: collision_grid.no_handler, CollisionType.WATER: collision_grid.no_handler }, CollisionType.PLAYER_LADDER, [CollisionType.LADDERS, CollisionType.DOOR, CollisionType.WATER]) self.collision_grid.add_new_shape_to_grid( self.triggers_collision_shape) self.velocity = [0.0, 0.0] self.move_speed = 0.0 # player speed values self.ground_top_speed = 500.0 self.ground_acceleration = 1000.0 self.slide_top_speed = 700.0 self.slide_acceleration = 1500.0 self.attack_top_speed = 300.0 self.attack_acceleration = 3000.0 self.air_top_speed = 250.0 self.air_acceleration = 500.0 self.attack_slide_top_speed = 1400.0 self.attack_slide_acceleration = 3000.0 self.speed_multiplier = 1.0 self.climb_acceleration = 600.0 self.climb_top_speed = 300.0 self.climb_speed = 0.0 self.jump_height = -600.0 self.base_health = 100 self.current_health = self.base_health self.action_to_start = "" self.motion_state = "idle" self.x_facing_direction = "right" self.y_facing_direction = "up" # directions self.move_left = False self.move_right = False self.climb_up = False self.climb_down = False # world position information self.in_climb_position = True self.touching_ground = False # weapons self.thrown_knife = False self.active_knives = [] # collision shape drawing self.projectile_drawable_rects = [] self.player_drawable_rectangle = DrawableCollisionRect( self.collision_shape) self.player_ladder_drawable_rectangle = DrawableCollisionRect( self.triggers_collision_shape) self.melee_attack_collision_drawable_rectangle = None self.frames_falling = 0 self.found_ladder_position = [0.0, 0.0] self.rect.centerx = int(self.screen_position[0]) self.rect.centery = int(self.screen_position[1]) self.respawning = False self.respawn_time = 5.0 self.respawn_timer = self.respawn_time self.should_respawn = False if self.x_facing_direction == "right": facing_vec = [1.0, 0.0] else: facing_vec = [-1.0, 0.0] self.view_cone = ViewCone(self.world_position, facing_vec, fov=145.0, length=450.0) self.collision_grid.add_new_shape_to_grid( self.view_cone.collision_circle) self.visible_enemies = None self.closest_visible_enemy = None # melee attack self.melee_collision_shape = None # handling level exit doors self.in_exit_door_position = False self.has_exited_level = False self.exit_door_hint = ExitDoorHint(self.fonts, self) def respawn(self): self.motion_state = "idle" self.current_health = self.base_health self.should_respawn = False self.world_position = [coord for coord in self.start_position] self.collision_shape.set_position([ self.world_position[0] + self.collision_shape_offset[0], self.world_position[1] + self.collision_shape_offset[1] ]) self.collision_shape.set_position([ self.world_position[0] + self.collision_shape_offset[0], self.world_position[1] + self.collision_shape_offset[1] ]) self.triggers_collision_shape.set_position([ self.world_position[0] + self.ladder_collider_offset[0], self.world_position[1] + self.ladder_collider_offset[1] ]) def process_events(self, event): if event.type == KEYDOWN: if event.key == K_LEFT: self.move_left = True if event.key == K_RIGHT: self.move_right = True if event.key == K_UP: if self.in_climb_position: self.climb_up = True elif self.in_exit_door_position: self.has_exited_level = True else: self.camera.process_input(event) if event.key == K_DOWN: if self.in_climb_position: self.climb_down = True else: self.camera.process_input(event) if event.key == K_SPACE: if self.motion_state == "idle" or self.motion_state == "run" and self.touching_ground: self.action_to_start = "jump" if event.key == K_LCTRL or event.key == K_RCTRL: if self.motion_state == "idle" or self.motion_state == "run" and self.touching_ground: self.action_to_start = "throw" elif not self.touching_ground: self.action_to_start = "jump_throw" if event.key == K_z or event.key == K_LSHIFT or event.key == K_RSHIFT: if self.motion_state == "idle" or self.motion_state == "run" and self.touching_ground: self.action_to_start = "attack" elif not self.touching_ground: self.action_to_start = "jump_attack" if event.key == K_x: if self.motion_state == "idle" or self.motion_state == "run" and self.touching_ground: self.action_to_start = "slide" if event.key == K_ESCAPE: self.should_respawn = True if event.type == KEYUP: if event.key == K_LEFT: self.move_left = False if event.key == K_RIGHT: self.move_right = False if event.key == K_UP: if self.in_climb_position: self.climb_up = False else: self.camera.process_input(event) if event.key == K_DOWN: if self.in_climb_position: self.climb_down = False else: self.camera.process_input(event) def lose_health(self, health): self.current_health -= health if self.current_health < 0: self.current_health = 0 def update(self, time_delta, gravity, camera): # react to collision stuff self.in_climb_position = False self.speed_multiplier = 1.0 floor_collided_this_frame = False in_exit_door_position = False if len(self.triggers_collision_shape.collided_shapes_this_frame) > 0: for shape in self.triggers_collision_shape.collided_shapes_this_frame: if shape.game_type == CollisionType.WATER: self.speed_multiplier = 0.5 elif shape.game_type == CollisionType.LADDERS: self.in_climb_position = True self.found_ladder_position = [shape.x, shape.y] elif shape.game_type == CollisionType.DOOR: in_exit_door_position = True if not self.in_exit_door_position: self.in_exit_door_position = True self.add_exit_door_hint() if len(self.collision_shape.collided_shapes_this_frame) > 0: for shape in self.collision_shape.collided_shapes_this_frame: if shape.game_type == CollisionType.AI_PROJECTILES: self.lose_health(10) if shape.game_type == CollisionType.WORLD_JUMP_THROUGH or\ shape.game_type == CollisionType.WORLD_JUMP_THROUGH_EDGE: # moderately complicated handling for platforms that you can jump and climb through # essentially they only act like platforms if you fall down on top of them # I treat them like collision shapes with no handling and then only apply the mtv vector # if a bunch of conditions are met (falling, not climbing or starting a jump & above the platform) mtv_vector = self.collision_shape.get_frame_mtv_vector( shape) if self.climb_down: self.touching_ground = False else: if mtv_vector is not None and not ( self.action_to_start == "jump") and self.motion_state != "climb": if abs(mtv_vector[1]) > abs( mtv_vector[0] ) and mtv_vector[1] < 0 and self.velocity[1] > 0.0: if abs(mtv_vector[1]) > 0.5: self.collision_shape.set_position([ self.collision_shape.x + mtv_vector[0], self.collision_shape.y + mtv_vector[1] ]) floor_collided_this_frame = True self.velocity[1] = 0.0 self.frames_falling = 0 self.touching_ground = True if self.motion_state == "jump" or self.motion_state == "jump_throw": self.thrown_knife = False elif mtv_vector is None and self.motion_state != "climb": if self.touching_ground: floor_collided_this_frame = True if shape.game_type == CollisionType.WORLD_SOLID or \ shape.game_type == CollisionType.WORLD_PLATFORM_EDGE: # see if we have an upwards facing mtv vector mtv_vector = self.collision_shape.get_frame_mtv_vector( shape) if mtv_vector is not None and not (self.action_to_start == "jump"): if abs(mtv_vector[1]) > abs( mtv_vector[0]) and mtv_vector[1] < 0: floor_collided_this_frame = True self.velocity[1] = 0.0 self.frames_falling = 0 self.touching_ground = True if self.motion_state == "jump" or self.motion_state == "jump_throw": self.thrown_knife = False # see if we have a sideways facing mtv vector if abs(mtv_vector[0]) - abs(mtv_vector[1]) > 0.1: self.move_speed = 0.0 self.motion_state = "idle" elif mtv_vector is None: if self.touching_ground: floor_collided_this_frame = True if not in_exit_door_position: if self.in_exit_door_position: self.in_exit_door_position = False self.remove_exit_door_hint() if not floor_collided_this_frame: if self.touching_ground and self.frames_falling > 10: self.frames_falling = 0 self.touching_ground = False self.frames_falling += 1 if len(self.collision_shape.collided_shapes_this_frame) > 0: self.world_position[ 0] = self.collision_shape.x - self.collision_shape_offset[0] self.world_position[ 1] = self.collision_shape.y - self.collision_shape_offset[1] self.update_climbing(time_delta) if self.motion_state != "climb": if self.touching_ground or self.motion_state == "die": if self.motion_state == "throw": self.update_throwing(camera) elif self.motion_state == "attack": self.update_melee_attacking(time_delta) elif self.motion_state == "slide": self.update_sliding(time_delta) elif self.motion_state == "die": self.update_dying() else: self.update_running(time_delta) else: self.update_jumping(time_delta, camera) self.velocity[ 0] = self.move_speed * self.speed_multiplier # A good place to add a move speed multiplier # if attack is interrupted make sure we kill attack shape if not (self.motion_state == "attack" or self.motion_state == "jump_attack"): if self.melee_collision_shape is not None: self.collision_grid.remove_shape_from_grid( self.melee_collision_shape) self.melee_collision_shape = None # if slide interrupted make sure we restore normal collision shape dimensions if not self.motion_state == "slide": if self.collision_shape.width == 96 and self.collision_shape.height == 32: self.collision_shape.set_dimensions(64, 96) self.collision_shape_offset = [0, 0] self.player_drawable_rectangle.on_change_dimensions() if self.action_to_start != "": if self.action_to_start == "jump": self.touching_ground = False self.velocity[1] = self.jump_height self.world_position[1] -= 10.0 if self.action_to_start == "slide": self.collision_shape.set_dimensions(96, 32) self.collision_shape_offset = [0, 32] self.player_drawable_rectangle.on_change_dimensions() self.motion_state = self.action_to_start self.action_to_start = "" if self.motion_state == "climb": pass else: if not floor_collided_this_frame: self.velocity[1] += gravity * time_delta self.world_position[0] += self.velocity[0] * time_delta self.world_position[1] += self.velocity[1] * time_delta self.update_animation(time_delta) # set the position of the collision shapes self.collision_shape.set_position([ self.world_position[0] + self.collision_shape_offset[0], self.world_position[1] + self.collision_shape_offset[1] ]) self.triggers_collision_shape.set_position([ self.world_position[0] + self.ladder_collider_offset[0], self.world_position[1] + self.ladder_collider_offset[1] ]) self.update_visible_enemies() # set the sprite's centre position based of the current # position and the animation's 'centre point' offset self.update_screen_position(camera) self.rect.centerx = int(self.screen_position[0] - self.active_anim.centre_offset[0]) self.rect.centery = int(self.screen_position[1] - self.active_anim.centre_offset[1]) if self.should_respawn: self.respawn() if self.current_health <= 0 and not self.respawning: self.action_to_start = "die" self.respawning = True self.respawn_timer = self.respawn_time if self.respawning: if self.respawn_timer <= 0.0: self.should_respawn = True self.respawning = False else: self.respawn_timer -= time_delta def in_range_and_in_front(self, sprite): x_dist = sprite.world_position[0] - self.world_position[0] y_dist = sprite.world_position[1] - self.world_position[1] distance_squared = x_dist**2 + y_dist**2 if distance_squared >= self.view_cone.length_squared: return False if self.x_facing_direction == "right": facing_vec = [1.0, 0.0] else: facing_vec = [-1.0, 0.0] dot = facing_vec[0] * x_dist + facing_vec[1] * y_dist if dot < 0: return False return True def update_visible_enemies(self): enemies = [ sprite for sprite in self.moving_sprites_group.sprites() if sprite.type == "enemy" and self.in_range_and_in_front(sprite) ] if len(enemies ) > 0 and self.collision_shape.moved_since_last_collision_test: self.view_cone.set_position( pygame.math.Vector2(self.world_position)) if self.x_facing_direction == "right": facing_vec = [1.0, 0.0] else: facing_vec = [-1.0, 0.0] self.view_cone.set_facing_direction( pygame.math.Vector2(facing_vec)) self.view_cone.update() self.visible_enemies = [ sprite for sprite in enemies if self.view_cone.is_subject_visible( pygame.math.Vector2(sprite.world_position)) ] if len(self.visible_enemies) > 0: self.closest_visible_enemy = self.find_closest( self.visible_enemies) else: self.closest_visible_enemy = None else: self.view_cone.clear() self.closest_visible_enemy = None def find_closest(self, sprites): closest_distance = 9999999999999999.0 closest_sprite = None for sprite in sprites: x_dist = self.world_position[0] - sprite.world_position[0] y_dist = self.world_position[1] - sprite.world_position[1] squared_dist = x_dist**2 + y_dist**2 if squared_dist < closest_distance: closest_distance = squared_dist closest_sprite = sprite return closest_sprite def update_screen_position(self, camera): view_top_left_position = (camera.position[0] - camera.half_width, camera.position[1] - camera.half_height) self.screen_position[ 0] = self.world_position[0] - view_top_left_position[0] self.screen_position[ 1] = self.world_position[1] - view_top_left_position[1] def update_animation(self, time_delta): # set the animation name based on motion and direction anim_name = self.motion_state + "_" if self.motion_state == "climb": anim_name += self.y_facing_direction else: anim_name += self.x_facing_direction # if this is a new animation, run the start function if self.active_anim != self.anim_sets[anim_name]: self.active_anim = self.anim_sets[anim_name] self.active_anim.start() # update the animation & set the sprite image to the current animation frame self.active_anim.update(time_delta) self.image = self.active_anim.current_frame def update_jumping(self, time_delta, camera): if self.motion_state == "jump_attack": if self.active_anim.frame_index == 3 and self.melee_collision_shape is None: attack_position = [ self.world_position[0], self.world_position[1] ] if self.x_facing_direction == "left": attack_position[0] -= 64 attack_dimensions = [64, 16] game_types_to_collide_with = [CollisionType.AI] handlers_to_use = { CollisionType.AI: self.collision_grid.no_handler } self.melee_collision_shape = CollisionRect( pygame.Rect(attack_position[0], attack_position[1], attack_dimensions[0], attack_dimensions[1]), 0, handlers_to_use, CollisionType.PLAYER_ATTACKS, game_types_to_collide_with) self.collision_grid.add_new_shape_to_grid( self.melee_collision_shape) self.melee_collision_shape.owner = self self.melee_attack_collision_drawable_rectangle = DrawableCollisionRect( self.melee_collision_shape) elif self.active_anim.frame_index > 3 and self.melee_collision_shape is not None: self.collision_grid.remove_shape_from_grid( self.melee_collision_shape) self.melee_collision_shape = None if not self.active_anim.running: self.motion_state = "jump" elif self.motion_state == "jump_throw": if self.active_anim.frame_index == 3 and not self.thrown_knife: self.thrown_knife = True if self.closest_visible_enemy is not None: x_dir = self.closest_visible_enemy.world_position[ 0] - self.world_position[0] y_dir = self.closest_visible_enemy.world_position[ 1] - self.world_position[1] distance = math.sqrt(x_dir**2 + y_dir**2) throwing_direction = [x_dir / distance, y_dir / distance] # knives travel at roughly 1000.0 pixels a second, gravity is 800 pixels down per second # so we need to correct the direction vector based on how far away the target is. # after 1000 distance we will have descended roughly 800 from our target position # calculated by assuming traveling in a straight line to the right to a point 1000.0 away gravity_offset_vec = [0.0, -0.625 * distance / 1000.0] throwing_direction = [ throwing_direction[0] + gravity_offset_vec[0], throwing_direction[1] + gravity_offset_vec[1] ] # renormalise direction throwing_dir_len = math.sqrt(throwing_direction[0]**2 + throwing_direction[1]**2) throwing_direction = [ throwing_direction[0] / throwing_dir_len, throwing_direction[1] / throwing_dir_len ] else: if self.x_facing_direction == 'left': throwing_direction = [ -0.98, -0.19 + random.normalvariate(0, 0.02) ] else: throwing_direction = [ 0.98, -0.19 + random.normalvariate(0, 0.02) ] knife = ThrowingKnife(self.moving_sprites_group, self.collision_grid, self.knife_image, self.world_position, throwing_direction, self.projectile_drawable_rects, camera) self.active_knives.append(knife) if not self.active_anim.running: self.thrown_knife = False self.motion_state = "jump" else: self.motion_state = "jump" if self.move_left: self.move_speed -= self.air_acceleration * time_delta if self.move_speed < -self.air_top_speed: self.move_speed = -self.air_top_speed self.velocity[0] = self.move_speed self.x_facing_direction = "left" speed_factor = abs(self.move_speed / self.air_top_speed) self.active_anim.set_speed_factor(speed_factor) elif self.move_right: self.move_speed += self.air_acceleration * time_delta if self.move_speed > self.air_top_speed: self.move_speed = self.air_top_speed self.velocity[0] = self.move_speed self.x_facing_direction = "right" speed_factor = abs(self.move_speed / self.air_top_speed) self.active_anim.set_speed_factor(speed_factor) def update_running(self, time_delta): if self.move_left: self.motion_state = "run" if self.x_facing_direction == "right": self.move_speed = 0.0 self.move_speed -= self.ground_acceleration * time_delta if self.move_speed < -self.ground_top_speed: self.move_speed = -self.ground_top_speed self.velocity[0] = self.move_speed self.x_facing_direction = "left" speed_factor = abs(self.move_speed / self.ground_top_speed) self.active_anim.set_speed_factor(speed_factor) elif self.move_right: self.motion_state = "run" if self.x_facing_direction == "left": self.move_speed = 0.0 self.move_speed += self.ground_acceleration * time_delta if self.move_speed > self.ground_top_speed: self.move_speed = self.ground_top_speed self.velocity[0] = self.move_speed self.x_facing_direction = "right" speed_factor = abs(self.move_speed / self.ground_top_speed) self.active_anim.set_speed_factor(speed_factor) else: self.move_speed = 0.0 self.velocity[0] = self.move_speed self.motion_state = "idle" def update_dying(self): if not self.active_anim.running: pass # self.dying = False def update_melee_attacking(self, time_delta): if self.active_anim.running: if 3 < self.active_anim.frame_index < 10: if self.x_facing_direction == "left": self.move_speed -= self.attack_slide_acceleration * time_delta if self.move_speed < -self.attack_slide_top_speed: self.move_speed = -self.attack_slide_top_speed self.velocity[0] = self.move_speed elif self.x_facing_direction == "right": self.move_speed += self.attack_slide_acceleration * time_delta if self.move_speed > self.attack_slide_top_speed: self.move_speed = self.attack_slide_top_speed self.velocity[0] = self.move_speed if self.active_anim.frame_index == 3 and self.melee_collision_shape is None: attack_position = [ self.world_position[0], self.world_position[1] ] if self.x_facing_direction == "left": attack_position[0] -= 64 attack_dimensions = [64, 16] game_types_to_collide_with = [CollisionType.AI] handlers_to_use = { CollisionType.AI: self.collision_grid.no_handler } self.melee_collision_shape = CollisionRect( pygame.Rect(attack_position[0], attack_position[1], attack_dimensions[0], attack_dimensions[1]), 0, handlers_to_use, CollisionType.PLAYER_ATTACKS, game_types_to_collide_with) self.collision_grid.add_new_shape_to_grid( self.melee_collision_shape) self.melee_collision_shape.owner = self self.melee_attack_collision_drawable_rectangle = DrawableCollisionRect( self.melee_collision_shape) elif self.active_anim.frame_index > 3 and self.melee_collision_shape is not None: self.collision_grid.remove_shape_from_grid( self.melee_collision_shape) self.melee_collision_shape = None else: self.motion_state = "idle" def update_throwing(self, camera): if self.active_anim.frame_index == 3 and not self.thrown_knife: self.thrown_knife = True if self.closest_visible_enemy is not None: x_dir = self.closest_visible_enemy.world_position[ 0] - self.world_position[0] y_dir = self.closest_visible_enemy.world_position[ 1] - self.world_position[1] distance = math.sqrt(x_dir**2 + y_dir**2) throwing_direction = [x_dir / distance, y_dir / distance] # knives travel at roughly 1000.0 pixels a second, gravity is 800 pixels down per second # so we need to correct the direction vector based on how far away the target is. # after 1000 distance we will have descended roughly 800 from our target position # calculated by assuming traveling in a straight line to the right to a point 1000.0 away gravity_offset_vec = [0.0, -0.625 * distance / 1000.0] throwing_direction = [ throwing_direction[0] + gravity_offset_vec[0], throwing_direction[1] + gravity_offset_vec[1] ] # renormalise direction throwing_dir_len = math.sqrt(throwing_direction[0]**2 + throwing_direction[1]**2) throwing_direction = [ throwing_direction[0] / throwing_dir_len, throwing_direction[1] / throwing_dir_len ] else: if self.x_facing_direction == 'left': throwing_direction = [ -0.98, -0.19 + random.normalvariate(0, 0.02) ] else: throwing_direction = [ 0.98, -0.19 + random.normalvariate(0, 0.02) ] knife = ThrowingKnife(self.moving_sprites_group, self.collision_grid, self.knife_image, self.world_position, throwing_direction, self.projectile_drawable_rects, camera) self.active_knives.append(knife) if not self.active_anim.running: self.motion_state = "idle" self.thrown_knife = False def update_sliding(self, time_delta): if self.active_anim.running: if self.x_facing_direction == "left": self.move_speed -= self.slide_acceleration * time_delta if self.move_speed < -self.slide_top_speed: self.move_speed = -self.slide_top_speed self.velocity[0] = self.move_speed elif self.x_facing_direction == "right": self.move_speed += self.slide_acceleration * time_delta if self.move_speed > self.slide_top_speed: self.move_speed = self.slide_top_speed self.velocity[0] = self.move_speed else: self.collision_shape.set_dimensions(64, 96) self.collision_shape_offset = [0, 0] self.player_drawable_rectangle.on_change_dimensions() self.motion_state = "idle" def update_climbing(self, time_delta): if self.motion_state == "climb" and not self.in_climb_position: self.motion_state = "idle" if self.in_climb_position: if self.climb_up: self.world_position[0] = self.found_ladder_position[0] if self.touching_ground: self.world_position[1] -= 10.0 self.motion_state = "climb" self.y_facing_direction = "up" self.velocity[1] = 0.0 self.move_speed = 0.0 self.touching_ground = False self.climb_speed -= self.climb_acceleration * time_delta if self.climb_speed < -self.climb_top_speed: self.climb_speed = -self.climb_top_speed self.world_position[1] += (self.climb_speed * time_delta) speed_factor = abs(self.climb_speed / self.climb_top_speed) self.active_anim.set_speed_factor(speed_factor) elif self.climb_down: self.world_position[0] = self.found_ladder_position[0] if self.touching_ground: self.motion_state = "idle" self.climb_speed = 0.0 else: self.motion_state = "climb" self.y_facing_direction = "down" self.velocity[1] = 0.0 self.move_speed = 0.0 self.touching_ground = False self.climb_speed += self.climb_acceleration * time_delta if self.climb_speed > self.climb_top_speed: self.climb_speed = self.climb_top_speed self.world_position[1] += (self.climb_speed * time_delta) speed_factor = abs(self.climb_speed / self.climb_top_speed) self.active_anim.set_speed_factor(speed_factor) elif self.motion_state == "climb": self.touching_ground = False self.velocity[1] = 0.0 self.climb_speed = 0.0 self.active_anim.set_speed_factor(0.0) else: self.climb_up = False self.climb_down = False if self.motion_state != "climb": self.climb_speed = 0.0 def draw_collision_shapes(self, screen, camera): self.player_drawable_rectangle.update_collided_colours() self.player_drawable_rectangle.draw( screen, camera.position, (camera.half_width, camera.half_height)) self.player_ladder_drawable_rectangle.update_collided_colours() self.player_ladder_drawable_rectangle.draw( screen, camera.position, (camera.half_width, camera.half_height)) for rect in self.projectile_drawable_rects: rect.update_collided_colours() rect.draw(screen, camera.position, (camera.half_width, camera.half_height)) self.view_cone.draw(screen, camera) if self.melee_collision_shape is not None: self.melee_attack_collision_drawable_rectangle.update_collided_colours( ) self.melee_attack_collision_drawable_rectangle.draw( screen, camera.position, (camera.half_width, camera.half_height)) def add_exit_door_hint(self): self.ui_sprites_group.add(self.exit_door_hint) def remove_exit_door_hint(self): self.ui_sprites_group.remove(self.exit_door_hint)
def test_cone(): pygame.init() screen = pygame.display.set_mode((800, 600)) background = pygame.Surface((800, 600)) background.fill(pygame.Color("#000000")) running = True view_cone = ViewCone([400, 300], [1.0, 0.0], fov=60, length=200) test_rect = CollisionRect(pygame.Rect((310, 400), (30, 30)), 0, {CollisionType.VIEW_CONE: CollisionNoHandler()}, CollisionType.WORLD_SOLID, [CollisionType.VIEW_CONE] ) drawable_test_rect = DrawableCollisionRect(test_rect) test_rect_2 = CollisionRect(pygame.Rect((340, 400), (30, 30)), 0, {CollisionType.VIEW_CONE: CollisionNoHandler()}, CollisionType.WORLD_SOLID, [CollisionType.VIEW_CONE] ) drawable_test_rect_2 = DrawableCollisionRect(test_rect_2) drawable_test_circle = DrawableCollisionCircle(view_cone.collision_circle) test_rect.normals["right"].should_skip = True test_rect_2.normals["left"].should_skip = True test_points = [] for _ in range(0, 1000): test_points.append(pygame.math.Vector2(float(random.randint(10, 790)), float(random.randint(10, 590)))) clock = pygame.time.Clock() while running: clock.tick(60) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == pygame.MOUSEBUTTONDOWN: if event.button == 1: mouse_pos = pygame.mouse.get_pos() vec_to_mouse = [mouse_pos[0] - view_cone.origin_centre_position[0], mouse_pos[1] - view_cone.origin_centre_position[1]] length = math.sqrt(vec_to_mouse[0] ** 2 + vec_to_mouse[1] ** 2) if length > 0.0: vec_to_mouse_norm = Vector2(vec_to_mouse[0] / length, vec_to_mouse[1] / length) view_cone.set_facing_direction(vec_to_mouse_norm) if event.button == 3: view_cone.set_position(Vector2(pygame.mouse.get_pos())) screen.blit(background, (0, 0)) view_cone.update() for point in test_points: if view_cone.is_subject_visible(point): pygame.draw.line(screen, pygame.Color("#0000FF"), point, [point.x + 1, point.y + 1], 2) else: pygame.draw.line(screen, pygame.Color("#FF0000"), point, [point.x + 1, point.y + 1], 2) if collide_circle_with_rotated_rectangle(view_cone.collision_circle, test_rect): test_rect.add_frame_collided_shape(view_cone.collision_circle) view_cone.collision_circle.add_frame_collided_shape(test_rect) else: test_rect.clear_frame_collided_shapes() view_cone.collision_circle.clear_frame_collided_shapes() if collide_circle_with_rotated_rectangle(view_cone.collision_circle, test_rect_2): test_rect_2.add_frame_collided_shape(view_cone.collision_circle) view_cone.collision_circle.add_frame_collided_shape(test_rect_2) else: test_rect_2.clear_frame_collided_shapes() view_cone.collision_circle.clear_frame_collided_shapes() drawable_test_rect.update_collided_colours() drawable_test_rect_2.update_collided_colours() drawable_test_circle.update_collided_colours() drawable_test_rect.draw(screen) drawable_test_rect_2.draw(screen) # drawable_test_circle.draw(screen) view_cone.draw(screen) pygame.display.flip()