Files
VFSUnity/Assets/Scripts/Core/StepManager.cs
T
yangbear a11e003fcd feat: VFS 虚拟仿真实验核心框架 v2
- 加权评分系统:每步独立 ScoreWeight,满分 100,SequenceClick 按子项比例计分
- StepData 新增 DetailedContent(学习模式详细操作说明)和 SubStepCount
- 伤情检查步骤扩展为 5 子项序列(视觉检查→肿胀→疼痛→生命体征→神经评估)
- 夹板固定模式完整评分(选择夹板→放置衬垫→夹板固定→固定后检查)
- Doc/ 文件夹归档三份文档:项目大纲、文案、评分标准
- README 更新评分权重表和夹板固定流程
2026-06-24 02:07:43 +08:00

173 lines
6.1 KiB
C#
Raw 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("配置")]
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~1SequenceClick 按子项命中比例,其余全对=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);
}
}