Files
Gen_Hack-and-Slash-Roguelit…/Client/Assets/Scripts/Entity/Entity.cs

715 lines
25 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using AI;
using Base;
using Data;
using Item;
using Managers;
using Prefab;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Utils;
namespace Entity
{
/// <summary>
/// 表示游戏中的实体类,继承自 MonoBehaviour 并实现 ITick 接口。
/// </summary>
public class Entity : MonoBehaviour, ITick
{
/// <summary>
/// 动画预制体,用于管理实体的动画逻辑。
/// </summary>
public SpriteAnimator animatorPrefab;
/// <summary>
/// 图像预制体,用于管理实体的静态图像显示。
/// </summary>
public ImagePrefab imagePrefab;
public ProgressBarPrefab healthBarPrefab;
public EntityPrefab entityPrefab;
public EntityDef entityDef;
/// <summary>
/// 手上拿着显示的贴图
/// </summary>
public GameObject weaponAnimator;
public SpriteAnimator weaponItem;
public SpriteAnimator weaponAttackAnimation;
/// <summary>
/// 人工智能行为树,定义实体的行为逻辑。
/// </summary>
public AIBase aiTree;
/// <summary>
/// 当前实体正在执行的任务。
/// </summary>
public JobBase currentJob;
private Attributes _attribute;
/// <summary>
/// 实体的属性定义,包括生命值、攻击力、防御力等。
/// </summary>
public virtual Attributes attributes
{
get { return _attribute ??= new Attributes(baseAttributes); }
protected set => _attribute = value;
}
public virtual Attributes baseAttributes => defAttributes;
private Attributes _defAttributes;
public virtual Attributes defAttributes
{
get
{
return _defAttributes ??= entityDef == null ? new Attributes() : new Attributes(entityDef.attributes);
}
}
/// <summary>
/// 实体当前的移动方向。
/// </summary>
public Vector3 direction;
public Vector3 attackDirection;
/// <summary>
/// 实体的身体部分,用于挂载动画和图像节点。
/// </summary>
public GameObject body;
/// <summary>
/// 实体所属的阵营或派系。
/// </summary>
public string affiliation;
/// <summary>
/// 表示实体是否可以被选择。
/// </summary>
public bool canSelect = true;
public string currentDimensionId = null;
/// <summary>
/// 表示实体是否由玩家控制。
/// </summary>
public bool PlayerControlled
{
set
{
if (value)
{
currentJob = null;
// 逻辑修改只有当存在一个不同的焦点实体时才将其PlayerControlled设为false
if (Program.Instance.FocusedEntity && Program.Instance.FocusedEntity != this)
{
Program.Instance.FocusedEntity.PlayerControlled = false;
}
Program.Instance.SetFocusedEntity(this);
}
// 逻辑修改:确保只有当自身是焦点实体时才取消焦点,避免不必要的逻辑执行
else if (Program.Instance.FocusedEntity == this)
{
Program.Instance.SetFocusedEntity(null);
}
}
get => Program.Instance.FocusedEntity == this;
}
public bool IsWalking => _walkingTimer > 0;
/// <summary>
/// 获取实体当前位置。
/// </summary>
public Vector3 Position => transform.position;
/// <summary>
/// 表示实体是否已经死亡(生命值小于等于零)。
/// </summary>
public bool IsDead => attributes.health <= 0;
public bool IsShowingHealthBarUI => _hitBarUIShowTimer > 0;
public bool IsAttacking => _attackTimer > 0;
private float _attackTimer = 0;
private float _attackDetectionTime = 0;
private WeaponResource currentAttackWeapon;
/// <summary>
/// 当实体受到伤害时触发的事件。
/// 可以订阅此事件来响应实体的生命值变化例如更新UI或播放受击特效。
/// </summary>
public event Action<EntityHitEventArgs> OnEntityHit;
/// <summary>
/// 当实体死亡时触发的事件。
/// 只在实体首次进入死亡状态时触发一次。
/// </summary>
public event Action<Entity> OnEntityDied;
private bool _warning = false;
private GameObject wearponAttackAnimationNodeRoot = null;
/// <summary>
/// 存储不同朝向下的身体节点对象。
/// </summary>
protected Dictionary<EntityState, Dictionary<Orientation, GameObject>> bodyNodes = new();
/// <summary>
/// 当前实体的朝向。
/// </summary>
private Orientation _currentOrientation = Orientation.Down;
/// <summary>
/// 当前实体的状态
/// </summary>
private EntityState _currentState = EntityState.Idle;
[SerializeField] private float _hitBarUIShowTime = 5;
private float _hitBarUIShowTimer = 0;
private int _walkingTimer = 0;
/// <summary>
/// 初始化实体的基本属性和行为树。
/// </summary>
/// <param name="entityDef">实体的定义数据。</param>
public virtual void Init(EntityDef entityDef)
{
attributes = new Attributes(entityDef.attributes);
aiTree = AI.BehaviorTree.ConvertToAIBase(entityDef.behaviorTree);
affiliation = entityDef.affiliation?.defName;
InitBody(entityDef.drawingOrder);
this.entityDef = entityDef;
HideHealthBar();
InitWeaponAnimator();
}
protected virtual void InitWeaponAnimator()
{
if (weaponAnimator && weaponAnimator.transform.childCount > 0)
{
foreach (Transform child in weaponAnimator.transform)
{
Destroy(child.gameObject);
}
}
var currentWeapon = GetCurrentWeapon();
if (currentWeapon?.AttackAnimation == null)
return;
weaponItem=GameObjectCreate.SpriteAnimator(currentWeapon.Icon.ToArray(), weaponAnimator.transform);
weaponItem.SetFPS(currentWeapon.FPS);
weaponAttackAnimation = GameObjectCreate.SpriteAnimator(currentWeapon.AttackAnimation.ToArray(),
weaponAnimator.transform);
weaponAttackAnimation.SetFPS(currentWeapon.FPS);
weaponAttackAnimation.gameObject.SetActive(false);
}
/// <summary>
/// 初始化实体的身体部分,包括不同朝向下的绘图节点。
/// </summary>
/// <param name="drawingOrder">绘制顺序定义。</param>
protected virtual void InitBody(DrawingOrderDef drawingOrder)
{
// 预缓存枚举值(避免每次循环重复调用 Enum.GetValues
var states = Enum.GetValues(typeof(EntityState)).Cast<EntityState>().ToArray();
var orientations = Enum.GetValues(typeof(Orientation)).Cast<Orientation>().ToArray();
// 预初始化字典结构(减少内层循环的字典检查)
foreach (var state in states)
{
bodyNodes.TryAdd(state, new Dictionary<Orientation, GameObject>());
}
// 主初始化逻辑
foreach (var state in states)
{
var stateBodyNodes = bodyNodes[state];
foreach (var orientation in orientations)
{
// 获取节点定义(避免重复调用)
var nodeDef = drawingOrder.GetDrawNodeDef(state, orientation, out var original);
GameObject targetObj;
if (nodeDef == null)
{
if (imagePrefab && Managers.PackagesImageManager.Instance.defaultSprite != null)
{
targetObj = Instantiate(imagePrefab.gameObject, body.transform);
targetObj.name = $"{state}_{orientation}_Default";
targetObj.transform.localPosition = Vector3.zero;
var imagePrefabCom = targetObj.GetComponent<ImagePrefab>();
if (imagePrefabCom)
{
imagePrefabCom.SetSprite(Managers.PackagesImageManager.Instance.defaultSprite);
}
else
{
Debug.LogWarning(
$"InitBody: 默认ImagePrefab中无法获取ImagePrefab组件状态: {state}, 朝向: {orientation}");
targetObj = new GameObject { name = $"{state}_{orientation}_Empty" };
targetObj.transform.SetParent(body.transform, false);
}
}
else
{
targetObj = new GameObject { name = $"{state}_{orientation}_Empty" };
targetObj.transform.SetParent(body.transform, false);
}
}
else
{
// 处理有效节点定义
if (original.HasValue && stateBodyNodes.TryGetValue(original.Value, out var reusedObj))
{
targetObj = reusedObj; // 复用已有对象
}
else
{
targetObj = GameObjectCreate.InitBodyPart(nodeDef, body); // 创建新对象
}
}
if (targetObj)
{
stateBodyNodes[orientation] = targetObj;
}
else
{
Debug.LogError($"InitBody: 无法为状态 {state}, 朝向 {orientation} 创建或找到有效的GameObject。");
stateBodyNodes[orientation] = new GameObject($"ErrorNode_{state}_{orientation}"); // 提供一个错误占位符
stateBodyNodes[orientation].transform.SetParent(body.transform, false);
}
}
}
// 批量隐藏所有节点(使用字典值集合直接操作)
foreach (var nodeDict in bodyNodes.Values)
{
foreach (var obj in nodeDict.Values)
{
obj.SetActive(false);
}
}
SetBodyTexture(EntityState.Idle, Orientation.Down); // 激活默认朝向
}
/// <summary>
/// 更新实体的逻辑,包括玩家控制和自动行为。
/// </summary>
public virtual void Tick()
{
if (_walkingTimer > 0)
{
_walkingTimer -= 1;
if (_walkingTimer <= 0)
{
SetBodyTexture(EntityState.Idle, _currentOrientation);
}
}
if (PlayerControlled)
{
UpdatePlayerControls();
}
else
{
AutoBehave();
}
if (IsShowingHealthBarUI)
{
_hitBarUIShowTimer -= Time.deltaTime;
if (_hitBarUIShowTimer <= 0)
{
HideHealthBar();
}
}
if (_attackTimer > 0)
{
_attackTimer -= Time.deltaTime;
if (currentAttackWeapon != null && _attackTimer <= _attackDetectionTime)
{
if (currentAttackWeapon.Type == WeaponType.Melee)
{
ExecuteMeleeAttack(currentAttackWeapon);
}
else
{
ExecuteRangedAttack(currentAttackWeapon);
}
currentAttackWeapon = null;
}
if (_attackTimer <= 0)
{
SetBodyTexture(EntityState.Idle, _currentOrientation);
}
}
}
/// <summary>
/// 尝试攻击目标实体。
/// </summary>
public virtual void TryAttack() // 使用override允许子类重写
{
if (IsAttacking || IsDead) return; // 死亡时无法攻击
// 尝试获取当前武器
var currentWeapon = GetCurrentWeapon();
// 如果没有武器,可以选择进行徒手攻击或者直接返回
// 暂时设定为:如果没有武器,则不进行攻击
if (currentWeapon == null)
{
return;
}
StartAttack(currentWeapon);
}
private void StartAttack(WeaponResource weaponResource)
{
_attackTimer = weaponResource.AttackCooldown;
_attackDetectionTime = weaponResource.AttackDetectionTime;
currentAttackWeapon = weaponResource;
if (weaponResource.AttackAnimationTime > 0)
{
TemporaryAnimationManager.Instance.GenerateTemporaryAnimation(
weaponResource.AttackAnimation.ToArray(), Position,transform, weaponResource.AttackAnimationTime,
weaponResource.FPS);
}
if (weaponResource.UseEntityAttackAnimation)
{
SetBodyTexture(
weaponResource.Type == WeaponType.Melee ? EntityState.MeleeAttack : EntityState.RangedAttack,
_currentOrientation);
}
else
{
HideCurrentBodyTexture();
}
}
public void SetBodyTexture(EntityState state, Orientation orientation)
{
HideCurrentBodyTexture();
if (IsAttacking && currentAttackWeapon is { UseEntityAttackAnimation: false })
return;
if (bodyNodes.TryGetValue(state, out var showStateNode))
{
if (showStateNode.TryGetValue(orientation, out var showNode))
{
showNode.SetActive(true);
}
}
_currentState = state;
_currentOrientation = orientation;
}
public void HideCurrentBodyTexture()
{
if (!bodyNodes.TryGetValue(_currentState, out var stateNode)) return;
if (stateNode.TryGetValue(_currentOrientation, out var node))
{
node.SetActive(false);
}
}
/// <summary>
/// 根据方向尝试移动实体。
/// </summary>
public virtual void TryMove()
{
// if (IsAttacking)
// return;
transform.position += direction * (attributes.moveSpeed * Time.deltaTime);
SetBodyTexture(EntityState.Walking, _currentOrientation);
_walkingTimer = 2;
}
/// <summary>
/// 处理实体受到攻击的逻辑。
/// </summary>
/// <param name="from">攻击来源实体。</param>
public virtual void OnHit(Entity from)
{
// MODIFIED: 收到攻击时触发回调
if (IsDead) // 如果已经死亡,则不再处理伤害 nor 触发事件
{
return;
}
var hit = from.attributes.attack - attributes.defense;
if (hit < 0)
hit = from.attributes.attack / 100;
// 确保伤害不为负最小为0
hit = Mathf.Max(0, hit);
attributes.health -= hit;
var wasFatal = IsDead; // 检查这次攻击是否导致实体死亡
// 触发 OnEntityHit 事件
OnEntityHit?.Invoke(new EntityHitEventArgs(
this, from, hit, attributes.health, entityDef.attributes.health, wasFatal));
currentJob?.StopJob();
if (wasFatal)
{
// 如果是首次死亡,则触发 OnEntityDied 事件
// MODIFIED: 停止所有活动,包括当前工作
currentJob = null; // 清除当前工作
OnEntityDied?.Invoke(this);
}
if (Setting.Instance.CurrentSettings.showHealthBarByHit)
{
ShowHealthBar();
}
if (Setting.Instance.CurrentSettings.showHitNumber)
{
TemporaryAnimationManager.Instance.GenerateTemporaryAnimation(hit.ToString(), Position);
}
}
public void ShowHealthBar()
{
if (!healthBarPrefab)
return;
healthBarPrefab.gameObject.SetActive(true);
healthBarPrefab.Progress = (float)attributes.health / entityDef.attributes.health;
_hitBarUIShowTimer = _hitBarUIShowTime;
}
public void HideHealthBar()
{
if (!healthBarPrefab)
return;
healthBarPrefab.gameObject.SetActive(false);
}
/// <summary>
/// 杀死实体,设置生命值为零。
/// </summary>
public virtual void Kill()
{
if (IsDead)
{
return;
}
attributes.health = 0; // 直接设置生命值为0
// MODIFIED: 停止所有活动,包括当前工作
currentJob?.StopJob();
currentJob = null; // 清除当前工作
// 触发 OnEntityDied 事件
OnEntityDied?.Invoke(this);
ShowHealthBar();
}
/// <summary>
/// 设置实体的目标位置。
/// </summary>
/// <param name="pos">目标位置。</param>
public virtual void SetTarget(Vector3 pos)
{
direction = (pos - transform.position).normalized;
Orientation ori;
// 判断方向向量最接近哪个朝向
if (Mathf.Abs(direction.y) > Mathf.Abs(direction.x))
{
// 垂直方向优先
ori = direction.y > 0 ? Orientation.Up : Orientation.Down;
}
else
{
// 水平方向优先
ori = direction.x > 0 ? Orientation.Right : Orientation.Left;
}
SetBodyTexture(_currentState, ori);
if (!PlayerControlled)
{
attackDirection=direction;
}
}
/// <summary>
/// 自动行为逻辑,根据行为树执行任务。
/// </summary>
protected virtual void AutoBehave()
{
if (aiTree == null)
return;
if (currentJob == null || !currentJob.Running)
{
currentJob = aiTree.GetJob(this);
if (currentJob == null)
{
if (!_warning)
{
Debug.LogWarning($"{GetType().Name}类型的{name}没有分配到任何工作,给行为树末尾添加等待行为,避免由于没有工作导致无意义的反复查找工作导致性能问题");
_warning = true;
}
return;
}
currentJob.StartJob(this);
}
currentJob.Update();
}
/// <summary>
/// 更新玩家控制的逻辑,处理输入和移动。
/// </summary>
protected virtual void UpdatePlayerControls()
{
if (Input.GetMouseButton(0))
{
var mousePos = MousePosition.GetWorldPosition();
attackDirection = new Vector3(mousePos.x,mousePos.y) - Position;
}
// 获取当前键盘输入状态2D 移动,只使用 X 和 Y 轴)
var inputDirection = Vector2.zero;
// 检测 WASD 或方向键输入
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow))
{
inputDirection += Vector2.up; // 向上移动Y 轴正方向)
}
if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow))
{
inputDirection += Vector2.down; // 向下移动Y 轴负方向)
}
if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
{
inputDirection += Vector2.left; // 向左移动X 轴负方向)
}
if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow))
{
inputDirection += Vector2.right; // 向右移动X 轴正方向)
}
if (Input.GetMouseButton(0))
{
TryAttack();
}
// 如果有输入方向,则设置目标位置并尝试移动
if (inputDirection == Vector2.zero) return;
// 归一化方向向量,确保对角线移动速度一致
inputDirection = inputDirection.normalized;
// 设置目标位置2D 移动Z 轴保持不变)
var targetPosition = transform.position + new Vector3(inputDirection.x, inputDirection.y, 0);
// 调用 SetTarget 方法设置目标位置
SetTarget(targetPosition);
// 调用 TryMove 方法处理实际移动逻辑
TryMove();
}
private void ExecuteMeleeAttack(WeaponResource weapon)
{
if (weapon.Attributes == null)
{
Debug.LogWarning($"武器 {weapon.DefName} 没有定义Attributes无法执行近战攻击。");
return;
}
var attackRange = weapon.Attributes.attackRange;
var attackTargetCount = weapon.Attributes.attackTargetCount;
var hits = Physics2D.OverlapCircleAll(
transform.position,
attackRange,
LayerMask.GetMask("Entity"));
foreach (var hit in hits)
{
if (attackTargetCount <= 0) break; // 已达到最大攻击目标数
if (hit.gameObject == gameObject) continue; // 不攻击自己
var entity = hit.GetComponent<Entity>();
if (entity != null && entity.affiliation != affiliation) // 确保是敌对实体
{
entity.OnHit(this); // 攻击时将自身作为攻击来源
attackTargetCount--;
}
}
}
// NEW: 辅助方法用于执行远程攻击
private void ExecuteRangedAttack(WeaponResource weapon)
{
if (weapon.Bullet == null)
{
Debug.LogWarning($"远程武器 {weapon.DefName} 没有定义Bullet无法发射子弹。");
return;
}
// 获取子弹方向。这里使用实体当前的移动方向作为子弹发射方向
// 更复杂的逻辑可能根据鼠标位置、目标位置等确定
var bulletDirection = attackDirection; // 实体当前的朝向
// 如果没有明确的方向,给一个默认值以防万一
if (bulletDirection == Vector3.zero) bulletDirection = Vector3.down;
// 假设 EntityManage.Instance.GenerateBulletEntity 方法存在
// (需要一个 EntityManage 单例来实现子弹生成)
if (EntityManage.Instance != null && Program.Instance != null)
{
EntityManage.Instance.GenerateBulletEntity(
Program.Instance.FocusedDimensionId,
weapon.Bullet,
transform.position, // 子弹的生成位置
bulletDirection, // 子弹的初始方向
this); // 子弹的发射者
}
else
{
Debug.LogError("EntityManage.Instance 或 Program.Instance 为空,无法生成子弹。请确保它们已正确初始化。");
}
}
public virtual WeaponResource GetCurrentWeapon()
{
return null;
}
}
}