当前位置:   article > 正文

unity制作答题系统_unity答题系统

unity答题系统

一:前言

首先要感谢周周的Unity小屋大佬,原文链接如下

Unity答题系统_unity 答题系统-CSDN博客

在此基础上增加了一些功能,使其比较完善

二:功能说明

通过xml文件写入问题答案和解析,点击开始答题进入答题界面,在规定时间内答题,倒计时结束,自动关闭答题界面,显示结束界面和得分,倒计时未结束,可以答题,答过的题不可二次修改,点击下方的小点按钮,选择对应的题目,题目选择完毕后,下方按钮也有对应标识,不可再次点击。

程序运行如下:

三:UI界面介绍

1.开始界面

2.答题界面

答题界面与周周的Unity小屋布置的框架一样,注意预制体,xml文件的位置等。添加了得分,倒计时等。

3.结束界面

4.提示界面

四:代码书写

  1. 选项预制体代码Options

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. public class Options : MonoBehaviour
  6. {
  7. /// <summary>
  8. /// 当前选项组件
  9. /// </summary>
  10. public Toggle thisToggle;
  11. /// <summary>
  12. /// 选项的内容文本
  13. /// </summary>
  14. public Text optionText;
  15. /// <summary>
  16. /// 选项对应的分数
  17. /// </summary>
  18. public int score;
  19. /// <summary>
  20. /// 选项的状态
  21. /// </summary>
  22. public bool IsSelect = false;
  23. public void Init(AnswerData answerData)
  24. {
  25. optionText.text = answerData.option;
  26. score = answerData.Score;
  27. thisToggle.onValueChanged.AddListener((isSelect) => { IsSelect =isSelect; });
  28. }
  29. }

这个脚本挂载上option预制体上,吧对应的内容拖到对应位置即可。

  1. Panel_Question、ButtonItem、DataPath

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Xml;
  5. using System.Xml.Linq;
  6. using UnityEngine;
  7. using UnityEngine.UI;
  8. public class Panel_Question : MonoBehaviour
  9. {
  10. [Header("root界面:开始界面的root 答题界面的root 结束界面的root 提示界面的root")]
  11. [SerializeField] GameObject startRoot;
  12. [SerializeField] GameObject answerRoot;
  13. [SerializeField] GameObject endRoot;
  14. [SerializeField] GameObject hintRoot;
  15. [Header("按钮:开始答题 上一题,提交,下一题")]
  16. [SerializeField] Button startBtn;
  17. [SerializeField] Button previousBtn;
  18. [SerializeField] Button submitBtn;
  19. [SerializeField] Button nextBtn;
  20. [SerializeField] Button backToMainBtn;
  21. [Header("按钮:确认提交 取消提交")]
  22. [SerializeField] Button affirmSubBtn;
  23. [SerializeField] Button cancelSubBtn;
  24. [Header("文本:题目序号 解析内容 得分 倒计时 最后得分")]
  25. [SerializeField] Text questionID;
  26. [SerializeField] Text analysisData;
  27. [SerializeField] Text scoreTxt;
  28. [SerializeField] Text countDownTxt;
  29. [SerializeField] Text endTxt;
  30. [Header("内容scroll的content 单选scroll的content 选项scroll的content")]
  31. [SerializeField] Transform contentScrollContent;
  32. [SerializeField] Transform questionBtnRoot;
  33. [SerializeField] Transform selectContent;
  34. [SerializeField] Transform scrollView;
  35. [SerializeField] ToggleGroup questionGroup;
  36. // 答题界面数据内容
  37. private QuestionPanelData mQuestionPanelData;
  38. // 每一道题的题目内容
  39. private QuestionData mQuestionData;
  40. // 题目内容物体
  41. private GameObject mQuestion;
  42. // 选项的链表
  43. private List<Options> options = new List<Options>();
  44. //倒计时的总时间
  45. public float secound ;
  46. [SerializeField] GameObject prefab;
  47. static Panel_Question instance;
  48. public static Panel_Question GetInstance()
  49. {
  50. return instance;
  51. }
  52. private void Awake()
  53. {
  54. Init();
  55. instance = this;
  56. }
  57. /// <summary>
  58. /// 按钮监听
  59. /// </summary>
  60. private void Init()
  61. {
  62. startBtn.onClick.AddListener(StartAnswer);
  63. previousBtn.onClick.AddListener(previousClick);
  64. submitBtn.onClick.AddListener(submitClick);
  65. nextBtn.onClick.AddListener(nextClick);
  66. backToMainBtn.onClick.AddListener(BackToMain);
  67. affirmSubBtn.onClick.AddListener(AffirmSub);
  68. cancelSubBtn.onClick.AddListener(CancelSub);
  69. }
  70. private void Start()
  71. {
  72. //读取xml文件
  73. StartCoroutine(LoadingQuesiton(DataPath.QuestionData));
  74. scoreTxt.text = "得分: 0";
  75. countDownTxt.text = "答题时间还剩:" + secound;
  76. //界面的显示与隐藏
  77. startRoot.SetActive(true);
  78. answerRoot.SetActive(false);
  79. endRoot.SetActive(false);
  80. hintRoot.SetActive(false);
  81. }
  82. private void Update()
  83. {
  84. if (endRoot.activeSelf)
  85. {
  86. //如果倒计时结束前答题完成,关闭倒计时的协程,时间重新赋值
  87. StopCoroutine(TimeChange());
  88. secound = 0;
  89. }
  90. }
  91. #region 读取xml文件
  92. IEnumerator LoadingQuesiton(string path)
  93. {
  94. yield return null;
  95. using (WWW www = new WWW(path))
  96. {
  97. yield return www;
  98. XmlDocument doc = new XmlDocument();
  99. doc.LoadXml(www.text);
  100. new QuestionPanel(doc.FirstChild);
  101. }
  102. }
  103. #endregion
  104. #region 初始化第一道题
  105. public void InitQuestionPanel(QuestionPanelData questionPanelData)
  106. {
  107. //this.gameObject.SetActive(true);
  108. mQuestionPanelData = questionPanelData;
  109. CreateQuestion(questionPanelData.questionData[index]);
  110. }
  111. #endregion
  112. #region 创建题目
  113. bool isFirst = false;
  114. public void CreateQuestion(QuestionData questionData)
  115. {
  116. //数据赋值
  117. analysisData.text = "";
  118. mQuestionData = questionData;
  119. questionID.text = string.Format("第{0}题(共" + mQuestionPanelData.questionData.Count + "题)", index + 1);
  120. if (mQuestion != null)
  121. {
  122. Destroy(mQuestion);
  123. }
  124. //实例化题目预制体
  125. mQuestion = Instantiate(Resources.Load<GameObject>(DataPath.QuestionText));
  126. mQuestion.transform.SetParent(contentScrollContent);
  127. mQuestion.transform.localScale = Vector3.one;
  128. mQuestion.GetComponent<Text>().text = questionData.problem;
  129. if (options.Count > 0)
  130. {
  131. for (int i = 0; i < options.Count; i++)
  132. {
  133. Destroy(options[i].gameObject);
  134. }
  135. }
  136. options = new List<Options>();
  137. //实例化按钮选项组序列
  138. if (!isFirst)
  139. for (int i = 0; i < mQuestionPanelData.questionData.Count; i++)
  140. {
  141. Instantiate(prefab, scrollView);
  142. isFirst = true;
  143. }
  144. //当前题目的按钮序列的标识变大
  145. for (int i = 0; i < mQuestionPanelData.questionData.Count; i++)
  146. {
  147. if (i!= index)
  148. scrollView.GetChild(i).gameObject.GetComponent<RectTransform>().localScale = new Vector2(1, 1);
  149. else
  150. scrollView.GetChild(i).gameObject.GetComponent<RectTransform>().localScale = new Vector2(1.5f, 1.5f);
  151. }
  152. //实例化选项预制体
  153. for (int i = 0; i < questionData.answerData.Count; i++)
  154. {
  155. Options option = Instantiate(Resources.Load<Options>("Options"));
  156. option.Init(questionData.answerData[i]);
  157. option.transform.SetParent(selectContent);
  158. //如果是单选则设置为一个toggle组
  159. if (questionData.isSingleChoice)
  160. {
  161. option.thisToggle.group = questionGroup;
  162. }
  163. options.Add(option);
  164. }
  165. }
  166. #endregion
  167. #region 开始答题 上一题 下一题 提交按钮事件
  168. // 开始答题
  169. public void StartAnswer()
  170. {
  171. for (int i = 0; i < QuestionPanel.questionPanelData.Count; i++)
  172. {
  173. InitQuestionPanel(QuestionPanel.questionPanelData[i]);
  174. }
  175. startRoot.SetActive(false);
  176. answerRoot.SetActive(true);
  177. secound = 30;
  178. //开启倒计时
  179. StartCoroutine(TimeChange());
  180. }
  181. // 上一题点击事件
  182. private void previousClick()
  183. {
  184. if (index > 0)
  185. {
  186. index--;
  187. CreateQuestion(mQuestionPanelData.questionData[index]);
  188. }
  189. if (scrollView.GetChild(index).GetComponent<Button>().interactable == false)
  190. foreach (var item in options)
  191. item.thisToggle.interactable = false;
  192. }
  193. // 下一题点击事件
  194. private void nextClick()
  195. {
  196. if (index < mQuestionPanelData.questionData.Count - 1)
  197. {
  198. index++;
  199. CreateQuestion(mQuestionPanelData.questionData[index]);
  200. isFirstClick = false;
  201. }
  202. if (scrollView.GetChild(index).GetComponent<Button>().interactable == false)
  203. foreach (var item in options)
  204. item.thisToggle.interactable = false;
  205. }
  206. int score = 0;
  207. bool isFirstClick = false; //是否是第一次答对,只有点击第一次提交的时候加分,再点提交不加分
  208. // 题目提交事件
  209. private void submitClick()
  210. {
  211. //遍历当前题目的选项,有选择的就可以提交核验答案,并显示解析内容
  212. foreach (var item in options)
  213. {
  214. if (item.thisToggle.isOn)
  215. {
  216. //回答正确加一分
  217. if (item.score > 0)
  218. {
  219. analysisData.text = "回答正确";
  220. if (!isFirstClick)
  221. {
  222. score += 1;
  223. isFirstClick = true;
  224. }
  225. scoreTxt.text = "得分:" + score.ToString();
  226. }
  227. else
  228. analysisData.text = "回答错误,解析:" + mQuestionData.Analysis;
  229. }
  230. //选择一个选项之后不能在选择其他选项
  231. item.thisToggle.interactable = false;
  232. }
  233. //如果当前题目是最后一题,提交之后,出现提示界面
  234. if (index + 1 == mQuestionPanelData.questionData.Count)
  235. {
  236. startRoot.SetActive(false);
  237. answerRoot.SetActive(true);
  238. endRoot.SetActive(false);
  239. hintRoot.SetActive(true);
  240. }
  241. //提交后,该题目不可再选择修改
  242. scrollView.GetChild(index).GetComponent<Image>().color = Color.green;
  243. scrollView.GetChild(index).GetComponent<Button>().interactable = false;
  244. }
  245. //返回主界面
  246. void BackToMain()
  247. {
  248. startRoot.SetActive(true);
  249. answerRoot.SetActive(false);
  250. endRoot.SetActive(false);
  251. score = 0;
  252. scoreTxt.text = "得分:" + score.ToString();
  253. for (int i = 0; i < scrollView.childCount; i++)
  254. {
  255. scrollView.GetChild(i).GetComponent<Image>().color = Color.white;
  256. scrollView.GetChild(i).GetComponent<Button>().interactable = true;
  257. Debug.Log(456789);
  258. }
  259. index = 0;
  260. }
  261. int index = 0;
  262. //缩列按钮事件
  263. public void ThisBtn(ButtonItem item)
  264. {
  265. for (int i = 0; i < scrollView.childCount; i++)
  266. {
  267. CreateQuestion(mQuestionPanelData.questionData[item.transform.GetSiblingIndex()]);
  268. index = item.transform.GetSiblingIndex();
  269. }
  270. }
  271. //确认提交 显示结束界面
  272. void AffirmSub()
  273. {
  274. startRoot.SetActive(false);
  275. answerRoot.SetActive(false);
  276. endRoot.SetActive(true);
  277. hintRoot.SetActive(false);
  278. endTxt.text = "答题结束\n 您的得分是:" + score.ToString();
  279. }
  280. //取消提交 返回原来的界面
  281. void CancelSub()
  282. {
  283. startRoot.SetActive(false);
  284. answerRoot.SetActive(true);
  285. endRoot.SetActive(false);
  286. hintRoot.SetActive(false);
  287. }
  288. #endregion
  289. #region 倒计时的协程
  290. IEnumerator TimeChange()
  291. {
  292. while (secound > 0)
  293. {
  294. yield return new WaitForSeconds(1);
  295. countDownTxt.text = "答题时间还剩:"+secound.ToString()+"秒";
  296. secound--;
  297. }
  298. if (secound<=0)
  299. {
  300. //界面的显示与隐藏
  301. startRoot.SetActive(false);
  302. answerRoot.SetActive(false);
  303. endRoot.SetActive(true);
  304. endTxt.text = "答题结束\n 您的得分是:" + score.ToString();
  305. }
  306. }
  307. #endregion
  308. }
  309. /// <summary>
  310. /// 答题panel数据类
  311. /// </summary>
  312. public class QuestionPanel
  313. {
  314. public static List<QuestionPanelData> questionPanelData;
  315. public QuestionPanel(XmlNode node)
  316. {
  317. questionPanelData = new List<QuestionPanelData>();
  318. for (int i = 0; i < node.ChildNodes.Count; i++)
  319. {
  320. questionPanelData.Add(new QuestionPanelData(node.ChildNodes[i]));
  321. }
  322. }
  323. }
  324. /// <summary>
  325. /// 答题界面数据类
  326. /// </summary>
  327. public class QuestionPanelData
  328. {
  329. public List<QuestionData> questionData;
  330. public QuestionPanelData(XmlNode node)
  331. {
  332. questionData = new List<QuestionData>();
  333. for (int i = 0; i < node.ChildNodes.Count; i++)
  334. {
  335. questionData.Add(new QuestionData(node.ChildNodes[i]));
  336. }
  337. }
  338. }
  339. /// <summary>
  340. /// 题目数据类
  341. /// </summary>
  342. public class QuestionData
  343. {
  344. // 是否为单选,true为单选,false为多选
  345. public bool isSingleChoice;
  346. // 解析内容
  347. public string Analysis;
  348. // 题目内容
  349. public string problem;
  350. public List<AnswerData> answerData;
  351. public QuestionData(XmlNode node)
  352. {
  353. isSingleChoice = bool.Parse(node.Attributes["SelectType"].InnerText);
  354. Analysis = node["Analysis"].InnerText;
  355. problem = node["Problem"].InnerText;
  356. answerData = new List<AnswerData>();
  357. XmlNodeList nodelist = node["Answer"].ChildNodes;
  358. for (int i = 0; i < nodelist.Count; i++)
  359. {
  360. answerData.Add(new AnswerData(nodelist[i]));
  361. }
  362. }
  363. }
  364. /// <summary>
  365. /// 答案数据类
  366. /// </summary>
  367. public class AnswerData
  368. {
  369. // 选项的内容
  370. public string option;
  371. // 选项对应的分数
  372. public int Score;
  373. public AnswerData(XmlNode node)
  374. {
  375. option = node.Attributes["option"].InnerText;
  376. Score = int.Parse(node.InnerText);
  377. }
  378. }

这个代码挂在canvas上,其中代码中的命名与场景中的命名一致,将对应物体添加到对应位置上,其中ButtonItem是挂载上image上的,image是一个预制体,内容如下。

ButtonItem代码如下:

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. public class ButtonItem : MonoBehaviour
  6. {
  7. Button Btn;
  8. // Start is called before the first frame update
  9. void Start()
  10. {
  11. Btn = GetComponent<Button>();
  12. Btn.onClick.AddListener(ThisBtn);
  13. }
  14. public void ThisBtn()
  15. {
  16. Panel_Question.GetInstance().ThisBtn(this);
  17. }
  18. }

其中,用www读取xml文件时,会报错,是xml格式有问题,我在读取时报错是XmlException: Data at the root level is invalid. Line 1, position 1.通过代码重新生成一个xml文件,再在其中添加想写的内容即可。我生成XmlTest的代码如下。有需要的可以参考

  1. using UnityEngine;
  2. using System.IO;
  3. using System.Xml;
  4. public class XmlTest : MonoBehaviour
  5. {
  6.     // Use this for initialization
  7.     void Start()
  8. {
  9. CreateXml();
  10. }
  11.     ///
  12.     /// Creates the xml.
  13.     ///
  14.     private void CreateXml()
  15. {
  16.         //设置保存路径
  17.         string path = Application.dataPath + "/XML/" + "ConfigFile.xml";
  18. //判断文件是否存在
  19. if (File.Exists(path) == false)
  20. {
  21.             //创建一个xml文件
  22.             XmlDocument xml = new XmlDocument();
  23.             //创建最上层节点
  24.             XmlElement root = xml.CreateElement("Root");
  25.             //创建子节点
  26.             XmlElement element = xml.CreateElement("Question");
  27. element.SetAttribute("SelectType", "True");
  28.             //创建子节点的第一个子节点,设置属性并添加内容
  29.             XmlElement Child1 = xml.CreateElement("Problem");
  30. Child1.InnerText = "这里输入您的题目";
  31.             //创建子节点的第二个子节点,设置属性并添加内容
  32.             XmlElement Child2 = xml.CreateElement("Answer");
  33. //创建三级子节点
  34.             XmlElement item1 = xml.CreateElement("Item");
  35. item1.SetAttribute("option", "A.答案一");
  36. item1.InnerText = "0";
  37. XmlElement item2 = xml.CreateElement("Item");
  38. item2.SetAttribute("option", "B.答案一");
  39. item2.InnerText = "0";
  40. XmlElement item3 = xml.CreateElement("Item");
  41. item3.SetAttribute("option", "C.答案一");
  42. item3.InnerText = "1";
  43. XmlElement item4 = xml.CreateElement("Item");
  44. item4.SetAttribute("option", "D.答案一");
  45. item4.InnerText = "0";
  46. //二级子节点
  47. XmlElement Child3 = xml.CreateElement("Analysis");
  48. Child3.InnerText = "这里输入解析";
  49. //创建子节点
  50.             XmlElement element2 = xml.CreateElement("Question");
  51. element2.SetAttribute("SelectType", "True");
  52.             //创建子节点的第一个子节点,设置属性并添加内容
  53.             XmlElement Child2_1 = xml.CreateElement("Problem");
  54. Child2_1.InnerText = "这里输入您的题目gvfrebr";
  55.             //创建子节点的第二个子节点,设置属性并添加内容
  56.             XmlElement Child2_2= xml.CreateElement("Answer");
  57. //创建三级子节点
  58.             XmlElement item2_1 = xml.CreateElement("Item");
  59. item2_1.SetAttribute("option", "A.答案一");
  60. item2_1.InnerText = "0";
  61. XmlElement item2_2 = xml.CreateElement("Item");
  62. item2_2.SetAttribute("option", "B.答案44");
  63. item2_2.InnerText = "0";
  64. XmlElement item2_3 = xml.CreateElement("Item");
  65. item2_3.SetAttribute("option", "C.答案3");
  66. item2_3.InnerText = "1";
  67. XmlElement item2_4 = xml.CreateElement("Item");
  68. item2_4.SetAttribute("option", "D.答案一");
  69. item2_4.InnerText = "0";
  70. //二级子节点
  71. XmlElement Child2_3 = xml.CreateElement("Analysis");
  72. Child2_3.InnerText = "这里输入解析";
  73. //把节点一层一层的添加至xml中,注意他们之间的先后顺序,这是生成XML文件的顺序
  74. element2.AppendChild(Child2_1);
  75. Child2_2.AppendChild(item2_1);
  76. Child2_2.AppendChild(item2_2);
  77. Child2_2.AppendChild(item2_3);
  78. Child2_2.AppendChild(item2_4);
  79. element2.AppendChild(Child2_2);
  80. element2.AppendChild(Child2_3);
  81. root.AppendChild(element2);
  82. element.AppendChild(Child1);
  83. Child2.AppendChild(item1);
  84. Child2.AppendChild(item2);
  85. Child2.AppendChild(item3);
  86. Child2.AppendChild(item4);
  87. element.AppendChild(Child2);
  88. element.AppendChild(Child3);
  89. root.AppendChild(element);
  90. xml.AppendChild(root);
  91.             //保存XML文档
  92.             xml.Save(path);
  93. Debug.Log("Xml 创建成功!");
  94. }
  95. }
  96. }

也可以只生成两个root根节点,然后内容自己再xml中写,就不用通过代码添加了。

通过xml文件读取的数据的路径通过一个代码封装静态变量,全局使用,代码如下:DataPath

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. /// <summary>
  5. /// 全局静态类,用来定义静态字段,方便调用
  6. /// </summary>
  7. public class DataPath
  8. {
  9. public static string QuestionData = "file://" + Application.dataPath + "/XML/" + "ConfigFile.xml";
  10. public static string QuestionText = "QuestionText";
  11. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/603985
推荐阅读
相关标签
  

闽ICP备14008679号