diff --git a/Assets/_Game/Scripts/Editor/Enemies/EnemyLocomotionEditor.cs b/Assets/_Game/Scripts/Editor/Enemies/EnemyLocomotionEditor.cs index 26f36fd0..225e39c5 100644 --- a/Assets/_Game/Scripts/Editor/Enemies/EnemyLocomotionEditor.cs +++ b/Assets/_Game/Scripts/Editor/Enemies/EnemyLocomotionEditor.cs @@ -10,10 +10,38 @@ namespace BaseGames.Editor /// 自定义 Inspector。 /// PlayMode 下实时显示移动/巡逻状态,并对"原地不动"给出定位诊断: /// 模式、策略、当前路点与吸附目标、到达距离、是否在导航图上、目标是否可达。 + /// + /// 性能:刷新走 EditorApplication.update 定频(~5Hz),与游戏帧率解耦;昂贵查询 + /// (CanReach 会同步阻塞寻路、_maxSnapDistance 读取)限频 ~2Hz 并缓存——避免每帧 + /// 触发一次阻塞式寻路把帧数拖垮。 /// [CustomEditor(typeof(EnemyLocomotion))] public class EnemyLocomotionEditor : UnityEditor.Editor { + private const double RepaintInterval = 0.2; // 面板刷新间隔(s),与游戏帧率解耦 + private const double HeavyInterval = 0.5; // 昂贵查询(CanReach 等)重算间隔(s) + + private double _nextRepaintTime; + private double _nextHeavyTime; + + // 昂贵查询的缓存(仅每 HeavyInterval 刷新一次) + private bool _cachedCanReach; + private bool _cachedCanReachValid; + private float _cachedMaxSnap = -1f; + + private void OnEnable() => EditorApplication.update += ThrottledRepaint; + private void OnDisable() => EditorApplication.update -= ThrottledRepaint; + + // 定频请求重绘(不在 OnInspectorGUI 里每帧 Repaint,避免绑定到游戏帧率狂刷) + private void ThrottledRepaint() + { + if (!Application.isPlaying || target == null) return; + double t = EditorApplication.timeSinceStartup; + if (t < _nextRepaintTime) return; + _nextRepaintTime = t + RepaintInterval; + Repaint(); + } + public override void OnInspectorGUI() { DrawDefaultInspector(); @@ -27,7 +55,6 @@ namespace BaseGames.Editor EditorGUILayout.Space(6f); DrawRuntimeSection(); - Repaint(); // 每帧刷新 } private void DrawRuntimeSection() @@ -35,6 +62,11 @@ namespace BaseGames.Editor var loco = (EnemyLocomotion)target; var enemy = loco.DebugEnemy; + // 是否到了重算昂贵查询的时刻(限频) + double now = EditorApplication.timeSinceStartup; + bool doHeavy = now >= _nextHeavyTime; + if (doHeavy) _nextHeavyTime = now + HeavyInterval; + EditorGUILayout.LabelField("运行时调试", EditorStyles.boldLabel); // ── 模式 / 策略 ───────────────────────────────────────────── @@ -62,15 +94,16 @@ namespace BaseGames.Editor agentMapped ? Green : Red); Row("Nav 正在寻路/移动", navMoving ? "是" : "否", navMoving ? Green : Gray); - // 身体宽度 / 吸附距离(上下文) + // 身体宽度 / 吸附距离(_maxSnapDistance 运行期不变 → 只读一次缓存) var mv = enemy.Movement; if (mv != null) Row("EdgeSafeMargin(身体半宽+偏移)", mv.EdgeSafeMargin.ToString("F2"), White); - if (nav is EnemyNavAgent ena2) + if (_cachedMaxSnap < 0f && nav is EnemyNavAgent ena2) { - var so = new SerializedObject(ena2); + using var so = new SerializedObject(ena2); var msd = so.FindProperty("_maxSnapDistance"); - if (msd != null) Row("_maxSnapDistance", msd.floatValue.ToString("F1"), White); + _cachedMaxSnap = msd != null ? msd.floatValue : 0f; } + if (_cachedMaxSnap >= 0f) Row("_maxSnapDistance", _cachedMaxSnap.ToString("F1"), White); // ── 路点巡逻细节 ──────────────────────────────────────────── if (loco.DebugStrategy == PatrolStrategy.Waypoints) @@ -96,10 +129,20 @@ namespace BaseGames.Editor Row("到目标距离 / 到达半径", $"{dist:F2} / {loco.DebugArriveRadius:F2}{(arrived ? " (已到达)" : "")}", arrived ? Green : White); - bool canReach = nav != null && nav.CanReach(goal); - Row("目标可达 (CanReach)", canReach ? "是" : "否 —— 起点/终点不连通", canReach ? Green : Red); + // CanReach 会同步阻塞寻路 → 仅限频重算并缓存 + if (doHeavy && nav != null) + { + _cachedCanReach = nav.CanReach(goal); + _cachedCanReachValid = true; + } + if (_cachedCanReachValid) + Row("目标可达 (CanReach)", _cachedCanReach ? "是" : "否 —— 起点/终点不连通", + _cachedCanReach ? Green : Red); + else + Row("目标可达 (CanReach)", "计算中…", Gray); - DrawDiagnosis(patrolActive, loco.DebugStrategy, agentMapped, loco.DebugResolveOk, canReach, navMoving, arrived); + DrawDiagnosis(patrolActive, agentMapped, loco.DebugResolveOk, + _cachedCanReachValid && _cachedCanReach, navMoving, arrived); } } @@ -108,7 +151,7 @@ namespace BaseGames.Editor } // ── "原地不动" 定位诊断 ───────────────────────────────────────── - private static void DrawDiagnosis(bool patrolActive, PatrolStrategy strat, bool mapped, + private static void DrawDiagnosis(bool patrolActive, bool mapped, bool resolveOk, bool canReach, bool navMoving, bool arrived) { EditorGUILayout.Space(4f);