From e7eb9a9cbcbb17c28251a630aeba41f7e717bad5 Mon Sep 17 00:00:00 2001 From: yangbear Date: Sun, 12 Jul 2026 12:17:06 +0800 Subject: [PATCH] Fix full flow reset cleanup for outlines and residual objects, adjust EndPanel buttons --- Assets/Scripts/Audio/AudioManager.cs | 54 +++++- Assets/Scripts/Core/UndoManager.cs | 8 +- .../Interaction/ChanraoTuibuHandler.cs | 18 +- .../Scripts/Interaction/PingClickHandler.cs | 28 ++- .../Scripts/Interaction/Point2ClickHandler.cs | 20 +- .../Scripts/Interaction/ShoseClickHandler.cs | 29 +-- Assets/Scripts/UI/PageController.cs | 178 ++++++++++++++---- Assets/Scripts/UI/UIManager.cs | 43 ++++- 8 files changed, 312 insertions(+), 66 deletions(-) diff --git a/Assets/Scripts/Audio/AudioManager.cs b/Assets/Scripts/Audio/AudioManager.cs index 8eccad4..ea0b367 100644 --- a/Assets/Scripts/Audio/AudioManager.cs +++ b/Assets/Scripts/Audio/AudioManager.cs @@ -19,6 +19,10 @@ public class AudioManager : MonoBehaviour private AudioSource _bgSource; private AudioSource _sfxSource; private AudioSource _voiceSource; + private Coroutine _cameraSwitchRoutine; + private Vector3 _camera3InitialPosition; + private Quaternion _camera3InitialRotation; + private bool _hasCamera3InitialTransform; void Awake() { @@ -42,6 +46,7 @@ public class AudioManager : MonoBehaviour if (_basketballSfx == null) _basketballSfx = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/Audio/start/basketballSound.wav"); if (_manCallSfx == null) _manCallSfx = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/Audio/start/mancall.wav"); #endif + CacheCamera3InitialTransform(); Debug.Log($"[AudioManager] 就绪 homeBg={_homeBg!=null} expBg={_experimentBg!=null} sfx={_basketballSfx!=null}/{_manCallSfx!=null}"); } @@ -49,6 +54,7 @@ public class AudioManager : MonoBehaviour public void PlayHomeBg() { if (_homeBg == null) return; + if (_bgSource.clip == _homeBg && _bgSource.isPlaying) return; _bgSource.clip = _homeBg; _bgSource.volume = BgVolume; _bgSource.Play(); @@ -58,6 +64,7 @@ public class AudioManager : MonoBehaviour public void PlayExperimentBg() { if (_experimentBg == null) return; + if (_bgSource.clip == _experimentBg && _bgSource.isPlaying) return; _bgSource.clip = _experimentBg; _bgSource.volume = BgVolume; _bgSource.Play(); @@ -99,7 +106,51 @@ public class AudioManager : MonoBehaviour public void SwitchToCamera3() { - StartCoroutine(SmoothSwitchCameraCoroutine()); + CacheCamera3InitialTransform(); + if (_cameraSwitchRoutine != null) StopCoroutine(_cameraSwitchRoutine); + _cameraSwitchRoutine = StartCoroutine(SmoothSwitchCameraCoroutine()); + } + + public void ResetCamerasToHome() + { + if (_cameraSwitchRoutine != null) + { + StopCoroutine(_cameraSwitchRoutine); + _cameraSwitchRoutine = null; + } + + var mainCam = GameObject.Find("Main Camera"); + var cam2 = GameObject.Find("camera2"); + var cam3 = GameObject.Find("camera3"); + + SetCameraEnabled(mainCam, true); + SetCameraEnabled(cam2, false); + SetCameraEnabled(cam3, false); + + if (cam3 != null && _hasCamera3InitialTransform) + { + cam3.transform.position = _camera3InitialPosition; + cam3.transform.rotation = _camera3InitialRotation; + } + + Debug.Log("[AudioManager] 已恢复首页相机状态"); + } + + private void CacheCamera3InitialTransform() + { + if (_hasCamera3InitialTransform) return; + var cam3 = GameObject.Find("camera3"); + if (cam3 == null) return; + _camera3InitialPosition = cam3.transform.position; + _camera3InitialRotation = cam3.transform.rotation; + _hasCamera3InitialTransform = true; + } + + private void SetCameraEnabled(GameObject cameraObject, bool enabled) + { + if (cameraObject == null) return; + var camera = cameraObject.GetComponent(); + if (camera != null) camera.enabled = enabled; } private System.Collections.IEnumerator SmoothSwitchCameraCoroutine() @@ -148,6 +199,7 @@ float duration = 1.0f; cam3.transform.position = targetPos; cam3.transform.rotation = targetRot; + _cameraSwitchRoutine = null; Debug.Log("[AudioManager] camera2→camera3 平滑过渡完成"); } diff --git a/Assets/Scripts/Core/UndoManager.cs b/Assets/Scripts/Core/UndoManager.cs index 1d2bd62..f58eade 100644 --- a/Assets/Scripts/Core/UndoManager.cs +++ b/Assets/Scripts/Core/UndoManager.cs @@ -22,6 +22,12 @@ public static class ActionHistory public static Stack History = new Stack(); + public static void Clear() + { + History.Clear(); + Debug.Log("[ActionHistory] 已清空历史记录"); + } + public static void Push(ActionType type, MonoBehaviour handler, int extra = 0) { History.Push(new HistoryItem { Type = type, Handler = handler, ExtraInt = extra }); @@ -102,4 +108,4 @@ public static class ActionHistory return true; } -} \ No newline at end of file +} diff --git a/Assets/Scripts/Interaction/ChanraoTuibuHandler.cs b/Assets/Scripts/Interaction/ChanraoTuibuHandler.cs index 8f91d82..052e50e 100644 --- a/Assets/Scripts/Interaction/ChanraoTuibuHandler.cs +++ b/Assets/Scripts/Interaction/ChanraoTuibuHandler.cs @@ -120,14 +120,28 @@ public class ChanraoTuibuHandler : MonoBehaviour } public void ResetState() + { + ResetState(true); + } + + public void ResetState(bool startBlink) { _isFinished = false; var mr = GetComponent(); if (mr != null) mr.enabled = false; var smr = GetComponent(); if (smr != null) smr.enabled = false; foreach (var r in GetComponentsInChildren(true)) { if (_outlineMesh != null && r == _outlineMesh) continue; r.enabled = false; } - if (_outlineMesh != null) _outlineMesh.enabled = true; + if (_outlineMesh != null) _outlineMesh.enabled = startBlink; StopAllCoroutines(); - StartCoroutine(BlinkOutline()); + if (startBlink && isActiveAndEnabled) + { + StartCoroutine(BlinkOutline()); + } + } + + public void ForceHideOutline() + { + StopAllCoroutines(); + if (_outlineMesh != null) _outlineMesh.enabled = false; } } diff --git a/Assets/Scripts/Interaction/PingClickHandler.cs b/Assets/Scripts/Interaction/PingClickHandler.cs index 88e9375..2f47f9b 100644 --- a/Assets/Scripts/Interaction/PingClickHandler.cs +++ b/Assets/Scripts/Interaction/PingClickHandler.cs @@ -188,9 +188,8 @@ public class PingClickHandler : MonoBehaviour remaining++; } - if (remaining <= 1) + if (remaining == 0) { - // 彻底修复:使用静态标志位锁定播放动作,这一轮绝不会播两次 if (!_hasPlayedFinishVoice) { _hasPlayedFinishVoice = true; @@ -208,7 +207,7 @@ public class PingClickHandler : MonoBehaviour if (uiMgr != null) { uiMgr.HideVideo(); - uiMgr.ShowEndPanel(); // 调用显示结束面板 + uiMgr.ShowEndPanel(); } } } @@ -250,7 +249,6 @@ public class PingClickHandler : MonoBehaviour _isBlinking = true; _enableFrame = Time.frameCount; - // 撤销的时候,把语音锁打开,以便能够重新播放 _hasPlayedFinishVoice = false; if (_outlineMesh != null) _outlineMesh.enabled = true; @@ -282,17 +280,33 @@ public class PingClickHandler : MonoBehaviour } public void ResetState() + { + ResetState(true); + } + + public void ResetState(bool startBlink) { _modelShown = false; - _isBlinking = true; + _isBlinking = startBlink; _enableFrame = Time.frameCount; _hasPlayedFinishVoice = false; if (_modelRenderer != null) _modelRenderer.enabled = false; + if (chanraoObject != null) chanraoObject.SetActive(false); foreach (var r in GetComponentsInChildren(true)) { if (_outlineMesh != null && r == _outlineMesh) continue; r.enabled = false; } - if (_outlineMesh != null) _outlineMesh.enabled = true; + if (_outlineMesh != null) _outlineMesh.enabled = startBlink; StopAllCoroutines(); - StartCoroutine(BlinkOutline()); + if (startBlink && isActiveAndEnabled) + { + StartCoroutine(BlinkOutline()); + } + } + + public void ForceHideOutline() + { + _isBlinking = false; + StopAllCoroutines(); + if (_outlineMesh != null) _outlineMesh.enabled = false; } } diff --git a/Assets/Scripts/Interaction/Point2ClickHandler.cs b/Assets/Scripts/Interaction/Point2ClickHandler.cs index 948be4a..34ea1f9 100644 --- a/Assets/Scripts/Interaction/Point2ClickHandler.cs +++ b/Assets/Scripts/Interaction/Point2ClickHandler.cs @@ -10,6 +10,12 @@ public class Point2ClickHandler : MonoBehaviour private MeshRenderer _meshRenderer; + private void OnDisable() + { + StopAllCoroutines(); + if (_meshRenderer != null) _meshRenderer.enabled = false; + } + void OnEnable() { if (GetComponent() == null) gameObject.AddComponent().radius = 0.5f; @@ -92,9 +98,14 @@ public class Point2ClickHandler : MonoBehaviour } public void ResetState() + { + ResetState(true); + } + + public void ResetState(bool startBlink) { _clicked = false; - if (_meshRenderer != null) _meshRenderer.enabled = true; + if (_meshRenderer != null) _meshRenderer.enabled = startBlink; if (nextObject != null) { @@ -113,8 +124,11 @@ public class Point2ClickHandler : MonoBehaviour } } - gameObject.SetActive(true); StopAllCoroutines(); - StartCoroutine(BlinkOutline()); + gameObject.SetActive(startBlink); + if (startBlink && isActiveAndEnabled) + { + StartCoroutine(BlinkOutline()); + } } } diff --git a/Assets/Scripts/Interaction/ShoseClickHandler.cs b/Assets/Scripts/Interaction/ShoseClickHandler.cs index 389451c..581a412 100644 --- a/Assets/Scripts/Interaction/ShoseClickHandler.cs +++ b/Assets/Scripts/Interaction/ShoseClickHandler.cs @@ -30,7 +30,6 @@ public class ShoseClickHandler : MonoBehaviour var sc = GetComponent(); if(sc != null) sc.enabled = false; var col = GetComponent(); if (col == null) col = gameObject.AddComponent(); - // Unity 的 BoxCollider.size 处于本地坐标空间,会自动乘上 localScale,这里不能重复缩放 col.center = mb.center; col.size = mb.size * 1.2f; col.enabled = true; @@ -104,7 +103,6 @@ public class ShoseClickHandler : MonoBehaviour anim.CrossFade("womanCrouch", 0.1f); } - // 移动 woman 到 point3 var point3 = GameObject.Find("womanPointArray/point3"); if (point3 != null) { @@ -121,13 +119,10 @@ public class ShoseClickHandler : MonoBehaviour { anim.SetBool("CheckFoot", true); anim.CrossFade("manInjuryFoot", 0.1f); - - // 启动协程,在动画播放完后将 CheckFoot 重置为 false StartCoroutine(ResetCheckFoot(anim)); } } - // 隐藏 point3 的边框 (touchArray/point3) var touchArray = GameObject.Find("touchArray"); if (touchArray != null) { @@ -138,7 +133,6 @@ public class ShoseClickHandler : MonoBehaviour } } - // 显示 VideoPanel 并播放视频 var uiMgr = FindObjectOfType(); if (uiMgr != null) { @@ -147,11 +141,15 @@ public class ShoseClickHandler : MonoBehaviour } public void ResetState() + { + ResetState(true); + } + + public void ResetState(bool startBlink) { _clicked = false; - // 【关键修复】确保 OutlineMesh 在重新激活时处于开启状态并正常闪烁 - if (_outlineMesh != null) _outlineMesh.enabled = true; + if (_outlineMesh != null) _outlineMesh.enabled = startBlink; if (_hasRecordedInitialTransform) { @@ -159,7 +157,6 @@ public class ShoseClickHandler : MonoBehaviour transform.rotation = _initialRotation; } - // 返回时要隐藏 ping if (nextObject != null) nextObject.SetActive(false); var uiMgr = FindObjectOfType(); @@ -168,7 +165,6 @@ public class ShoseClickHandler : MonoBehaviour uiMgr.HideVideo(); } - // 把 woman 重置(取消蹲下) var woman = GameObject.Find("woman"); if (woman != null) { @@ -185,7 +181,6 @@ public class ShoseClickHandler : MonoBehaviour } } - // 取消男伤员的 CheckFoot 动作 var manInjury = GameObject.Find("manInjury"); if (manInjury != null) { @@ -196,7 +191,6 @@ public class ShoseClickHandler : MonoBehaviour } } - // 重新显示 touchArray/point3 的边框 var touchArray = GameObject.Find("touchArray"); if (touchArray != null) { @@ -208,7 +202,16 @@ public class ShoseClickHandler : MonoBehaviour } StopAllCoroutines(); - StartCoroutine(BlinkOutline()); + if (startBlink && isActiveAndEnabled) + { + StartCoroutine(BlinkOutline()); + } + } + + public void ForceHideOutline() + { + StopAllCoroutines(); + if (_outlineMesh != null) _outlineMesh.enabled = false; } private System.Collections.IEnumerator ResetCheckFoot(Animator anim) diff --git a/Assets/Scripts/UI/PageController.cs b/Assets/Scripts/UI/PageController.cs index ee69165..a566187 100644 --- a/Assets/Scripts/UI/PageController.cs +++ b/Assets/Scripts/UI/PageController.cs @@ -44,6 +44,7 @@ public class PageController : MonoBehaviour private Page _currentPage = Page.Home; private ExperimentMode _selectedMode = ExperimentMode.Learn; + private int _lastReturnHomeFrame = -1; private void Start() { @@ -114,6 +115,13 @@ public class PageController : MonoBehaviour if (page == Page.ModeSelect && _modeDescText != null) _modeDescText.text = ""; + + var audioManager = FindObjectOfType(); + if (audioManager != null) + { + if (page == Page.Experiment) audioManager.PlayExperimentBg(); + else audioManager.PlayHomeBg(); + } Debug.Log($"PageController: 页面切换 -> [{page}]"); } @@ -198,40 +206,64 @@ public class PageController : MonoBehaviour public void ReturnToHome() { - Debug.Log("PageController: 返回首页"); + if (_lastReturnHomeFrame == Time.frameCount) + { + Debug.Log("PageController: 返回首页已在本帧执行,跳过重复调用"); + return; + } + + _lastReturnHomeFrame = Time.frameCount; + Debug.Log("PageController: 返回首页,开始完整复位流程"); + + StopAllCoroutines(); + ActionHistory.Clear(); + + if (_uiManager != null) + { + _uiManager.HideVideo(); + _uiManager.HideEndPanel(); + } + + HideCanvasPanel("VideoPanel"); + HideCanvasPanel("EndPanel"); + HideCanvasPanel("DialogueBubble"); + HideCanvasPanel("GradeResult"); + HideCanvasPanel("TipPanel"); // --- 核心修复:退出时彻底清理并隐藏所有交互物体 --- - var manInjury = GameObject.Find("manInjury"); - if (manInjury != null) + foreach (var handler in GetSceneObjectsOfType()) { - foreach (var handler in manInjury.GetComponentsInChildren(true)) - { - handler.ResetState(); - handler.gameObject.SetActive(false); // 强制隐藏 ping - } - foreach (var handler in manInjury.GetComponentsInChildren(true)) - { - handler.ResetState(); - handler.gameObject.SetActive(false); // 强制隐藏 chanraotuibu - } - // 这里也可以顺带隐藏所有的 man_ba_zi_chan_rao 下的 chanrao + handler.ResetState(false); + handler.ForceHideOutline(); + handler.gameObject.SetActive(false); + Debug.Log($"PageController: 已隐藏 Ping {handler.name}"); } + + foreach (var handler in GetSceneObjectsOfType()) + { + handler.ResetState(false); + handler.ForceHideOutline(); + handler.gameObject.SetActive(false); + Debug.Log($"PageController: 已隐藏 ChanraoTuibu {handler.name}"); + } + + HideSceneObjectsByNamePrefix("chanrao"); // 找到 Shose 重置并彻底隐藏 - var shose = GameObject.Find("Shose"); - if (shose == null) - { - var allTransforms = Resources.FindObjectsOfTypeAll(); - foreach(var t in allTransforms) { if (t.parent == null && t.name == "Shose") { shose = t.gameObject; break; } } - } + var shose = FindSceneObject("Shose"); if (shose != null) { var h = shose.GetComponent(); - if (h != null) h.ResetState(); + if (h != null) + { + h.ResetState(false); + h.ForceHideOutline(); + } // 关键:返回主页时,必须禁用点击组件,并隐藏物体 if (h != null) h.enabled = false; shose.SetActive(false); + Debug.Log("PageController: 已隐藏 Shose"); } // 把 touchArray 里的点也关掉 @@ -240,12 +272,84 @@ public class PageController : MonoBehaviour { foreach (Transform t in touchArray.transform) { + var point2Handler = t.GetComponent(); + if (point2Handler != null) point2Handler.ResetState(false); t.gameObject.SetActive(false); } } + + var woman = GameObject.Find("woman"); + if (woman != null) + { + var anim = woman.GetComponent(); + if (anim != null) + { + anim.SetBool("IsCheck", false); + anim.SetBool("isCrouch", false); + } + } + + var manInjury = GameObject.Find("manInjury"); + if (manInjury != null) + { + var anim = manInjury.GetComponent(); + if (anim != null) anim.SetBool("CheckFoot", false); + } + + var audioManager = FindObjectOfType(); + if (audioManager != null) + { + audioManager.StopVoice(); + audioManager.StopSfx(); + audioManager.ResetCamerasToHome(); + } // ------------------------------------------------ ShowPage(Page.Home); + Debug.Log("PageController: 返回首页复位完成"); + } + + private void HideCanvasPanel(string panelName) + { + var panel = FindSceneObject(panelName); + if (panel != null) panel.SetActive(false); + } + + private GameObject FindSceneObject(string objectName) + { + var found = GameObject.Find(objectName); + if (found != null) return found; + + foreach (var t in Resources.FindObjectsOfTypeAll()) + { + if (t == null || !t.gameObject.scene.IsValid()) continue; + if (t.name == objectName) return t.gameObject; + } + + return null; + } + + private T[] GetSceneObjectsOfType() where T : Component + { + var all = Resources.FindObjectsOfTypeAll(); + var list = new System.Collections.Generic.List(); + foreach (var item in all) + { + if (item == null || !item.gameObject.scene.IsValid()) continue; + list.Add(item); + } + return list.ToArray(); + } + + private void HideSceneObjectsByNamePrefix(string prefix) + { + foreach (var t in Resources.FindObjectsOfTypeAll()) + { + if (t == null || !t.gameObject.scene.IsValid()) continue; + if (!t.name.StartsWith(prefix, System.StringComparison.OrdinalIgnoreCase)) continue; + t.gameObject.SetActive(false); + Debug.Log($"PageController: 已隐藏残留对象 {t.name}"); + } } private System.Collections.IEnumerator StartInitialVoiceAndCameraRoutine() @@ -264,11 +368,7 @@ public class PageController : MonoBehaviour } // 找鞋子 - var shose = GameObject.Find("Shose"); - if (shose == null) - { - foreach(var t in allT) { if (t.parent == null && t.name == "Shose") { shose = t.gameObject; break; } } - } + var shose = FindSceneObject("Shose"); var point2 = GameObject.Find("touchArray/point2"); if (point2 == null) @@ -287,14 +387,7 @@ public class PageController : MonoBehaviour if (h == null) h = shose.AddComponent(); h.targetPoint = GameObject.Find("ShosePoint")?.transform; - // 这里因为你换了绑带模型:我们需要确保拿到正确的 ping.004。 - // 只要场景里激活/存在叫 ping.004 的就行 - var ping004 = GameObject.Find("ping.004"); - if (ping004 == null) - { - // 如果是隐藏状态,用全局查找 - foreach(var t in allT) { if (t.name == "ping.004") { ping004 = t.gameObject; break; } } - } + var ping004 = FindFirstPingStepObject(); h.nextObject = ping004; // 鞋子出现,但默认不开组件(不可点击) @@ -350,13 +443,28 @@ public class PageController : MonoBehaviour private void HideShose() { - var shose = GameObject.Find("Shose"); + var shose = FindSceneObject("Shose"); if (shose != null) { shose.SetActive(false); } } + private GameObject FindFirstPingStepObject() + { + foreach (var ping in GetSceneObjectsOfType()) + { + if (ping != null && ping.name == "ping.004") return ping.gameObject; + } + + foreach (var ping in GetSceneObjectsOfType()) + { + if (ping != null && ping.pingIndex == 0) return ping.gameObject; + } + + return null; + } + #endregion } diff --git a/Assets/Scripts/UI/UIManager.cs b/Assets/Scripts/UI/UIManager.cs index daa46a7..59a02e6 100644 --- a/Assets/Scripts/UI/UIManager.cs +++ b/Assets/Scripts/UI/UIManager.cs @@ -41,6 +41,7 @@ public class UIManager : MonoBehaviour private void Start() { Debug.Log("=== UIManager 初始化开始 ==="); + AutoFindPanels(); HideVideo(); if (_endPanel != null) _endPanel.SetActive(false); @@ -56,6 +57,24 @@ public class UIManager : MonoBehaviour Debug.Log("=== UIManager 初始化完成 ==="); } + private void AutoFindPanels() + { + var canvas = GameObject.Find("Canvas")?.transform; + if (canvas == null) return; + + if (_videoPanel == null) + { + var t = canvas.Find("VideoPanel"); + if (t != null) _videoPanel = t.gameObject; + } + + if (_endPanel == null) + { + var t = canvas.Find("EndPanel"); + if (t != null) _endPanel = t.gameObject; + } + } + private void BindNavbarButtons() { var experimentPanel = GameObject.Find("ExperimentPanel"); @@ -146,26 +165,43 @@ public class UIManager : MonoBehaviour public void ShowVideo(string fileName) { + AutoFindPanels(); if (_videoPanel != null) { _videoPanel.SetActive(true); var vp = _videoPanel.GetComponentInChildren(true); if (vp != null) vp.Play(); + Debug.Log("UIManager: 显示并播放 VideoPanel"); } } public void HideVideo() { + AutoFindPanels(); if (_videoPanel != null) { var vp = _videoPanel.GetComponentInChildren(true); if (vp != null) vp.Stop(); _videoPanel.SetActive(false); + Debug.Log("UIManager: 隐藏 VideoPanel"); } } + public void HideEndPanel() + { + AutoFindPanels(); + if (_endPanel == null) + { + var t = GameObject.Find("Canvas")?.transform.Find("EndPanel"); + if (t != null) _endPanel = t.gameObject; + } + + if (_endPanel != null) _endPanel.SetActive(false); + } + public void ShowEndPanel() { + AutoFindPanels(); if (_endPanel == null) { var t = GameObject.Find("Canvas")?.transform.Find("EndPanel"); @@ -176,12 +212,12 @@ public class UIManager : MonoBehaviour { _endPanel.SetActive(true); - // 绑定 BtnYes:关闭 EndPanel + // 绑定 BtnYes:结束后返回首页 var btnYes = _endPanel.transform.Find("BtnYes")?.GetComponent