[Unity学习教程] unity制作答题系统

[复制链接]
查看1142 | 回复0 | 2023-8-23 11:56:34 | 显示全部楼层 |阅读模式 来自 中国北京
一:前言

  首先要感谢周周的Unity小屋大佬,原文链接如下
  https://blog.csdn.net/qq_42437783/article/details/121613573?spm=1001.2014.3001.5506
  在此基础上增长了一些功能,使其比力美满
  二:功能说明

  通过xml文件写入问题答案息争析,点击开始答题进入答题界面,在规定时间内答题,倒计时结束,主动关闭答题界面,表现结束界面和得分,倒计时未结束,可以答题,答过的题不可二次修改,点击下方的小点按钮,选择对应的标题,标题选择完毕后,下方按钮也有对应标识,不可再次点击。
  步伐运行如下:
                         三:UI界面先容

  1.开始界面

                         2.答题界面

                         答题界面与周周的Unity小屋摆设的框架一样,留意预制体,xml文件的位置等。添加了得分,倒计时等。
  3.结束界面

                         4.提示界面

                         四:代码誊写

  

  • 选项预制体代码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预制体上,吧对应的内容拖到对应位置即可。
  

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

  https://www.aigei.com/item/unity_da_ti_xi.html
  
  

来源:https://blog.csdn.net/shijinlinaaa/article/details/129025153
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则