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

788 lines
28 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.Generic;
using System.Linq;
using Parsing;
using UnityEngine;
using Utils;
namespace Entity
{
/// <summary>
/// 表示游戏中的实体类,继承自 MonoBehaviour 并实现 ITick 接口。
/// </summary>
public class Entity : MonoBehaviour, ITick
{
public ProgressBarPrefab healthBarPrefab;
public EntityPrefab entityPrefab;
public EntityDef entityDef;
/// <summary>
/// 手上拿着显示的贴图
/// </summary>
public GameObject weaponAnimator;
public SpriteAnimator weaponItem;
public SpriteAnimator weaponAttackAnimation;
/// <summary>
/// 人工智能行为树,定义实体的行为逻辑。
/// </summary>
public BehaviorTreeBase BehaviorTreeTree { get; private set; }
// /// <summary>
// /// 当前实体正在执行的任务。
// /// </summary>
// public JobBase currentJob;
private Attributes _attribute;
/// <summary>
/// 实体的属性定义,包括生命值、攻击力、防御力等。
/// </summary>
public virtual Attributes AttributesNow
{
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);
}
}
private Vector3 _direction;
/// <summary>
/// 实体当前的移动方向。
/// </summary>
public Vector3 Direction
{
get => _direction;
set
{
_direction = Mathf.Approximately(value.sqrMagnitude, 1) ? value : value.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);
}
}
public Vector3 attackDirection;
/// <summary>
/// 实体的身体部分,用于挂载动画和图像节点。
/// </summary>
public GameObject body;
/// <summary>
/// 实体所属的阵营或派系。
/// </summary>
public string affiliation;
/// <summary>
/// 表示实体是否可以被选择。
/// </summary>
public bool canSelect = true;
public string currentDimensionId;
public Vector2 Size => entityPrefab.outline.GetColliderSize();
/// <summary>
/// 表示实体是否由玩家控制。
/// </summary>
public bool PlayerControlled
{
set
{
if (value)
{
BehaviorTreeTree?.Reset();
if (Program.Instance.FocusedEntity && Program.Instance.FocusedEntity != this)
{
Program.Instance.FocusedEntity.PlayerControlled = false;
}
Program.Instance.SetFocusedEntity(this);
gameObject.tag="Player";
}
else if (Program.Instance.FocusedEntity == this)
{
Program.Instance.SetFocusedEntity(null);
gameObject.tag = "Untagged";
}
}
get => Program.Instance.FocusedEntity == this;
}
public bool IsWalking => _walkingTimer > 0;
/// <summary>
/// 获取实体当前位置。
/// </summary>
public Vector3 Position => transform.position;
/// <summary>
/// 表示实体是否已经死亡(生命值小于等于零)。
/// </summary>
public bool IsDead => AttributesNow.health <= 0;
public bool IsShowingHealthBarUI => _hitBarUIShowTimer > 0;
public bool IsAttacking => _attackTimer > 0;
private float _attackTimer;
private float _attackDetectionTime;
private WeaponResource currentAttackWeapon;
/// <summary>
/// 当实体死亡时触发的事件。
/// 只在实体首次进入死亡状态时触发一次。
/// </summary>
public event Action<Entity> OnEntityDied;
private bool _warning;
/// <summary>
/// 存储不同朝向下的身体节点对象。
/// </summary>
protected Dictionary<EntityState, Dictionary<Orientation, GameObject>> bodyNodes = new();
/// <summary>
/// 当前实体的朝向。
/// </summary>
public Orientation CurrentOrientation { get; private set; } = Orientation.Up;
/// <summary>
/// 当前实体的状态
/// </summary>
public EntityState CurrentState { get; private set; } = EntityState.Idle;
public AudioSource Audio;
public Vector3 moveTarget;
private const float TargetReachThresholdSquared = 0.001f;
public virtual bool OnTargetPoint
{
get
{
var offset = Position - moveTarget;
var distanceSquared = offset.sqrMagnitude;
return distanceSquared <= TargetReachThresholdSquared;
}
}
[SerializeField] private float _hitBarUIShowTime = 5;
private float _hitBarUIShowTimer;
protected int _walkingTimer;
private List<(Func<Entity, bool>,string)> _conditionalEvents=new();
/// <summary>
/// 初始化实体的基本属性和行为树。
/// </summary>
/// <param name="entityDefine">实体的定义数据。</param>
public virtual void Init(EntityDef entityDefine)
{
AttributesNow = new Attributes(entityDefine.attributes);
affiliation = entityDefine.affiliation?.defName;
InitBody(entityDefine.drawingOrder);
entityDef = entityDefine;
BehaviorTreeTree = BehaviorTreeUtils.ConvertToAIBase(entityDefine.behaviorTree);
BehaviorTreeTree?.Init(entityDefine.behaviorTree, this);
HideHealthBar();
InitWeaponAnimator();
InitConditionalEvents(entityDefine.eventDef?.GetAllConditionalEvents());
}
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);
weaponItem.gameObject.SetActive(true);
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>().ToList();
states.Remove(EntityState.Death);
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 (PackagesImageManager.Instance.defaultSprite)
{
targetObj = Instantiate(GameObjectCreate.ImagePrefab.gameObject, body.transform);
targetObj.name = $"{state}_{orientation}_Default";
targetObj.transform.localPosition = Vector3.zero;
var imagePrefabCom = targetObj.GetComponent<ImagePrefab>();
if (imagePrefabCom)
{
imagePrefabCom.SetSprite(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); // 激活默认朝向
}
protected void InitConditionalEvents(ConditionalEvent[] conditionalEvents)
{
if(conditionalEvents==null)return;
_conditionalEvents.Clear();
foreach (var conditionalEvent in conditionalEvents)
{
var condition = ConditionDelegateFactory.CreateConditionDelegate(conditionalEvent.parameter,
typeof(Entity), typeof(ConditionFunctions));
if(condition==null)continue;
_conditionalEvents.Add((condition, conditionalEvent.defName));
}
}
/// <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);
}
}
//条件事件控制
foreach (var conditionalEvent in _conditionalEvents)
{
if (conditionalEvent.Item1(this))
{
EventManager.Instance.Action(conditionalEvent.Item2, this);
}
}
}
/// <summary>
/// 尝试攻击目标实体。
/// </summary>
public virtual bool TryAttack() // 使用override允许子类重写
{
if (IsAttacking || IsDead) return false; // 死亡时无法攻击
// 尝试获取当前武器
var currentWeapon = GetCurrentWeapon();
if (currentWeapon == null)
{
return false;
}
StartAttack(currentWeapon);
return true;
}
private void StartAttack(WeaponResource weaponResource)
{
_attackTimer = weaponResource.AttackCooldown;
_attackDetectionTime =Mathf.Max(0,weaponResource.AttackCooldown- 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)
{
if (state == CurrentState && orientation == CurrentOrientation)
return;
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 (OnTargetPoint)
{
return; // 已到达目标点,无需进一步移动。
}
var currentPosition = Position; // 假设 Position 是获取当前位置的属性
var targetDestination = moveTarget; // 假设 moveTarget 是目标位置字段
var directionToTarget = targetDestination - currentPosition;
// 逻辑修改1计算平方距离避免开方。
var sqrDistanceToTarget = directionToTarget.sqrMagnitude;
// 逻辑修改2将到达目标点的判断改为使用平方距离并包含等于阈值的情况避免开方。
if (sqrDistanceToTarget <= TargetReachThresholdSquared)
{
transform.position = targetDestination; // 假设 transform 是 Transform 组件
return;
}
var maxMoveDistance = AttributesNow.moveSpeed * Time.deltaTime; // 假设 AttributesNow 是包含 moveSpeed 的属性
// 逻辑修改4计算最大移动距离的平方用于后续与平方距离的比较避免开方。
var maxMoveDistanceSquared = maxMoveDistance * maxMoveDistance;
Vector3 newPosition;
bool willReachTargetThisFrame;
// 逻辑修改5直接比较平方距离来确定是否能到达目标优化了之前的Mathf.Min和复杂判断。
if (maxMoveDistanceSquared >= sqrDistanceToTarget)
{
newPosition = targetDestination; // 精确捕捉到目标点
willReachTargetThisFrame = true;
}
else
{
newPosition = currentPosition + Direction * maxMoveDistance;
willReachTargetThisFrame = false;
}
transform.position = newPosition;
// 如果已到达目标,则不设置行走动画和重置计时器
if (willReachTargetThisFrame) return;
SetBodyTexture(EntityState.Walking, CurrentOrientation); // 假设 SetBodyTexture 是设置动画的方法
_walkingTimer = 2; // 假设 _walkingTimer 是一个计时器
}
public virtual void Move()
{
transform.position += Direction * (AttributesNow.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.AttributesNow.attack - AttributesNow.defense;
if (hit < 0)
hit = from.AttributesNow.attack / 100;
// 确保伤害不为负最小为0
hit = Mathf.Max(0, hit);
AttributesNow.health -= hit;
var wasFatal = IsDead; // 检查这次攻击是否导致实体死亡
BehaviorTreeTree?.Reset();
if (wasFatal)
{
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)AttributesNow.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;
}
AttributesNow.health = 0; // 直接设置生命值为0
BehaviorTreeTree?.Reset();
// 触发 OnEntityDied 事件
OnEntityDied?.Invoke(this);
ShowHealthBar();
}
/// <summary>
/// 设置实体的目标位置。
/// </summary>
/// <param name="pos">目标位置。</param>
public virtual void SetTarget(Vector3 pos)
{
Direction = (pos - transform.position).normalized;
moveTarget = pos;
if (!PlayerControlled)
{
attackDirection=Direction;
}
}
/// <summary>
/// 自动行为逻辑,根据行为树执行任务。
/// </summary>
protected virtual void AutoBehave()
{
var result=BehaviorTreeTree?.Tick();
}
/// <summary>
/// 更新玩家控制的逻辑,处理输入和移动。
/// </summary>
protected virtual void UpdatePlayerControls()
{
if (Input.GetMouseButton(0))
{
var mousePos = MousePosition.GetWorldPosition();
attackDirection = new Vector3(mousePos.x, mousePos.y) - Position;
if (weaponItem)
{
var angle= RotateTool.RotateTransformToDirection(weaponItem.transform, attackDirection);
weaponItem.Flip(false,angle is > 90 or <= -90);
}
}
if (Input.GetKeyDown(KeyCode.V))
{
weaponItem.gameObject.SetActive(!weaponItem.gameObject.activeSelf);
}
// 获取当前键盘输入状态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;
Direction = new Vector3(inputDirection.x, inputDirection.y, 0);
Move();
}
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 (EntityManager.Instance != null && Program.Instance != null)
{
EntityManager.Instance.GenerateBulletEntity(
Program.Instance.FocusedDimensionId,
weapon.Bullet,
transform.position, // 子弹的生成位置
bulletDirection, // 子弹的初始方向
this); // 子弹的发射者
}
else
{
Debug.LogError("EntityManage.Instance 或 Program.Instance 为空,无法生成子弹。请确保它们已正确初始化。");
}
}
public virtual WeaponResource GetCurrentWeapon()
{
return null;
}
public void ExecuteEvent(EntityEventType type)
{
var eventDefs = entityDef?.eventDef?.GetEventsByType(type);
if (eventDefs == null || eventDefs.Length == 0) return;
foreach (var eventDef in eventDefs)
{
EventManager.Instance.Action(eventDef,this);
}
}
}
}