using System; using System.Collections; using System.Collections.Generic; using System.Linq; using AI; using Base; using Data; using Prefab; using UnityEngine; namespace Entity { /// /// 表示游戏中的实体类,继承自 MonoBehaviour 并实现 ITick 接口。 /// public class Entity : MonoBehaviour, ITick { /// /// 动画预制体,用于管理实体的动画逻辑。 /// public SpriteAnimator animatorPrefab; /// /// 图像预制体,用于管理实体的静态图像显示。 /// public ImagePrefab imagePrefab; public ProgressBarPrefab healthBarPrefab; public EntityPrefab entityPrefab; public EntityDef entityDef; /// /// 人工智能行为树,定义实体的行为逻辑。 /// public AIBase aiTree; /// /// 当前实体正在执行的任务。 /// public JobBase currentJob; /// /// 实体的属性定义,包括生命值、攻击力、防御力等。 /// public Attributes attributes=new(); /// /// 实体当前的移动方向。 /// public Vector3 direction; /// /// 实体的身体部分,用于挂载动画和图像节点。 /// public GameObject body; /// /// 实体所属的阵营或派系。 /// public string affiliation; /// /// 表示实体是否可以被选择。 /// public bool canSelect = true; /// /// 表示实体是否处于追逐状态(影响移动速度)。 /// public bool IsChase { set; get; } = true; public string currentDimensionId = null; /// /// 表示实体是否由玩家控制。 /// public bool PlayerControlled { set { if (value) { IsChase = true; currentJob = null; // 逻辑修改:只有当存在一个不同的焦点实体时,才将其PlayerControlled设为false if (Program.Instance.focusedEntity && Program.Instance.focusedEntity != this) { Program.Instance.focusedEntity.PlayerControlled = false; } Program.Instance.focusedEntity = this; } // 逻辑修改:确保只有当自身是焦点实体时才取消焦点,避免不必要的逻辑执行 else if (Program.Instance.focusedEntity == this) { Program.Instance.focusedEntity = null; } } get => Program.Instance.focusedEntity == this; } public bool IsWalking => _walkingTimer > 0; /// /// 获取实体当前位置。 /// public Vector3 Position => transform.position; /// /// 表示实体是否已经死亡(生命值小于等于零)。 /// public bool IsDead => attributes.health <= 0; public bool IsShowingHealthBarUI=>_hitBarUIShowTimer > 0; public bool IsAttacking => _attackCoroutine != null; private bool _warning = false; /// /// 存储不同朝向下的动画节点集合。 /// public Dictionary>> bodyAnimationNode = new(); private List _currentAnimatorCache=new (); /// /// 存储不同朝向下的身体节点对象。 /// private Dictionary> bodyNodes = new(); /// /// 当前实体的朝向。 /// private Orientation _currentOrientation = Orientation.Down; /// /// 当前实体的状态 /// private EntityState _currentState = EntityState.Idle; /// /// 攻击动画的持续时间(秒)。 /// private const float AttackAnimationDuration = 0.1f; /// /// 抖动的偏移量。 /// private const float ShakeOffset = 0.5f; // 协程引用 private Coroutine _attackCoroutine; [SerializeField] private float _hitBarUIShowTime = 5; private float _hitBarUIShowTimer = 0; private int _walkingTimer = 0; /// /// 初始化实体的基本属性和行为树。 /// /// 实体的定义数据。 public virtual void Init(EntityDef entityDef) { attributes = new Attributes(entityDef.attributes); aiTree = Utils.BehaviorTree.ConvertToAIBase(entityDef.behaviorTree); affiliation = entityDef.affiliation?.defName; InitBody(entityDef.drawingOrder); this.entityDef = entityDef; HideHealthBar(); } /// /// 初始化实体的身体部分,包括不同朝向下的绘图节点。 /// /// 绘制顺序定义。 public virtual void InitBody(DrawingOrderDef drawingOrder) { // 预缓存枚举值(避免每次循环重复调用 Enum.GetValues) var states = Enum.GetValues(typeof(EntityState)).Cast().ToArray(); var orientations = Enum.GetValues(typeof(Orientation)).Cast().ToArray(); // 预初始化字典结构(减少内层循环的字典检查) foreach (var state in states) { bodyNodes.TryAdd(state, new Dictionary()); bodyAnimationNode.TryAdd(state, new Dictionary>()); } // 主初始化逻辑 foreach (var state in states) { var stateBodyNodes = bodyNodes[state]; var stateAnimNodes = bodyAnimationNode[state]; foreach (var orientation in orientations) { // 获取节点定义(避免重复调用) var nodeDef = drawingOrder.GetDrawNodeDef(state, orientation, out var original); // 处理空节点定义(直接创建空对象) if (nodeDef == null) { var obj = new GameObject { name = $"{state}_Empty" }; obj.transform.SetParent(body.transform, false); stateBodyNodes[orientation] = obj; continue; // 跳过后续动画处理 } // 处理有效节点定义 GameObject targetObj; if (original.HasValue && stateBodyNodes.TryGetValue(original.Value, out var reusedObj)) { targetObj = reusedObj; // 复用已有对象 } else { targetObj = InitBodyPart(nodeDef, body); // 创建新对象 } stateBodyNodes[orientation] = targetObj; // 逻辑修改:确保 stateAnimNodes[orientation] 总是被初始化为一个列表 var animatorsForOrientation = new List(); // 总是创建一个新的列表 var animators = targetObj.GetComponentsInChildren(); if (animators.Length > 0) { animatorsForOrientation.AddRange(animators); } stateAnimNodes[orientation] = animatorsForOrientation; } } // 批量隐藏所有节点(使用字典值集合直接操作) foreach (var nodeDict in bodyNodes.Values) { foreach (var obj in nodeDict.Values) { obj.SetActive(false); } } SetBodyTexture(EntityState.Idle,Orientation.Down); // 激活默认朝向 } /// /// 递归初始化单个绘图节点及其子节点,具有更强的健壮性和错误处理。 /// /// 绘图节点定义。 /// 父节点对象。 /// 创建的GameObject,如果失败则返回null public virtual GameObject InitBodyPart(DrawNodeDef drawNode, GameObject parent) { try { // 参数验证 if (drawNode == null) { Debug.LogWarning("InitBodyPart: drawNode参数为null"); return null; } if (parent == null) { Debug.LogWarning($"InitBodyPart: 父节点为null (节点名: {drawNode.nodeName})"); return null; } GameObject nodeObject = null; // 根据纹理数量创建不同类型的节点 switch (drawNode.textures?.Count ?? 0) { case 0: // 无纹理节点 nodeObject = new GameObject(drawNode.nodeName); nodeObject.transform.SetParent(parent.transform, false); break; case 1: // 单纹理节点 if (imagePrefab == null) { Debug.LogError($"InitBodyPart: imagePrefab未设置 (节点名: {drawNode.nodeName})"); return null; } nodeObject = Instantiate(imagePrefab.gameObject, parent.transform); var texture = Managers.PackagesImageManager.Instance?.GetSprite(drawNode.packID, drawNode.textures[0]); if (!texture) { Debug.LogWarning( $"InitBodyPart: 无法获取纹理 (节点名: {drawNode.nodeName}, 纹理ID: {drawNode.textures[0]})"); } var imagePrefabCom = nodeObject.GetComponent(); if (imagePrefabCom != null) { imagePrefabCom.SetSprite(texture); } else { Debug.LogWarning($"InitBodyPart: 无法获取ImagePrefab组件 (节点名: {drawNode.nodeName})"); } break; default: // 多纹理动画节点 if (!animatorPrefab) { Debug.LogError($"InitBodyPart: animatorPrefab未设置 (节点名: {drawNode.nodeName})"); return null; } nodeObject = Instantiate(animatorPrefab.gameObject, parent.transform); var animator = nodeObject.GetComponent(); if (animator == null) { Debug.LogWarning($"InitBodyPart: 无法获取SpriteAnimator组件 (节点名: {drawNode.nodeName})"); break; } animator.SetFPS(drawNode.FPS); var animatedSprites = new List(); foreach (var textureId in drawNode.textures) { try { var sprite = Managers.PackagesImageManager.Instance?.GetSprite(drawNode.packID, textureId); if (sprite != null) { animatedSprites.Add(sprite); } else { Debug.LogWarning( $"InitBodyPart: 无法获取动画纹理 (节点名: {drawNode.nodeName}, 纹理ID: {textureId})"); } } catch (Exception ex) { Debug.LogError( $"InitBodyPart: 加载动画纹理时出错 (节点名: {drawNode.nodeName}, 纹理ID: {textureId}): {ex.Message}"); } } if (animatedSprites.Count > 0) { animator.SetSprites(animatedSprites.ToArray()); } else { Debug.LogWarning($"InitBodyPart: 没有有效的动画纹理 (节点名: {drawNode.nodeName})"); } break; } // 设置节点属性 if (!nodeObject) return nodeObject; nodeObject.transform.localPosition = drawNode.position; nodeObject.name = drawNode.nodeName ?? "UnnamedNode"; // 递归初始化子节点 if (drawNode.nodes == null) return nodeObject; foreach (var child in drawNode.nodes) { try { InitBodyPart(child, nodeObject); } catch (Exception ex) { Debug.LogError($"InitBodyPart: 初始化子节点失败 (父节点: {drawNode.nodeName}): {ex.Message}"); } } return nodeObject; } catch (Exception ex) { Debug.LogError($"InitBodyPart: 初始化节点时发生未处理的异常 (节点名: {drawNode?.nodeName}): {ex}"); return null; } } /// /// 更新实体的逻辑,包括玩家控制和自动行为。 /// public void Tick() { if (_walkingTimer > 0) { _walkingTimer -= 1; if (_walkingTimer <= 0) { SetBodyTexture(EntityState.Idle, _currentOrientation); } } if (PlayerControlled) { UpdatePlayerControls(); } else { AutoBehave(); } if (_currentAnimatorCache!=null) { foreach (var animator in _currentAnimatorCache) { animator.Tick(); } } if (IsShowingHealthBarUI) { _hitBarUIShowTimer -= Time.deltaTime; if (_hitBarUIShowTimer <= 0) { HideHealthBar(); } } } /// /// 尝试攻击目标实体。 /// public virtual void TryAttack() { if(!IsAttacking) _attackCoroutine = StartCoroutine(AttackFlow()); } public virtual void SetBodyTexture(EntityState state, Orientation orientation) { if (bodyNodes.TryGetValue(_currentState, out var stateNode)) { if (stateNode.TryGetValue(_currentOrientation, out var node)) { node.SetActive(false); } } if (bodyNodes.TryGetValue(state, out var showStateNode)) { if (showStateNode.TryGetValue(orientation, out var showNode)) { showNode.SetActive(true); } } _currentState = state; _currentOrientation = orientation; if (bodyAnimationNode.TryGetValue(_currentState, out var animationNode) && animationNode.TryGetValue(_currentOrientation, out var value)) { _currentAnimatorCache = value; } else { _currentAnimatorCache = new List(); // 如果没有找到动画,则使用空列表 } } /// /// 根据方向尝试移动实体。 /// public virtual void TryMove() { if (IsAttacking) return; transform.position += direction * (attributes.moveSpeed * Time.deltaTime * (IsChase ? 1 : 0.5f)); SetBodyTexture(EntityState.Walking,_currentOrientation); _walkingTimer = 2; } /// /// 处理实体受到攻击的逻辑。 /// /// 攻击来源实体。 public virtual void OnHit(Entity from) { var hit = from.attributes.attack - attributes.defense; if (hit < 0) hit = from.attributes.attack / 100; attributes.health -= hit; currentJob?.StopJob(); ShowHealthBar(); } 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); } /// /// 杀死实体,设置生命值为零。 /// public virtual void Kill() { attributes.health = 0; } /// /// 设置实体的目标位置。 /// /// 目标位置。 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); } /// /// 自动行为逻辑,根据行为树执行任务。 /// 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(); } /// /// 更新玩家控制的逻辑,处理输入和移动。 /// protected virtual void UpdatePlayerControls() { // 检测 Shift 键状态 var isHoldingShift = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); IsChase = !isHoldingShift; // 按住 Shift 时 IsChase = false,否则 true // 获取当前键盘输入状态(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.GetMouseButtonDown(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(); } // 攻击流程协程 IEnumerator AttackFlow() { // 播放攻击动画并获取动画持续时间 var animationDuration = PlayAttackAnimation(); // 等待动画执行完毕 yield return new WaitForSeconds(animationDuration); // 调用检测并攻击敌人的方法 DetectAndAttackEnemies(); // 攻击流程结束,清理协程引用 _attackCoroutine = null; } /// /// 播放攻击动画。 /// /// 开始检测攻击范围内敌人的时间。 public float PlayAttackAnimation() { // 启动协程来执行攻击动画 StartCoroutine(ShakeInDirectionCoroutine()); // 返回检测敌人的起始时间 return AttackAnimationDuration; } private IEnumerator ShakeInDirectionCoroutine() { var originalPosition = transform.position; // 记录原始位置 transform.position += direction * ShakeOffset; yield return new WaitForSeconds(AttackAnimationDuration); transform.position = originalPosition; } public void DetectAndAttackEnemies() { var attackCount = attributes.attackTargetCount; // 获取攻击范围内的所有碰撞体 var hits = Physics2D.OverlapCircleAll( transform.position, attributes.attackRange, LayerMask.GetMask("Entity")); foreach (var hit in hits) { if (attackCount <= 0) break; // 检查是否是自身(额外安全措施) if (hit.gameObject == this.gameObject) continue; // 获取Entity组件 var entity = hit.GetComponent(); if (!entity) continue; // 执行攻击 entity.OnHit(this); attackCount--; } } } }