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
}
}