173 lines
6.1 KiB
C#
173 lines
6.1 KiB
C#
|
|
using UnityEngine;
|
|||
|
|
using UnityEngine.Events;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 实验流程状态机——整个项目的核心控制器。
|
|||
|
|
/// 负责步骤推进、模式切换、UI 更新、加权考核判分调度。
|
|||
|
|
/// 挂在一个持久化的 GameObject 上。
|
|||
|
|
/// </summary>
|
|||
|
|
public class StepManager : MonoBehaviour
|
|||
|
|
{
|
|||
|
|
[Header("配置")]
|
|||
|
|
public ExperimentConfig Config;
|
|||
|
|
|
|||
|
|
[Header("事件(拖入场景对象或通过代码绑定)")]
|
|||
|
|
public UnityEvent<int> OnStepChanged; // 步骤切换时触发,传新步骤索引
|
|||
|
|
public UnityEvent<string> OnTipChanged; // Tip 文案更新
|
|||
|
|
public UnityEvent<string> OnDialogueChanged; // 角色对话更新
|
|||
|
|
public UnityEvent<StepData> OnExamPopup; // 考核弹窗触发,传当前 StepData
|
|||
|
|
public UnityEvent<string> OnDetailedContent; // 详细操作说明更新(甲方补充)
|
|||
|
|
public UnityEvent OnExperimentComplete; // 实验全部完成
|
|||
|
|
public UnityEvent<string, bool> OnVideoToggle; // 画中画视频开关(文件名, 是否激活)
|
|||
|
|
public UnityEvent<bool> OnModeUIUpdate; // 通知 UI 刷新模式按钮状态
|
|||
|
|
|
|||
|
|
// 私有状态
|
|||
|
|
private StepData[] _activeSteps;
|
|||
|
|
private int _currentStepIndex = -1;
|
|||
|
|
private ExperimentMode _mode;
|
|||
|
|
private FixationMethod _fixation;
|
|||
|
|
|
|||
|
|
public int CurrentStepIndex => _currentStepIndex;
|
|||
|
|
public int TotalSteps => _activeSteps != null ? _activeSteps.Length : 0;
|
|||
|
|
public ExperimentMode Mode => _mode;
|
|||
|
|
public FixationMethod Fixation => _fixation;
|
|||
|
|
|
|||
|
|
/// <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)
|
|||
|
|
{
|
|||
|
|
_mode = mode;
|
|||
|
|
_fixation = fixation;
|
|||
|
|
|
|||
|
|
Config = Resources.Load<ExperimentConfig>($"Configs/{type}Config");
|
|||
|
|
if (Config == null)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"StepManager: 找不到 ExperimentConfig for {type},请在 Resources/Configs/ 下创建");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_activeSteps = Config.GetStepsFor(mode, fixation);
|
|||
|
|
if (_activeSteps == null || _activeSteps.Length == 0)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"StepManager: 实验 {type} 模式 {mode} 固定方式 {fixation} 无步骤数据!");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_currentStepIndex = -1;
|
|||
|
|
|
|||
|
|
if (mode == ExperimentMode.Exam)
|
|||
|
|
GameManager.Instance.InitExamRecord(_activeSteps.Length);
|
|||
|
|
|
|||
|
|
OnModeUIUpdate?.Invoke(mode == ExperimentMode.Exam);
|
|||
|
|
GoToStep(0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>跳转到指定步骤(含边界检查)</summary>
|
|||
|
|
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);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>下一步</summary>
|
|||
|
|
public void NextStep()
|
|||
|
|
{
|
|||
|
|
if (_activeSteps == null) return;
|
|||
|
|
if (_currentStepIndex >= _activeSteps.Length - 1)
|
|||
|
|
{
|
|||
|
|
CompleteExperiment();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
GoToStep(_currentStepIndex + 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>上一步</summary>
|
|||
|
|
public void PreviousStep()
|
|||
|
|
{
|
|||
|
|
if (_activeSteps == null || _currentStepIndex <= 0) return;
|
|||
|
|
GoToStep(_currentStepIndex - 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>获取当前步骤数据(只读)</summary>
|
|||
|
|
public StepData GetCurrentStep()
|
|||
|
|
{
|
|||
|
|
if (_activeSteps == null || _currentStepIndex < 0 || _currentStepIndex >= _activeSteps.Length)
|
|||
|
|
return null;
|
|||
|
|
return _activeSteps[_currentStepIndex];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 当前步骤互动完成后的回调。
|
|||
|
|
/// proportion: 得分比例 0~1(SequenceClick 按子项命中比例,其余全对=1,全错=0)。
|
|||
|
|
/// </summary>
|
|||
|
|
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();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>实验完成</summary>
|
|||
|
|
private void CompleteExperiment()
|
|||
|
|
{
|
|||
|
|
Debug.Log("StepManager: 实验流程结束");
|
|||
|
|
OnExperimentComplete?.Invoke();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>切换模式(学习 ↔ 考核),从头开始</summary>
|
|||
|
|
public void SwitchMode(ExperimentMode newMode)
|
|||
|
|
{
|
|||
|
|
GameManager.Instance.CurrentMode = newMode;
|
|||
|
|
Initialize(GameManager.Instance.CurrentExperimentType, newMode, GameManager.Instance.CurrentFixationMethod);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>切换固定方式,从头开始</summary>
|
|||
|
|
public void SwitchFixation(FixationMethod newFixation)
|
|||
|
|
{
|
|||
|
|
GameManager.Instance.CurrentFixationMethod = newFixation;
|
|||
|
|
Initialize(GameManager.Instance.CurrentExperimentType, GameManager.Instance.CurrentMode, newFixation);
|
|||
|
|
}
|
|||
|
|
}
|