【Unity】4択クイズの作り方-アセットなしでゼロから作成する

当サイトで紹介する商品・サービス等の外部リンクは、アフィリエイト広告を含む場合があります。
スポンサーリンク
本記事を読むと次の製作ができます

Unityで4択クイズを作成します。


4択クイズは、ゲームやクイズアプリでよく使用されています。

ゲームの中でも比較的簡単に作れるので、ミニゲームとして搭載することでバラエティーに富んだ作品にすることができます。

本記事では、4択クイズの作り方を9ステップで紹介します。
手順通りに作成を進めると、Unity初心者の方でも完成できます。

4択クイズ作成の概要
  • Step1
    画面とUIの作成
  • Step2
    解答を選択した時に正解・不正解のアニメーション用意
  • Step3
    解答を選択した時に正解・不正解の効果音を用意

  • Step4
    問題と4つの選択肢の用意

  • Step5
    画面サイズ変更、UIやアニメーション設置

  • Step6
    選択肢をランダムに表示
  • Step7
    出題する問題をランダム
  • Step8
    制限時間の設置
  • Step9
    10問連続で出題して、終了後にリザルト画面を表示

スポンサーリンク

画面の作成

4択クイズの画面作りーFigmaで構築

初めに画面を作成していきます。
しかし、いきなりUnityで作成してはいけません

ゲームをはじめプロジェクトには企画書が欠かせません
企画書は、新商品やサービスの案を社員に共有するために使います。

個人で開発する場合にも企画書を作るべきです。
要するに、ボタンやテキスト、画像などのUIの配置を下書きします。

画面の形成をすることで、ゲームの指針や機能が思いつくことがあります。

UIデザインツールの使用

Webページを作るときにUIデザインツールを使いますが、Unityの画面作成にも役立ちます。

有名なソフトウェアは、「Adobe XD」になります。
Adobe製のツールなので、ネット上に情報が多くあります。

さらに、無料で使用できる「Figma」もおすすめです。
無料で使用上に、操作もしやすく機能も豊富です。

本記事ではFigmaを使用して、画面をデザインしていきます。

UIデザインツール(Figma)で作成したUnityの画面

PCの場合は、全画面である1920×1080(フルHD)にします。
画像では緑色の四角です。

問題を表示する枠、4つの選択肢、タイトル画面にシーン遷移(切り替え)するボタンを配置します。
あくまで下書きなので、画面からUIがはみ出ないか確認だけで正確に作る必要はありません。

大体の配置が決定した後、Unity内で使用するUIを作成します。

素材づくりー選択肢と問題ボートをゲーム内で表示

以上までがUIデザインツールの利用です。

ゲームの初心者感はUIがダサいことが原因!

UIは、ゲームのクオリティーを左右させる重要な要素です。
しかし、ゲームの世界観と調和させるデザインを創ることは非常に難しいです。

ココナラ は、ゲーム業界でUI制作をしていたプロに直接仕事を依頼することができます。

正解・不正解のアニメーション用意

正解・不正解のアニメーションをAvUtlで用意

ユーザーが問題の解答を選択肢から選んで、正解・不正解の表現を追加します。
このとき、アニメーション表現を導入する方が、ゲームの質が高まります。

アニメーションを作ったことがない場合には、動画を使用することをおすすめします。
動画編集ソフトは多数ありますが、本製作では「AvUtil」を使用しています。

AvUtilでは、動画だけではなくgif画像の作成できます。

完成した正解・不正解のアニメーションは、Gif画像になります。
出力する時はグリーンバックにした方が、扱いやすいです。

アニメーションの効果音

アニメーションの効果音を用意します。

商用利用可能な効果音配布サイト

商用利用可能ですが効果音は著作物なので、2次配布は禁止です。

自分が必要とする効果音がない場合は、検索エンジンで「剣振る音 無料」のように調べてください。
ダウンロードする際は、使用範囲・利用規約の確認をして下さい。

問題と4つの選択肢の用意

csvファイルを使って問題と選択肢を表示

次に四択クイズに使用する問題と選択肢を用意します。
Unityでは、csvファイルを読み込むことができるので、csvにデータをまとめていきます。

csvで問題と選択肢作成

csvファイルは、エクセルやGoogleスプレッドシートなどで作成することができます。
セルのA列に問題、B列に正解肢、C~E列に不正解肢を入れます。
そして、行ごとに問題を出題します。

画面サイズ変更、UIやアニメーション設置

以上までが、本製作で使用するゲームの素材になります。
Unityでプロジェクトを立ち上げてください。

画面サイズの変更やUIの操作を詳細に紹介しています。

起動後、画面のサイズを変更します。
Gameビュータブを選択し、「Free Aspect」から「Full HD」に変更してください。

問題表示

Unity4択クイズの画面様子

次に、問題を表示するボードを作成します。

問題文だけでも成立しますが、制限時間や現在の正答率を表示させます。

表示する情報
  • 問題文
  • 正解数、割合
  • 制限時間(ゲージ付き)

HierarchyウィンドウからUI>Imageを選択し、
InspectorウィンドウのImage>Source Imageに表示したい画像をアタッチしてください。
もしも項目がない場合は、Add ComponentからImageを追加してください。

次にテキストを用いて問題文や正解数、制限時間を表示します。
同様にUI>Text-TextMeshPro、あるいはUI>Legacy>Textからテキストを選択してください。
Unityに慣れていない場合は、後者のTextがおすすめです。

Text-TextMeshProは、日本語表示する設定をする必要がありますが、自動で折り返しができます。
その他にも多くの機能があるので、Text-TextMeshProの使用をおすすめします。

Text-TextMeshProの導入から処理までサンプルコードを交えて紹介しています。

文字数超過で枠からはみ出る恐れがあるので、想定できる最大の文字数で表示させます。
要するに、第1問ではなく、第10問のように2桁においてください。

次に制限時間のゲージをUI>Sliderから追加してください。
制限時間のゲージは、緑が赤に変わっていく動作をします。
要するに、緑で満たされているゲージが減少して、赤の背景が出現させます。

スライダーを使って、HPバーや時間ゲージを作成します。

このスライダーは制限時間のゲージとして扱うので、「Handle Slide Area」は不要です。
Fill Area>Fillから、ImageのColorを緑にしてください。
Backgroundから、ImageのColorを赤にしてください。

ユーザー(プレイヤー)がSliderの操作ができないように「Interactable」のチェックマークを外してください。

以上までが、問題関連の表示です。

選択肢の表示

表示された選択肢を押すと、正解・不正解どちらかのアニメーションが動く。
この処理をスクリプトで行うので、ボタンを使用します。

UI>Button-TextMeshPro、あるいはUI>Legacy>Buttonからボタンを選択してください。
テキストと同様に設定が必要ですが、自動で枠内に収まるように調整されるTextMeshProの使用をお勧めします。

アニメーションの表示

作成した正解・不正解のアニメーションを表示させます。
Video>Video Playerを追加してください。

InspectorウィンドウのVideo Playerに動かしたい動画をVideo Clipにアタッチしてください。
このとき、Loopのチェックを外してください。

グリーンバックの取り除くには、クロマキー操作が必要です。

動画を使ったUnityのアニメーション表現を解説

その他素材を表示

ゲームの機能とは直接関わらないですが、設置した方がゲームの操作性や華やかさを表現できます。

追加する情報
  • 立ち絵
  • タイトル

立ち絵は、正解・不正解によって表情が変化するようにします。
タイトルは、ボタンで設置して押すと、タイトル画面に戻ります。

本製作では、立ち絵に「イラストや」を使用しています。

ソースコード

ここまで、ゲームの素材と配置を行ってきました。

ここからは、ソースコードも用いて、プログラミングをしていきます。

おすすめの副業エージェント
  • レバテックフリーランス
  • 業界トップクラスの案件数と高単価案件。正社員並みの福利厚生と税理士紹介あり。

  • ITプロパートナーズ
  • 週2~・リモートなど柔軟な働き方が可能。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using System.IO;
    using System.Linq;
    using UnityEngine.UI;
    using TMPro;
    using UnityEngine.Video;
    using UnityEngine.SceneManagement;
    
    
    public class choice : MonoBehaviour
    {
        private TextAsset csvFile;  // CSVファイル
        private List<string[]> csvData = new List<string[]>();  // CSVファイルの中身を入れるリスト
        int index; //問題番号
        int correct; //正解数
        bool AnswerFlag;    //解答判定
        float timelimit = 10f; //制限時間
        float nowtimer = 0f;   //経過時間
        public TextMeshProUGUI ProblemLog;  //問題文
        public TextMeshProUGUI ProblemNum;  //問題番号
        public TextMeshProUGUI[] ChoiceLog;    //選択肢文字
        public TextMeshProUGUI[] Judgment; //○×表示
        public TextMeshProUGUI CorrectNum;   //正解数
        public TextMeshProUGUI CorrectRate;  //正答率
        public TextMeshProUGUI time;    //残り時間表示
        public Button[] ChoiceBtns;   //選択肢
        private List<int> numbers = new List<int> { 1, 2, 3, 4 };   //選択肢番号
        private List<int> Problem = new List<int> { }; //未正解問題
        public GameObject[] JudgmentObj;   //正解不正解の表示-オブジェクト
        private VideoPlayer[] videoPlayers; //正解不正解の表示-ビデオ
        public Slider TimeSlider;   //残り時間ゲージ
        public Image Character;      //画像
        public Sprite[] newCharacters;  //格納している画像を変化させる
        public GameObject Result;       //リザルト画面の表示
        public AudioSource BGM;     //BGM
        public AudioSource ClearSound;
        public AudioSource FailSound;
    
        void Start()
        {
            //BGM
            float Score = BGMsys.score;
            BGM.volume = Score;
            ClearSound.volume = Score;
            FailSound.volume = Score;
    
            //CSVロード
            csvFile = Resources.Load("test") as TextAsset;
            StringReader reader = new StringReader(csvFile.text);
            while (reader.Peek() != -1)
            {
                string line = reader.ReadLine();
                csvData.Add(line.Split(','));
            }
    
            //CSVの問題をリストにいれる
            Problem = new List<int>(csvData.Count);
            for (int i = 0; i < csvData.Count; i++)
            {
                Problem.Add(i);
            }
            Shuffle(Problem);  //問題をシャッフル
            Debug.Log("Problem: " + string.Join(", ", Problem.Select(x => x.ToString())));
    
    
            TimeSlider.value = 1f;  //制限時間ゲージ
    
            Result.SetActive(false);    //結果発表を非表示
            JudgmentObj[0].SetActive(false);    //正解アニメーション非表示
            JudgmentObj[1].SetActive(false);    //不正解アニメーション非表示
    
            //ビデオを保存しているオブジェクトをビデオにする
            videoPlayers = new VideoPlayer[JudgmentObj.Length];
            for (int i = 0; i < JudgmentObj.Length; i++)
            {
                videoPlayers[i] = JudgmentObj[i].GetComponentInChildren<VideoPlayer>();
                videoPlayers[i].loopPointReached += OnVideoEnd;  //ループしないように
            }
            // 最初の問題を表示
            ShowNextQuestion();
        }
    
        void Update()
        {
            //時間制御
            nowtimer += Time.deltaTime; // タイマー
            float t = nowtimer / timelimit;// スライダーの値ー正規化
            TimeSlider.value = Mathf.Lerp(1f, 0f, t);
            float TimeLimit = 10f - nowtimer; //残り時間
            TimeLimit = Mathf.Max(TimeLimit, 0f);
    
            string LimitLog = TimeLimit.ToString("F0");
            time.text = LimitLog + "秒";
            time.color = (TimeLimit > 5.5f) ? Color.green : Color.red;// 5.5秒以上は緑、5.5秒未満は赤
    
            // タイムオーバー
            if (nowtimer >= timelimit)
            {
                nowtimer = 0f;     //タイマーを0秒に戻す
                JudgmentObj[1].SetActive(true); //不正解を表示
                videoPlayers[1].Play(); //動画を流す
                AnswerFlag = false;
                AnswerCheck();
                StartCoroutine(NextQuestion());
            }
        }
    
        IEnumerator NextQuestion()
        {
            yield return new WaitForSeconds(1.5f);  // 1.5秒待つ
    
            ShowNextQuestion();
        }
    
        void ShowNextQuestion()
        {
            Character.sprite = newCharacters[0];
            string No = (index + 1).ToString(); //問題番号
            ProblemNum.text = "第" + No + "問";
    
            // 問題と選択肢を表示
            if (Problem.Count > index)
            {
                if (index < 10) //10問連続で出題、0から始めているから未満
                {
                    Quiz();
                }
                else
                {
                    Result.SetActive(true);
                    Time.timeScale = 0;
                    BGM.Stop();
                    Debug.Log("問題終了");
                }
            }
            else if (Problem.Count == index)
            {
    
                Debug.Log("終了!!!!");
                Result.SetActive(true);
                BGM.Stop();
    
    
            }
    
        }
    
        void Quiz()
        {
            for (int i = 0; i < numbers.Count; i++)
            {
                int randomrow = Problem[index];
                int randomcol = numbers[i];
                ProblemLog.text = csvData[randomrow][0];
                ChoiceLog[i].text = csvData[randomrow][randomcol];
    
                // すでにイベントが追加されている場合は一旦解除してから追加
                ChoiceBtns[i].onClick.RemoveAllListeners();
                ChoiceBtns[i].onClick.AddListener(() => OnButtonClick(randomcol));
            }
    
            // タイマーをリセット
            ResetTimer();
            Shuffle(numbers);
        }
    
    
        void OnButtonClick(int selectedAnswer)
        {
            if (selectedAnswer == 1)
            {
                AnswerFlag = true;
            }
            else
            {
                AnswerFlag = false;
            }
    
            AnswerCheck();
            // 次の問題を表示
            StartCoroutine(NextQuestion());
        }
    
    
        void AnswerCheck()
        {
            ResetTimer();
            if (AnswerFlag == true)//正解
            {
                JudgmentObj[0].SetActive(true);
                videoPlayers[0].Play();
                Character.sprite = newCharacters[1];
                Judgment[index].color = Color.red;
                Judgment[index].text = ("〇");
                correct += 1;   //正解数
    
            }
            else if (AnswerFlag == false)//不正解
            {
                JudgmentObj[1].SetActive(true);
                videoPlayers[1].Play();
                Character.sprite = newCharacters[2];
                Judgment[index].color = Color.blue;
                Judgment[index].text = ("×");
            }
            index += 1;//次の問題へ
    
            //正解の割合
            float Rate = correct / (float)index * 100;
            string Ratenum = Rate.ToString("F0");
            string correctNum = correct.ToString();
            CorrectRate.text = Ratenum;
            CorrectNum.text = correctNum;
        }
    
        //再生終了後、正解画面非表示
        void OnVideoEnd(VideoPlayer vp)
        {
            JudgmentObj[0].SetActive(false);
            JudgmentObj[1].SetActive(false);
        }
    
    
        void ResetTimer()
        {
            BGM.time = 0;
            nowtimer = 0f;
            Time.timeScale = 1.0f;
        }
    
    
        public void Back()
        {
            SceneManager.LoadScene("Title");
        }
        public void RetryClick()
        {
            SceneManager.LoadScene("Play");
        }
    
        // Fisher-Yatesシャッフル
        void Shuffle<T>(List<T> list)
        {
            int n = list.Count;
            for (int i = n - 1; i > 0; i--)
            {
                int j = Random.Range(0, i + 1);
                T temp = list[i];
                list[i] = list[j];
                list[j] = temp;
            }
        }
    }

    解説

    初めに名前空間について説明します。

    System.Collections」・「System.Collections.Generic」は、非同期処理を行うIEnumeratorとリストを扱うために必要です。
    「UnityEngine」は、Unityでゲームを作るために必要です。
    System.IO」は、ファイルやデータストリームの読み書きするもので、csvファイルを扱うので必要です。
    System.Linq」は、配列・リストを処理します。
    UnityEngine.UI」は、ボタンや文字などのUIをコードに書くときに使用します。
    TMPro」UI文字のTextMeshProのコードを書くときに必要です。
    UnityEngine.Video」は、ビデオを再生するときに使用します。
    UnityEngine.SceneManagement」はシーン移動するときに使用します。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using System.IO;
    using System.Linq;
    using UnityEngine.UI;
    using TMPro;
    using UnityEngine.Video;
    using UnityEngine.SceneManagement;

    Startメソッド

    void Start()での処理の一覧は以下の通りです。

    • BGM
    • CSVファイル
    • 制限時間スライダーを最大値
    • リザルト画面、正解・不正解アニメーション
    • ビデオをオブジェクトに格納
    • 問題を表示

    BGMの設定は、ユーザーがタイトル画面のゲージで調整し、プレイ画面でその音量を保持しています。

    4択クイズの問題は、csvファイルで扱っています。ExcelやGoogleスプレッドシートを使った作成から、導入と処理の手順をまとめています。

    CSVファイルの読み込みから画面表示までの方法を解説しています。

    結果発表の画面は、最終問題が終了後あるいは10問目解答した時に表示します。
    それまでは、表示しないようにします。
    同様に、正解・不正解のアニメーションはユーザーが解答するまでは、非表示にしておきます。

    オブジェクトの非表示は、「(オブジェクト名).SetActive(false)」を使います。

    ビデオをクロマキーしてグリーンバックを外したアニメーションをゲームオブジェクトにします。
    配列に格納して、ループしないように処理します。

    問題を表示する関数(メソッド)を加えます。

    Updateメソッド

    void Update()での処理の一覧は以下の通りです。

    • 時間制御
    • 文字の表示

    制限時間は常に更新するので、Update関数で管理します。
    現在の時間に応じて、その割合からゲージを緑から赤に変えます。

    制限時間を超過した場合は、不正解の処理をします。
    次の問題では時間をリセットしなければいけないので、タイマーを0秒にします。

    コールチン

    正解・不正解のアニメーションが流れますが、次の問題がすぐに始まってしまいます。
    指定した秒数の間に処理するときや非同期の処理を作成することをコールチンといいます。

    特定の時間停止して次の処理を行う方法を紹介しています。

    1.5秒間アニメーションを表示して、次の問題に移動しています。

    問題の表示

    ShowNextQuestion()での、処理一覧は以下の通りです。

    • 立ち絵・問題表示
    • 時間・BGMの停止
    • リザルト画面表示

    次に、Quiz()での、処理一覧は以下の通りです。

    • 出題する問題の表示
    • イベントリスナーの追加
    • 選択肢・問題シャッフル

    選択肢・問題のリストをシャッフルします。
    int型の変数で、問題とそれぞれの選択肢ボタンに文字を表示します。

    押したボタンの列の値を引数とします。

    ボタン判定

    押したボタンの引数から、判定をします。
    正解はB列に並んでいるので、1を継承した場合に正解になります。

    正解・不正解の処理をして、コールチンします。

    正解・不正解処理

    AnswerCheck()での、処理一覧は以下の通りです。

    • 正解処理
    • 不正解処理
    • 正解割合

    正解・不正解いずれも、アニメーションと立ち絵表情、○×を表示します。
    さらに、正解の場合は正解数を加算して、正答率を表示します。

    Scene遷移

    リザルト画面では、パネルを使用して表示していますが、タイトル画面ではシーンを変えています。

    シーン変更は、タイトルとゲーム画面を分けて開発しやすくなります。
    しかし、変数は初期化されるので、変数を静的にして引き継ぐ必要があります。

    シーン切り替えの方法を紹介しています。

    Fisher-Yatesシャッフル

    フィッシャー–イェーツのシャッフルは、配列やリストからランダムな順列を生成するアルゴリズムです。
    Unityだけではなく、プログラミング全体でシャッフルを扱う場合に使用できて、汎用性が高いです。

    Unityでのシャッフルは、その他ありますので自分が扱いやすいものを使ってください。

    Unityで数値、確率、配列のランダムを解説しています。

    実演

    スクリプトをHierarchyウィンドウにアタッチをして、
    それぞれ対応するゲームオブジェクトをアタッチしてください。

    時間超過

    制限時間内に回答しなかった場合は、強制的に不正解になります。

    正解・不正解

    回答した場合、正解・不正解で顔表情とエフェクトを入れます。

    まとめ

    4択クイズは、基本的なUnityを機能を複合することで作成できるので、
    Unityを理解する課題としてとても適しています。

    以下の応用機能は、執筆が完了次第投稿をしていきます。

    応用機能
    • Item1
      セーブ・ロード搭載

    • Item2
      一度正解した問題は出題しない(リセット可能)
    • Item3
      BGM・SE(効果音)の音量設定
    • Item4
      残りの問題数

    この記事を書いた人

    プロフィール

    アリッシア

                     

    大学4年間で何か胸を張れるスキルを身に着けたくて当サイト運営を始めました。
    現在、大学院に進学するか就職するか迷いながら勉強しています。
    詳しいプロフィールはこちら

    Contact icon

    contact

    X icon

    X

    Instagram icon

    Instagram

    Note icon

    Note

    スポンサーリンク
    Unity
    フォローする
    タイトルとURLをコピーしました