using UnityEngine; namespace IndianOceanAssets.Engine2_5D { /// /// 敌人AI —— 检测/追击/脱离持续/摇铃聆听响应。 /// /// 状态流程: /// - Idle:站岗。主角进入 detectionRange → Chasing /// - Chasing:追击主角。主角离开 detectionRange → ChasingPersist /// - ChasingPersist:脱离后继续追击 chasePersistTime 秒。 /// 主角重新进入范围 → 回到 Chasing;超时 → Idle(停在当前位置,不回出生点) /// - BellResponding:主角在 listenRange 内按E摇铃时触发, /// 朝主角方向移动 bellMoveTime 秒(距离 = 速度 × 时间),然后停下 → Idle /// /// 挂载位置:敌人预制体上 /// 需要:场景中有 EchoSystem(用于摇铃事件)、玩家物体带 "Player" 标签 /// (或手动把玩家拖到 playerTarget 字段) /// public class EnemyAI : MonoBehaviour { [Header("目标")] [Tooltip("玩家目标(留空则自动查找 Player 标签)")] [SerializeField] private Transform playerTarget; [Header("检测与追击")] [Tooltip("发现主角的范围")] [SerializeField] private float detectionRange = 8f; [Tooltip("追击速度")] [SerializeField] private float chaseSpeed = 3f; [Tooltip("靠近主角到此距离时停下")] [SerializeField] private float stopDistance = 1.2f; [Tooltip("主角脱离检测范围后,继续追击的时间(秒)")] [SerializeField] private float chasePersistTime = 3f; [Header("聆听(摇铃响应)")] [Tooltip("聆听范围:主角在此范围内按E摇铃时,敌人会朝主角方向移动")] [SerializeField] private float listenRange = 15f; [Tooltip("摇铃响应移动速度")] [SerializeField] private float bellMoveSpeed = 4f; [Tooltip("摇铃响应移动时间(距离 = 速度 × 时间)")] [SerializeField] private float bellMoveTime = 1.5f; private enum State { Idle, Chasing, ChasingPersist, BellResponding } private State _state = State.Idle; private float _stateTimer; private Vector3 _bellDirection; private void Start() { if (playerTarget == null) { var player = GameObject.FindWithTag("Player"); if (player != null) playerTarget = player.transform; } } private void OnEnable() { EchoSystem.OnEchoReleased += OnBell; } private void OnDisable() { EchoSystem.OnEchoReleased -= OnBell; } private void Update() { switch (_state) { case State.Idle: UpdateIdle(); break; case State.Chasing: UpdateChasing(); break; case State.ChasingPersist: UpdateChasingPersist(); break; case State.BellResponding: UpdateBellResponding(); break; } } // ===== Idle:站岗,等主角进入检测范围 ===== private void UpdateIdle() { if (playerTarget == null) return; if (XZDistance(transform.position, playerTarget.position) <= detectionRange) { _state = State.Chasing; } } // ===== Chasing:追击主角 ===== private void UpdateChasing() { if (playerTarget == null) return; float dist = XZDistance(transform.position, playerTarget.position); // 主角脱离检测范围 → 继续追击一段时间 if (dist > detectionRange) { _state = State.ChasingPersist; _stateTimer = chasePersistTime; return; } // 靠近到停止距离时停下(但仍处于追击状态) if (dist > stopDistance) MoveToward(playerTarget.position, chaseSpeed); } // ===== ChasingPersist:脱离后继续追击,倒计时 ===== private void UpdateChasingPersist() { if (playerTarget == null) return; _stateTimer -= Time.deltaTime; // 超时 → 停在当前位置,回到 Idle(不回出生点) if (_stateTimer <= 0f) { _state = State.Idle; return; } float dist = XZDistance(transform.position, playerTarget.position); // 主角重新进入检测范围 → 回到正常追击 if (dist <= detectionRange) { _state = State.Chasing; return; } if (dist > stopDistance) MoveToward(playerTarget.position, chaseSpeed); } // ===== BellResponding:摇铃响应,朝主角方向移动固定时间 ===== private void UpdateBellResponding() { _stateTimer -= Time.deltaTime; if (_stateTimer <= 0f) { _state = State.Idle; return; } // 沿摇铃时刻的方向移动(距离 = 速度 × 时间) Vector3 move = _bellDirection * bellMoveSpeed * Time.deltaTime; transform.position += new Vector3(move.x, 0f, move.z); } // ===== 摇铃事件回调(由 EchoSystem.OnEchoReleased 触发)===== private void OnBell(Vector3 bellPos) { if (playerTarget == null) return; // 只有在聆听范围内才响应 if (XZDistance(transform.position, bellPos) > listenRange) return; // 计算朝主角方向(摇铃时刻的位置) _bellDirection = new Vector3( bellPos.x - transform.position.x, 0f, bellPos.z - transform.position.z ); if (_bellDirection.sqrMagnitude > 0.001f) _bellDirection.Normalize(); else _bellDirection = transform.forward; // 重叠时用当前朝向 // 摇铃响应可打断任何当前状态 _state = State.BellResponding; _stateTimer = bellMoveTime; } // ===== 工具方法 ===== private void MoveToward(Vector3 target, float speed) { Vector3 dir = new Vector3( target.x - transform.position.x, 0f, target.z - transform.position.z ); float dist = dir.magnitude; if (dist < 0.01f) return; dir /= dist; Vector3 move = dir * speed * Time.deltaTime; if (move.magnitude > dist) move = dir * dist; // 不越过目标 transform.position += new Vector3(move.x, 0f, move.z); } private float XZDistance(Vector3 a, Vector3 b) { float dx = a.x - b.x; float dz = a.z - b.z; return Mathf.Sqrt(dx * dx + dz * dz); } // ===== 编辑器可视化 ===== private void OnDrawGizmosSelected() { // 检测范围(黄色) Gizmos.color = new Color(1f, 0.85f, 0f, 0.6f); Gizmos.DrawWireSphere(transform.position, detectionRange); // 聆听范围(青色) Gizmos.color = new Color(0f, 0.85f, 1f, 0.6f); Gizmos.DrawWireSphere(transform.position, listenRange); // 停止距离(红色) Gizmos.color = new Color(1f, 0.3f, 0.3f, 0.6f); Gizmos.DrawWireSphere(transform.position, stopDistance); } /// 当前状态(调试用) public string CurrentState => _state.ToString(); } }