Unityで4択クイズを作成します。
4択クイズは、ゲームやクイズアプリでよく使用されています。
ゲームの中でも比較的簡単に作れるので、ミニゲームとして搭載することでバラエティーに富んだ作品にすることができます。
本記事では、4択クイズの作り方を9ステップで紹介します。
手順通りに作成を進めると、Unity初心者の方でも完成できます。
- Step1画面とUIの作成
- Step2解答を選択した時に正解・不正解のアニメーション用意
- Step3解答を選択した時に正解・不正解の効果音を用意
- Step4問題と4つの選択肢の用意
- Step5画面サイズ変更、UIやアニメーション設置
- Step6選択肢をランダムに表示
- Step7出題する問題をランダム
- Step8制限時間の設置
- Step910問連続で出題して、終了後にリザルト画面を表示
画面の作成
初めに画面を作成していきます。
しかし、いきなりUnityで作成してはいけません。
ゲームをはじめプロジェクトには企画書が欠かせません。
企画書は、新商品やサービスの案を社員に共有するために使います。
個人で開発する場合にも企画書を作るべきです。
要するに、ボタンやテキスト、画像などのUIの配置を下書きします。
画面の形成をすることで、ゲームの指針や機能が思いつくことがあります。
UIデザインツールの使用
Webページを作るときにUIデザインツールを使いますが、Unityの画面作成にも役立ちます。
有名なソフトウェアは、「Adobe XD」になります。
Adobe製のツールなので、ネット上に情報が多くあります。
さらに、無料で使用できる「Figma」もおすすめです。
無料で使用上に、操作もしやすく機能も豊富です。
本記事ではFigmaを使用して、画面をデザインしていきます。
PCの場合は、全画面である1920×1080(フルHD)にします。
画像では緑色の四角です。
問題を表示する枠、4つの選択肢、タイトル画面にシーン遷移(切り替え)するボタンを配置します。
あくまで下書きなので、画面からUIがはみ出ないか確認だけで正確に作る必要はありません。
大体の配置が決定した後、Unity内で使用するUIを作成します。
以上までがUIデザインツールの利用です。
正解・不正解のアニメーション用意
ユーザーが問題の解答を選択肢から選んで、正解・不正解の表現を追加します。
このとき、アニメーション表現を導入する方が、ゲームの質が高まります。
アニメーションを作ったことがない場合には、動画を使用することをおすすめします。
動画編集ソフトは多数ありますが、本製作では「AvUtil」を使用しています。
完成した正解・不正解のアニメーションは、Gif画像になります。
出力する時はグリーンバックにした方が、扱いやすいです。
アニメーションの効果音
アニメーションの効果音を用意します。
商用利用可能ですが効果音は著作物なので、2次配布は禁止です。
自分が必要とする効果音がない場合は、検索エンジンで「剣振る音 無料」のように調べてください。
ダウンロードする際は、使用範囲・利用規約の確認をして下さい。
問題と4つの選択肢の用意
次に四択クイズに使用する問題と選択肢を用意します。
Unityでは、csvファイルを読み込むことができるので、csvにデータをまとめていきます。
csvファイルは、エクセルやGoogleスプレッドシートなどで作成することができます。
セルのA列に問題、B列に正解肢、C~E列に不正解肢を入れます。
そして、行ごとに問題を出題します。
画面サイズ変更、UIやアニメーション設置
以上までが、本製作で使用するゲームの素材になります。
Unityでプロジェクトを立ち上げてください。
起動後、画面のサイズを変更します。
Gameビュータブを選択し、「Free Aspect」から「Full HD」に変更してください。
問題表示
次に、問題を表示するボードを作成します。
問題文だけでも成立しますが、制限時間や現在の正答率を表示させます。
HierarchyウィンドウからUI>Imageを選択し、
InspectorウィンドウのImage>Source Imageに表示したい画像をアタッチしてください。
もしも項目がない場合は、Add ComponentからImageを追加してください。
次にテキストを用いて問題文や正解数、制限時間を表示します。
同様にUI>Text-TextMeshPro、あるいはUI>Legacy>Textからテキストを選択してください。
Unityに慣れていない場合は、後者のTextがおすすめです。
Text-TextMeshProは、日本語表示する設定をする必要がありますが、自動で折り返しができます。
その他にも多くの機能があるので、Text-TextMeshProの使用をおすすめします。
文字数超過で枠からはみ出る恐れがあるので、想定できる最大の文字数で表示させます。
要するに、第1問ではなく、第10問のように2桁においてください。
次に制限時間のゲージをUI>Sliderから追加してください。
制限時間のゲージは、緑が赤に変わっていく動作をします。
要するに、緑で満たされているゲージが減少して、赤の背景が出現させます。
このスライダーは制限時間のゲージとして扱うので、「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のチェックを外してください。
グリーンバックの取り除くには、クロマキー操作が必要です。
その他素材を表示
ゲームの機能とは直接関わらないですが、設置した方がゲームの操作性や華やかさを表現できます。
立ち絵は、正解・不正解によって表情が変化するようにします。
タイトルは、ボタンで設置して押すと、タイトル画面に戻ります。
本製作では、立ち絵に「イラストや」を使用しています。
UdemyでUnityを学習
Udemyは、オンデマンド式の学習講座です。
趣味から実務まで使えるおすすめの講座を紹介します。
- 【Unity C# ゲーム開発超入門】7つのミニゲームを作っておぼえる!UnityとC#講座
Unityで頻出の機能を実際のゲーム作りで学習。
何か作りたい、今後の開発で役立てたい初心者におすすめ
- 【Unity C# ゲーム開発初心者レベルアップ】7つのトランプゲームを作っておぼえる!UnityとC#講座
トランプやボードゲーム作りに特化している講座。
テーブルゲームを作りたい人におすすめ。
- 【全行程を網羅!最初に学びたい総合学習】Unityワールド制作講座
Unityワールド制作の全工程を学習できる講座。
RPGを作りたい人におすすめ。
- Unity ゲーム開発:インディーゲームクリエイターが教える C#の基礎からゲームリリースまで【スタジオしまづ】
C#の文法やApp StoreとGoogle Playへゲームをリリース方法を解説。
ゲームを出品したい人におすすめ。
ソースコード
ここまで、ゲームの素材と配置を行ってきました。
ここからは、ソースコードも用いて、プログラミングをしていきます。
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スプレッドシートを使った作成から、導入と処理の手順をまとめています。
結果発表の画面は、最終問題が終了後あるいは10問目解答した時に表示します。
それまでは、表示しないようにします。
同様に、正解・不正解のアニメーションはユーザーが解答するまでは、非表示にしておきます。
オブジェクトの非表示は、「(オブジェクト名).SetActive(false)」を使います。
ビデオをクロマキーしてグリーンバックを外したアニメーションをゲームオブジェクトにします。
配列に格納して、ループしないように処理します。
問題を表示する関数(メソッド)を加えます。
Updateメソッド
void Update()での処理の一覧は以下の通りです。
- 時間制御
- 文字の表示
制限時間は常に更新するので、Update関数で管理します。
現在の時間に応じて、その割合からゲージを緑から赤に変えます。
制限時間を超過した場合は、不正解の処理をします。
次の問題では時間をリセットしなければいけないので、タイマーを0秒にします。
コールチン
正解・不正解のアニメーションが流れますが、次の問題がすぐに始まってしまいます。
指定した秒数の間に処理するときや非同期の処理を作成することをコールチンといいます。
1.5秒間アニメーションを表示して、次の問題に移動しています。
問題の表示
ShowNextQuestion()での、処理一覧は以下の通りです。
- 立ち絵・問題表示
- 時間・BGMの停止
- リザルト画面表示
次に、Quiz()での、処理一覧は以下の通りです。
- 出題する問題の表示
- イベントリスナーの追加
- 選択肢・問題シャッフル
選択肢・問題のリストをシャッフルします。
int型の変数で、問題とそれぞれの選択肢ボタンに文字を表示します。
押したボタンの列の値を引数とします。
ボタン判定
押したボタンの引数から、判定をします。
正解はB列に並んでいるので、1を継承した場合に正解になります。
正解・不正解の処理をして、コールチンします。
正解・不正解処理
AnswerCheck()での、処理一覧は以下の通りです。
- 正解処理
- 不正解処理
- 正解割合
正解・不正解いずれも、アニメーションと立ち絵表情、○×を表示します。
さらに、正解の場合は正解数を加算して、正答率を表示します。
Scene遷移
リザルト画面では、パネルを使用して表示していますが、タイトル画面ではシーンを変えています。
シーン変更は、タイトルとゲーム画面を分けて開発しやすくなります。
しかし、変数は初期化されるので、変数を静的にして引き継ぐ必要があります。
Fisher-Yatesシャッフル
フィッシャー–イェーツのシャッフルは、配列やリストからランダムな順列を生成するアルゴリズムです。
Unityだけではなく、プログラミング全体でシャッフルを扱う場合に使用できて、汎用性が高いです。
Unityでのシャッフルは、その他ありますので自分が扱いやすいものを使ってください。
実演
スクリプトをHierarchyウィンドウにアタッチをして、
それぞれ対応するゲームオブジェクトをアタッチしてください。
時間超過
制限時間内に回答しなかった場合は、強制的に不正解になります。
正解・不正解
回答した場合、正解・不正解で顔表情とエフェクトを入れます。
まとめ
4択クイズは、基本的なUnityを機能を複合することで作成できるので、
Unityを理解する課題としてとても適しています。
以下の応用機能は、執筆が完了次第投稿をしていきます。
- Item1セーブ・ロード搭載
- Item2一度正解した問題は出題しない(リセット可能)
- Item3BGM・SE(効果音)の音量設定
- Item4残りの問題数