Loading multiple questions from WEBAPI

I want to know how to load questions and answers from a WEBAPI, how do I go about this, because in the course, we made use of predefined questions that happened to be our scriptobject.

This is a whole lecture on its ownā€¦

If you know C# and Unity, you can make a web request to whichever API with UnityWebRequest.Get


If you are still a beginner, read on

I donā€™t have a working copy of QuizMaster on hand, so I am just going to get you set up to get 10 questions that we will write to the console, and then grab code from the repo and try to squeeze it in there

The first thing weā€™ll do is get a url from the open trivia db to use. Iā€™ll keep it simple with 10 easy mulitple choice general knowledge questions

You can generate the api url here Open Trivia DB: Free to use, user-contributed trivia question database.
You will see a screen like this

I have already selected the options I will use. Click ā€œgenerate api urlā€ and it will give you the url that we will use
https://opentdb.com/api.php?amount=10&category=9&difficulty=easy&type=multiple

Next, in Unity we create a QuestionManager.cs script. In this script, we will use the above url to retrieve the questions. The code is pretty straight forward - I took it almost directly from the documentation linked above

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

public class QuestionManager : MonoBehaviour
{
    [SerializeField] int _numberOfQuestions = 10;

    private IEnumerator Start()
    {
        using UnityWebRequest request = UnityWebRequest.Get($"https://opentdb.com/api.php?amount={_numberOfQuestions}&category=9&difficulty=easy&type=multiple");
        yield return request.SendWebRequest();

        switch (request.result)
        {
            case UnityWebRequest.Result.ConnectionError:
            case UnityWebRequest.Result.DataProcessingError:
                Debug.LogError($"ERROR: {request.error}");
                break;
            case UnityWebRequest.Result.ProtocolError:
                Debug.LogError($"HTTP ERROR: {request.error}");
                break;
            case UnityWebRequest.Result.Success:
                string jsonString = request.downloadHandler.text;
                Debug.Log(jsonString);
                break;
        }
    }
}

At the top I have exposed a field to the inspector where we can set the number of questions to retrieve. Donā€™t go overboard with this, 10 should be good

I have changed Start() to be a coroutine because the web request will take some time and we donā€™t want to freeze the game while itā€™s loading. The code creates a request object using the url (into which we have injected the number of questions specified), and then sends it off. When it returns, there may be some issues, but otherwise we will get a json string containing the 10 requested questions. For now, I have logged these to the console. Hereā€™s an example of what the response looks like (I have made it pretty and Iā€™m only showing 2 questions to preserve space)

{
    "response_code":0,
    "results":[
        {
            "category":"General Knowledge",
            "type":"multiple",
            "difficulty":"easy",
            "question":"Which sign of the zodiac is represented by the Crab?",
            "correct_answer":"Cancer",
            "incorrect_answers":[
                "Libra",
                "Virgo",
                "Sagittarius"
            ]
        },
        {
            "category":"General Knowledge",
            "type":"multiple",
            "difficulty":"easy",
            "question":"Which American president appears on a one dollar bill?",
            "correct_answer":"George Washington",
            "incorrect_answers":[
                "Thomas Jefferson",
                "Abraham Lincoln",
                "Benjamin Franklin"
            ]
        }
    ]
}

These questions donā€™t quite fit into our scriptable object, so we will need to do some work.

First, we want to make 2 new objects to hold this data; one to hold a single question, and one to hold the whole list. I am not including all the values here, because they are of no interest to us. Iā€™m just keeping it real simple

[System.Serializable]
public struct QuestionModel
{
    public string question;
    public string correct_answer;
    public string[] incorrect_answers;
}
[System.Serializable]
public struct QuestionsModel
{
    public QuestionModel[] results;
}

With these we can now parse the questions into neat little structs for use. Letā€™s add it just below our Debug.Log

            case UnityWebRequest.Result.Success:
                string jsonString = request.downloadHandler.text;
                Debug.Log(jsonString);
                QuestionsModel questions = JsonUtility.FromJson<QuestionsModel>(jsonString);
                break;

Now we have an object holding 10 questions.

We now need to convert these into our QuestionSO scriptable object. We can do this by adding a new method to the scriptable object

using UnityEngine;

[CreateAssetMenu(menuName = "Quiz Question", fileName = "New Question")]
public class QuestionSO : ScriptableObject
{
    /* All the existing code is here */

    public static QuestionSO CreateQuestion(QuestionModel question)
    {
        QuestionSO result = CreateInstance<QuestionSO>();
        result.question = question.question;

        // pick a random index for the correct answer
        result.correctAnswerIndex = Random.Range(0, 4);
        int currentIncorrectIndex = 0;
        for (int i = 0; i < 4; i++)
        {
            if (i == result.correctAnswerIndex)
            {
                result.answers[i] = question.correct_answer;
            }
            else
            {
                result.answers[i] = question.incorrect_answers[currentIncorrectIndex++];
            }
        }

        return result;
    }
}

This code takes in one of the API questions and ā€˜translatesā€™ it into our format. Note that I do not do any safety checks and assume that the API has returned 4 possible answers.
Letā€™s translate our questions

            case UnityWebRequest.Result.Success:
                string jsonString = request.downloadHandler.text;
                Debug.Log(jsonString);
                QuestionsModel questions = JsonUtility.FromJson<QuestionsModel>(jsonString);
                List<QuestionSO> questionList = new List<QuestionSO>();
                foreach(QuestionModel question in questions)
                {
                    questionList.Add(QuestionSO.CreateQuestion(question));
                }
                break;

Now, all we need to do is get these into the Quiz.cs (I have left out all the course code)

public class Quiz : MonoBehaviour
{
    public void SetQuestions(List<QuestionSO> questions)
    {
        this.questions = questions;
    }
}

back to our loader

            case UnityWebRequest.Result.Success:
                string jsonString = request.downloadHandler.text;
                Debug.Log(jsonString);
                QuestionsModel questions = JsonUtility.FromJson<QuestionsModel>(jsonString);
                List<QuestionSO> questionList = new List<QuestionSO>();
                foreach(QuestionModel question in questions)
                {
                    questionList.Add(QuestionSO.CreateQuestion(question));
                }
                GetComponent<Quiz>().SetQuestions(questionList);
                break;

Note the GetComponent here. This means that our QuestionManager component goes on the same game object as the Quiz component. I have not enforced this (because I wrote all this on the fly and only though about it now)

Also, the quiz may start before the API has returned questions. We need to cater for that. I will see if I can throw a quizmaster together from the repo and test all this

2 Likes

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms