From fccd87df6b37c8c9e70ac35ae9271f5cfac2afb6 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Thu, 23 Jul 2026 15:33:41 +0800 Subject: [PATCH] =?UTF-8?q?fix(enemy):=20=E4=BF=AE=E5=A4=8D=E9=80=80?= =?UTF-8?q?=E5=87=BA=20Play=20=E6=97=B6=E5=B7=A1=E9=80=BB=E8=B0=83?= =?UTF-8?q?=E8=AF=95=20Inspector=20=E7=9A=84=20NRE=20=E4=B8=8E=20IMGUI=20?= =?UTF-8?q?=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 退出 Play 有一个窗口:Application.isPlaying 仍为 true 但 PBWorld 已销毁,此时 Inspector 调 nav.CanReach → PBWorld.PathTo(instance=null) 抛 NRE;该异常在 IMGUI 布局组中途中断, 使 BeginHorizontal 组失衡 → 级联 ArgumentException("control N in a group with only N controls")。 修: - 订阅 playModeStateChanged,ExitingPlayMode 置 _exitingPlay,CanReach 调用点加 !_exitingPlay && isPlaying 守卫,退出期不再查询已销毁的 PBWorld。 - 运行时区块的绘制判定只在 Layout 事件锁定、Repaint 复用,避免 isPlaying 在两趟 GUI 间翻转导致控件数不一致。 实测:选中敌人连续两次进/退 Play,Console 无 NRE/ArgumentException。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Editor/Enemies/EnemyLocomotionEditor.cs | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/Assets/_Game/Scripts/Editor/Enemies/EnemyLocomotionEditor.cs b/Assets/_Game/Scripts/Editor/Enemies/EnemyLocomotionEditor.cs index 8c124d8b..9fc63519 100644 --- a/Assets/_Game/Scripts/Editor/Enemies/EnemyLocomotionEditor.cs +++ b/Assets/_Game/Scripts/Editor/Enemies/EnemyLocomotionEditor.cs @@ -30,8 +30,29 @@ namespace BaseGames.Editor private bool _cachedCanReachValid; private float _cachedMaxSnap = -1f; - private void OnEnable() => EditorApplication.update += ThrottledRepaint; - private void OnDisable() => EditorApplication.update -= ThrottledRepaint; + // 退出 Play 期间:Application.isPlaying 仍为 true 但 PBWorld 已销毁,此时调 CanReach 会 NRE。 + // 收到 ExitingPlayMode 即停止一切运行时查询。 + private bool _exitingPlay; + // 本帧是否绘制运行时区块——在 Layout 事件锁定、Repaint 复用,保证两趟控件数一致(否则 IMGUI 报错)。 + private bool _drawRuntimeThisFrame; + + private void OnEnable() + { + EditorApplication.update += ThrottledRepaint; + EditorApplication.playModeStateChanged += OnPlayModeChanged; + } + + private void OnDisable() + { + EditorApplication.update -= ThrottledRepaint; + EditorApplication.playModeStateChanged -= OnPlayModeChanged; + } + + private void OnPlayModeChanged(PlayModeStateChange s) + { + if (s == PlayModeStateChange.ExitingPlayMode) _exitingPlay = true; + else if (s == PlayModeStateChange.EnteredPlayMode) _exitingPlay = false; + } // 定频请求重绘(不在 OnInspectorGUI 里每帧 Repaint,避免绑定到游戏帧率狂刷) private void ThrottledRepaint() @@ -58,7 +79,12 @@ namespace BaseGames.Editor } GUI.backgroundColor = prevBg; - if (!Application.isPlaying) + // 是否绘制运行时区块的判定只在 Layout 事件更新,Repaint 复用同一结果—— + // 避免退出 Play 那一帧 isPlaying 在 Layout/Repaint 两趟间翻转导致控件数不一致而 IMGUI 报错。 + if (Event.current.type == EventType.Layout) + _drawRuntimeThisFrame = Application.isPlaying && !_exitingPlay; + + if (!_drawRuntimeThisFrame) { EditorGUILayout.Space(4f); EditorGUILayout.HelpBox("进入 Play 模式后此处显示实时巡逻调试信息(按钮在编辑期也可打印静态配置)。", MessageType.None); @@ -251,8 +277,9 @@ namespace BaseGames.Editor Row("到目标距离 / 到达半径", $"{dist:F2} / {loco.DebugArriveRadius:F2}{(arrived ? " (已到达)" : "")}", arrived ? Green : White); - // CanReach 会同步阻塞寻路 → 仅限频重算并缓存 - if (doHeavy && nav != null) + // CanReach 会同步阻塞寻路 → 仅限频重算并缓存。 + // 额外守卫 !_exitingPlay && isPlaying:退出 Play 那一刻 PBWorld 已销毁,此时调会 NRE。 + if (doHeavy && nav != null && !_exitingPlay && Application.isPlaying) { _cachedCanReach = nav.CanReach(goal); _cachedCanReachValid = true;