4091 lines
208 KiB
C#
4091 lines
208 KiB
C#
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
|
||
}
|
||
} |