using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class ExamFlowManager : MonoBehaviour { private const int StepCount = 7; private const string ManMubanName = "manMuban"; private const string ManInjuryName = "manInjury"; 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 _placedPings = new HashSet(); private readonly HashSet _wrappedPings = new HashSet(); private readonly HashSet _clickedSplintPlanks = new HashSet(); private Transform _dialogueBubble; private GameObject _gradeResult; private GameObject _endPanel; private GameObject _manMuban; private GameObject _manInjury; private Button _dialogueYesButton; private Button _dialogueBackButton; private Text _gradeContent; private Text _gradeScoreText; private Font _cachedChineseFont; private PingClickHandler[] _splintPings; private SplintObjectClickHandler _splintBodyPad; private SplintObjectClickHandler[] _splintPlanks; private int _currentStep; private bool _examActive; private bool _waitingForPoint2; private bool _waitingForShose; private bool _waitingForWrapClicks; private bool _splintBodyClicked; private bool IsSplintExam { get { return GameManager.Instance != null && GameManager.Instance.CurrentFixationMethod == FixationMethod.SplintFixation; } } 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(); ConfigureExamModelState(); ShowStep(1); Debug.Log("[ExamFlow] 考核模式流程启动: " + GameManager.Instance.CurrentFixationMethod); } 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); } ShowTip(GetExamTipText(_currentStep)); 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, "已按要求完成前置操作起始步骤。"); ShowTip("伤情评估已完成。下一步:阅读弹窗提示,准备完成前置处理。"); StartCoroutine(ShowStepDelayed(2, 1f)); } return; } if (clicked is ShoseClickHandler) { if (_waitingForShose || _currentStep <= 2) { _waitingForShose = false; RecordStep(2, true, IsSplintExam ? "已完成夹板固定前置处理。" : "已完成下肢固定前置处理。"); ShowTip(IsSplintExam ? "夹板固定前置处理完成。下一步:选择固定带宽度。" : "下肢固定前置处理完成。下一步:选择绑带宽度。"); StartCoroutine(ShowStepDelayed(3, 1f)); } return; } var ping = clicked as PingClickHandler; if (ping != null) { if (IsSplintExam) { HandleSplintPingClicked(ping); return; } 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; RecordStep(5, true, "已按由远端到近端的顺序完成系带。"); ShowTip("系带顺序已完成。下一步:阅读打结位置判断题。"); ShowStep(6); } } else { _placedPings.Add(ping.name); Debug.Log("[ExamFlow] 放带点击 " + _placedPings.Count + "/4: " + ping.name); } return; } var splintObject = clicked as SplintObjectClickHandler; if (splintObject != null) { if (IsSplintExam) { HandleSplintObjectClicked(splintObject); } return; } if (clicked is ChanraoTuibuHandler) { if (!_answered[3]) { RecordStep(4, _placedPings.Count >= 4, _placedPings.Count >= 4 ? "已按要求完成四条绑带摆放。" : "绑带尚未全部摆放完成。"); } _waitingForWrapClicks = true; ShowTip("环绕固定带已显示。下一步:依次点击 4 个蓝色绑带点完成系带固定。"); ShowStep(5); } } 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); } } 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; ShowTip("请点击伤员大腿附近,完成伤情评估。"); break; case 2: HideDialogue(); _waitingForShose = true; ShowTip("请点击伤侧鞋子,完成脱鞋和前置处理。"); break; case 3: RecordStep(3, GetSelectedToggleIndex(3) == 1, "正确答案:10cm。"); ShowStep(4); if (IsSplintExam) ActivateFirstSplintPing(); else ActivateFirstPing(); ShowTip(IsSplintExam ? "请由脚踝向大腿依次点击 4 个蓝色绑带点,预置固定带。" : "请由脚踝向大腿依次点击 4 个蓝色绑带点,摆放绑带。"); break; case 4: HideDialogue(); ShowTip(IsSplintExam ? "请继续由远端向近端点击蓝色绑带点,完成 4 条固定带预置。" : "请继续由远端向近端点击蓝色绑带点,完成 4 条绑带摆放。"); break; case 5: HideDialogue(); if (IsSplintExam) { ShowSplintBodyTarget(); ShowTip("请点击 Body.001 蓝色边框,先放置夹板衬垫。"); } else { _waitingForWrapClicks = true; ShowTip("请依次点击蓝色绑带点,按由远端到近端顺序完成系带。"); } break; case 6: if (IsSplintExam) { HideDialogue(); _waitingForWrapClicks = true; EnableSplintFinalPingStage(); ShowTip("请由脚踝向大腿依次点击 4 个蓝色固定点,完成夹板系带固定。"); } else { RecordStep(6, false, "错误操作:在受伤腿一侧打结。正确做法是在未受伤腿位置打结。"); ShowStep(7); } break; case 7: RecordStep(7, GetSelectedToggleIndex(7) == 1, "正确答案:足背。"); ShowTip("考核答题完成,正在生成成绩汇总。"); 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); if (_gradeContent != null) { ApplyGradeTextStyle(_gradeContent); _gradeContent.text = gradeContent; } if (_gradeScoreText != null) _gradeScoreText.text = totalScore.ToString(); RefreshGradeContentLayout(); } 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() { PingClickHandler firstHandler = null; foreach (var ping in Resources.FindObjectsOfTypeAll()) { if (ping == null || !ping.gameObject.scene.IsValid()) continue; if (ping.name == "ping.004" || ping.pingIndex == 0) { firstHandler = ping; break; } } if (firstHandler != null) { firstHandler.gameObject.SetActive(true); firstHandler.ResetState(true); } } 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(); if (handler == null) handler = guide.gameObject.AddComponent(); 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(); if (_splintBodyPad == null) _splintBodyPad = body.gameObject.AddComponent(); 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 planks = new List(); for (int i = 0; i < plankNames.Length; i++) { Transform plank = _manMuban.transform.Find(plankNames[i]); if (plank == null) continue; SplintObjectClickHandler handler = plank.GetComponent(); if (handler == null) handler = plank.gameObject.AddComponent(); 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; } 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