perf(enemy): 巡逻调试 Inspector 限频,消除每帧阻塞式 CanReach 掉帧
上版 OnInspectorGUI 每帧 Repaint 且每帧调用 NavAgent.CanReach——该 API 会同步阻塞主线程 跑一次完整寻路(其文档自述"not optimal for performance"),选中敌人时每帧一次直接把帧率拖垮。 改为:刷新走 EditorApplication.update 定频(~5Hz)与游戏帧率解耦;CanReach 限频 ~2Hz 并缓存; _maxSnapDistance 运行期不变只读一次缓存(不再每帧 new SerializedObject)。实测选中敌人 ~53fps 稳定无卡顿。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,10 +10,38 @@ namespace BaseGames.Editor
|
||||
/// <see cref="EnemyLocomotion"/> 自定义 Inspector。
|
||||
/// PlayMode 下实时显示移动/巡逻状态,并对"原地不动"给出定位诊断:
|
||||
/// 模式、策略、当前路点与吸附目标、到达距离、是否在导航图上、目标是否可达。
|
||||
///
|
||||
/// 性能:刷新走 EditorApplication.update 定频(~5Hz),与游戏帧率解耦;昂贵查询
|
||||
/// (CanReach 会同步阻塞寻路、_maxSnapDistance 读取)限频 ~2Hz 并缓存——避免每帧
|
||||
/// 触发一次阻塞式寻路把帧数拖垮。
|
||||
/// </summary>
|
||||
[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);
|
||||
|
||||
Reference in New Issue
Block a user