You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

247 lines
9.3 KiB

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)