using System; using System.Collections; using System.Collections.Generic; using System.Linq; using AI; using Base; using Data; using Prefab; using UnityEngine; using UnityEngine.Serialization; namespace Entity { /// /// 表示游戏中的实体类,继承自 MonoBehaviour 并实现 ITick 接口。 /// public class Entity : MonoBehaviour, ITick { /// /// 动画预制体,用于管理实体的动画逻辑。 /// public SpriteAnimator animatorPrefab; /// /// 图像预制体,用于管理实体的静态图像显示。 /// public ImagePrefab imagePrefab; public ProgressBarPrefab healthBarPrefab; /// /// 人工智能行为树,定义实体的行为逻辑。 /// public AIBase aiTree; /// /// 当前实体正在执行的任务。 /// public JobBase currentJob; /// /// 实体的属性定义,包括生命值、攻击力、防御力等。 /// public AttributesDef attributes = new(); /// /// 实体当前的移动方向。 /// public Vector3 direction; /// /// 实体的身体部分,用于挂载动画和图像节点。 /// public GameObject body; /// /// 实体所属的阵营或派系。 /// public string affiliation; /// /// 表示实体是否可以被选择。 /// public bool canSelect = true; /// /// 表示实体是否处于追逐状态(影响移动速度)。 /// public bool IsChase { set; get; } = true; /// /// 表示实体是否由玩家控制。 /// public bool PlayerControlled { set { if (value) { IsChase = true; currentJob = null; } _isPlayerControlled = value; } get => _isPlayerControlled; } /// /// 获取实体当前位置。 /// public Vector3 Position => transform.position; /// /// 表示实体是否已经死亡(生命值小于等于零)。 /// public bool IsDead => attributes.health <= 0; public bool IsShowingHealthBarUI=>hitBarUIShowTimer > 0; public bool IsAttacking => attackCoroutine != null; private bool _isPlayerControlled = false; 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; protected EntityDef entityDef; public float hitBarUIShowTime = 5; private float hitBarUIShowTimer = 0; /// /// 初始化实体的基本属性和行为树。 /// /// 实体的定义数据。 public virtual void Init(EntityDef entityDef) { attributes = entityDef.attributes.Clone(); 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; // 提取动画组件(安全处理空组件情况) var animators = targetObj.GetComponentsInChildren(); if (animators.Length > 0) { stateAnimNodes[orientation] = animators.Cast().ToList(); } // 无动画组件时保持 stateAnimNodes[orientation] 为 null(符合原始逻辑) } } // 批量隐藏所有节点(使用字典值集合直接操作) 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.animationTextures?.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.animationTextures[0]); if (!texture) { Debug.LogWarning( $"InitBodyPart: 无法获取纹理 (节点名: {drawNode.nodeName}, 纹理ID: {drawNode.animationTextures[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.animationTextures) { 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 (_isPlayerControlled) { 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)) { if (animationNode.TryGetValue(currentOrientation, out var value)) { currentAnimatorCache=value; } } } /// /// 根据方向尝试移动实体。 /// public virtual void TryMove() { if (IsAttacking) return; transform.position += direction * (attributes.moveSpeed * Time.deltaTime * (IsChase ? 1 : 0.5f)); } /// /// 处理实体受到攻击的逻辑。 /// /// 攻击来源实体。 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--; } } } }