Files

756 lines
29 KiB
C#
Raw Permalink 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 System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using AI;
using Base;
using Data;
using Item;
using Managers;
using Parsing;
using Prefab;
using UnityEngine;
using Utils;
namespace Entity
{
/// <summary>
/// 表示游戏中的实体类,继承自 MonoBehaviour 并实现 ITick 接口。
/// </summary>
public class Entity : MonoBehaviour, ITick
{
private const float TAN_50_DEGREES = 1.19175356925f; // Mathf.Tan(50f * Mathf.Deg2Rad)
private const float TargetReachThresholdSquared = 0.001f;
public ProgressBarPrefab healthBarPrefab;
public EntityPrefab entityPrefab;
/// <summary>
/// 手上拿着显示的贴图
/// </summary>
public GameObject weaponAnimator;
public SpriteAnimator weaponItem;
public SpriteAnimator weaponAttackAnimation;
public Vector3 attackDirection;
/// <summary>
/// 实体的身体部分,用于挂载动画和图像节点。
/// </summary>
public GameObject body;
/// <summary>
/// 实体所属的阵营或派系。
/// </summary>
public string affiliation;
/// <summary>
/// 表示实体是否可以被选择。
/// </summary>
public bool canSelect = true;
public string currentDimensionId;
public AudioSource Audio;
public Vector3 moveTarget;
[SerializeField] private float _hitBarUIShowTime = 5;
private readonly List<(Func<Entity, bool>, string)> _conditionalEvents = new();
private float _attackDetectionTime;
private float _attackTimer;
// /// <summary>
// /// 当前实体正在执行的任务。
// /// </summary>
// public JobBase currentJob;
private Attributes _attribute;
private Attributes _defAttributes;
private Vector3 _direction;
private float _hitBarUIShowTimer;
protected int _walkingTimer;
private bool _warning;
/// <summary>
/// 存储不同朝向下的身体节点对象。
/// </summary>
protected Dictionary<EntityState, Dictionary<Orientation, GameObject>> bodyNodes = new();
private WeaponResource currentAttackWeapon;
public EntityDef entityDef;
/// <summary>
/// 人工智能行为树,定义实体的行为逻辑。
/// </summary>
public BehaviorTreeBase BehaviorTreeTree { get; private set; }
/// <summary>
/// 实体的属性定义,包括生命值、攻击力、防御力等。
/// </summary>
public virtual Attributes AttributesNow
{
get { return _attribute ??= new Attributes(BaseAttributes); }
protected set
{
if (_attribute != null)
_attribute = Attributes.Min(_attribute, value);
else
_attribute = value;
gameObject.SetActive(!IsDead);
}
}
public virtual Attributes BaseAttributes => DefAttributes;
public virtual Attributes DefAttributes
{
get
{
return _defAttributes ??= entityDef == null ? new Attributes() : new Attributes(entityDef.attributes);
}
}
public Vector3 Direction
{
get => _direction;
set
{
// 确保方向向量是单位向量
_direction = Mathf.Approximately(value.sqrMagnitude, 1) ? value : value.normalized;
Orientation newOrientation;
// 获取方向向量的绝对X和Y值用于比较
var absX = Mathf.Abs(Direction.x);
var absY = Mathf.Abs(Direction.y);
// 检查当前方向,并根据切换角度决定新的方向
// 这里体现了滞后Hysteresis逻辑
// 从上下切换到左右时45°切换左右优先
if (CurrentOrientation is Orientation.Up or Orientation.Down)
{
// 如果X的绝对值大于Y的绝对值与X轴夹角小于45度则切换到左右
if (absX > absY)
newOrientation = Direction.x > 0 ? Orientation.Right : Orientation.Left;
else
// 否则与X轴夹角大于等于45度保持上下
newOrientation = Direction.y > 0 ? Orientation.Up : Orientation.Down;
}
// 修改部分二:替换原 else 分支中的角度计算使用高效的XY分量比较
else // 当前方向是 Orientation.Left or Orientation.Right
{
// 从左右切换到上下时,需要更强的垂直分量。
// 原始逻辑要求角度 >= 50度或 <= 130度对于'Up'
// 这等同于 absY / absX >= tan(50度)。
// 使用 absY >= absX * TAN_50_DEGREES 进行高效比较,并避免除零问题。
if (absY >= absX * TAN_50_DEGREES)
newOrientation = Direction.y > 0 ? Orientation.Up : Orientation.Down;
else // 垂直分量不足,保持左右
newOrientation = Direction.x > 0 ? Orientation.Right : Orientation.Left;
}
SetBodyTexture(CurrentState, newOrientation);
}
}
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;
public float _attackingAnimationEndTime;
/// <summary>
/// 当前实体的朝向。
/// </summary>
public Orientation CurrentOrientation { get; private set; } = Orientation.Up;
/// <summary>
/// 当前实体的状态
/// </summary>
public EntityState CurrentState { get; private set; } = EntityState.Idle;
public virtual bool OnTargetPoint
{
get
{
var offset = Position - moveTarget;
var distanceSquared = offset.sqrMagnitude;
return distanceSquared <= TargetReachThresholdSquared;
}
}
/// <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 <= _attackingAnimationEndTime)
SetBodyTexture(EntityState.Idle, CurrentOrientation);
}
//条件事件控制
foreach (var conditionalEvent in _conditionalEvents)
if (conditionalEvent.Item1(this))
EventManager.Instance.Action(conditionalEvent.Item2, this);
}
public event Action<Entity> OnEntityDiedEvent;
public event Action<Entity> OnAttackEvent;
public event Action<Entity> OnHitEvent;
public event Action<Entity, Entity> OnDealHitEvent;
/// <summary>
/// 初始化实体的基本属性和行为树。
/// </summary>
/// <param name="entityDefine">实体的定义数据。</param>
public virtual void Init(EntityDef entityDefine)
{
entityDef = entityDefine;
AttributesNow = BaseAttributes;
affiliation = entityDefine.affiliation?.defName;
InitBody(entityDefine.drawingOrder);
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 || !currentWeapon.SelfWeaponDef.showInHand)
return;
weaponItem = AnimationUtils.SpriteAnimator(currentWeapon.Icon.ToArray(), weaponAnimator.transform);
weaponItem.SetFPS(currentWeapon.FPS);
weaponItem.gameObject.SetActive(true);
weaponAttackAnimation = AnimationUtils.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(AnimationUtils.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 = AnimationUtils.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 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);
_attackingAnimationEndTime = MathF.Max(0, weaponResource.AttackCooldown - weaponResource.AttackAnimationTime);
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 || AttributesNow.moveSpeed <= 0) 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;
hit += (int)from.AttributesNow.totalDamageTakenAbsoluteOffset;
hit = (int)(hit * (1 + from.AttributesNow.totalDamageTakenPercentOffset));
OnHit(hit);
}
public virtual void OnHit(int hit, bool showAnimation = true)
{
hit += (int)AttributesNow.totalDamageTakenAbsoluteOffset;
hit = (int)(hit * (1 + AttributesNow.totalDamageTakenPercentOffset));
hit = Mathf.Max(0, hit);
AttributesNow.health -= hit;
BehaviorTreeTree?.Reset();
OnHitCallback();
if (IsDead)
OnDiedCallback();
if (Setting.Instance.CurrentSettings.showHealthBarByHit && showAnimation) ShowHealthBar();
if (Setting.Instance.CurrentSettings.showHitNumber && showAnimation)
{
var textObj = TemporaryAnimationManager.Instance.GenerateTemporaryAnimation(hit.ToString(), Position,
0.5f, 1, null,
(f => MathF.Log(f * 100 + 1)));
textObj.text.fontSize = 5;
}
}
public void OnHitCallback()
{
OnHitEvent?.Invoke(this);
if (entityDef?.eventDef?.onHitEvents != null)
{
foreach (var onHitEvent in entityDef.eventDef.onHitEvents)
{
EventManager.Instance.Action(onHitEvent, this);
}
}
}
public void OnDiedCallback()
{
OnEntityDiedEvent?.Invoke(this);
if (entityDef?.eventDef?.onDeathEvents != null)
{
foreach (var eventDef in entityDef.eventDef.onDeathEvents)
{
EventManager.Instance.Action(eventDef, this);
}
}
}
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();
OnDiedCallback();
}
public void OnDealHit(Entity target)
{
OnDealHitEvent?.Invoke(this, target);
}
/// <summary>
/// 设置实体的目标位置。
/// </summary>
/// <param name="pos">目标位置。</param>
public virtual bool SetTarget(Vector3 pos)
{
Direction = (pos - transform.position).normalized;
moveTarget = pos;
return true;
}
/// <summary>
/// 自动行为逻辑,根据行为树执行任务。
/// </summary>
protected virtual void AutoBehave()
{
var result = BehaviorTreeTree?.Tick();
}
/// <summary>
/// 更新玩家控制的逻辑,处理输入和移动。
/// </summary>
protected virtual void UpdatePlayerControls()
{
if (EntityManager.Instance.ExistsHostileInSightRange(currentDimensionId, entityPrefab,
AttributesNow.attackRange))
{
attackDirection =
EntityManager.Instance
.FindNearestEntityByRelation(currentDimensionId, entityPrefab, Relation.Hostile).Position -
Position;
if (weaponItem && weaponItem.gameObject.activeInHierarchy)
{
var angle = RotateTool.RotateTransformToDirection(weaponItem.transform, attackDirection);
weaponItem.Flip(false, angle is > 90 or <= -90);
}
TryAttack();
}
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 (inputDirection == Vector2.zero) return;
// 归一化方向向量,确保对角线移动速度一致
inputDirection = inputDirection.normalized;
Direction = new Vector3(inputDirection.x, inputDirection.y, 0);
Move();
}
protected virtual void ExecuteMeleeAttack(WeaponResource weapon)
{
var attackRange = weapon.Attributes.attackRange;
var attackTargetCount = weapon.Attributes.attackTargetCount;
var hits = EntityManager.FindEntity<LivingEntity>(Position, attackRange);
foreach (var hit in hits)
{
if (attackTargetCount <= 0) break; // 已达到最大攻击目标数
if (hit.gameObject == gameObject) continue; // 不攻击自己
var relation = AffiliationManager.Instance.GetRelation(affiliation, hit.affiliation);
if (!Setting.Instance.CurrentSettings.friendlyFire && relation != Relation.Hostile) continue;
hit.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 && 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);
}
public void OffsetAttribute(AttributesOffsetDef offsetDef)
{
AttributesNow = AttributesNow.GetModifiedAttributes(offsetDef);
}
}
}