Files
VFSUnity/Assets/Scripts/Core/StepManager.cs
T

381 lines
14 KiB
C#
Raw Normal View History

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()
{
// Woman 自由步进优先
if (_pingObjects != null && _pingObjects.Length > 0 && _pingStepIndex < _pingMaxStep) { PingStepNext(); return; }
if (_womanObject != null && _womanPositions != null) { WomanStepNext(); return; }
if (_activeSteps == null) return;
if (_currentStepIndex >= _activeSteps.Length - 1) { CompleteExperiment(); return; }
GoToStep(_currentStepIndex + 1);
}
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<PingClickHandler>();
if (h != null) h.UndoSecondClick();
}
}
else
{
// Undo first click: hide current ping, show previous ping (if any)
if (_pingObjects != null && idx < _pingObjects.Length && _pingObjects[idx] != null)
{
var h = _pingObjects[idx].GetComponent<PingClickHandler>();
if (h != null) h.UndoShow();
}
// If going back to start, show ping.004
if (idx == 0 && _pingObjects != null && _pingObjects.Length > 0 && _pingObjects[0] != null)
_pingObjects[0].SetActive(true);
}
_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<PingClickHandler>();
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<PingClickHandler>();
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)
{
_pingStepIndex = pingIndex * 2 + (secondClick ? 2 : 1);
Debug.Log($"StepManager: Ping步进 -> {_pingStepIndex} (ping{pingIndex} second={secondClick})");
OnStepChanged?.Invoke(_pingStepIndex);
}
2026-07-02 16:29:52 +08:00
public void PreviousStep()
{
// Woman 自由步进优先
if (_pingObjects != null && _pingObjects.Length > 0 && _pingStepIndex > 0) { PingStepPrevious(); return; }
if (_womanObject != null && _womanPositions != null) { WomanStepPrevious(); return; }
if (_activeSteps == null) return;
if (_currentStepIndex <= 0) return;
GoToStep(_currentStepIndex - 1);
}
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];
for (int i = 1; i <= 4; i++)
{
string pn = "ping." + i.ToString("D3");
string cn = "chanrao." + i.ToString("D3");
var pt = mi.transform.Find(pn);
var ct = mi.transform.Find(cn);
if (pt != null) _pingObjects[i-1] = pt.gameObject;
if (ct != null) _chanraoObjects[i-1] = ct.gameObject;
}
_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 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)
{
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;
}