Files
gold_dolphin/unity/Assets/enemy/EnemyAI.cs
2026-06-27 22:21:16 +08:00

223 lines
7.7 KiB
C#
Raw 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 UnityEngine;
namespace IndianOceanAssets.Engine2_5D
{
/// <summary>
/// 敌人AI —— 检测/追击/脱离持续/摇铃聆听响应。
///
/// 状态流程:
/// - Idle站岗。主角进入 detectionRange → Chasing
/// - Chasing追击主角。主角离开 detectionRange → ChasingPersist
/// - ChasingPersist脱离后继续追击 chasePersistTime 秒。
/// 主角重新进入范围 → 回到 Chasing超时 → Idle停在当前位置不回出生点
/// - BellResponding主角在 listenRange 内按E摇铃时触发
/// 朝主角方向移动 bellMoveTime 秒(距离 = 速度 × 时间),然后停下 → Idle
///
/// 挂载位置:敌人预制体上
/// 需要:场景中有 EchoSystem用于摇铃事件、玩家物体带 "Player" 标签
/// (或手动把玩家拖到 playerTarget 字段)
/// </summary>
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);
}
/// <summary>当前状态(调试用)</summary>
public string CurrentState => _state.ToString();
}
}