535 lines
15 KiB
C#
535 lines
15 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using System.Text;
|
||
using System.Xml;
|
||
using TMPro;
|
||
using Unity.VisualScripting;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using static System.Collections.Specialized.BitVector32;
|
||
|
||
public class QuizSystem : MonoBehaviour
|
||
{
|
||
public static QuizSystem Instance;
|
||
|
||
public string FileName;
|
||
// UI元素
|
||
public GameObject TiMuPage;
|
||
public TextMeshProUGUI questionText; // 显示问题文本
|
||
public ToggleGroup optionsGroup; // 选项组,用于单选题
|
||
public List<Toggle> optionToggles; // 选项切换按钮列表
|
||
public TextMeshProUGUI progressText; // 显示答题进度
|
||
//public Button submitButton; // 提交按钮
|
||
//上一题下一题页面
|
||
public GameObject CtrlPage;
|
||
public Button nextButton; // 下一题按钮
|
||
//public Button previousButton; // 上一题按钮
|
||
public TextMeshProUGUI hintText; // 显示提示文本
|
||
public TextMeshProUGUI selfText; // 显示已选文本
|
||
//结果页面
|
||
public GameObject ResultPage;//结果页面
|
||
public TextMeshProUGUI resultText; // 显示结果文本
|
||
public Button MoreOnceBtn;//再来一次
|
||
|
||
public Sprite[] AllColors;
|
||
|
||
//public Button hintButton; // 提示按钮
|
||
//public Slider timeSlider; // 时间滑动条
|
||
//public TextMeshProUGUI timerText; // 倒计时文本
|
||
|
||
// 问题列表和当前状态
|
||
private List<Question> questions = new List<Question>(); // 问题列表
|
||
private int currentQuestionIndex = 0; // 当前问题索引
|
||
private List<int> userAnswers = new List<int>(); // 用户答案列表
|
||
//private bool isSubmitted = false; // 是否已提交
|
||
|
||
// 总体倒计时
|
||
//private float totalTime = 1800f; // 总时间(秒)
|
||
//private float remainingTime; // 剩余时间
|
||
|
||
private void Awake()
|
||
{
|
||
Instance = this;
|
||
}
|
||
|
||
void Start()
|
||
{
|
||
// 确保数组不为空
|
||
if (optionToggles == null || optionToggles.Count == 0)
|
||
{
|
||
Debug.LogError("请先初始化optionToggles数组!");
|
||
return;
|
||
}
|
||
|
||
// 遍历每个Toggle,添加事件
|
||
for (int i = 0; i < optionToggles.Count; i++)
|
||
{
|
||
// 保存当前索引(避免lambda闭包捕获的i值异常)
|
||
int index = i;
|
||
// 清除旧监听(关键:避免重复添加导致多次触发)
|
||
optionToggles[i].onValueChanged.RemoveAllListeners();
|
||
// 添加新监听(使用lambda绑定回调,可传入索引和状态)
|
||
optionToggles[i].onValueChanged.AddListener((isOn) =>
|
||
{
|
||
// 调用处理方法(也可直接在这里写逻辑)
|
||
OnToggleValueChanged(index, isOn);
|
||
});
|
||
}
|
||
|
||
// 加载问题
|
||
LoadQuestions();
|
||
|
||
ToStartQuestion();
|
||
|
||
// 初始化倒计时
|
||
//remainingTime = totalTime;
|
||
//UpdateTimerUI();
|
||
}
|
||
|
||
|
||
// 处理Toggle状态变化的回调方法
|
||
void OnToggleValueChanged(int toggleIndex, bool isOn)
|
||
{
|
||
if (isOn)
|
||
{
|
||
//Debug.Log($"Toggle {toggleIndex} 被选中了!");
|
||
// 选中时的逻辑(例如:切换选项、更新UI等)
|
||
OnSubmitButtonClicked();
|
||
}
|
||
}
|
||
|
||
public void ToStartQuestion()
|
||
{
|
||
//结果和控制页面消失
|
||
ResultPage.SetActive(false);
|
||
CtrlPage.SetActive(false);
|
||
TiMuPage.SetActive(true);
|
||
|
||
userAnswers.Clear();
|
||
|
||
// 初始化用户答案列表
|
||
for (int i = 0; i < questions.Count; i++)
|
||
{
|
||
userAnswers.Add(-1);
|
||
}
|
||
|
||
currentQuestionIndex = 0;
|
||
|
||
// 显示第一个问题
|
||
DisplayQuestion();
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
//// 更新倒计时
|
||
//if (remainingTime > 0)
|
||
//{
|
||
// remainingTime -= Time.deltaTime;
|
||
// UpdateTimerUI();
|
||
//}
|
||
//else if (!isSubmitted)
|
||
//{
|
||
// // 时间到,自动提交
|
||
// OnSubmitButtonClicked();
|
||
//}
|
||
}
|
||
|
||
void LoadQuestions()
|
||
{
|
||
questions = XmlTextReaderTest();
|
||
}
|
||
|
||
void DisplayQuestion()
|
||
{
|
||
// 检查是否超出问题列表
|
||
if (currentQuestionIndex >= questions.Count)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 获取当前问题
|
||
Question question = questions[currentQuestionIndex];
|
||
questionText.text = question.Text;
|
||
progressText.text = $"【答题进度】 {GetAnsweredCount() + 1} / {questions.Count}";
|
||
|
||
// 显示选项
|
||
for (int i = 0; i < optionToggles.Count; i++)
|
||
{
|
||
optionToggles[i].transform.Find("Background").GetComponent<Image>().sprite = AllColors[0];
|
||
optionToggles[i].transform.Find("Background/Checkmark").GetComponent<Image>().sprite = AllColors[0];
|
||
if (i < question.Options.Length && (!question.IsJudgment || i < 2))
|
||
{
|
||
optionToggles[i].gameObject.SetActive(true);
|
||
var label = optionToggles[i].GetComponentInChildren<Text>();
|
||
if (label != null)
|
||
{
|
||
string sign = "A:";
|
||
switch (i)
|
||
{
|
||
case 0: sign = "A | "; break;
|
||
case 1: sign = "B | "; break;
|
||
case 2: sign = "C | "; break;
|
||
case 3: sign = "D | "; break;
|
||
}
|
||
label.text = sign+question.Options[i];
|
||
}
|
||
|
||
optionToggles[i].isOn = false;
|
||
|
||
|
||
// 判断题或单选题使用 ToggleGroup
|
||
optionToggles[i].group = (question.IsSingleChoice || question.IsJudgment) ? optionsGroup : null;
|
||
}
|
||
else
|
||
{
|
||
optionToggles[i].gameObject.SetActive(false);
|
||
}
|
||
}
|
||
}
|
||
|
||
//选择结束后确认该题结果
|
||
public void OnSubmitButtonClicked()
|
||
{
|
||
|
||
// 提交答案
|
||
SaveUserAnswers();
|
||
//检查结果并显示
|
||
CheckCurrentAnswer();
|
||
|
||
|
||
}
|
||
|
||
//保存用户答案
|
||
void SaveUserAnswers()
|
||
{
|
||
// 保存用户选择的答案
|
||
int index = 0;
|
||
foreach (Toggle toggle in optionToggles)
|
||
{
|
||
if (toggle.isOn)
|
||
{
|
||
userAnswers[currentQuestionIndex]=index;
|
||
}
|
||
index++;
|
||
}
|
||
}
|
||
|
||
//检查是否正确
|
||
void CheckCurrentAnswer()
|
||
{
|
||
// 检查用户答案是否为正确答案的子集
|
||
if (questions[currentQuestionIndex].CorrectAnswers==userAnswers[currentQuestionIndex])
|
||
{
|
||
OnHintViewShow(true);
|
||
}
|
||
else
|
||
{
|
||
OnHintViewShow(false);
|
||
}
|
||
}
|
||
|
||
//显示正确还是错误
|
||
public void OnHintViewShow(bool isTrue)
|
||
{
|
||
// 显示正确答案提示
|
||
Question question = questions[currentQuestionIndex];
|
||
string correctAnswerLetters = ConvertNumbersToLetters(question.CorrectAnswers);
|
||
string nowAnswerLetters = ConvertNumbersToLetters(userAnswers[currentQuestionIndex]);
|
||
//如果是单选题
|
||
if (question.IsSingleChoice)
|
||
{
|
||
hintText.text = $"{correctAnswerLetters}";
|
||
selfText.text = $"{nowAnswerLetters}";
|
||
}
|
||
//如果是判断题
|
||
else if (question.IsJudgment)
|
||
{
|
||
if (correctAnswerLetters == "A")
|
||
{
|
||
hintText.text = "对";
|
||
if (isTrue)
|
||
{
|
||
selfText.text = "对";
|
||
}
|
||
else
|
||
{
|
||
selfText.text = "错";
|
||
}
|
||
}
|
||
else
|
||
{
|
||
hintText.text = "错";
|
||
if (isTrue)
|
||
{
|
||
selfText.text = "错";
|
||
}
|
||
else
|
||
{
|
||
selfText.text = "对";
|
||
}
|
||
}
|
||
}
|
||
|
||
Debug.Log(userAnswers[currentQuestionIndex]+" "+ currentQuestionIndex + " " + questions[currentQuestionIndex].CorrectAnswers);
|
||
|
||
|
||
if (isTrue)
|
||
{
|
||
selfText.color= hintText.color;
|
||
optionToggles[userAnswers[currentQuestionIndex]].transform.Find("Background/Checkmark").GetComponent<Image>().sprite = AllColors[1];
|
||
}
|
||
else
|
||
{
|
||
selfText.color = Color.red;
|
||
optionToggles[userAnswers[currentQuestionIndex]].transform.Find("Background/Checkmark").GetComponent<Image>().sprite = AllColors[2];
|
||
optionToggles[questions[currentQuestionIndex].CorrectAnswers].transform.Find("Background").GetComponent<Image>().sprite = AllColors[1];
|
||
}
|
||
|
||
CtrlPage.SetActive(true);
|
||
}
|
||
|
||
//下一题
|
||
public void OnNextButtonClicked()
|
||
{
|
||
// 切换到下一题
|
||
if (currentQuestionIndex < questions.Count - 1)
|
||
{
|
||
//SaveUserAnswers();
|
||
currentQuestionIndex++;
|
||
DisplayQuestion();
|
||
//hintText.gameObject.SetActive(false);
|
||
}
|
||
//计算结果并进入结果页面
|
||
else
|
||
{
|
||
TiMuPage.SetActive(false);
|
||
CheckAllAnswers();
|
||
}
|
||
|
||
//控制页面消失
|
||
CtrlPage.SetActive(false);
|
||
}
|
||
|
||
//检查所有答案结果并显示结果页面
|
||
void CheckAllAnswers()
|
||
{
|
||
// 检查所有答案并计算正确率
|
||
int correctCount = 0;
|
||
for (int i = 0; i < questions.Count; i++)
|
||
{
|
||
// 检查用户答案是否为正确答案的子集
|
||
if (questions[i].CorrectAnswers==userAnswers[i])
|
||
{
|
||
correctCount++;
|
||
}
|
||
}
|
||
resultText.text = $"正确率: {(float)correctCount / questions.Count * 100:F2}%";
|
||
ResultPage.SetActive(true);
|
||
}
|
||
|
||
//再来一遍
|
||
public void OnMoreDoOnce()
|
||
{
|
||
ToStartQuestion();
|
||
}
|
||
|
||
//上一题
|
||
public void OnPreviousButtonClicked()
|
||
{
|
||
// 切换到上一题
|
||
if (currentQuestionIndex > 0)
|
||
{
|
||
SaveUserAnswers();
|
||
currentQuestionIndex--;
|
||
DisplayQuestion();
|
||
//isSubmitted = false; // 重置提交状态
|
||
hintText.gameObject.SetActive(false);
|
||
}
|
||
}
|
||
|
||
void UpdateTimerUI()
|
||
{
|
||
//// 更新滑动条和倒计时文本
|
||
//if (!isSubmitted) // 仅在未提交时更新
|
||
//{
|
||
// timeSlider.value = remainingTime / totalTime;
|
||
// timerText.text = $"剩余时间: {Mathf.Ceil(remainingTime)}秒";
|
||
//}
|
||
}
|
||
|
||
//void LockSliderPosition()
|
||
//{
|
||
// // 禁用滑块交互
|
||
// timeSlider.interactable = false;
|
||
//}
|
||
|
||
//void StopTimer()
|
||
//{
|
||
// // 停止倒计时并更新文本
|
||
// timerText.text = $"本次用时:{totalTime - remainingTime:F0}s";
|
||
// timeSlider.value = 0; // 停止滑动条
|
||
//}
|
||
|
||
|
||
int GetAnsweredCount()
|
||
{
|
||
// 获取已回答的问题数量
|
||
int count = 0;
|
||
foreach (var answer in userAnswers)
|
||
{
|
||
if (answer > -1)
|
||
{
|
||
count++;
|
||
}
|
||
}
|
||
return count;
|
||
}
|
||
|
||
string ConvertNumbersToLetters(int number)
|
||
{
|
||
// 将数字转换为字母
|
||
StringBuilder letters = new StringBuilder();
|
||
|
||
if (number >= 0 && number <= 3)
|
||
{
|
||
char letter = (char)('A' + number);
|
||
letters.Append(letter);
|
||
}
|
||
|
||
return letters.ToString();
|
||
}
|
||
|
||
private List<Question> XmlTextReaderTest()
|
||
{
|
||
List<Question> questions = new List<Question>();
|
||
|
||
// 构建文件路径
|
||
string filePath = Path.Combine(Application.streamingAssetsPath, FileName+".xml");
|
||
|
||
if (!File.Exists(filePath))
|
||
{
|
||
Debug.LogError("Questions file not found at: " + filePath);
|
||
return questions;
|
||
}
|
||
|
||
string textmsg = File.ReadAllText(filePath);
|
||
|
||
XmlDocument xmlDocument = new XmlDocument();
|
||
try
|
||
{
|
||
xmlDocument.LoadXml(textmsg);
|
||
}
|
||
catch (XmlException e) {
|
||
Debug.LogError($"XML格式错误:{e.Message}");
|
||
return questions;
|
||
}
|
||
|
||
XmlNode rootNode = xmlDocument.SelectSingleNode("root");
|
||
if (rootNode == null)
|
||
{
|
||
Debug.LogError("XML中未找到<root>节点");
|
||
return questions;
|
||
}
|
||
|
||
//遍历所有<question>子节点
|
||
XmlNodeList questionNodes = rootNode.SelectNodes("question");
|
||
if (questionNodes == null || questionNodes.Count == 0)
|
||
{
|
||
Debug.LogWarning("未找到任何<question>节点");
|
||
return questions;
|
||
}
|
||
|
||
// 提取每个question的属性值
|
||
foreach (XmlNode node in questionNodes)
|
||
{
|
||
Question question = new Question();
|
||
|
||
// 读取id属性(转换为int)
|
||
if (!TryGetIntAttribute(node, "id", out int id))
|
||
{
|
||
Debug.LogWarning("跳过无效的question节点(缺少id或格式错误)");
|
||
continue;
|
||
}
|
||
|
||
// 读取quesTitle属性(字符串)
|
||
if (!TryGetStringAttribute(node, "quesTitle", out string title))
|
||
{
|
||
Debug.LogWarning($"id为{id}的节点缺少quesTitle属性");
|
||
continue;
|
||
}
|
||
|
||
string[] allMsg = title.Split('|');
|
||
question.Text = allMsg[0];
|
||
if (allMsg.Length > 1) {
|
||
question.Options=new string[allMsg.Length-1];
|
||
for (int i = 1;i<allMsg.Length;i++)
|
||
{
|
||
question.Options[i-1]=allMsg[i];
|
||
}
|
||
}
|
||
else
|
||
{
|
||
question.Options=null;
|
||
}
|
||
|
||
// 读取answer属性(转换为string)
|
||
if (!TryGetStringAttribute(node, "answer", out string answer))
|
||
{
|
||
Debug.LogWarning($"id为{id}的节点answer格式错误");
|
||
continue;
|
||
}
|
||
question.CorrectAnswers = int.Parse(answer);
|
||
|
||
// 读取type属性(转换为int)
|
||
if (!TryGetIntAttribute(node, "type", out int type))
|
||
{
|
||
Debug.LogWarning($"id为{id}的节点type格式错误");
|
||
continue;
|
||
}
|
||
question.IsSingleChoice = type == 1;
|
||
question.IsJudgment = type == 0;// 判断题标识
|
||
|
||
// 添加到列表
|
||
questions.Add(question);
|
||
}
|
||
|
||
return questions;
|
||
}
|
||
|
||
// 辅助方法:获取节点的字符串属性
|
||
private bool TryGetStringAttribute(XmlNode node, string attrName, out string value)
|
||
{
|
||
XmlAttribute attr = node.Attributes[attrName];
|
||
if (attr != null)
|
||
{
|
||
value = attr.Value;
|
||
return true;
|
||
}
|
||
value = null;
|
||
return false;
|
||
}
|
||
|
||
// 辅助方法:获取节点的int属性(并验证格式)
|
||
private bool TryGetIntAttribute(XmlNode node, string attrName, out int value)
|
||
{
|
||
if (TryGetStringAttribute(node, attrName, out string strValue))
|
||
{
|
||
return int.TryParse(strValue, out value);
|
||
}
|
||
value = 0;
|
||
return false;
|
||
}
|
||
|
||
}
|
||
|
||
public class Question
|
||
{
|
||
public string Text; // 问题文本
|
||
public string[] Options; // 选项数组
|
||
public bool IsSingleChoice; // 是否为单选题
|
||
public int CorrectAnswers; // 正确答案集合
|
||
public bool IsJudgment; // 是否为判断题
|
||
} |