using System; using System.Collections; using System.Collections.Generic; 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 IsShowingOfHitBarUI=>hitBarUIShowTimer > 0; public bool IsAttacking => attackCoroutine != null; private bool _isPlayerControlled = false; private bool _warning = false; /// /// 存储不同朝向下的动画节点集合。 /// public Dictionary> bodyAnimationNode = new(); /// /// 存储不同朝向下的身体节点对象。 /// private Dictionary bodyNodes = new(); /// /// 当前实体的朝向。 /// private Orientation currentOrientation = Orientation.Down; /// /// 攻击动画的持续时间(秒)。 /// 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) { // 定义方向枚举和对应的 GetDrawingOrder 调用 Orientation[] orientations = { Orientation.Down, Orientation.Up, Orientation.Left, Orientation.Right }; foreach (var orientation in orientations) { currentOrientation = orientation; bodyAnimationNode[orientation] = new(); // 获取当前方向的绘图节点 var drawNode = drawingOrder.GetDrawingOrder(orientation, out var realOrientation); if (drawNode == null) continue; var directionRoot = new GameObject(orientation.ToString()); directionRoot.transform.SetParent(body.transform, false); InitBodyPart(drawNode, directionRoot, drawingOrder.texturePath,realOrientation); bodyNodes[orientation] = directionRoot; } currentOrientation = Orientation.Down; foreach (var bodyNode in bodyNodes) { bodyNode.Value.SetActive(false); } SetOrientation(Orientation.Down); } /// /// 递归初始化单个绘图节点及其子节点。 /// /// 绘图节点定义。 /// 父节点对象。 /// 纹理资源路径。 public virtual void InitBodyPart(DrawNodeDef drawNode, GameObject parent, string folderPath,Orientation realOrientation) { if (drawNode == null) return; GameObject nodeObject; if (drawNode.nodeName == "noName") { nodeObject = new(); nodeObject.transform.SetParent(parent.transform); } else { switch (drawNode.drawNodeType) { case DrawNodeType.Image: nodeObject = Instantiate(imagePrefab.gameObject, parent.transform); var texture = Managers.PackagesImageManager.Instance.FindBodyTextures(drawNode.packID, folderPath, $"{drawNode.nodeName}_{realOrientation}"); var image = nodeObject.GetComponent(); image.SetSprite(texture.Length > 0 ? texture[0] : Managers.PackagesImageManager.Instance.defaultSprite); break; case DrawNodeType.Animation: nodeObject = Instantiate(animatorPrefab.gameObject, parent.transform); ITick tick = nodeObject.GetComponent(); if (tick != null) bodyAnimationNode[currentOrientation].Add(tick); var textures = Managers.PackagesImageManager.Instance.FindBodyTextures(drawNode.packID, folderPath, $"{drawNode.nodeName}_{realOrientation}"); var animator = nodeObject.GetComponent(); animator.SetSprites(textures); break; default: throw new ArgumentOutOfRangeException(); } } nodeObject.transform.localPosition = drawNode.position; nodeObject.name = drawNode.nodeName; // 递归初始化子节点 foreach (var child in drawNode.children) { InitBodyPart(child, nodeObject, folderPath,realOrientation); } } /// /// 更新实体的逻辑,包括玩家控制和自动行为。 /// public void Tick() { if (_isPlayerControlled) { UpdatePlayerControls(); } else { AutoBehave(); } if (bodyAnimationNode.TryGetValue(currentOrientation, out var ticks)) { foreach (var tick in ticks) { tick.Tick(); } } if (IsShowingOfHitBarUI) { hitBarUIShowTimer -= Time.deltaTime; if (hitBarUIShowTimer <= 0) { HideHealthBar(); } } } /// /// 尝试攻击目标实体。 /// public virtual void TryAttack() { if(!IsAttacking) attackCoroutine = StartCoroutine(AttackFlow()); } /// /// 设置实体的朝向。 /// /// 新的朝向。 public virtual void SetOrientation(Orientation orientation) { // 禁用当前朝向的节点 if (bodyNodes.TryGetValue(currentOrientation, out var currentNode)) { currentNode.SetActive(false); } // 设置新的朝向 currentOrientation = orientation; // 激活新朝向的节点 if (bodyNodes.TryGetValue(orientation, out var newNode)) { newNode.SetActive(true); } } /// /// 根据方向尝试移动实体。 /// 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; } SetOrientation(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--; } } } }