jjzjj

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

周周的Unity小屋 2024-12-12 原文

目录导航

答题系统修改说明

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:

运行效果




创作不易,喜欢就一键三连呀,点点关注哦!

有关Unity答题系统3.0版本(增加题目导航栏和提交后显示题目正误标识功能)的更多相关文章

  1. ruby - 检查数组是否在增加 - 2

    这个问题在这里已经有了答案:Checktoseeifanarrayisalreadysorted?(8个答案)关闭9年前。我只是想知道是否有办法检查数组是否在增加?这是我的解决方案,但我正在寻找更漂亮的方法:n=-1@arr.flatten.each{|e|returnfalseife

  2. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  3. ruby-on-rails - 在 ruby​​ .gemspec 文件中,如何指定依赖项的多个版本? - 2

    我正在尝试修改当前依赖于定义为activeresource的gem:s.add_dependency"activeresource","~>3.0"为了让gem与Rails4一起工作,我需要扩展依赖关系以与activeresource的版本3或4一起工作。我不想简单地添加以下内容,因为它可能会在以后引起问题:s.add_dependency"activeresource",">=3.0"有没有办法指定可接受版本的列表?~>3.0还是~>4.0? 最佳答案 根据thedocumentation,如果你想要3到4之间的所有版本,你可以这

  4. ruby-on-rails - 如果我将 ruby​​ 版本 2.5.1 与 rails 版本 2.3.18 一起使用会怎样? - 2

    如果我使用ruby​​版本2.5.1和Rails版本2.3.18会怎样?我有基于rails2.3.18和ruby​​1.9.2p320构建的rails应用程序,我只想升级ruby的版本,而不是rails,这可能吗?我必须面对哪些挑战? 最佳答案 GitHub维护apublicfork它有针对旧Rails版本的分支,有各种变化,它们一直在运行。有一段时间,他们在较新的Ruby版本上运行较旧的Rails版本,而不是最初支持的版本,因此您可能会发现一些关于需要向后移植的有用提示。不过,他们现在已经有几年没有使用2.3了,所以充其量只能让更

  5. ruby-on-rails - 获取 inf-ruby 以使用 ruby​​ 版本管理器 (rvm) - 2

    我安装了ruby​​版本管理器,并将RVM安装的ruby​​实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby​​。有没有办法让emacs像shell一样尊重ruby​​的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el

  6. ruby-on-rails - Cucumber 是否只是 rspec 的包装器以帮助将测试组织成功能? - 2

    只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您

  7. ruby-on-rails - 如何在发布新的 Ruby 或 Rails 版本时收到通知? - 2

    有人知道在发布新版本的Ruby和Rails时收到电子邮件的方法吗?他们有邮件列表,RubyonRails有一个推特,但我不想听到那些随之而来的喧嚣,我只想知道什么时候发布新版本,尤其是那些有安全修复的版本。 最佳答案 从therailsblog获取提要.http://weblog.rubyonrails.org/feed/atom.xml 关于ruby-on-rails-如何在发布新的Ruby或Rails版本时收到通知?,我们在StackOverflow上找到一个类似的问题:

  8. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  9. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  10. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

随机推荐