|
|
|
|
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)
|