• Home  / 
  • Unity3D
  •  /  Using Unity3D Xml Files for game Data – Quiz Game Example

Using Unity3D Xml Files for game Data – Quiz Game Example

Recently I shared a method for creating a QuizGame using the ScriptableObject functionality built into Unity3D.  Today, we’re going to modify that example to be a little more scalable for cases where you might want 1000’s of questions that aren’t all managed in the editor.  To accomplish this, we’ll use some Unity3D xml files and XmlSerializer.  By using xml files, we can easily swap or modify the data outside the engine, and if we pull the XML data from a web service, we could change the questions for our quiz game without any modification or update to the game itself.

Setup – Read Part 1

If you haven’t seen the previous post, take a look at it now.  It covers the basics for laying out a sample UI and how the game will work.

You can see that here: HowTo: Build a Unity3D Quiz Game – Like Trivia Crack / Heads Up

The differences

In the previous examples, our QuizQuestion class was actually pretty heavy.  Almost all of that was due to editor customizations that made it more streamlined to work with.

Since we’ll be serializing our QuizQuestions in xml now, we can go with a very simple class that only contains 4 auto properties.


The QuizCollection

Because of how the previous sample is architected, we don’t need to change much to allow for XML serialization of our questions.  Other than the QuizQuestion class, we only need to adjust our collection.

Instead of using Resources.LoadAll, we’ll be using the XmlSerializer and StreamReader classes to do the work.

The XmlSerializer requires a type for it’s constructor.  Our data is an array of QuizQuestions, so we initialize it by passing “typeof(QuizQuestion[])” into the constructor on line 23.


In Awake, we check to see if a questions.xml file exists.  If not, we’ll write out a sample xml file that can be used as a starting point.

Then we load the questions..  everything else is the same as the previous example.

Separation of Concerns

The reason this change is so small is that in part one, we focused on keeping a clear separation of concerns.  Keeping things split up like this makes change easy and painless.

What about JSON?

There’s no doubt a good number of people will think “why use xml, what about json?”, “json is sooooo much better”, “etc”.

As a whole, I agree json is a better format.  It’s cleaner and removes a lot of the clutter from verbose ceremony..

Unfortunately, I still really dislike the built in json serializer for Unity.  If you do want to use JSON though, I’d recommend something like JSON.Net for Unity.  I’ve used it in the past and it was great.

For this example though, that seemed like overkill.  Xml will work fine here, and realistically you’ll probably want a web service providing the data here… and writing out to xml/json for most services is as simple as swapping the request header.

Conclusions & Download

This post is meant to answer a specific question from the Unity3D.College facebook group.  If you have a question of your own, join us and share 🙂
If you’d like to download the full project sample, it’s available here: https://unity3dcollegedownloads.blob.core.windows.net/vrgames/QuizGameXml.zip

 

  • Pingback: HowTo: Build a Unity3D Quiz Game - Like Trivia Crack / Heads Up - Unity3D.College()

  • Jeanelle

    Hi ! Im creating a quiz game in unity. and im using a json file for my questions, is it possible for me to randomize my questions ?

    • JW

      Definitely. If you take a look at lines 33-36 above you’ll see how I select a random one out of the questions.

      • Jeanelle

        Thank you for responding 🙂 im new to unity and C# and im still a bit confused, my code is different and here it is:
        please please hoping you could help me 🙂

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
        using LitJson;
        using UnityEngine.UI;

        public class G6 : MonoBehaviour
        {

        public string filePath;
        public string jsonString;
        public JsonData questionData;
        public int NumberQuestion = 0;
        public GameObject answerPrefab;
        public bool nextQuestion;
        public bool clickAnswer;
        public GameObject wrong;
        public GameObject correct;
        public int score;
        public GameObject VeryGood;
        public GameObject Good;
        public GameObject Bad;
        public GameObject Poor;
        public GameObject pausemenu;
        public GameObject Gamebutton;
        public GameObject GameLabel;
        public AudioSource CorrectSound;
        public AudioSource WrongSound;
        public Text countText6;
        public Text coinsText1;
        public GameObject Hint;
        private ScoreManager theScoreMana;
        private CoinManager theCoinManage;

        void Start()
        {
        coinsText1.text =”Coins: “+ FindObjectOfType().currentScore;
        }

        public void QuestionBegin(string jsonName)
        {

        pausemenu.SetActive(false);
        score = 0;
        nextQuestion = true;
        filePath = System.IO.Path.Combine(Application.streamingAssetsPath + “/Grade6”, jsonName + “.json”);
        //jsonString = System.IO.File.ReadAllText(filePath);
        StartCoroutine(“Json”);
        questionData = JsonMapper.ToObject(jsonString);
        onClick();
        }
        IEnumerator Json()
        {
        if (filePath.Contains(“://”))
        {

        WWW www = new WWW(filePath);
        yield return www;
        jsonString = http://www.text;
        }
        else
        {
        jsonString = System.IO.File.ReadAllText(filePath);
        }
        }
        public void onClick()
        {

        if (NumberQuestion >= questionData[“data”].Count)
        {
        Debug.Log(“The result”);

        if (score == questionData[“data”].Count)
        {
        GameObject.Find(“Rank”).GetComponent().text = “Ikaw ay isang mabuting mag-aaral!”;
        VeryGood.SetActive(true);
        Good.SetActive(false);
        Bad.SetActive(false);
        Poor.SetActive(false);
        Gamebutton.SetActive(true);
        GameLabel.SetActive(true);
        }
        if (score == questionData[“data”].Count * 0)
        {
        GameObject.Find(“Rank”).GetComponent().text = “Mag-aral pa ng mabuti!”;
        VeryGood.SetActive(false);
        Good.SetActive(false);
        Bad.SetActive(false);
        Poor.SetActive(true);
        Gamebutton.SetActive(false);
        GameLabel.SetActive(false);
        }
        else if (score >= questionData[“data”].Count * 1 / 2)
        {
        GameObject.Find(“Rank”).GetComponent().text = “Ipagpatuloy ang pag-aaral ng mabuti!”;
        VeryGood.SetActive(false);
        Good.SetActive(true);
        Bad.SetActive(false);
        Poor.SetActive(false);
        Gamebutton.SetActive(true);
        GameLabel.SetActive(true);
        }
        else if (score <= questionData["data"].Count * 1 / 2)
        {
        GameObject.Find("Rank").GetComponent().text = “Mag-aral pa ng mabuti!”;
        VeryGood.SetActive(false);
        Good.SetActive(false);
        Bad.SetActive(true);
        Poor.SetActive(false);
        Gamebutton.SetActive(false);
        GameLabel.SetActive(false);
        }

        MenuManager menuResult = GameObject.Find(“Canvas”).GetComponent();
        menuResult.ShowMenu(GameObject.Find(“Result”).GetComponent());

        GameObject.Find(“Score”).GetComponent().text = score.ToString() + “/” + questionData[“data”].Count;

        }
        wrong.SetActive(false);
        correct.SetActive(false);
        if (nextQuestion)
        {

        Hint.SetActive(true);
        GameObject[] answerDestroy = GameObject.FindGameObjectsWithTag(“Answer5”);
        if (answerDestroy != null)
        {
        for (int x = 0; x < answerDestroy.Length; x++)
        {
        DestroyImmediate(answerDestroy[x]);
        }

        }
        GameObject.Find("G1(C6)/Panel/QuestionC6/Question6").GetComponentInChildren().text = questionData[“data”][NumberQuestion][“question”].ToString();
        countText6.text = GameObject.Find(“G1(C6)/Panel/Image”).GetComponentInChildren().text = questionData[“data”][NumberQuestion][“id”].ToString();
        for (int i = 0; i < questionData["data"][NumberQuestion]["answer"].Count; i++)
        {
        GameObject answer = Instantiate(answerPrefab);
        answer.GetComponentInChildren().text = questionData[“data”][NumberQuestion][“answer”][i].ToString();
        Transform answerC = GameObject.Find(“AnswerC6”).GetComponent();
        answer.transform.SetParent(answerC);
        string x = i.ToString();

        if (i == 0)
        {
        answer.name = “correctAnswer”;
        answer.GetComponent().onClick.AddListener(() => Answer(“0”));
        }
        else
        {
        answer.name = “wrongAnswer” + x;
        answer.GetComponent().onClick.AddListener(() => Answer(x));
        }
        answer.transform.SetSiblingIndex(Random.Range(0, 3));
        }

        NumberQuestion++;
        nextQuestion = false;
        clickAnswer = true;
        StartCoroutine(“Timer6”);

        }
        }
        public void Answer(string x)
        {
        if (clickAnswer)
        {
        if (x == “0”)
        {
        score++;
        GameObject.Find(“correctAnswer”).GetComponent().image.color = Color.green;
        correct.SetActive(true);
        CorrectSound.Play();
        Debug.Log(“Correct Answer”);
        }
        else
        {
        GameObject.Find(“wrongAnswer” + x).GetComponent().image.color = Color.red;
        wrong.SetActive(true);
        WrongSound.Play();
        Debug.Log(“Wrong Answer”);
        }
        nextQuestion = true;
        clickAnswer = false;

        }
        }
        IEnumerator Timer6()
        {
        Image time = GameObject.Find(“Timer6”).GetComponent();
        time.fillAmount = 1;
        float timeToWait = 10f;
        float incrementToRemove = 0.05f;
        float x = time.fillAmount / timeToWait * incrementToRemove;

        while (timeToWait > 0)
        {
        yield return new WaitForSeconds(incrementToRemove);

        if (!nextQuestion)
        {
        time.fillAmount -= x;
        timeToWait -= incrementToRemove;
        }
        else
        {
        timeToWait = 0;
        }
        }
        if (time.fillAmount <= 0.1f)
        {
        for (int i = 1; i < 4; i++)
        {
        GameObject.Find("wrongAnswer" + i).GetComponent().image.color = Color.red;
        wrong.SetActive(false);
        }
        GameObject.Find(“correctAnswer”).GetComponent().image.color = Color.green;
        clickAnswer = false;
        nextQuestion = true;
        }
        }

        }

        • Looking through the code it seems like you’ve overcomplicated things a bit for yourself. You shouldn’t need to instantiate/destroy objects and your single class here is doing a bit too much work and should be split into multiple.

          If you just want to randomize it though, you’ll want to pick a random NumberQuestion value each time instead of incrementing it.

          You could do that with the UnityEngine.Random call. But you’ll need to keep track of the #’s already used and re-choose a random # if you pick one already taken. (or keep a list of available question #’s and randomly pick from one of those which would probably be a little easier and is similar to what I do in the code above 🙂