クイズゲームを作ろう!
今回は、3択クイズゲームを作っていきます!
目次
ステップ① スプレッドシート(CSVスクリプト)でクイズの作成
ステップ② クイズ用のクラスの作成
ステップ③ クイズの表示
ステップ④ 当たり判定
ステップ⑤ 正誤判定
ステップ⑥ 選択肢をランダムに
ステップ⑦ 複数問題の出題
おまけ 複数ステージの実装
ステップ① スプレッドシートでクイズを作成しよう
CSVファイルを用意しよう!
まず、Googleドライブから新しいスプレッドシートを作成します。
いちばん左のセルから順に問題文、正答、誤答1、誤答2を書き込んでいきましょう。
最後「;(セミコロン)」を入力するのを忘れずに!
保存する時は、【ファイル】→【形式を指定してダウンロード】→【カンマ区切りの値
(.csv、現在のシート)】でCSVファイルとして自分のPCに保存します。
その際、ファイルの名前を「QuizForStage1」としておきましょう。
UnityプロジェクトにCSVファイルを入れよう!
Unityプロジェクトに戻って、Projectビューの[Create]から新しいフォルダを作成し、
「Resources」という名前にします。
さらに、Resourcesフォルダの中に「CSV」という名前のフォルダを作成し、自分のPC
のファイル/ファインダーから保存したCSVファイルをぽいっとドラッグ&ドロップしま
す。
これで、作問の準備は完了です!
ステップ② クイズ用のクラスを作ろう!
クイズを入力するためのファイルはできたので、次はUnity上でCSVファイルのデータを使えるようにする準備をしていきます。
Unityプロジェクトを開いて、Projectビューの[Create]から新しいスクリプトを作ります。
スクリプトの名前を「QuizManager」にし、以下のようにコードを書いていきます
これで、クイズクラスの完成です。
ステップ③ クイズを表示しよう!
クイズの準備が一通りできました!
今度はUnityでクイズを1問だけ表示させてみましょう。
まず、Unityプロジェクトを開いて以下3種類を↓の画像のように配置していきましょう。
・問題文を表示するためのテキスト
・選択肢を表示するためのテキスト ×3つ
・選択肢ボタンがわりになるキューブ ×3つ
(キューブは、上から名前を「AnswerWallTop」、「AnswerWallMiddle」、「AnswerWallBottom」としておきます。)
テキスト・オブジェクトの配置が終わったら、次は「CsvManager」というスクリプトを用意するのですが、内容はコピペでOKです(注1)。
Projectビューの中にドラッグ&ドロップで入れておきます。
次に、新しいスクリプトを作成し、名前を「GameManagerScript」にします。
スクリプトを開いて↓のようにコードを書いていきます。
最後にスクリプトで宣言した変数とテキストとを紐付けし、実行してクイズが表示されればOKです。
ステップ④ 当たり判定をしよう!
今回は、”Ray”というコードを使って、タップしたキューブに当たり判定を設定していきます。
まず、「AspectRatioManager」という名前のスクリプトを作り内容をコピぺして(注2)、Projectビューの中にドラッグ&ドロップで入れておきます。
Rayはオブジェクトのタグで当たりを検知するので、選択肢のキューブ3つに「Answer」という名前のタグを作り、タグ付けします。
次に、GameManagerScriptのvoid Update関数内に当たり判定のコードを書いていきます↓
Unityプロジェクトに戻り、実行してみましょう!
ステップ⑤ 正誤判定をしよう!
押した選択肢が反応してくれるようになりました。
今、AnswerWallTopに正答が反映されるようになっています。
そこで、今度は上記の選択肢をタッチすると「正解」とコンソールに表示させてみましょう。
正誤判定をするために、以下のようにコードを追記していきます。
・「answerString」というstring型の変数を宣言(void Start関数の前)
・65行目のanswerStringに正答を保存
・75〜81行目の"AnswerSelected"関数
最後に、各選択肢にRayが当たった時の処理のところで、AnswerSelected関数を呼び出します。
ステップ⑥ 選択肢をランダムにしよう!
今の状態では、一番上の選択肢が正解になってしまっています。今度は、正答をランダムないちに表示する処理を行います。
はじめに、選択肢の表示をランダムにするための「ListExtension」スクリプトを作成し(注3)、UnityプロジェクトのProjectビューに入れておきましょう。
次に、GameMnager上で選択肢テキストを以下のようにシャッフルします
これで選択肢のテキストをランダムに表示できます。
ステップ⑦ 複数の問題を出題しよう!
最後に正誤判定をした後に、次の問題をランダムに出題する、という実装を行います。
GameMangerScriptを開いて、以下のようにコードを加筆・修正して行ってください。
コードの変更については、以下の4点を確認しましょう。
・CreateQuestion関数で、quizListがfor文になっているか
・「currentQuizIndex」変数が宣言されているか
・ShowQuestion関数の、quizList[0]がquizList[i]に変わっているか
・currentQuizIndexのランダム設定ができているか・ShowQuestion関数の引数がcurrentQuizIndexになっているか
これで、クイズゲームの基本システムができました!!
おまけ 複数のステージを実装しよう!
おまけですが、複数ステージを作る時、ステージの数だけシーンを作る必要はありません。
ステップ①を参考に、もう一つのステージ用のCSVファイルを作成し、名前を「QuizForStage2」として作成します。
次に、新しくProjectビューの[Create]からステージ選択用のシーンを作ります。
また、新しいステージにボタンを2つ追加し、片方を押下するとCSVファイルのQuizForStage1が読み込まれ、もう片方を押下するとQuizForStage2が読み込まれるようにしていきます。
シーンができたら、今度は新しいスクリプトを作成し、「MenuManagerScript」と名前をつけ、以下のようにボタンをクリックした時の関数を2つ作ります。
ボタンと関数を紐付けられたら、GameManagerScriptに戻り、以下のようにコードを書き換えます。
以上、複数ステージの実装でした!
コピペファイル一覧
注1. CsvManager
using UnityEngine;
using System.Collections;
using System.IO;
using System.Collections.Generic;
using System.Text;
public class CsvManager
{
///
/// Resourcesフォルダ内のcsvファイルを読み込み、string型2次元配列で返す。
/// example) CsvManager.ReadTextFile("Commands/BasicCommands.csv")
///
public static string ReadTextFile (string dataPath)
{
string data;
string textData = OpenTextFile (GetPath () + dataPath);
if (textData != "ERROR") {
data = textData;
} else {
data = "";
}
if (string.IsNullOrEmpty (data)) {
return null;
}
return data;
}
public static string[,] ReadCsvFile (string dataPath)
{
string data;
string textData = OpenTextFile (GetPath () + dataPath);
if (textData != "ERROR") {
data = textData;
} else {
TextAsset textAsset = (TextAsset)Resources.Load (dataPath.Split ('.') [0]);
data = textAsset.ToString ();
}
if (data == null) {
return null;
}
string[] rows = data.Replace ("\ r\n", "\ n").Split ('\ n');
int dataRows = rows.Length;
int dataCols = rows [0].Split ("," [0]).Length;
string[,] csvData = new string[dataRows, dataCols];
for (int i = 0; i < csvData.GetLength (0); i++) {
for (int j = 0; j < csvData.GetLength (1); j++) {
string[] value = rows [i].Split ("," [0]);
csvData [i, j] = value [j];
}
}
return csvData;
}
///
/// Resourcesフォルダ内に、ファイルを書き込む。
/// 例) csvManager.WriteData("data.csv", dataArray);
///
public static void WriteData (string dataPath, string[,] newData)
{
Debug.Log("[CsvManager.WriteData(string dataPath, string[,] newData)] called");
string stringData = "";
for (int i = 0; i < newData.GetLength (0); i++) {
for (int j = 0; j < newData.GetLength (1); j++) {
if (j < newData.GetLength (1) - 1) {
stringData += newData [i, j] + ",";
} else if (j == newData.GetLength (1) - 1 && i < newData.GetLength (0) - 1) {
stringData += newData [i, j] + "\ n";
} else {
stringData += newData [i, j];
}
}
}
string[] directries = dataPath.Split ('/');
for (int i = 0; i < directries.Length-1; i++) {
string tmpPath = "";
for (int j = 0; j < i + 1; j++) {
tmpPath += directries [j] + "/";
}
tmpPath = tmpPath.Remove (tmpPath.Length - 1, 1);
if (!Directory.Exists (GetPath () + tmpPath)) {
Directory.CreateDirectory (GetPath () + tmpPath);
}
}
string existingString = OpenTextFile (GetPath () + dataPath);
FileStream fs;
if (existingString == "ERROR") {
fs = new FileStream (GetPath () + dataPath, FileMode.CreateNew);
} else {
fs = new FileStream (GetPath () + dataPath, FileMode.Create);
}
StreamWriter sw = new StreamWriter (fs);
sw.Write (stringData);
sw.Flush ();
sw.Close ();
}
public static void WriteData (string dataPath, List newData)
{
Debug.Log("[CsvManager.WriteData(string dataPath, List newData)] called");
int maxLength = 1;
for (int i = 0; i < newData.Count; i++) {
if (maxLength < newData [i].Length) {
maxLength = newData [i].Length;
}
}
string stringData = "";
for (int i = 0; i < newData.Count; i++) {
for (int j = 0; j < maxLength; j++) {
if (j < maxLength - 1) {
stringData += newData [i] [j] + ",";
} else if (j == maxLength - 1 && i < newData.Count - 1) {
stringData += newData [i] [j] + "\ n";
} else {
stringData += newData [i] [j];
}
}
}
string[] directries = dataPath.Split ('/');
for (int i = 0; i < directries.Length-1; i++) {
string tmpPath = "";
for (int j = 0; j < i + 1; j++) {
tmpPath += directries [j] + "/";
}
tmpPath = tmpPath.Remove (tmpPath.Length - 1, 1);
if (!Directory.Exists (GetPath () + tmpPath)) {
Directory.CreateDirectory (GetPath () + tmpPath);
}
}
string existingString = OpenTextFile (GetPath () + dataPath);
FileStream fs;
if (existingString == "ERROR") {
fs = new FileStream (GetPath () + dataPath, FileMode.CreateNew);
} else {
fs = new FileStream (GetPath () + dataPath, FileMode.Create);
}
StreamWriter sw = new StreamWriter (fs);
sw.Write (stringData);
sw.Flush ();
sw.Close ();
}
public static void WriteData (string dataPath, string data)
{
Debug.Log("[CsvManager.WriteData(string dataPath, string data)] called");
string[] directries = dataPath.Split ('/');
for (int i = 0; i < directries.Length-1; i++) {
string tmpPath = "";
for (int j = 0; j < i + 1; j++) {
tmpPath += directries [j] + "/";
}
tmpPath = tmpPath.Remove (tmpPath.Length - 1, 1);
if (!Directory.Exists (GetPath () + tmpPath)) {
Directory.CreateDirectory (GetPath () + tmpPath);
}
}
string existingString = OpenTextFile (GetPath () + dataPath);
FileStream fs;
if (existingString == "ERROR") {
fs = new FileStream (GetPath () + dataPath, FileMode.CreateNew);
} else {
fs = new FileStream (GetPath () + dataPath, FileMode.Create);
}
StreamWriter sw = new StreamWriter (fs);
sw.Write (data);
sw.Flush ();
sw.Close ();
}
public static string GetPath ()
{
#if UNITY_EDITOR
return Application.dataPath + "/Resources/";
#elif UNITY_ANDROID
return Application.persistentDataPath + "/";
#elif UNITY_IPHONE
return Application.persistentDataPath + "/";
#else
return Application.dataPath + "";
#endif
}
public static string OpenTextFile (string _filePath)
{
FileInfo fi = new FileInfo (_filePath);
string returnSt = "";
if (fi.Exists) {
StreamReader sr = new StreamReader (fi.OpenRead (), Encoding.UTF8);
returnSt = sr.ReadToEnd ();
sr.Close ();
} else {
returnSt = "ERROR";
}
return returnSt;
}
}
注2. AspectRatioManager
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class AspectRatioManager : MonoBehaviour {
public float x_aspect = 1242f;
public float y_aspect = 2208f;
public CanvasScaler[] canvasScaler = new CanvasScaler[1];
void Awake()
{
//Cameraのアスペクト比を設定する
Camera camera = GetComponent();
Rect rect = calcAspect(x_aspect, y_aspect);
camera.rect = rect;
//Canvasのアスペクト比を設定する
for (int i = 0; i scale_height)
{
rect.x = 0;
rect.y = (1.0f - scale_height) / 2.0f;
rect.width = 1.0f;
rect.height = scale_height;
}
else
{
float scale_width = 1.0f / scale_height;
rect.x = (1.0f - scale_width) / 2.0f;
rect.y = 0.0f;
rect.width = scale_width;
rect.height = 1.0f;
}
return rect;
}
///
/// Checks the screen ratio. Return 0 when width, 1 when height
///
private int CheckScreenRatio(int i){
if (Screen.width * canvasScaler[i].referenceResolution.y / canvasScaler[i].referenceResolution.x < Screen.height) {
return 0;
} else {
return 1;
}
}
}
注3. ListExtension
// ListExtension.cs
// http://kan-kikuchi.hatenablog.com/entry/ListExtension
//
// Created by kan.kikuchi on 2016.04.29.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
///
/// Listの拡張クラス
///
public static class ListExtension {
//=================================================================================
//重複
//=================================================================================
///
/// 重複しないように追加
///
public static void AddToNotDuplicate(this List list, T t){
if(list.Contains(t)){
return;
}
list.Add (t);
}
///
/// 重複を無くす
///
public static void RemoveDuplicate(this List list){
List newList = new List();
foreach (T item in list) {
newList.AddToNotDuplicate(item);
}
list = newList;
}
//=================================================================================
//並び変え
//=================================================================================
///
/// ランダムに並び替え
///
public static List Shuffle(this List list){
for (int i = 0; i < list.Count; i++) {
T temp = list[i];
int randomIndex = Random.Range(0, list.Count);
list[i] = list[randomIndex];
list[randomIndex] = temp;
}
return list;
}
//=================================================================================
//取得
//=================================================================================
///
/// 指定したNoのものを取得し、リストから消す
///
public static T GetAndRemove(this List list, int targetNo){
if(list.Count <= targetNo || targetNo < 0){
Debug.LogError ("リストの範囲を超えています!(ListCount : " + list.Count + ", No : " + targetNo + ")");
}
T target = list[targetNo];
list.Remove (target);
return target;
}
//=================================================================================
//先入先出
//=================================================================================
///
/// 先頭から取り出し、リストから消す
///
public static T Pop(this List list){
return list.GetAndRemove(list.Count - 1);
}
//=================================================================================
//後入先出
//=================================================================================
///
/// 最後尾から取り出し、リストから消す
///
public static T Dequeue(this List list){
return list.GetAndRemove(0);
}
//=================================================================================
//ランダム取得
//=================================================================================
///
/// ランダムに取得する
///
public static T GetAtRandom(this List list){
if(list.Count == 0){
Debug.LogError ("リストが空です!");
}
return list[Random.Range(0, list.Count)];
}
///
/// ランダムに取得し、リストから消す
///
public static T GetAndRemoveAtRandom(this List list){
T target = list.GetAtRandom ();
list.Remove (target);
return target;
}
}