From 8123f187856e0b9ea26ac0baa19b435675a97728 Mon Sep 17 00:00:00 2001 From: yangbear Date: Thu, 9 Jul 2026 03:16:00 +0800 Subject: [PATCH] feat: rewrite 3D step undo using an exact ActionHistory stack --- Assets/Scripts/Core/StepManager.cs | 137 ++---------------- Assets/Scripts/Core/UndoManager.cs | 83 +++++++++++ .../Interaction/ChanraoTuibuHandler.cs | 3 + .../Scripts/Interaction/PingClickHandler.cs | 25 +++- .../Scripts/Interaction/ShoseClickHandler.cs | 3 + .../Interaction/TouchPositionHandler.cs | 1 + 6 files changed, 124 insertions(+), 128 deletions(-) create mode 100644 Assets/Scripts/Core/UndoManager.cs diff --git a/Assets/Scripts/Core/StepManager.cs b/Assets/Scripts/Core/StepManager.cs index c42fb5e..06d7165 100644 --- a/Assets/Scripts/Core/StepManager.cs +++ b/Assets/Scripts/Core/StepManager.cs @@ -208,111 +208,9 @@ public class StepManager : MonoBehaviour private bool TryUndoActiveHandler() { - if (_pingStepIndex > 0 && _pingStepIndex <= _pingMaxStep) - { - if (_pingObjects == null) InitPingSteps(); - PingStepPrevious(); - return true; - } - if (_womanStepIndex > 0 && _womanStepIndex <= _womanMaxStep && _pingStepIndex == 0) - { - WomanStepPrevious(); - return true; - } - return false; + return ActionHistory.TryUndoLast(); } - private void PingStepPrevious() - { - if (_pingStepIndex <= 0) return; - int idx = (_pingStepIndex - 1) / 2; // ping index (0-3, mapping to 004-001) - bool wasSecond = (_pingStepIndex - 1) % 2 == 1; // true if was second click - - if (wasSecond) - { - // Undo second click: hide chanrao, restore ping to first-click state - if (_chanraoObjects != null && idx < _chanraoObjects.Length && _chanraoObjects[idx] != null) - _chanraoObjects[idx].SetActive(false); - if (_pingObjects != null && idx < _pingObjects.Length && _pingObjects[idx] != null) - { - var h = _pingObjects[idx].GetComponent(); - if (h != null) h.UndoSecondClick(); - } - } - else - { - // Undo first click: reset current ping to blink state, and hide the next ping it spawned - if (_pingObjects != null && idx < _pingObjects.Length && _pingObjects[idx] != null) - { - var h = _pingObjects[idx].GetComponent(); - if (h != null) - { - // 撤销它对下一个目标的影响 - if (h.nextPing != null) - { - h.nextPing.SetActive(false); - } - else - { - // 如果是最后一个,撤销它唤醒的所有其他边框 - var allPings = Object.FindObjectsOfType(); - foreach (var p in allPings) - { - if (p.gameObject != h.gameObject) - { - p.gameObject.SetActive(false); - } - } - } - // 重置自己到初始状态 - h.ResetState(); - } - } - } - _pingStepIndex--; - Debug.Log($"StepManager: Ping撤销 -> {_pingStepIndex}"); - OnStepChanged?.Invoke(_pingStepIndex); - } - - private void PingStepNext() - { - if (_pingStepIndex >= _pingMaxStep) return; - int idx = _pingStepIndex / 2; - bool isSecond = _pingStepIndex % 2 == 1; - - if (!isSecond) - { - // Advance to first click: ensure ping is visible and in first-click state - if (_pingObjects != null && idx < _pingObjects.Length && _pingObjects[idx] != null) - { - var ping = _pingObjects[idx]; - ping.SetActive(true); - var h = ping.GetComponent(); - if (h != null) - { - h.UndoSecondClick(); // Reset to first-click state - // Trigger first click via reflection/method - } - } - } - else - { - // Advance to second click: show chanrao - if (_chanraoObjects != null && idx < _chanraoObjects.Length && _chanraoObjects[idx] != null) - _chanraoObjects[idx].SetActive(true); - if (_pingObjects != null && idx < _pingObjects.Length && _pingObjects[idx] != null) - { - var h = _pingObjects[idx].GetComponent(); - if (h != null) h.UndoShow(); - } - // Show next ping for first click - if (idx + 1 < 4 && _pingObjects != null && _pingObjects[idx+1] != null) - _pingObjects[idx+1].SetActive(true); - } - _pingStepIndex++; - Debug.Log($"StepManager: Ping前进 -> {_pingStepIndex}"); - OnStepChanged?.Invoke(_pingStepIndex); - } public void RecordPingStep(int pingIndex, bool secondClick) { @@ -321,6 +219,20 @@ public class StepManager : MonoBehaviour OnStepChanged?.Invoke(_pingStepIndex); } + public void DecrementPingStep() + { + _pingStepIndex--; + Debug.Log($"StepManager: Ping撤销 -> {_pingStepIndex}"); + OnStepChanged?.Invoke(_pingStepIndex); + } + + public void DecrementWomanStep() + { + if (_womanStepIndex > 0) _womanStepIndex--; + Debug.Log($"StepManager: Woman撤销 -> {_womanStepIndex}"); + OnStepChanged?.Invoke(_womanStepIndex); + } + public void PreviousStep() { Debug.LogError("====== StepManager: 接收到 PreviousStep 指令! ======"); @@ -429,25 +341,6 @@ public class StepManager : MonoBehaviour public void RecordWomanStep(int newStepIndex) { _womanStepIndex = Mathf.Clamp(newStepIndex, 0, _womanMaxStep); Debug.Log($"StepManager: Woman步进 -> {_womanStepIndex}"); OnStepChanged?.Invoke(_womanStepIndex); } - private void WomanStepPrevious() - { - if (_womanStepIndex <= 0) return; - _womanStepIndex--; - MoveWomanToStep(_womanStepIndex); - UpdateTouchVisibility(_womanStepIndex); - Debug.Log($"StepManager: Woman撤销 -> {_womanStepIndex}"); - OnStepChanged?.Invoke(_womanStepIndex); - } - - private void WomanStepNext() - { - if (_womanStepIndex >= _womanMaxStep) return; - _womanStepIndex++; - MoveWomanToStep(_womanStepIndex); - UpdateTouchVisibility(_womanStepIndex); - Debug.Log($"StepManager: Woman前进 -> {_womanStepIndex}"); - OnStepChanged?.Invoke(_womanStepIndex); - } private void MoveWomanToStep(int step) { diff --git a/Assets/Scripts/Core/UndoManager.cs b/Assets/Scripts/Core/UndoManager.cs new file mode 100644 index 0000000..c9b9f08 --- /dev/null +++ b/Assets/Scripts/Core/UndoManager.cs @@ -0,0 +1,83 @@ +using UnityEngine; +using System.Collections.Generic; + +public static class ActionHistory +{ + public enum ActionType + { + Shose, + ChanraoTuibu, + PingFirst, + PingSecond, + Woman + } + + public class HistoryItem + { + public ActionType Type; + public MonoBehaviour Handler; + public int ExtraInt; + } + + public static Stack History = new Stack(); + + public static bool TryUndoLast() + { + if (History.Count == 0) return false; + var item = History.Pop(); + + if (item.Type == ActionType.Shose) + { + var h = item.Handler as ShoseClickHandler; + if (h != null) h.ResetState(); + } + else if (item.Type == ActionType.ChanraoTuibu) + { + var h = item.Handler as ChanraoTuibuHandler; + if (h != null) h.ResetState(); + } + else if (item.Type == ActionType.PingSecond) + { + var h = item.Handler as PingClickHandler; + if (h != null) h.UndoSecondClick(); + + // 撤销对 UI 步骤的影响 + var sm = Object.FindObjectOfType(); + if (sm != null) sm.DecrementPingStep(); + } + else if (item.Type == ActionType.PingFirst) + { + var h = item.Handler as PingClickHandler; + if (h != null) + { + if (h.nextPing != null) h.nextPing.SetActive(false); + else + { + var allPings = Object.FindObjectsOfType(); + foreach (var p in allPings) { if (p != h) p.UndoShow(); } + } + h.ResetState(); + } + + var sm = Object.FindObjectOfType(); + if (sm != null) sm.DecrementPingStep(); + } + else if (item.Type == ActionType.Woman) + { + var sm = Object.FindObjectOfType(); + if (sm != null) sm.DecrementWomanStep(); + + // 隐藏下一个小球并恢复当前小球 + var h = item.Handler as TouchPositionHandler; + if (h != null) + { + if (h.nextTouchObject != null) h.nextTouchObject.SetActive(false); + h.gameObject.SetActive(true); + var mr = h.GetComponent(); + if (mr != null) mr.enabled = true; + } + } + + return true; + } +} diff --git a/Assets/Scripts/Interaction/ChanraoTuibuHandler.cs b/Assets/Scripts/Interaction/ChanraoTuibuHandler.cs index 10cf541..e9f5cc7 100644 --- a/Assets/Scripts/Interaction/ChanraoTuibuHandler.cs +++ b/Assets/Scripts/Interaction/ChanraoTuibuHandler.cs @@ -64,6 +64,7 @@ public class ChanraoTuibuHandler : MonoBehaviour public void PerformClick() { Debug.Log($"[ChanraoTuibu] {name} 被点击"); + ActionHistory.Push(ActionHistory.ActionType.ChanraoTuibu, this); _isBlinking = false; StopAllCoroutines(); if (_outlineMesh != null) _outlineMesh.enabled = false; @@ -88,10 +89,12 @@ public class ChanraoTuibuHandler : MonoBehaviour public void ResetState() { + _isBlinking = true; 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 (nextObject != null) nextObject.SetActive(false); StopAllCoroutines(); StartCoroutine(BlinkOutline()); } diff --git a/Assets/Scripts/Interaction/PingClickHandler.cs b/Assets/Scripts/Interaction/PingClickHandler.cs index 6530a63..715d715 100644 --- a/Assets/Scripts/Interaction/PingClickHandler.cs +++ b/Assets/Scripts/Interaction/PingClickHandler.cs @@ -69,6 +69,7 @@ public class PingClickHandler : MonoBehaviour { _modelShown = true; _isBlinking = false; + ActionHistory.Push(ActionHistory.ActionType.PingFirst, this); StopAllCoroutines(); if (_modelRenderer != null) _modelRenderer.enabled = true; foreach (var r in GetComponentsInChildren(true)) r.enabled = true; @@ -92,7 +93,8 @@ public class PingClickHandler : MonoBehaviour else { if (chanraoObject != null) { chanraoObject.SetActive(true); Debug.Log($"[PingClick] {name} → {chanraoObject.name}"); } - gameObject.SetActive(false); + HideSelf(); + ActionHistory.Push(ActionHistory.ActionType.PingSecond, this); var sm = FindObjectOfType(); if (sm != null) sm.RecordPingStep(pingIndex, true); @@ -123,15 +125,26 @@ public class PingClickHandler : MonoBehaviour public void ReEnableBorder() { _isBlinking = true; if (_outlineMesh != null) { _outlineMesh.enabled = true; StopAllCoroutines(); StartCoroutine(BlinkOutline()); } } public void UndoSecondClick() { - gameObject.SetActive(true); + ShowSelf(); _modelShown = true; _isBlinking = false; - if (_modelRenderer != null) _modelRenderer.enabled = true; - foreach (var r in GetComponentsInChildren(true)) r.enabled = true; - if (_outlineMesh != null) _outlineMesh.enabled = false; + } + public void UndoShow() { HideSelf(); } + + private void HideSelf() + { + foreach (var r in GetComponentsInChildren(true)) r.enabled = false; + var col = GetComponent(); if (col != null) col.enabled = false; + _isBlinking = false; StopAllCoroutines(); } - public void UndoShow() { gameObject.SetActive(false); } + + private void ShowSelf() + { + foreach (var r in GetComponentsInChildren(true)) r.enabled = true; + if (_outlineMesh != null) _outlineMesh.enabled = false; + var col = GetComponent(); if (col != null) col.enabled = true; + } public void ResetState() { diff --git a/Assets/Scripts/Interaction/ShoseClickHandler.cs b/Assets/Scripts/Interaction/ShoseClickHandler.cs index 31a974d..f0d5faa 100644 --- a/Assets/Scripts/Interaction/ShoseClickHandler.cs +++ b/Assets/Scripts/Interaction/ShoseClickHandler.cs @@ -79,6 +79,7 @@ public class ShoseClickHandler : MonoBehaviour if (_clicked) return; _clicked = true; Debug.Log("[ShoseClick] ★ 被点击 ★"); + ActionHistory.Push(ActionHistory.ActionType.Shose, this); StopAllCoroutines(); if (_outlineMesh != null) _outlineMesh.enabled = false; if (targetPoint != null) { transform.position = targetPoint.position; transform.rotation = targetPoint.rotation; } @@ -157,6 +158,8 @@ public class ShoseClickHandler : MonoBehaviour transform.rotation = _initialRotation; } + if (nextObject != null) nextObject.SetActive(false); + StopAllCoroutines(); StartCoroutine(BlinkOutline()); } diff --git a/Assets/Scripts/Interaction/TouchPositionHandler.cs b/Assets/Scripts/Interaction/TouchPositionHandler.cs index abe4f12..9be1cd0 100644 --- a/Assets/Scripts/Interaction/TouchPositionHandler.cs +++ b/Assets/Scripts/Interaction/TouchPositionHandler.cs @@ -83,6 +83,7 @@ public class TouchPositionHandler : MonoBehaviour int stepIndex = transform.GetSiblingIndex() + 1; var sm = FindObjectOfType(); if (sm != null) sm.RecordWomanStep(stepIndex); + ActionHistory.Push(ActionHistory.ActionType.Woman, this); // point1 点击后先激活 Shose if (stepIndex == 1)