みつまめ杏仁

アンリアルエンジン(UE4)でGUIを作るためにゴニョゴニョしてます。UIデザイナーの皆様の助けになれば幸いです。

そうだ、QUIZゲームを作ろう《おまけ編》たくさんの問題

 UE4クイズゲームを作る記事の続きです

 クイズゲームなのでここで扱う「問題」はトラブルではありません。なんか、内容に問題ありな題をつけてますが、「問題だらけ」とか「問題がいっぱい」よりはマシかなぁとか考えてみたけど、あまり変わらない気もする。

 繰り返し遊ぶとなると、問題がたくさんあると面白くなります。知ってる問題が何度も出題されるとラッキーだけど、ちょっと残念な気持ちになります。クイズって、自分の知識を試す場でもあるけど、自分の知らない知識に出会う場でもあると思うのですよ。自分はこの問題について全く知らないけど、知ってる人がいるから、出題されてるんだと思うと、ちょっぴり悔しいし、へぇっていう場面もあるのが面白いです。少なからず知識欲が刺激されてるんだと思います。

 黒猫のウィズとかは買い切りじゃないからオンラインでアプデできるし、メモリが問題になるなら場面ごとにダウンロードしてもいいわけで、そうなると、メンテすればいくらでも問題を作って増やせるよな、とか思ってるんですけど実際どうなってるんでしょうかね? 

 

さてさて、

ここまでで、一通りのゲームサイクルができたと思っています。

f:id:hiyokosabrey:20210625230121p:plain

超シンプルな構成です。

あとはここにお好みで素敵な絵やストーリー、スゴロク的マップ、育成などの要素を盛り込んでいけばグレードアップ!あとジャンルセレクトとかも!

 

はい 盛り上がりもほどほどに、問題を用意してランダムに選択する仕組みを作っていこうと思います。

 

仕様は、1つの問題文に対して選択肢が4つ。テキストが合計 5つ必要です。

今回少しアレンジを加える想定です。

 

目次

 

 

ストラクチャとデータテーブル

最終的にDataTable(データテーブル) というアセットを用意するのですが、その前にStructure(ストラクチャ=構造体)を作る必要があります。

f:id:hiyokosabrey:20210625232307p:plain

MicrosoftExcel を触ったことがある方なら少しはイメージしやすいかもしれないです。

Excelは列やセルに、データの表示形式を設定できます。 「数」なのか「数」なのか入力する人がルールを決めないと判断がつかないからです。UE4のデータテーブルは、Excelでいうところの各列(A~)が変数のタイプと連動するように設計されています。

 

Structureの作り方は、コンテンツブラウザで右クリック

コンテキストメニューから Blueprints > Structure を選択

f:id:hiyokosabrey:20210626104506p:plain

f:id:hiyokosabrey:20210626105015p:plain

QuestionStruct と命名。このあと検索するので、なるべく見つけやすい名前にするといいです。

ダブルクリックして編集します。

f:id:hiyokosabrey:20210626105247p:plain

エディタが開きます。

ブーリアン型の MemberVar_0  という変数名がすでに一つ登録されています。

名前を変更して、変数タイプを Text に変えます。

f:id:hiyokosabrey:20210626105830p:plain

変数の中身は、エディタ下部の Default Value に適当なテキストを入力しておきます。

構造体というのは複数の変数を一式として扱うことができます。

New Variable ボタンをクリックします。 5回。

同じ要領で、変数の型をText に変えておきます。

f:id:hiyokosabrey:20210626110947p:plain

変数名はとりあえず A0~A4 としました。アレンジ用に選択肢を5つにしました。

これで保存します。

 

Structureが用意できたので、今度は問題と解答を用意します。

 

問題と選択肢をCSVで用意する

 問題と解答のテキストは、CSV形式でUE4にインポートします。

CSV(カンマ区切り形式:Comma-Separated Values)はメモ帳みたいなテキストエディタでも簡単に作れますが、管理するならExcelのような表組ベースのエディタが便利です。

 ぼくは Office製品を手元に持ってないので、 Googleスプレッドシートを使いました。

Googleアカウントでのログインが必要になります。

f:id:hiyokosabrey:20210626121316p:plain

重要なのが、一行目に半角英数で変数名を入力することです。

Structureの変数名と同じにしておく必要があります。

 

 左上の A1 のセルは何を入力しても無視されるので、空欄でOK。

 A列は Name型という型で扱われることになります。今回は数字で連番にしておきます。

 なお、正解はひとつなので、C列 を 正解として扱うことに決めます。D~G列は間違いとしてそれっぽい、ひっかけ的な答えを適当に用意します。

 

 ある程度数が用意できたら、これをCSV形式でダウンロードしてローカルPCに保存します。

 ファイル名に拡張子 .csv がついていればUE4にドラッグ&ドロップできます。

 適当にDLしてからインポート前にリネームしてもOK。

 メモ帳で開くとこうなってました。

f:id:hiyokosabrey:20210626123815p:plain

 カンマで区切られてるけど、間が詰まりすぎて編集しにくいですね。

 一応 Tab文字(Tabキーで入力) を入れて整形できますが、

f:id:hiyokosabrey:20210626124240p:plain

 問題文が複数行だとあまり解決できている気がしません。

 複数行の場合、改行を意味する文字が見えないので、ダブルクォート → " " でくくることで範囲を示します。

 例えば3行あるとき

f:id:hiyokosabrey:20210626130303p:plain

CSV専用のエディタがあったほうが良さそうです。

 

UE4にインポートする

 で、このCSVUE4のコンテンツブラウザにドラッグ&ドロップすると、インポートダイアログが出てきます。

f:id:hiyokosabrey:20210626145934j:plain

Choose DataTable Row Type: のプルダウンから、用意した Structure の名前を検索します。

f:id:hiyokosabrey:20210626150112p:plain

インポートが完了するとCSVと同じファイル名のアセットが誕生します。

f:id:hiyokosabrey:20210626150325p:plain

 

カンマの数が多かったり少なかったりすると、警告されます。

f:id:hiyokosabrey:20210626151353p:plain

f:id:hiyokosabrey:20210626151024p:plain

OKボタンを押すとそれなりにインポートはしてくれます。

足りないときは、初期値が入れられて、多すぎるときは、その行はスキップされます。

CSVを修正して再インポートすればキレイにインポートされます。

インポート時にエラーで突っぱねるのではなく、ひとまず事故らないようにインポートしてくれるのはありがたいですね。

 

インポートしてできた DataTableを開いてみましょう。

f:id:hiyokosabrey:20210626155929p:plain

上の Reimport ボタンで、CSVファイルを何度でも再読み込みできます。

CSVファイルを更新したら、毎度 Reimport → Save という操作が必要です。

このエディタを開かなくても、コンテンツブラウザ上でもできます。

f:id:hiyokosabrey:20210626173721p:plain

再インポートがうまくいくと、右下から表示が現れて成功を知らせてくれます。

f:id:hiyokosabrey:20210626173826p:plain


また、このエディタからデータの追加や編集が可能です。

 

いまのところ改行の方法が見つけられていません。\n みたいな改行文字を入れておいて別途置換する方法を使えば1行で書いてしまうこともできますが、今回は問題を増やしやすくするために外部のエディタをオススメしておきます。

Googleスプレッドシートなら思いついたときに出先からでも追加できますしね。

共有すれば共同作業も可能です。インポートの手間はかかるけども、外部のエディタで作業するのはバックアップを取りやすいのもメリットじゃないかなと。

 

f:id:hiyokosabrey:20210627150848p:plain



 

 DataTabeleが保存できたら、あとはブループリントから読みだして表示してやるだけです。

 

データテーブルを読む関数

 クイズ本体の wb_Mainを編集します。

f:id:hiyokosabrey:20210626211141p:plain


 新しい関数を作ります。

 編集モードを Graph にして、My Blueprint の Functions から +ボタンをクリック。

f:id:hiyokosabrey:20210626211256p:plain

 関数名は getQuestionInDataTable と命名

 

関数を編集していきます。

getDataTableRowノードでDataTableへアクセスします。

f:id:hiyokosabrey:20210626212844p:plain

f:id:hiyokosabrey:20210626212833p:plain

 DataTable ピンのところに、用意したDataTableアセットをセットします。

 

 Row Name のピンには、数字を渡して中身を引き出すのですが、Name型というタイプじゃないと受け付けてくれません。

 

 今回は 番号 でクイズの問題を管理しているので、キャスト(型変換)が必要になります。

f:id:hiyokosabrey:20210626214054p:plain

 キャストには しりとりのように順番があります。

 数値 → 文字列 → Name型 の流れです。

 

 逆から進めると少し手間が減るので下の画像は逆引きでつないだ場合です。

f:id:hiyokosabrey:20210626212539p:plain

 関数のInputsピンを追加してからでも問題ありません。

 

 これで整数を使って任意のクイズを取り出すことができます。

 

 取り出したクイズは、 青い Out Row ピンから出力されます。

 そこから ドラッグして Breakノードを取り出します。

f:id:hiyokosabrey:20210626215504g:plain

 Breakノードはストラクチャ(構造体)の中身を個別に取り出せるようにするノードです。ちなみにストラクチャに戻すのは Makeノードになります。

 

 Structureで設定したピンがずらりと並びます。

 この内容を保持するために、この関数専用のローカル変数を追加します。

 関数編集中にのみ現れる Local Variables から +ボタンを 3回。

f:id:hiyokosabrey:20210626220800p:plain

 Text型、Boolean型、Integer型の 3つ。

f:id:hiyokosabrey:20210626221135p:plain

 Text型とBoolean型は配列にします。

 Variable Type の 右端にあるアイコンをクリックすると、小さなリストメニューがポップします。

f:id:hiyokosabrey:20210626221541p:plain

 9マスのグリッド状のアイコンが配列です。

 

 名前に  temp_ と付けているのは、一時的なという意味の temporary(テンポラリー) の略です。

 関数が呼び出されるたびに作られて、関数の処理が終了すると破棄されます。

 刹那的な変数なので関数の外に持ち出すことができません。

 

 この一時的配列変数をグラフにドロップして、Add ノードを取り出します。

f:id:hiyokosabrey:20210626222729p:plain

 もう一つのBoolean型の配列も同じように Add ノードを取り出します。

 

 getDataTableRowノードの Row Found ピンとつなぎ、Breakノードの A1 ピンともつなぎます。

f:id:hiyokosabrey:20210626223008p:plain

 残りの A2~A4 のピンをつなぐために、Add ノードを複製します。

f:id:hiyokosabrey:20210626225711p:plain

 新たに3セット追加。
f:id:hiyokosabrey:20210626225933p:plain

 この灰色のノードたちに魂を入れます。

f:id:hiyokosabrey:20210626230033p:plain

 とどめに Shuffle ノードをつなぐ。

f:id:hiyokosabrey:20210626230412p:plain

 配列のGetノードは下に移動しました。

f:id:hiyokosabrey:20210626230541p:plain

 この Shuffle ノードは配列の中身を文字通りシャッフルしてくれます。

 A0 のピンは 正解で配列には含めていません。

 A1~A4 はすべて間違いなので、どれだけシャッフルしても何の問題もないのです。

 

 次に、正解を上書きするためのランダムな値を作ります。

f:id:hiyokosabrey:20210626231442p:plain

 Random Integer ノードは 0 ~ Max-1 の間でランダムな値を生みだします。

 この配列は 4回の Addノードをつないでいるので、 Maxには  4 を指定。 0 ~3 の4面ダイスを振るイメージ。

 こうして選ばれた数字を ローカル変数に取り置きします。

 

 次に、配列の中身に対して、指定したIndex番号の中身を書き換えるのが Set Array Elem ノードです。これをつなぎます。

f:id:hiyokosabrey:20210626232410p:plain

 Item ピンのところに DataTable から取り出す A0 のピンをつなぎます。

f:id:hiyokosabrey:20210626232904p:plain

 正解を上書きする場所(Index番号)が決まったので、おなじく判定用の配列にも反映させます。

f:id:hiyokosabrey:20210626233239p:plain

 

 配列の中身が変化する流れとしては3つのステップ。

f:id:hiyokosabrey:20210627154012p:plain

 

 

 関数の最後はReturn (リターン)ノードを取り出してつなぎます。

 関数にだけ用意されているノードです。

 何もないところで右クリックして、 .  ←ピリオドで検索すると出てきます。

f:id:hiyokosabrey:20210626233736p:plain

 この Returnノードは 戻り値(もどりち)とか Return Value とかいう外に値を渡すための ピンを追加することができます。

 

 このReturnノードに、 Breakノードでバラした問題文 Qピン をドロップしてつなぎます。

f:id:hiyokosabrey:20210627111921p:plain

 ラインが重なるのと見づらいので、途中でダブルクリックしてリルートノードを使って経路を整えるのをオススメ。

f:id:hiyokosabrey:20210627112218p:plain

 あとは、配列変数を 2つを同様に Returnノードに渡します。

f:id:hiyokosabrey:20210627112503p:plain

 これで関数は完成です。

 この関数のInputs(入力) と Outputs(出力)はこのようになりました。

f:id:hiyokosabrey:20210627112736p:plain

 Detailsタブから、先に Inputs も Outputs もピンを追加しておくこともできます。

 +ボタンを押して追加するのですが、ピンの名前が NewParam みたいなテキトーな名前になります。

 ノードに直接変数からドロップすると変数名を使ってピンを追加してくれるので、小さなことですが、手間を減らしつつブループリントが分かりやすくなるのでオススメです。

 Inputs と Outputs ピンは変数の型を設定して利用します。

 これは、フィルターというかラベルみたいなものなので、変数が作られているわけではありません。古くからある知育玩具で、形を合わせないとブロックが通過できないアレに似ていますね。

 

 "temp_" とかは関数の外では不要な情報です。気になる場合は整えます。

f:id:hiyokosabrey:20210627113547p:plain

 

 

クイズを選択

 関数の編集が終了したので EventGraphに戻ります。

f:id:hiyokosabrey:20210627115627p:plain

 

 クイズを選択してセットするカスタムイベントを用意します。

 空いてるところでカスタムイベントを取り出します。名前を chooseQuiz としました。

 そこへ関数を グラフにドロップしてつなぎます

f:id:hiyokosabrey:20210627115834p:plain


この関数の戻り値(ReturnValue)を保持する変数を新しく追加します。

Variables の+ボタンから2つ変数を追加して配列にします。

f:id:hiyokosabrey:20210627120719p:plain

 この配列の中身は数が予め決まっていて、ゲームの開始から終了まで変動しないので、サイズを固定してしまいます。

 

 まず追加した2つの配列変数をドロップ。

f:id:hiyokosabrey:20210627121201p:plain

 Event Pre Construct で初期化します。

 そこに Resize ノードをつないで、値を 4 にします。

f:id:hiyokosabrey:20210627121314p:plain

 配列変数って、作っただけだと何個空きを用意するか決まっていないので、可能な限り前もって確保しておくようにします。

 

 この配列変数を、クイズ選択の関数につなぎます。

 併せて、クイズの問題文もキャンバスのTextSetTextノードでセットします。

f:id:hiyokosabrey:20210627130024p:plain

f:id:hiyokosabrey:20210627125652p:plain

 この続きに、回答ボタンへのSetTextをつないでいきます。

f:id:hiyokosabrey:20210627130411p:plain

 

 あとはこのカスタムイベントを、クイズスタートイベントのところに挿入します。

f:id:hiyokosabrey:20210627131510p:plain

f:id:hiyokosabrey:20210627131848p:plain

 これで問題文と回答ボタンが更新されます。

 

回答ボタンに配列をセット

 つぎに、回答ボタンに対して正解、不正解をセットしてやります。

今までは、正解ボタンを決めて処理してましたが、ここで配列を反映させます。

f:id:hiyokosabrey:20210627132335p:plain

 

あともう少しで完成です。

いったんコンパイルして確認してみましょうか。

f:id:hiyokosabrey:20210627133449p:plain

 回答ボタンが毎回シャッフルされています。同じ問題で答えが判っていてもそれなりに戸惑うはず。消去法に頼れなくなるやつです。これが今回アレンジとして入れたかった仕様です。

 

完成一歩手前!

 

ランダムチョイス

 最後に いっぱい考えた問題をランダムで選ぶ処理を追加します。

f:id:hiyokosabrey:20210627134020p:plain

 このピンに問題番号を渡せばいいのですが、ただランダムにすると、重複することがあります。

f:id:hiyokosabrey:20210627134502p:plain

 重複を回避する方法は いくつかあるみたいですが、今回のクイズゲームでは制限時間があり、それほど多く回答できる機会はないという想定です。

 問題の数が少ないうちは トランプ同様に 配列を Shuffle するのが簡単なのですが、問題数が多くなると、Shuffle用の巨大な配列を作る必要が出るし、処理負荷が気になります。

 なので、

 今回はランダムな値を記録用の配列に積んでいく作戦を採用します。

 このランダムな値を積むときに、重複していないかをチェックして、重複を発見すると、積むのを止めて、もう一度ランダムな値を作るところからやり直します。

 

 選択履歴を保存しておくための配列変数を新しく追加します。

f:id:hiyokosabrey:20210627164450p:plain

 SelectedQuizと命名

 これをクイズゲーム開始時に初期化しないといけないので、初期化用のカスタムイベント Initialize で CLEARノードと合わせてつなぐ。

f:id:hiyokosabrey:20210627164744p:plain

 CLEAR は 配列の中身をキレイサッパリ空っぽにしてくれるノードです。

 いろいろ履歴が積まれていてもここでお掃除。

 

 ランダムセレクト用に新しく関数を作ります。

 ローカル変数を 3つ。 Integer型(整数型)が 2つ。Boolean型が 1つ。

f:id:hiyokosabrey:20210627165721p:plain

temp_NewNum は、選択決定した問題の番号。

temp_Max は、用意した問題の最大数を意味します。

temp_isFound は、既に選んだことがあるかどうかの判定用に使います。

 

 関数の中身はこうなります。

f:id:hiyokosabrey:20210627170211p:plain

 まずクイズの最大数を受け取って、WhileLoop で最大数までのランダムな値をゲット、重複チェックをしています。

 重複がなければ選択履歴用の配列変数 SelectedQuiz に追加して完了というものです。

 

 WhileLoop ノードは、 Condition(条件)が true だと、LoopBody を実行し、false になると Loopを止めます。

 WhileLoop のセリフ

 「君 (temp_isFound) がッ false になるまで 僕は LoopBody の処理を止めないッ!」

 

 LoopBody の処理については以下のようになってます。

f:id:hiyokosabrey:20210627184844p:plain

 

今回時間制限タイプのクイズなので、制限時間が長くなって間違いを選びまくると問題数が足りなくなるのが予想されます。すべての問題を出題してしまうと無限ループになってしまいます。

解決策として思いつくのは 3つ

  • 問題数を増やす(最短時間で間違いを連打しても大丈夫なくらい用意する)
  • 間違いを選択するとタイムを減らす(ペナルティな印象が強くなるが・・・)
  • すべての問題を出題したらリセットする

安全で確実なのは、3つ目のリセット。

ということで、選択履歴用配列の数が問題数と同じになれば、CLEARするようにします。

f:id:hiyokosabrey:20210628220810p:plain

LENGTH(レングス)ノードは、配列の数を調べてくれるノード。

これで安心です。

 

 

 これで関数ができました。

 ここに挿入すればOK。

 

Max の値は、今回用意できた問題数ということで 12 を入力しています。

ここは用意できた数に合わせて変更してください。

f:id:hiyokosabrey:20210627185258p:plain

 

 編集は以上です。

 

wb_Main の全貌

※関数とマクロは除く

f:id:hiyokosabrey:20210627185652p:plain

 

  おつかれさまです。

 これで一通りの実装ができました。

 コンパイルして保存したら確認してみます。

 

youtu.be

 

完成!

 UI要素だけで完結できるゲームとしてクイズゲームをチョイスしました。なかなかのボリュームになり、途中くじけそうになりましたが、無事なんとかカタチにはできたので胸をなでおろしています。

 基本操作の説明から始まって、できるだけ現場で使える実践向きの考え方やテクニックを織り込んで記事を書いてみました。

 ビジュアルについてはあえて力を抜いています。デザイナーの方はぜひアレンジを楽しんでほしいし、画像系の作成環境に自信のない方でも進められるようにしたかったからです。どっかのストレージにアップするのも考えましたが、このぐらいのクオリティでプロトタイプがサクッと作れる、というのをアピールしたかったというのもあります。

 

 多少の手戻りっぽい修正を意図的に入れましたが、実際には試行錯誤しながら、確実に成功する方法を記事にしています。できればもっとデバッグの方法も紹介したいのですが、さすがに面白くないでしょうね。

 

 UE5が早期アクセスを開始され、世間が驚愕のグラフィックに沸いてる中、UMGを使って地味にクイズゲームを作るという、なんだかイベント会場を間違えて露店を開いたような気分になりつつも、UE5にゲーム開発の未来を見たUIクリエイター志望の方々、またUE4を初めて触る現役UIクリエイターの皆様へ、少しでもエールを送れたらなというのをモチベーションにしてここまで来れました。

 エディタの操作方法についてもなるべく丁寧に説明を書いたつもりですが、分かりにくいところや要望などあれば、お気軽にお問い合わせくだされば対応します。

アレンジのやり方なども内容次第ですがアドバイスできるかもしれません。

このブログのコメント機能(承認制なので応答まで時間がかかります)かTwitter@MMAn_nin)にてお待ちしています。

 

 

ではでは

ステキなクイズゲームライフを!

 

 

 

今回参考にさせていただいた記事

キンアジちゃんのブログ。現場で使えるテクニカルな記事満載!

kinnaji.com