添加mcp服务器

优化敌人AI,修改成双重规则寻敌
This commit is contained in:
2026-06-28 23:08:20 +08:00
parent 3da879f58d
commit 6cc4f1035d
9 changed files with 473 additions and 116 deletions

View File

@@ -3,15 +3,13 @@ using UnityEngine;
namespace IndianOceanAssets.Engine2_5D
{
/// <summary>
/// 敌人AI —— 检测/追击/脱离持续/摇铃聆听响应
/// 敌人AI v2 —— 双重寻敌机制(视觉检测 + 铃铛响应
///
/// 状态流程:
/// - Idle站岗。主角进入 detectionRange → Chasing
/// - Chasing:追击主角。主角离开 detectionRange → ChasingPersist
/// - ChasingPersist脱离后继续追击 chasePersistTime 秒。
/// 主角重新进入范围 → 回到 Chasing超时 → Idle停在当前位置不回出生点
/// - BellResponding主角在 listenRange 内按E摇铃时触发
/// 朝主角方向移动 bellMoveTime 秒(距离 = 速度 × 时间),然后停下 → Idle
/// - Idle待机。射线检测到玩家 → ChasingPlayer收到铃铛信号 → ChasingBell
/// - ChasingPlayer追击玩家。持续看到玩家则追击脱离视线3秒后 → Idle
/// - ChasingBell追击铃铛。追击过程中看到玩家 → ChasingPlayer到达铃铛位置 → PatrollingBell
/// - PatrollingBell铃铛位置巡逻。巡逻过程中看到玩家 → ChasingPlayer巡逻时间结束 → Idle
///
/// 挂载位置:敌人预制体上
/// 需要:场景中有 EchoSystem用于摇铃事件、玩家物体带 "Player" 标签
@@ -24,32 +22,63 @@ namespace IndianOceanAssets.Engine2_5D
[SerializeField] private Transform playerTarget;
[Header("检测与追击")]
[Tooltip("发现主角的范围")]
[SerializeField] private float detectionRange = 8f;
[Tooltip("靠近主角到此距离时停下")]
[SerializeField] private float stopDistance = 1.2f;
[Tooltip("追击速度")]
[SerializeField] private float chaseSpeed = 3f;
[Tooltip("靠近主角到此距离时停下")]
[SerializeField] private float stopDistance = 1.2f;
[Header("视觉检测")]
[Tooltip("射线检测层(哪些物体会阻挡视线)")]
[SerializeField] private LayerMask obstacleLayers;
[Tooltip("主角脱离检测范围后,继续追击的时间(秒")]
[SerializeField] private float chasePersistTime = 3f;
[Tooltip("射线起点偏移(避免从地面发射")]
[SerializeField] private float rayOffsetY = 0.5f;
[Header("脱离计时")]
[Tooltip("玩家脱离视线后,继续追击的时间(秒)")]
[SerializeField] private float loseSightTime = 3f;
[Header("聆听(摇铃响应)")]
[Tooltip("聆听范围主角在此范围内按E摇铃时敌人会朝主角方向移动")]
[Tooltip("聆听范围主角在此范围内按E摇铃时敌人会朝铃铛位置移动")]
[SerializeField] private float listenRange = 15f;
[Tooltip("摇铃响应移动速度")]
[SerializeField] private float bellMoveSpeed = 4f;
[Header("铃铛追击")]
[Tooltip("到达铃铛位置后的容差距离")]
[SerializeField] private float bellArriveDistance = 1f;
[Tooltip("摇铃响应移动时间(距离 = 速度 × 时间")]
[SerializeField] private float bellMoveTime = 1.5f;
[Tooltip("追击铃铛的最大时间(超时则放弃")]
[SerializeField] private float bellChaseTimeout = 10f;
[Header("铃铛巡逻")]
[Tooltip("到达铃铛位置后的巡逻时间")]
[SerializeField] private float bellPatrolTime = 5f;
[Tooltip("巡逻半径")]
[SerializeField] private float bellPatrolRadius = 3f;
// 状态枚举
private enum State
{
Idle, // 待机
ChasingPlayer, // 追击玩家
ChasingBell, // 追击铃铛
PatrollingBell // 铃铛位置巡逻
}
private enum State { Idle, Chasing, ChasingPersist, BellResponding }
private State _state = State.Idle;
private float _stateTimer;
private Vector3 _bellDirection;
// 脱离视线计时器
private float _loseSightTimer;
// 铃铛相关
private Vector3 _bellTargetPosition; // 铃铛目标位置
private float _bellChaseTimer; // 铃铛追击计时器
// 巡逻相关
private Vector3 _patrolCenter; // 巡逻中心点
private float _patrolTimer; // 巡逻计时器
private Vector3 _patrolTarget; // 当前巡逻目标点
private void Start()
{
@@ -75,104 +104,209 @@ namespace IndianOceanAssets.Engine2_5D
{
switch (_state)
{
case State.Idle: UpdateIdle(); break;
case State.Chasing: UpdateChasing(); break;
case State.ChasingPersist: UpdateChasingPersist(); break;
case State.BellResponding: UpdateBellResponding(); break;
case State.Idle: UpdateIdle(); break;
case State.ChasingPlayer: UpdateChasingPlayer(); break;
case State.ChasingBell: UpdateChasingBell(); break;
case State.PatrollingBell: UpdatePatrollingBell(); break;
}
}
// ===== Idle站岗,等主角进入检测范围 =====
// ===== Idle待机,等待检测 =====
private void UpdateIdle()
{
if (playerTarget == null) return;
if (XZDistance(transform.position, playerTarget.position) <= detectionRange)
// 检测是否看到玩家
if (CanSeePlayer())
{
_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;
_state = State.ChasingPlayer;
_loseSightTimer = 0f;
return;
}
// 靠近到停止距离时停下(但仍处于追击状态)
if (dist > stopDistance)
MoveToward(playerTarget.position, chaseSpeed);
}
// ===== ChasingPersist脱离后继续追击倒计时 =====
private void UpdateChasingPersist()
// ===== ChasingPlayer追击玩家 =====
private void UpdateChasingPlayer()
{
if (playerTarget == null) return;
_stateTimer -= Time.deltaTime;
// 检测是否看到玩家
if (CanSeePlayer())
{
_loseSightTimer = 0f; // 重置计时器
// 追击玩家
float dist = XZDistance(transform.position, playerTarget.position);
if (dist > stopDistance)
{
MoveToward(playerTarget.position, chaseSpeed);
}
}
else
{
// 玩家脱离视线,开始计时
_loseSightTimer += Time.deltaTime;
// 继续朝玩家最后已知位置移动
if (playerTarget != null)
{
float dist = XZDistance(transform.position, playerTarget.position);
if (dist > stopDistance)
{
MoveToward(playerTarget.position, chaseSpeed);
}
}
// 超过设定时间,停止追击
if (_loseSightTimer >= loseSightTime)
{
_state = State.Idle;
}
}
}
// 超时 → 停在当前位置,回到 Idle不回出生点
if (_stateTimer <= 0f)
// ===== ChasingBell追击铃铛 =====
private void UpdateChasingBell()
{
// 追击过程中检测是否看到玩家
if (CanSeePlayer())
{
_state = State.ChasingPlayer;
_loseSightTimer = 0f;
return;
}
// 追击计时
_bellChaseTimer += Time.deltaTime;
if (_bellChaseTimer >= bellChaseTimeout)
{
_state = State.Idle;
return;
}
float dist = XZDistance(transform.position, playerTarget.position);
// 主角重新进入检测范围 → 回到正常追击
if (dist <= detectionRange)
// 检测是否到达铃铛位置
float dist = XZDistance(transform.position, _bellTargetPosition);
if (dist <= bellArriveDistance)
{
_state = State.Chasing;
// 到达铃铛位置,开始巡逻
_state = State.PatrollingBell;
_patrolCenter = _bellTargetPosition;
_patrolTimer = 0f;
PickNewPatrolPoint();
return;
}
if (dist > stopDistance)
MoveToward(playerTarget.position, chaseSpeed);
// 朝铃铛位置移动
MoveToward(_bellTargetPosition, chaseSpeed);
}
// ===== BellResponding摇铃响应朝主角方向移动固定时间 =====
private void UpdateBellResponding()
// ===== PatrollingBell铃铛位置巡逻 =====
private void UpdatePatrollingBell()
{
_stateTimer -= Time.deltaTime;
if (_stateTimer <= 0f)
// 巡逻过程中检测是否看到玩家
if (CanSeePlayer())
{
_state = State.ChasingPlayer;
_loseSightTimer = 0f;
return;
}
// 巡逻计时
_patrolTimer += Time.deltaTime;
if (_patrolTimer >= bellPatrolTime)
{
_state = State.Idle;
return;
}
// 沿摇铃时刻的方向移动(距离 = 速度 × 时间)
Vector3 move = _bellDirection * bellMoveSpeed * Time.deltaTime;
transform.position += new Vector3(move.x, 0f, move.z);
// 朝巡逻点移动
float dist = XZDistance(transform.position, _patrolTarget);
if (dist <= 0.5f)
{
// 到达巡逻点,选择新的巡逻点
PickNewPatrolPoint();
}
else
{
MoveToward(_patrolTarget, chaseSpeed * 0.7f); // 巡逻时速度稍慢
}
}
// ===== 摇铃事件回调(由 EchoSystem.OnEchoReleased 触发)=====
private void OnBell(Vector3 bellPos)
{
if (playerTarget == null) return;
// 只有在聆听范围内才响应
if (XZDistance(transform.position, bellPos) > listenRange) return;
// 设置铃铛目标位置
_bellTargetPosition = bellPos;
_bellChaseTimer = 0f;
// 切换到追击铃铛状态(可以打断任何状态)
_state = State.ChasingBell;
}
// 计算朝主角方向(摇铃时刻的位置)
_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; // 重叠时用当前朝向
/// <summary>
/// 选择新的巡逻点
/// </summary>
private void PickNewPatrolPoint()
{
// 在巡逻中心周围随机选择一个点
Vector2 randomDir = Random.insideUnitCircle * bellPatrolRadius;
_patrolTarget = _patrolCenter + new Vector3(randomDir.x, 0f, randomDir.y);
}
// 摇铃响应可打断任何当前状态
_state = State.BellResponding;
_stateTimer = bellMoveTime;
/// <summary>
/// 检测是否能看到玩家(射线检测)
/// </summary>
private bool CanSeePlayer()
{
if (playerTarget == null) return false;
// 射线起点(稍微抬高,避免从地面发射)
Vector3 rayStart = transform.position + Vector3.up * rayOffsetY;
Vector3 rayEnd = playerTarget.position + Vector3.up * rayOffsetY;
Vector3 dir = rayEnd - rayStart;
float dist = dir.magnitude;
// 调试在Scene视图中绘制射线
Debug.DrawLine(rayStart, rayEnd, CanSeePlayerDebugColor());
// 发射射线,只检测障碍物层
if (Physics.Raycast(rayStart, dir.normalized, out RaycastHit hit, dist, obstacleLayers))
{
// 击中了物体
Debug.DrawLine(rayStart, hit.point, Color.red);
// 如果击中的是玩家,说明可以看到
if (hit.transform == playerTarget)
{
return true;
}
// 击中了其他物体(被遮挡)
return false;
}
// 没有击中任何物体 = 没有遮挡 = 可以看到玩家
return true;
}
/// <summary>
/// 调试用:根据是否能看到玩家返回不同颜色
/// </summary>
private Color CanSeePlayerDebugColor()
{
if (playerTarget == null) return Color.gray;
Vector3 rayStart = transform.position + Vector3.up * rayOffsetY;
Vector3 rayEnd = playerTarget.position + Vector3.up * rayOffsetY;
Vector3 dir = rayEnd - rayStart;
float dist = dir.magnitude;
if (Physics.Raycast(rayStart, dir.normalized, out RaycastHit hit, dist, obstacleLayers))
{
return hit.transform == playerTarget ? Color.green : Color.red;
}
return Color.green; // 没有遮挡
}
// ===== 工具方法 =====
@@ -203,17 +337,48 @@ namespace IndianOceanAssets.Engine2_5D
// ===== 编辑器可视化 =====
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);
// 根据状态显示不同颜色
Color stateColor = Color.white;
switch (_state)
{
case State.Idle: stateColor = Color.gray; break;
case State.ChasingPlayer: stateColor = Color.red; break;
case State.ChasingBell: stateColor = Color.yellow; break;
case State.PatrollingBell: stateColor = Color.cyan; break;
}
// 绘制当前状态指示
Gizmos.color = stateColor;
Gizmos.DrawWireSphere(transform.position, 0.5f);
// 绘制聆听范围
Gizmos.color = new Color(0f, 0.85f, 1f, 0.3f);
Gizmos.DrawWireSphere(transform.position, listenRange);
// 停止距离(红色)
Gizmos.color = new Color(1f, 0.3f, 0.3f, 0.6f);
// 绘制停止距离
Gizmos.color = new Color(1f, 0.3f, 0.3f, 0.3f);
Gizmos.DrawWireSphere(transform.position, stopDistance);
// 绘制铃铛目标(如果在追击铃铛或巡逻)
if (_state == State.ChasingBell || _state == State.PatrollingBell)
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(_bellTargetPosition, bellArriveDistance);
// 绘制到铃铛的连线
Gizmos.DrawLine(transform.position, _bellTargetPosition);
}
// 绘制巡逻范围(如果在巡逻)
if (_state == State.PatrollingBell)
{
Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere(_patrolCenter, bellPatrolRadius);
// 绘制当前巡逻目标
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(_patrolTarget, 0.3f);
}
}
/// <summary>当前状态(调试用)</summary>