using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Cysharp.Threading.Tasks; using DefaultNamespace.Dto; using Framework.Manager; using Framework.ProcessMode; using HighlightPlus; // using HighlightPlus; using MotionFramework; using Newtonsoft.Json; using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; using TMPro; using Unity.VisualScripting; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; using SceneManager = Framework.Scripts.Runtime.Engine.Scene.SceneManager; using Type = System.Type; namespace DefaultNamespace.ProcessMode { /// /// 流程模式管理器 /// public class ProcessManager : ModuleSingleton, IModule { #region 字段定义 private Dictionary _processes = new Dictionary(); public ProcessMode _currentMode; // 当前模式 private int _currentStepIndex; // 当前步骤索引 private int _currentActionIndex; // 当前动作索引 private int _currentActionGameIndex; // 当前动作索引 public Dictionary<(int stepIndex, int actionIndex), List> _incorrectClicksPerStep; // 每个步骤下每个动作的错误点击记录 public Dictionary<(int stepIndex, int actionIndex), List> _correctAnswersPerStep; // 每个步骤下每个动作的正确答案记录 private List _globalIncorrectClicks = new List(); // 全局错误点击记录池 private List _submitScoreSteps = new List(); // 存储分数集合 private Dictionary<(int stepIndex, int actionIndex, string uiId), string> _validatedInputAnswers = new Dictionary<(int stepIndex, int actionIndex, string uiId), string>(); // 记录已验证通过的输入答案(按UI ID区分) private List records; // 分数集合 private Dictionary<(int stepIndex, int actionIndex), List> _actionClickedObjects; // 保存每个动作的点击记录 private bool _isMaterialProcess; // 标识当前流程是否为物料流程 private bool _teachingModeStrict; // 教学模式严格标志:true时必须答对才能继续,false允许容错 private GameObject _topic; //提示 private float _remainingTime; // 剩余时间(秒) private bool _isTimerRunning; // 计时器是否在运行 private float _totalTime; // 总时间(秒) #endregion #region 属性 public ProcessCollection CurrentProcessCollection => _processes[_currentMode.ToString()]; #endregion #region 事件定义 public delegate void CompleteEventHandler(float score); public delegate void UIEventHandler(); public delegate void StepProcessDescriptionMessage(string message); public delegate void StepProcessMessage(string message); public delegate void TeachingPromptsObjects(GameObject gameObj); public delegate void TeachingMessagePrompt(string message); /// /// 最后一步题目验证错误委托 /// public delegate void FinalStepValidationErrorHandler(string errorMessage, List wrongAnswers, List correctAnswers); /// /// 全部流程结束调用方法 /// public event CompleteEventHandler OnCompleteEvent; public event UIEventHandler OnUIEvent; /// /// 右上角消息通知 /// public event StepProcessMessage OnStepProcessMessage; /// /// 教学模式箭头指向 /// public event TeachingPromptsObjects OnTeachingPromptsObjects; /// /// 步骤描述 /// public event StepProcessDescriptionMessage OnStepProcessDescriptionMessage; /// /// 当前步骤所有动作完成事件 /// public event Action OnStepActionsCompleteEvent; /// /// 当前动作完成事件 /// public event Action OnCurrentActionCompleteEvent; /// /// 最后一步题目验证错误事件 /// public event FinalStepValidationErrorHandler OnFinalStepValidationError; /// /// 教学模式和课程预览最后一步完成事件 /// public event Action OnTeachingModeLastStepCompleteEvent; /// /// 所有模式的最后一个动作开始事件 /// public event Action OnLastActionStartEvent; #endregion #region 初始化方法 /// /// 初始化第一个步骤 /// public void InitializeFirstStep(string json, ProcessMode mode, GameObject topic = null) { _currentMode = mode; _topic = topic; _actionClickedObjects = new Dictionary<(int stepIndex, int actionIndex), List>(); _processes[_currentMode.ToString()] = new ProcessCollection(_currentMode.ToString()); List steps = JsonConvert.DeserializeObject>(json); foreach (var processStep in steps) { AddStepToProcess(_currentMode.ToString(), processStep); } // 初始化计时器 var examInfo = MotionEngine.GetModule()?.ExamInfo; if (examInfo != null && !string.IsNullOrEmpty(examInfo.Time) && int.TryParse(examInfo.Time, out int minutes)) { _totalTime = minutes * 60; // 转换为秒 _remainingTime = _totalTime; _isTimerRunning = true; } // 判断当前流程是否为物料流程 _isMaterialProcess = IsMaterialProcess(steps); _teachingModeStrict = false; InitTopic(); // 在教学模式下,根据流程类型和严格标志决定是否启用容错机制 if (_currentMode == ProcessMode.教学模式) { if (_isMaterialProcess) { Debug.Log("【框架消息】【教学模式初始化】检测到物料流程,保持原有严格逻辑,要求全部答案做完再继续下一步"); } else if (_teachingModeStrict) { Debug.Log("【框架消息】【教学模式初始化】教学模式严格标志为true,要求必须答对才能继续"); } else { Debug.Log("【框架消息】【教学模式初始化】检测到非物料流程且非严格模式,正在启用容错机制..."); SetTeachingModeTolerance(); Debug.Log("【框架消息】【教学模式初始化】容错机制设置完成,用户答错后可以继续学习"); } } InitializeModeFeedback(); } private void InitTopic() { if (_currentMode == ProcessMode.教学模式) { _topic.SetActive(true); _topic.GetComponent().Init(); } else { _topic.SetActive(false); } } /// /// 初始化模式反馈 /// private void InitializeModeFeedback() { if (_currentMode == ProcessMode.教学模式 || _currentMode == ProcessMode.培训模式) { if (CurrentProcessCollection.Steps.Count > 0) { ProcessStep firstStep = CurrentProcessCollection.Steps[0]; if (firstStep.Actions.Count > 0) { HandleModeFeedback(_currentMode, firstStep.Actions[0]); } } } } /// /// 重置流程并加载新的配置 /// /// 新的配置文件路径,如果为空则使用当前配置文件 /// 重置是否成功 public bool ResetProcess(string newJson, ProcessMode mode) { try { // 重置状态 _currentStepIndex = 0; _currentActionIndex = 0; _currentActionGameIndex = 0; _incorrectClicksPerStep.Clear(); _correctAnswersPerStep.Clear(); // 清空正确答案记录 _globalIncorrectClicks.Clear(); // 清空全局错误记录池 _validatedInputAnswers.Clear(); // 清空已验证输入答案记录 _submitScoreSteps.Clear(); // 清除当前流程集合 _processes.Clear(); Debug.Log($"【框架消息】流程已重置,新json--->{newJson}"); // 重新初始化第一个步骤 InitializeFirstStep(newJson, mode); // 重新判断流程类型(因为可能切换了不同的流程) if (_processes.ContainsKey(mode.ToString())) { var steps = _processes[mode.ToString()].Steps; _isMaterialProcess = IsMaterialProcess(steps); Debug.Log($"【框架消息】【流程重置】流程类型重新判断完成,是否为物料流程:{_isMaterialProcess}"); } return true; } catch (System.Exception e) { Debug.LogError($"【框架消息】重置流程失败:{e.Message}"); return false; } } // /// // /// 添加流程类型 // /// // public void AddProcessType(string type) // { // _processes[type] = new ProcessCollection(type); // Enum.TryParse(type, true, out _currentMode); // } /// /// 添加步骤到流程 /// private void AddStepToProcess(string type, ProcessStep step) { if (_processes.ContainsKey(type)) { _processes[type].AddStep(step); } } #endregion #region 点击处理逻辑 /// /// 处理选择题答案集合 /// public bool HandleChoiceQuestions(List answers) { Debug.Log($"【框架消息】【HandleChoiceQuestions】开始处理选择题答案,答案数量:{answers?.Count ?? 0}"); string type = _currentMode.ToString(); if (!_processes.ContainsKey(type)) return false; ProcessCollection processCollection = _processes[type]; if (_currentStepIndex >= processCollection.Steps.Count) return false; ProcessStep step = processCollection.Steps[_currentStepIndex]; ProcessStepDescription currentAction = step.Actions[_currentActionIndex]; // 只处理选择题类型 if (currentAction.ActionType != ProcessActionType.判断题) { Debug.Log("【框架消息】当前动作不是选择题类型!"); return false; } // 确保有选择题配置 if (currentAction.JudgmentQuestions == null || currentAction.JudgmentQuestions.Count == 0) { Debug.Log("【框架消息】当前动作没有配置选择题!"); return false; } // 检查答案数量是否匹配 // 检查答案数量是否匹配 if (answers.Count != currentAction.JudgmentQuestions.Count) { // 在教学模式下,根据流程类型决定是否启用容错机制 if (_currentMode == ProcessMode.教学模式) { // 判断当前动作是否为物料流程 bool isCurrentActionMaterial = false; if (!string.IsNullOrEmpty(currentAction.Title) && currentAction.Title.Contains("物料") && currentAction.Title.Contains("检查")) { isCurrentActionMaterial = true; Debug.Log($"【框架消息】【教学模式物料流程】检测到当前动作标题包含'物料'和'检查'关键词:{currentAction.Title},要求严格完成所有题目!"); } // 如果是物料流程,保持原有严格逻辑,不允许答案数量不匹配 if (isCurrentActionMaterial) { Debug.Log($"【框架消息】【教学模式物料流程】物料流程要求严格完成所有题目!题目总数:{currentAction.JudgmentQuestions.Count} 个,当前提交:{answers.Count} 个"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式提示】物料流程要求严格完成所有题目,请完成 {currentAction.JudgmentQuestions.Count} 个题目后再提交。"); return false; } else { // 非物料流程启用容错机制 Debug.Log($"【框架消息】【教学模式容错】答案数量不匹配!题目总数:{currentAction.JudgmentQuestions.Count} 个,当前提交:{answers.Count} 个"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式提示】您提交了 {answers.Count} 个答案,但题目总共有 {currentAction.JudgmentQuestions.Count} 个。建议您完成所有题目后再提交,或者您可以继续学习。"); // 在教学模式下,即使答案数量不匹配,也允许处理已提交的答案 if (answers.Count > 0) { Debug.Log("【框架消息】【教学模式容错】允许处理已提交的答案,继续学习过程"); // 继续处理已提交的答案,而不是直接返回false } else { // 如果没有提交任何答案,给出更友好的提示 OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式提示】请至少选择一个答案,或者您可以继续学习其他内容。"); return false; } } } else { // 非教学模式下的原有逻辑 Debug.Log($"【框架消息】答案数量不匹配!列表里一共有:{currentAction.JudgmentQuestions.Count} 个,传入的数量有:{answers.Count} 个"); return false; } } bool allCorrect = true; int processedAnswers = 0; // 安全处理答案:确保不会超出题目数组范围 int maxProcessCount = Math.Min(answers.Count, currentAction.JudgmentQuestions.Count); Debug.Log($"【框架消息】【安全检查】答案数量:{answers.Count},题目数量:{currentAction.JudgmentQuestions.Count},安全处理数量:{maxProcessCount}"); for (int i = 0; i < maxProcessCount; i++) { var question = currentAction.JudgmentQuestions[i]; // 使用忽略大小写的字符串比较,提高用户体验 bool isCorrect = IsStringEqualIgnoreCase(question.CorrectAnswer, answers[i]); if (isCorrect) { Debug.Log($"【框架消息】第{i + 1}题回答正确!问题:{question.Question},答案:{answers[i]}"); currentAction.ClickedObjects.Add(answers[i]); processedAnswers++; // 记录正确答案(避免重复记录) if (!_correctAnswersPerStep.ContainsKey((_currentStepIndex, _currentActionIndex))) { _correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)] = new List(); } // 检查是否已经记录过这个答案,避免重复记录 if (!_correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Contains(answers[i])) { _correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Add(answers[i]); Debug.Log($"【框架消息】【正确答案记录】记录正确答案:{answers[i]},当前动作正确数:{_correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Count}"); } else { Debug.Log($"【框架消息】【正确答案记录】答案 {answers[i]} 已记录,跳过重复记录"); } // 答对题目时将全局错误记录转移到当前动作,然后清空全局错误记录 if (_globalIncorrectClicks.Count > 0) { Debug.Log($"【框架消息】【错误记录转移】答对题目,将累积的 {_globalIncorrectClicks.Count} 个错误记录转移到当前动作"); Debug.Log($"【框架消息】【错误记录转移】转移的错误内容:{string.Join(", ", _globalIncorrectClicks)}"); // 将全局错误记录转移到当前动作 if (!_incorrectClicksPerStep.ContainsKey((_currentStepIndex, _currentActionIndex))) { _incorrectClicksPerStep[(_currentStepIndex, _currentActionIndex)] = new List(); } _incorrectClicksPerStep[(_currentStepIndex, _currentActionIndex)].AddRange(_globalIncorrectClicks); Debug.Log($"【框架消息】【错误记录转移】已将 {_globalIncorrectClicks.Count} 个错误转移到步骤 {_currentStepIndex + 1} 动作 {_currentActionIndex + 1}"); // 清空全局错误记录 _globalIncorrectClicks.Clear(); Debug.Log($"【框架消息】【错误记录转移】全局错误记录已清空,等待下次错误记录"); } } else { allCorrect = false; Debug.Log($"【框架消息】第{i + 1}题回答错误!问题:{question.Question},你的答案:{answers[i]},正确答案:{question.CorrectAnswer}"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】第{i + 1}题回答错误,正确答案:{question.CorrectAnswer}"); AddIncorrectClick(_currentStepIndex, _currentActionIndex, answers[i]); // 在教学模式下,即使答错也记录为已处理 if (_currentMode == ProcessMode.教学模式) { processedAnswers++; } } } // 在教学模式下,如果答案数量不匹配,给出进度提示 if (_currentMode == ProcessMode.教学模式 && answers.Count != currentAction.JudgmentQuestions.Count) { Debug.Log($"【框架消息】【教学模式进度】已处理 {processedAnswers} 个答案,总题目数 {currentAction.JudgmentQuestions.Count} 个"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式进度】您已完成 {processedAnswers}/{currentAction.JudgmentQuestions.Count} 个题目,继续加油!"); // 如果答案数量超过题目数量,给出额外提示 if (answers.Count > currentAction.JudgmentQuestions.Count) { Debug.Log($"【框架消息】【教学模式警告】答案数量({answers.Count})超过题目数量({currentAction.JudgmentQuestions.Count}),已安全处理前{maxProcessCount}个答案"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式提示】您提交了{answers.Count}个答案,但只有{currentAction.JudgmentQuestions.Count}个题目,已处理前{maxProcessCount}个答案"); } } // 使用全局配置和动作的单独配置 bool requireCorrect = currentAction.RequireCorrectCompletion; // 在教学模式下,根据严格标志决定是否允许容错 if (_currentMode == ProcessMode.教学模式) { if (answers.Count > 0) { // 更新当前题目索引,记录已完成的进度(使用安全处理的数量) currentAction.CurrentObjectIndex = maxProcessCount; Debug.Log($"【框架消息】【教学模式进度更新】当前题目索引更新为:{currentAction.CurrentObjectIndex}"); // 检查教学模式严格标志 if (_teachingModeStrict) { // 严格模式:必须全部答对才能继续 if (allCorrect) { Debug.Log($"【框架消息】【教学模式严格】所有答案正确,允许继续学习"); CompleteAction(step, currentAction); return true; } else { Debug.Log($"【框架消息】【教学模式严格】有错误答案,不允许继续,请重新作答"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式严格】有错误答案,请重新作答后再继续"); return false; } } else { // 容错模式:允许答错后继续学习 if (allCorrect || !requireCorrect) { Debug.Log($"【框架消息】【教学模式容错】已完成 {processedAnswers} 个题目,允许继续学习"); CompleteAction(step, currentAction); return true; } else { Debug.Log($"【框架消息】【教学模式容错】有错误答案但允许继续学习,已完成 {processedAnswers} 个题目"); CompleteAction(step, currentAction); return true; } } } else { // 没有提交任何答案,给出友好提示 OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式提示】请至少选择一个答案,或者您可以继续学习其他内容。"); return false; } } else { // 非教学模式下的原有逻辑 if (allCorrect || (!requireCorrect && IsInPracticeOrAssessment())) { currentAction.CurrentObjectIndex = currentAction.JudgmentQuestions.Count; CompleteAction(step, currentAction); return true; } else if (requireCorrect) { currentAction.ClickedObjects.Clear(); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】需要正确完成所有题目,请重新作答"); } } return false; } /// /// 处理点击事件 - 多选题答案集合版本 /// public bool HandleClick(List selectedAnswers) { if (_currentStepIndex == -1 || _currentActionIndex == -1) { Debug.Log($"【框架消息】【无匹配错误】当前步骤或动作索引无效:步骤={_currentStepIndex},动作={_currentActionIndex}"); return false; } string type = _currentMode.ToString(); if (!_processes.ContainsKey(type)) { Debug.Log($"【框架消息】【无匹配错误】流程类型不存在:{type}"); return false; } ProcessCollection processCollection = _processes[type]; if (_currentStepIndex >= processCollection.Steps.Count) { Debug.Log($"【框架消息】【无匹配错误】当前步骤索引超出范围:{_currentStepIndex},总步骤数:{processCollection.Steps.Count}"); return false; } // 智能匹配:根据答案内容和数量,找到匹配的动作 var matchedAction = FindMatchingAction(selectedAnswers, processCollection); Debug.Log($"【框架消息】【HandleClick调试】FindMatchingAction返回结果:{matchedAction}"); if (matchedAction.HasValue) { Debug.Log($"【框架消息】【HandleClick调试】找到智能匹配:步骤 {matchedAction.Value.stepIndex + 1} 动作 {matchedAction.Value.actionIndex + 1}"); int targetStepIndex = matchedAction.Value.stepIndex; int targetActionIndex = matchedAction.Value.actionIndex; // 如果匹配到的动作不是当前动作,则切换到目标动作 if (targetStepIndex != _currentStepIndex || targetActionIndex != _currentActionIndex) { Debug.Log($"【框架消息】【智能匹配】检测到答案匹配到步骤 {targetStepIndex + 1} 动作 {targetActionIndex + 1},当前在步骤 {_currentStepIndex + 1} 动作 {_currentActionIndex + 1}"); // 清空当前动作的已验证答案记录(清理所有UI的记录) var keysToRemove = _validatedInputAnswers.Keys.Where(key => key.stepIndex == _currentStepIndex && key.actionIndex == _currentActionIndex).ToList(); foreach (var key in keysToRemove) { _validatedInputAnswers.Remove(key); } if (keysToRemove.Count > 0) { Debug.Log($"【框架消息】切换到新动作,清空动作 {_currentActionIndex + 1} 的已验证答案记录(共 {keysToRemove.Count} 个UI)"); } _currentStepIndex = targetStepIndex; _currentActionIndex = targetActionIndex; ProcessStep targetStep = processCollection.Steps[targetStepIndex]; ProcessStepDescription targetAction = targetStep.Actions[targetActionIndex]; HandleModeFeedback(_currentMode, targetAction); } // 如果已经通过智能匹配找到了动作,直接处理当前动作,不再执行后续的考核模式匹配逻辑 ProcessStep matchedStep = processCollection.Steps[_currentStepIndex]; ProcessStepDescription matchedActionDesc = matchedStep.Actions[_currentActionIndex]; // 根据动作类型进行不同的处理 if (matchedActionDesc.ActionType == ProcessActionType.判断题) { return HandleChoiceQuestions(selectedAnswers); } else if (matchedActionDesc.ActionType == ProcessActionType.多选题) { // 只处理多选题类型 if (matchedActionDesc.MultipleChoiceQuestions == null || matchedActionDesc.MultipleChoiceQuestions.Count == 0) { return false; } // 获取当前要回答的多选题 if (matchedActionDesc.CurrentObjectIndex >= matchedActionDesc.MultipleChoiceQuestions.Count) { Debug.LogWarning("【框架消息】已经回答完所有多选题!"); return false; } var currentQuestion = matchedActionDesc.MultipleChoiceQuestions[matchedActionDesc.CurrentObjectIndex]; // 检查答案正确性 var correctAnswers = currentQuestion.Options; var selectedAnswersSet = new HashSet(selectedAnswers); var correctAnswersSet = new HashSet(correctAnswers); bool isCorrect = selectedAnswersSet.SetEquals(correctAnswersSet); if (isCorrect) { Debug.Log($"【框架消息】回答正确!问题:{currentQuestion.Question}"); matchedActionDesc.CurrentObjectIndex++; matchedActionDesc.ClickedObjects.Clear(); // 记录正确答案(避免重复记录) if (!_correctAnswersPerStep.ContainsKey((_currentStepIndex, _currentActionIndex))) { _correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)] = new List(); } // 记录所有选中的正确答案 foreach (var answer in selectedAnswers) { if (!_correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Contains(answer)) { _correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Add(answer); Debug.Log($"【框架消息】【正确答案记录】记录正确答案:{answer},当前动作正确数:{_correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Count}"); } else { Debug.Log($"【框架消息】【正确答案记录】答案 {answer} 已记录,跳过重复记录"); } } // 答对题目时将全局错误记录转移到当前动作,然后清空全局错误记录 if (_globalIncorrectClicks.Count > 0) { Debug.Log($"【框架消息】【错误记录转移】答对题目,将累积的 {_globalIncorrectClicks.Count} 个错误记录转移到当前动作"); Debug.Log($"【框架消息】【错误记录转移】转移的错误内容:{string.Join(", ", _globalIncorrectClicks)}"); // 将全局错误记录转移到当前动作 if (!_incorrectClicksPerStep.ContainsKey((_currentStepIndex, _currentActionIndex))) { _incorrectClicksPerStep[(_currentStepIndex, _currentActionIndex)] = new List(); } _incorrectClicksPerStep[(_currentStepIndex, _currentActionIndex)].AddRange(_globalIncorrectClicks); Debug.Log($"【框架消息】【错误记录转移】已将 {_globalIncorrectClicks.Count} 个错误转移到步骤 {_currentStepIndex + 1} 动作 {_currentActionIndex + 1}"); // 清空全局错误记录 _globalIncorrectClicks.Clear(); Debug.Log($"【框架消息】【错误记录转移】全局错误记录已清空,等待下次错误记录"); } if (matchedActionDesc.CurrentObjectIndex >= matchedActionDesc.MultipleChoiceQuestions.Count) { CompleteAction(matchedStep, matchedActionDesc); } return true; } else { Debug.Log($"【框架消息】回答错误!问题:{currentQuestion.Question}"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】回答错误"); AddIncorrectClick(_currentStepIndex, _currentActionIndex, string.Join(",", selectedAnswers)); // 在教学模式下,根据严格标志决定是否允许容错 if (_currentMode == ProcessMode.教学模式) { if (_teachingModeStrict) { // 严格模式:不允许答错后继续 Debug.Log($"【框架消息】【教学模式严格】多选题答错,不允许继续,请重新作答"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式严格】答错不允许继续,请重新作答,正确答案:{string.Join("、", correctAnswers)}"); return false; } else { // 容错模式:允许答错后继续学习 Debug.Log($"【框架消息】【教学模式容错】多选题答错后允许继续学习,当前题目:{currentQuestion.Question},正确答案:{string.Join("、", correctAnswers)},用户答案:{string.Join("、", selectedAnswers)}"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式容错】答错后允许继续学习,正确答案:{string.Join("、", correctAnswers)}"); matchedActionDesc.CurrentObjectIndex++; matchedActionDesc.ClickedObjects.Clear(); if (matchedActionDesc.CurrentObjectIndex >= matchedActionDesc.MultipleChoiceQuestions.Count) { CompleteAction(matchedStep, matchedActionDesc); } return true; } } // 如果不要求正确完成,且在考核模式下 else if (!matchedActionDesc.RequireCorrectCompletion && _currentMode == ProcessMode.考核模式) { matchedActionDesc.CurrentObjectIndex++; matchedActionDesc.ClickedObjects.Clear(); if (matchedActionDesc.CurrentObjectIndex >= matchedActionDesc.MultipleChoiceQuestions.Count) { CompleteAction(matchedStep, matchedActionDesc); } } return false; } } else if (matchedActionDesc.ActionType == ProcessActionType.默认) { // 处理默认动作类型 if (matchedActionDesc.TargetObjects != null && matchedActionDesc.TargetObjects.Count > 0) { // 检查答案是否匹配目标物体 int matchCount = 0; foreach (var answer in selectedAnswers) { if (matchedActionDesc.TargetObjects.Any(obj => obj.ObjectName == answer)) { matchCount++; } } if (matchCount == selectedAnswers.Count) { Debug.Log($"【框架消息】默认动作匹配成功!"); CompleteAction(matchedStep, matchedActionDesc); return true; } else { Debug.Log($"【框架消息】默认动作匹配失败!"); return false; } } return false; } // 如果智能匹配找到了动作,直接返回,不再执行后续的考核模式匹配逻辑 return false; } // 如果智能匹配没有找到结果,在考核模式下继续检查点击的答案是否属于其他步骤的动作 if (_currentMode == ProcessMode.考核模式) { bool foundTargetAction = false; // 首先检查当前步骤的动作 var currentStepActions = processCollection.Steps[_currentStepIndex]; for (int j = 0; j < currentStepActions.Actions.Count && !foundTargetAction; j++) { var action = currentStepActions.Actions[j]; bool isTargetAction = false; // 根据动作类型检查答案是否属于该动作 if (action.ActionType == ProcessActionType.判断题 && action.JudgmentQuestions != null) { // 先检查每个答案是否都匹配到该动作,再检查数量 Debug.Log($"【框架消息】【考核模式匹配调试】检查步骤 {_currentStepIndex + 1} 动作 {j + 1},输入:{string.Join(",", selectedAnswers)},期望:{string.Join(",", action.JudgmentQuestions.Select(q => q.CorrectAnswer))}"); // 检查每个输入答案是否都匹配到该动作的正确答案 int matchedAnswerCount = 0; foreach (var answer in selectedAnswers) { bool answerMatched = false; foreach (var question in action.JudgmentQuestions) { if (IsStringEqualIgnoreCase(question.CorrectAnswer, answer)) { answerMatched = true; Debug.Log($"【框架消息】【考核模式匹配调试】答案 '{answer}' 匹配到题目 '{question.Question}' 的正确答案 '{question.CorrectAnswer}'"); break; } } if (answerMatched) { matchedAnswerCount++; } else { Debug.Log($"【框架消息】【考核模式匹配调试】答案 '{answer}' 未匹配到该动作的任何正确答案"); } } Debug.Log($"【框架消息】【考核模式匹配调试】匹配的答案数量:{matchedAnswerCount},总输入答案数量:{selectedAnswers.Count}"); // 只有在每个答案都匹配到该动作的情况下,才检查数量是否匹配 if (matchedAnswerCount == selectedAnswers.Count) { if (selectedAnswers.Count == action.JudgmentQuestions.Count) { Debug.Log($"【框架消息】【考核模式匹配调试】步骤 {_currentStepIndex + 1} 动作 {j + 1} 每个答案都匹配且数量也匹配!"); isTargetAction = true; } else { Debug.Log($"【框架消息】【考核模式匹配调试】步骤 {_currentStepIndex + 1} 动作 {j + 1} 每个答案都匹配但数量不匹配!"); isTargetAction = false; } } else { Debug.Log($"【框架消息】【考核模式匹配调试】步骤 {_currentStepIndex + 1} 动作 {j + 1} 不是每个答案都匹配到该动作,跳过此动作"); isTargetAction = false; } } else if (action.ActionType == ProcessActionType.多选题 && action.MultipleChoiceQuestions != null) { // 先检查答案数量是否匹配 if (selectedAnswers.Count == action.MultipleChoiceQuestions.Count) { // 检查所有答案是否都匹配这个动作的多选题 int matchCount = 0; foreach (var answer in selectedAnswers) { if (action.MultipleChoiceQuestions.Any(q => q.Options.Contains(answer))) { matchCount++; } } isTargetAction = matchCount == selectedAnswers.Count; } } else if (action.ActionType == ProcessActionType.默认) { // 先检查答案数量是否匹配 if (selectedAnswers.Count == action.TargetObjects.Count) { // 检查所有答案是否都匹配这个动作的目标物体 int matchCount = 0; foreach (var answer in selectedAnswers) { if (action.TargetObjects.Any(obj => obj.ObjectName == answer)) { matchCount++; } } isTargetAction = matchCount == selectedAnswers.Count; } } // 如果找到目标动作,切换到该动作 if (isTargetAction) { _currentActionIndex = j; Debug.Log($"【框架消息】在当前步骤内切换到动作 {j + 1}"); HandleModeFeedback(_currentMode, action); foundTargetAction = true; break; } } // 如果当前步骤没找到匹配的动作,再查找其他步骤 if (!foundTargetAction) { for (int i = 0; i < processCollection.Steps.Count && !foundTargetAction; i++) { // 跳过当前步骤,因为已经检查过了 if (i == _currentStepIndex) continue; var otherStep = processCollection.Steps[i]; for (int j = 0; j < otherStep.Actions.Count && !foundTargetAction; j++) { var action = otherStep.Actions[j]; bool isTargetAction = false; // 根据动作类型检查答案是否属于该动作 if (action.ActionType == ProcessActionType.判断题 && action.JudgmentQuestions != null) { // 先检查每个答案是否都匹配到该动作,再检查数量 Debug.Log($"【框架消息】【考核模式匹配调试】检查步骤 {i + 1} 动作 {j + 1},输入:{string.Join(",", selectedAnswers)},期望:{string.Join(",", action.JudgmentQuestions.Select(q => q.CorrectAnswer))}"); // 检查每个输入答案是否都匹配到该动作的正确答案 int matchedAnswerCount = 0; foreach (var answer in selectedAnswers) { bool answerMatched = false; foreach (var question in action.JudgmentQuestions) { if (IsStringEqualIgnoreCase(question.CorrectAnswer, answer)) { answerMatched = true; Debug.Log($"【框架消息】【考核模式匹配调试】答案 '{answer}' 匹配到题目 '{question.Question}' 的正确答案 '{question.CorrectAnswer}'"); break; } } if (answerMatched) { matchedAnswerCount++; } else { Debug.Log($"【框架消息】【考核模式匹配调试】答案 '{answer}' 未匹配到该动作的任何正确答案"); } } Debug.Log($"【框架消息】【考核模式匹配调试】匹配的答案数量:{matchedAnswerCount},总输入答案数量:{selectedAnswers.Count}"); // 只有在每个答案都匹配到该动作的情况下,才检查数量是否匹配 if (matchedAnswerCount == selectedAnswers.Count) { if (selectedAnswers.Count == action.JudgmentQuestions.Count) { Debug.Log($"【框架消息】【考核模式匹配调试】步骤 {i + 1} 动作 {j + 1} 每个答案都匹配且数量也匹配!"); isTargetAction = true; } else { Debug.Log($"【框架消息】【考核模式匹配调试】步骤 {i + 1} 动作 {j + 1} 每个答案都匹配但数量不匹配!"); isTargetAction = false; } } else { Debug.Log($"【框架消息】【考核模式匹配调试】步骤 {i + 1} 动作 {j + 1} 不是每个答案都匹配到该动作,跳过此动作"); isTargetAction = false; } } else if (action.ActionType == ProcessActionType.多选题 && action.MultipleChoiceQuestions != null) { // 先检查答案数量是否匹配 if (selectedAnswers.Count == action.MultipleChoiceQuestions.Count) { // 检查所有答案是否都匹配这个动作的多选题 int matchCount = 0; foreach (var answer in selectedAnswers) { if (action.MultipleChoiceQuestions.Any(q => q.Options.Contains(answer))) { matchCount++; } } isTargetAction = matchCount == selectedAnswers.Count; } } else if (action.ActionType == ProcessActionType.默认) { // 先检查答案数量是否匹配 if (selectedAnswers.Count == action.TargetObjects.Count) { // 检查所有答案是否都匹配这个动作的目标物体 int matchCount = 0; foreach (var answer in selectedAnswers) { if (action.TargetObjects.Any(obj => obj.ObjectName == answer)) { matchCount++; } } isTargetAction = matchCount == selectedAnswers.Count; } } // 如果找到目标动作,切换到该动作 if (isTargetAction) { _currentStepIndex = i; _currentActionIndex = j; Debug.Log($"【框架消息】切换到步骤 {i + 1} 动作 {j + 1}"); HandleModeFeedback(_currentMode, action); foundTargetAction = true; break; } } } } } // 处理当前步骤和动作(只有在没有通过智能匹配找到动作时才执行) ProcessStep currentStep = processCollection.Steps[_currentStepIndex]; ProcessStepDescription currentAction = currentStep.Actions[_currentActionIndex]; // 记录智能匹配失败的错误 AddIncorrectClick(_currentStepIndex, _currentActionIndex, string.Join(",", selectedAnswers)); Debug.Log($"【框架消息】【无匹配错误】智能匹配失败,输入内容在流程中没有找到匹配的动作:{string.Join(",", selectedAnswers)}"); // 根据动作类型进行不同的处理 if (currentAction.ActionType == ProcessActionType.判断题) { return HandleChoiceQuestions(selectedAnswers); } else if (currentAction.ActionType == ProcessActionType.多选题) { // 只处理多选题类型 if (currentAction.MultipleChoiceQuestions == null || currentAction.MultipleChoiceQuestions.Count == 0) { Debug.LogWarning("【框架消息】当前动作没有配置多选题!"); return false; } // 获取当前要回答的多选题 if (currentAction.CurrentObjectIndex >= currentAction.MultipleChoiceQuestions.Count) { Debug.LogWarning("【框架消息】已经回答完所有多选题!"); return false; } var currentQuestion = currentAction.MultipleChoiceQuestions[currentAction.CurrentObjectIndex]; // 检查答案正确性 var correctAnswers = currentQuestion.Options; var selectedAnswersSet = new HashSet(selectedAnswers); var correctAnswersSet = new HashSet(correctAnswers); bool isCorrect = selectedAnswersSet.SetEquals(correctAnswersSet); if (isCorrect) { Debug.Log($"【框架消息】回答正确!问题:{currentQuestion.Question}"); currentAction.CurrentObjectIndex++; currentAction.ClickedObjects.Clear(); // 记录正确答案(避免重复记录) if (!_correctAnswersPerStep.ContainsKey((_currentStepIndex, _currentActionIndex))) { _correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)] = new List(); } // 记录所有选中的正确答案 foreach (var answer in selectedAnswers) { if (!_correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Contains(answer)) { _correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Add(answer); Debug.Log($"【框架消息】【正确答案记录】记录正确答案:{answer},当前动作正确数:{_correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Count}"); } else { Debug.Log($"【框架消息】【正确答案记录】答案 {answer} 已记录,跳过重复记录"); } } // 答对题目时将全局错误记录转移到当前动作,然后清空全局错误记录 if (_globalIncorrectClicks.Count > 0) { Debug.Log($"【框架消息】【错误记录转移】答对题目,将累积的 {_globalIncorrectClicks.Count} 个错误记录转移到当前动作"); Debug.Log($"【框架消息】【错误记录转移】转移的错误内容:{string.Join(", ", _globalIncorrectClicks)}"); // 将全局错误记录转移到当前动作 if (!_incorrectClicksPerStep.ContainsKey((_currentStepIndex, _currentActionIndex))) { _incorrectClicksPerStep[(_currentStepIndex, _currentActionIndex)] = new List(); } _incorrectClicksPerStep[(_currentStepIndex, _currentActionIndex)].AddRange(_globalIncorrectClicks); Debug.Log($"【框架消息】【错误记录转移】已将 {_globalIncorrectClicks.Count} 个错误转移到步骤 {_currentStepIndex + 1} 动作 {_currentActionIndex + 1}"); // 清空全局错误记录 _globalIncorrectClicks.Clear(); Debug.Log($"【框架消息】【错误记录转移】全局错误记录已清空,等待下次错误记录"); } if (currentAction.CurrentObjectIndex >= currentAction.MultipleChoiceQuestions.Count) { CompleteAction(currentStep, currentAction); } return true; } else { Debug.Log($"【框架消息】回答错误!问题:{currentQuestion.Question}"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】回答错误"); AddIncorrectClick(_currentStepIndex, _currentActionIndex, string.Join(",", selectedAnswers)); // 在教学模式下,根据严格标志决定是否允许容错 if (_currentMode == ProcessMode.教学模式) { if (_teachingModeStrict) { // 严格模式:不允许答错后继续 Debug.Log($"【框架消息】【教学模式严格】多选题答错,不允许继续,请重新作答"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式严格】答错不允许继续,请重新作答,正确答案:{string.Join("、", correctAnswers)}"); return false; } else { // 容错模式:允许答错后继续学习 Debug.Log($"【框架消息】【教学模式容错】多选题答错后允许继续学习,当前题目:{currentQuestion.Question},正确答案:{string.Join("、", correctAnswers)},用户答案:{string.Join("、", selectedAnswers)}"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式容错】答错后允许继续学习,正确答案:{string.Join("、", correctAnswers)}"); currentAction.CurrentObjectIndex++; currentAction.ClickedObjects.Clear(); if (currentAction.CurrentObjectIndex >= currentAction.MultipleChoiceQuestions.Count) { CompleteAction(currentStep, currentAction); } return true; } } // 如果不要求正确完成,且在考核模式下 else if (!currentAction.RequireCorrectCompletion && _currentMode == ProcessMode.考核模式) { currentAction.CurrentObjectIndex++; currentAction.ClickedObjects.Clear(); if (currentAction.CurrentObjectIndex >= currentAction.MultipleChoiceQuestions.Count) { CompleteAction(currentStep, currentAction); } } return false; } } // 如果没有找到任何匹配,记录为错误点击 AddIncorrectClick(_currentStepIndex, _currentActionIndex, string.Join(",", selectedAnswers)); Debug.Log($"【框架消息】【无匹配错误】输入内容在流程中没有找到匹配的动作:{string.Join(",", selectedAnswers)}"); return false; } /// /// 处理点击事件 /// public bool HandleClick(string clickedObject) { Debug.Log($"【框架消息】【HandleClick(string)调试】点击物体:{clickedObject}"); // // 检测安全帽点击并通知引导系统 // if (!string.IsNullOrEmpty(clickedObject) && clickedObject.Contains("安全帽")) // { // Debug.Log($"【框架消息】检测到安全帽点击,通知引导系统"); // if (TutorialGuideManager.Instance != null) // { // TutorialGuideManager.Instance.RecordHelmetClick(clickedObject); // } // } string type = _currentMode.ToString(); if (!_processes.ContainsKey(type)) { Debug.Log($"【框架消息】【无匹配错误】流程类型不存在:{type}"); return false; } ProcessCollection processCollection = _processes[type]; if (_currentStepIndex >= processCollection.Steps.Count) { Debug.Log($"【框架消息】【无匹配错误】当前步骤索引超出范围:{_currentStepIndex},总步骤数:{processCollection.Steps.Count}"); return false; } // 将单个字符串转换为List,然后使用精确匹配逻辑 var selectedAnswers = new List { clickedObject }; var matchedAction = FindMatchingAction(selectedAnswers, processCollection); Debug.Log($"【框架消息】【HandleClick(string)调试】FindMatchingAction返回结果:{matchedAction}"); if (matchedAction.HasValue) { Debug.Log($"【框架消息】【HandleClick(string)调试】找到智能匹配:步骤 {matchedAction.Value.stepIndex + 1} 动作 {matchedAction.Value.actionIndex + 1}"); int targetStepIndex = matchedAction.Value.stepIndex; int targetActionIndex = matchedAction.Value.actionIndex; // 如果匹配到的动作不是当前动作,则切换到目标动作 if (targetStepIndex != _currentStepIndex || targetActionIndex != _currentActionIndex) { Debug.Log($"【框架消息】【智能匹配】检测到答案匹配到步骤 {targetStepIndex + 1} 动作 {targetActionIndex + 1},当前在步骤 {_currentStepIndex + 1} 动作 {_currentActionIndex + 1}"); // 清空当前动作的已验证答案记录(清理所有UI的记录) var keysToRemove = _validatedInputAnswers.Keys.Where(key => key.stepIndex == _currentStepIndex && key.actionIndex == _currentActionIndex).ToList(); foreach (var key in keysToRemove) { _validatedInputAnswers.Remove(key); } if (keysToRemove.Count > 0) { Debug.Log($"【框架消息】切换到新动作,清空动作 {_currentActionIndex + 1} 的已验证答案记录(共 {keysToRemove.Count} 个UI)"); } _currentStepIndex = targetStepIndex; _currentActionIndex = targetActionIndex; ProcessStep targetStep = processCollection.Steps[targetStepIndex]; ProcessStepDescription targetAction = targetStep.Actions[targetActionIndex]; HandleModeFeedback(_currentMode, targetAction); } // 如果已经通过智能匹配找到了动作,直接处理当前动作,不再执行后续的考核模式匹配逻辑 ProcessStep matchedStep = processCollection.Steps[_currentStepIndex]; ProcessStepDescription matchedActionDesc = matchedStep.Actions[_currentActionIndex]; // 根据动作类型进行不同的处理 if (matchedActionDesc.ActionType == ProcessActionType.判断题) { return HandleJudgmentQuestionClick(matchedStep, matchedActionDesc, clickedObject); } else if (matchedActionDesc.ActionType == ProcessActionType.多选题) { // 对于多选题,如果是单个点击,添加或移除选项 if (!matchedActionDesc.ClickedObjects.Contains(clickedObject)) { matchedActionDesc.ClickedObjects.Add(clickedObject); } else { matchedActionDesc.ClickedObjects.Remove(clickedObject); } return true; } else if (matchedActionDesc.ActionType == ProcessActionType.默认) { return HandleOtherModeClick(matchedStep, matchedActionDesc, clickedObject); } // 如果智能匹配找到了动作,直接返回,不再执行后续的考核模式匹配逻辑 return false; } // 如果智能匹配没有找到结果,继续使用原来的逻辑 // 查找这个物体可能属于哪个步骤和动作 var targetStepAction = FindTargetStepAndAction(clickedObject); Debug.Log($"【框架消息】【HandleClick(string)调试】FindTargetStepAndAction返回结果:{targetStepAction}"); if (_currentMode == ProcessMode.考核模式) { // 如果找到了目标步骤和动作 if (targetStepAction.HasValue) { int targetStepIndex = targetStepAction.Value.stepIndex; int targetActionIndex = targetStepAction.Value.actionIndex; // 如果点击的不是当前步骤和动作 if (targetStepIndex != _currentStepIndex || targetActionIndex != _currentActionIndex) { Debug.Log($"【框架消息】检测到点击了步骤 {targetStepIndex + 1} 的动作 {targetActionIndex + 1},当前在步骤 {_currentStepIndex + 1} 的动作 {_currentActionIndex + 1}"); // 如果要求严格按顺序完成,则记录错误点击 if (IsStrictSequence()) { ProcessStep currentStep = processCollection.Steps[_currentStepIndex]; ProcessStepDescription currentAction1 = currentStep.Actions[_currentActionIndex]; string correctObjectName = "当前步骤的目标物体"; if (currentAction1.TargetObjects.Count > 0 && currentAction1.CurrentObjectIndex < currentAction1.TargetObjects.Count) { correctObjectName = currentAction1.TargetObjects[currentAction1.CurrentObjectIndex].ObjectName; } Debug.Log($"【框架消息】顺序错误:应该先点击 {correctObjectName}"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】顺序错误,请先完成当前步骤"); AddIncorrectClick(_currentStepIndex, _currentActionIndex, clickedObject); return false; } else { // 如果允许跳步,则切换到目标步骤和动作 _currentStepIndex = targetStepIndex; _currentActionIndex = targetActionIndex; ProcessStep targetStep = processCollection.Steps[targetStepIndex]; ProcessStepDescription targetAction = targetStep.Actions[targetActionIndex]; Debug.Log($"【框架消息】已跳转到步骤 {targetStepIndex + 1} 的动作 {targetActionIndex + 1}"); HandleModeFeedback(_currentMode, targetAction); } } } } // 处理当前步骤和动作 ProcessStep step = processCollection.Steps[_currentStepIndex]; ProcessStepDescription currentAction = step.Actions[_currentActionIndex]; // 根据动作类型进行不同的处理 if (currentAction.ActionType == ProcessActionType.默认) { return HandleOtherModeClick(step, currentAction, clickedObject); } else if (currentAction.ActionType == ProcessActionType.判断题) { return HandleJudgmentQuestionClick(step, currentAction, clickedObject); } else if (currentAction.ActionType == ProcessActionType.多选题) { // 对于多选题,如果是单个点击,添加或移除选项 if (!currentAction.ClickedObjects.Contains(clickedObject)) { currentAction.ClickedObjects.Add(clickedObject); // 检查是否为正确答案,如果是则记录 if (currentAction.MultipleChoiceQuestions != null && currentAction.MultipleChoiceQuestions.Count > 0) { var currentQuestion = currentAction.MultipleChoiceQuestions[currentAction.CurrentObjectIndex]; if (currentQuestion.Options.Contains(clickedObject)) { // 记录正确答案(避免重复记录) if (!_correctAnswersPerStep.ContainsKey((_currentStepIndex, _currentActionIndex))) { _correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)] = new List(); } if (!_correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Contains(clickedObject)) { _correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Add(clickedObject); Debug.Log($"【框架消息】【正确答案记录】记录正确答案:{clickedObject},当前动作正确数:{_correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Count}"); } else { Debug.Log($"【框架消息】【正确答案记录】答案 {clickedObject} 已记录,跳过重复记录"); } } } } else { currentAction.ClickedObjects.Remove(clickedObject); // 移除正确答案记录 if (_correctAnswersPerStep.ContainsKey((_currentStepIndex, _currentActionIndex))) { _correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Remove(clickedObject); Debug.Log($"【框架消息】【正确答案记录】移除正确答案:{clickedObject},当前动作正确数:{_correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Count}"); } } return true; } // 如果没有找到任何匹配,记录为错误点击 AddIncorrectClick(_currentStepIndex, _currentActionIndex, clickedObject); Debug.Log($"【框架消息】【无匹配错误】点击物体在流程中没有找到匹配的动作:{clickedObject}"); return false; } /// /// 查找点击物体所属的步骤和动作 /// /// 点击的物体名称 /// 找到的步骤索引和动作索引,如果未找到则返回null private (int stepIndex, int actionIndex)? FindTargetStepAndAction(string clickedObject) { Debug.Log($"【框架消息】【FindTargetStepAndAction调试】开始查找物体:{clickedObject}"); string type = _currentMode.ToString(); if (!_processes.ContainsKey(type)) return null; ProcessCollection processCollection = _processes[type]; // 优先查找正确答案匹配的动作,然后再查找其他匹配 var exactAnswerMatches = new List<(int stepIndex, int actionIndex)>(); var otherMatches = new List<(int stepIndex, int actionIndex)>(); // 遍历所有步骤和动作 for (int i = 0; i < processCollection.Steps.Count; i++) { var step = processCollection.Steps[i]; for (int j = 0; j < step.Actions.Count; j++) { var action = step.Actions[j]; // 检查是否为正确答案匹配 if (action.ActionType == ProcessActionType.判断题 && action.JudgmentQuestions != null) { foreach (var question in action.JudgmentQuestions) { if (IsStringEqualIgnoreCase(question.CorrectAnswer, clickedObject)) { exactAnswerMatches.Add((i, j)); Debug.Log($"【框架消息】【FindTargetStepAndAction调试】找到正确答案匹配:步骤 {i + 1} 动作 {j + 1},题目:{question.Question},正确答案:{question.CorrectAnswer}"); break; } } } // 其他类型的匹配 else if (IsObjectInAction(clickedObject, action)) { otherMatches.Add((i, j)); Debug.Log($"【框架消息】【FindTargetStepAndAction调试】找到其他匹配:步骤 {i + 1} 动作 {j + 1}"); } } } // 优先返回正确答案匹配,按步骤顺序排序 if (exactAnswerMatches.Count > 0) { var result = exactAnswerMatches.OrderBy(x => x.stepIndex).ThenBy(x => x.actionIndex).First(); Debug.Log($"【框架消息】【FindTargetStepAndAction调试】返回正确答案匹配:步骤 {result.stepIndex + 1} 动作 {result.actionIndex + 1}"); return result; } // 如果没有正确答案匹配,返回其他匹配 if (otherMatches.Count > 0) { var result = otherMatches.OrderBy(x => x.stepIndex).ThenBy(x => x.actionIndex).First(); Debug.Log($"【框架消息】【FindTargetStepAndAction调试】返回其他匹配:步骤 {result.stepIndex + 1} 动作 {result.actionIndex + 1}"); return result; } Debug.Log($"【框架消息】【FindTargetStepAndAction调试】未找到任何匹配"); return null; } /// /// 根据答案集合智能匹配动作 /// /// 选择的答案集合 /// 流程集合 /// 匹配的动作索引,如果未找到则返回null private (int stepIndex, int actionIndex)? FindMatchingAction(List selectedAnswers, ProcessCollection processCollection) { if (selectedAnswers == null || selectedAnswers.Count == 0) return null; // 只进行精确匹配(数量+答案完全匹配) var exactMatch = FindExactMatch(selectedAnswers, processCollection); if (exactMatch.HasValue) { Debug.Log($"【框架消息】【智能匹配】找到精确匹配的动作:步骤 {exactMatch.Value.stepIndex + 1} 动作 {exactMatch.Value.actionIndex + 1},答案数量:{selectedAnswers.Count}"); return exactMatch.Value; } Debug.Log($"【框架消息】【智能匹配】未找到精确匹配的动作,答案数量:{selectedAnswers.Count}"); return null; } /// /// 寻找答案数量完全相等的精确匹配 /// /// 选择的答案集合 /// 流程集合 /// 匹配的动作索引,如果未找到则返回null private (int stepIndex, int actionIndex)? FindExactMatch(List selectedAnswers, ProcessCollection processCollection) { Debug.Log($"【框架消息】【精确匹配调试】开始查找精确匹配,输入答案:{string.Join(",", selectedAnswers)},答案数量:{selectedAnswers.Count},当前步骤:{_currentStepIndex + 1}"); // 优先检查当前步骤的动作,然后再检查其他步骤 var stepsToCheck = new List<(int stepIndex, ProcessStep step)>(); // 先添加当前步骤 if (_currentStepIndex >= 0 && _currentStepIndex < processCollection.Steps.Count) { stepsToCheck.Add((_currentStepIndex, processCollection.Steps[_currentStepIndex])); } // 再添加其他步骤 for (int i = 0; i < processCollection.Steps.Count; i++) { if (i != _currentStepIndex) { stepsToCheck.Add((i, processCollection.Steps[i])); } } // 遍历步骤和动作,寻找答案数量完全相等的匹配 foreach (var (stepIndex, step) in stepsToCheck) { Debug.Log($"【框架消息】【精确匹配调试】检查步骤 {stepIndex + 1}:{step.StepDescription}"); for (int j = 0; j < step.Actions.Count; j++) { var action = step.Actions[j]; Debug.Log($"【框架消息】【精确匹配调试】检查步骤 {stepIndex + 1} 动作 {j + 1}:{action.Title},完成状态:{action.IsCompleted}"); // 检查动作是否已完成,如果已完成则跳过该动作 if (action.IsCompleted) { Debug.Log($"【框架消息】【精确匹配调试】步骤 {stepIndex + 1} 动作 {j + 1} 已完成,跳过匹配检查"); continue; } // 根据动作类型进行匹配 bool isMatch = false; switch (action.ActionType) { case ProcessActionType.判断题: if (action.JudgmentQuestions != null && action.JudgmentQuestions.Count > 0) { // Debug.Log($"【框架消息】【精确匹配调试】步骤 {stepIndex + 1} 动作 {j + 1} 是判断题,有 {action.JudgmentQuestions.Count} 个问题"); // 先检查每个答案是否都匹配到该动作,再检查数量 Debug.Log($"【框架消息】【精确匹配调试】开始检查每个答案是否都匹配到该动作,输入:{string.Join(",", selectedAnswers)},动作期望答案:{string.Join(",", action.JudgmentQuestions.Select(q => q.CorrectAnswer))}"); // 检查每个输入答案是否都匹配到该动作的正确答案 int matchedAnswerCount = 0; foreach (var answer in selectedAnswers) { bool answerMatched = false; foreach (var question in action.JudgmentQuestions) { if (IsStringEqualIgnoreCase(question.CorrectAnswer, answer)) { answerMatched = true; Debug.Log($"【框架消息】【精确匹配调试】答案 '{answer}' 匹配到题目 '{question.Question}' 的正确答案 '{question.CorrectAnswer}'"); break; } } if (answerMatched) { matchedAnswerCount++; } else { Debug.Log($"【框架消息】【精确匹配调试】答案 '{answer}' 未匹配到该动作的任何正确答案"); } } Debug.Log($"【框架消息】【精确匹配调试】匹配的答案数量:{matchedAnswerCount},总输入答案数量:{selectedAnswers.Count}"); // 检查是否有任何答案匹配到该动作 if (matchedAnswerCount > 0) { Debug.Log($"【框架消息】【精确匹配调试】有 {matchedAnswerCount} 个答案匹配到该动作,匹配率:{(float)matchedAnswerCount / selectedAnswers.Count:F2}"); // 只有在每个答案都匹配到该动作的情况下,才检查数量是否匹配 if (matchedAnswerCount == selectedAnswers.Count) { if (selectedAnswers.Count == action.JudgmentQuestions.Count) { Debug.Log($"【框架消息】【精确匹配调试】完全匹配!输入:{selectedAnswers.Count},动作:{action.JudgmentQuestions.Count}"); isMatch = true; } else { Debug.Log($"【框架消息】【精确匹配调试】答案匹配但数量不匹配!输入:{selectedAnswers.Count},动作:{action.JudgmentQuestions.Count}"); isMatch = false; } } else { Debug.Log($"【框架消息】【精确匹配调试】不是每个答案都匹配!输入:{selectedAnswers.Count},匹配:{matchedAnswerCount}"); isMatch = false; } } else { Debug.Log($"【框架消息】【精确匹配调试】没有任何答案匹配到该动作,跳过此动作"); isMatch = false; } } break; case ProcessActionType.多选题: if (action.MultipleChoiceQuestions != null && action.MultipleChoiceQuestions.Count > 0) { // 检查答案数量是否完全相等 if (selectedAnswers.Count == action.MultipleChoiceQuestions.Count) { // 使用频率映射进行严格的答案匹配 var inputAnswerFreq = new Dictionary(); var expectedAnswerFreq = new Dictionary(); // 统计输入答案的频率 foreach (var answer in selectedAnswers) { string normalizedAnswer = NormalizeChineseSymbols(answer).ToLowerInvariant(); if (inputAnswerFreq.ContainsKey(normalizedAnswer)) inputAnswerFreq[normalizedAnswer]++; else inputAnswerFreq[normalizedAnswer] = 1; } // 统计期望答案的频率 foreach (var question in action.MultipleChoiceQuestions) { foreach (var option in question.Options) { string normalizedOption = NormalizeChineseSymbols(option).ToLowerInvariant(); if (expectedAnswerFreq.ContainsKey(normalizedOption)) expectedAnswerFreq[normalizedOption]++; else expectedAnswerFreq[normalizedOption] = 1; } } // 检查频率是否完全匹配 bool frequencyMatch = true; foreach (var kvp in inputAnswerFreq) { if (!expectedAnswerFreq.ContainsKey(kvp.Key) || expectedAnswerFreq[kvp.Key] != kvp.Value) { frequencyMatch = false; break; } } // 还需要检查期望答案中是否有输入答案中没有的 if (frequencyMatch) { foreach (var kvp in expectedAnswerFreq) { if (!inputAnswerFreq.ContainsKey(kvp.Key) || inputAnswerFreq[kvp.Key] != kvp.Value) { frequencyMatch = false; break; } } } isMatch = frequencyMatch; } } break; case ProcessActionType.默认: if (action.TargetObjects != null && action.TargetObjects.Count > 0) { // 检查答案数量是否完全相等 if (selectedAnswers.Count == action.TargetObjects.Count) { // 使用频率映射进行严格的答案匹配 var inputAnswerFreq = new Dictionary(); var expectedAnswerFreq = new Dictionary(); // 统计输入答案的频率 foreach (var answer in selectedAnswers) { string normalizedAnswer = NormalizeChineseSymbols(answer).ToLowerInvariant(); if (inputAnswerFreq.ContainsKey(normalizedAnswer)) inputAnswerFreq[normalizedAnswer]++; else inputAnswerFreq[normalizedAnswer] = 1; } // 统计期望答案的频率 foreach (var targetObject in action.TargetObjects) { string normalizedObjectName = NormalizeChineseSymbols(targetObject.ObjectName).ToLowerInvariant(); if (expectedAnswerFreq.ContainsKey(normalizedObjectName)) expectedAnswerFreq[normalizedObjectName]++; else expectedAnswerFreq[normalizedObjectName] = 1; } // 检查频率是否完全匹配 bool frequencyMatch = true; foreach (var kvp in inputAnswerFreq) { if (!expectedAnswerFreq.ContainsKey(kvp.Key) || expectedAnswerFreq[kvp.Key] != kvp.Value) { frequencyMatch = false; break; } } // 还需要检查期望答案中是否有输入答案中没有的 if (frequencyMatch) { foreach (var kvp in expectedAnswerFreq) { if (!inputAnswerFreq.ContainsKey(kvp.Key) || inputAnswerFreq[kvp.Key] != kvp.Value) { frequencyMatch = false; break; } } } isMatch = frequencyMatch; } } break; } if (isMatch) { //Debug.Log($"【框架消息】【精确匹配调试】找到精确匹配:步骤 {stepIndex + 1} 动作 {j + 1}"); return (stepIndex, j); } } } // Debug.Log($"【框架消息】【精确匹配调试】未找到精确匹配"); return null; } /// /// 寻找包含所有输入答案的部分匹配 /// /// 选择的答案集合 /// 流程集合 /// 匹配的动作索引,如果未找到则返回null private (int stepIndex, int actionIndex)? FindPartialMatch(List selectedAnswers, ProcessCollection processCollection) { // 遍历所有步骤和动作,寻找包含所有输入答案的动作 for (int i = 0; i < processCollection.Steps.Count; i++) { var step = processCollection.Steps[i]; for (int j = 0; j < step.Actions.Count; j++) { var action = step.Actions[j]; // 根据动作类型进行匹配 bool isMatch = false; switch (action.ActionType) { case ProcessActionType.判断题: if (action.JudgmentQuestions != null && action.JudgmentQuestions.Count > 0) { // 检查所有输入答案是否都包含在这个动作的判断题中(只匹配正确答案) int matchCount = 0; foreach (var answer in selectedAnswers) { if (action.JudgmentQuestions.Any(q => IsStringEqualIgnoreCase(q.CorrectAnswer, answer))) { matchCount++; } } isMatch = matchCount == selectedAnswers.Count; } break; case ProcessActionType.多选题: if (action.MultipleChoiceQuestions != null && action.MultipleChoiceQuestions.Count > 0) { // 检查所有输入答案是否都包含在这个动作的多选题中 int matchCount = 0; foreach (var answer in selectedAnswers) { if (action.MultipleChoiceQuestions.Any(q => q.Options.Contains(answer))) { matchCount++; } } isMatch = matchCount == selectedAnswers.Count; } break; case ProcessActionType.默认: if (action.TargetObjects != null && action.TargetObjects.Count > 0) { // 检查所有输入答案是否都包含在这个动作的目标物体中 int matchCount = 0; foreach (var answer in selectedAnswers) { if (action.TargetObjects.Any(obj => obj.ObjectName == answer)) { matchCount++; } } isMatch = matchCount == selectedAnswers.Count; } break; } if (isMatch) { return (i, j); } } } return null; } /// /// 检查物体是否属于指定动作 /// private bool IsObjectInAction(string objectName, ProcessStepDescription action) { // 检查默认动作类型 if (action.ActionType == ProcessActionType.默认) { return action.TargetObjects.Any(obj => obj.ObjectName == objectName); } // 检查判断题(只匹配正确答案) else if (action.ActionType == ProcessActionType.判断题 && action.JudgmentQuestions != null) { foreach (var question in action.JudgmentQuestions) { if (IsStringEqualIgnoreCase(question.CorrectAnswer, objectName)) { return true; } } } // 检查多选题 else if (action.ActionType == ProcessActionType.多选题 && action.MultipleChoiceQuestions != null) { foreach (var question in action.MultipleChoiceQuestions) { if (question.Options.Contains(objectName) || question.Question == objectName) { return true; } } } return false; } /// /// 处理判断题类型的点击 /// private bool HandleJudgmentQuestionClick(ProcessStep step, ProcessStepDescription currentAction, string userAnswer) { // 确保有判断题配置 if (currentAction.JudgmentQuestions == null || currentAction.JudgmentQuestions.Count == 0) { Debug.LogWarning("【框架消息】当前动作没有配置判断题!"); return false; } // 获取当前要回答的判断题 if (currentAction.CurrentObjectIndex >= currentAction.JudgmentQuestions.Count) { Debug.LogWarning("【框架消息】已经回答完所有判断题!"); return false; } var currentQuestion = currentAction.JudgmentQuestions[currentAction.CurrentObjectIndex]; // 使用忽略大小写的字符串比较,提高用户体验 bool isCorrect = IsStringEqualIgnoreCase(currentQuestion.CorrectAnswer, userAnswer); if (isCorrect) { Debug.Log($"【框架消息】回答正确!问题:{currentQuestion.Question},答案:{userAnswer}"); currentAction.ClickedObjects.Add(userAnswer); // 记录已回答 currentAction.CurrentObjectIndex++; // 移动到下一题 // 记录正确答案(避免重复记录) if (!_correctAnswersPerStep.ContainsKey((_currentStepIndex, _currentActionIndex))) { _correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)] = new List(); } // 检查是否已经记录过这个答案,避免重复记录 if (!_correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Contains(userAnswer)) { _correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Add(userAnswer); Debug.Log($"【框架消息】【正确答案记录】记录正确答案:{userAnswer},当前动作正确数:{_correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Count}"); } else { Debug.Log($"【框架消息】【正确答案记录】答案 {userAnswer} 已记录,跳过重复记录"); } // 答对题目时将全局错误记录转移到当前动作,然后清空全局错误记录 if (_globalIncorrectClicks.Count > 0) { Debug.Log($"【框架消息】【错误记录转移】答对题目,将累积的 {_globalIncorrectClicks.Count} 个错误记录转移到当前动作"); Debug.Log($"【框架消息】【错误记录转移】转移的错误内容:{string.Join(", ", _globalIncorrectClicks)}"); // 将全局错误记录转移到当前动作 if (!_incorrectClicksPerStep.ContainsKey((_currentStepIndex, _currentActionIndex))) { _incorrectClicksPerStep[(_currentStepIndex, _currentActionIndex)] = new List(); } _incorrectClicksPerStep[(_currentStepIndex, _currentActionIndex)].AddRange(_globalIncorrectClicks); Debug.Log($"【框架消息】【错误记录转移】已将 {_globalIncorrectClicks.Count} 个错误转移到步骤 {_currentStepIndex + 1} 动作 {_currentActionIndex + 1}"); // 清空全局错误记录 _globalIncorrectClicks.Clear(); Debug.Log($"【框架消息】【错误记录转移】全局错误记录已清空,等待下次错误记录"); } // 如果所有题目都回答完了 if (currentAction.CurrentObjectIndex >= currentAction.JudgmentQuestions.Count) { CompleteAction(step, currentAction); } return true; } else { Debug.Log($"【框架消息】回答错误!问题:{currentQuestion.Question},你的答案:{userAnswer},正确答案:{currentQuestion.CorrectAnswer}"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】回答错误,正确答案:{currentQuestion.CorrectAnswer}"); AddIncorrectClick(_currentStepIndex, _currentActionIndex, userAnswer); // 使用全局配置和动作的单独配置 bool requireCorrect = currentAction.RequireCorrectCompletion; // 在教学模式下,根据严格标志决定是否允许容错 if (_currentMode == ProcessMode.教学模式) { if (_teachingModeStrict) { // 严格模式:不允许答错后继续 Debug.Log($"【框架消息】【教学模式严格】判断题答错,不允许继续,请重新作答"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式严格】答错不允许继续,请重新作答,正确答案:{currentQuestion.CorrectAnswer}"); return false; } else { // 容错模式:允许答错后继续学习 Debug.Log($"【框架消息】【教学模式容错】判断题答错后允许继续学习,当前题目:{currentQuestion.Question},正确答案:{currentQuestion.CorrectAnswer},用户答案:{userAnswer}"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式容错】答错后允许继续学习,正确答案:{currentQuestion.CorrectAnswer}"); currentAction.ClickedObjects.Add(userAnswer); currentAction.CurrentObjectIndex++; if (currentAction.CurrentObjectIndex >= currentAction.JudgmentQuestions.Count) { CompleteAction(step, currentAction); } return true; } } // 如果不要求正确完成,且在练习、考核模式下 else if (!requireCorrect && IsInPracticeOrAssessment()) { currentAction.ClickedObjects.Add(userAnswer); currentAction.CurrentObjectIndex++; if (currentAction.CurrentObjectIndex >= currentAction.JudgmentQuestions.Count) { CompleteAction(step, currentAction); } return true; } else if (requireCorrect) { // 如果要求正确完成,清空当前作答记录,重新开始当前题目 currentAction.ClickedObjects.Clear(); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】需要正确完成此题,请重新作答"); } return false; } } /// /// 处理教学模式下的点击 /// private bool HandleTeachingModeClick(ProcessStep step, ProcessStepDescription currentAction, string clickedObject) { if (currentAction.TargetObjects.Count == 0) return false; if (currentAction.TargetObjects[currentAction.CurrentObjectIndex].ObjectName == clickedObject) { ProcessCorrectClick(step, currentAction, clickedObject); return true; } string correctObjectName = currentAction.TargetObjects[currentAction.CurrentObjectIndex].ObjectName; Debug.Log($"【框架消息】错误点击:{clickedObject} --- 正确的物体是:{correctObjectName}"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】错误点击,正确对象:{correctObjectName}"); // 在教学模式下,根据严格标志决定是否允许容错 if (_currentMode == ProcessMode.教学模式) { if (_teachingModeStrict) { // 严格模式:不允许点击错误后继续 Debug.Log($"【框架消息】【教学模式严格】物体点击错误,不允许继续,请重新点击"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式严格】点击错误不允许继续,请重新点击,正确物体:{correctObjectName}"); return false; } else { // 容错模式:允许点击错误后继续学习 Debug.Log($"【框架消息】【教学模式容错】物体点击错误后允许继续学习,点击物体:{clickedObject},正确物体:{correctObjectName}"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式容错】点击错误后允许继续学习,正确物体:{correctObjectName}"); ProcessCorrectClick(step, currentAction, clickedObject); return true; } } // 如果不要求正确完成,且在练习、考核模式下 else if (!currentAction.RequireCorrectCompletion && IsInPracticeOrAssessment()) { ProcessCorrectClick(step, currentAction, clickedObject); return true; } return false; } /// /// 处理其他模式下的点击 /// private bool HandleOtherModeClick(ProcessStep step, ProcessStepDescription currentAction, string clickedObject) { if (currentAction.IsSequential) { return HandleSequentialClick(step, currentAction, clickedObject); } else { return HandleNonSequentialClick(step, currentAction, clickedObject); } } /// /// 处理顺序点击 /// private bool HandleSequentialClick(ProcessStep step, ProcessStepDescription currentAction, string clickedObject) { if (currentAction.CurrentObjectIndex < currentAction.TargetObjects.Count && currentAction.TargetObjects[currentAction.CurrentObjectIndex].ObjectName == clickedObject) { ProcessCorrectClick(step, currentAction, clickedObject); return true; } string correctObjectName = currentAction.TargetObjects[currentAction.CurrentObjectIndex].ObjectName; Debug.Log($"【框架消息】错误点击或顺序错误:{clickedObject}。正确的物体是:{correctObjectName}"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】错误点击,正确:{correctObjectName}"); AddIncorrectClick(_currentStepIndex, _currentActionIndex, clickedObject); // 使用全局配置和动作的单独配置 bool requireCorrect = currentAction.RequireCorrectCompletion; // 在教学模式下,根据严格标志决定是否允许容错 if (_currentMode == ProcessMode.教学模式) { if (_teachingModeStrict) { // 严格模式:不允许顺序错误后继续 Debug.Log($"【框架消息】【教学模式严格】顺序错误,不允许继续,请重新点击"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式严格】顺序错误不允许继续,请重新点击,正确物体:{correctObjectName}"); return false; } else { // 容错模式:允许顺序错误后继续学习 Debug.Log($"【框架消息】【教学模式容错】顺序错误后允许继续学习,点击物体:{clickedObject},正确物体:{correctObjectName}"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式容错】顺序错误后允许继续学习,正确物体:{correctObjectName}"); ProcessCorrectClick(step, currentAction, clickedObject); return true; } } // 如果不要求正确完成,且在练习、考核模式下 else if (!requireCorrect && IsInPracticeOrAssessment()) { ProcessCorrectClick(step, currentAction, clickedObject); return true; } return false; } /// /// 处理非顺序点击 /// private bool HandleNonSequentialClick(ProcessStep step, ProcessStepDescription currentAction, string clickedObject) { if (currentAction.TargetObjects.Any(obj => obj.ObjectName == clickedObject)) { if (currentAction.ClickedObjects.Contains(clickedObject)) { Debug.Log($"【框架消息】错误点击:{clickedObject}。这个物体已经点击过。"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】错误点击:{clickedObject}。这个物体已经点击过。"); return true; } ProcessCorrectClick(step, currentAction, clickedObject); return true; } HandleIncorrectClick(currentAction, clickedObject); // 使用全局配置和动作的单独配置 bool requireCorrect = currentAction.RequireCorrectCompletion; // 在教学模式下,根据严格标志决定是否允许容错 if (_currentMode == ProcessMode.教学模式) { if (_teachingModeStrict) { // 严格模式:不允许点击错误后继续 Debug.Log($"【框架消息】【教学模式严格】非顺序点击错误,不允许继续,请重新点击"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式严格】点击错误不允许继续,请重新点击"); return false; } else { // 容错模式:允许点击错误后继续学习 Debug.Log($"【框架消息】【教学模式容错】非顺序点击错误后允许继续学习,点击物体:{clickedObject}"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】【教学模式容错】点击错误后允许继续学习"); ProcessCorrectClick(step, currentAction, clickedObject); return true; } } // 如果不要求正确完成,且在练习、考核模式下 else if (!requireCorrect && IsInPracticeOrAssessment()) { ProcessCorrectClick(step, currentAction, clickedObject); return true; } return false; } /// /// 处理错误点击 /// private void HandleIncorrectClick(ProcessStepDescription currentAction, string clickedObject) { List correctObjectNames = currentAction.TargetObjects .Where(obj => !currentAction.ClickedObjects.Contains(obj.ObjectName)) .Select(obj => obj.ObjectName) .ToList(); string correctObjects = string.Join("【框架消息】,", correctObjectNames); Debug.Log($"【框架消息】错误点击:{clickedObject} 正确的物体是:{correctObjects}"); OnStepProcessDescriptionMessage?.Invoke($"【框架消息】错误点击,正确:{correctObjects}"); AddIncorrectClick(_currentStepIndex, _currentActionIndex, clickedObject); } #endregion #region 步骤处理逻辑 /// /// 处理正确点击 /// private void ProcessCorrectClick(ProcessStep step, ProcessStepDescription currentAction, string clickedObject) { Debug.Log($"【框架消息】正确点击了:{clickedObject}"); currentAction.ClickedObjects.Add(clickedObject); currentAction.CurrentObjectIndex++; // 记录正确答案(避免重复记录) if (!_correctAnswersPerStep.ContainsKey((_currentStepIndex, _currentActionIndex))) { _correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)] = new List(); } if (!_correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Contains(clickedObject)) { _correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Add(clickedObject); Debug.Log($"【框架消息】【正确答案记录】记录正确答案:{clickedObject},当前动作正确数:{_correctAnswersPerStep[(_currentStepIndex, _currentActionIndex)].Count}"); } else { Debug.Log($"【框架消息】【正确答案记录】答案 {clickedObject} 已记录,跳过重复记录"); } Debug.Log($"【框架消息】当前动作进度:{currentAction.CurrentObjectIndex}/{currentAction.TargetObjects.Count}"); if (currentAction.CurrentObjectIndex >= currentAction.TargetObjects.Count) { Debug.Log($"【框架消息】动作完成!当前索引:{currentAction.CurrentObjectIndex},目标数量:{currentAction.TargetObjects.Count}"); CompleteAction(step, currentAction); } else { HighlightNextObject(currentAction); } } /// /// 完成当前动作 /// private void CompleteAction(ProcessStep step, ProcessStepDescription currentAction) { Debug.Log($"【框架消息】【框架】完成了动作 {_currentActionIndex + 1}"); step.PlayAnimation(_currentActionIndex); // 注意:全局错误记录现在在单题答对时就会被清空,不再需要在动作完成时转移 // 这里保留按步骤记录的错误统计(用于兼容性) Debug.Log($"【框架消息】【动作完成】当前全局错误记录数量:{_globalIncorrectClicks.Count}"); // 保存当前动作的点击记录 _actionClickedObjects[(_currentStepIndex, _currentActionIndex)] = new List(currentAction.ClickedObjects); Debug.Log($"【框架消息】保存动作点击记录:{string.Join("【框架消息】, ", currentAction.ClickedObjects)}"); // 标记当前动作为已完成 currentAction.IsCompleted = true; // 触发当前动作完成事件 OnCurrentActionCompleteEvent?.Invoke(currentAction); // 清空当前动作的已验证答案记录,为下一个动作做准备(清理所有UI的记录) var keysToRemove = _validatedInputAnswers.Keys.Where(key => key.stepIndex == _currentStepIndex && key.actionIndex == _currentActionIndex).ToList(); foreach (var key in keysToRemove) { _validatedInputAnswers.Remove(key); } if (keysToRemove.Count > 0) { Debug.Log($"【框架消息】清空动作 {_currentActionIndex + 1} 的已验证答案记录(共 {keysToRemove.Count} 个UI)"); } _currentActionIndex++; currentAction.CurrentObjectIndex = 0; currentAction.ClickedObjects.Clear(); currentAction.FeedbackDisplayed = false; if (_currentActionIndex >= step.Actions.Count) { HandleStepCompletion(step); } else { Debug.Log("【框架消息】开始下一个动作!"); HandleModeFeedback(_currentMode, step.Actions[_currentActionIndex]); } } /// /// 处理步骤完成 /// private void HandleStepCompletion(ProcessStep step) { // 检查步骤中的所有动作是否都已完成 bool allActionsCompleted = true; for (int i = 0; i < step.Actions.Count; i++) { if (!step.Actions[i].IsCompleted) { allActionsCompleted = false; break; } } // 只有所有动作都完成了,才标记步骤为已完成 if (allActionsCompleted) { Debug.Log("【框架消息】所有动作完成!=>" + step.StepDescription); step.IsCompleted = true; // 触发当前步骤所有动作完成事件 OnStepActionsCompleteEvent?.Invoke(step); if (_currentMode == ProcessMode.练习模式) { HandlePracticeModeCompletion(); } else { HandleOtherModeCompletion(); } } else { // 如果还有未完成的动作,找到第一个未完成的动作 for (int i = 0; i < step.Actions.Count; i++) { if (!step.Actions[i].IsCompleted) { _currentActionIndex = i; Debug.Log($"【框架消息】步骤未完全完成,转到动作 {i + 1}"); HandleModeFeedback(_currentMode, step.Actions[i]); break; } } } } /// /// 处理练习模式完成 /// private void HandlePracticeModeCompletion() { if (_currentStepIndex < _processes[_currentMode.ToString()].Steps.Count) { var nextStep = _processes[_currentMode.ToString()].Steps[_currentStepIndex]; HandleModeFeedback(_currentMode, nextStep.Actions[0]); _currentActionIndex = 0; _currentStepIndex++; if (_currentStepIndex == _processes[_currentMode.ToString()].Steps.Count) { // 修复bug:使用真正的步骤完成验证,而不是简单的索引判断 if (AreAllStepsCompleted()) { Debug.Log("【框架消息】【练习模式】所有步骤都已经真正完成了!"); CompleteProcess(); } else { Debug.LogWarning("【框架消息】【练习模式步骤验证】检测到还有未完成的步骤,不允许完成流程"); Debug.Log("【框架消息】【练习模式步骤验证】请完成所有步骤后再继续"); // 找到第一个未完成的步骤并跳转到该步骤 var steps = _processes[_currentMode.ToString()].Steps; for (int i = 0; i < steps.Count; i++) { if (!steps[i].IsCompleted) { _currentStepIndex = i; _currentActionIndex = 0; Debug.Log($"【框架消息】【练习模式步骤验证】跳转到第一个未完成的步骤:{i + 1} - {steps[i].StepDescription}"); HandleModeFeedback(_currentMode, steps[i].Actions[0]); break; } } } } } else { // 修复bug:使用真正的步骤完成验证,而不是简单的索引判断 if (AreAllStepsCompleted()) { Debug.Log("【框架消息】【练习模式】所有步骤都已经真正完成了!"); CompleteProcess(); } else { Debug.LogWarning("【框架消息】【练习模式步骤验证】检测到还有未完成的步骤,不允许完成流程"); Debug.Log("【框架消息】【练习模式步骤验证】请完成所有步骤后再继续"); // 找到第一个未完成的步骤并跳转到该步骤 var steps = _processes[_currentMode.ToString()].Steps; for (int i = 0; i < steps.Count; i++) { if (!steps[i].IsCompleted) { _currentStepIndex = i; _currentActionIndex = 0; Debug.Log($"【框架消息】【练习模式步骤验证】跳转到第一个未完成的步骤:{i + 1} - {steps[i].StepDescription}"); HandleModeFeedback(_currentMode, steps[i].Actions[0]); break; } } } } } /// /// 验证所有步骤是否真正完成 /// /// 如果所有步骤都已完成则返回true,否则返回false private bool AreAllStepsCompleted() { if (!_processes.ContainsKey(_currentMode.ToString())) { Debug.LogError("【框架消息】当前模式不存在于流程中,无法验证步骤完成状态"); return false; } var steps = _processes[_currentMode.ToString()].Steps; int completedStepsCount = 0; int totalStepsCount = steps.Count; Debug.Log($"【框架消息】【步骤完成验证】开始验证所有步骤完成状态,总步骤数:{totalStepsCount}"); for (int i = 0; i < steps.Count; i++) { var step = steps[i]; if (step.IsCompleted) { completedStepsCount++; Debug.Log($"【框架消息】【步骤完成验证】步骤 {i + 1}:{step.StepDescription} - 已完成"); } else { Debug.Log($"【框架消息】【步骤完成验证】步骤 {i + 1}:{step.StepDescription} - 未完成"); } } bool allCompleted = completedStepsCount == totalStepsCount; Debug.Log($"【框架消息】【步骤完成验证】验证结果:{completedStepsCount}/{totalStepsCount} 步骤已完成,全部完成:{allCompleted}"); return allCompleted; } /// /// 处理其他模式完成 /// private void HandleOtherModeCompletion() { var steps = _processes[_currentMode.ToString()].Steps; // 检查是否已经是最后一个步骤的最后一个动作 if (_currentStepIndex >= steps.Count - 1) { // 已经是最后一个步骤,检查是否还有未完成的动作 var lastStep = steps[_currentStepIndex]; bool hasUncompletedAction = false; for (int i = 0; i < lastStep.Actions.Count; i++) { if (!lastStep.Actions[i].IsCompleted) { _currentActionIndex = i; hasUncompletedAction = true; Debug.Log($"【框架消息】【最后步骤】找到未完成的动作:{i + 1} - {lastStep.Actions[i].Title}"); HandleModeFeedback(_currentMode, lastStep.Actions[i]); break; } } // 如果最后一个步骤的所有动作都完成了,则流程结束 if (!hasUncompletedAction) { Debug.Log($"【框架消息】【最后步骤】最后一个步骤的所有动作都已完成,流程结束"); // 检测是否为教学模式或课程预览的最后一步 if (_currentMode == ProcessMode.教学模式 || _currentMode == ProcessMode.课程预览) { Debug.Log($"【框架消息】【教学/课程预览最后一步】检测到教学模式或课程预览的最后一步完成:{lastStep.StepDescription}"); // 触发教学模式和课程预览最后一步完成事件 OnTeachingModeLastStepCompleteEvent?.Invoke(lastStep); } // 修复bug:使用真正的步骤完成验证,而不是简单的索引判断 if (AreAllStepsCompleted()) { // 检查当前题目是否为"系统入库冲销凭证分析" if (IsCurrentExamSystemWarehouseAnalysis()) { Debug.Log("【框架消息】检测到当前题目为系统入库冲销凭证分析,开始验证用户输入..."); ValidateFinalStepAnswers(); } if (OnTeachingPromptsObjects != null) OnTeachingPromptsObjects(null); Debug.Log("【框架消息】所有步骤都已经真正完成了!"); Debug.Log("【框架消息】========== 所有步骤完成,但不自动提交考试数据 =========="); TutorialGuideManager.Instance.HideGuide(); OnStepProcessMessage?.Invoke("所有步骤都已完成!"); TutorialGuideManager.Instance.DisableGuide(); // FileComponent.UploadExamFiles(); // 已取消自动提交考试功能 } else { Debug.LogWarning("【框架消息】【步骤完成验证】检测到还有未完成的步骤,不允许提交考试数据"); Debug.Log("【框架消息】【步骤完成验证】请完成所有步骤后再提交"); // 找到第一个未完成的步骤并跳转到该步骤 for (int i = 0; i < steps.Count; i++) { if (!steps[i].IsCompleted) { _currentStepIndex = i; _currentActionIndex = 0; Debug.Log($"【框架消息】【步骤完成验证】跳转到第一个未完成的步骤:{i + 1} - {steps[i].StepDescription}"); HandleModeFeedback(_currentMode, steps[i].Actions[0]); break; } } } } return; } // 不是最后一个步骤,继续正常的步骤切换逻辑 _currentActionIndex = 0; _currentStepIndex++; // 跳过已完成的步骤,直到找到未完成的步骤 while (_currentStepIndex < steps.Count && steps[_currentStepIndex].IsCompleted) { Debug.Log($"【框架消息】【步骤跳过】步骤 {_currentStepIndex + 1}:{steps[_currentStepIndex].StepDescription} 已完成,跳过"); _currentStepIndex++; } if (_currentStepIndex < steps.Count) { var nextStep = steps[_currentStepIndex]; Debug.Log($"【框架消息】【步骤跳转】开始继续下一个未完成的步骤:{_currentStepIndex + 1} - {nextStep.StepDescription}"); HandleModeFeedback(_currentMode, nextStep.Actions[0]); } else { // 所有步骤都已完成,流程结束 Debug.Log("【框架消息】【流程结束】所有步骤都已完成,流程结束"); // 检测是否为教学模式或课程预览的最后一步 if (_currentMode == ProcessMode.教学模式 || _currentMode == ProcessMode.课程预览) { // 获取最后一个步骤 var lastStep = steps[steps.Count - 1]; Debug.Log($"【框架消息】【教学/课程预览最后一步】检测到教学模式或课程预览的最后一步完成:{lastStep.StepDescription}"); // 触发教学模式和课程预览最后一步完成事件 OnTeachingModeLastStepCompleteEvent?.Invoke(lastStep); } // 修复bug:使用真正的步骤完成验证,而不是简单的索引判断 if (AreAllStepsCompleted()) { // 检查当前题目是否为"系统入库冲销凭证分析" if (IsCurrentExamSystemWarehouseAnalysis()) { Debug.Log("【框架消息】检测到当前题目为系统入库冲销凭证分析,开始验证用户输入..."); ValidateFinalStepAnswers(); } if (OnTeachingPromptsObjects != null) OnTeachingPromptsObjects(null); Debug.Log("【框架消息】所有步骤都已经真正完成了!"); Debug.Log("【框架消息】========== 所有步骤完成,但不自动提交考试数据 =========="); TutorialGuideManager.Instance.HideGuide(); OnStepProcessMessage?.Invoke("所有步骤都已完成!"); TutorialGuideManager.Instance.DisableGuide(); // FileComponent.UploadExamFiles(); // 已取消自动提交考试功能 } else { Debug.LogWarning("【框架消息】【步骤完成验证】检测到还有未完成的步骤,不允许提交考试数据"); Debug.Log("【框架消息】【步骤完成验证】请完成所有步骤后再提交"); // 找到第一个未完成的步骤并跳转到该步骤 for (int i = 0; i < steps.Count; i++) { if (!steps[i].IsCompleted) { _currentStepIndex = i; _currentActionIndex = 0; Debug.Log($"【框架消息】【步骤完成验证】跳转到第一个未完成的步骤:{i + 1} - {steps[i].StepDescription}"); HandleModeFeedback(_currentMode, steps[i].Actions[0]); break; } } } } } /// /// 完成整个流程 /// private void CompleteProcess() { OnCompleteEvent?.Invoke(CalculateTotalScore()); //UploadExamConponent.Upload(); Debug.Log("【框架消息】全部完成了!!!!"); } #endregion #region 高亮处理逻辑 /// /// 高亮显示下一个需要点击的物体 /// private void HighlightNextObject(ProcessStepDescription action) { if (action == null) return; bool hasDuplicates = action.TargetObjects.GroupBy(x => x.ObjectName).Any(g => g.Count() > 1); for (int i = action.CurrentObjectIndex; i < action.TargetObjects.Count; i++) { var nextTarget = action.TargetObjects[i]; string nextObjectName = nextTarget.ObjectName; if (hasDuplicates) { if (HandleDuplicateObjectHighlight(action, nextTarget, nextObjectName, i)) break; } else { if (HandleUniqueObjectHighlight(action, nextTarget, nextObjectName)) break; } } } /// /// 处理重复物体的高亮 /// private bool HandleDuplicateObjectHighlight(ProcessStepDescription action, TargetObjectConfig nextTarget, string nextObjectName, int currentIndex) { int count = action.TargetObjects.Take(currentIndex).Count(o => o.ObjectName == nextObjectName); string uniqueObjectKey = $"【框架消息】{nextObjectName}_{count}"; if (!action.ClickedObjects.Contains(uniqueObjectKey)) { DisplayHighlightFeedback(action, nextTarget, nextObjectName); return true; } return false; } /// /// 处理唯一物体的高亮 /// private bool HandleUniqueObjectHighlight(ProcessStepDescription action, TargetObjectConfig nextTarget, string nextObjectName) { if (!action.ClickedObjects.Contains(nextObjectName)) { DisplayHighlightFeedback(action, nextTarget, nextObjectName); return true; } return false; } /// /// 构建教学模式下的完整描述(包含题目和答案) /// private string BuildTeachingModeDescription(ProcessStepDescription action) { // 在教学模式和课程预览模式下,只显示动作描述,不显示具体题目和答案 if (_currentMode == ProcessMode.教学模式 || _currentMode == ProcessMode.课程预览) { var descriptions = new List(); // 只添加动作标题和描述,不包含具体的题目和答案 if (!string.IsNullOrEmpty(action.StepDescription)) { descriptions.Add(action.StepDescription); } return descriptions.Count > 0 ? string.Join(" ", descriptions) : action.Description; } // 其他模式保持原有逻辑 return action.Description; } /// /// 显示高亮反馈 /// private void DisplayHighlightFeedback(ProcessStepDescription action, TargetObjectConfig nextTarget, string nextObjectName) { // 构建教学模式下的完整描述(包含题目和答案) string fullDescription = BuildTeachingModeDescription(action); Debug.Log("【框架消息】提示:" + action.StepDescription); OnStepProcessMessage?.Invoke("【框架消息】提示:" + fullDescription); OnStepProcessDescriptionMessage?.Invoke($"{fullDescription} {nextObjectName}"); if (nextTarget.Type == ProcessTargetType.Event) { OnTeachingPromptsObjects?.Invoke(null); } else if (nextTarget.Type == ProcessTargetType.Model) { HighlightObject(nextObjectName); } } /// /// 高亮指定的物体 /// private void HighlightObject(string objectName) { try { var obj = GameObject.Find(objectName); if (obj != null) { obj.GetComponent().highlighted = true; OnTeachingPromptsObjects?.Invoke(obj); Debug.Log($"【框架消息】高亮显示:{objectName}"); } } catch (Exception e) { Debug.LogError($"【框架消息】异常高亮物体 {objectName}: {e.Message}"); } } #endregion #region 验证记录管理 /// /// 清空当前动作的已验证答案记录 /// private void ClearCurrentActionValidatedAnswers() { // 清空当前动作的已验证答案记录(清理所有UI的记录) var keysToRemove = _validatedInputAnswers.Keys.Where(key => key.stepIndex == _currentStepIndex && key.actionIndex == _currentActionIndex).ToList(); foreach (var key in keysToRemove) { _validatedInputAnswers.Remove(key); } if (keysToRemove.Count > 0) { Debug.Log($"【框架消息】【验证记录清理】开始新动作,清空动作 {_currentActionIndex + 1} 的已验证答案记录(共 {keysToRemove.Count} 个UI)"); } } #endregion #region 模式反馈处理 /// /// 根据当前模式处理相应的反馈逻辑 /// public void HandleModeFeedback(ProcessMode mode, object stepOrAction) { // 在开始新动作时,清空当前动作的已验证答案记录,避免前一个动作的验证记录干扰 ClearCurrentActionValidatedAnswers(); // 检查是否为整个流程的最后一个动作开始 if (stepOrAction is ProcessStepDescription action) { bool isLastActionInProcess = IsLastActionInProcessForStart(action); if (isLastActionInProcess) { Debug.Log($"【框架消息】【最后动作开始检测】检测到整个流程的最后一个动作开始:{action.Title}"); // 触发所有模式的最后一个动作开始事件 OnLastActionStartEvent?.Invoke(action); } } switch (mode) { case ProcessMode.教学模式: HandleTeachingModeFeedback(stepOrAction); break; case ProcessMode.培训模式: HandleTrainingModeFeedback(stepOrAction); break; case ProcessMode.练习模式: HandlePracticeModeFeedback(stepOrAction); break; case ProcessMode.考核模式: // 考核模式无提示 break; } } /// /// 处理教学模式反馈 /// private void HandleTeachingModeFeedback(object stepOrAction) { ProcessStepDescription stringStep = (ProcessStepDescription)stepOrAction; Debug.Log($"【框架消息】{stringStep.Title}---{stringStep.StepDescription}"); // 在教学模式下,显示包含题目和答案的完整描述 string fullDescription = BuildTeachingModeDescription(stringStep); OnStepProcessMessage?.Invoke(fullDescription); HighlightNextObject(stringStep); } /// /// 处理培训模式反馈 /// private void HandleTrainingModeFeedback(object stepOrAction) { if (stepOrAction is ProcessStepDescription action) { DisplayModeFeedback(action.StepDescription); } else if (stepOrAction is ProcessStep step && step.Actions.Count > 0) { DisplayModeFeedback(step.Actions[0].StepDescription); } } /// /// 处理练习模式反馈 /// private void HandlePracticeModeFeedback(object stepOrAction) { if (stepOrAction is ProcessStepDescription practiceAction) { if (IsLastTargetObject(practiceAction)) { ShowPracticeStep(practiceAction); } } else if (stepOrAction is ProcessStep practiceStep && practiceStep.Actions.Count > 0) { if (IsLastTargetObject(practiceStep.Actions[0])) { ShowPracticeStep(practiceStep.Actions[0]); } } } /// /// 显示模式反馈信息 /// private void DisplayModeFeedback(string message) { Debug.Log($"【框架消息】培训模式:{message}"); OnStepProcessMessage?.Invoke("【框架消息】提示:" + message); OnStepProcessDescriptionMessage?.Invoke("【框架消息】提示:" + message); } #endregion #region 工具方法 /// /// 将中文符号转换为英文符号 /// /// 输入字符串 /// 转换后的字符串 private string NormalizeChineseSymbols(string input) { if (string.IsNullOrEmpty(input)) return input; return input .Replace("(", "(") // 中文左括号转英文左括号 .Replace(")", ")") // 中文右括号转英文右括号 .Replace(",", ",") // 中文逗号转英文逗号 .Replace("、", ",") // 中文顿号转英文逗号 .Replace(";", ";") // 中文分号转英文分号 .Replace(":", ":") // 中文冒号转英文冒号 .Replace("?", "?") // 中文问号转英文问号 .Replace("!", "!"); // 中文感叹号转英文感叹号 } /// /// 忽略大小写比较两个字符串是否相等 /// /// 第一个字符串 /// 第二个字符串 /// 如果忽略大小写后相等则返回true,否则返回false private bool IsStringEqualIgnoreCase(string str1, string str2) { // 如果两个字符串都为null,则认为相等 if (string.IsNullOrEmpty(str1)&& string.IsNullOrEmpty(str2)) return true; // 如果只有一个为null,则认为不相等 if (string.IsNullOrEmpty(str1) || string.IsNullOrEmpty(str2) ) return false; // 先进行中文符号转换,再进行忽略大小写的比较 string normalizedStr1 = NormalizeChineseSymbols(str1); string normalizedStr2 = NormalizeChineseSymbols(str2); // 使用StringComparison.OrdinalIgnoreCase进行忽略大小写的比较 bool directMatch = string.Equals(normalizedStr1, normalizedStr2, StringComparison.OrdinalIgnoreCase); // 如果直接匹配失败,尝试智能匹配(处理逗号位置差异) if (!directMatch) { // 移除所有逗号后比较 string noCommaStr1 = normalizedStr1.Replace(",", ""); string noCommaStr2 = normalizedStr2.Replace(",", ""); bool noCommaMatch = string.Equals(noCommaStr1, noCommaStr2, StringComparison.OrdinalIgnoreCase); if (noCommaMatch) { Debug.Log($"【框架消息】【智能匹配】通过移除逗号匹配成功:'{str1}' <-> '{str2}'"); } return noCommaMatch; } return directMatch; } /// /// 添加错误点击记录 /// private void AddIncorrectClick(int stepIndex, int actionIndex, string clickedObject) { // 只记录到全局错误池,避免重复记录 _globalIncorrectClicks.Add(clickedObject); Debug.Log($"【框架消息】【全局错误记录】添加错误点击:{clickedObject},当前全局错误数量:{_globalIncorrectClicks.Count}"); // 不再同时记录到步骤池,避免重复记录 // 错误记录会在答对题目时统一转移到当前动作 } /// /// 判断是否为targetObjects中的最后一个物体 /// private bool IsLastTargetObject(ProcessStepDescription action) { return action.CurrentObjectIndex == action.TargetObjects.Count - 1; } /// /// 判断是否在练习、考核或教学模式 /// private bool IsInPracticeOrAssessment() { // return (MotionEngine.GetModule().GetProcessMode() == ProcessMode.Assessment || // MotionEngine.GetModule().GetProcessMode() == ProcessMode.Practice); return (_currentMode != ProcessMode.考核模式); } /// /// 清空全局错误记录 /// public void ClearGlobalIncorrectClicks() { _globalIncorrectClicks.Clear(); Debug.Log($"【框架消息】【错误记录管理】全局错误记录已清空"); } /// /// 获取全局错误记录数量 /// /// 全局错误记录的数量 public int GetGlobalIncorrectClicksCount() { return _globalIncorrectClicks.Count; } /// /// 获取全局错误记录内容 /// /// 全局错误记录的列表副本 public List GetGlobalIncorrectClicks() { return new List(_globalIncorrectClicks); } /// /// 检查是否为整个流程的最后一个动作(用于动作开始时检测) /// /// 要检测的动作 /// 如果是最后一个动作则返回true,否则返回false private bool IsLastActionInProcessForStart(ProcessStepDescription action) { try { if (!_processes.ContainsKey(_currentMode.ToString())) { return false; } var processCollection = _processes[_currentMode.ToString()]; var steps = processCollection.Steps; // 检查当前步骤是否为最后一个步骤 if (_currentStepIndex != steps.Count - 1) { return false; } // 检查当前动作是否为当前步骤的最后一个动作 if (_currentActionIndex != steps[_currentStepIndex].Actions.Count - 1) { return false; } // 检查传入的动作是否确实是当前动作 if (steps[_currentStepIndex].Actions[_currentActionIndex] != action) { return false; } Debug.Log($"【框架消息】【最后动作开始检测】确认是最后一个动作开始:步骤 {_currentStepIndex + 1}/{steps.Count},动作 {_currentActionIndex + 1}/{steps[_currentStepIndex].Actions.Count}"); return true; } catch (System.Exception ex) { Debug.LogError($"【框架消息】【最后动作开始检测】检测最后一个动作开始时发生异常:{ex.Message}"); return false; } } #endregion #region 模块接口实现 public void OnCreate(object createParam) { _processes = new Dictionary(); _incorrectClicksPerStep = new Dictionary<(int stepIndex, int actionIndex), List>(); _correctAnswersPerStep = new Dictionary<(int stepIndex, int actionIndex), List>(); // 初始化正确答案记录 _globalIncorrectClicks = new List(); // 初始化全局错误记录池 } public void OnUpdate() { if (_isTimerRunning && _remainingTime > 0) { _remainingTime -= Time.deltaTime; // Debug.Log($"【框架消息】剩余时间: {_remainingTime}"); if (_remainingTime <= 0) { _isTimerRunning = false; _remainingTime = 0; // 时间到,自动计算分数 // Debug.Log("【框架消息】考试时间到!"); OnCompleteEvent?.Invoke(CalculateTotalScore()); FileComponent.UploadExamFiles(); } } } public void OnDestroy() { // 清理逻辑 } public void OnGUI() { // GUI逻辑 } #endregion #region 辅助方法 /// /// 练习模式反馈 /// /// private void ShowPracticeStep(object nextStepOrAction) { if (nextStepOrAction is ProcessStepDescription practiceAction) { if (OnStepProcessMessage != null) OnStepProcessMessage(practiceAction.StepDescription); OnStepProcessDescriptionMessage?.Invoke(practiceAction.StepDescription); Debug.Log($"【框架消息】练习模式:{practiceAction.StepDescription}"); } } /// /// 计算总分 /// 新的分数分配逻辑:步骤分数按动作数量平均分配,动作分数按答案数量平均分配 /// 支持小数点分数计算,确保分数分配更加精确,完全保留小数点精度 /// 基础总分:所有动作按满分计算,错误扣分单独处理 /// public float CalculateTotalScore() { float totalScore = 0f; // 明确指定为float类型 Debug.Log("【框架消息】========== 开始计算总分(完全保留小数点精度)=========="); if (!_processes.ContainsKey(_currentMode.ToString())) { Debug.LogError("【框架消息】当前模式不存在于流程中"); return -1f; } var steps = _processes[_currentMode.ToString()].Steps; Debug.Log($"【框架消息】当前模式: {_currentMode}, 总步骤数: {steps.Count}"); for (int i = 0; i < steps.Count; i++) { var step = steps[i]; float stepScore = (float)step.Score; // 确保转换为float类型 int actionCount = step.Actions.Count; // 精确计算每个动作的分数,使用float除法 float actionScorePerAction = 0f; if (actionCount > 0) { actionScorePerAction = stepScore / (float)actionCount; } Debug.Log($"【框架消息】\n===== 步骤 {i + 1}: {step.StepDescription} ====="); Debug.Log($"【框架消息】步骤总分: {stepScore:F3}"); Debug.Log($"【框架消息】动作数量: {actionCount}"); Debug.Log($"【框架消息】每个动作分数: {actionScorePerAction:F3}"); for (int j = 0; j < step.Actions.Count; j++) { var action = step.Actions[j]; Debug.Log($"【框架消息】\n动作 {j + 1}: {action.Title}"); Debug.Log($"【框架消息】描述: {action.Description}"); Debug.Log($"【框架消息】动作分配分数: {actionScorePerAction:F3}"); // 所有动作都参与计分,包括未完成的动作 bool isActionCompleted = action.IsCompleted; Debug.Log($"【框架消息】动作完成状态: {isActionCompleted}"); // 精确计算每个答案的分数,使用float除法 int answerCount = GetTotalQuestionsCount(action); float answerScorePerAnswer = 0f; if (answerCount > 0) { answerScorePerAnswer = actionScorePerAction / (float)answerCount; } Debug.Log($"【框架消息】答案数量: {answerCount}"); Debug.Log($"【框架消息】每个答案分数: {answerScorePerAnswer:F3}"); // 获取正确答案数量 int correctAnswerCount = 0; if (_correctAnswersPerStep.ContainsKey((i, j))) { correctAnswerCount = _correctAnswersPerStep[(i, j)].Count; } // 基础总分:所有动作都按满分计算,错误扣分单独处理 float actionScore = actionScorePerAction; Debug.Log($"【框架消息】动作计分(基础总分):"); Debug.Log($"【框架消息】动作完成状态: {isActionCompleted}"); Debug.Log($"【框架消息】正确答案数量: {correctAnswerCount}"); Debug.Log($"【框架消息】总题目数量: {answerCount}"); Debug.Log($"【框架消息】正确率: {(answerCount > 0 ? (float)correctAnswerCount / (float)answerCount : 0f):F3}"); Debug.Log($"【框架消息】动作总分: {actionScorePerAction:F3}"); Debug.Log($"【框架消息】基础得分: {actionScore:F3}"); // 添加详细分析 if (correctAnswerCount < answerCount) { Debug.Log($"【框架消息】【得分分析】动作 {j + 1} 有错误:"); Debug.Log($"【框架消息】【得分分析】- 正确答案: {correctAnswerCount}/{answerCount}"); Debug.Log($"【框架消息】【得分分析】- 错误数量: {answerCount - correctAnswerCount}"); Debug.Log($"【框架消息】【得分分析】- 错误扣分将在后续计算"); } totalScore += actionScore; } } // 计算错误扣分 float errorPenalty = CalculateErrorPenalty(); totalScore -= errorPenalty; Debug.Log("【框架消息】========== 评分总结(完全保留小数点精度)=========="); Debug.Log($"【框架消息】基础总分: {(totalScore + errorPenalty):F3}"); Debug.Log($"【框架消息】错误扣分: {errorPenalty:F3}"); Debug.Log($"【框架消息】最终总分: {totalScore:F3}"); Debug.Log($"【框架消息】有错误步骤数: {_incorrectClicksPerStep.Count}"); Debug.Log($"【框架消息】全局错误记录数: {_globalIncorrectClicks.Count}"); Debug.Log("【框架消息】============计算结束================\n"); return totalScore; // 返回float类型,完全保留小数点精度 } /// /// 计算错误扣分 /// 扣分规则:按动作分值比例扣分,每个唯一错误扣分 = 动作分数 / 答案数量 /// 相同错误多次记录只按一次扣分 /// /// 错误扣分 private float CalculateErrorPenalty() { float totalPenalty = 0f; int totalErrorCount = 0; Debug.Log("【框架消息】【错误扣分计算】开始按动作分值比例计算扣分"); if (!_processes.ContainsKey(_currentMode.ToString())) { Debug.LogError("【框架消息】当前模式不存在于流程中"); return 0f; } var steps = _processes[_currentMode.ToString()].Steps; // 先输出所有错误记录的详细信息 Debug.Log("【框架消息】【错误记录详情】开始输出所有错误记录"); foreach (var kvp in _incorrectClicksPerStep) { Debug.Log($"【框架消息】【错误记录详情】步骤{kvp.Key.stepIndex + 1}动作{kvp.Key.actionIndex + 1}: {string.Join(", ", kvp.Value)}"); } if (_globalIncorrectClicks.Count > 0) { Debug.Log($"【框架消息】【错误记录详情】全局错误记录: {string.Join(", ", _globalIncorrectClicks)}"); } for (int i = 0; i < steps.Count; i++) { var step = steps[i]; float stepScore = (float)step.Score; int actionCount = step.Actions.Count; // 计算每个动作的分数 float actionScorePerAction = 0f; if (actionCount > 0) { actionScorePerAction = stepScore / (float)actionCount; } Debug.Log($"【框架消息】【步骤分析】步骤{i+1}: {step.StepDescription}, 总分: {stepScore}, 动作数: {actionCount}, 每个动作分数: {actionScorePerAction:F3}"); for (int j = 0; j < step.Actions.Count; j++) { var action = step.Actions[j]; // 获取动作的答案数量 int answerCount = GetTotalQuestionsCount(action); if (answerCount == 0) { Debug.Log($"【框架消息】【动作分析】步骤{i+1}动作{j+1}: {action.Title}, 答案数量为0,跳过"); continue; } // 计算每个答案的分数 float answerScorePerAnswer = actionScorePerAction / (float)answerCount; // 获取当前动作的错误记录 HashSet actionUniqueErrors = new HashSet(); // 从步骤错误记录中获取当前动作的错误 if (_incorrectClicksPerStep.ContainsKey((i, j))) { foreach (string error in _incorrectClicksPerStep[(i, j)]) { actionUniqueErrors.Add(error); totalErrorCount++; } } // 从全局错误记录中获取当前动作的错误(如果有的话) // 注意:全局错误记录通常已经转移到具体动作,这里主要是保险 if (i == _currentStepIndex && j == _currentActionIndex) { foreach (string error in _globalIncorrectClicks) { actionUniqueErrors.Add(error); totalErrorCount++; } } // 计算当前动作的错误扣分 int actionUniqueErrorCount = actionUniqueErrors.Count; float actionPenalty = 0f; // 判断动作是否完成 bool isActionCompleted = action.IsCompleted; if (!isActionCompleted) { // 未完成:扣掉整个动作分(无论是否有错误) actionPenalty = actionScorePerAction; Debug.Log($"【框架消息】【未完成动作扣分】步骤{i+1}动作{j+1}: {action.Title}"); Debug.Log($"【框架消息】【未完成动作扣分】动作未完成,扣掉整个动作分: {actionPenalty:F3}"); } else if (isActionCompleted && actionUniqueErrorCount > 0) { // 已完成但有错误:按比例扣分 actionPenalty = answerScorePerAnswer * actionUniqueErrorCount; Debug.Log($"【框架消息】【已完成动作扣分】步骤{i+1}动作{j+1}: {action.Title}"); Debug.Log($"【框架消息】【已完成动作扣分】动作已完成但有错误,按比例扣分: {actionPenalty:F3}"); } totalPenalty += actionPenalty; Debug.Log($"【框架消息】【动作分析】步骤{i+1}动作{j+1}: {action.Title}"); Debug.Log($"【框架消息】【动作分析】动作完成状态: {isActionCompleted}"); Debug.Log($"【框架消息】【动作分析】动作分数: {actionScorePerAction:F3}"); Debug.Log($"【框架消息】【动作分析】答案数量: {answerCount}"); Debug.Log($"【框架消息】【动作分析】每个答案分数: {answerScorePerAnswer:F3}"); Debug.Log($"【框架消息】【动作分析】唯一错误数量: {actionUniqueErrorCount}"); Debug.Log($"【框架消息】【动作分析】动作扣分: {actionPenalty:F3}"); Debug.Log($"【框架消息】【动作分析】错误内容: {(actionUniqueErrors.Count > 0 ? string.Join(", ", actionUniqueErrors) : "无")}"); } } Debug.Log($"【框架消息】【错误扣分计算】总错误次数: {totalErrorCount}"); Debug.Log($"【框架消息】【错误扣分计算】总扣分: {totalPenalty:F3}"); return totalPenalty; } /// /// 获取动作的总题目数量 /// private int GetTotalQuestionsCount(ProcessStepDescription action) { switch (action.ActionType) { case ProcessActionType.默认: return action.TargetObjects.Count; case ProcessActionType.判断题: return action.JudgmentQuestions.Count; case ProcessActionType.多选题: return action.MultipleChoiceQuestions.Count; default: return 0; } } /// /// 跳转到指定的步骤和动作 /// /// 步骤下标 /// 步骤下的动作下标 public async UniTask JumpToProcessAsync(int targetStepIndex = 0, int targetActionIndex = 0) { if (!_processes.ContainsKey(_currentMode.ToString())) { Debug.LogError($"【框架消息】流程 {_currentMode.ToString()} 不存在!"); return; } var targetProcessCollection = _processes[_currentMode.ToString()]; // 检查目标步骤索引是否有效 if (targetStepIndex < 0 || targetStepIndex >= targetProcessCollection.Steps.Count) { Debug.LogError($"【框架消息】目标步骤索引 {targetStepIndex} 超出范围!"); return; } // 获取目标步骤 var targetStep = targetProcessCollection.Steps[targetStepIndex]; // 如果指定了动作索引,则检查其有效性 if (targetActionIndex < 0 || targetActionIndex >= targetStep.Actions.Count) { Debug.LogError($"【框架消息】步骤 {targetStepIndex} 下目标动作索引 {targetActionIndex} 超出范围!"); return; } // 将目标步骤之前的步骤标记为已完成 for (int i = 0; i < targetStepIndex; i++) { var step = targetProcessCollection.Steps[i]; step.IsCompleted = true; // 记录每个动作的点击记录 for (int j = 0; j < step.Actions.Count; j++) { var action = step.Actions[j]; // 标记动作为已完成 action.IsCompleted = true; // 根据动作类型设置不同的点击记录 switch (action.ActionType) { case ProcessActionType.默认: _actionClickedObjects[(i, j)] = action.TargetObjects.Select(t => t.ObjectName).ToList(); break; case ProcessActionType.判断题: _actionClickedObjects[(i, j)] = action.JudgmentQuestions.Select(q => q.CorrectAnswer).ToList(); break; case ProcessActionType.多选题: _actionClickedObjects[(i, j)] = action.MultipleChoiceQuestions.SelectMany(q => q.CorrectAnswers).ToList(); break; } } } // 处理目标步骤内的动作 for (int j = 0; j < targetActionIndex; j++) { var action = targetStep.Actions[j]; // 标记动作为已完成 action.IsCompleted = true; action.CurrentObjectIndex = GetTotalQuestionsCount(action); // 设置当前索引为总数,表示完成 // 记录动作的点击记录 switch (action.ActionType) { case ProcessActionType.默认: _actionClickedObjects[(targetStepIndex, j)] = action.TargetObjects.Select(t => t.ObjectName).ToList(); break; case ProcessActionType.判断题: _actionClickedObjects[(targetStepIndex, j)] = action.JudgmentQuestions.Select(q => q.CorrectAnswer).ToList(); break; case ProcessActionType.多选题: _actionClickedObjects[(targetStepIndex, j)] = action.MultipleChoiceQuestions.SelectMany(q => q.CorrectAnswers).ToList(); break; } } // 遍历到目标步骤及动作 for (int stepIndex = 0; stepIndex < targetProcessCollection.Steps.Count; stepIndex++) { var step = targetProcessCollection.Steps[stepIndex]; // 检测场景加载状态,异步等待直到场景加载完成 for (int actionIndex = 0; actionIndex < step.Actions.Count; actionIndex++) { var action = step.Actions[actionIndex]; string str = action.TargetObjects.Count == 0 ? "没有目标!" : ""; foreach (var target in action.TargetObjects) { // 执行目标物体绑定的事件 await ExecuteObjectEventWhenSceneLoadedAsync(action, target.ObjectName); // 使用异步执行 } // 如果达到目标步骤且达到目标动作,则停止遍历 if (stepIndex == targetStepIndex && actionIndex == targetActionIndex) { _currentStepIndex = stepIndex; _currentActionIndex = actionIndex; Debug.Log($"【框架消息】已跳转到流程:{step.StepDescription} -> 动作:{action.Title}"); return; } } } } /// /// 检查场景加载状态并在场景加载完成后执行目标物体事件 /// /// 动作对象 /// 目标物体名称 private async UniTask ExecuteObjectEventWhenSceneLoadedAsync(ProcessStepDescription action, string objectName) { // 如果场景正在加载,则等待场景加载完成 if (MotionEngine.GetModule().IsLoading) { Debug.Log("【框架消息】场景正在加载,等待..."); // 使用 UniTask.WaitUntil 检查 SceneManagerSingleton 状态 await UniTask.WaitUntil(() => !(MotionEngine.GetModule().IsLoading)); // 等待场景加载完成 } action.ExecuteObjectEvent(objectName); } /// /// 判断当前流程是否为物料流程 /// /// 流程步骤列表 /// true表示是物料流程,false表示不是物料流程 private bool IsMaterialProcess(List steps) { if (steps == null || steps.Count == 0) { Debug.LogWarning("【框架消息】流程步骤为空,无法判断流程类型"); return false; } // 检查当前要执行的动作标题是否包含"物料"关键词 if (_currentStepIndex >= 0 && _currentStepIndex < steps.Count) { var currentStep = steps[_currentStepIndex]; if (currentStep.Actions != null && _currentActionIndex >= 0 && _currentActionIndex < currentStep.Actions.Count) { var currentAction = currentStep.Actions[_currentActionIndex]; // 检查当前动作标题是否包含"物料"关键词 if (!string.IsNullOrEmpty(currentAction.Title) && currentAction.Title.Contains("物料")&& currentAction.Title.Contains("检查")) { Debug.Log($"【框架消息】【流程类型判断】检测到当前动作标题包含'物料'关键词:{currentAction.Title},判定为物料流程"); return true; } // 检查当前动作描述是否包含"物料"关键词 //if (!string.IsNullOrEmpty(currentAction.Description) && // currentAction.Description.Contains("物料")) //{ // Debug.Log($"【框架消息】【流程类型判断】检测到当前动作描述包含'物料'关键词:{currentAction.Description},判定为物料流程"); // return true; //} //// 检查当前动作的判断题内容是否包含"物料"关键词 //if (currentAction.ActionType == ProcessActionType.判断题 && // currentAction.JudgmentQuestions != null && // currentAction.JudgmentQuestions.Count > 0) //{ // foreach (var question in currentAction.JudgmentQuestions) // { // if (!string.IsNullOrEmpty(question.Question) && // question.Question.Contains("物料")) // { // Debug.Log($"【框架消息】【流程类型判断】检测到当前动作判断题包含'物料'关键词:{question.Question},判定为物料流程"); // return true; // } // } //} //// 检查当前动作的多选题内容是否包含"物料"关键词 //if (currentAction.ActionType == ProcessActionType.多选题 && // currentAction.MultipleChoiceQuestions != null && // currentAction.MultipleChoiceQuestions.Count > 0) //{ // foreach (var question in currentAction.MultipleChoiceQuestions) // { // if (!string.IsNullOrEmpty(question.Question) && // question.Question.Contains("物料")) // { // Debug.Log($"【框架消息】【流程类型判断】检测到当前动作多选题包含'物料'关键词:{question.Question},判定为物料流程"); // return true; // } // } //} } } Debug.Log("【框架消息】【流程类型判断】当前动作未检测到物料相关关键词,判定为非物料流程"); return false; } /// /// 设置教学模式下的容错机制 /// private void SetTeachingModeTolerance() { if (!_processes.ContainsKey(_currentMode.ToString())) return; // 如果是物料流程或严格模式,则不启用容错机制,保持原有严格逻辑 if (_isMaterialProcess || _teachingModeStrict) { Debug.Log("【框架消息】【教学模式容错设置】检测到物料流程或严格模式,保持原有严格逻辑,不启用容错机制"); return; } var processCollection = _processes[_currentMode.ToString()]; int totalActions = 0; int modifiedActions = 0; foreach (var step in processCollection.Steps) { foreach (var action in step.Actions) { totalActions++; if (action.RequireCorrectCompletion) { action.RequireCorrectCompletion = false; modifiedActions++; } } } Debug.Log($"【框架消息】【教学模式容错设置】已为 {modifiedActions}/{totalActions} 个动作启用容错机制,允许答错后继续学习"); } /// /// 设置教学模式严格标志 /// /// true时必须答对才能继续,false允许容错 public void SetTeachingModeStrict(bool strict) { _teachingModeStrict = strict; Debug.Log($"【框架消息】【教学模式严格设置】教学模式严格标志设置为:{strict}"); // 如果当前是教学模式,重新应用容错设置 if (_currentMode == ProcessMode.教学模式) { if (strict) { Debug.Log("【框架消息】【教学模式严格设置】启用严格模式,要求必须答对才能继续"); } else { Debug.Log("【框架消息】【教学模式严格设置】启用容错模式,允许答错后继续学习"); SetTeachingModeTolerance(); } } } /// /// 获取教学模式严格标志状态 /// /// true表示严格模式,false表示容错模式 public bool GetTeachingModeStrict() { return _teachingModeStrict; } /// /// 设置流程完成要求 /// /// 是否严格按顺序完成 /// 是否要求正确完成才能进行 public void SetCompletionRequirements(bool strictSequence, bool correctCompletion) { // 为所有步骤和动作设置正确完成要求 string type = _currentMode.ToString(); if (_processes.ContainsKey(type)) { foreach (var step in _processes[type].Steps) { foreach (var action in step.Actions) { action.RequireCorrectCompletion = correctCompletion; } } } Debug.Log($"【框架消息】流程完成要求:严格顺序={strictSequence},正确完成={correctCompletion}"); } /// /// 判断当前模式是否需要严格按顺序执行 /// private bool IsStrictSequence() { // 只有考核模式下允许自由点击动作,其他模式都需要严格按顺序 return _currentMode != ProcessMode.考核模式; } /// /// 获取已用时间(秒) /// public int GetUsedTimeInSeconds() { return (int)(_totalTime - _remainingTime); } /// /// 获取剩余时间(秒) /// public int GetRemainingTimeInSeconds() { return (int)_remainingTime; } /// /// 获取当前步骤的标题名 /// /// 当前步骤的标题名,如果没有当前步骤则返回空字符串 public string GetCurrentStepTitle() { try { // 检查当前步骤索引是否有效 if (_currentStepIndex < 0 || _currentStepIndex >= _processes[_currentMode.ToString()].Steps.Count) { Debug.LogWarning($"【框架消息】当前步骤索引无效: {_currentStepIndex}"); return string.Empty; } // 获取当前步骤 var currentStep = _processes[_currentMode.ToString()].Steps[_currentStepIndex]; // 返回步骤描述作为标题(因为ProcessStep类中没有StepTitle属性) string stepTitle = currentStep.StepDescription; Debug.Log($"【框架消息】获取当前步骤标题: {stepTitle}"); return stepTitle; } catch (System.Exception ex) { Debug.LogError($"【框架消息】获取当前步骤标题时发生异常: {ex.Message}"); return string.Empty; } } /// /// 获取当前动作的标题名 /// /// 当前动作的标题名,如果没有当前动作则返回空字符串 public string GetCurrentActionTitle() { try { // 检查当前步骤索引是否有效 if (_currentStepIndex < 0 || _currentStepIndex >= _processes[_currentMode.ToString()].Steps.Count) { Debug.LogWarning($"【框架消息】当前步骤索引无效: {_currentStepIndex}"); return string.Empty; } // 检查当前动作索引是否有效 if (_currentActionIndex < 0 || _currentActionIndex >= _processes[_currentMode.ToString()].Steps[_currentStepIndex].Actions.Count) { Debug.LogWarning($"【框架消息】当前动作索引无效: {_currentActionIndex}"); return string.Empty; } // 获取当前动作 var currentAction = _processes[_currentMode.ToString()].Steps[_currentStepIndex].Actions[_currentActionIndex]; // 返回动作标题,如果没有标题则返回动作描述 string actionTitle = !string.IsNullOrEmpty(currentAction.Title) ? currentAction.Title : currentAction.Description; Debug.Log($"【框架消息】获取当前动作标题: {actionTitle}"); return actionTitle; } catch (System.Exception ex) { Debug.LogError($"【框架消息】获取当前动作标题时发生异常: {ex.Message}"); return string.Empty; } } /// /// 获取指定步骤的标题名 /// /// 步骤索引 /// 指定步骤的标题名,如果索引无效则返回空字符串 public string GetStepTitle(int stepIndex) { try { // 检查步骤索引是否有效 if (stepIndex < 0 || stepIndex >= _processes[_currentMode.ToString()].Steps.Count) { Debug.LogWarning($"【框架消息】步骤索引超出范围: {stepIndex}"); return string.Empty; } // 获取指定步骤 var step = _processes[_currentMode.ToString()].Steps[stepIndex]; // 返回步骤描述作为标题(因为ProcessStep类中没有StepTitle属性) string stepTitle = step.StepDescription; Debug.Log($"【框架消息】获取步骤 {stepIndex + 1} 标题: {stepTitle}"); return stepTitle; } catch (System.Exception ex) { Debug.LogError($"【框架消息】获取步骤标题时发生异常: {ex.Message}"); return string.Empty; } } /// /// 获取当前动作的正确答案列表 /// /// 当前动作的正确答案列表,如果没有当前动作则返回空列表 public List GetCurrentActionCorrectAnswers() { try { // 检查当前步骤索引是否有效 if (_currentStepIndex < 0 || _currentStepIndex >= _processes[_currentMode.ToString()].Steps.Count) { Debug.LogWarning($"【框架消息】当前步骤索引无效: {_currentStepIndex}"); return new List(); } // 检查当前动作索引是否有效 if (_currentActionIndex < 0 || _currentActionIndex >= _processes[_currentMode.ToString()].Steps[_currentStepIndex].Actions.Count) { Debug.LogWarning($"【框架消息】当前动作索引无效: {_currentActionIndex}"); return new List(); } // 获取当前动作 var currentAction = _processes[_currentMode.ToString()].Steps[_currentStepIndex].Actions[_currentActionIndex]; var correctAnswers = new List(); // 根据动作类型获取正确答案 switch (currentAction.ActionType) { case ProcessActionType.判断题: if (currentAction.JudgmentQuestions != null) { foreach (var question in currentAction.JudgmentQuestions) { if (!string.IsNullOrEmpty(question.CorrectAnswer)) { correctAnswers.Add(question.CorrectAnswer); } } } break; case ProcessActionType.多选题: if (currentAction.MultipleChoiceQuestions != null) { foreach (var question in currentAction.MultipleChoiceQuestions) { if (question.CorrectAnswers != null) { foreach (var correctAnswer in question.CorrectAnswers) { if (!string.IsNullOrEmpty(correctAnswer)) { correctAnswers.Add(correctAnswer); } } } } } break; case ProcessActionType.默认: if (currentAction.TargetObjects != null) { foreach (var target in currentAction.TargetObjects) { if (!string.IsNullOrEmpty(target.ObjectName)) { correctAnswers.Add(target.ObjectName); } } } break; } Debug.Log($"【框架消息】获取当前动作正确答案: {string.Join(", ", correctAnswers)}"); return correctAnswers; } catch (System.Exception ex) { Debug.LogError($"【框架消息】获取当前动作正确答案时发生异常: {ex.Message}"); return new List(); } } /// /// 检测当前动作的答案是否有重复 /// /// 答案重复统计字典,key为答案,value为重复次数 private Dictionary GetAnswerDuplicationInfo() { var correctAnswers = GetCurrentActionCorrectAnswers(); var duplicationInfo = new Dictionary(); foreach (var answer in correctAnswers) { if (duplicationInfo.ContainsKey(answer)) { duplicationInfo[answer]++; } else { duplicationInfo[answer] = 1; } } return duplicationInfo; } /// /// 验证输入是否匹配当前动作的正确答案 /// /// 用户输入 /// UI组件ID,用于区分不同UI的重复验证 /// 是否匹配当前动作的正确答案 public bool ValidateInputAgainstCurrentAction(string input, string uiId = "") { try { if (string.IsNullOrWhiteSpace(input)) { Debug.Log($"【框架消息】【输入验证】输入为空,验证失败 (UI: {uiId})"); return false; } // 获取当前动作的正确答案 var correctAnswers = GetCurrentActionCorrectAnswers(); if (correctAnswers.Count == 0) { Debug.LogWarning($"【框架消息】【输入验证】当前动作没有配置正确答案 (UI: {uiId})"); return false; } Debug.Log($"【框架消息】【输入验证】开始验证输入: '{input}' (UI: {uiId}),当前动作正确答案: [{string.Join(", ", correctAnswers)}]"); // 检查输入是否匹配任何正确答案 string matchedAnswer = null; foreach (var correctAnswer in correctAnswers) { if (IsStringEqualIgnoreCase(correctAnswer, input)) { matchedAnswer = correctAnswer; break; } } if (string.IsNullOrEmpty(matchedAnswer)) { Debug.Log($"【框架消息】【输入验证】输入验证失败: '{input}' 不匹配当前动作的正确答案 (UI: {uiId})"); return false; } // 获取答案重复信息 var duplicationInfo = GetAnswerDuplicationInfo(); bool hasDuplicates = duplicationInfo.Values.Any(count => count > 1); var currentKey = (_currentStepIndex, _currentActionIndex, uiId); Debug.Log($"【框架消息】【输入验证】答案重复信息: {string.Join(", ", duplicationInfo.Select(kvp => $"{kvp.Key}({kvp.Value}次)"))},有重复答案: {hasDuplicates}"); if (hasDuplicates) { // 有重复答案的情况:允许不同UI输入不同的重复答案 // 检查该UI是否已经输入过这个答案 if (_validatedInputAnswers.ContainsKey(currentKey) && IsStringEqualIgnoreCase(_validatedInputAnswers[currentKey], matchedAnswer)) { Debug.Log($"【框架消息】【输入验证】UI '{uiId}' 已输入过答案 '{matchedAnswer}',跳过重复验证"); return false; } // 记录该UI输入的答案 _validatedInputAnswers[currentKey] = matchedAnswer; Debug.Log($"【框架消息】【输入验证】输入验证成功(有重复答案): '{input}' 匹配正确答案 '{matchedAnswer}' (UI: {uiId})"); Debug.Log($"【框架消息】【输入验证】当前已验证答案记录: {string.Join(", ", _validatedInputAnswers.Where(kvp => kvp.Key.stepIndex == _currentStepIndex && kvp.Key.actionIndex == _currentActionIndex).Select(kvp => $"{kvp.Key.uiId}:{kvp.Value}"))}"); return true; } else { // 无重复答案的情况:严格按UI ID+答案配对 if (_validatedInputAnswers.ContainsKey(currentKey) && IsStringEqualIgnoreCase(_validatedInputAnswers[currentKey], matchedAnswer)) { Debug.Log($"【框架消息】【输入验证】UI '{uiId}' 已输入过答案 '{matchedAnswer}',跳过重复验证"); return false; } // 检查是否有其他UI已经输入过这个答案 bool answerUsedByOtherUI = _validatedInputAnswers.Any(kvp => kvp.Key.stepIndex == _currentStepIndex && kvp.Key.actionIndex == _currentActionIndex && kvp.Key.uiId != uiId && IsStringEqualIgnoreCase(kvp.Value, matchedAnswer)); if (answerUsedByOtherUI) { Debug.Log($"【框架消息】【输入验证】答案 '{matchedAnswer}' 已被其他UI使用,UI '{uiId}' 不能重复输入"); Debug.Log($"【框架消息】【输入验证】当前已验证答案记录: {string.Join(", ", _validatedInputAnswers.Where(kvp => kvp.Key.stepIndex == _currentStepIndex && kvp.Key.actionIndex == _currentActionIndex).Select(kvp => $"{kvp.Key.uiId}:{kvp.Value}"))}"); return false; } // 记录该UI输入的答案 _validatedInputAnswers[currentKey] = matchedAnswer; Debug.Log($"【框架消息】【输入验证】输入验证成功(无重复答案): '{input}' 匹配正确答案 '{matchedAnswer}' (UI: {uiId})"); Debug.Log($"【框架消息】【输入验证】当前已验证答案记录: {string.Join(", ", _validatedInputAnswers.Where(kvp => kvp.Key.stepIndex == _currentStepIndex && kvp.Key.actionIndex == _currentActionIndex).Select(kvp => $"{kvp.Key.uiId}:{kvp.Value}"))}"); return true; } } catch (System.Exception ex) { Debug.LogError($"【框架消息】【输入验证】验证输入时发生异常: {ex.Message}"); return false; } } /// /// 获取下一个步骤的标题名 /// /// 下一个步骤的标题名,如果没有下一个步骤则返回空字符串 public string GetNextStepTitle() { try { // 检查当前模式是否有效 if (!_processes.ContainsKey(_currentMode.ToString())) { Debug.LogWarning($"【框架消息】当前模式无效: {_currentMode}"); return string.Empty; } var processCollection = _processes[_currentMode.ToString()]; // 检查是否存在下一个步骤 int nextStepIndex = _currentStepIndex + 1; if (nextStepIndex >= processCollection.Steps.Count) { Debug.Log($"【框架消息】没有下一个步骤,当前步骤索引: {_currentStepIndex}"); return string.Empty; } // 获取下一个步骤 var nextStep = processCollection.Steps[nextStepIndex]; string nextStepTitle = nextStep.StepDescription; Debug.Log($"【框架消息】获取下一个步骤标题: {nextStepTitle}"); return nextStepTitle; } catch (System.Exception ex) { Debug.LogError($"【框架消息】获取下一个步骤标题时发生异常: {ex.Message}"); return string.Empty; } } /// /// 判断当前题目是否为"系统入库冲销凭证分析" /// /// 如果是则返回true,否则返回false private bool IsCurrentExamSystemWarehouseAnalysis() { try { var globalDataStorage = MotionEngine.GetModule(); if (globalDataStorage == null) { Debug.LogWarning("【框架消息】【题目验证】GlobalDataStorage模块不存在"); return false; } string examName = globalDataStorage.ExamName; if (string.IsNullOrEmpty(examName)) { Debug.LogWarning("【框架消息】【题目验证】ExamName为空"); return false; } Debug.Log($"【框架消息】【题目验证】当前题目名称: {examName}"); // 检查题目名称是否包含"系统入库冲销凭证分析" bool isSystemWarehouseAnalysis = examName.Contains("系统入库冲销凭证分析"); if (isSystemWarehouseAnalysis) { Debug.Log($"【框架消息】【题目验证】确认为系统入库冲销凭证分析题目"); } else { Debug.Log("【框架消息】【题目验证】不是系统入库冲销凭证分析题目"); } return isSystemWarehouseAnalysis; } catch (System.Exception ex) { Debug.LogError($"【框架消息】【题目验证】判断当前题目时发生异常: {ex.Message}"); return false; } } /// /// 验证最后一步的答案 /// private void ValidateFinalStepAnswers() { try { if (!_processes.ContainsKey(_currentMode.ToString())) { Debug.LogError("【框架消息】【答案验证】当前模式不存在于流程中"); return; } var processCollection = _processes[_currentMode.ToString()]; if (processCollection.Steps.Count == 0) { Debug.LogError("【框架消息】【答案验证】流程中没有步骤"); return; } // 获取最后一步 var lastStep = processCollection.Steps[processCollection.Steps.Count - 1]; if (lastStep.Actions == null || lastStep.Actions.Count == 0) { Debug.LogError("【框架消息】【答案验证】最后一步没有动作"); return; } var lastAction = lastStep.Actions[0]; if (lastAction.JudgmentQuestions == null || lastAction.JudgmentQuestions.Count == 0) { Debug.LogError("【框架消息】【答案验证】最后一步没有判断题"); return; } Debug.Log($"【框架消息】【答案验证】开始验证最后一步答案,题目数量: {lastAction.JudgmentQuestions.Count}"); // 获取用户已记录的正确答案 var lastStepIndex = processCollection.Steps.Count - 1; var lastActionIndex = 0; var userCorrectAnswers = new List(); if (_correctAnswersPerStep.ContainsKey((lastStepIndex, lastActionIndex))) { userCorrectAnswers = _correctAnswersPerStep[(lastStepIndex, lastActionIndex)].ToList(); } var wrongAnswers = new List(); var correctAnswers = new List(); Debug.Log($"【框架消息】【答案验证】用户已记录正确答案数量: {userCorrectAnswers.Count}"); Debug.Log($"【框架消息】【答案验证】用户已记录正确答案: {string.Join(", ", userCorrectAnswers)}"); // 验证每个题目的答案 for (int i = 0; i < lastAction.JudgmentQuestions.Count; i++) { var question = lastAction.JudgmentQuestions[i]; string correctAnswer = question.CorrectAnswer?.Trim() ?? ""; // 检查用户是否正确回答了这道题 bool userAnsweredCorrectly = userCorrectAnswers.Contains(correctAnswer); Debug.Log($"【框架消息】【答案验证】题目 {i + 1}: {question.Question}"); Debug.Log($"【框架消息】【答案验证】正确答案: {correctAnswer}"); Debug.Log($"【框架消息】【答案验证】用户是否答对: {userAnsweredCorrectly}"); if (!userAnsweredCorrectly) { wrongAnswers.Add($"{question.Question}: 用户未正确回答,正确答案是({correctAnswer})"); correctAnswers.Add($"{question.Question}: {correctAnswer}"); Debug.Log($"【框架消息】【答案验证】题目 {i + 1} 答案错误"); } else { Debug.Log($"【框架消息】【答案验证】题目 {i + 1} 答案正确"); } } // 如果有错误答案,触发错误验证事件 if (wrongAnswers.Count > 0) { string errorMessage = $"系统入库冲销凭证分析题目验证失败,共有 {wrongAnswers.Count} 个答案错误"; Debug.Log($"【框架消息】【答案验证】{errorMessage}"); // 触发错误验证委托,通知前端显示错误信息 OnFinalStepValidationError?.Invoke(errorMessage, wrongAnswers, correctAnswers); } else { Debug.Log("【框架消息】【答案验证】所有答案都正确,验证通过"); } } catch (System.Exception ex) { Debug.LogError($"【框架消息】【答案验证】验证最后一步答案时发生异常: {ex.Message}"); } } #endregion } }