Files
VFSUnity/Assets/Scripts/Core/ExamFlowManager.cs
T
2026-07-14 03:46:59 +08:00

733 lines
23 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 System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ExamFlowManager : MonoBehaviour
{
private const int StepCount = 7;
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>();
private Transform _dialogueBubble;
private GameObject _gradeResult;
private GameObject _endPanel;
private Button _dialogueYesButton;
private Button _dialogueBackButton;
private Text _gradeContent;
private Text _gradeScoreText;
private int _currentStep;
private bool _examActive;
private bool _waitingForPoint2;
private bool _waitingForShose;
private bool _waitingForWrapClicks;
private void Awake()
{
AutoBind();
}
private void Start()
{
AutoBind();
BindButtons();
EnsureToggleGroups();
HideAllPanels();
}
private void Update()
{
bool shouldRun = GameManager.Instance != null &&
GameManager.Instance.CurrentMode == ExperimentMode.Exam &&
GameManager.Instance.CurrentFixationMethod == FixationMethod.LimbFixation &&
IsExperimentPageVisible();
if (!shouldRun && _examActive)
{
ResetExamState(false);
}
}
public void BeginExam()
{
if (GameManager.Instance == null ||
GameManager.Instance.CurrentMode != ExperimentMode.Exam ||
GameManager.Instance.CurrentFixationMethod != FixationMethod.LimbFixation ||
!IsExperimentPageVisible())
{
ResetExamState(false);
return;
}
AutoBind();
BindButtons();
EnsureToggleGroups();
ResetExamState(true);
_examActive = true;
_waitingForPoint2 = true;
if (GameManager.Instance != null)
{
GameManager.Instance.InitExamRecord(StepCount);
}
ForceHideExamOnlyOutlines();
MoveWomanToExamPoint();
ShowStep(1);
Debug.Log("[ExamFlow] 考核模式流程启动");
}
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);
}
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, "已按要求完成前置操作起始步骤。");
StartCoroutine(ShowStepDelayed(2, 1f));
}
return;
}
if (clicked is ShoseClickHandler)
{
if (_waitingForShose || _currentStep <= 2)
{
_waitingForShose = false;
RecordStep(2, true, "已完成下肢固定前置处理。");
StartCoroutine(ShowStepDelayed(3, 1f));
}
return;
}
var ping = clicked as PingClickHandler;
if (ping != null)
{
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, "已按由远端到近端的顺序完成系带。");
ShowStep(6);
}
}
else
{
_placedPings.Add(ping.name);
Debug.Log("[ExamFlow] 放带点击 " + _placedPings.Count + "/4: " + ping.name);
}
return;
}
if (clicked is ChanraoTuibuHandler)
{
if (!_answered[3])
{
RecordStep(4, _placedPings.Count >= 4, _placedPings.Count >= 4
? "已按要求完成四条绑带摆放。"
: "绑带尚未全部摆放完成。");
}
_waitingForWrapClicks = true;
ShowStep(5);
}
}
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;
break;
case 2:
HideDialogue();
_waitingForShose = true;
break;
case 3:
RecordStep(3, GetSelectedToggleIndex(3) == 1, "正确答案:10cm。");
ShowStep(4);
ActivateFirstPing();
break;
case 4:
HideDialogue();
break;
case 5:
HideDialogue();
_waitingForWrapClicks = true;
break;
case 6:
RecordStep(6, false, "错误操作:在受伤腿一侧打结。正确做法是在未受伤腿位置打结。");
ShowStep(7);
break;
case 7:
RecordStep(7, GetSelectedToggleIndex(7) == 1, "正确答案:足背。");
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) _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<PingClickHandler>())
{
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 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;
Text text = content.GetComponent<Text>();
if (text == null)
{
text = content.GetComponentInChildren<Text>(true);
}
if (text == null)
{
text = content.gameObject.AddComponent<Text>();
text.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
text.fontSize = 24;
text.color = Color.black;
text.alignment = TextAnchor.UpperLeft;
text.horizontalOverflow = HorizontalWrapMode.Wrap;
text.verticalOverflow = VerticalWrapMode.Overflow;
}
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;
return text;
}
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();
yes.interactable = true;
yes.enabled = true;
yes.onClick.AddListener(() =>
{
_gradeResult.SetActive(false);
if (_endPanel != null) _endPanel.SetActive(true);
});
}
Button back = _gradeResult.transform.Find("BtnBack")?.GetComponent<Button>();
if (back != null)
{
back.onClick.RemoveAllListeners();
back.interactable = true;
back.enabled = true;
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();
yes.interactable = true;
yes.enabled = true;
yes.onClick.AddListener(ReturnToHome);
}
}
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;
Text title = step.Find("Title")?.GetComponent<Text>();
Text content = step.Find("content")?.GetComponent<Text>();
if (title != null) title.text = "考核点" + ToChineseNumber(stepNumber);
if (content != null) content.text = GetQuestionText(stepNumber);
if (stepNumber == 3)
{
SetToggleLabel(step, "Toggle1", "5cm");
SetToggleLabel(step, "Toggle2", "10cm");
SetToggleLabel(step, "Toggle3", "20cm");
}
else if (stepNumber == 7)
{
SetToggleLabel(step, "Toggle1", "足底");
SetToggleLabel(step, "Toggle2", "足背");
}
}
private string GetQuestionText(int stepNumber)
{
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 GetAnswerText(int stepNumber)
{
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 "";
}
}
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)
{
Transform toggle = root.Find(toggleName);
Text text = toggle?.Find("Label")?.GetComponent<Text>();
if (text != null) text.text = label;
}
private int GetSelectedToggleIndex(int stepNumber)
{
Transform step = _dialogueBubble?.Find("step" + stepNumber);
if (step == null) return -1;
Toggle[] toggles = step.GetComponentsInChildren<Toggle>(true);
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;
Toggle[] toggles = step.GetComponentsInChildren<Toggle>(true);
for (int i = 0; i < toggles.Length; i++)
{
toggles[i].isOn = false;
}
}
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;
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);
}
}
private void EnsureToggleGroup(int stepNumber)
{
Transform step = _dialogueBubble?.Find("step" + stepNumber);
if (step == null) return;
ToggleGroup group = step.GetComponent<ToggleGroup>();
if (group == null) group = step.gameObject.AddComponent<ToggleGroup>();
group.allowSwitchOff = true;
Toggle[] toggles = step.GetComponentsInChildren<Toggle>(true);
for (int i = 0; i < toggles.Length; i++)
{
toggles[i].group = group;
}
}
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;
_placedPings.Clear();
_wrappedPings.Clear();
for (int i = 0; i < StepCount; i++)
{
_answered[i] = false;
_correct[i] = false;
_earned[i] = 0;
_feedback[i] = "";
}
HideAllPanels();
}
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;
}
}