2026-07-12 20:34:49 +08:00
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using UnityEngine.UI;
|
|
|
|
|
|
|
|
|
|
|
|
public class ExamFlowManager : MonoBehaviour
|
|
|
|
|
|
{
|
|
|
|
|
|
private const int StepCount = 7;
|
2026-07-17 05:42:52 +08:00
|
|
|
|
private const string ManMubanName = "manMuban";
|
|
|
|
|
|
private const string ManInjuryName = "manInjury";
|
2026-07-12 20:34:49 +08:00
|
|
|
|
|
|
|
|
|
|
private readonly int[] _stepScores = { 6, 22, 11, 11, 11, 22, 17 };
|
|
|
|
|
|
private readonly bool[] _answered = new bool[StepCount];
|
|
|
|
|
|
private readonly bool[] _correct = new bool[StepCount];
|
|
|
|
|
|
private readonly int[] _earned = new int[StepCount];
|
|
|
|
|
|
private readonly string[] _feedback = new string[StepCount];
|
|
|
|
|
|
|
|
|
|
|
|
private readonly HashSet<string> _placedPings = new HashSet<string>();
|
|
|
|
|
|
private readonly HashSet<string> _wrappedPings = new HashSet<string>();
|
2026-07-17 05:42:52 +08:00
|
|
|
|
private readonly HashSet<SplintObjectClickHandler> _clickedSplintPlanks = new HashSet<SplintObjectClickHandler>();
|
2026-07-12 20:34:49 +08:00
|
|
|
|
|
|
|
|
|
|
private Transform _dialogueBubble;
|
|
|
|
|
|
private GameObject _gradeResult;
|
|
|
|
|
|
private GameObject _endPanel;
|
2026-07-17 05:42:52 +08:00
|
|
|
|
private GameObject _manMuban;
|
|
|
|
|
|
private GameObject _manInjury;
|
2026-07-12 20:34:49 +08:00
|
|
|
|
private Button _dialogueYesButton;
|
|
|
|
|
|
private Button _dialogueBackButton;
|
|
|
|
|
|
private Text _gradeContent;
|
|
|
|
|
|
private Text _gradeScoreText;
|
2026-07-17 05:42:52 +08:00
|
|
|
|
private Font _cachedChineseFont;
|
|
|
|
|
|
private PingClickHandler[] _splintPings;
|
|
|
|
|
|
private SplintObjectClickHandler _splintBodyPad;
|
|
|
|
|
|
private SplintObjectClickHandler[] _splintPlanks;
|
2026-07-12 20:34:49 +08:00
|
|
|
|
private int _currentStep;
|
|
|
|
|
|
private bool _examActive;
|
|
|
|
|
|
private bool _waitingForPoint2;
|
|
|
|
|
|
private bool _waitingForShose;
|
|
|
|
|
|
private bool _waitingForWrapClicks;
|
2026-07-17 05:42:52 +08:00
|
|
|
|
private bool _splintBodyClicked;
|
|
|
|
|
|
|
|
|
|
|
|
private bool IsSplintExam
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return GameManager.Instance != null &&
|
|
|
|
|
|
GameManager.Instance.CurrentFixationMethod == FixationMethod.SplintFixation;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-07-12 20:34:49 +08:00
|
|
|
|
|
|
|
|
|
|
private void Awake()
|
|
|
|
|
|
{
|
|
|
|
|
|
AutoBind();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void Start()
|
|
|
|
|
|
{
|
|
|
|
|
|
AutoBind();
|
|
|
|
|
|
BindButtons();
|
|
|
|
|
|
EnsureToggleGroups();
|
|
|
|
|
|
HideAllPanels();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void Update()
|
|
|
|
|
|
{
|
|
|
|
|
|
bool shouldRun = GameManager.Instance != null &&
|
|
|
|
|
|
GameManager.Instance.CurrentMode == ExperimentMode.Exam &&
|
|
|
|
|
|
IsExperimentPageVisible();
|
|
|
|
|
|
|
|
|
|
|
|
if (!shouldRun && _examActive)
|
|
|
|
|
|
{
|
|
|
|
|
|
ResetExamState(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void BeginExam()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (GameManager.Instance == null ||
|
|
|
|
|
|
GameManager.Instance.CurrentMode != ExperimentMode.Exam ||
|
|
|
|
|
|
!IsExperimentPageVisible())
|
|
|
|
|
|
{
|
|
|
|
|
|
ResetExamState(false);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
AutoBind();
|
|
|
|
|
|
BindButtons();
|
|
|
|
|
|
EnsureToggleGroups();
|
|
|
|
|
|
ResetExamState(true);
|
|
|
|
|
|
_examActive = true;
|
|
|
|
|
|
_waitingForPoint2 = true;
|
|
|
|
|
|
|
|
|
|
|
|
if (GameManager.Instance != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
GameManager.Instance.InitExamRecord(StepCount);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ForceHideExamOnlyOutlines();
|
|
|
|
|
|
MoveWomanToExamPoint();
|
2026-07-17 05:42:52 +08:00
|
|
|
|
ConfigureExamModelState();
|
2026-07-12 20:34:49 +08:00
|
|
|
|
ShowStep(1);
|
2026-07-17 05:42:52 +08:00
|
|
|
|
Debug.Log("[ExamFlow] 考核模式流程启动: " + GameManager.Instance.CurrentFixationMethod);
|
2026-07-12 20:34:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void StopExam()
|
|
|
|
|
|
{
|
|
|
|
|
|
ResetExamState(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void ShowStep(int stepNumber)
|
|
|
|
|
|
{
|
|
|
|
|
|
AutoBind();
|
|
|
|
|
|
_currentStep = Mathf.Clamp(stepNumber, 1, StepCount);
|
|
|
|
|
|
|
|
|
|
|
|
if (_dialogueBubble == null) return;
|
|
|
|
|
|
_dialogueBubble.gameObject.SetActive(true);
|
|
|
|
|
|
EnsureStepPanelsDoNotBlockButtons();
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < _dialogueBubble.childCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform child = _dialogueBubble.GetChild(i);
|
|
|
|
|
|
if (child.name.StartsWith("step"))
|
|
|
|
|
|
{
|
|
|
|
|
|
child.gameObject.SetActive(child.name == "step" + _currentStep);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ApplyStepText(_currentStep);
|
|
|
|
|
|
if (_currentStep == 3 || _currentStep == 7)
|
|
|
|
|
|
{
|
|
|
|
|
|
ResetStepToggles(_currentStep);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-17 05:42:52 +08:00
|
|
|
|
ShowTip(GetExamTipText(_currentStep));
|
|
|
|
|
|
|
2026-07-12 20:34:49 +08:00
|
|
|
|
Debug.Log("[ExamFlow] 显示 step" + _currentStep);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void OnObjectClicked(Component clicked)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_examActive || clicked == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (clicked is Point2ClickHandler)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_waitingForPoint2 && clicked.name == "point2")
|
|
|
|
|
|
{
|
|
|
|
|
|
_waitingForPoint2 = false;
|
|
|
|
|
|
RecordStep(1, true, "已按要求完成前置操作起始步骤。");
|
2026-07-17 05:42:52 +08:00
|
|
|
|
ShowTip("伤情评估已完成。下一步:阅读弹窗提示,准备完成前置处理。");
|
2026-07-12 20:34:49 +08:00
|
|
|
|
StartCoroutine(ShowStepDelayed(2, 1f));
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (clicked is ShoseClickHandler)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_waitingForShose || _currentStep <= 2)
|
|
|
|
|
|
{
|
|
|
|
|
|
_waitingForShose = false;
|
2026-07-17 05:42:52 +08:00
|
|
|
|
RecordStep(2, true, IsSplintExam ? "已完成夹板固定前置处理。" : "已完成下肢固定前置处理。");
|
|
|
|
|
|
ShowTip(IsSplintExam ? "夹板固定前置处理完成。下一步:选择固定带宽度。" : "下肢固定前置处理完成。下一步:选择绑带宽度。");
|
2026-07-12 20:34:49 +08:00
|
|
|
|
StartCoroutine(ShowStepDelayed(3, 1f));
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var ping = clicked as PingClickHandler;
|
|
|
|
|
|
if (ping != null)
|
|
|
|
|
|
{
|
2026-07-17 05:42:52 +08:00
|
|
|
|
if (IsSplintExam)
|
|
|
|
|
|
{
|
|
|
|
|
|
HandleSplintPingClicked(ping);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-12 20:34:49 +08:00
|
|
|
|
bool isWrapClick = ping.chanraoObject != null && ping.chanraoObject.activeSelf;
|
|
|
|
|
|
if (isWrapClick)
|
|
|
|
|
|
{
|
|
|
|
|
|
_wrappedPings.Add(ping.name);
|
|
|
|
|
|
Debug.Log("[ExamFlow] 系带点击 " + _wrappedPings.Count + "/4: " + ping.name);
|
|
|
|
|
|
|
|
|
|
|
|
if (_waitingForWrapClicks && _wrappedPings.Count >= 4)
|
|
|
|
|
|
{
|
|
|
|
|
|
_waitingForWrapClicks = false;
|
2026-07-17 05:42:52 +08:00
|
|
|
|
RecordStep(5, true, "已按由远端到近端的顺序完成系带。");
|
|
|
|
|
|
ShowTip("系带顺序已完成。下一步:阅读打结位置判断题。");
|
2026-07-12 20:34:49 +08:00
|
|
|
|
ShowStep(6);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
_placedPings.Add(ping.name);
|
|
|
|
|
|
Debug.Log("[ExamFlow] 放带点击 " + _placedPings.Count + "/4: " + ping.name);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-17 05:42:52 +08:00
|
|
|
|
var splintObject = clicked as SplintObjectClickHandler;
|
|
|
|
|
|
if (splintObject != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (IsSplintExam)
|
|
|
|
|
|
{
|
|
|
|
|
|
HandleSplintObjectClicked(splintObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-12 20:34:49 +08:00
|
|
|
|
if (clicked is ChanraoTuibuHandler)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_answered[3])
|
|
|
|
|
|
{
|
|
|
|
|
|
RecordStep(4, _placedPings.Count >= 4, _placedPings.Count >= 4
|
|
|
|
|
|
? "已按要求完成四条绑带摆放。"
|
|
|
|
|
|
: "绑带尚未全部摆放完成。");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_waitingForWrapClicks = true;
|
2026-07-17 05:42:52 +08:00
|
|
|
|
ShowTip("环绕固定带已显示。下一步:依次点击 4 个蓝色绑带点完成系带固定。");
|
2026-07-12 20:34:49 +08:00
|
|
|
|
ShowStep(5);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-17 05:42:52 +08:00
|
|
|
|
private void HandleSplintPingClicked(PingClickHandler ping)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!IsSplintPing(ping)) return;
|
|
|
|
|
|
|
|
|
|
|
|
bool isFinalFixClick = ping.chanraoObject != null && ping.chanraoObject.activeSelf;
|
|
|
|
|
|
if (isFinalFixClick)
|
|
|
|
|
|
{
|
|
|
|
|
|
_wrappedPings.Add(ping.name);
|
|
|
|
|
|
Debug.Log("[ExamFlow] 夹板固定点击 " + _wrappedPings.Count + "/4: " + ping.name);
|
|
|
|
|
|
|
|
|
|
|
|
if (_waitingForWrapClicks && _wrappedPings.Count >= 4)
|
|
|
|
|
|
{
|
|
|
|
|
|
_waitingForWrapClicks = false;
|
|
|
|
|
|
RecordStep(6, true, "已按由远端到近端的顺序完成夹板固定。");
|
|
|
|
|
|
ShowTip("夹板系带固定已完成。下一步:选择固定后末梢观察位置。");
|
|
|
|
|
|
ShowStep(7);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_placedPings.Add(ping.name);
|
|
|
|
|
|
Debug.Log("[ExamFlow] 夹板绑带摆放 " + _placedPings.Count + "/4: " + ping.name);
|
|
|
|
|
|
|
|
|
|
|
|
if (_placedPings.Count >= 4 && !_answered[3])
|
|
|
|
|
|
{
|
|
|
|
|
|
RecordStep(4, true, "已按由远端到近端的顺序预置四条绑带。");
|
|
|
|
|
|
ShowTip("四条固定带已预置。下一步:阅读弹窗,准备放置衬垫和夹板。");
|
|
|
|
|
|
ShowStep(5);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void HandleSplintObjectClicked(SplintObjectClickHandler splintObject)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (splintObject == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (splintObject == _splintBodyPad)
|
|
|
|
|
|
{
|
|
|
|
|
|
_splintBodyClicked = true;
|
|
|
|
|
|
ShowSplintPlankTargets();
|
|
|
|
|
|
Debug.Log("[ExamFlow] 已放置夹板衬垫 Body.001");
|
|
|
|
|
|
ShowTip("衬垫已放置。下一步:依次点击两块木板蓝色边框,放置伤肢两侧夹板。");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!_splintBodyClicked) return;
|
|
|
|
|
|
|
|
|
|
|
|
_clickedSplintPlanks.Add(splintObject);
|
|
|
|
|
|
Debug.Log("[ExamFlow] 已放置夹板 " + _clickedSplintPlanks.Count + "/2: " + splintObject.name);
|
|
|
|
|
|
|
|
|
|
|
|
if (_clickedSplintPlanks.Count >= 2 && !_answered[4])
|
|
|
|
|
|
{
|
|
|
|
|
|
RecordStep(5, true, "已先放置衬垫,再完成伤肢两侧夹板放置。");
|
|
|
|
|
|
ShowTip("两侧夹板已放置。下一步:阅读弹窗,准备按顺序系带固定。");
|
|
|
|
|
|
ShowStep(6);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-12 20:34:49 +08:00
|
|
|
|
private IEnumerator ShowStepDelayed(int stepNumber, float delay)
|
|
|
|
|
|
{
|
|
|
|
|
|
yield return new WaitForSeconds(delay);
|
|
|
|
|
|
ShowStep(stepNumber);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnDialogueYesClicked()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_examActive)
|
|
|
|
|
|
{
|
|
|
|
|
|
HideDialogue();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch (_currentStep)
|
|
|
|
|
|
{
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
HideDialogue();
|
|
|
|
|
|
_waitingForPoint2 = true;
|
2026-07-17 05:42:52 +08:00
|
|
|
|
ShowTip("请点击伤员大腿附近,完成伤情评估。");
|
2026-07-12 20:34:49 +08:00
|
|
|
|
break;
|
|
|
|
|
|
case 2:
|
|
|
|
|
|
HideDialogue();
|
|
|
|
|
|
_waitingForShose = true;
|
2026-07-17 05:42:52 +08:00
|
|
|
|
ShowTip("请点击伤侧鞋子,完成脱鞋和前置处理。");
|
2026-07-12 20:34:49 +08:00
|
|
|
|
break;
|
|
|
|
|
|
case 3:
|
|
|
|
|
|
RecordStep(3, GetSelectedToggleIndex(3) == 1, "正确答案:10cm。");
|
|
|
|
|
|
ShowStep(4);
|
2026-07-17 05:42:52 +08:00
|
|
|
|
if (IsSplintExam) ActivateFirstSplintPing();
|
|
|
|
|
|
else ActivateFirstPing();
|
|
|
|
|
|
ShowTip(IsSplintExam
|
|
|
|
|
|
? "请由脚踝向大腿依次点击 4 个蓝色绑带点,预置固定带。"
|
|
|
|
|
|
: "请由脚踝向大腿依次点击 4 个蓝色绑带点,摆放绑带。");
|
2026-07-12 20:34:49 +08:00
|
|
|
|
break;
|
|
|
|
|
|
case 4:
|
|
|
|
|
|
HideDialogue();
|
2026-07-17 05:42:52 +08:00
|
|
|
|
ShowTip(IsSplintExam
|
|
|
|
|
|
? "请继续由远端向近端点击蓝色绑带点,完成 4 条固定带预置。"
|
|
|
|
|
|
: "请继续由远端向近端点击蓝色绑带点,完成 4 条绑带摆放。");
|
2026-07-12 20:34:49 +08:00
|
|
|
|
break;
|
|
|
|
|
|
case 5:
|
|
|
|
|
|
HideDialogue();
|
2026-07-17 05:42:52 +08:00
|
|
|
|
if (IsSplintExam)
|
|
|
|
|
|
{
|
|
|
|
|
|
ShowSplintBodyTarget();
|
|
|
|
|
|
ShowTip("请点击 Body.001 蓝色边框,先放置夹板衬垫。");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
_waitingForWrapClicks = true;
|
|
|
|
|
|
ShowTip("请依次点击蓝色绑带点,按由远端到近端顺序完成系带。");
|
|
|
|
|
|
}
|
2026-07-12 20:34:49 +08:00
|
|
|
|
break;
|
|
|
|
|
|
case 6:
|
2026-07-17 05:42:52 +08:00
|
|
|
|
if (IsSplintExam)
|
|
|
|
|
|
{
|
|
|
|
|
|
HideDialogue();
|
|
|
|
|
|
_waitingForWrapClicks = true;
|
|
|
|
|
|
EnableSplintFinalPingStage();
|
|
|
|
|
|
ShowTip("请由脚踝向大腿依次点击 4 个蓝色固定点,完成夹板系带固定。");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
RecordStep(6, false, "错误操作:在受伤腿一侧打结。正确做法是在未受伤腿位置打结。");
|
|
|
|
|
|
ShowStep(7);
|
|
|
|
|
|
}
|
2026-07-12 20:34:49 +08:00
|
|
|
|
break;
|
|
|
|
|
|
case 7:
|
|
|
|
|
|
RecordStep(7, GetSelectedToggleIndex(7) == 1, "正确答案:足背。");
|
2026-07-17 05:42:52 +08:00
|
|
|
|
ShowTip("考核答题完成,正在生成成绩汇总。");
|
2026-07-12 20:34:49 +08:00
|
|
|
|
ShowGradeResult();
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
HideDialogue();
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ShowGradeResult()
|
|
|
|
|
|
{
|
|
|
|
|
|
AutoBind();
|
|
|
|
|
|
BindResultButtons();
|
|
|
|
|
|
BindEndPanelButtons();
|
|
|
|
|
|
HideDialogue();
|
|
|
|
|
|
int totalScore;
|
|
|
|
|
|
string gradeContent = BuildGradeContent(out totalScore);
|
|
|
|
|
|
if (_gradeResult != null) _gradeResult.SetActive(true);
|
2026-07-17 05:42:52 +08:00
|
|
|
|
if (_gradeContent != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ApplyGradeTextStyle(_gradeContent);
|
|
|
|
|
|
_gradeContent.text = gradeContent;
|
|
|
|
|
|
}
|
2026-07-12 20:34:49 +08:00
|
|
|
|
if (_gradeScoreText != null) _gradeScoreText.text = totalScore.ToString();
|
2026-07-14 03:46:59 +08:00
|
|
|
|
RefreshGradeContentLayout();
|
2026-07-12 20:34:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string BuildGradeContent(out int total)
|
|
|
|
|
|
{
|
|
|
|
|
|
total = 0;
|
|
|
|
|
|
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
|
|
|
|
|
sb.AppendLine("考核结果汇总");
|
|
|
|
|
|
sb.AppendLine();
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < StepCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
total += _earned[i];
|
|
|
|
|
|
bool answered = _answered[i];
|
|
|
|
|
|
bool correct = answered && _correct[i];
|
|
|
|
|
|
|
|
|
|
|
|
sb.AppendFormat("考核点{0}({1}分):{2}", i + 1, _stepScores[i], correct ? "正确" : "错误");
|
|
|
|
|
|
if (!answered) sb.Append("(未完成)");
|
|
|
|
|
|
sb.AppendLine();
|
|
|
|
|
|
sb.AppendLine("题目:" + GetQuestionText(i + 1));
|
|
|
|
|
|
sb.AppendLine("答案:" + GetAnswerText(i + 1));
|
|
|
|
|
|
sb.AppendFormat("得分:{0}/{1}", _earned[i], _stepScores[i]);
|
|
|
|
|
|
sb.AppendLine();
|
|
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(_feedback[i]) && (!correct || i == 5))
|
|
|
|
|
|
{
|
|
|
|
|
|
sb.AppendLine("说明:" + _feedback[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sb.AppendLine();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sb.AppendFormat("最终得分:{0}/100", total);
|
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void RecordStep(int oneBasedStep, bool isCorrect, string feedback)
|
|
|
|
|
|
{
|
|
|
|
|
|
int index = oneBasedStep - 1;
|
|
|
|
|
|
if (index < 0 || index >= StepCount || _answered[index]) return;
|
|
|
|
|
|
|
|
|
|
|
|
_answered[index] = true;
|
|
|
|
|
|
_correct[index] = isCorrect;
|
|
|
|
|
|
_earned[index] = isCorrect ? _stepScores[index] : 0;
|
|
|
|
|
|
_feedback[index] = feedback;
|
|
|
|
|
|
|
|
|
|
|
|
if (GameManager.Instance != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
GameManager.Instance.RecordExamResult(index, _stepScores[index], isCorrect ? 1f : 0f, feedback);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ActivateFirstPing()
|
|
|
|
|
|
{
|
2026-07-14 03:46:59 +08:00
|
|
|
|
PingClickHandler firstHandler = null;
|
|
|
|
|
|
foreach (var ping in Resources.FindObjectsOfTypeAll<PingClickHandler>())
|
2026-07-12 20:34:49 +08:00
|
|
|
|
{
|
2026-07-14 03:46:59 +08:00
|
|
|
|
if (ping == null || !ping.gameObject.scene.IsValid()) continue;
|
|
|
|
|
|
if (ping.name == "ping.004" || ping.pingIndex == 0)
|
2026-07-12 20:34:49 +08:00
|
|
|
|
{
|
2026-07-14 03:46:59 +08:00
|
|
|
|
firstHandler = ping;
|
|
|
|
|
|
break;
|
2026-07-12 20:34:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-14 03:46:59 +08:00
|
|
|
|
if (firstHandler != null)
|
2026-07-12 20:34:49 +08:00
|
|
|
|
{
|
2026-07-14 03:46:59 +08:00
|
|
|
|
firstHandler.gameObject.SetActive(true);
|
|
|
|
|
|
firstHandler.ResetState(true);
|
2026-07-12 20:34:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-17 05:42:52 +08:00
|
|
|
|
private void ActivateFirstSplintPing()
|
|
|
|
|
|
{
|
|
|
|
|
|
ConfigureSplintObjects();
|
|
|
|
|
|
PingClickHandler firstHandler = GetSplintPingByIndex(0);
|
|
|
|
|
|
if (firstHandler == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
firstHandler.gameObject.SetActive(true);
|
|
|
|
|
|
firstHandler.ResetState(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ConfigureExamModelState()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (IsSplintExam)
|
|
|
|
|
|
{
|
|
|
|
|
|
ConfigureSplintObjects();
|
|
|
|
|
|
if (_manInjury != null) _manInjury.SetActive(false);
|
|
|
|
|
|
if (_manMuban != null) _manMuban.SetActive(true);
|
|
|
|
|
|
HideSplintObjects();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_manMuban == null) _manMuban = FindSceneObject(ManMubanName);
|
|
|
|
|
|
if (_manMuban != null) _manMuban.SetActive(false);
|
|
|
|
|
|
if (_manInjury == null) _manInjury = FindSceneObject(ManInjuryName);
|
|
|
|
|
|
if (_manInjury != null) _manInjury.SetActive(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ConfigureSplintObjects()
|
|
|
|
|
|
{
|
|
|
|
|
|
_manMuban = FindSceneObject(ManMubanName);
|
|
|
|
|
|
_manInjury = FindSceneObject(ManInjuryName);
|
|
|
|
|
|
if (_manMuban == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
ConfigureSplintPings();
|
|
|
|
|
|
ConfigureSplintBodyPad();
|
|
|
|
|
|
ConfigureSplintPlanks();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ConfigureSplintPings()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_manMuban == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
_splintPings = new PingClickHandler[4];
|
|
|
|
|
|
string[] guideNames = { "ping.001", "ping.002", "ping.003", "ping.004" };
|
|
|
|
|
|
string[] targetNames = { "Armature/Bottoms.001", "ping.005", "ping.006", "ping.007" };
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < guideNames.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform guide = _manMuban.transform.Find(guideNames[i]);
|
|
|
|
|
|
Transform target = _manMuban.transform.Find(targetNames[i]);
|
|
|
|
|
|
if (guide == null || target == null) continue;
|
|
|
|
|
|
|
|
|
|
|
|
PingClickHandler handler = guide.GetComponent<PingClickHandler>();
|
|
|
|
|
|
if (handler == null) handler = guide.gameObject.AddComponent<PingClickHandler>();
|
|
|
|
|
|
|
|
|
|
|
|
handler.pingIndex = i;
|
|
|
|
|
|
handler.chanraoObject = target.gameObject;
|
|
|
|
|
|
handler.nextPing = null;
|
|
|
|
|
|
_splintPings[i] = handler;
|
|
|
|
|
|
|
|
|
|
|
|
target.gameObject.SetActive(false);
|
|
|
|
|
|
guide.gameObject.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < _splintPings.Length - 1; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_splintPings[i] != null && _splintPings[i + 1] != null)
|
|
|
|
|
|
_splintPings[i].nextPing = _splintPings[i + 1].gameObject;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ConfigureSplintBodyPad()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_manMuban == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
Transform body = _manMuban.transform.Find("Body.001");
|
|
|
|
|
|
if (body == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
_splintBodyPad = body.GetComponent<SplintObjectClickHandler>();
|
|
|
|
|
|
if (_splintBodyPad == null) _splintBodyPad = body.gameObject.AddComponent<SplintObjectClickHandler>();
|
|
|
|
|
|
body.gameObject.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ConfigureSplintPlanks()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_manMuban == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
string[] plankNames = { "Wood plank - warm oak board", "Wood plank - warm oak board.001" };
|
|
|
|
|
|
List<SplintObjectClickHandler> planks = new List<SplintObjectClickHandler>();
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < plankNames.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform plank = _manMuban.transform.Find(plankNames[i]);
|
|
|
|
|
|
if (plank == null) continue;
|
|
|
|
|
|
|
|
|
|
|
|
SplintObjectClickHandler handler = plank.GetComponent<SplintObjectClickHandler>();
|
|
|
|
|
|
if (handler == null) handler = plank.gameObject.AddComponent<SplintObjectClickHandler>();
|
|
|
|
|
|
planks.Add(handler);
|
|
|
|
|
|
plank.gameObject.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_splintPlanks = planks.ToArray();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void HideSplintObjects()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_manMuban == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
string[] names =
|
|
|
|
|
|
{
|
|
|
|
|
|
"ping.001", "ping.002", "ping.003", "ping.004",
|
|
|
|
|
|
"ping.005", "ping.006", "ping.007",
|
|
|
|
|
|
"Body.001",
|
|
|
|
|
|
"Wood plank - warm oak board", "Wood plank - warm oak board.001"
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < names.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform target = _manMuban.transform.Find(names[i]);
|
|
|
|
|
|
if (target != null) target.gameObject.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Transform bottoms = _manMuban.transform.Find("Armature/Bottoms.001");
|
|
|
|
|
|
if (bottoms != null) bottoms.gameObject.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ShowSplintBodyTarget()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_splintBodyPad == null) ConfigureSplintBodyPad();
|
|
|
|
|
|
if (_splintBodyPad == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
_splintBodyPad.gameObject.SetActive(true);
|
|
|
|
|
|
_splintBodyPad.ResetState(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ShowSplintPlankTargets()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_splintPlanks == null || _splintPlanks.Length == 0) ConfigureSplintPlanks();
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < _splintPlanks.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_splintPlanks[i] == null) continue;
|
|
|
|
|
|
_splintPlanks[i].gameObject.SetActive(true);
|
|
|
|
|
|
_splintPlanks[i].ResetState(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void EnableSplintFinalPingStage()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_splintPings == null || _splintPings.Length == 0) ConfigureSplintPings();
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < _splintPings.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_splintPings[i] == null) continue;
|
|
|
|
|
|
_splintPings[i].gameObject.SetActive(true);
|
|
|
|
|
|
_splintPings[i].ReEnableBorder();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private PingClickHandler GetSplintPingByIndex(int index)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_splintPings == null || _splintPings.Length == 0) ConfigureSplintPings();
|
|
|
|
|
|
if (_splintPings == null || index < 0 || index >= _splintPings.Length) return null;
|
|
|
|
|
|
return _splintPings[index];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool IsSplintPing(PingClickHandler ping)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ping == null || _splintPings == null) return false;
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < _splintPings.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_splintPings[i] == ping) return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-12 20:34:49 +08:00
|
|
|
|
private void AutoBind()
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform canvas = FindSceneObject("Canvas")?.transform;
|
|
|
|
|
|
if (canvas == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (_dialogueBubble == null) _dialogueBubble = canvas.Find("DialogueBubble");
|
|
|
|
|
|
if (_gradeResult == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform t = canvas.Find("GradeResult");
|
|
|
|
|
|
if (t != null) _gradeResult = t.gameObject;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_endPanel == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform t = canvas.Find("EndPanel");
|
|
|
|
|
|
if (t != null) _endPanel = t.gameObject;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (_dialogueBubble != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_dialogueYesButton = _dialogueBubble.Find("BtnYes")?.GetComponent<Button>();
|
|
|
|
|
|
_dialogueBackButton = _dialogueBubble.Find("BtnBack")?.GetComponent<Button>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (_gradeResult != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform grade = _gradeResult.transform.Find("Grade");
|
|
|
|
|
|
if (grade != null) _gradeScoreText = grade.GetComponent<Text>();
|
|
|
|
|
|
|
|
|
|
|
|
_gradeContent = FindGradeDetailText(_gradeResult.transform);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Text FindGradeDetailText(Transform root)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (root == null) return null;
|
|
|
|
|
|
|
|
|
|
|
|
Transform scrollView = root.Find("Scroll View");
|
|
|
|
|
|
if (scrollView != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform viewportContent = scrollView.Find("Viewport/Content");
|
|
|
|
|
|
Text text = EnsureTextOnContent(viewportContent);
|
|
|
|
|
|
if (text != null) return text;
|
|
|
|
|
|
|
|
|
|
|
|
text = FindTextByName(scrollView, "Content");
|
|
|
|
|
|
if (text != null) return text;
|
|
|
|
|
|
|
|
|
|
|
|
text = FindTextByName(scrollView, "content");
|
|
|
|
|
|
if (text != null) return text;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Transform legacyContent = root.Find("content");
|
|
|
|
|
|
return legacyContent != null ? legacyContent.GetComponent<Text>() : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Text EnsureTextOnContent(Transform content)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (content == null) return null;
|
|
|
|
|
|
|
2026-07-17 05:42:52 +08:00
|
|
|
|
HideLegacyContentTextChildren(content);
|
|
|
|
|
|
|
2026-07-12 20:34:49 +08:00
|
|
|
|
Text text = content.GetComponent<Text>();
|
|
|
|
|
|
|
|
|
|
|
|
if (text == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
text = content.gameObject.AddComponent<Text>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-17 05:42:52 +08:00
|
|
|
|
ApplyGradeTextStyle(text);
|
|
|
|
|
|
|
2026-07-14 03:46:59 +08:00
|
|
|
|
RectTransform rect = content.GetComponent<RectTransform>();
|
|
|
|
|
|
if (rect != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
rect.anchorMin = new Vector2(0f, 1f);
|
|
|
|
|
|
rect.anchorMax = new Vector2(1f, 1f);
|
|
|
|
|
|
rect.pivot = new Vector2(0f, 1f);
|
|
|
|
|
|
rect.anchoredPosition = Vector2.zero;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ContentSizeFitter fitter = content.GetComponent<ContentSizeFitter>();
|
|
|
|
|
|
if (fitter == null) fitter = content.gameObject.AddComponent<ContentSizeFitter>();
|
|
|
|
|
|
fitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
|
|
|
|
|
|
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
|
|
|
|
|
|
2026-07-12 20:34:49 +08:00
|
|
|
|
return text;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-17 05:42:52 +08:00
|
|
|
|
private void HideLegacyContentTextChildren(Transform content)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (content == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < content.childCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform child = content.GetChild(i);
|
|
|
|
|
|
if (child == null) continue;
|
|
|
|
|
|
|
|
|
|
|
|
if (child.GetComponent<Text>() != null || child.GetComponentInChildren<Text>(true) != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
child.gameObject.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ApplyGradeTextStyle(Text text)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (text == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
Font chineseFont = ResolveChineseUiFont(text);
|
|
|
|
|
|
if (chineseFont != null) text.font = chineseFont;
|
|
|
|
|
|
|
|
|
|
|
|
text.fontSize = 22;
|
|
|
|
|
|
text.color = new Color(0.12f, 0.12f, 0.12f, 1f);
|
|
|
|
|
|
text.alignment = TextAnchor.UpperLeft;
|
|
|
|
|
|
text.horizontalOverflow = HorizontalWrapMode.Wrap;
|
|
|
|
|
|
text.verticalOverflow = VerticalWrapMode.Overflow;
|
|
|
|
|
|
text.supportRichText = false;
|
|
|
|
|
|
text.raycastTarget = false;
|
|
|
|
|
|
|
|
|
|
|
|
RectTransform rect = text.GetComponent<RectTransform>();
|
|
|
|
|
|
if (rect != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
rect.anchorMin = new Vector2(0f, 1f);
|
|
|
|
|
|
rect.anchorMax = new Vector2(1f, 1f);
|
|
|
|
|
|
rect.pivot = new Vector2(0f, 1f);
|
|
|
|
|
|
rect.anchoredPosition = Vector2.zero;
|
|
|
|
|
|
rect.offsetMin = new Vector2(0f, rect.offsetMin.y);
|
|
|
|
|
|
rect.offsetMax = new Vector2(0f, rect.offsetMax.y);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Font ResolveChineseUiFont(Text exclude)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_cachedChineseFont != null) return _cachedChineseFont;
|
|
|
|
|
|
|
|
|
|
|
|
Text[] texts = Resources.FindObjectsOfTypeAll<Text>();
|
|
|
|
|
|
for (int i = 0; i < texts.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Text candidate = texts[i];
|
|
|
|
|
|
if (candidate == null || candidate == exclude || candidate.font == null) continue;
|
|
|
|
|
|
if (!candidate.gameObject.scene.IsValid()) continue;
|
|
|
|
|
|
if (!ContainsChinese(candidate.text)) continue;
|
|
|
|
|
|
|
|
|
|
|
|
_cachedChineseFont = candidate.font;
|
|
|
|
|
|
return _cachedChineseFont;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return exclude != null ? exclude.font : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool ContainsChinese(string value)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(value)) return false;
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < value.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
char ch = value[i];
|
|
|
|
|
|
if (ch >= '\u4e00' && ch <= '\u9fff') return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-12 20:34:49 +08:00
|
|
|
|
private Text FindTextByName(Transform root, string objectName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (root == null) return null;
|
|
|
|
|
|
|
|
|
|
|
|
Text direct = root.name == objectName ? root.GetComponent<Text>() : null;
|
|
|
|
|
|
if (direct != null) return direct;
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < root.childCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Text childText = FindTextByName(root.GetChild(i), objectName);
|
|
|
|
|
|
if (childText != null) return childText;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BindButtons()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_dialogueYesButton != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_dialogueYesButton.onClick.RemoveAllListeners();
|
|
|
|
|
|
_dialogueYesButton.interactable = true;
|
|
|
|
|
|
_dialogueYesButton.enabled = true;
|
|
|
|
|
|
_dialogueYesButton.onClick.AddListener(OnDialogueYesClicked);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (_dialogueBackButton != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_dialogueBackButton.onClick.RemoveAllListeners();
|
|
|
|
|
|
_dialogueBackButton.interactable = true;
|
|
|
|
|
|
_dialogueBackButton.enabled = true;
|
|
|
|
|
|
_dialogueBackButton.onClick.AddListener(HideDialogue);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BindResultButtons();
|
|
|
|
|
|
BindEndPanelButtons();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BindResultButtons()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_gradeResult == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
Button yes = _gradeResult.transform.Find("BtnYes")?.GetComponent<Button>();
|
|
|
|
|
|
if (yes != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
yes.onClick.RemoveAllListeners();
|
2026-07-14 03:46:59 +08:00
|
|
|
|
yes.interactable = true;
|
|
|
|
|
|
yes.enabled = true;
|
2026-07-17 05:42:52 +08:00
|
|
|
|
yes.onClick.AddListener(ShowEndPanelFromGrade);
|
2026-07-12 20:34:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Button back = _gradeResult.transform.Find("BtnBack")?.GetComponent<Button>();
|
|
|
|
|
|
if (back != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
back.onClick.RemoveAllListeners();
|
2026-07-14 03:46:59 +08:00
|
|
|
|
back.interactable = true;
|
|
|
|
|
|
back.enabled = true;
|
2026-07-12 20:34:49 +08:00
|
|
|
|
back.onClick.AddListener(() => _gradeResult.SetActive(false));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BindEndPanelButtons()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_endPanel == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
Button yes = _endPanel.transform.Find("BtnYes")?.GetComponent<Button>();
|
|
|
|
|
|
if (yes != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
yes.onClick.RemoveAllListeners();
|
2026-07-14 03:46:59 +08:00
|
|
|
|
yes.interactable = true;
|
|
|
|
|
|
yes.enabled = true;
|
2026-07-12 20:34:49 +08:00
|
|
|
|
yes.onClick.AddListener(ReturnToHome);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-17 05:42:52 +08:00
|
|
|
|
private void ShowEndPanelFromGrade()
|
|
|
|
|
|
{
|
|
|
|
|
|
AutoBind();
|
|
|
|
|
|
if (_gradeResult != null) _gradeResult.SetActive(false);
|
|
|
|
|
|
if (_endPanel != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_endPanel.SetActive(true);
|
|
|
|
|
|
BindEndPanelButtons();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-12 20:34:49 +08:00
|
|
|
|
private void ReturnToHome()
|
|
|
|
|
|
{
|
|
|
|
|
|
PageController page = FindObjectOfType<PageController>();
|
|
|
|
|
|
if (page != null) page.ReturnToHome();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ApplyStepText(int stepNumber)
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform step = _dialogueBubble?.Find("step" + stepNumber);
|
|
|
|
|
|
if (step == null) return;
|
|
|
|
|
|
|
2026-07-17 05:42:52 +08:00
|
|
|
|
Text title = FindStepText(step, "Title");
|
|
|
|
|
|
Text content = FindStepText(step, "content");
|
2026-07-12 20:34:49 +08:00
|
|
|
|
|
2026-07-17 05:42:52 +08:00
|
|
|
|
if (title != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ApplyQuestionTextStyle(title, true);
|
|
|
|
|
|
title.text = "考核点" + ToChineseNumber(stepNumber);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (content != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ApplyQuestionTextStyle(content, false);
|
|
|
|
|
|
content.text = GetQuestionText(stepNumber);
|
|
|
|
|
|
}
|
2026-07-12 20:34:49 +08:00
|
|
|
|
|
|
|
|
|
|
if (stepNumber == 3)
|
|
|
|
|
|
{
|
2026-07-17 05:42:52 +08:00
|
|
|
|
ConfigureStepToggles(stepNumber);
|
2026-07-12 20:34:49 +08:00
|
|
|
|
SetToggleLabel(step, "Toggle1", "5cm");
|
|
|
|
|
|
SetToggleLabel(step, "Toggle2", "10cm");
|
|
|
|
|
|
SetToggleLabel(step, "Toggle3", "20cm");
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (stepNumber == 7)
|
|
|
|
|
|
{
|
2026-07-17 05:42:52 +08:00
|
|
|
|
ConfigureStepToggles(stepNumber);
|
2026-07-12 20:34:49 +08:00
|
|
|
|
SetToggleLabel(step, "Toggle1", "足底");
|
|
|
|
|
|
SetToggleLabel(step, "Toggle2", "足背");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string GetQuestionText(int stepNumber)
|
|
|
|
|
|
{
|
2026-07-17 05:42:52 +08:00
|
|
|
|
if (IsSplintExam)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (stepNumber)
|
|
|
|
|
|
{
|
|
|
|
|
|
case 1: return "操作题:点击确定后,请先完成现场安全确认、伤员沟通与伤情初步评估。";
|
|
|
|
|
|
case 2: return "操作题:点击确定后,请继续完成夹板固定前置处理,包括脱除伤侧鞋袜、暴露并观察伤肢。";
|
|
|
|
|
|
case 3: return "单选题:请选择夹板固定时较合适的固定带宽度。";
|
|
|
|
|
|
case 4: return "操作题:点击确定后,请按照由远心端到近心端的顺序依次预置四条固定带。";
|
|
|
|
|
|
case 5: return "操作题:点击确定后,请先放置衬垫保护骨突,再将两块夹板分别放在伤肢两侧。";
|
|
|
|
|
|
case 6: return "操作题:点击确定后,请按正确顺序收紧并固定四条绑带,注意从远端向近端依次固定。";
|
|
|
|
|
|
case 7: return "单选题:固定完成后,应优先观察哪个位置判断足部末梢血运与感觉?";
|
|
|
|
|
|
default: return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch (stepNumber)
|
|
|
|
|
|
{
|
|
|
|
|
|
case 1: return "操作题:点击确定后,请先评估现场环境和伤员情况,确认可以开展救护。";
|
|
|
|
|
|
case 2: return "操作题:点击确定后,请按顺序完成下肢固定前置处理,包括取出用品、脱除伤侧鞋子并保护踝关节。";
|
|
|
|
|
|
case 3: return "单选题:请选择下肢固定时较合适的带子宽度。";
|
|
|
|
|
|
case 4: return "操作题:点击确定后,请按由远心端到近心端的顺序依次摆放绑带。";
|
|
|
|
|
|
case 5: return "操作题:点击确定后,请按正确顺序进行系带固定。";
|
|
|
|
|
|
case 6: return "判断题:本次演示中带子打结在受伤腿一侧,请判断该操作是否正确。";
|
|
|
|
|
|
case 7: return "单选题:踝关节处打结应选择哪个位置?";
|
|
|
|
|
|
default: return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string GetExamTipText(int stepNumber)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (IsSplintExam)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (stepNumber)
|
|
|
|
|
|
{
|
|
|
|
|
|
case 1: return "阅读考核点一,点击确定后开始伤情评估。";
|
|
|
|
|
|
case 2: return "阅读考核点二,点击确定后完成脱鞋和伤肢暴露观察。";
|
|
|
|
|
|
case 3: return "请选择固定带宽度,选择后点击确定。";
|
|
|
|
|
|
case 4: return "点击确定后,按由脚踝到大腿的顺序预置 4 条固定带。";
|
|
|
|
|
|
case 5: return "点击确定后,先放置衬垫,再放置两侧夹板。";
|
|
|
|
|
|
case 6: return "点击确定后,由远端到近端依次完成 4 个固定点。";
|
|
|
|
|
|
case 7: return "请选择固定完成后用于观察末梢血运的位置。";
|
|
|
|
|
|
default: return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-12 20:34:49 +08:00
|
|
|
|
switch (stepNumber)
|
|
|
|
|
|
{
|
2026-07-17 05:42:52 +08:00
|
|
|
|
case 1: return "阅读考核点一,点击确定后开始评估伤情。";
|
|
|
|
|
|
case 2: return "阅读考核点二,点击确定后完成脱鞋和下肢固定前置处理。";
|
|
|
|
|
|
case 3: return "请选择绑带宽度,选择后点击确定。";
|
|
|
|
|
|
case 4: return "点击确定后,按由脚踝到大腿的顺序摆放 4 条绑带。";
|
|
|
|
|
|
case 5: return "点击确定后,继续由远端到近端完成系带固定。";
|
|
|
|
|
|
case 6: return "阅读打结位置判断题,点击确定后进入下一题。";
|
|
|
|
|
|
case 7: return "请选择踝关节打结位置,选择后点击确定查看成绩。";
|
2026-07-12 20:34:49 +08:00
|
|
|
|
default: return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string GetAnswerText(int stepNumber)
|
|
|
|
|
|
{
|
2026-07-17 05:42:52 +08:00
|
|
|
|
if (IsSplintExam)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (stepNumber)
|
|
|
|
|
|
{
|
|
|
|
|
|
case 1: return "确认现场安全,表明救护身份,询问并评估伤员,判断疑似骨折后准备夹板固定用品。";
|
|
|
|
|
|
case 2: return "轻柔暴露伤肢,脱除伤侧鞋袜,观察肿胀畸形和末梢情况,操作中避免牵拉骨折端。";
|
|
|
|
|
|
case 3: return "10cm。宽度过窄易产生局部压迫,过宽则不利于稳定贴合。";
|
|
|
|
|
|
case 4: return "从脚踝等远心端开始,向膝部和大腿近心端依次预置四条固定带。";
|
|
|
|
|
|
case 5: return "先在骨突和空隙处放置衬垫,再将两块夹板置于伤肢两侧,夹板长度应跨过骨折上下相邻关节。";
|
|
|
|
|
|
case 6: return "先固定远端脚踝,再由远心端到近心端逐条固定,松紧以能固定且不影响血运为准。";
|
|
|
|
|
|
case 7: return "足背。足背动脉、皮肤颜色、温度和感觉可用于判断固定后末梢循环情况。";
|
|
|
|
|
|
default: return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-12 20:34:49 +08:00
|
|
|
|
switch (stepNumber)
|
|
|
|
|
|
{
|
2026-07-17 05:42:52 +08:00
|
|
|
|
case 1: return "先评估现场安全和伤员意识、疼痛、畸形等情况,取得配合后再开始固定。";
|
|
|
|
|
|
case 2: return "取出急救用品,脱除伤侧鞋子,摆正伤肢并保护踝关节,避免加重损伤。";
|
|
|
|
|
|
case 3: return "10cm。带子过窄容易压迫皮肤,过宽会影响固定贴合。";
|
2026-07-12 20:34:49 +08:00
|
|
|
|
case 4: return "由远心端(脚踝)向近心端(大腿根部)依次摆放绑带。";
|
2026-07-17 05:42:52 +08:00
|
|
|
|
case 5: return "先固定远端脚踝,再依次向小腿、膝部、大腿近端收紧捆绑。";
|
|
|
|
|
|
case 6: return "错误。带结应打在未受伤腿一侧,避免压迫伤肢,也便于观察和后续拆除。";
|
|
|
|
|
|
case 7: return "足背。踝关节处应避免把结打在足底受压位置。";
|
2026-07-12 20:34:49 +08:00
|
|
|
|
default: return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-17 05:42:52 +08:00
|
|
|
|
private void ShowTip(string text)
|
|
|
|
|
|
{
|
|
|
|
|
|
UIManager uiManager = FindObjectOfType<UIManager>();
|
|
|
|
|
|
if (uiManager != null) uiManager.ShowTip(text);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-12 20:34:49 +08:00
|
|
|
|
private string ToChineseNumber(int number)
|
|
|
|
|
|
{
|
|
|
|
|
|
string[] values = { "", "一", "二", "三", "四", "五", "六", "七" };
|
|
|
|
|
|
return number >= 0 && number < values.Length ? values[number] : number.ToString();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void SetToggleLabel(Transform root, string toggleName, string label)
|
|
|
|
|
|
{
|
2026-07-17 05:42:52 +08:00
|
|
|
|
Transform toggle = FindToggleTransform(root, toggleName);
|
|
|
|
|
|
if (toggle == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
Text text = FindStepText(toggle, "Label");
|
|
|
|
|
|
if (text == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Text[] labels = toggle.GetComponentsInChildren<Text>(true);
|
|
|
|
|
|
if (labels.Length > 0) text = labels[0];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (text != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ApplyOptionTextStyle(text);
|
|
|
|
|
|
text.text = label;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Text FindStepText(Transform root, string objectName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (root == null) return null;
|
|
|
|
|
|
|
|
|
|
|
|
Text direct = root.name.Equals(objectName, System.StringComparison.OrdinalIgnoreCase)
|
|
|
|
|
|
? root.GetComponent<Text>()
|
|
|
|
|
|
: null;
|
|
|
|
|
|
if (direct != null) return direct;
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < root.childCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Text child = FindStepText(root.GetChild(i), objectName);
|
|
|
|
|
|
if (child != null) return child;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Transform FindToggleTransform(Transform root, string toggleName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (root == null) return null;
|
|
|
|
|
|
|
|
|
|
|
|
Transform direct = root.Find(toggleName);
|
|
|
|
|
|
if (direct != null) return direct;
|
|
|
|
|
|
|
|
|
|
|
|
Toggle[] toggles = GetOrderedToggles(root);
|
|
|
|
|
|
for (int i = 0; i < toggles.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (toggles[i] != null && toggles[i].name == toggleName)
|
|
|
|
|
|
return toggles[i].transform;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int index = GetTrailingNumber(toggleName) - 1;
|
|
|
|
|
|
if (index >= 0 && index < toggles.Length && toggles[index] != null)
|
|
|
|
|
|
return toggles[index].transform;
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private int GetTrailingNumber(string value)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(value)) return 0;
|
|
|
|
|
|
|
|
|
|
|
|
int multiplier = 1;
|
|
|
|
|
|
int result = 0;
|
|
|
|
|
|
bool found = false;
|
|
|
|
|
|
for (int i = value.Length - 1; i >= 0; i--)
|
|
|
|
|
|
{
|
|
|
|
|
|
char ch = value[i];
|
|
|
|
|
|
if (ch < '0' || ch > '9') break;
|
|
|
|
|
|
found = true;
|
|
|
|
|
|
result += (ch - '0') * multiplier;
|
|
|
|
|
|
multiplier *= 10;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return found ? result : 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ApplyQuestionTextStyle(Text text, bool isTitle)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (text == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
Font chineseFont = ResolveChineseUiFont(text);
|
|
|
|
|
|
if (chineseFont != null) text.font = chineseFont;
|
|
|
|
|
|
|
|
|
|
|
|
text.fontSize = isTitle ? Mathf.Max(text.fontSize, 30) : Mathf.Max(text.fontSize, 26);
|
|
|
|
|
|
text.color = isTitle ? Color.white : Color.black;
|
|
|
|
|
|
text.horizontalOverflow = HorizontalWrapMode.Wrap;
|
|
|
|
|
|
text.verticalOverflow = VerticalWrapMode.Overflow;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ApplyOptionTextStyle(Text text)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (text == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
Font chineseFont = ResolveChineseUiFont(text);
|
|
|
|
|
|
if (chineseFont != null) text.font = chineseFont;
|
|
|
|
|
|
|
|
|
|
|
|
text.fontSize = Mathf.Max(text.fontSize, 24);
|
|
|
|
|
|
text.color = new Color(0.18f, 0.18f, 0.18f, 1f);
|
|
|
|
|
|
text.horizontalOverflow = HorizontalWrapMode.Overflow;
|
|
|
|
|
|
text.verticalOverflow = VerticalWrapMode.Overflow;
|
2026-07-12 20:34:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private int GetSelectedToggleIndex(int stepNumber)
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform step = _dialogueBubble?.Find("step" + stepNumber);
|
|
|
|
|
|
if (step == null) return -1;
|
|
|
|
|
|
|
2026-07-17 05:42:52 +08:00
|
|
|
|
Toggle[] toggles = GetOrderedToggles(step);
|
2026-07-12 20:34:49 +08:00
|
|
|
|
for (int i = 0; i < toggles.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (toggles[i].isOn) return i;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ResetStepToggles(int stepNumber)
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform step = _dialogueBubble?.Find("step" + stepNumber);
|
|
|
|
|
|
if (step == null) return;
|
|
|
|
|
|
|
2026-07-17 05:42:52 +08:00
|
|
|
|
Toggle[] toggles = GetOrderedToggles(step);
|
2026-07-12 20:34:49 +08:00
|
|
|
|
for (int i = 0; i < toggles.Length; i++)
|
|
|
|
|
|
{
|
2026-07-17 05:42:52 +08:00
|
|
|
|
toggles[i].SetIsOnWithoutNotify(false);
|
2026-07-12 20:34:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void EnsureToggleGroups()
|
|
|
|
|
|
{
|
|
|
|
|
|
EnsureToggleGroup(3);
|
|
|
|
|
|
EnsureToggleGroup(7);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void EnsureStepPanelsDoNotBlockButtons()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_dialogueBubble == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < _dialogueBubble.childCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform child = _dialogueBubble.GetChild(i);
|
|
|
|
|
|
if (!child.name.StartsWith("step")) continue;
|
|
|
|
|
|
|
2026-07-14 03:46:59 +08:00
|
|
|
|
Graphic[] graphics = child.GetComponentsInChildren<Graphic>(true);
|
|
|
|
|
|
for (int j = 0; j < graphics.Length; j++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Graphic graphic = graphics[j];
|
|
|
|
|
|
if (graphic == null) continue;
|
|
|
|
|
|
if (graphic.GetComponentInParent<Toggle>() != null) continue;
|
|
|
|
|
|
graphic.raycastTarget = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void RefreshGradeContentLayout()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_gradeContent == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
RectTransform textRect = _gradeContent.GetComponent<RectTransform>();
|
|
|
|
|
|
if (textRect != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
float preferredHeight = _gradeContent.preferredHeight + 32f;
|
|
|
|
|
|
textRect.sizeDelta = new Vector2(textRect.sizeDelta.x, Mathf.Max(preferredHeight, 400f));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ScrollRect scrollRect = _gradeResult != null ? _gradeResult.GetComponentInChildren<ScrollRect>(true) : null;
|
|
|
|
|
|
if (scrollRect != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Canvas.ForceUpdateCanvases();
|
|
|
|
|
|
scrollRect.verticalNormalizedPosition = 1f;
|
|
|
|
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(scrollRect.content);
|
2026-07-12 20:34:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void EnsureToggleGroup(int stepNumber)
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform step = _dialogueBubble?.Find("step" + stepNumber);
|
|
|
|
|
|
if (step == null) return;
|
|
|
|
|
|
|
2026-07-17 05:42:52 +08:00
|
|
|
|
ConfigureStepToggles(stepNumber);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ConfigureStepToggles(int stepNumber)
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform step = _dialogueBubble?.Find("step" + stepNumber);
|
|
|
|
|
|
if (step == null) return;
|
|
|
|
|
|
|
2026-07-12 20:34:49 +08:00
|
|
|
|
ToggleGroup group = step.GetComponent<ToggleGroup>();
|
|
|
|
|
|
if (group == null) group = step.gameObject.AddComponent<ToggleGroup>();
|
|
|
|
|
|
group.allowSwitchOff = true;
|
|
|
|
|
|
|
2026-07-17 05:42:52 +08:00
|
|
|
|
Toggle[] toggles = GetOrderedToggles(step);
|
2026-07-12 20:34:49 +08:00
|
|
|
|
for (int i = 0; i < toggles.Length; i++)
|
|
|
|
|
|
{
|
2026-07-17 05:42:52 +08:00
|
|
|
|
int selectedIndex = i;
|
|
|
|
|
|
Toggle toggle = toggles[i];
|
|
|
|
|
|
if (toggle == null) continue;
|
|
|
|
|
|
|
|
|
|
|
|
toggle.enabled = true;
|
|
|
|
|
|
toggle.interactable = true;
|
|
|
|
|
|
toggle.group = group;
|
|
|
|
|
|
EnsureToggleRaycastArea(toggle);
|
|
|
|
|
|
|
|
|
|
|
|
toggle.onValueChanged.RemoveAllListeners();
|
|
|
|
|
|
toggle.onValueChanged.AddListener((isOn) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!isOn) return;
|
|
|
|
|
|
|
|
|
|
|
|
Toggle[] currentToggles = GetOrderedToggles(step);
|
|
|
|
|
|
for (int j = 0; j < currentToggles.Length; j++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (j == selectedIndex || currentToggles[j] == null) continue;
|
|
|
|
|
|
currentToggles[j].SetIsOnWithoutNotify(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Toggle[] GetOrderedToggles(Transform step)
|
|
|
|
|
|
{
|
|
|
|
|
|
Toggle[] toggles = step != null ? step.GetComponentsInChildren<Toggle>(true) : new Toggle[0];
|
|
|
|
|
|
System.Array.Sort(toggles, (a, b) => string.Compare(a.name, b.name, System.StringComparison.Ordinal));
|
|
|
|
|
|
return toggles;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void EnsureToggleRaycastArea(Toggle toggle)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (toggle == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
Graphic rootGraphic = toggle.GetComponent<Graphic>();
|
|
|
|
|
|
if (rootGraphic == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Image image = toggle.gameObject.AddComponent<Image>();
|
|
|
|
|
|
image.color = new Color(1f, 1f, 1f, 0f);
|
|
|
|
|
|
rootGraphic = image;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
rootGraphic.raycastTarget = true;
|
|
|
|
|
|
toggle.targetGraphic = rootGraphic;
|
|
|
|
|
|
|
|
|
|
|
|
Graphic[] graphics = toggle.GetComponentsInChildren<Graphic>(true);
|
|
|
|
|
|
for (int i = 0; i < graphics.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (graphics[i] == null) continue;
|
|
|
|
|
|
graphics[i].raycastTarget = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RectTransform rect = toggle.GetComponent<RectTransform>();
|
|
|
|
|
|
if (rect != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Vector2 size = rect.sizeDelta;
|
|
|
|
|
|
if (Mathf.Abs(size.x) < 120f) size.x = 220f;
|
|
|
|
|
|
if (Mathf.Abs(size.y) < 32f) size.y = 48f;
|
|
|
|
|
|
rect.sizeDelta = size;
|
2026-07-12 20:34:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void HideDialogue()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_dialogueBubble != null) _dialogueBubble.gameObject.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void HideAllPanels()
|
|
|
|
|
|
{
|
|
|
|
|
|
HideDialogue();
|
|
|
|
|
|
if (_gradeResult != null) _gradeResult.SetActive(false);
|
|
|
|
|
|
if (_endPanel != null) _endPanel.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ResetExamState(bool keepActive)
|
|
|
|
|
|
{
|
|
|
|
|
|
StopAllCoroutines();
|
|
|
|
|
|
_examActive = keepActive;
|
|
|
|
|
|
_currentStep = 0;
|
|
|
|
|
|
_waitingForPoint2 = false;
|
|
|
|
|
|
_waitingForShose = false;
|
|
|
|
|
|
_waitingForWrapClicks = false;
|
2026-07-17 05:42:52 +08:00
|
|
|
|
_splintBodyClicked = false;
|
2026-07-12 20:34:49 +08:00
|
|
|
|
_placedPings.Clear();
|
|
|
|
|
|
_wrappedPings.Clear();
|
2026-07-17 05:42:52 +08:00
|
|
|
|
_clickedSplintPlanks.Clear();
|
2026-07-12 20:34:49 +08:00
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < StepCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
_answered[i] = false;
|
|
|
|
|
|
_correct[i] = false;
|
|
|
|
|
|
_earned[i] = 0;
|
|
|
|
|
|
_feedback[i] = "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HideAllPanels();
|
2026-07-17 05:42:52 +08:00
|
|
|
|
|
|
|
|
|
|
if (!keepActive)
|
|
|
|
|
|
{
|
|
|
|
|
|
HideSplintObjects();
|
|
|
|
|
|
if (_manMuban != null) _manMuban.SetActive(false);
|
|
|
|
|
|
}
|
2026-07-12 20:34:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ForceHideExamOnlyOutlines()
|
|
|
|
|
|
{
|
|
|
|
|
|
ShoseClickHandler shose = FindObjectOfType<ShoseClickHandler>(true);
|
|
|
|
|
|
if (shose != null) shose.ForceHideOutline();
|
|
|
|
|
|
|
|
|
|
|
|
ChanraoTuibuHandler chanraoTuibu = FindObjectOfType<ChanraoTuibuHandler>(true);
|
|
|
|
|
|
if (chanraoTuibu != null) chanraoTuibu.ForceHideOutline();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void MoveWomanToExamPoint()
|
|
|
|
|
|
{
|
|
|
|
|
|
GameObject woman = FindSceneObject("woman");
|
|
|
|
|
|
GameObject pointArray = FindSceneObject("womanPointArray");
|
|
|
|
|
|
Transform target = pointArray != null ? pointArray.transform.Find("point1") : null;
|
|
|
|
|
|
if (woman == null || target == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
woman.transform.position = target.position;
|
|
|
|
|
|
woman.transform.rotation = target.rotation;
|
|
|
|
|
|
|
|
|
|
|
|
Animator anim = woman.GetComponent<Animator>();
|
|
|
|
|
|
if (anim != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
anim.SetBool("IsCheck", false);
|
|
|
|
|
|
anim.SetBool("isCrouch", false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool IsExperimentPageVisible()
|
|
|
|
|
|
{
|
|
|
|
|
|
GameObject panel = FindSceneObject("ExperimentPanel");
|
|
|
|
|
|
return panel == null || panel.activeInHierarchy;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private GameObject FindSceneObject(string objectName)
|
|
|
|
|
|
{
|
|
|
|
|
|
GameObject found = GameObject.Find(objectName);
|
|
|
|
|
|
if (found != null) return found;
|
|
|
|
|
|
|
|
|
|
|
|
Transform[] all = Resources.FindObjectsOfTypeAll<Transform>();
|
|
|
|
|
|
for (int i = 0; i < all.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform t = all[i];
|
|
|
|
|
|
if (t != null && t.gameObject.scene.IsValid() && t.name == objectName)
|
|
|
|
|
|
{
|
|
|
|
|
|
return t.gameObject;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|