赞
踩
首先要感谢周周的Unity小屋大佬,原文链接如下
在此基础上增加了一些功能,使其比较完善
通过xml文件写入问题答案和解析,点击开始答题进入答题界面,在规定时间内答题,倒计时结束,自动关闭答题界面,显示结束界面和得分,倒计时未结束,可以答题,答过的题不可二次修改,点击下方的小点按钮,选择对应的题目,题目选择完毕后,下方按钮也有对应标识,不可再次点击。
程序运行如下:
答题界面与周周的Unity小屋布置的框架一样,注意预制体,xml文件的位置等。添加了得分,倒计时等。
-
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.UI;
-
- public class Options : MonoBehaviour
- {
- /// <summary>
- /// 当前选项组件
- /// </summary>
- public Toggle thisToggle;
- /// <summary>
- /// 选项的内容文本
- /// </summary>
- public Text optionText;
- /// <summary>
- /// 选项对应的分数
- /// </summary>
- public int score;
- /// <summary>
- /// 选项的状态
- /// </summary>
- public bool IsSelect = false;
-
- public void Init(AnswerData answerData)
- {
- optionText.text = answerData.option;
- score = answerData.Score;
- thisToggle.onValueChanged.AddListener((isSelect) => { IsSelect =isSelect; });
- }
- }
这个脚本挂载上option预制体上,吧对应的内容拖到对应位置即可。
-
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Xml;
- using System.Xml.Linq;
- using UnityEngine;
- using UnityEngine.UI;
-
- public class Panel_Question : MonoBehaviour
- {
-
- [Header("root界面:开始界面的root 答题界面的root 结束界面的root 提示界面的root")]
- [SerializeField] GameObject startRoot;
- [SerializeField] GameObject answerRoot;
- [SerializeField] GameObject endRoot;
- [SerializeField] GameObject hintRoot;
-
- [Header("按钮:开始答题 上一题,提交,下一题")]
- [SerializeField] Button startBtn;
- [SerializeField] Button previousBtn;
- [SerializeField] Button submitBtn;
- [SerializeField] Button nextBtn;
- [SerializeField] Button backToMainBtn;
-
- [Header("按钮:确认提交 取消提交")]
- [SerializeField] Button affirmSubBtn;
- [SerializeField] Button cancelSubBtn;
-
- [Header("文本:题目序号 解析内容 得分 倒计时 最后得分")]
- [SerializeField] Text questionID;
- [SerializeField] Text analysisData;
- [SerializeField] Text scoreTxt;
- [SerializeField] Text countDownTxt;
- [SerializeField] Text endTxt;
-
- [Header("内容scroll的content 单选scroll的content 选项scroll的content")]
- [SerializeField] Transform contentScrollContent;
- [SerializeField] Transform questionBtnRoot;
- [SerializeField] Transform selectContent;
- [SerializeField] Transform scrollView;
-
- [SerializeField] ToggleGroup questionGroup;
-
- // 答题界面数据内容
- private QuestionPanelData mQuestionPanelData;
- // 每一道题的题目内容
- private QuestionData mQuestionData;
- // 题目内容物体
- private GameObject mQuestion;
- // 选项的链表
- private List<Options> options = new List<Options>();
-
- //倒计时的总时间
- public float secound ;
-
- [SerializeField] GameObject prefab;
-
- static Panel_Question instance;
-
- public static Panel_Question GetInstance()
- {
- return instance;
- }
-
- private void Awake()
- {
- Init();
- instance = this;
- }
-
- /// <summary>
- /// 按钮监听
- /// </summary>
- private void Init()
- {
- startBtn.onClick.AddListener(StartAnswer);
- previousBtn.onClick.AddListener(previousClick);
- submitBtn.onClick.AddListener(submitClick);
- nextBtn.onClick.AddListener(nextClick);
- backToMainBtn.onClick.AddListener(BackToMain);
- affirmSubBtn.onClick.AddListener(AffirmSub);
- cancelSubBtn.onClick.AddListener(CancelSub);
- }
-
- private void Start()
- {
- //读取xml文件
- StartCoroutine(LoadingQuesiton(DataPath.QuestionData));
-
- scoreTxt.text = "得分: 0";
- countDownTxt.text = "答题时间还剩:" + secound;
- //界面的显示与隐藏
- startRoot.SetActive(true);
- answerRoot.SetActive(false);
- endRoot.SetActive(false);
- hintRoot.SetActive(false);
-
- }
-
- private void Update()
- {
- if (endRoot.activeSelf)
- {
- //如果倒计时结束前答题完成,关闭倒计时的协程,时间重新赋值
- StopCoroutine(TimeChange());
- secound = 0;
- }
- }
-
- #region 读取xml文件
- IEnumerator LoadingQuesiton(string path)
- {
- yield return null;
- using (WWW www = new WWW(path))
- {
- yield return www;
- XmlDocument doc = new XmlDocument();
- doc.LoadXml(www.text);
- new QuestionPanel(doc.FirstChild);
- }
- }
- #endregion
-
- #region 初始化第一道题
- public void InitQuestionPanel(QuestionPanelData questionPanelData)
- {
- //this.gameObject.SetActive(true);
- mQuestionPanelData = questionPanelData;
- CreateQuestion(questionPanelData.questionData[index]);
- }
- #endregion
-
- #region 创建题目
- bool isFirst = false;
- public void CreateQuestion(QuestionData questionData)
- {
- //数据赋值
- analysisData.text = "";
- mQuestionData = questionData;
- questionID.text = string.Format("第{0}题(共" + mQuestionPanelData.questionData.Count + "题)", index + 1);
- if (mQuestion != null)
- {
- Destroy(mQuestion);
- }
-
- //实例化题目预制体
- mQuestion = Instantiate(Resources.Load<GameObject>(DataPath.QuestionText));
- mQuestion.transform.SetParent(contentScrollContent);
- mQuestion.transform.localScale = Vector3.one;
- mQuestion.GetComponent<Text>().text = questionData.problem;
-
- if (options.Count > 0)
- {
- for (int i = 0; i < options.Count; i++)
- {
- Destroy(options[i].gameObject);
- }
- }
- options = new List<Options>();
-
- //实例化按钮选项组序列
- if (!isFirst)
- for (int i = 0; i < mQuestionPanelData.questionData.Count; i++)
- {
- Instantiate(prefab, scrollView);
- isFirst = true;
- }
-
- //当前题目的按钮序列的标识变大
- for (int i = 0; i < mQuestionPanelData.questionData.Count; i++)
- {
- if (i!= index)
- scrollView.GetChild(i).gameObject.GetComponent<RectTransform>().localScale = new Vector2(1, 1);
- else
- scrollView.GetChild(i).gameObject.GetComponent<RectTransform>().localScale = new Vector2(1.5f, 1.5f);
-
- }
-
- //实例化选项预制体
- for (int i = 0; i < questionData.answerData.Count; i++)
- {
- Options option = Instantiate(Resources.Load<Options>("Options"));
- option.Init(questionData.answerData[i]);
- option.transform.SetParent(selectContent);
- //如果是单选则设置为一个toggle组
- if (questionData.isSingleChoice)
- {
- option.thisToggle.group = questionGroup;
- }
- options.Add(option);
- }
- }
- #endregion
-
- #region 开始答题 上一题 下一题 提交按钮事件
-
- // 开始答题
- public void StartAnswer()
- {
- for (int i = 0; i < QuestionPanel.questionPanelData.Count; i++)
- {
- InitQuestionPanel(QuestionPanel.questionPanelData[i]);
- }
- startRoot.SetActive(false);
- answerRoot.SetActive(true);
- secound = 30;
- //开启倒计时
- StartCoroutine(TimeChange());
- }
-
- // 上一题点击事件
- private void previousClick()
- {
- if (index > 0)
- {
- index--;
- CreateQuestion(mQuestionPanelData.questionData[index]);
- }
-
- if (scrollView.GetChild(index).GetComponent<Button>().interactable == false)
- foreach (var item in options)
- item.thisToggle.interactable = false;
-
- }
-
- // 下一题点击事件
- private void nextClick()
- {
- if (index < mQuestionPanelData.questionData.Count - 1)
- {
- index++;
- CreateQuestion(mQuestionPanelData.questionData[index]);
- isFirstClick = false;
- }
-
- if (scrollView.GetChild(index).GetComponent<Button>().interactable == false)
- foreach (var item in options)
- item.thisToggle.interactable = false;
- }
-
- int score = 0;
- bool isFirstClick = false; //是否是第一次答对,只有点击第一次提交的时候加分,再点提交不加分
- // 题目提交事件
- private void submitClick()
- {
- //遍历当前题目的选项,有选择的就可以提交核验答案,并显示解析内容
- foreach (var item in options)
- {
- if (item.thisToggle.isOn)
- {
- //回答正确加一分
- if (item.score > 0)
- {
- analysisData.text = "回答正确";
- if (!isFirstClick)
- {
- score += 1;
- isFirstClick = true;
- }
- scoreTxt.text = "得分:" + score.ToString();
- }
- else
- analysisData.text = "回答错误,解析:" + mQuestionData.Analysis;
- }
- //选择一个选项之后不能在选择其他选项
- item.thisToggle.interactable = false;
- }
-
- //如果当前题目是最后一题,提交之后,出现提示界面
- if (index + 1 == mQuestionPanelData.questionData.Count)
- {
- startRoot.SetActive(false);
- answerRoot.SetActive(true);
- endRoot.SetActive(false);
- hintRoot.SetActive(true);
- }
-
- //提交后,该题目不可再选择修改
- scrollView.GetChild(index).GetComponent<Image>().color = Color.green;
- scrollView.GetChild(index).GetComponent<Button>().interactable = false;
-
- }
-
- //返回主界面
- void BackToMain()
- {
- startRoot.SetActive(true);
- answerRoot.SetActive(false);
- endRoot.SetActive(false);
- score = 0;
- scoreTxt.text = "得分:" + score.ToString();
-
- for (int i = 0; i < scrollView.childCount; i++)
- {
- scrollView.GetChild(i).GetComponent<Image>().color = Color.white;
- scrollView.GetChild(i).GetComponent<Button>().interactable = true;
- Debug.Log(456789);
- }
-
- index = 0;
- }
-
- int index = 0;
- //缩列按钮事件
- public void ThisBtn(ButtonItem item)
- {
- for (int i = 0; i < scrollView.childCount; i++)
- {
- CreateQuestion(mQuestionPanelData.questionData[item.transform.GetSiblingIndex()]);
- index = item.transform.GetSiblingIndex();
- }
- }
-
- //确认提交 显示结束界面
- void AffirmSub()
- {
- startRoot.SetActive(false);
- answerRoot.SetActive(false);
- endRoot.SetActive(true);
- hintRoot.SetActive(false);
- endTxt.text = "答题结束\n 您的得分是:" + score.ToString();
- }
-
- //取消提交 返回原来的界面
- void CancelSub()
- {
- startRoot.SetActive(false);
- answerRoot.SetActive(true);
- endRoot.SetActive(false);
- hintRoot.SetActive(false);
- }
- #endregion
-
- #region 倒计时的协程
- IEnumerator TimeChange()
- {
- while (secound > 0)
- {
- yield return new WaitForSeconds(1);
- countDownTxt.text = "答题时间还剩:"+secound.ToString()+"秒";
- secound--;
- }
- if (secound<=0)
- {
- //界面的显示与隐藏
- startRoot.SetActive(false);
- answerRoot.SetActive(false);
- endRoot.SetActive(true);
- endTxt.text = "答题结束\n 您的得分是:" + score.ToString();
- }
- }
- #endregion
-
- }
-
- /// <summary>
- /// 答题panel数据类
- /// </summary>
- public class QuestionPanel
- {
- public static List<QuestionPanelData> questionPanelData;
- public QuestionPanel(XmlNode node)
- {
- questionPanelData = new List<QuestionPanelData>();
- for (int i = 0; i < node.ChildNodes.Count; i++)
- {
- questionPanelData.Add(new QuestionPanelData(node.ChildNodes[i]));
- }
- }
- }
-
- /// <summary>
- /// 答题界面数据类
- /// </summary>
- public class QuestionPanelData
- {
- public List<QuestionData> questionData;
- public QuestionPanelData(XmlNode node)
- {
- questionData = new List<QuestionData>();
- for (int i = 0; i < node.ChildNodes.Count; i++)
- {
- questionData.Add(new QuestionData(node.ChildNodes[i]));
- }
- }
- }
-
- /// <summary>
- /// 题目数据类
- /// </summary>
- public class QuestionData
- {
- // 是否为单选,true为单选,false为多选
- public bool isSingleChoice;
- // 解析内容
- public string Analysis;
- // 题目内容
- public string problem;
- public List<AnswerData> answerData;
- public QuestionData(XmlNode node)
- {
- isSingleChoice = bool.Parse(node.Attributes["SelectType"].InnerText);
- Analysis = node["Analysis"].InnerText;
- problem = node["Problem"].InnerText;
- answerData = new List<AnswerData>();
- XmlNodeList nodelist = node["Answer"].ChildNodes;
- for (int i = 0; i < nodelist.Count; i++)
- {
- answerData.Add(new AnswerData(nodelist[i]));
- }
- }
- }
-
- /// <summary>
- /// 答案数据类
- /// </summary>
- public class AnswerData
- {
- // 选项的内容
- public string option;
- // 选项对应的分数
- public int Score;
- public AnswerData(XmlNode node)
- {
- option = node.Attributes["option"].InnerText;
- Score = int.Parse(node.InnerText);
- }
- }
这个代码挂在canvas上,其中代码中的命名与场景中的命名一致,将对应物体添加到对应位置上,其中ButtonItem是挂载上image上的,image是一个预制体,内容如下。
ButtonItem代码如下:
-
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.UI;
-
- public class ButtonItem : MonoBehaviour
- {
- Button Btn;
-
-
- // Start is called before the first frame update
- void Start()
- {
- Btn = GetComponent<Button>();
- Btn.onClick.AddListener(ThisBtn);
- }
-
- public void ThisBtn()
- {
- Panel_Question.GetInstance().ThisBtn(this);
- }
-
- }
其中,用www读取xml文件时,会报错,是xml格式有问题,我在读取时报错是XmlException: Data at the root level is invalid. Line 1, position 1.通过代码重新生成一个xml文件,再在其中添加想写的内容即可。我生成XmlTest的代码如下。有需要的可以参考
-
- using UnityEngine;
- using System.IO;
- using System.Xml;
- public class XmlTest : MonoBehaviour
- {
- // Use this for initialization
- void Start()
- {
- CreateXml();
- }
- ///
- /// Creates the xml.
- ///
- private void CreateXml()
- {
- //设置保存路径
- string path = Application.dataPath + "/XML/" + "ConfigFile.xml";
- //判断文件是否存在
- if (File.Exists(path) == false)
- {
- //创建一个xml文件
- XmlDocument xml = new XmlDocument();
- //创建最上层节点
- XmlElement root = xml.CreateElement("Root");
- //创建子节点
- XmlElement element = xml.CreateElement("Question");
- element.SetAttribute("SelectType", "True");
-
- //创建子节点的第一个子节点,设置属性并添加内容
- XmlElement Child1 = xml.CreateElement("Problem");
- Child1.InnerText = "这里输入您的题目";
-
- //创建子节点的第二个子节点,设置属性并添加内容
- XmlElement Child2 = xml.CreateElement("Answer");
-
- //创建三级子节点
- XmlElement item1 = xml.CreateElement("Item");
- item1.SetAttribute("option", "A.答案一");
- item1.InnerText = "0";
-
- XmlElement item2 = xml.CreateElement("Item");
- item2.SetAttribute("option", "B.答案一");
- item2.InnerText = "0";
-
- XmlElement item3 = xml.CreateElement("Item");
- item3.SetAttribute("option", "C.答案一");
- item3.InnerText = "1";
-
- XmlElement item4 = xml.CreateElement("Item");
- item4.SetAttribute("option", "D.答案一");
- item4.InnerText = "0";
-
- //二级子节点
- XmlElement Child3 = xml.CreateElement("Analysis");
- Child3.InnerText = "这里输入解析";
-
- //创建子节点
- XmlElement element2 = xml.CreateElement("Question");
- element2.SetAttribute("SelectType", "True");
-
- //创建子节点的第一个子节点,设置属性并添加内容
- XmlElement Child2_1 = xml.CreateElement("Problem");
- Child2_1.InnerText = "这里输入您的题目gvfrebr";
-
- //创建子节点的第二个子节点,设置属性并添加内容
- XmlElement Child2_2= xml.CreateElement("Answer");
-
- //创建三级子节点
- XmlElement item2_1 = xml.CreateElement("Item");
- item2_1.SetAttribute("option", "A.答案一");
- item2_1.InnerText = "0";
-
- XmlElement item2_2 = xml.CreateElement("Item");
- item2_2.SetAttribute("option", "B.答案44");
- item2_2.InnerText = "0";
-
- XmlElement item2_3 = xml.CreateElement("Item");
- item2_3.SetAttribute("option", "C.答案3");
- item2_3.InnerText = "1";
-
- XmlElement item2_4 = xml.CreateElement("Item");
- item2_4.SetAttribute("option", "D.答案一");
- item2_4.InnerText = "0";
-
- //二级子节点
- XmlElement Child2_3 = xml.CreateElement("Analysis");
- Child2_3.InnerText = "这里输入解析";
-
- //把节点一层一层的添加至xml中,注意他们之间的先后顺序,这是生成XML文件的顺序
- element2.AppendChild(Child2_1);
- Child2_2.AppendChild(item2_1);
- Child2_2.AppendChild(item2_2);
- Child2_2.AppendChild(item2_3);
- Child2_2.AppendChild(item2_4);
- element2.AppendChild(Child2_2);
- element2.AppendChild(Child2_3);
- root.AppendChild(element2);
-
- element.AppendChild(Child1);
- Child2.AppendChild(item1);
- Child2.AppendChild(item2);
- Child2.AppendChild(item3);
- Child2.AppendChild(item4);
- element.AppendChild(Child2);
- element.AppendChild(Child3);
- root.AppendChild(element);
- xml.AppendChild(root);
- //保存XML文档
- xml.Save(path);
- Debug.Log("Xml 创建成功!");
- }
- }
- }
也可以只生成两个root根节点,然后内容自己再xml中写,就不用通过代码添加了。
通过xml文件读取的数据的路径通过一个代码封装静态变量,全局使用,代码如下:DataPath
-
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- /// <summary>
- /// 全局静态类,用来定义静态字段,方便调用
- /// </summary>
- public class DataPath
- {
- public static string QuestionData = "file://" + Application.dataPath + "/XML/" + "ConfigFile.xml";
- public static string QuestionText = "QuestionText";
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。