Unity答题系统3.0版本(增加题目导航栏和提交后显示题目正误标识功能)

答题系统修改说明

1、3.0版本的答题系统同样延续了前两个版本一样的方式,题库信息存储在xml文档中,通过www的形式加载并解析,构建题目数据类。3.0版本增加了一个管理题目选项的数据结构,修复了1.0版本中题目选择后点击下一题或上一题已经选择的题目信息不保存的bug,这次可以通过题目管理类QuestionNumber来维护这些数据,让题目初始化的时候全部创建好并缓存到该类的数据结构中,需要的时候直接显示或隐藏即可,避免了频繁的实例化题目对象,浪费内存。
2、3.0版本保存了上一题、下一题和交卷功能,同时增加了左侧题目导航栏功能,可以选答任一题目;增加了正误标识图功能,提交后显示答题结果:选对的标识图为绿色,选错或未选的为红色。
3、题库xml文档节点信息如下:
在这里插入图片描述

答题界面搭建

  • 答题主界面
    在这里插入图片描述
    答题主界面由题目、选项、上一下、下一题、交卷和关闭按钮组成,题目是一个Text文本,实例化时根据题库题目数量生成,均保存在父物体contentText_X 下,生成所有题目的选项toggle,均保存在QuestionItem_X 下。制作好之后,在Resources文件夹下新建一个Question文件夹,将题目Text和选项Toggle拖拽到Question下作为预制体,动态实例化创建。
  • 左侧题目导航栏
    在这里插入图片描述
    用一个滚动视图(Scroll View)来管理题目导航toggle,题库有多少道题目就生成多少个toggle组件,命名为QuestionTog,生成的QuestionTog均保存在ButtonConten_X父物体下,buttonconten_需添加自动布局组件,同样将QuestionTog拖拽到Question文件夹下作为预制体使用。
  • 下方正误标识栏
    在这里插入图片描述
    正误标识栏也是用一个滚动视图来管理,用一张Image图,并添加一个text组件作为子物体表示是第几道题,将Image命名为tagImage,生成的tagImage均保存在TagContent_X 下,同理作为预制体。

以上预支体工作完成之后可以将其从Hierarchy视图里删除或隐藏掉。
在这里插入图片描述

核心脚本代码

  • Question.cs
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System.Collections.Generic;
using System.Xml;
public class Question : MonoBehaviour
{
    
    
    /// <summary>
    /// 左边题目导航栏,用作题目管理
    /// </summary>
    public class QuestionNumber
    {
    
    
        /// <summary>
        /// 题目对应的问题和选项
        /// </summary>
        public class QuestionContent
        {
    
    
            private QuestionNumber number;
            private List<ToggleItem> options;
            public QuestionContent(QuestionNumber _number, List<ToggleItem> tog)
            {
    
    
                number = _number;
                options = tog;
            }
            /// <summary>
            /// 控制选项的显隐
            /// </summary>
            /// <param name="value"></param>
            public void SetVisible(bool value)
            {
    
    
                foreach (ToggleItem item in options)
                {
    
    
                    item.gameObject.SetActive(value);
                }
            }
        }
        private List<QuestionContent> cacheQuestions = new List<QuestionContent>();
        private QuestionBtnItem BtnItem;
        private GameObject text;
        public QuestionNumber(QuestionBtnItem btnItem, GameObject tex)
        {
    
    
            BtnItem = btnItem;
            text = tex;
        }
        /// <summary>
        /// 控制题目显隐
        /// </summary>
        /// <param name="value"></param>
        public void SetVisible(bool value)
        {
    
    
            text.SetActive(value);
        }
        /// <summary>
        /// 添加题目到题目缓存列表
        /// </summary>
        /// <param name="question"></param>
        public void AddQuestion(QuestionContent question)
        {
    
    
            cacheQuestions.Add(question);
        }
        public void RemoveQuestion(QuestionContent question)
        {
    
    
            cacheQuestions.Remove(question);
        }

    }
    public static Question Instance;//脚本单例,提供给外部访问
    public Button previousBtn;//上一题按钮
    public Button nextBtn;//下一题按钮
    public Button closeBtn;//关闭按钮
    public Button submitBtn;//提交按钮
    public Transform questionItem;//选项toggle父物体
    public Transform btnContent;//题目导航父物体
    public Transform tagContent;//正误标识父物体
    public Transform contentText;//题目文本父物体
    private string questionPath;//题库xml文件路径
    private int questionCount = 0;//当前题目序号
    private ToggleGroup questionGroup;
    private ToggleGroup questionItemGroup;
    //private List<ToggleItem> _AnswerData;//
    [HideInInspector]
    public List<QuestionBtnItem> _QuesitonBtnItemDatas;//左侧题目列表数据
    List<Image> tagImages = new List<Image>();//正误标识图
    private List<QuestionNumber> mQuestionlist = new List<QuestionNumber>();//左侧题目列表
    private List<QuestionNumber.QuestionContent> mContenList = new List<QuestionNumber.QuestionContent>();//题目选项列表
    QuestionDatas questionData;//题库数据
    int questionTotalScore = 0;//总分
    private void Awake()
    {
    
    
        Instance = this;
    }
    private void Start()
    {
    
    
        questionPath = "file://" + Application.dataPath + "/data/Question/Question.xml";
        Init();
    }
    /// <summary>
    /// 初始化
    /// </summary>
    private void Init()
    {
    
    
        questionGroup = questionItem.GetComponent<ToggleGroup>();
        questionItemGroup = btnContent.GetComponent<ToggleGroup>();
        //_AnswerData = new List<ToggleItem>();
        StartCoroutine(LoadQuestion(questionPath));
        closeBtn.onClick.AddListener(delegate
        {
    
    
            gameObject.SetActive(false);
        });
        previousBtn.onClick.AddListener(PreviousClick);
        submitBtn.onClick.AddListener(SubmitClick);
        nextBtn.onClick.AddListener(NextClick);
    }
    
    /// <summary>
    /// 加载题库协程
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    IEnumerator LoadQuestion(string path)
    {
    
    
        yield return null;

        using (WWW www = new WWW(path))
        {
    
    
            yield return www;
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(www.text);
            questionData = new QuestionDatas(doc.FirstChild);
        }
        InitQuestionPanel(questionData);
    }
    /// <summary>
    /// 初始化答题面板
    /// </summary>
    /// <param name="questionData"></param>
    private void InitQuestionPanel(QuestionDatas questionData)
    {
    
    
        _QuesitonBtnItemDatas = new List<QuestionBtnItem>();
        for (int i = 0; i < questionData.questionItemDatas.Count; i++)
        {
    
    
            GameObject text = GameObject.Instantiate(Resources.Load<GameObject>("Question/Text"));//实例化题目
            text.GetComponent<Text>().text = questionData.questionItemDatas[i].Problem;
            text.transform.SetParent(contentText);
            text.transform.localScale = Vector3.one;
            QuestionBtnItem btnItem = GameObject.Instantiate(Resources.Load<QuestionBtnItem>("Question/QuestionTog"));//实例化左侧题目导航
            Image tagImg = GameObject.Instantiate(Resources.Load<Image>("Question/tagImage"));//实例化下方正误标识图
            tagImg.gameObject.GetComponentInChildren<Text>().text = (i + 1).ToString();
            tagImg.transform.SetParent(tagContent);
            tagImg.transform.localScale = Vector3.one;
            tagImages.Add(tagImg);
            _QuesitonBtnItemDatas.Add(btnItem);

            btnItem.Init(i, (id) =>
            {
    
    
                CallBackId(id);
            });
            QuestionNumber tmpQuestion = new QuestionNumber(btnItem, text);//初始化题目对象
            tmpQuestion.SetVisible(false);
            mQuestionlist.Add(tmpQuestion);
            btnItem.thisBtn.group = questionItemGroup;
            btnItem.transform.SetParent(btnContent);
            btnItem.transform.localScale = Vector3.one;
            CreateQuestionItem(questionData.questionItemDatas[i]);
        }
        mQuestionlist[questionCount].SetVisible(true);//初始显示第一题
        mContenList[questionCount].SetVisible(true);
    }
    /// <summary>
    /// 点击左侧题目回调函数
    /// </summary>
    /// <param name="id"></param>
    private void CallBackId(int id)
    {
    
    
        if (id != questionCount)
        {
    
    
            mQuestionlist[questionCount].SetVisible(false);//上一道题目隐藏
            mContenList[questionCount].SetVisible(false);
            questionCount = id;
            mQuestionlist[questionCount].SetVisible(true);//显示当前题目内容
            mContenList[questionCount].SetVisible(true);
        }
    }
    /// <summary>
    /// 创建题目
    /// </summary>
    /// <param name="questionItemData"></param>
    private void CreateQuestionItem(QuestionItemDatas questionItemData)
    {
    
    
        List<ToggleItem> tmp = new List<ToggleItem>();
        for (int i = 0; i < questionItemData.answerDatas.Count; i++)
        {
    
    
            ToggleItem tog = GameObject.Instantiate(Resources.Load<ToggleItem>("Question/Toggle"));//实例化选项toggle
            tog.Init(questionItemData, questionItemData.answerDatas[i]);//toggle初始化
            tog.thisTog.group = questionGroup;
            tog.transform.SetParent(questionItem);
            tog.transform.localScale = Vector3.one;
            tmp.Add(tog);
        }
        QuestionNumber.QuestionContent questionContent = new QuestionNumber.QuestionContent(mQuestionlist[questionCount], tmp);//初始化选项对象
        questionContent.SetVisible(false);
        mQuestionlist[questionCount].AddQuestion(questionContent);
        mContenList.Add(questionContent);
    }
    /// <summary>
    /// 上一题点击事件
    /// </summary>
    private void PreviousClick()
    {
    
    
        if (questionCount > 0)
        {
    
    
            questionCount--;
            CheckOtherQuestion();
            _QuesitonBtnItemDatas[questionCount].ChangeQuestionState(QuestionState.Select);
            
        }
    }
    /// <summary>
    /// 下一题点击事件
    /// </summary>
    private void NextClick()
    {
    
    
        if (questionCount < questionData.questionItemDatas.Count - 1)
        {
    
    
            questionCount++;
            CheckOtherQuestion();
            _QuesitonBtnItemDatas[questionCount].ChangeQuestionState(QuestionState.Select);
        }
    }
    /// <summary>
    /// 检查其他未作答的题目
    /// </summary>
    private void CheckOtherQuestion()
    {
    
    
        for (int i = 0; i < _QuesitonBtnItemDatas.Count; i++)
        {
    
    
            if (!_QuesitonBtnItemDatas[i].isSelectAnswer)
            {
    
    
                _QuesitonBtnItemDatas[i].img.sprite = _QuesitonBtnItemDatas[i].Default;//未作答恢复默认状态
            }
        }
    }
    /// <summary>
    /// 提交事件
    /// </summary>
    private void SubmitClick()
    {
    
    
        //double questionTotalScore = 0;
        for (int i = 0; i < _QuesitonBtnItemDatas.Count; i++)
        {
    
    
            if (_QuesitonBtnItemDatas[i].QuestionScore > 0)
            {
    
    
                questionTotalScore += questionData.questionItemDatas[i].RightVar;
                tagImages[i].color = Color.green;
            }
            else
            {
    
    
                tagImages[i].color = Color.red;
            }
        }
    }
    /// <summary>
    /// 题目数据类
    /// </summary>
    public class QuestionDatas
    {
    
    
        public List<QuestionItemDatas> questionItemDatas;

        public QuestionDatas(XmlNode node)
        {
    
    
            questionItemDatas = new List<QuestionItemDatas>();
            for (int i = 0; i < node.ChildNodes.Count; i++)
            {
    
    
                questionItemDatas.Add(new QuestionItemDatas(node.ChildNodes[i]));
            }
        }
    }
    /// <summary>
    /// 一道题目数据类
    /// </summary>
    public class QuestionItemDatas
    {
    
    
        /// <summary>
        /// 正确答案分值
        /// </summary>
        public int RightVar;
        /// <summary>
        /// 问题
        /// </summary>
        public string Problem;
        public int Id;
        public QuestionItemDatas(XmlNode node)
        {
    
    
            answerDatas = new List<AnswerDatas>();
            Problem = node["Name"].InnerText;
            Id = int.Parse(node.Attributes["Id"].InnerText);
            XmlNodeList answerList = node["Answer"].ChildNodes;
            for (int i = 0; i < answerList.Count; i++)
            {
    
    
                answerDatas.Add(new AnswerDatas(answerList[i]));
            }
        }
        public List<AnswerDatas> answerDatas;
    }
    /// <summary>
    /// 一道题目对应的选项数据类
    /// </summary>
    public class AnswerDatas
    {
    
    
        /// <summary>
        /// 选项对应的分数
        /// </summary>
        public int score;
        /// <summary>
        /// 选项内容
        /// </summary>
        public string answerName;

        public AnswerDatas(XmlNode node)
        {
    
    
            answerName = node.Attributes["Name"].InnerText;
            score = int.Parse(node.InnerText);
        }
    }
}

将该脚本拖拽到UI上即可,并给其中对象拖拽赋值:
在这里插入图片描述

  • QuestionBtnItem.cs
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 题目默认、选中、完成作答三个状态
/// </summary>
public enum QuestionState
{
    
    
    Default,
    Select,
    Finished
}

public class QuestionBtnItem : MonoBehaviour 
{
    
    
    public int Id;
    public Image img;
    public Toggle thisBtn;
    public Sprite Default;//默认灰色图
    public Sprite Finished;//完成深色图
    public Sprite Select;//选中颜色图
    private System.Action<int> callBack;
    private QuestionState questionState = QuestionState.Default;//题目默认未选中状态
    public bool isSelectAnswer = false;//当前题目是否已选择答案
    //public string SelectAnswer = "";
    private float questionScore = 0;
    public float QuestionScore
    {
    
    
        get {
    
     return questionScore; }
        set {
    
     questionScore = value; }
    }

    public void Init(int i,System.Action<int > callBack=null)
    {
    
    
        thisBtn = gameObject.GetComponent<Toggle>();
        if (i == 0)
        {
    
    
            ChangeQuestionState(QuestionState.Select);//默认选中第一道题
        }
        Id = i;
        this.callBack = callBack;
        thisBtn.name = (i+1).ToString();
        thisBtn.GetComponentInChildren<Text>().text = "第 " + thisBtn.name + " 题";
        thisBtn.onValueChanged.AddListener((isSelect) =>
        {
    
    
            if (callBack!=null)
            {
    
    
                callBack(Id);//选中就调用一次回调函数
            }
        });

    }
    /// <summary>
    /// 修改题目状态
    /// </summary>
    /// <param name="state"></param>
    public void ChangeQuestionState(QuestionState state)
    {
    
    
        switch (state)
        {
    
    
            case QuestionState.Default:
                img.sprite = Default;
                break;
            case QuestionState.Select:
                thisBtn.isOn = true;
                break;
            case QuestionState.Finished:
                isSelectAnswer = true;
                img.sprite = Finished;
                break;
        }
    }
}

将该脚本拖拽给QuestionTog对象:赋值题目的三种状态图
在这里插入图片描述

  • ToggleItem.cs
using UnityEngine;
using UnityEngine.UI;


public class ToggleItem : MonoBehaviour 
{
    
    
	public Toggle thisTog;
    public Text optionText;//选项文本
    public float Score;//选项对应分值
    public void Init(Question.QuestionItemDatas questionItemData, Question.AnswerDatas answerData)
    {
    
    
        Score = answerData.score;
        optionText.text = answerData.answerName;
        thisTog.onValueChanged.AddListener((isSelect) =>
        {
    
    
            Question.Instance._QuesitonBtnItemDatas[questionItemData.Id].ChangeQuestionState(QuestionState.Finished);
            Question.Instance._QuesitonBtnItemDatas[questionItemData.Id].QuestionScore = Score;//将选择的选项分值赋值给当前题目
        });
    }
}

将改脚本拖拽给选项Toggle:
在这里插入图片描述

运行效果

请添加图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
创作不易,喜欢就一键三连呀,点点关注哦!

猜你喜欢

转载自blog.csdn.net/qq_42437783/article/details/123844632