feat(enemy): EnemyLocomotion 运行时巡逻调试(Inspector 面板 + Scene Gizmos)
排查"敌人原地不动"用:PlayMode 下自定义 Inspector 实时显示 模式/策略/是否移动/是否在导航图上/ 当前路点与吸附目标/到目标距离vs到达半径/目标是否可达,并按优先级给出定位诊断(模式非Patrol|不在 NavGraph|吸附失败|不可达|已寻路未动)。Scene 视图 Gizmos 画出全部路点连线 + 当前吸附目标(绿/红)+到达半径。 EnemyLocomotion 加 #if UNITY_EDITOR 只读调试访问器 + OnDrawGizmosSelected;记录 _wpResolveOk。 Editor 程序集补 BaseGames.AI 引用(PatrolStrategy/LocomotionMode 所在)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
"BaseGames.Player.States",
|
||||
"BaseGames.Enemies",
|
||||
"BaseGames.Enemies.Navigation",
|
||||
"BaseGames.AI",
|
||||
"BaseGames.Camera",
|
||||
"BaseGames.World",
|
||||
"BaseGames.UI",
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using BaseGames.AI;
|
||||
using BaseGames.Enemies;
|
||||
using BaseGames.Enemies.Navigation;
|
||||
|
||||
namespace BaseGames.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="EnemyLocomotion"/> 自定义 Inspector。
|
||||
/// PlayMode 下实时显示移动/巡逻状态,并对"原地不动"给出定位诊断:
|
||||
/// 模式、策略、当前路点与吸附目标、到达距离、是否在导航图上、目标是否可达。
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(EnemyLocomotion))]
|
||||
public class EnemyLocomotionEditor : UnityEditor.Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
EditorGUILayout.Space(4f);
|
||||
EditorGUILayout.HelpBox("进入 Play 模式后此处显示实时巡逻调试信息。", MessageType.None);
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(6f);
|
||||
DrawRuntimeSection();
|
||||
Repaint(); // 每帧刷新
|
||||
}
|
||||
|
||||
private void DrawRuntimeSection()
|
||||
{
|
||||
var loco = (EnemyLocomotion)target;
|
||||
var enemy = loco.DebugEnemy;
|
||||
|
||||
EditorGUILayout.LabelField("运行时调试", EditorStyles.boldLabel);
|
||||
|
||||
// ── 模式 / 策略 ─────────────────────────────────────────────
|
||||
var mode = loco.CurrentMode;
|
||||
bool patrolActive = mode == LocomotionMode.Patrol;
|
||||
Row("当前模式 (Mode)", mode.ToString(),
|
||||
patrolActive ? Green : (mode == LocomotionMode.Idle ? Gray : White));
|
||||
Row("巡逻策略 (Strategy)", loco.DebugStrategy.ToString(), White);
|
||||
Row("正在移动 (IsMoving)", loco.IsMoving ? "是" : "否", loco.IsMoving ? Green : Gray);
|
||||
|
||||
if (enemy == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("EnemyBase 未解析(_enemy=null)。", MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
// ── 导航代理状态 ────────────────────────────────────────────
|
||||
var nav = enemy.Nav;
|
||||
bool agentMapped = true; // 非 PB2d 代理默认视为有效
|
||||
bool navMoving = nav != null && nav.IsMoving;
|
||||
if (nav is EnemyNavAgent ena && ena.RawNavAgent != null)
|
||||
agentMapped = ena.RawNavAgent.HasValidPosition;
|
||||
|
||||
Row("在导航图上 (Mapped)", agentMapped ? "是" : "否 —— 敌人不在 NavGraph 上",
|
||||
agentMapped ? Green : Red);
|
||||
Row("Nav 正在寻路/移动", navMoving ? "是" : "否", navMoving ? Green : Gray);
|
||||
|
||||
// 身体宽度 / 吸附距离(上下文)
|
||||
var mv = enemy.Movement;
|
||||
if (mv != null) Row("EdgeSafeMargin(身体半宽+偏移)", mv.EdgeSafeMargin.ToString("F2"), White);
|
||||
if (nav is EnemyNavAgent ena2)
|
||||
{
|
||||
var so = new SerializedObject(ena2);
|
||||
var msd = so.FindProperty("_maxSnapDistance");
|
||||
if (msd != null) Row("_maxSnapDistance", msd.floatValue.ToString("F1"), White);
|
||||
}
|
||||
|
||||
// ── 路点巡逻细节 ────────────────────────────────────────────
|
||||
if (loco.DebugStrategy == PatrolStrategy.Waypoints)
|
||||
{
|
||||
EditorGUILayout.Space(4f);
|
||||
EditorGUILayout.LabelField("Waypoints", EditorStyles.miniBoldLabel);
|
||||
|
||||
var wps = loco.DebugWaypoints;
|
||||
int idx = loco.DebugWaypointIndex;
|
||||
int cnt = wps != null ? wps.Length : 0;
|
||||
Row("当前路点索引", cnt > 0 ? $"{idx} / {cnt - 1}" : "无路点", cnt > 0 ? White : Red);
|
||||
|
||||
if (wps != null && idx >= 0 && idx < cnt && wps[idx] != null)
|
||||
{
|
||||
Vector2 raw = wps[idx].position;
|
||||
Vector2 goal = loco.DebugResolvedGoal;
|
||||
Row("原始路点", $"{wps[idx].name} {raw}", White);
|
||||
Row("吸附目标 (Resolved)", loco.DebugResolveOk ? goal.ToString() : $"{goal} (吸附失败,回落原始点)",
|
||||
loco.DebugResolveOk ? Green : Red);
|
||||
|
||||
float dist = Vector2.Distance(enemy.transform.position, goal);
|
||||
bool arrived = dist <= loco.DebugArriveRadius;
|
||||
Row("到目标距离 / 到达半径", $"{dist:F2} / {loco.DebugArriveRadius:F2}{(arrived ? " (已到达)" : "")}",
|
||||
arrived ? Green : White);
|
||||
|
||||
bool canReach = nav != null && nav.CanReach(goal);
|
||||
Row("目标可达 (CanReach)", canReach ? "是" : "否 —— 起点/终点不连通", canReach ? Green : Red);
|
||||
|
||||
DrawDiagnosis(patrolActive, loco.DebugStrategy, agentMapped, loco.DebugResolveOk, canReach, navMoving, arrived);
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("在 Hierarchy 中定位敌人", EditorStyles.miniButton))
|
||||
Selection.activeGameObject = enemy.gameObject;
|
||||
}
|
||||
|
||||
// ── "原地不动" 定位诊断 ─────────────────────────────────────────
|
||||
private static void DrawDiagnosis(bool patrolActive, PatrolStrategy strat, bool mapped,
|
||||
bool resolveOk, bool canReach, bool navMoving, bool arrived)
|
||||
{
|
||||
EditorGUILayout.Space(4f);
|
||||
string msg; MessageType type;
|
||||
|
||||
if (!patrolActive)
|
||||
{
|
||||
msg = "模式不是 Patrol —— AI 尚未进入巡逻态(如伪装/待机),TickPatrol 不会执行,故不动。" +
|
||||
"需触发 AI 切到巡逻状态。";
|
||||
type = MessageType.Warning;
|
||||
}
|
||||
else if (!mapped)
|
||||
{
|
||||
msg = "敌人不在导航图上(HasValidPosition=false)—— RequestMoveTo/UpdatePath 被直接忽略。" +
|
||||
"检查:碰撞体底部是否贴地(原点=脚底)、是否站在已 bake 的 NavSurface 上、PointMappingDistance(默认0.2m)。";
|
||||
type = MessageType.Error;
|
||||
}
|
||||
else if (!resolveOk)
|
||||
{
|
||||
msg = "路点吸附失败 —— 目标离所有烘焙段都超过 _maxSnapDistance。调大 _maxSnapDistance 或把路点摆近平台。";
|
||||
type = MessageType.Error;
|
||||
}
|
||||
else if (!canReach)
|
||||
{
|
||||
msg = "目标不可达 —— 起点与吸附目标不在同一连通区,或中间隔着不可穿越的 NavLink(跳/落/爬)。";
|
||||
type = MessageType.Error;
|
||||
}
|
||||
else if (!navMoving && !arrived)
|
||||
{
|
||||
msg = "已寻路成功但没有移动 —— 检查 EnemyMovement 速度/受阻(WouldBlockAhead 夹停)、刚体约束、是否被其他状态覆盖输入。";
|
||||
type = MessageType.Warning;
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = "巡逻链路正常。";
|
||||
type = MessageType.Info;
|
||||
}
|
||||
EditorGUILayout.HelpBox(msg, type);
|
||||
}
|
||||
|
||||
// ── 绘制辅助 ────────────────────────────────────────────────────
|
||||
private static readonly Color Green = new Color(0.4f, 0.9f, 0.4f);
|
||||
private static readonly Color Red = new Color(1f, 0.5f, 0.4f);
|
||||
private static readonly Color Gray = new Color(0.7f, 0.7f, 0.7f);
|
||||
private static readonly Color White = Color.white;
|
||||
|
||||
private static void Row(string label, string value, Color valueColor)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField(label, GUILayout.Width(200f));
|
||||
var prev = GUI.color; GUI.color = valueColor;
|
||||
EditorGUILayout.LabelField(value, EditorStyles.boldLabel);
|
||||
GUI.color = prev;
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c75f9efe0c364f47b03472f71be96b9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user