diff --git a/enemy.py b/enemy.py new file mode 100644 index 0000000..1b1742e --- /dev/null +++ b/enemy.py @@ -0,0 +1,247 @@ +import pygame +from settings import * +from entity import Entity +from support import * + +class Enemy(Entity): + def __init__(self,monster_name,pos,groups,obstacle_sprites,damage_player,trigger_death_particles,add_exp): + + # general setup + # 调用父类的初始化方法,将实例添加到指定的分组中 + super().__init__(groups) + # 设置角色的精灵类型为 'enemy' + self.sprite_type = 'enemy' + + # graphics setup + # 导入指定怪物名称的图像资源 + self.import_graphics(monster_name) + # 设置角色的状态为 'idle'(空闲状态) + self.status = 'idle' + # 获取角色当前状态的图像 + self.image = self.animations[self.status][self.frame_index] + + # movement + # 设置角色的位置 + self.rect = self.image.get_rect(topleft = pos) + # 设置角色的碰撞框,稍微向上收缩一些 + self.hitbox = self.rect.inflate(0,-10) + # 设置角色所在的障碍物精灵组 + self.obstacle_sprites = obstacle_sprites + + # stats 将怪物的各种值赋予 + self.monster_name = monster_name + # 从怪物数据字典中获取指定怪物的信息 + monster_info = monster_data[self.monster_name] + # 怪物的生命值 + self.health = monster_info['health'] + # 击败怪物后获得的经验值 + self.exp = monster_info['exp'] + # 怪物的移动速度 + self.speed = monster_info['speed'] + # 怪物的攻击伤害 + self.attack_damage = monster_info['damage'] + # 怪物的抵抗力 + self.resistance = monster_info['resistance'] + # 怪物的攻击范围 + self.attack_radius = monster_info['attack_radius'] + # 怪物的发现玩家范围 + self.notice_radius = monster_info['notice_radius'] + # 怪物的攻击类型 + self.attack_type = monster_info['attack_type'] + + # player interaction 设置角色与玩家交互相关的属性和方法 + # 角色是否能够进行攻击 + self.can_attack = True + # 记录上次攻击的时间 + self.attack_time = None + # 攻击的冷却时间(毫秒) + self.attack_cooldown = 400 + # 对玩家造成伤害的方法 + self.damage_player = damage_player + # 触发角色死亡特效的方法 + self.trigger_death_particles = trigger_death_particles + # 增加玩家经验值的方法 + self.add_exp = add_exp + + # invincibility timer 设置角色的无敌时间相关属性 + # 角色是否处于易受伤状态 + self.vulnerable = True + # 记录角色受到攻击的时间点 + self.hit_time = None + # 角色的无敌持续时间(毫秒) + self.invincibility_duration = 300 + + # sounds 设置角色相关的音效 + # 死亡音效 + self.death_sound = pygame.mixer.Sound('../audio/death.wav') + # 受伤音效 + self.hit_sound = pygame.mixer.Sound('../audio/hit.wav') + # 攻击音效 + self.attack_sound = pygame.mixer.Sound(monster_info['attack_sound']) + # 设置死亡音效的音量 + self.death_sound.set_volume(0.6) + # 设置受伤音效的音量 + self.hit_sound.set_volume(0.6) + # 设置攻击音效的音量 + self.attack_sound.set_volume(0.6) + + def import_graphics(self,name): + # 初始化动画字典,包含三种动画状态:idle(空闲)、move(移动)、attack(攻击) + self.animations = {'idle':[],'move':[],'attack':[]} + # 指定敌人角色图形资源的主路径,根据角色名称构建路径 + main_path = f'../graphics/monsters/{name}/' + # 调用 import_folder 函数导入指定路径下的图像资源,并将结果存储到动画字典中对应状态的键中 + for animation in self.animations.keys(): + self.animations[animation] = import_folder(main_path + animation) + + def get_player_distance_direction(self,player): + # 获取敌人角色和玩家角色的中心点坐标作为向量 + enemy_vec = pygame.math.Vector2(self.rect.center) + player_vec = pygame.math.Vector2(player.rect.center) + # 计算敌人角色与玩家角色之间的距离 + distance = (player_vec - enemy_vec).magnitude() + # 如果距离大于 0,则计算方向向量 + if distance > 0: + # 计算方向向量并归一化 + direction = (player_vec - enemy_vec).normalize() + # 距离为 0 或负数时,方向向量为零向量 + else: + direction = pygame.math.Vector2() + # 返回距离和方向向量的元组 + return (distance,direction) + + def get_status(self, player): + # 获取敌人角色与玩家角色的距离 + distance = self.get_player_distance_direction(player)[0] + # 根据距离和设定的攻击范围、发现范围确定敌人角色的状态 + if distance <= self.attack_radius and self.can_attack: + # 如果距离小于等于攻击范围并且可以进行攻击 + if self.status != 'attack': + # 重置动画帧索引 + self.frame_index = 0 + # 设置状态为攻击 + self.status = 'attack' + # 如果距离小于等于发现范围 + elif distance <= self.notice_radius: + # 设置状态为移动 + self.status = 'move' + # 距离大于发现范围 + else: + # 设置状态为空闲(静止) + self.status = 'idle' + + def actions(self,player): + # 如果状态为攻击 + if self.status == 'attack': + # 记录攻击时间 + self.attack_time = pygame.time.get_ticks() + # 对玩家造成伤害 + self.damage_player(self.attack_damage,self.attack_type) + # 播放攻击音效 + self.attack_sound.play() + # 如果状态为移动 + elif self.status == 'move': + # 获取朝向玩家的方向向量 + self.direction = self.get_player_distance_direction(player)[1] + # 其他情况(状态为空闲或其他) + else: + # 将方向向量设为零向量,即停止移动 + self.direction = pygame.math.Vector2() + + def animate(self): + # 获取当前状态对应的动画帧列表 + animation = self.animations[self.status] + # 更新帧索引,控制动画播放速度 + self.frame_index += self.animation_speed + # 如果帧索引超过动画帧列表的长度 + if self.frame_index >= len(animation): + # 如果是攻击状态,设置为不可攻击状态 + if self.status == 'attack': + self.can_attack = False + # 重置帧索引,从头开始播放动画 + self.frame_index = 0 + # 更新角色的图像为当前帧的图像 + self.image = animation[int(self.frame_index)] + # 更新角色的矩形位置 + self.rect = self.image.get_rect(center = self.hitbox.center) + # 根据角色的易受伤状态设置图像的透明度 + if not self.vulnerable: + # 调用自定义的方法获取波形值,用于设置透明度 + alpha = self.wave_value() + # 设置图像的透明度 + self.image.set_alpha(alpha) + # 如果角色不是易受伤状态,则设置完全不透明(透明度为 255) + else: + self.image.set_alpha(255) +# 处理敌人角色的攻击冷却和无敌状态的持续时间。 + def cooldowns(self): + # 获取当前时间(以毫秒为单位) + current_time = pygame.time.get_ticks() + # 处理攻击冷却时间 + # 如果当前不能攻击(即处于攻击冷却中) + if not self.can_attack: + # 如果距离上次攻击时间已经超过攻击冷却时间 + if current_time - self.attack_time >= self.attack_cooldown: + # 设置为可以再次攻击 + self.can_attack = True + # 处理无敌状态持续时间 + # 如果当前不可受伤(即处于无敌状态) + if not self.vulnerable: + # 如果距离上次受伤时间已经超过无敌状态持续时间 + if current_time - self.hit_time >= self.invincibility_duration: + # 设置为可受伤状态(结束无敌状态) + self.vulnerable = True +# 处理敌人角色受到玩家攻击造成的伤害。 + def get_damage(self,player,attack_type): + if self.vulnerable: + # 播放受击音效 + self.hit_sound.play() + # 设置受击后的朝向玩家的方向向量 + self.direction = self.get_player_distance_direction(player)[1] + # 根据攻击类型应用不同的伤害值 + if attack_type == 'weapon': + # 玩家使用武器攻击造成伤害 + self.health -= player.get_full_weapon_damage() + # 玩家使用魔法攻击造成伤害 + else: + self.health -= player.get_full_magic_damage() + # 记录受击时间 + self.hit_time = pygame.time.get_ticks() + # 设置为不可受伤状态,进入无敌状态 + self.vulnerable = False + #检查角色是否死亡 + def check_death(self): + # 如果敌人角色的生命值小于等于 0,表示角色已经死亡 + if self.health <= 0: + # 从精灵组中移除角色,通常用于删除角色或停止其更新和渲染 + self.kill() + # 触发死亡粒子效果,通常用于播放死亡动画或粒子效果 + self.trigger_death_particles(self.rect.center,self.monster_name) + # 增加经验值 + self.add_exp(self.exp) + # 播放死亡音效,提供游戏中的音效反馈 + self.death_sound.play() + #被攻击后的反应 + def hit_reaction(self): + # 如果敌人角色不可受伤 + if not self.vulnerable: + # 则将角色的移动方向向量乘以负阻力,模拟受击后的击退效果。 + self.direction *= -self.resistance + #更新状态 + def update(self): + # 处理受击反应 + self.hit_reaction() + # 移动角色,传入速度参数 + self.move(self.speed) + # 更新角色动画 + self.animate() + # 处理攻击冷却和无敌状态持续时间 + self.cooldowns() + # 检查角色是否死亡并执行死亡逻辑 + self.check_death() + + def enemy_update(self,player): + # 获取敌人角色的状态 + self.get_status(player) + # 执行敌人角色的行为 + self.actions(player) \ No newline at end of file