記事の紹介
初めまして、私は、独学でAI開発を学んでいる者です。今回記事にするのはC#とUnityを使いクイズゲームを作っていく内容になります。
参考程度に記事を読んでいただければと思っています。
前提
Unity、C#歴6年目のUnity初心者
私が好きなことについての、QuizゲームをUnityで作りたくて実装してみましたのでそのお話です。
今回使用するのは、chatGPT(無料版)、Unityバージョン2022.3x、C#
ハードル1
ファイルは1種類、プログラムは3種類実装を行いました。csvから作成していきます。
Unityを立ち上げ、からのフォルダーを作成します。名前はResourcesにします。
その中にcsvファイルの作成を行います。
Question,answer1,answer2,answer3,answer4,answer
トマトの栽培で「わき芽」を取る主な理由は?,A. 実を小さくする,B. 株を弱らせる,C. 栄養を実に集中させる,D. 葉を黄色にする,3
トマトが好む環境は?,A. 日陰で湿気が多い,B. 日当たりが良く風通しが良い,C. 水の中,D. 寒い場所,2
トマトの支柱を立てる理由は?,A. 虫を増やすため,B. 実を土につけないため,C. 葉を切るため,D. 色を変えるため,2
トマトの花が咲いた後にできるものは?,A. 根,B. 茎,C. 実,D. 種だけ,3
トマトの水やりで正しいのは?,A. 毎日ずっと水浸し,B. 土が乾いたら与える,C. 一切水を与えない,D. 葉だけにかける,2
キュウリがよく伸びる部分は?,A. つる,B. 根,C. 花粉,D. 種,1
キュウリ栽培でよく使うものは?,A. ネット,B. 水槽,C. 毛布,D. 石,1
キュウリの実が大きくなりすぎるとどうなる?,A. 黄色くなる,B. 甘くなる,C. 花になる,D. 種が消える,1
キュウリの花の色は一般的に?,A. 赤,B. 青,C. 黄色,D. 黒,3
キュウリの収穫で適切なのは?,A. 小まめに収穫する,B. 全部巨大化させる,C. 葉を全部切る,D. 水を止める,1
ナスの花の色として多いのは?,A. 紫,B. 緑,C. 白,D. 黒,1
ナス栽培で重要なことは?,A. 水切れを防ぐ,B. 氷を置く,C. 日陰だけに置く,D. 葉を全部切る,1
ナスの実を長く収穫するために行うことは?,A. 更新剪定,B. 全部抜く,C. 花を切る,D. 根を乾燥させる,1
ナスが育ちやすい季節は?,A. 夏,B. 冬,C. 雪の日,D. 秋の終わり,1
ナスの実の特徴として多いのは?,A. 紫色,B. 透明,C. 金色,D. 白黒模様,1
とうもろこしの先に出るひげは何と関係がある?,A. 根,B. 実,C. 葉,D. 茎,2
とうもろこしは何によって受粉しやすい?,A. 風,B. 水,C. 石,D. 火,1
とうもろこしを複数列で植える理由は?,A. 受粉しやすくするため,B. 色を変えるため,C. 虫を増やすため,D. 背を低くするため,1
とうもろこしの雄花はどこにつく?,A. 根元,B. 茎の途中,C. 一番上,D. 土の中,3
甘いとうもろこしの特徴は?,A. 収穫後すぐ食べると甘い,B. 冬に収穫する,C. 水の中で育つ,D. 青くなる,1
フォーマットを決めるとスムーズに対応できます。今回は、以下のようにしてみました。
Question,answer1,answer2,answer3,answer4,answer
質問文,答え1,答え2,答え3,答え4,正解
csvの文字化けを直す方法はUTF-8にして保存すると出来るそうなのですが週末2日間でそこまで調べて実装することは難しいので今回は割愛します。
ハードル2
次に、決めたフォーマットをchatGPTに流し込みます。ある程度仕様を決めておいてその最小限の要件を達成するプログラムになるようにやさしい文章で丁寧にお願いをしていきます。
フォーマット
今回のクイズゲームの場合、CSVから読み込むなら、
問題文
選択肢1
選択肢2
選択肢3
選択肢4
正解番号
を受け取る形にすると扱いやすいです。
こちらのサイトを参考にcsvファイルの内容を読み込みます。
参考サイト(https://qiita.com/Kirikabu_ueda/items/23b4827abf5b8b6251bc)
先にCSVSerializer.csを作成します。
Unityを立ち上げ、からのフォルダーを作成します。名前はScriptsにします。
その中にC#スクリプトの作成を行います。
using System;
using System.Collections.Generic;
using UnityEngine;
public static class CSVSerializer
{
public static Question[] Deserialize<Question>(string csvText)
{
List<global::Question> questionList = new List<global::Question>();
// 改行で分割
string[] lines = csvText.Split('\n');
// 1行目はヘッダーなのでスキップ
for (int i = 1; i < lines.Length; i++)
{
// 空行スキップ
if (string.IsNullOrWhiteSpace(lines[i]))
continue;
// カンマ区切り
string[] values = lines[i].Split(',');
// 問題文
string questionText = values[0];
// 選択肢
string[] options = new string[]
{
values[1],
values[2],
values[3],
values[4]
};
// Question生成
global::Question question =
new global::Question(questionText, options);
questionList.Add(question);
}
return questionList.ToArray() as Question[];
}
}
こちらのプログラムではcsvのテキストデータをQuestionというクラスの変数に流し込む中身の処理を担当しています。,で区切られたフォーマットを読み込んでリストに追加する感じです。
ハードル3
Scripts内にC#スクリプトの作成を行います。QuestionクラスとTestQuizManagerクラスの実装を行います。
-要件
- 質問文を表示する
- 正解したら正解の表示、不正解だったら不正解の表示をする
- すべての問題が終了したらすべての問題が終了しましたと表示する
- 選択肢のボタン配列にボタンが押された時の正誤判定処理を設定し、テキストを選択肢にして表示する
☆クイズの問題を格納する配列にcsvデータを流し込む☆ - 出題する問題をランダムにする
using UnityEngine;
using UnityEngine.UI;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
[System.Serializable]
public class Question
{
public string questionText;
public string[] options;
public int correctAnswerIndex; // 正解は取得した配列内容から判定できる
public Question(string question, string[] choices)
{
questionText = question;
options = choices;
}
}
public class TestQuizManager : MonoBehaviour
{
// 正解・不正解メッセージを定数として定義
private const string CORRECT_MESSAGE = "正解!";
private const string INCORRECT_MESSAGE = "不正解...";
private const string ALL_QUESTIONS_COMPLETED_MESSAGE = "全ての問題が終了しました!";
public Text questionText; // 質問文を表示するTextオブジェクト
public Text resultText; // 成否を表示するTextオブジェクト
public Button[] optionButtons; // 選択肢のボタン配列
public Question[] questions; // クイズの問題を格納する配列
public int maxQuestions = 3; // 出題する問題の数を設定
private Question[] questionPool; // ランダムに選ばれた問題のプール
private int currentQuestionIndex = 0; // 現在の質問のインデックス
public float resultDisplayTime = 1f; // 成否メッセージを表示する時間
void Start()
{
// 流し込み
// テキストファイルの読み込みを行ってくれるクラス
TextAsset textasset = new TextAsset();
// 先ほど用意したcsvファイルを読み込ませる。
// ファイルは「Resources」フォルダを作り、そこに入れておくこと。また"CSVTestData"の部分はファイル名に合わせて変更する。
textasset = Resources.Load("QuestionData", typeof(TextAsset)) as TextAsset;
// Questionを用いてcsvファイルを配列に流し込む。
questions = CSVSerializer.Deserialize<Question>(textasset.text);
// 設定された数の問題をランダムに抽出し、プールに格納
questionPool = questions
.OrderBy(q => Random.value) // ランダムに並び替える
.Take(maxQuestions) // 指定された数の問題を抽出
.ToArray();
// 最初の質問を表示
DisplayNextQuestion();
}
void DisplayNextQuestion()
{
// 成否テキストをクリア
resultText.text = "";
// クイズが残っている場合は次の問題を表示
if (currentQuestionIndex < questionPool.Length)
{
// 現在の質問を取得
Question currentQuestion = questionPool[currentQuestionIndex];
questionText.text = currentQuestion.questionText; // 質問文を設定
// 選択肢をシャッフルし、ボタンに設定
var shuffledOptions = currentQuestion.options
.Select((option, index) => new { option, index }) // 元のインデックスを保持
.OrderBy(x => Random.value) // ランダムにシャッフル
.ToArray();
// 各ボタンにシャッフルされた選択肢を設定
for (int i = 0; i < optionButtons.Length; i++)
{
var optionData = shuffledOptions[i]; // 現在の選択肢データ
optionButtons[i].GetComponentInChildren<Text>().text = optionData.option; // ボタンに選択肢を設定 //:TODO
// 以前のリスナーを削除し、クリック時の処理を設定
optionButtons[i].onClick.RemoveAllListeners();
// ボタンの選択肢が正解かどうかを判定するリスナーを追加
bool isCorrect = optionData.index == currentQuestion.correctAnswerIndex;
optionButtons[i].onClick.AddListener(() => OnOptionSelected(isCorrect));
}
}
else
{
// 全問終了時の処理
resultText.text = ALL_QUESTIONS_COMPLETED_MESSAGE;
}
}
void OnOptionSelected(bool isCorrect)
{
// 選択が正解かどうかをUIに表示
resultText.text = isCorrect ? CORRECT_MESSAGE : INCORRECT_MESSAGE;
// 次の質問に進む
currentQuestionIndex++;
// 一定時間後に次の質問を表示するコルーチンを開始
StartCoroutine(HideResultAndDisplayNextQuestion());
}
IEnumerator HideResultAndDisplayNextQuestion()
{
// 指定された秒数待機
yield return new WaitForSeconds(resultDisplayTime);
// 成否メッセージをクリア
resultText.text = "";
// 次の質問を表示
DisplayNextQuestion();
}
}
これでUnity側においてTextを設定するだけで問題表示を自動的に配列に格納し、問題文、選択肢1~4、正解の表示がされ、一通り遊べるようになりました!
ありがとうございました。