fix: WebGL关闭Brotli压缩解决服务器部署.br解析问题; 新增PageController编辑器页面预览下拉框; 创建FractureFixationConfig ScriptableObject; 修复ExperimentFlowCoordinator自动初始化问题

This commit is contained in:
yangbear
2026-06-24 06:05:55 +08:00
parent a6a7fd06d4
commit 3b4438731d
9 changed files with 494 additions and 47 deletions
@@ -3,7 +3,9 @@ using UnityEngine;
/// <summary>
/// 实验场景的总协调器:挂载在 Experiment 场景的根 GameObject 上,
/// 负责将 StepManager / InteractionSystem / UIManager 串联在一起。
/// 考核模式以 proportion0~1)计分。
///
/// 不再在 Start() 中自动初始化 StepManager——由 PageController 在用户选择
/// 固定方式后触发 InitializeFromGameManager()。
/// </summary>
public class ExperimentFlowCoordinator : MonoBehaviour
{
@@ -14,11 +16,15 @@ public class ExperimentFlowCoordinator : MonoBehaviour
[SerializeField] private StepAnimationHandler _animationHandler;
[SerializeField] private AudioManager _audioManager;
[Header("配置")]
[Header("兜底配置(当 StepManager.Config 未在 Inspector 中赋值时使用)")]
[SerializeField] private ExperimentConfig _defaultConfig;
/// <summary>供 StepManager 读取兜底配置</summary>
public ExperimentConfig DefaultConfig => _defaultConfig;
private void Start()
{
// 只绑定事件回调,不自动初始化实验流程
if (_interactionSystem != null && _stepManager != null)
{
_interactionSystem.OnObjectClicked.AddListener(OnObjectClicked);
@@ -30,12 +36,14 @@ public class ExperimentFlowCoordinator : MonoBehaviour
_stepManager.OnStepChanged.AddListener(_animationHandler.PlayStepAnimation);
}
if (_defaultConfig != null)
// 将兜底配置传给 StepManager
if (_stepManager != null && _stepManager.Config == null && _defaultConfig != null)
{
_stepManager.Config = _defaultConfig;
}
_stepManager.InitializeFromGameManager();
// 实验流程由 PageController 在用户选择完模式和固定方式后启动
Debug.Log("ExperimentFlowCoordinator: 就绪,等待 PageController 触发实验启动。");
}
#region
@@ -62,12 +70,10 @@ public class ExperimentFlowCoordinator : MonoBehaviour
break;
case StepCompleteType.SequenceClick:
// 按子项命中比例计分
bool isComplete;
bool isCorrect = _interactionSystem.TrySequenceClick(tag, out isComplete);
if (isComplete)
{
// 取当前序列已命中数 / 总子项数
int hit = _interactionSystem.GetSequenceHitCount();
int total = step.ClickSequenceTags?.Length ?? step.SubStepCount;
float proportion = total > 0 ? (float)hit / total : 1f;
+35 -26
View File
@@ -8,31 +8,33 @@ using UnityEngine.Events;
/// </summary>
public class StepManager : MonoBehaviour
{
[Header("配置")]
[Header("配置(优先级:Inspector直接赋值 > ExperimentFlowCoordinator兜底 > Resources加载)")]
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 刷新模式按钮状态
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>
/// <summary>用 GameManager 的当前设置初始化</summary>
public void InitializeFromGameManager()
{
var gm = GameManager.Instance;
@@ -43,16 +45,33 @@ public class StepManager : MonoBehaviour
/// <summary>显式指定参数初始化</summary>
public void Initialize(ExperimentType type, ExperimentMode mode, FixationMethod fixation)
{
_mode = mode;
_fixation = fixation;
Config = Resources.Load<ExperimentConfig>($"Configs/{type}Config");
// 如果 Inspector 里没有直接拖 Config,尝试从 ExperimentFlowCoordinator 获取兜底配置
if (Config == null)
{
Debug.LogError($"StepManager: 找不到 ExperimentConfig for {type},请在 Resources/Configs/ 下创建");
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)
{
@@ -61,6 +80,7 @@ public class StepManager : MonoBehaviour
}
_currentStepIndex = -1;
_initialized = true;
if (mode == ExperimentMode.Exam)
GameManager.Instance.InitExamRecord(_activeSteps.Length);
@@ -69,7 +89,6 @@ public class StepManager : MonoBehaviour
GoToStep(0);
}
/// <summary>跳转到指定步骤(含边界检查)</summary>
public void GoToStep(int index)
{
if (_activeSteps == null) return;
@@ -103,7 +122,6 @@ public class StepManager : MonoBehaviour
OnStepChanged?.Invoke(_currentStepIndex);
}
/// <summary>下一步</summary>
public void NextStep()
{
if (_activeSteps == null) return;
@@ -115,14 +133,12 @@ public class StepManager : MonoBehaviour
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)
@@ -130,10 +146,6 @@ public class StepManager : MonoBehaviour
return _activeSteps[_currentStepIndex];
}
/// <summary>
/// 当前步骤互动完成后的回调。
/// proportion: 得分比例 0~1SequenceClick 按子项命中比例,其余全对=1,全错=0)。
/// </summary>
public void OnStepInteractionComplete(float proportion = 1f, string feedback = "")
{
var step = GetCurrentStep();
@@ -149,21 +161,18 @@ public class StepManager : MonoBehaviour
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;
+59 -10
View File
@@ -36,7 +36,13 @@ public class PageController : MonoBehaviour
[SerializeField] [TextArea(2, 4)] private string _examModeDesc =
"● 考核评估模式:限定时间,完成后系统自动评分,并提供详细反馈报告。";
private enum Page { Home, ModeSelect, Fixation, Experiment }
[Header("=== 编辑器预览(仅 Editor 模式有效)===")]
[SerializeField] private EditorPreviewPage _editorPreview = EditorPreviewPage.None;
// Page 枚举改为 publicInspector 中可通过 _editorPreview 切换
public enum Page { Home, ModeSelect, Fixation, Experiment }
public enum EditorPreviewPage { None, Home, ModeSelect, Fixation, Experiment }
private Page _currentPage = Page.Home;
private ExperimentMode _selectedMode = ExperimentMode.Learn;
@@ -66,6 +72,58 @@ public class PageController : MonoBehaviour
ShowPage(Page.Home);
}
#if UNITY_EDITOR
/// <summary>
/// 在 Inspector 中修改 _editorPreview 下拉框时,立即切换 Scene View 中显示的页面,
/// 方便编辑不同页面的 UI 布局。仅在非运行状态下生效。
/// </summary>
private void OnValidate()
{
if (Application.isPlaying) return;
// 延迟一帧执行,避免在序列化过程中修改场景导致警告
UnityEditor.EditorApplication.delayCall += () =>
{
if (this == null) return;
if (Application.isPlaying) return;
switch (_editorPreview)
{
case EditorPreviewPage.None:
// 不做任何切换,保留当前状态
break;
case EditorPreviewPage.Home:
ShowPageEditMode(Page.Home);
break;
case EditorPreviewPage.ModeSelect:
ShowPageEditMode(Page.ModeSelect);
break;
case EditorPreviewPage.Fixation:
ShowPageEditMode(Page.Fixation);
break;
case EditorPreviewPage.Experiment:
ShowPageEditMode(Page.Experiment);
break;
}
// 切换完成后重置为 None,避免与后续操作冲突
_editorPreview = EditorPreviewPage.None;
};
}
private void ShowPageEditMode(Page page)
{
if (_homePagePanel != null) _homePagePanel.SetActive(page == Page.Home);
if (_modeSelectPanel != null) _modeSelectPanel.SetActive(page == Page.ModeSelect);
if (_fixationPanel != null) _fixationPanel.SetActive(page == Page.Fixation);
if (_uiManager != null)
_uiManager.enabled = (page == Page.Experiment);
Debug.Log($"PageController(编辑器预览): 切换到 [{page}]");
}
#endif
#region
private void ShowPage(Page page)
@@ -76,7 +134,6 @@ public class PageController : MonoBehaviour
if (_modeSelectPanel != null) _modeSelectPanel.SetActive(page == Page.ModeSelect);
if (_fixationPanel != null) _fixationPanel.SetActive(page == Page.Fixation);
// 实验内界面:隐藏所有实验 UI,等模式确定后再由 UIManager 激活
if (_uiManager != null)
_uiManager.enabled = (page == Page.Experiment);
}
@@ -99,18 +156,15 @@ public class PageController : MonoBehaviour
#endif
}
// 模式选择页 -> 返回首页
private void OnModeBackClicked()
{
ShowPage(Page.Home);
}
// 选择模式后 -> 进入固定方式选择页
private void OnModeSelected(ExperimentMode mode)
{
_selectedMode = mode;
// 更新说明文案显示当前选择
if (_modeDescText != null)
{
_modeDescText.text = mode == ExperimentMode.Learn
@@ -121,10 +175,8 @@ public class PageController : MonoBehaviour
ShowPage(Page.Fixation);
}
// 固定方式选择页 -> 返回模式选择页
private void OnFixationBackClicked()
{
// 恢复原始说明文案
if (_modeDescText != null)
{
_modeDescText.text = _practiceModeDesc + "\n\n" + _examModeDesc;
@@ -133,7 +185,6 @@ public class PageController : MonoBehaviour
ShowPage(Page.ModeSelect);
}
// 选择固定方式后 -> 进入实验
private void OnFixationSelected(FixationMethod method)
{
var gm = GameManager.Instance;
@@ -146,10 +197,8 @@ public class PageController : MonoBehaviour
gm.CurrentMode = _selectedMode;
gm.CurrentFixationMethod = method;
// 切换到实验界面
ShowPage(Page.Experiment);
// 启动实验流程
var stepManager = FindObjectOfType<StepManager>();
if (stepManager != null)
{