using UnityEngine; using UnityEngine.Events; /// /// 实验流程状态机——整个项目的核心控制器。 /// 负责步骤推进、模式切换、UI 更新、加权考核判分调度。 /// 挂在一个持久化的 GameObject 上。 /// public class StepManager : MonoBehaviour { [Header("配置(优先级:Inspector直接赋值 > ExperimentFlowCoordinator兜底 > Resources加载)")] public ExperimentConfig Config; [Header("事件(拖入场景对象或通过代码绑定)")] public UnityEvent OnStepChanged; public UnityEvent OnTipChanged; public UnityEvent OnDialogueChanged; public UnityEvent OnExamPopup; public UnityEvent OnDetailedContent; public UnityEvent OnExperimentComplete; public UnityEvent OnVideoToggle; public UnityEvent OnModeUIUpdate; // 私有状态 private StepData[] _activeSteps; private int _currentStepIndex = -1; private ExperimentMode _mode; private FixationMethod _fixation; private bool _initialized = false; public int CurrentStepIndex => _currentStepIndex; public int TotalSteps => _activeSteps != null ? _activeSteps.Length : 0; public ExperimentMode Mode => _mode; public FixationMethod Fixation => _fixation; public bool IsInitialized => _initialized; /// 用 GameManager 的当前设置初始化 public void InitializeFromGameManager() { var gm = GameManager.Instance; if (gm == null) { Debug.LogError("StepManager: GameManager.Instance is null!"); return; } Initialize(gm.CurrentExperimentType, gm.CurrentMode, gm.CurrentFixationMethod); } /// 显式指定参数初始化 public void Initialize(ExperimentType type, ExperimentMode mode, FixationMethod fixation) { // 如果 Inspector 里没有直接拖 Config,尝试从 ExperimentFlowCoordinator 获取兜底配置 if (Config == null) { var coordinator = FindObjectOfType(); if (coordinator != null && coordinator.DefaultConfig != null) { Config = coordinator.DefaultConfig; } } // 最后尝试从 Resources 加载 if (Config == null) { Config = Resources.Load($"Configs/{type}Config"); } if (Config == null) { Debug.LogError($"StepManager: 找不到 ExperimentConfig for {type}。" + "请在 Inspector 中直接拖入 Config,或在 ExperimentFlowCoordinator 上设置 Default Config," + "或在 Resources/Configs/ 下创建 ScriptableObject。"); return; } _mode = mode; _fixation = fixation; _activeSteps = Config.GetStepsFor(mode, fixation); if (_activeSteps == null || _activeSteps.Length == 0) { Debug.LogError($"StepManager: 实验 {type} 模式 {mode} 固定方式 {fixation} 无步骤数据!"); return; } _currentStepIndex = -1; _initialized = true; if (mode == ExperimentMode.Exam) GameManager.Instance.InitExamRecord(_activeSteps.Length); OnModeUIUpdate?.Invoke(mode == ExperimentMode.Exam); GoToStep(0); } public void GoToStep(int index) { if (_activeSteps == null) return; index = Mathf.Clamp(index, 0, _activeSteps.Length - 1); if (index == _currentStepIndex) return; _currentStepIndex = index; var step = _activeSteps[index]; if (_mode == ExperimentMode.Learn) { OnTipChanged?.Invoke(step.TipText); OnDialogueChanged?.Invoke(step.DialogueText); OnDetailedContent?.Invoke(step.DetailedContent); if (step.ShowPipVideo) OnVideoToggle?.Invoke(step.VideoFileName, true); else OnVideoToggle?.Invoke(null, false); } else { OnTipChanged?.Invoke(""); OnDialogueChanged?.Invoke(""); OnDetailedContent?.Invoke(""); OnVideoToggle?.Invoke(null, false); if (!string.IsNullOrEmpty(step.ExamQuestionText)) OnExamPopup?.Invoke(step); } OnStepChanged?.Invoke(_currentStepIndex); } public void NextStep() { Debug.LogError("====== StepManager: 接收到 NextStep 指令! ======"); // 首先尝试是否存在可以点击的 3D 物品,如果有,优先执行 3D 交互 if (TryClickActiveHandler()) { Debug.Log("====== StepManager: 成功模拟 3D 物品点击,不进行常规的 NextStep 流程 ======"); return; } Debug.Log("====== StepManager: 未找到 3D 物品,执行常规 NextStep 流程 ======"); if (_activeSteps != null && _currentStepIndex < _activeSteps.Length - 1) { GoToStep(_currentStepIndex + 1); } else if (_activeSteps != null && _currentStepIndex == _activeSteps.Length - 1) { CompleteExperiment(); } } public void TryInteractCurrent3DStep() { Debug.Log("====== StepManager: 接收到 3D 自动交互指令 ======"); if (TryClickActiveHandler()) return; Debug.Log("====== StepManager: 当前没有可自动交互的 3D 物品 ======"); } private bool TryClickActiveHandler() { var point2s = Object.FindObjectsOfType(); foreach (var p in point2s) { if (p.gameObject.activeInHierarchy && p.CanBeClicked()) { Debug.LogError($"====== StepManager: 模拟点击了 {p.name}! ======"); p.PerformClick(); return true; } } var touches = Object.FindObjectsOfType(); System.Array.Sort(touches, (a, b) => string.Compare(a.name, b.name)); foreach (var t in touches) { Debug.Log($"[StepManager] 检查 Touch: {t.name}, active={t.gameObject.activeInHierarchy}, CanBeClicked={t.CanBeClicked()}"); if (t.gameObject.activeInHierarchy && t.CanBeClicked()) { Debug.LogError($"====== StepManager: 模拟点击了 {t.name}! ======"); t.PerformClick(); return true; } } var shoses = Object.FindObjectsOfType(); foreach (var h in shoses) { Debug.Log($"[StepManager] 检查 Shose: {h.name}, active={h.gameObject.activeInHierarchy}, CanBeClicked={h.CanBeClicked()}"); if (h.gameObject.activeInHierarchy && h.CanBeClicked()) { Debug.LogError($"====== StepManager: 模拟点击了 {h.name}! ======"); h.PerformClick(); return true; } } var chanraos = Object.FindObjectsOfType(); foreach (var h in chanraos) { Debug.Log($"[StepManager] 检查 Chanrao: {h.name}, active={h.gameObject.activeInHierarchy}, CanBeClicked={h.CanBeClicked()}"); if (h.gameObject.activeInHierarchy && h.CanBeClicked()) { Debug.LogError($"====== StepManager: 模拟点击了 {h.name}! ======"); h.PerformClick(); return true; } } var pings = Object.FindObjectsOfType(); System.Array.Sort(pings, (a, b) => string.Compare(b.name, a.name)); foreach (var p in pings) { Debug.Log($"[StepManager] 检查 Ping: {p.name}, active={p.gameObject.activeInHierarchy}, CanBeClicked={p.CanBeClicked()}"); if (p.gameObject.activeInHierarchy && p.CanBeClicked()) { Debug.LogError($"====== StepManager: 模拟点击了 {p.name}! ======"); p.PerformClick(); return true; } } return false; } private bool TryUndoActiveHandler() { return ActionHistory.TryUndoLast(); } public void RecordPingStep(int pingIndex, bool secondClick) { _pingStepIndex = pingIndex * 2 + (secondClick ? 2 : 1); Debug.Log($"StepManager: Ping步进 -> {_pingStepIndex} (ping{pingIndex} second={secondClick})"); OnStepChanged?.Invoke(_pingStepIndex); } public void SetPingStepIndex(int index) { _pingStepIndex = index; Debug.Log($"StepManager: Ping彻底重置 -> {index}"); } public void DecrementPingStep() { _pingStepIndex = Mathf.Max(0, _pingStepIndex - 1); Debug.Log($"StepManager: Ping撤销 -> {_pingStepIndex}"); OnStepChanged?.Invoke(_pingStepIndex); } public void ResetPingStep() { _pingStepIndex = 0; 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 指令! ======"); // 如果尝试撤销 3D 交互成功,就不再回退 UI if (TryUndoActiveHandler()) { Debug.Log("====== StepManager: 成功撤销 3D 交互,不进行常规的 PreviousStep 流程 ======"); return; } if (_currentStepIndex > 0) { GoToStep(_currentStepIndex - 1); } else { Debug.LogWarning("====== StepManager: 已经是第一步,无法后退 ======"); } } public StepData GetCurrentStep() { if (_activeSteps == null || _currentStepIndex < 0 || _currentStepIndex >= _activeSteps.Length) return null; return _activeSteps[_currentStepIndex]; } public void OnStepInteractionComplete(float proportion = 1f, string feedback = "") { var step = GetCurrentStep(); if (step == null) return; if (_mode == ExperimentMode.Exam) { string fb = string.IsNullOrEmpty(feedback) ? step.ErrorFeedback : feedback; GameManager.Instance.RecordExamResult(_currentStepIndex, step.ScoreWeight, proportion, fb); } if (_mode == ExperimentMode.Learn) NextStep(); } private void CompleteExperiment() { Debug.Log("StepManager: 实验流程结束"); OnExperimentComplete?.Invoke(); } public void SwitchMode(ExperimentMode newMode) { GameManager.Instance.CurrentMode = newMode; Initialize(GameManager.Instance.CurrentExperimentType, newMode, GameManager.Instance.CurrentFixationMethod); } public void SwitchFixation(FixationMethod newFixation) { GameManager.Instance.CurrentFixationMethod = newFixation; Initialize(GameManager.Instance.CurrentExperimentType, GameManager.Instance.CurrentMode, newFixation); } public void InitWomanSteps() { _womanObject = GameObject.Find("woman"); _touchArrayObject = GameObject.Find("touchArray"); // 保存初始位置,用于回退到 step 0 if (_womanObject != null) { _womanInitPos = _womanObject.transform.position; _womanInitRot = _womanObject.transform.rotation; Debug.Log($"StepManager: woman初始位置已保存 ({_womanInitPos})"); } var pts = GameObject.Find("womanPointArray"); if (pts != null) { _womanMaxStep = pts.transform.childCount; _womanPositions = new Transform[_womanMaxStep]; for (int i = 0; i < _womanMaxStep; i++) _womanPositions[i] = pts.transform.GetChild(i); } if (_touchArrayObject != null) { _touchPoints = new GameObject[_touchArrayObject.transform.childCount]; for (int i = 0; i < _touchPoints.Length; i++) _touchPoints[i] = _touchArrayObject.transform.GetChild(i).gameObject; } _womanStepIndex = 0; Debug.Log($"StepManager: Woman步进已初始化, 共{_womanMaxStep}个位置"); } public void InitPingSteps() { var mi = GameObject.Find("manInjury"); if (mi == null) return; _pingObjects = new GameObject[4]; _chanraoObjects = new GameObject[4]; var pings = Object.FindObjectsOfType(true); foreach (var p in pings) { if (p.pingIndex >= 0 && p.pingIndex < 4) { _pingObjects[p.pingIndex] = p.gameObject; if (p.chanraoObject != null) { _chanraoObjects[p.pingIndex] = p.chanraoObject; } } } _pingStepIndex = 0; Debug.Log("StepManager: Ping步进已初始化"); } public void RecordWomanStep(int newStepIndex) { _womanStepIndex = Mathf.Clamp(newStepIndex, 0, _womanMaxStep); Debug.Log($"StepManager: Woman步进 -> {_womanStepIndex}"); OnStepChanged?.Invoke(_womanStepIndex); } private void MoveWomanToStep(int step) { if (_womanObject == null || _womanPositions == null) return; if (step < 0 || step > _womanPositions.Length) return; if (step == 0) { // 回到初始位置 _womanObject.transform.position = _womanInitPos; _womanObject.transform.rotation = _womanInitRot; Debug.Log("StepManager: woman 回到初始位置"); } else { var t = _womanPositions[step - 1]; _womanObject.transform.position = t.position; _womanObject.transform.rotation = t.rotation; } } private void UpdateTouchVisibility(int step) { if (_touchPoints == null) return; for (int i = 0; i < _touchPoints.Length; i++) { bool show = (i == step); _touchPoints[i].SetActive(show); if (show) { var mr = _touchPoints[i].GetComponent(); if (mr != null) mr.enabled = true; } } } [Header("=== Woman 步进追踪(不依赖 Config) ===")] [SerializeField] private GameObject _womanObject; [SerializeField] private GameObject _touchArrayObject; private int _womanStepIndex = 0; private int _womanMaxStep = 3; private Transform[] _womanPositions; private GameObject[] _touchPoints; private GameObject[] _pingObjects; private GameObject[] _chanraoObjects; private int _pingStepIndex = 0; private int _pingMaxStep = 8; // 4 pings × 2 clicks each private Vector3 _womanInitPos; private Quaternion _womanInitRot; }