Test-TaizhouWarehousePhaseII/3d/Assets/Framework/Manager/ProcessManager.cs

4091 lines
208 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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