Files
2026-07-17 05:42:52 +08:00

434 lines
15 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// 实验流程状态机——整个项目的核心控制器。
/// 负责步骤推进、模式切换、UI 更新、加权考核判分调度。
/// 挂在一个持久化的 GameObject 上。
/// </summary>
public class StepManager : MonoBehaviour
{
[Header("配置(优先级:Inspector直接赋值 > ExperimentFlowCoordinator兜底 > Resources加载)")]
public ExperimentConfig Config;
[Header("事件(拖入场景对象或通过代码绑定)")]
public UnityEvent<int> OnStepChanged;
public UnityEvent<string> OnTipChanged;
public UnityEvent<string> OnDialogueChanged;
public UnityEvent<StepData> OnExamPopup;
public UnityEvent<string> OnDetailedContent;
public UnityEvent OnExperimentComplete;
public UnityEvent<string, bool> OnVideoToggle;
public UnityEvent<bool> 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;
/// <summary>用 GameManager 的当前设置初始化</summary>
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);
}
/// <summary>显式指定参数初始化</summary>
public void Initialize(ExperimentType type, ExperimentMode mode, FixationMethod fixation)
{
// 如果 Inspector 里没有直接拖 Config,尝试从 ExperimentFlowCoordinator 获取兜底配置
if (Config == null)
{
var coordinator = FindObjectOfType<ExperimentFlowCoordinator>();
if (coordinator != null && coordinator.DefaultConfig != null)
{
Config = coordinator.DefaultConfig;
}
}
// 最后尝试从 Resources 加载
if (Config == null)
{
Config = Resources.Load<ExperimentConfig>($"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<Point2ClickHandler>();
foreach (var p in point2s)
{
if (p.gameObject.activeInHierarchy && p.CanBeClicked())
{
Debug.LogError($"====== StepManager: 模拟点击了 {p.name} ======");
p.PerformClick();
return true;
}
}
var touches = Object.FindObjectsOfType<TouchPositionHandler>();
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<ShoseClickHandler>();
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<ChanraoTuibuHandler>();
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 splintObjects = Object.FindObjectsOfType<SplintObjectClickHandler>();
foreach (var h in splintObjects)
{
Debug.Log($"[StepManager] 检查 SplintObject: {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<PingClickHandler>();
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<PingClickHandler>(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<MeshRenderer>();
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;
}