一:界面展示
(录gif太长了,上传不了)
二:功能简述
- 点击主界面进入盲盒界面
- 点击任意一个盲盒进入答题界面,回答完一个题不需要返回,回答完一个3秒后进入下一题,一旦答对6题或答错4题,直接显示成功或失败界面(这个答对答错是可以通过代码修改的)
- 等待5秒后,返回主界面
- 点击任意盲盒的题目是不一样的,进入盲盒后,题目顺序也是不一样的,总结来说:就是盲盒中的题目不是固定的一个。
- 可以有单选题,多选题,判断题,题目数量可以内部修改,要是想做外部修改的话,加一个configDFile文件即可
三:代码梳理
主要代码:(这个是在原先的答题系统延申出来的)
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界面:首界面 开始答题 过度界面 答题界面 结束界面 ")]
[SerializeField] GameObject firstRoot;
[SerializeField] GameObject startRoot;
[SerializeField] GameObject answerRoot;
[SerializeField] GameObject endRoot;
[Header("按钮:前往答题界面按钮 开始答题 上一题,提交,下一题")]
[SerializeField] Button toStartRootBtn;
[SerializeField] Button submitBtn;
[SerializeField] Button[] startBtns;
[Header("正确图片,错误图片 题型图片 题型英文图片")]
[SerializeField] Image correntImg;
[SerializeField] Image defeatImg;
[SerializeField] Image QTImg;
[SerializeField] Image QTEngImg;
[SerializeField] List<Sprite> QTSprites;
[Header("文本:题目序号 解析内容 ")]
[SerializeField] Text questionID;
[SerializeField] Text analysisData;
[Header("内容scroll的content 单选scroll的content 选项scroll的content")]
[SerializeField] Transform contentScrollContent;
[SerializeField] Transform questionBtnRoot;
[SerializeField] Transform selectContent;
[SerializeField] ToggleGroup questionGroup;
[Header("正确音效 错误音效 成功音效 失败音效")]
[SerializeField] List<AudioClip> musics;
// 答题界面数据内容
private QuestionPanelData mQuestionPanelData;
// 每一道题的题目内容
private QuestionData mQuestionData;
// 题目内容物体
private GameObject mQuestion;
// 选项的链表
private List<Options> options = new List<Options>();
private List<int> randomNum = new List<int>(); //存放随机数
int index = 0;
#region 单例和初始化
static Panel_Question instance;
public static Panel_Question GetInstance()
{
return instance;
}
private void Awake()
{
Init();
instance = this;
}
#endregion
#region 按钮监听和获取随机数
private void Init()
{
//按钮监听
toStartRootBtn.onClick.AddListener(ToStartRootEvent);
submitBtn.onClick.AddListener(submitClick);
for (int i = 0; i < startBtns.Length; i++)
{
startBtns[i].onClick.AddListener(StartAnswer);
}
//获取随机数
GetRandomNum();
}
#endregion
private void Start()
{
//读取xml文件,随机出一套题库
StartCoroutine(LoadingQuesiton(DataPath.QuestionData));
//初始界面的显示与隐藏
firstRoot.SetActive(true);
startRoot.SetActive(false);
answerRoot.SetActive(false);
endRoot.SetActive(false);
//设置多选字段为空
StringToNull();
//默认提交按钮不能使用,没有提交
submitBtn.interactable = false;
}
string[] optionContent;
string temp;
string optionA = "";
string optionB = "";
string optionC = "";
string optionD = "";
bool isSub; //是否点击了提交按钮
private void Update()
{
//提交按钮是否能使用
foreach (var item in options)
{
//题型图片的设置
if (mQuestionData.answerData.Count == 4)
{
if (mQuestionData.IsSingleChoice)//单选图片
{
QTImg.sprite = QTSprites[0];
QTEngImg.sprite = QTSprites[3];
}
else //多选图片
{
QTImg.sprite = QTSprites[1];
QTEngImg.sprite = QTSprites[4];
}
}
else if (mQuestionData.answerData.Count == 2)//判断图片
{
QTImg.sprite = QTSprites[2];
QTEngImg.sprite = QTSprites[5];
}
//提取选项
optionContent = item.optionText.GetComponent<Text>().text.Split('.');
//题目判断
if (item.thisToggle.isOn)
{
if (!isSub)
{
submitBtn.interactable = true;
isSub = true;
}
//如果是单选的话,直接把正确答案跟选择的答案比较
if (mQuestionData.IsSingleChoice)
{
temp = optionContent[0];
Debug.Log("单选" + optionContent[0]);
}
else
{
if (optionContent[0] == "A")
optionA = "A";
else if (optionContent[0] == "B")
optionB = "B";
else if (optionContent[0] == "C")
optionC = "C";
else if (optionContent[0] == "D")
optionD = "D";
temp = optionA + optionB + optionC + optionD;
}
}
else
{
if (!mQuestionData.IsSingleChoice)
{
if (optionContent[0] == "A")
optionA = "";
else if (optionContent[0] == "B")
optionB = "";
else if (optionContent[0] == "C")
optionC = "";
else if (optionContent[0] == "D")
optionD = "";
temp = optionA + optionB + optionC + optionD;
Debug.Log("多选2" + temp);
}
}
}
}
#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)
{
randomNum.Clear();
GetRandomNum();
//this.gameObject.SetActive(true);
mQuestionPanelData = questionPanelData;
CreateQuestion(questionPanelData.questionData[randomNum[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);
}
//实例化题目预制体
QuestionText = "QuestionText";
mQuestion = Instantiate(Resources.Load<GameObject>(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 < 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 前往盲盒界面 开始答题 上一题 下一题 提交按钮事件
//前往盲盒界面
void ToStartRootEvent()
{
firstRoot.SetActive(false);
startRoot.SetActive(true);
answerRoot.SetActive(false);
endRoot.SetActive(false);
}
#region 开始答题
string QuestionText;
// 开始答题
public void StartAnswer()
{
firstRoot.SetActive(false);
startRoot.SetActive(false);
answerRoot.SetActive(true);
endRoot.SetActive(false);
//QuestionText = DataPath.QuestionData;
//StartCoroutine(LoadingQuesiton(DataPath.QuestionData));
for (int i = 0; i < QuestionPanel.questionPanelData.Count; i++)
{
InitQuestionPanel(QuestionPanel.questionPanelData[i]);
}
}
#endregion
// 下一题点击事件
private void nextClick()
{
if (index < mQuestionPanelData.questionData.Count - 1)
{
index++;
CreateQuestion(mQuestionPanelData.questionData[randomNum[index]]);
}
submitBtn.interactable = false;
isSub = false;
}
bool isCorrent; //是否全部答对
string rightAnswer;
int correntNum = 0; //正确题目个数
int errerNUM = 0; //错误题目个数
// 题目提交事件
private void submitClick()
{
isSub = true;
//遍历当前题目的选项,有选择的就可以提交核验答案,并显示解析内容
foreach (var item in options)
{
if (item.thisToggle.isOn)
{
rightAnswer = "";
for (int i = 0; i < mQuestionData.answerData.Count; i++)
{
switch (i)
{
case 0:
if (mQuestionData.answerData[i].Score > 0)//xml文档里面score属性对应的数值是用来判断该选项是否为正确选项
{
rightAnswer += "A";
}
break;
case 1:
if (mQuestionData.answerData[i].Score > 0)
{
rightAnswer += "B";
}
break;
case 2:
if (mQuestionData.answerData[i].Score > 0)
{
rightAnswer += "C";
}
break;
case 3:
if (mQuestionData.answerData[i].Score > 0)
{
rightAnswer += "D";
}
break;
}
}
}
//选择一个选项之后不能在选择其他选项
item.thisToggle.interactable = false;
}
//题目正确与否的判断
if (rightAnswer == temp)
{
//正确数量计数
correntNum += 1;
Debug.Log("正确数量"+correntNum);
//把多选的字符串置空
StringToNull();
analysisData.text = String.Format("恭喜你,答对啦!");
GetComponent<AudioSource>().clip = musics[0];
GetComponent<AudioSource>().Play();
}
else
{
//错误数量计数
errerNUM += 1;
Debug.Log("错的数量" + errerNUM);
//把多选的字符串置空
StringToNull();
analysisData.text = String.Format("很遗憾,答错了!正确答案是:{0}\n", rightAnswer);
//错误音效播放
GetComponent<AudioSource>().clip = musics[1];
GetComponent<AudioSource>().Play();
defeatImg.gameObject.SetActive(true);
}
submitBtn.interactable = false;
//答对题目数量大于等于6个,显示成功界面,打错数量>=4个,显示失败界面
if ( correntNum >= 6)
{
isCorrent = true;
Invoke("ShowLastRoot", 3);
}
else if (errerNUM >= 4)
{
isCorrent = false;
Invoke("ShowLastRoot", 3);
}
else
//三秒后到下一题
Invoke("nextClick", 3);
//提交后,该题目不可再选择修改
submitBtn.interactable = false;
}
//返回主界面
void BackToMain()
{
//返回主界面
firstRoot.SetActive(true);
startRoot.SetActive(false);
answerRoot.SetActive(false);
endRoot.SetActive(false);
//计数归零
index = 0;
correntNum = 0;
errerNUM = 0;
//提交按钮的初始化
submitBtn.interactable = false;
isSub = false;
}
//显示结束界面
void ShowLastRoot()
{
//界面的显示
firstRoot.SetActive(false);
startRoot.SetActive(false);
answerRoot.SetActive(false);
endRoot.SetActive(true);
//如果答对显示正确界面,答错显示错误界面
if (isCorrent)
{
correntImg.gameObject.SetActive(true);
defeatImg.gameObject.SetActive(false);
GetComponent<AudioSource>().clip = musics[2];
GetComponent<AudioSource>().Play();
}
else
{
correntImg.gameObject.SetActive(false);
defeatImg.gameObject.SetActive(true);
GetComponent<AudioSource>().clip = musics[3];
GetComponent<AudioSource>().Play();
}
//延迟3秒返回主界面
Invoke("BackToMain", 5);
}
#region 多选字符串归零
void StringToNull()
{
optionA = "";
optionB = "";
optionC = "";
optionD = "";
}
#endregion
#endregion
#region 随机顺序出题
void GetRandomNum()
{
//利用哈希函数获取随机数
HashSet<int> nums = new HashSet<int>();
System.Random r = new System.Random();
while (nums.Count != 9)
{
nums.Add(r.Next(0, 9));
}
//Debug.Log(topicMax);
foreach (var item in nums)
{
randomNum.Add(item);
}
}
#endregion
}
#region 答题数据类
/// <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]));
//Debug.Log(questionPanelData);
}
}
}
/// <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为多选
private bool isSingleChoice;
// 解析内容
public string Analysis;
// 题目内容
public string problem;
public List<AnswerData> answerData;
public bool IsSingleChoice { get => isSingleChoice; set => isSingleChoice = value; }
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);
}
}
#endregion
答案类:
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; });
}
}
答题的xml文件位置
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 全局静态类,用来定义静态字段,方便调用
/// </summary>
public class DataPath
{
public static string QuestionData = "file://" + Application.dataPath + "/StreamingAssets/" + 0 + ".xml";
//public static string QuestionData;
public static string QuestionText = "QuestionText";
}
四:UI界面
把第一个代码挂在canvas上面,然后把里面对应的内容拉进去就行了
(答题的那个内容的各个部分可以看上一个文章unity制作答题系统_unity答题系统_石阿包的博客-CSDN博客)