UE4のUMGでクイズゲームができた《記事まとめ》
アンリアルエンジン4でクイズゲームづくりにチャレンジということで、約1ヶ月8回に渡って記事を書きました。ここで一覧にしてまとめておこうと思います。
完成したクイズゲームとしての仕様
- 複数の問題から出題
- 選択肢は 4つ
- 時間制限がある
- 制限時間内に一定数正解するとゲームクリア
- 制限時間内に一定数正解できなければタイムアップ
- 選択肢の配置はランダム
UIの 素材として作るものは
- タイトル画面
- 共通背景
- 出題画面
- 正解画面
- 不正解画面
- タイムアップ画面
- クリア画面
- タイマー表示
- 正解数カウント表示
UE4のUMGを触ったことをない方でも試していただけるように、操作についてはなるべく省略しないように書いてます。
まずは《準備編》と《組み立て編》を試していただくと、
紙芝居のような画面切り替え方式で一応のクイズゲームができます。
UE4 の バージョンは 4.26 を使っています。
汎用的なしくみを使うようにしてるので、少し前のバージョンでも問題ないと思います。たぶん。
WidgetSwitcher(ウィジェットスイッチャー)という、ストックさせた複数のWidgetを切り替えることができるコンポーネントを利用します。
《準備編》ではビジュアル素材を揃えます。画面ごとにWidgetアセットを用意するところまで。
《組み立て編》ではブループリントで複数の画面を切り替えられるようにします。
ボタンクリックがトリガーとなって切り替えられるようにします。
まだクイズと選択肢は固定です。
ここからは、ゲームとしての機能を充実させつつ、実践で役立ちそうなテクニックについて紹介しています。
ただの数字にラベルを付けて扱いやすくする仕組み。
WidgetSwitcherの切り替えは数字で行うのですが、いちいち覚えていられないので、Enum を利用します
UIデザインに関わる立場として、プロトタイプから本番に向けて効率のいい作業フローを模索してみた成果。
無事プロトタイプが採用された体での本番デザインの差し替えについて。
よりゲームらしく。
「タイマー」を扱うための仕組み。一時停止と再開のコントロール。
さらにゲームらしく。
クリア条件を決めるとその進捗が必要になる。記事のボリュームが大きくなったので前後編に分割。
さらにゲームらしく。
クリア条件ができると、失敗条件もまた必要。
クイズゲームらしく。
たくさんの問題の中からランダムに選ぶ。ランダムで選ぶことの難しさと面白さ。
問題を用意するのが結構大変ですが、探してみるとクイズの問題集を公開しているサイトが見つかるので、利用できると手っ取り早く楽しめます。
ここまでで完成とします。
今回のクイズゲーム制作を通して、UE4でのUI制作に慣れ親しんでもらえたら、またそのきっかけになれば嬉しいです。ぜひカスタマイズやアレンジに挑んでみてください。
ではでは
すきな UMGライフを!
そうだ、QUIZゲームを作ろう《おまけ編》たくさんの問題
クイズゲームなのでここで扱う「問題」はトラブルではありません。なんか、内容に問題ありな題をつけてますが、「問題だらけ」とか「問題がいっぱい」よりはマシかなぁとか考えてみたけど、あまり変わらない気もする。
繰り返し遊ぶとなると、問題がたくさんあると面白くなります。知ってる問題が何度も出題されるとラッキーだけど、ちょっと残念な気持ちになります。クイズって、自分の知識を試す場でもあるけど、自分の知らない知識に出会う場でもあると思うのですよ。自分はこの問題について全く知らないけど、知ってる人がいるから、出題されてるんだと思うと、ちょっぴり悔しいし、へぇっていう場面もあるのが面白いです。少なからず知識欲が刺激されてるんだと思います。
黒猫のウィズとかは買い切りじゃないからオンラインでアプデできるし、メモリが問題になるなら場面ごとにダウンロードしてもいいわけで、そうなると、メンテすればいくらでも問題を作って増やせるよな、とか思ってるんですけど実際どうなってるんでしょうかね?
さてさて、
ここまでで、一通りのゲームサイクルができたと思っています。
超シンプルな構成です。
あとはここにお好みで素敵な絵やストーリー、スゴロク的マップ、育成などの要素を盛り込んでいけばグレードアップ!あとジャンルセレクトとかも!
はい 盛り上がりもほどほどに、問題を用意してランダムに選択する仕組みを作っていこうと思います。
仕様は、1つの問題文に対して選択肢が4つ。テキストが合計 5つ必要です。
今回少しアレンジを加える想定です。
目次
- ストラクチャとデータテーブル
- 問題と選択肢をCSVで用意する
- UE4にインポートする
- データテーブルを読む関数
- クイズを選択
- 回答ボタンに配列をセット
- ランダムチョイス
- wb_Main の全貌
- 完成!
- 今回参考にさせていただいた記事
ストラクチャとデータテーブル
最終的にDataTable(データテーブル) というアセットを用意するのですが、その前にStructure(ストラクチャ=構造体)を作る必要があります。
Microsoftの Excel を触ったことがある方なら少しはイメージしやすいかもしれないです。
Excelは列やセルに、データの表示形式を設定できます。 「数字」なのか「数値」なのか入力する人がルールを決めないと判断がつかないからです。UE4のデータテーブルは、Excelでいうところの各列(A~)が変数のタイプと連動するように設計されています。
Structureの作り方は、コンテンツブラウザで右クリック
コンテキストメニューから Blueprints > Structure を選択
QuestionStruct と命名。このあと検索するので、なるべく見つけやすい名前にするといいです。
ダブルクリックして編集します。
エディタが開きます。
ブーリアン型の MemberVar_0 という変数名がすでに一つ登録されています。
名前を変更して、変数タイプを Text に変えます。
変数の中身は、エディタ下部の Default Value に適当なテキストを入力しておきます。
構造体というのは複数の変数を一式として扱うことができます。
New Variable ボタンをクリックします。 5回。
同じ要領で、変数の型をText に変えておきます。
変数名はとりあえず A0~A4 としました。アレンジ用に選択肢を5つにしました。
これで保存します。
Structureが用意できたので、今度は問題と解答を用意します。
問題と選択肢をCSVで用意する
問題と解答のテキストは、CSV形式でUE4にインポートします。
CSV(カンマ区切り形式:Comma-Separated Values)はメモ帳みたいなテキストエディタでも簡単に作れますが、管理するならExcelのような表組ベースのエディタが便利です。
ぼくは Office製品を手元に持ってないので、 Googleスプレッドシートを使いました。
Googleアカウントでのログインが必要になります。
重要なのが、一行目に半角英数で変数名を入力することです。
Structureの変数名と同じにしておく必要があります。
左上の A1 のセルは何を入力しても無視されるので、空欄でOK。
A列は Name型という型で扱われることになります。今回は数字で連番にしておきます。
なお、正解はひとつなので、C列 を 正解として扱うことに決めます。D~G列は間違いとしてそれっぽい、ひっかけ的な答えを適当に用意します。
ある程度数が用意できたら、これをCSV形式でダウンロードしてローカルPCに保存します。
ファイル名に拡張子 .csv がついていればUE4にドラッグ&ドロップできます。
適当にDLしてからインポート前にリネームしてもOK。
メモ帳で開くとこうなってました。
カンマで区切られてるけど、間が詰まりすぎて編集しにくいですね。
一応 Tab文字(Tabキーで入力) を入れて整形できますが、
問題文が複数行だとあまり解決できている気がしません。
複数行の場合、改行を意味する文字が見えないので、ダブルクォート → " " でくくることで範囲を示します。
例えば3行あるとき
CSV専用のエディタがあったほうが良さそうです。
UE4にインポートする
で、このCSVをUE4のコンテンツブラウザにドラッグ&ドロップすると、インポートダイアログが出てきます。
Choose DataTable Row Type: のプルダウンから、用意した Structure の名前を検索します。
インポートが完了するとCSVと同じファイル名のアセットが誕生します。
カンマの数が多かったり少なかったりすると、警告されます。
OKボタンを押すとそれなりにインポートはしてくれます。
足りないときは、初期値が入れられて、多すぎるときは、その行はスキップされます。
CSVを修正して再インポートすればキレイにインポートされます。
インポート時にエラーで突っぱねるのではなく、ひとまず事故らないようにインポートしてくれるのはありがたいですね。
インポートしてできた DataTableを開いてみましょう。
上の Reimport ボタンで、CSVファイルを何度でも再読み込みできます。
CSVファイルを更新したら、毎度 Reimport → Save という操作が必要です。
このエディタを開かなくても、コンテンツブラウザ上でもできます。
再インポートがうまくいくと、右下から表示が現れて成功を知らせてくれます。
また、このエディタからデータの追加や編集が可能です。
いまのところ改行の方法が見つけられていません。\n みたいな改行文字を入れておいて別途置換する方法を使えば1行で書いてしまうこともできますが、今回は問題を増やしやすくするために外部のエディタをオススメしておきます。
Googleのスプレッドシートなら思いついたときに出先からでも追加できますしね。
共有すれば共同作業も可能です。インポートの手間はかかるけども、外部のエディタで作業するのはバックアップを取りやすいのもメリットじゃないかなと。
DataTabeleが保存できたら、あとはブループリントから読みだして表示してやるだけです。
データテーブルを読む関数
クイズ本体の wb_Mainを編集します。
新しい関数を作ります。
編集モードを Graph にして、My Blueprint の Functions から +ボタンをクリック。
関数名は getQuestionInDataTable と命名。
関数を編集していきます。
getDataTableRowノードでDataTableへアクセスします。
DataTable ピンのところに、用意したDataTableアセットをセットします。
Row Name のピンには、数字を渡して中身を引き出すのですが、Name型というタイプじゃないと受け付けてくれません。
今回は 番号 でクイズの問題を管理しているので、キャスト(型変換)が必要になります。
キャストには しりとりのように順番があります。
数値 → 文字列 → Name型 の流れです。
逆から進めると少し手間が減るので下の画像は逆引きでつないだ場合です。
関数のInputsピンを追加してからでも問題ありません。
これで整数を使って任意のクイズを取り出すことができます。
取り出したクイズは、 青い Out Row ピンから出力されます。
そこから ドラッグして Breakノードを取り出します。
Breakノードはストラクチャ(構造体)の中身を個別に取り出せるようにするノードです。ちなみにストラクチャに戻すのは Makeノードになります。
Structureで設定したピンがずらりと並びます。
この内容を保持するために、この関数専用のローカル変数を追加します。
関数編集中にのみ現れる Local Variables から +ボタンを 3回。
Text型、Boolean型、Integer型の 3つ。
Text型とBoolean型は配列にします。
Variable Type の 右端にあるアイコンをクリックすると、小さなリストメニューがポップします。
9マスのグリッド状のアイコンが配列です。
名前に temp_ と付けているのは、一時的なという意味の temporary(テンポラリー) の略です。
関数が呼び出されるたびに作られて、関数の処理が終了すると破棄されます。
刹那的な変数なので関数の外に持ち出すことができません。
この一時的配列変数をグラフにドロップして、Add ノードを取り出します。
もう一つのBoolean型の配列も同じように Add ノードを取り出します。
getDataTableRowノードの Row Found ピンとつなぎ、Breakノードの A1 ピンともつなぎます。
残りの A2~A4 のピンをつなぐために、Add ノードを複製します。
新たに3セット追加。
この灰色のノードたちに魂を入れます。
とどめに Shuffle ノードをつなぐ。
配列のGetノードは下に移動しました。
この Shuffle ノードは配列の中身を文字通りシャッフルしてくれます。
A0 のピンは 正解で配列には含めていません。
A1~A4 はすべて間違いなので、どれだけシャッフルしても何の問題もないのです。
次に、正解を上書きするためのランダムな値を作ります。
Random Integer ノードは 0 ~ Max-1 の間でランダムな値を生みだします。
この配列は 4回の Addノードをつないでいるので、 Maxには 4 を指定。 0 ~3 の4面ダイスを振るイメージ。
こうして選ばれた数字を ローカル変数に取り置きします。
次に、配列の中身に対して、指定したIndex番号の中身を書き換えるのが Set Array Elem ノードです。これをつなぎます。
Item ピンのところに DataTable から取り出す A0 のピンをつなぎます。
正解を上書きする場所(Index番号)が決まったので、おなじく判定用の配列にも反映させます。
配列の中身が変化する流れとしては3つのステップ。
関数の最後はReturn (リターン)ノードを取り出してつなぎます。
関数にだけ用意されているノードです。
何もないところで右クリックして、 . ←ピリオドで検索すると出てきます。
この Returnノードは 戻り値(もどりち)とか Return Value とかいう外に値を渡すための ピンを追加することができます。
このReturnノードに、 Breakノードでバラした問題文 Qピン をドロップしてつなぎます。
ラインが重なるのと見づらいので、途中でダブルクリックしてリルートノードを使って経路を整えるのをオススメ。
あとは、配列変数を 2つを同様に Returnノードに渡します。
これで関数は完成です。
この関数のInputs(入力) と Outputs(出力)はこのようになりました。
Detailsタブから、先に Inputs も Outputs もピンを追加しておくこともできます。
+ボタンを押して追加するのですが、ピンの名前が NewParam みたいなテキトーな名前になります。
ノードに直接変数からドロップすると変数名を使ってピンを追加してくれるので、小さなことですが、手間を減らしつつブループリントが分かりやすくなるのでオススメです。
Inputs と Outputs ピンは変数の型を設定して利用します。
これは、フィルターというかラベルみたいなものなので、変数が作られているわけではありません。古くからある知育玩具で、形を合わせないとブロックが通過できないアレに似ていますね。
"temp_" とかは関数の外では不要な情報です。気になる場合は整えます。
クイズを選択
関数の編集が終了したので EventGraphに戻ります。
クイズを選択してセットするカスタムイベントを用意します。
空いてるところでカスタムイベントを取り出します。名前を chooseQuiz としました。
そこへ関数を グラフにドロップしてつなぎます
この関数の戻り値(ReturnValue)を保持する変数を新しく追加します。
Variables の+ボタンから2つ変数を追加して配列にします。
この配列の中身は数が予め決まっていて、ゲームの開始から終了まで変動しないので、サイズを固定してしまいます。
まず追加した2つの配列変数をドロップ。
Event Pre Construct で初期化します。
そこに Resize ノードをつないで、値を 4 にします。
配列変数って、作っただけだと何個空きを用意するか決まっていないので、可能な限り前もって確保しておくようにします。
この配列変数を、クイズ選択の関数につなぎます。
併せて、クイズの問題文もキャンバスのTextに SetTextノードでセットします。
この続きに、回答ボタンへのSetTextをつないでいきます。
あとはこのカスタムイベントを、クイズスタートイベントのところに挿入します。
これで問題文と回答ボタンが更新されます。
回答ボタンに配列をセット
つぎに、回答ボタンに対して正解、不正解をセットしてやります。
今までは、正解ボタンを決めて処理してましたが、ここで配列を反映させます。
あともう少しで完成です。
いったんコンパイルして確認してみましょうか。
回答ボタンが毎回シャッフルされています。同じ問題で答えが判っていてもそれなりに戸惑うはず。消去法に頼れなくなるやつです。これが今回アレンジとして入れたかった仕様です。
完成一歩手前!
ランダムチョイス
最後に いっぱい考えた問題をランダムで選ぶ処理を追加します。
このピンに問題番号を渡せばいいのですが、ただランダムにすると、重複することがあります。
重複を回避する方法は いくつかあるみたいですが、今回のクイズゲームでは制限時間があり、それほど多く回答できる機会はないという想定です。
問題の数が少ないうちは トランプ同様に 配列を Shuffle するのが簡単なのですが、問題数が多くなると、Shuffle用の巨大な配列を作る必要が出るし、処理負荷が気になります。
なので、
今回はランダムな値を記録用の配列に積んでいく作戦を採用します。
このランダムな値を積むときに、重複していないかをチェックして、重複を発見すると、積むのを止めて、もう一度ランダムな値を作るところからやり直します。
選択履歴を保存しておくための配列変数を新しく追加します。
SelectedQuizと命名。
これをクイズゲーム開始時に初期化しないといけないので、初期化用のカスタムイベント Initialize で CLEARノードと合わせてつなぐ。
CLEAR は 配列の中身をキレイサッパリ空っぽにしてくれるノードです。
いろいろ履歴が積まれていてもここでお掃除。
ランダムセレクト用に新しく関数を作ります。
ローカル変数を 3つ。 Integer型(整数型)が 2つ。Boolean型が 1つ。
temp_NewNum は、選択決定した問題の番号。
temp_Max は、用意した問題の最大数を意味します。
temp_isFound は、既に選んだことがあるかどうかの判定用に使います。
関数の中身はこうなります。
まずクイズの最大数を受け取って、WhileLoop で最大数までのランダムな値をゲット、重複チェックをしています。
重複がなければ選択履歴用の配列変数 SelectedQuiz に追加して完了というものです。
WhileLoop ノードは、 Condition(条件)が true だと、LoopBody を実行し、false になると Loopを止めます。
WhileLoop のセリフ
「君 (temp_isFound) がッ false になるまで 僕は LoopBody の処理を止めないッ!」
LoopBody の処理については以下のようになってます。
今回時間制限タイプのクイズなので、制限時間が長くなって間違いを選びまくると問題数が足りなくなるのが予想されます。すべての問題を出題してしまうと無限ループになってしまいます。
解決策として思いつくのは 3つ
- 問題数を増やす(最短時間で間違いを連打しても大丈夫なくらい用意する)
- 間違いを選択するとタイムを減らす(ペナルティな印象が強くなるが・・・)
- すべての問題を出題したらリセットする
安全で確実なのは、3つ目のリセット。
ということで、選択履歴用配列の数が問題数と同じになれば、CLEARするようにします。
LENGTH(レングス)ノードは、配列の数を調べてくれるノード。
これで安心です。
これで関数ができました。
ここに挿入すればOK。
Max の値は、今回用意できた問題数ということで 12 を入力しています。
ここは用意できた数に合わせて変更してください。
編集は以上です。
wb_Main の全貌
※関数とマクロは除く
おつかれさまです。
これで一通りの実装ができました。
コンパイルして保存したら確認してみます。
完成!
UI要素だけで完結できるゲームとしてクイズゲームをチョイスしました。なかなかのボリュームになり、途中くじけそうになりましたが、無事なんとかカタチにはできたので胸をなでおろしています。
基本操作の説明から始まって、できるだけ現場で使える実践向きの考え方やテクニックを織り込んで記事を書いてみました。
ビジュアルについてはあえて力を抜いています。デザイナーの方はぜひアレンジを楽しんでほしいし、画像系の作成環境に自信のない方でも進められるようにしたかったからです。どっかのストレージにアップするのも考えましたが、このぐらいのクオリティでプロトタイプがサクッと作れる、というのをアピールしたかったというのもあります。
多少の手戻りっぽい修正を意図的に入れましたが、実際には試行錯誤しながら、確実に成功する方法を記事にしています。できればもっとデバッグの方法も紹介したいのですが、さすがに面白くないでしょうね。
UE5が早期アクセスを開始され、世間が驚愕のグラフィックに沸いてる中、UMGを使って地味にクイズゲームを作るという、なんだかイベント会場を間違えて露店を開いたような気分になりつつも、UE5にゲーム開発の未来を見たUIクリエイター志望の方々、またUE4を初めて触る現役UIクリエイターの皆様へ、少しでもエールを送れたらなというのをモチベーションにしてここまで来れました。
エディタの操作方法についてもなるべく丁寧に説明を書いたつもりですが、分かりにくいところや要望などあれば、お気軽にお問い合わせくだされば対応します。
アレンジのやり方なども内容次第ですがアドバイスできるかもしれません。
このブログのコメント機能(承認制なので応答まで時間がかかります)かTwitter(@MMAn_nin)にてお待ちしています。
ではでは
ステキなクイズゲームライフを!
今回参考にさせていただいた記事
キンアジちゃんのブログ。現場で使えるテクニカルな記事満載!
そうだ、QUIZゲームを作ろう《おまけ編》クリアへの道 後編
前回タイマーの改造に始まって、ForLoopやらSelectノードやら配列やら説明してたらボリュームがえらいことになったので、2部構成にしました。
クイズゲームとして、制限時間内に規定回数分正解するとゲームクリア。という仕様に決めました。前回の記事でその正解数をカウント表示するWidgetを作ったのでいよいよ組み込んでいこうと意気込んだところで、タイムアップの時にタイトル画面に戻るようにしてなかったのを思い出しました。
目次
タイトルへ戻る
タイムアップしてもまたクイズ画面に戻るようになっているので、タイトル画面に移行するように変えます。
ゲーム本体の wb_QuizGame を編集します。
NEXTボタンを押したときの処理はこうなってました。
タイムアップの時だけ、専用のカスタムイベントを用意してやります。
これでOK。コンパイルして確認してみる。
・・・
タイトル画面にちゃんと戻ることができているけど、リトライするとタイマーがリセットできていないことが判明。もう少し詳しく調べるために変数を確認することにします。
タイマーを管理しているのは wb_Main です。
効率よくチェックしたいので、タイマーの長さを短くするために 変数 Seconds の初期値を 5.0 にして再生してみると。
タイトル画面で一度出たきりでした。
何度かプレイしてみて判ったことが 2つ。
- 残り時間を保持する変数 RemainTime はリセットされる機会がない
- WidgetSwitcher に登録したWidgetは、表示されていなくても(アクティブになてっていなくても)EventConstruct は実行される
変数がどこでどう使われているかの確認は、Find References を使います。
Variablesリストの変数の上から右クリックメニューで選択できます。
エディタ下に Find Results というタブがあって、そこに検索結果が表示されます。
このリスト上でダブルクリックすると、配置されている箇所にワープします。
変数で 検索すると、 Get と Set が変数名の前に付きます。
これは、ジャンプするとわかるのですが、値の 入力 と 取得 を表しています。
タイムをリセットするには、Seconds の値と同じにする必要があります。Set ~ のところを見てみると、Event Construction のところと、 ポーズをかけたときに関数内の2か所で、 Event Construct はプレイ開始時 1回だけしか通ってませんでした。
なので、回答ボタンを押してポーズしたら、RemainTime は値が変更されます。そしてそのままタイムアップになってリトライしても、変更されたままになっていました。
ということで、初期化用のイベントを作って、別の場所からも呼べるようにします。
カスタムイベントを追加して、 initialize と命名。そこへ Event Construct でつないでいたのを移植します。
Seconds もこの際なので、 Set の形で置きなおして値を設定しやすくしました。
Event Construct には カスタムイベントをつなげば元の通りに動きます。
コンパイルして保存します。
再び、クイズゲーム本体にに戻ってきました。
この初期化のイベントを、ここに挟めば完了。
コンパイルして問題なければ、保存します。
タイマー問題はこれで解決です。
組み込む
ようやくカウント表示を組み込んでいきます。
組み込む場所は 2か所。
- クイズの回答する画面 ← 現在の状況を確認したい 演出は不要
- 正解したときの画面 ← 獲得したことを演出付きで見せたい
この 2つ タイミングを合わせる必要がありそうです。
流れとしては、
回答ボタンを押した瞬間、正解画面に切り替わります。
演出をじっくり見せるには、時間制限のある回答画面では嫌われるので、正解演出画面で見せるのがベスト。
NEXTボタンを押して画面が切り替わると、すぐに次のクイズが始まるので、その時点でカウント表示は更新されていてほしい。
流れはこれで行こうと思います。
時間制限については、メインの wb_Main でのみ表示されるパーツだったので、wb_Main で管理していました。今回は複数画面に配置しているので、カウント数の管理はもう一段上の wb_QuizGame でやるほうがよさそうです。
ということで組み込みを進めます。
まずはクイズのメイン画面から。
キャンバスに、wb_Count をドロップして配置します。
いい感じの場所に配置したら、編集モードをGraphにします。
カスタムイベントを新しく追加。
配置したばかりのカウントWidget の関数を呼び出すだけの簡単なお仕事。
このイベントは外から呼び出されるようにしています。いわば中継ってやつです。
これで、wb_Main の編集は完了です。
コンパイルして問題がなければ保存します。
つぎは、正解演出画面。
目立つ位置に置きました。
ここで提案。
演出表示が増えてきました。
緊張感のある回答画面から遷移してきて、時間も止まっているので一息つける画面でもあります。
NEXTボタンの表示タイミングに間を開けるのが気遣いというものです。※諸説あり
デフォルト状態として Visibility を Hidden にしておきます。
キャンバスはここまで。
編集モードを Graph に切り替えます。
こうなっています。
ここに、カウントのエフェクト再生とNEXTボタンの表示にタイムラグをつくるDelayノードをつなぎます。
PlayAnimation ノード と wb_Count の NewGetWithEffect イベントを呼び出したあと、Delay ノードで、少し待ってから、 NEXTボタンの Visibility を Visible に変えます。
NEXTボタンを押したときのイベント OnClicked で、再び NEXTボタンの Visibility を Hidden に戻しておきます。
Delayノードは、流れに一呼吸おいたりしてリズムを作ることができるので、演出を重視したい時には大変重宝しますが、使用には注意が必要です。
- 後ろの処理は待たされます(処理にポーズがかかるイメージ)
- 関数の中では使用できません(右上の時計アイコンが目印)
一定時間後に○○ という場合は、Set Timer by Event や Set Timer by Function との違いを理解して使いこなせるとカッコいいと思います。
NewGetWithEffect イベントには パラメータを受け取るようになっているので、どこかからパラメータ用の値を持ってこないといけません。
先頭のイベント StartAnim はBPインターフェイスで作ったものです。
クリックしても Detailsタブは空欄です。
ここでBPインターフェイスを再び編集します。
エディタを開いたら、Inputs のところから +ボタンを押して Integer型を選択します。
コンパイルして保存したら閉じます。
再び、wb_Right に戻ってみると、
ピンが追加されています。
このピンと、カウントWidgetのイベントをつなぎます。
まっすぐつなぐとノードと重なるので、リルートノードを使っています。
最後に繰り返し遊ぶときのために、カウントの初期化イベントを用意します。
この正解演出用のWidgetは これで編集終了です。
コンパイルして保存します。
つなげる
組み込んだカウントを機能させるために、クイズ本体 wb_QuizGame を編集します。
まずは 編集モードを Graph にします。
カウント数を管理するための、Integer型の変数をひとつ追加します。
最初にカウントアップしたいのは正解演出画面なので、
正解演出画面に移行するタイミングで カウントをプラスします。
正解ボタンを押したときだけ、Count変数に +1 します。
そして、そのプラスされた値を、StartAnim 関数に渡してやります。
足し算するための +ノードは、 int + int で探します。
次に正解演出画面から帰ってきたときの処理。
ここでようやくメイン画面 wb_Main のカウントを増やしてやります。
メイン画面に切り替えるのは、正解演出画面と、誤答演出画面からの帰り道。
同じところに戻ってくるので、ここを切り分けます。
正解画面専用のカスタムイベントを追加します。
ちょっと狭くなってきたので、少しスペースを作りました。
このイベントでやることは、カウントが規定数に達したかどうかを判定することです。
≦ (小なりイコール)で判定します。 ノード検索は半角で <= と入力します。
これで 3 以下 か 3 と同じなら True 、それ以外なら False という判定結果になります。
入力ピンが縦に並ぶので直感的じゃないですが、こうなってます。
判定結果が True ということは、まだノルマは達成していないので、
ここで、メイン画面のカウントを更新します。そのまま元の流れに戻します。
判定結果が False の場合。つまりは Count に 4 という値が入っているときです。
正解演出画面にいく直前に Count は +1 するので、3の状態で正解すると、まず 4 になってカウントアップ演出を見せてから戻ってきます。
ここで メイン画面に行くのではなく、ゲームクリア画面に行けばいいわけです。
いったんこの状態でコンパイルして確認してみましょう。
まだ設問が一つでさみしいですが、想定通りになっているでしょうか?
Congratulations!
クリアおめでとう画面を作ります。
構造的に変わったことをしないのと、タイトル画面に戻るボタンを置くので、 wb_Title を複製して改造することにしました。
名前は wb_Clear にしました。
やっぱり紙吹雪とか散らしたくなりますね。
いい感じの画像でも配置してみてください。
ここはオマケのオマケでテクスチャを使わないマテリアルを使ったキラリをご紹介。
マテリアルエディタの操作の説明は省くので要望があれば別の記事で説明します。
コンテンツブラウザで、Materialを作ったら、下のようにノードをつなぎます。
右端のノードを選択した状態で、 Detailsタブから
Material Domain を User Interface
Blend Mode を Transparent
にすると、Widgetで使用できるようになります。
コンパイルして保存したら、コンテンツブラウザから、直接Widgetのキャンバスにドラッグ&ドロップできます。
ブループリントは特に何もしないので、クリア画面はこれで完成です。
コンパイルして保存します。
画面を新しく作ったら、WidgetSwitcher に追加しないといけません。
ゲーム本体の wb_QuizGame を編集します。
編集モードを Designer にします。
WidgetSwitcher の上にドロップすると、一番下に追加されます。
いーなむの編集をします。
Newボタンを押してクリア用のいーなむを追加します。
保存したら閉じます。
ゲーム本体の wb_QuizGame に戻ります。
Branchノードの False に画面切り替えする関数をつなぎます。
NewScreenState に クリア画面のいーなむが追加されているので選択します。
つぎに、クリアおめでとう画面からの戻り先を用意します。
今回はタイムアップと同じにします。
あと少し
クリアしてもしなくても、タイトル画面に戻るようにしているので、繰り返し遊ぶことができます。
カウントをリセットしないと続きからカウントしてしまうので、カウントの初期化処理を入れます。場所はこのタイトル画面へ切り替えるところ。
変数を 0 に戻すのと、正解演出画面の表示を 0 個に戻します。
今回はこれで完成です。
このグラフの全貌です。
コンパイルして保存したら確認してみましょう。
今回はここまでです。
わからないところやツッコミなどあれば、このブログのコメント機能をご利用いただくか、Twitter(@MMAn_nin)にてメッセージを送ってください。
それなりにゲームぽくできたので、次回でおまけ編を最終回にしようと思います。
クイズゲームらしく設問を増やしてランダムに出題できるようにします。
ではでは
ステキなクリア画面ライフを!
そうだ、QUIZゲームを作ろう《おまけ編》クリアへの道 前編
引き続きクイズゲームを作っていきます。
ここでいったん過去記事リンク
『そうだ、UE4でQUIZゲームを作ろう』
1、2まではUMGのチュートリアル記事として書いてみたので、ある程度の基本的なオペレーションに慣れるのと、複数のWidget間のやり取りをゴールとしています。
3以降はおまけ編としていますが、実践的で使えそう(だといいな)なテクニックを紹介しつつそこそこクイズゲームらしく仕上げたいという思いで書いています。自転車の補助輪として見ていただけるといいのかなと。
目次
クリアの条件
タイマーを実装して緊張感を演出できるようになったところで、今回はゲームクリアを実装することにします。
始まりと終わりがあって、そこに成功と失敗が加われば最低限ゲームとしての体をなすことができます。
ゲームクリアには条件があったほうが達成感が得られて、嬉しくなりますよね。
そこでクリア条件を考えました。
クリア条件: 制限時間内に設定された正解数を達成すること
となると、失敗の条件はおのずと、タイムアップのみ。
タイマーのしくみを変える
というわけで、さっそく タイマーを改造するところから。
前回までのつくりでは、回答して戻ってくるたびに、タイマーがリセットされます。
それを、
回答ボタン押す → タイマーストップ → Main画面に戻ってくる → タイマー再開
という流れに改造します。
タイマーを再生するのは、Play Animationノード。
そこに Start at Time というパラメータが用意されています。
ここが 0.0 だと、頭から再生します。再生終了は 1.0 です。
ということは、ここにストップした時の時間=進捗率 を値にして入力すればよさそうです。
UMG では アニメーションをポーズした際に、どこで止まったかを知ることができるようになっています。
Pause Animationノードの ReturnValue を変数に取り置きしておいて、次回 Play Animation ノードに来たときに、Start at Time として渡すだけで再開できます。
ですが、今回はこの方法は使いません。0に戻す処理が必要だし、今後 wb_Main の部分的な表示パーツ になる予定なので、メンテナンスしやすいように、wb_Main から受け取る構造にします。
というわけで、Start at Time のピンをカスタムイベントとつなぎます。
これでタイマーは改造終了です。
ラインのクロスを解消するのは、カスタムイベントを選択状態にして、Detailsタブから行います。
コンパイルして問題なければ、保存して閉じてOK。
タイマーの制御
となると、タイマーのカスタムイベントに異変発生!ということで、きっと大騒ぎのはず・・・
wb_Main を開こうとすると、
アセットのアイコンに未保存のマークがついています。
参照される側が更新されたので、参照してる側としてはコンパイルし直してね、という意味です。
開いてみるとピンが増えてるのが確認できます。
※《おまけ編》 時間制限をつける の最後のほうで変数管理にした状態で説明します。
ここで、残り時間を保持するための変数を一つ追加します。
変数の増やし方は
Variablesリストの右上にある +ボタンを押します。
名前は RemainTime
タイプは Float を選択
残り時間をゲットして、この変数にセットすることになるのですが、場所は回答ボタンを押した直後。
まずVariablesリストからグラフにドロップして取り出しますが、Set~ を選択します。
Alt キーを押しながらドロップすると、選択の手間がスキップできてラクチンです。
ちなみに Ctrl キーを押しながらだと、 Getタイプでノード化できます。
次に、白い4本のラインをつなぎ替えるのですが、スマートにやりましょう。
五角形の白いピンの上で、Ctrlキーを押しながらドラッグを開始すると、複数のラインがまとめて動かせます。
セットしたタイマーをリセットする Clear~ノードを後ろにつないで挿入完了。
実は タイマーハンドラーから、残り時間を取り出せるのですが、クリアしてしまうと 0 になってしまうために、クリアする直前でGetする必要があるのです。
Get Timer Rimaining Time by Handle ノードを取り出して変数に入力します。
ここでアニメーションも止めないといけません。
ノードの配置を整えつつ wb_Timer に用意したイベント StopTimer を呼び出します。
中央上部にある青い玉は、リルートノードといって、ラインの流れを意図的に変えることができる便利なノードです。ラインの上でダブルクリックすると間に挿入されるのであとはドラッグしていい感じのポジションに移動させるとつながりが整理できます。
これで 残り時間保存 → タイマーストップ → アニメーションストップ の流れができました。
一見するとスマートにまとまってる気がしますが、タイムアップがゲームオーバーになるという時間にシビアなゲーム性なので、時間を止めるまでの処理は少ないに越したことはありません。クリックした瞬間に止まってほしいと思うはず・・・。
ブループリントマクロを使う
そこで順番を変えようと思います。画面切り替え通知のイベントディスパッチャーの前にしたいと思います。
といっても、コピペで並べるようなことはしません。マクロを使います。
マウスでドラッグしてマクロにするノードをまとめて選択します。
どれでもいいので、ノードの上で右クリックして、Collapse to Macro を選択。
すると
ひとつのノードにまとまりました。
ダブルクリックして中身を整えます。
右端のOutputノードまでがつながっていないのでつなぎます。
これでマクロ編集終了です。
元のグラフに戻るには、 マクロのタブを閉じるか、Event Graphのタブにきりかえます。
マクロの名前を変えておきます。
エディタ左の Macros リストのところに作ったばかりのマクロが追加されているので、右クリックするか、F12キーでリネームします。
pauseTime と命名しました。
次につながったラインを切ります。
右クリックでまとめて切ることができます。
このマクロを間に入れます。
残りの分もグラフにマクロをドロップして同じようにつなぐのですが、ちょっとだけラクをしましょう。
カスタムイベントのピンからドラッグして検索するだけでも、挿入できます。
こうなりました。
マクロは、同じノードのまとまりが、別の場所でも使われるときに便利です。
いくつかのノードがまとまると、ある一つのタスクになります。タスクに名前を付けて管理できるので、何をしているか分かり易くなるし、ブループリントがスッキリします。マクロは関数やイベントと違い、自身以外のアセットから呼び出せません。ローカルな存在です。
停止から再開へ
あとは再開した時の処理。
その前に、残り時間を保持する変数は、初期値を制限時間と同じにしておかないと、残り時間がゼロでゲームスタートしてしまいます。
Event Construct で 2つの変数 RemainTime と Seconds の値を揃えておきます。
これで、Seconds の初期値をいじっても、RemainTime も同じ値にできます。
10秒かな、15秒かな~とかいろいろ試すには、Seconds の値をいじるだけで済みます。
改めて再開処理。
これも下の部分を改造していきます。
まず Set Timer by Event の Time につながっているのを、 RemainTime に替えます。
つぎは Start at Time です。
変数 RemainTime には 残りの秒数が値として持っています。wb_Timer では PlayAnimation ノードでアニメーションを管理するのですが、再開するために時間指定ではなく進捗率を指定することになります。進捗率はアニメーションがすべて再生したときに 1.0 になる値です。 ちょっと面倒ですが計算してから渡すことになります。
例えば、制限時間が 10秒で、たまたま 6秒が経過したときに回答ボタンを押した場合。
残り 4.0秒 という数字を何とかして 進捗率である 0.6 に変えなくてはいけません。
そこでこの計算で進捗率を出します。
ノードにすると、こうなります。
タイマーの改造はこれで完了です。
これでコンパイルしてテストしてみましょう。
回答ボタンを押して、結果表示から戻ってきた時に、ちゃんと続きからタイマーが動いていればバッチリ。
カウント表示をつくる
タイムアップの流れができたところで、新しく正解数をカウントする表示を作ります。
新しくWidgetブループリントを用意します
アセット名は wb_Count としました。
『ノルマ』にしようかと英語のスペルを調べてみると、普段よく使ってる割にあまりいい歴史背景の言葉じゃなかったのでしばらく彷徨った結果『カウント』にしました。いろいろ使いまわしの利く単語でもあるということで。
見た目には『数字』と『ゲージ(アイコンタイプ)』のどちらにしようか考えてみます。
制限時間があるし、どうしてもクイズゲームは文章を読む時間が多くなります。少しでも脳に情報処理負荷をかけないほうが喜ばれると思うので、『ゲージ』 を採用します。
『数字』 は分母がどれだけ増えてもコンパクトで場所を取らないのがメリットですが、数字の表記から「あとどれくらい」を判断する必要があるので、直感的な把握には向いていません。ゲームのテンポにゆとりがあったり、判断できる時間のあるゲーム画面で採用できます。
一方の 『ゲージ』 は全体の長さ(量)がネックになります。
あまり多くなるようだと、アイコンではなく棒ゲージにしたほうが良さそうですが、クイズゲームでそれほど多くならない想定。長くなるとしたら、おそらくラスボス戦。その場合だけノルマ制を廃止して、ボスに体力ゲージを設定、それを削った方が特別感が出て盛り上がると思います。時間いっぱいになると大技喰らってGAME OVER。
そういった、シチュエーションの変化で、UIの構成が変わることは日常茶飯事なので、いろんなアイデアを持っておくといいことありますよ。
アイコンタイプのゲージを採用するということで画像を2つ用意しました。
「あるとき」左と「ないとき」右。
ブログの下敷きと同じ白なので見えないけどいます。
右クリックで保存してお使いください。
画像を2つUE4にインポートしたら、キャンバスに配置します。
ノルマの数を可変させたい場合は HorizontalBox を使ってレイアウトすると可能ですが、説明が長くなりそうなので今回は固定の数で進めます。
Image を4つ等間隔に並べて、「ないとき」のテクスチャをセットします。
テクスチャが 64x64px スキマは 16pxなので、80px ずつ並んでいます。
「COUNT」 は Text、下線 は Image で作りました。この2つは装飾です。
カウントアップ演出
ここにエフェクトキャラを追加します。
もう一つ Image を追加します。テクスチャは「あるとき」です。
通常は非表示なのと、ブループリントで位置を変更するので、場所はどこでもよいのですが、レイアウトするときに余計なサイズ計算されて配置しにくいので、左上に重ねて置きました。
通常で非表示にするには、DetailsタブにあるBehabior(ビヘイビア)のVisibility(ビジビリティ)を Collapse にしておきます。
Collapse はゲームを再生中に存在しないものとして扱われるので、処理負荷軽減になります。一方同じ非表示設定の Hidden は見た目に見えないだけなので、Collapse よりは負荷が高くなります。
このエフェクトはたまにしか出ないうえに、他のパーツのレイアウトに干渉しないので、Collapseを選択しました。
そしてアニメーションを付けます。名前は NEWGET としました。
Visibility と、 Transform の Scale 、 Render Opacity の3つ。
ヒエラルキーはこうなってます。
ちょっと気づきにくいですが、飾りの下線とCOUNTの文字は、Is Variable を無効にしているので、[ ] がつけられています。
これでキャンバスでのレイアウト作業は完了です。
編集モードをGraph に変えます。
ふるまいを作る
まず Image を 配列 にします。
Variablesのリストに並んでいる Image を 4つ グラフにドロップします。
順番は左から順番に扱うので配列にする際にタテに並べます。
キャンバスにはこう並んでました。
前もって、 変数タイプがImage の配列変数を作ってもOKですが、変数の型がよくわからないうちは、逆引きみたいな作り方がオススメ。
名前を変更して Event Pre Construct につなぎます。
Images と命名。
配列 というのは、同じタイプの Variable(変数)がたくさん存在していて、いちいち名前を付けて管理するのが面倒なときに、配列という形態にします。上のMake Array ノードのピンに書かれているように [0] [1] [2] [3] と番号をつけて管理するのです。この番号のことを Index番号とか 添字(そえじ)と呼びます。
身近な例で、コインロッカー がイメージしやすいと思います。
コインロッカーは入りさえすればなんでも入れられますが、配列変数の場合は、型(タイプ)が1種類固定なので、どちらかといえば下駄箱のほうがイメージが近いかも。
ここで関数を用意します。
この関数は指定したカウント分の画像を「ないとき」から「あるとき」に切り替えます。
エディタ左の My Blueprintタブにある Functionsリスト の +Function ボタンをクリックします。
名前は SetCount にしました。
で、最終的にこうなります。
順番にいきますね。
まずは For Loop ノード。とてもメジャーなのでよく出会います。
指定した回数だけ繰り返すノードです。
ループについて
ノード右にある Loop Body から先の処理 をぶん回します。
回数分回すと Complete に移ります。
ループ処理が終わった後の処理は、 Complete 以降につないでいきます。
Loop Body の先には、できるかぎり単純で軽くてレスポンスのいい仕事をさせるようにします。
特にアニメーションやDelayなど時間を扱うような処理はForLoopには向いていません。コンパイルは通るけど、玉突きが起こって引きつったり警告がでたりします。
回す回数は、 First Index と Last Index という2つのパラメータで指示します。
First Index がループ開始の番号。
陸上のトラックをIndex番号がゼッケンに書かれたランナーがリレーで走るところをイメージしてみてください。
First Index が0 つまり最初のランナーのゼッケン が 0番ということです。
0番のランナーが無事走り終えると、ゴールに待ち構えていた Last Index と比較します。0番とは違うので、次は ゼッケン1番のランナーが走ります。帰ってきたら、またLast Index と比較。違うので次2番。という風に順番にカウントを増やしながら次々走っていきます。
ある時 Last Index と同じ ゼッケン番号のランナーが帰ってきました。そのタイミングでようやく For Loop は仕事を終えることができます。
今回は ノルマの表示が 4つ なので、計 4回 まわします。
なので、 First Index は 0(初期値) Last Index は 3 になります。
First Index は 0以外の値ももちろんOKです。
First Index を 1 にすると、Last Index はまんま 回数と同じ値になります。
なぜ 0 始まりなのか?それは 配列の Index が 0 始まりで、ForLoopと相性がいいからです。他にも 0始まりが便利な場面がたくさんあります。
次は、配列の中身を取り出す Get ノード。
配列は Index番号を指定するので、専用の Get ノードが用意されています。
まず Variablesリスト にある Images を グラフにドロップします。 Get タイプで。
For Loopノード右(出力側)にある Index ピンとつなぎます。
これで配列の中身 Index 0~3 を順番に取り出すことができます。
取り出したものをどうするか?
画像を差し替えるためのノード、 Set Brush from Texture ノードをつなぎます。
Image に直接テクスチャを渡してくれるノードです。
次にこの左にある Texture ピンからドラッグして Select ノードを取り出します。
Select ノードはつなぐ対象によって見た目が変化します。慣れないうちはつないでいいのか戸惑うと思うので、不安を感じる場合、この方法だとひと手間減るのでオススメです。
Option ~ のピンのところに テクスチャを セットします。
セットする方法は 3つあります。
- Select Asset のプルダウンから探す
- コンテンツブラウザで選んでおいてから白い矢印ボタンをクリック
- コンテンツブラウザからドラッグして直接ドロップ
Selectノードは条件によってReturnValue から出されるデータを変えることができます。
その条件とは、
指示されたカウント数とForLoop の Index番号と比較した結果です。
というわけで 指示してもらうために、この関数に 値を受け取るための Inputs を一つ追加します。
変数のタイプ(型)は Integer(整数)、名前は Count としました。
あとは 条件式 をつなげば完成。
Count を いきなり マイナス 1 しているのは、個数を 配列に合わせるためです。
<= は 「小なりイコール」です。日本では「 ≦ 」で習いますね。プログラミングでは使わないのでキータイプする際は半角の < と = を並べて書きます。
条件の評価について
どうなっているかというと、例えば カウントが 2個 の時。
今回 ForLoopでは 4回 処理します。毎回 Index番号と比較するのです。
実は Selectノードと小なりイコールのノードをつないだ瞬間、Selectノードは変化していたのです。
ちなみに0個の指示が来たときはこうなります。
4個だと すべて True になります。
Selectノードは判定結果にもとづいて渡す内容を振り分けてくれるので結構便利なヤツなのです。
これでこの関数は完成です。
この関数さえあれば、必要な機能は揃ったことにしてもいいのですが、あとひとつ
アニメーションの再生が残っていました。
イベント グラフに戻ります。
呼び出されたときのために
Integer(整数)型の変数を一つ用意します。
名前をCount にしました。
この変数は、表示してほしい数を受け取って保持するために使います。
新しくカスタムイベントを用意してそこにつなぎます。
カスタムイベントは NewGetWithEffectと命名。
そろそろ基本的な操作は慣れてきたころだと思うので、少しずつテンポアップします。
つぎにやるのは、エフェクトを新しくゲットするカウントのところに置きなおす処理。
UMGのキャンバスに配置したものは、Position というパラメータで管理しています。
なので、ここを書き換えるときは、Slot as Canvas Slot というノードを間に挟むのが基本になります。
この場合、いきなり Slot as ~ ノードを探すより、
Image Effect をドロップしてから、Slot as ~ をドラッグで取り出して、そこから Set Position です。
次に 指定する座標(In Position)が 0, 0 のままだといけないので、キャンバスに並べたImage から取得します。
Set Position ノードがあるということは 、Get Position もあるということになります。
ここでもマイナス1して配列から取り出します。
実は0個の指定でこのイベントが呼ばれると、Index に -1 という番号は存在しないので、エラーになります。
4個までしか置いていないので、当然 5以上の指定でも Index番号[4]以降が存在しないのでエラーになります。
そもそも新しく追加するのに 0個の指定はおかしいし、その場合指定する側が修正すれればいいのです。5個以上については、今後の拡張性を決めてからでも問題ないでしょう。今回はひとまず4個獲得したらゲームクリアにするので、5以上の値がくることもないはずです。そのクリアの判定はこのWidgetで行うことでもありません。
ちなみに
先の関数で -1 を許容していたのは、Index番号として使用しないからです。ForLoopから出てきたIndex値は 0~3 で、配列の範囲を越えることがないためエラーはでません。
ここまでで、指定した個数のところにエフェクトを置くことができました。
ようやくPlay Animation です。
Get End Time ノードは アニメーションの終了時間を教えてくれるノードです。
なので、 Set Timer by Event に渡すと、アニメーションの再生が終わったタイミングで任意のイベントを呼び出すことができます。
Play Animationノードは再生を開始するだけして処理は次のノードに移ります。
ほぼタイムラグを意識することなくタイマーを発動させることができます。
UIではアニメーションが終わったら○○して、それが終わったら今度は◇◇で、という風に演出の終わりを待つことがよくあります。上記のはそういう場合の対策のうちの一つです。個人的にメンテナンスがしやすいと思うのでよく使います。
あと少し
Set Timer by Event にカスタムイベントをつないでいきます。
仕上げはこのようになります。
演出のアニメーションが終了しているので次回のために、エフェクトは非表示(Collapse)にします。タイムラインでVisibility を 表示(Visible)にしているので、流れ的にちゃんと交互になります。
初期状態: Collapse
この 表示・非表示のタイミング制御が結構表示系バグのなかでも特にメジャーなやつという印象があります。Visibility の状態は タイムラインでしかやらない、ブループリントだけで制御する、という縛りを作ったほうがいい場合もあります。
最後に、先に用意しておいた関数 SetCount をつなぎます。
これで準備完了。
説明が長くなりましたが、イベントグラフの全貌はこれだけです。
Event Construct と Event Tick は使いません。
この wb_Count の仕様をまとめると、
- 表示したい個数を指示された → SetCount 関数を呼び出してもらう
- カウントアップしたい → NewGetWithEffect イベントを呼び出してもらう
- 個数は 0~4 で指定してもらう
コンパイルして保存したら閉じて大丈夫。
あとはこのカウントを表示したい場所に仕込むだけですが、さすがに長くなったので、前後編に分けることにしました。
今回はこの辺までにします。
次回 クリアへの道 後編 で組み込んでいきます。
そんなに間があかないように頑張りますので、お付き合いくださる方はもうしばしお待ちいただけると嬉しいです。
記事内容について、わからないところや、ツッコミ等あれば当ブログのコメント機能か、Twitterの方からメッセージいただけると対応します。お気軽にどうぞ~
ではでは
ステキなカウンター表示ライフを!
そうだ、UE4でQUIZゲームを作ろう《おまけ編》時間制限をつける
クイズゲームには時間制限は当たり前ということで、作ってみたいと思います。
表現方法によっていろんな作り方があるので、どういう形にしようか迷ってしまいました。
ただ一口にタイマーといっても、目立ち具合というか、主張する強度みたいなのがあって、レースゲームみたいなタイムアタック系のゲームと脱出劇や爆弾解除みたいな時限系のシチュエーションとは与える印象は変えた方がいいですよね。この辺またどこかで記事が書けたら書こうと思います。
チュートリアルって最適解なものを無駄なく教える場というのが求められているんだろうなとは感じつつ、ゲームUIのように解がたくさんあるとチョイスがむずかしいのですよ、と一応言い訳を。
シンプルにUMG使ったやつと、マテリアルを使ったやつ、どちらにしようかちょいとばかし悩んで、UMGだけで作るのを紹介しようと思います。
見た目はゲージタイプですが、プログレスバーは使用しません。アニメーションを使いつつ、あとから調整できるタイマーにします。
仕様としては、
- 一定の秒数をゲージで表現
- 時間の長さを調整可能
- 徐々に短くなることで焦らせる
- 0になるとタイムオーバー
まだノルマの仕様が入ってませんが、タイマーができたら実装してもいいかな。いずれ問題もたくさん仕込んでランダムチョイスもやりたいので、今のうちから問題を集めておくとしましょう。
目次
専用のWidget
新しくタイマー用のWidgetブループリントを用意します。
wb_Timer と命名。
さっそくキャンバスに Image を 2つ 配置します。
ひとつは下敷き、ひとつは伸び縮みするゲージ本体です。
それぞれ、 Image_Gauge、 Image_base と命名。
いい感じにカラーをつけて重ねます。
下図は Text を配置していますが飾りなのでお好みでアレンジしてみてください。
テキストの代わりに画像が用意できるなら、ストップウォッチのアイコンとかが合いそうです。
ゲージ本体と下敷きの重ね方次第で立体的に見せることもできます。
キャンバスにはこのようにレイアウトしました。
参考までに、
ゲージ本体の長さ(Size X)は 1200 、高さ(Size Y)は 8
ゲージ下敷きの長さは 1200 、高さは 16
一度ここでコンパイルして保存します。
クイズのメイン画面担当のWidgetに配置してみましょう。
編集モードをDesignerにして、新しく作った wb_Timer を 探してドロップします。
サイズ感や色味を確認して気になるようだったら戻って調整します。
OKなら、そのままコンパイルして保存して、wb_Timer に戻ります。
ゲージが減るアニメーション
ゲージ本体の Image に 長さを変えるアニメーションを付けます。
ゲージの長さを変えるには、Detailsタブの下のほうにある、Render Transform の Scale X を変化させます。
ただ、初期状態で Pivot X の 値が 0.5 なので、ゲージの真ん中が中心になっています。
そこで、Pivot X の値を 0.0 に変えます。
Pivot はそのパーツを変形させる基準となる点です。
同じくDetailsタブの Slot(Canvas Panel Slot)にある Alignment(アライメント) と同じ認識で問題ないと思います。
Alignment はレイアウトのために使用するパラメータで、Pivot は変形のためのパラメータという扱いです。
パーツの左上が (0, 0)右下が (1, 1)になるので、初期値の(0.5, 0.5)というのは、ド真ん中になります。
この状態でScaleのアニメーションを付けます。
水平方向だけなので、 Scale X に 1.0 → 0.0 の変化をつけます。 尺 は 1 秒。
この ボタンをクリックしてCurveエディタを開くと確認できるのですが
UE4は初期状態でEase In-Out が設定されています。
タイマー表示に緩急は不要なので、リニアに変えます。
このカーブエディタからも操作できますが、普段のTrack編集中でも変更できます。
Keyを打ったポイントを選択して、右クリックメニューから Linear を選びます。
Curveエディタの場合は、Keyを打ったポイントを選択して、右上にある、アイコンボタンからリニアにできます。
1秒のアニメーションなんて一瞬です。
どんだけ早押しゲーだよ!無理ゲーだよ! という声が聞こえてきそうですが、これにはブループリントで答えることにしましょう。
おっと、その前にひとつやることがありました。
ゲージ本体の初期状態を確認します。
アニメーションを選択していない状態で、
ゲージ本体の Scale X を 0.0 にします。
見た目にサイズが 0 になって消失します。
これは、アニメーションを再生しきった状態と同じにしておくためです。
UMGのアニメーションは再生終了すると、自動でデフォルト状態に戻ります。
その時、デフォルト状態が満タンな見た目だと、せっかく減らしたゲージが回復したように見えてしまうのです。
では気を取りなおして 編集モードをGraph に切り替えます。
まず、何もないところで右クリックしてカスタムイベントを取り出します。
Add Event の中にあります。上の方にあるので検索しなくてもすぐ見つかるはず。
イベント名は、 startTimer と名付けました。
このイベントで アニメーションを再生させます。
ちょっとひと工夫
再生するだけならここまでなんですが、ひと工夫加えます。
カスタムイベントにパラメータ(引数)のピンを追加します。
Detailsタブから Inputs で + ボタンを押す以外にグラフ内でドラッグする方法を紹介していおきます。
型がわかっていなくても正確にピンが追加できるので、結構便利です。
間に計算ノードを追加するので、つないでから切断してます。
ピンの名前を Seconds (秒)に変えます。
カスタムイベントからは、秒数を受け取る想定。
PlaybackSpeed は 1.0 (初期値)が 通常のスピード。
2.0 で 2倍速。 0.5 で1/2の速度でスロー再生になります。
ということで、割り算のノードを追加します。
割り算は / (半角スラッシュ)で検索できます。
カスタムイベントのパラメータと、PlaybackSpeed が float型(浮動小数)なので、 float / float を選択する必要があります。
秒数をプレイバックスピードに変換するには、 1.0 を秒数で割ります。
用意したアニメーションの尺は 1秒。
例えば5秒の再生速度にしたい場合 1÷5 ということになって、 PlaybackSpeed は 0.2 です。
本来のスピードの1/5の速度ということで、5倍の時間をかけるとようやく1秒の長さを再生したことになります。まぁゆっくり再生します。
割り算というのはちょっとイメージしにくいですよね。
この仕様にしておくとメリットがあります。
- あとからタイマーの長さを調整しやすい
- ゲーム中にアイテム使用やデバフ、ボス戦等、特別な状況で変更が可能
- アニメーションのトラックを再編集しなくていい
- タイマーの終了タイミングを管理しやすい
最初から最後まで制限時間が変化しないのであれば、決め打ちでタイムラインにキーを打っても大丈夫ですが、親のWidgetとの連携が少しだけ複雑になるので、今回はシンプルに作ります。
あと必要そうなイベントを2つ用意します。
readyTimer
アニメーションがスタートしないと満タンにならないので、一時的に満タン状態にするカスタムイベントです。今はクイズの設問をいきなり表示していますが、「第1問」とか演出が増えるかもしれないので、一応用意しておいてもいいかな~という程度です。
stopTimer
何かしらのポーズがかかったり、タイムボーナスの計算したりするときに必要になりそう、という程度です。
残りをつなぐとこうなります
Image_Gauge はキャンバスに配置した ゲージ本体 の Image です。
Scale に 1.0 を入れると、本来のサイズで表示してくれます。
アニメーションを停止するのは Pause Animeation ノードです。
Stop Animation ノードが用意されているのですが、こちらは停止したあと、状態をアニメーションする前の初期状態にリセットしてしまうので、今回の場合ゲージが消えてしまうことになります。時間が止まった感を出すためにポーズさせることにしました。
ちなみに
イベント名を ”stopTimer” にしているのはノード名とちぐはぐな感じですが、イメージとの距離感で、”stop” という言葉を採用しました。イベント名を ”pauseTimer” にすると再開を期待したりしそうだし、クイズゲームというのもあって、ポーズ状態を見せることもないのが理由だったりします。まぁ後々なるべく誤解が起こりにくいのがいいなぁという程度の考えです。こういうイベントや関数は自身で呼ぶより、他の場所から呼び出して使うことが多いですし。
タイマーはこれで完成です。
コンパイルして問題なければ保存します。
実装
早く減らすところを見てみたいので、wb_Main を編集していきます。
カスタムイベントを追加します。
wb_Timer が持っているカスタムイベント startTimer を呼び出しています。
制限時間は10 秒ということでパラメータに 10 を入力しています。
関数ノードやイベントノードを取り出したい場合、先にVariablesリストにいる wb_Timer を Getタイプで取り出して、ドラッグして検索すると見つけやすいです。wb_Timer は、キャンバスに置くと同時に、Variables リストに追加されます。
エンジンの言語環境が English の場合は、ざっくりと "call" で検索できて便利です。
これでコンパイルして問題なければ保存します。
次にタイマーをスタートさせるために、ゲーム本体の wb_QuizGame を編集します。
タイマーをスタートさせるタイミングは 2か所。
メイン画面に切り替わるタイミング。
まずは 最初のタイトル画面でSTARTボタンを押したとき。
以下の画像はこちらの記事を適用しています。
そうだ、UE4でQUIZゲームを作ろう《おまけ編》いーなむで切り替えをわかりやすく - みつまめ杏仁
もうひとつは、回答ボタンを押して、正解か不正解かの画面から帰ってきたとき。
ひとまずこれで確認できるはず。
コンパイルして再生してみましょう。
無事動いたのを確認したら、タイムアップ画面を追加します。
タイムアップ画面
ちょっとラクするために正解演出画面の wb_Right を複製することにします。
テキストを 『TIME UP』に変更して、アニメーションもちょっといじってみる。
これで編集終了。
コンパイルして保存します。
これを WidgetSwitcher に追加します。
Paletteタブの UserCreated から wb_TimeUp を探します。
いーなむにも登録します。
これで切り替える準備完了です。
今 切り替えのつながりのフローイメージはこうなりました。
まだこの状態では切り替わりません。
切り替えを行うのは → wb_QuizGame
タイマーをコントロールしているのは → wb_Main
ということで、wb_Main がタイムアップとした時に、wb_QuizGame に通知してやる必要があります。すでに wb_Main は 4つの回答ボタンをクリックしたとき wb_QuizGame に通知しています。このイベントディスパッチャーに相乗りできると、新たにイベントディスパッチャーとバインドのセットを新設しなくても済みそうです。時間切れも無回答というひとつの答えですし。
切り替えるためには
ということで、今度は wb_Main を編集します。
回答ボタン用のイベントディスパッチャーを選択して
Detailsタブから、 Inputsのパラメータを追加します。
変数のタイプは Boolean。
パラメータ名を TimeUP にしました。
パラメータ名を変更してコンパイルすると、エラーが出てしまいます。
かといって New Paramのままだと後で意味が分からなくなるので、ここでエラーをつぶしておきます。
グラフを見てみると、置いたやつ全部エラーになってますね。
よくみると、追加したパラメータの名前が New Param のままです。更新が中途半端に行われた感じに見えます。仕方ないので、ノードをリフレッシュしてやります。
ERROR! になっているノードの上で右クリックして Refresh Nodes を選択。
これでコンパイルエラーは消えます。
あとはタイムアップのタイミングでこのイベントディスパッチャーを Call すればよさそうです。
QuizStartのカスタムイベントのところで、タイマーを開始しているので、ここに仕込むことにします。
Set Timer by Event ノードを使います。
これは 指定した時間が経つと、カスタムイベント を呼び出してくれるノードです。
カスタムイベント は バインドしたときと同じようにつなぎます。
ここにイベントディスパッチャーをつなぎます。
TimeUp にチェックをつけておきます。
いったんコンパイルして保存したら、
クイズゲーム本体でタイムアップ画面に切り替える処理を作ります。
回答ボタンを受け取るバインド処理のところに変化が!
ここの ブランチノードの前にタイムアップの分岐を挿入します。
タイムアップ優先で判定します。
正解、不正解で使ってる 画面切り替え用の関数を使います。いーなむを登録しているので、TimeUpを選ぶだけ。簡単!
カスタムイベントの赤いラインのクロスが気になる場合は wb_Main のイベントディスパッチャーの方で順番を変えてやると解消できます。
コンパイルして保存すると反映されます。
ここで再生して確認してみましょうか。
無事切り替わりました。
かんせー! と叫びたいところですが、バグを発見。
普通に時間切れだと問題ないのですが、回答してNextボタンを押さずに放置していると、きっちり時間が来てタイムアップ画面に切り替わります。
真面目過ぎ!と突っ込みたくなりますが、タイマーを使っているのが原因なので対策を講じます。
Set Timer by Event ノードにある Return Value ピンからドラッグして、変数を作ります。Promote to Variable は日本語環境だと 「変数へ昇格」となります。
この変数を TimerHandler と命名。制御するので ハンドラー です。
こうしておくと Set Timer by Event ノードが見えないところでやってるタイムカウントに介入することができます。
いろいろ制御できるのですが、今回やりたいのは、タイマーのキャンセル。
ノードだと Clear and Invalidate Timer by Event というノードになります。
この辺のワードは WebでJavaScript を経験された方ならなじみ深いかもしれません。
これを、回答ボタンイベントのところにつなぎます。
タイマーが動いている間に、回答ボタンを押すとタイマーをなかったことにします。
これで完成!
コンパイルして保存したら再生してみましょう。
最後にちょっとだけ
秒数の指定を変数にしておきます。
調整しやすくするためと、うっかり防止と、タイムボーナスなんかの対応に使用します。
この方法で変数を作ると、Default Value に自動的に値が入れられます。(※コンパイル後に確認可能)
この値を変更してコンパイルすると、タイマーの時間を自由に変更できます。
おつかれさまでした!タイマーの実装完了です。
ボタンを押して遷移するのに比べて難易度が上がった感じがしますね。
いろんなタイマーを紹介してみたいのですが、先に進まなくなりそうなのでここまでにします。
リクエスト頂ければどこかでぶっこみましょう。
わかりにくいとか質問やツッコミなどあれば、このブログのコメント機能かTwitterにてお待ちしています。
ではでは
今回はこの辺で
ステキなタイマーライフを!
そうだ、UE4でQUIZゲームを作ろう《おまけ編》ボタンのデザインをどうにかする
UE5の早期アクセスが公開されましたね。まだ触れていないので祝祭のツイートを眺めてるとマウスを操作する中指がつりそうです。UIがスッキリしてモダンな雰囲気を醸しているのがなんだかさみしくもあります。
UE4のアイコンはそれなりに丁寧で考え抜かれてる感があって好きです。似たような機能を差別化するには、ある程度情報量が必要で、UE5の開発者がどんな解決を見せてくれるかとても興味があります。
パネルの開閉に期待です。シングルモニターでの作業が捗りそうな予感。
エディタとしての操作方法が変わっても、UE4での経験値はそのまま引き継げそうな話もチラホラあるので、安心してUE4を触っていこうと思います。
単純な構造ではあるけどひとまず動くものができたので、見た目に手を入れていこうと思います。
UMGに馴れるのを主軸にして書いていこうと思っているので、まずはデフォルトのボタンデザインから作りかけ感がにじみ出るのを抑えることにします。テクスチャを作れる環境が必要となります。PNGをブログに貼っていますので、右クリック保存ができれば利用してみてください。テクスチャのインポート操作についても説明しています。
今回の内容は、以下の2つの記事の内容を作り終えた後のおまけ編となっています。
目次
- Buttonの見た目を変える方法
- テクスチャをインポート
- さあカスタマイズを始めようか
- まず、手っ取り早くコピペで!
- オリジナルのButtonに差し替える!
- いろんなデザインを試したい!
- 適用していく
Buttonの見た目を変える方法
UMGのボタンコンポーネントである Button の見た目を変える方法は、デザインを変更したい Button を選んでDetailsタブから各種パラメータをいじっていくのが基本の操作になります。
公式ドキュメントにさらっと書かれています。→ スタイリング
Appearance(アピアランス)の項目を編集します。
Normal ・・・ 通常時の見た目
Hovered ・・・ マウスカーソルが重なった時の見た目
Pressed ・・・ クリックした時の見た目
Disabled ・・・ 使用不可の時の見た目
それぞれ中を開けると
Image ・・・ テクスチャをセット
Draw As ・・・ 描画する方法 None / Box / Border / Image の4種
Margin ・・・ Draw As が Box または Border の時に設定可能
Tiling ・・・ Draw As が Image の時に設定可能
Draw As の Box と Border は 古いところで Flashでは Scale9Grid 、Unityだと 9Slice と同じものです。四隅を引き延ばすので小さくミニマムなテクスチャで高解像度な見栄えが出来上がります。
Border は 9分割した中央部が描画されないので、描画負荷を下げたいときに指定します。
ボタンひとつにつき、Normal、Hoverd、Pressed、Disabled(※必要に応じて) 毎に、テクスチャセット、Margin再設定 なので、結構手間を必要とします。
セットするだけで、このふるまいを得られるので安いかどうかは検討案件です。
Disabled は無い想定で3種類のテクスチャを用意しました。
サイズは 64x64ピクセル
↑ ブラウザからDLしてご利用いただけます
ゲーム開発には可能な限り PNG を避けておきたいと考えているのですが、3分クッキング的にブログに乗せられるフォーマットとしてPNG にしました。
いろいろ理由があって書きだすと長くなるのでやめておきます。
UE4にはちょっとだけステキな機能があるので、PNGでもリスクが減るのはうれしい限り。
テクスチャをインポート
テクスチャが用意できたら、UE4へのインポートが必要です。
アセットが増えてきたので、テクスチャ専用のフォルダを作ります。
まずは、コンテンツブラウザの何もないところで右クリックして新しいフォルダを作成します。
Widget 専用テクスチャなので、 Widgets フォルダの中がおすすめ。
メッシュやテクスチャなど、外部のDCC系アプリでテクスチャを作成することになりますが、UE4で扱う場合、Windowsのエクスプローラでファイルをコピーしたり移動するのはできません。かならず インポート という操作を経て、UE4内で使用可能なアセットに変換する必要があります。このアセットのことを中間データと言ったりします。
ファイル形式をもとに説明すると PSD は完全に作業データです。
ゲーム制作においては、TGA や PNG 、DDS も 中間データ に含みますが、しいて分類にするなら、テクスチャインポート用データといったところでしょうか。
ゲームハードやサービスごとにネイティブなデータ形式がそれぞれ存在します。
ゲームエンジンがパッケージを作る際にターゲットになったプラットフォームに合わせて最適化してくれます。
使い勝手のいい状態を維持しつつ、用途に応じたいろんなメタデータをくっつけたり、メモリ使用量をコントロールしたりするために、インポート という操作フローが必須なのです。
PSDのような自由度の高い作業データは、思い出や不安がたくさん詰まっています。
プロジェクトフォルダの中に直接PSDを置かないのは、そういった個人的な感傷を共有の場にコミットさせないということでもあります。そんなセンシティブで巨大なPhotoshopデータはそっとしておくワークフローになっているのが素敵です。
続きに戻りましょう
ダウブルクリックしてフォルダを開いたら
また、何もないところを右クリックします。
Import to ~ (~は今開いている場所=フォルダパス)を選択すると、Windows形式のファイル選択ダイアログが出てくるの、インポートしたいPNGファイルを選択します。
開くと
コンテンツブラウザにアイコンが現れます。
ダブルクリックするとインポート設定のダイアログが開きます。
確実にチェックしておきたいのが、品質 と ミップマップ
まずは上から
UMGで使用するため、
Compression Settings は UserInterface2D(RGBA) を選択します。
次は、サイズとPNG、sRGB対策
Power Of Two Mode は、自動判別されるので、基本気にしなくても大丈夫ですが、2のべき乗の対応が必要になればここを変更します。強制的に2のべき乗サイズのテクスチャにできます。
PNGをインポートした場合に限り、Padding Color が大事になります。
Photoshopが出力するPNG形式は、完全透明の部分のカラーが白になるので、BC5などピクセル圧縮が掛かると、意図しない白いゴミピクセルがうっすら見えることがあるためです。表示されたときにフリンジのようなゴミピクセルが見えたら、このPadding Colorを変えてみてください。
普段のPhotoshopでの作業はカラーマネジメントを変更しない限り sRGB 環境になっているので、チェックがついている状態できちんと見えていればOK。
sRGBに応じた補正が不要なリニアなデータを作成した場合は、このチェックを外します。
次は Level Of Detail
これがミップマップを生成するかどうかの設定です。
ゲームの描画は基本リアルタイムレンダリングで、カメラからの距離と、ポリゴン面の角度に合わせて、最適なテクスチャの解像度を動的に変更する仕組みがあります。その仕組みに対応するために事前にいくつかの解像度のテクスチャを作ってしまいます。
ゲーム中にリアルタイムに作るのは負荷が大きいので、インポート時に作ってしまえというわけです。内容は単純でオリジナル面積の1/4、1/16、1/64 ・・・と、何段階かの縮小テクスチャが作られ並べられます。
2D表示が主な UI はUMGのキャンバスに描くのでカメラは存在しません。ミップマップは不要なのです。作ったテクスチャにさらにミップマップ用のデータが追加されるとなると、データ容量がもったいないので、Mip Gen Settings は NoMipMaps を選択します。Gen はおそらく Genarate = 生成 の略記でしょうね。
64x64とか 256x256、1024x1024 みたいなべき乗の正方形テクスチャをインポートすると、自動で ミップマップを生成する設定になっているので、必ず確認しましょう。
ちなみに、テクスチャサイズを長方形にしたり2のべき乗でないサイズにすると、自動的に NoMipmaps にされ変更できなくなります。
Texture Group はテクスチャとしての用途を明示しておくことで、強制的なクオリティコントロール時に効果を発揮します。ここは UI を選択しておきましょう。
・・・
ひとまず大事なインポート設定は以上です。
特殊な状況じゃなければ、通常のテクスチャの場合はこれで問題は起こらないと思います。
残りの必要なテクスチャをインポートしましょう。
テクスチャ一枚ごとにちまちま設定するの大変という場合は 一括して行う方法もあります。
コンテンツブラウザで複数選択して、右クリック
Asset Actions > Bulk Edit via Property Matrix... が便利です。
使い方は今回は説明を省きますので、興味のある方は 公式ドキュメント かこちらをどうぞ。
さあカスタマイズを始めようか
必要なテクスチャをインポート出来たら、
Button の設定を編集していきましょう。
Image にインポートしたテクスチャをセットします。検索で探してもいいし、コンテンツブラウザからの ドロップも便利。
Image Size はテクスチャをセットした際に自動で読み取ってくれるので基本放置。
Margin は 0.25 で問題ないデザインなので、そのままにしています。
Disabledの下にある、~ Padding というパラメータは、クリックした際に凹む動きを実現する数値です。WEB の CSS で見かけることも多いかな。マイクロインタラクションってやつで、クリックした感が出るので地味にありがたい設定です。動いてほしくないときは、Normal Padding の値と、 Pressed Padding の値を同じにします。
SEの設定もできます。
これでようやくボタンひとつぶんの設定完了。
まじか! これを1個1個設定するの?なんかメンドクサくない?
はい。がんばれー!
で、ここまでは、マニュアル通りの答え。
とはいえ効率悪いよね?
ゲームのプロトタイプを作るとき、最初からおしゃれでいい感じのデザインで作ることはマレです。まずはアリものでアイデアが消えないうちにサクッと作りたいものです。
とはいえ、見た目でテンション上がるのは間違いないので、少しでも見映えはよくしたい。でもコストはかけたくない。
で、ここからは、ゲーム開発者としての答え。
後からボタンのデザインを差し替える方法を紹介します。
一時的に解決する方法と、後々のことを考えてメンテナンスできる形に変更する方法があります。
今判明している方法を3つご紹介。
まず、手っ取り早くコピペで!
設定の済んだ Button の Details > Appearance > Style のところで右クリックすると小さなメニューがポップアップするので、Copy を選びます。
次に、設定のされていない Button で 同じように Style に 今度は Paste を選びます。
これだけで移植できます。
この方法はデザインを変更するたびに、複数のアセットを開いて編集する必要があるのと、うっかりペーストし忘れが起こるかもしれないので注意が必要です。
オリジナルのButtonに差し替える!
新しくButtonアセットを作って、それと入れ替えます。
そうすれば、このアセットをひとつ変更するだけですべてに反映されます。
問題なのは、入れ替える際、OnClickなどのButtonにまつわるイベントが解除されてしまうことです。
差し替えにかかる手間はこの一度きりなので、後々のことを考えてキリのいいところで一気にやってしまうのも手です。
作り方は、
コンテンツブラウザの何もないところで右クリックして Blueprint Class を選択
Class 選択のダイアログがでてくるので、 button で検索
ヒットした Button をハイライトしたら、Select ボタンをクリックします。
適当に名前を付けるとアイコンが変化します。
ダブルクリックして編集します。
キャンバスが無く、Detailsタブも Appearance が一番上にいます。
ちょっと様子が違うようですが、スタイル設定をいじっていきます。
カスタマイズが終了したらコンパイル。
保存したら閉じてもOK。
ではタイトル画面のWidgetを開きます。
Designer モードにすると、左の Common リストのところに さっき作った Button が追加されているので、キャンバスにドロップします。
じつはこれ、この後削除します。リプレイスするために一時的に必要になるためです。
ヒエラルキーの中で先に置いてあった、Button を選択して右クリックメニュー
Replace With... からの Replace With 作ったやつ_C を選択。
入れ替わりました。
ドロップしたやつは削除します。ついでに文字の色も見やすくなるよう変更。
ここでコンパイルしてみると・・・!
どうやら少々機嫌を損ねてしまった様子。
編集モードを Graphに替えます。
原因はこれでした。
Button が別の Button に替わってしまったので、この OnClicked イベントが無効になってしまいました。
ここも差し替えます。
Variables リストにある、新しい Button を選択した状態で、
Detailsタブの下のほうにある 緑色のボタンをクリック。
グラフに新たな On Clickedイベントのノードが現れるので、古いのと交換します。
これでコンパイルできれば大丈夫。
この方法だと、確実に差し替わるので、今後のデザイン変更にも強い構造になりますが、ブループリントもそれなりに編集することになるので、状況によっては手間取ることもあると思います。
デザインの差し替えを想定するなら、先に自前の Button をデフォルトの状態のままで組んでいくのはとても効率が良い気がします。
いろんなデザインを試したい!
もう一つデザインを差し替える方法があります。
ブループリントを使ったコピペ方式のようなものです。
手でコピペするのと同じで局所的な対処になるのですが、デザインの付け替えが気軽に行えます。
新しく作ったオリジナルボタンアセットをキャンバスにドロップします。
すぐに 非表示にしておきます。
同じ非表示でも Hidden より Collapsed の方が描画の負荷が少ないです。
この設定は、レイアウト作業中には反映されなくて、ゲームをプレイすると反映されます。
編集モードを Graph にします。
Variables リスト から Button をドロップ。
それぞれのGetノードから、スタイルに関するノードを取り出します。
styで検索すると見つけやすいです。
スタイルを上書きする Set Style ノードは2つあります。
fアイコンのついた方が関数タイプで、複数の Button に対して対応可能。
青い コッペパンの形のアイコンの方は自身のパラメータ専用なので、1対1 にする場合。
今回は 関数タイプにします。
スタイルを読み出す Get Style ノード
取り出したノード同士をつなぎます。
これを Event Pre Construction につないで確認してみましょう。
Designer モードにしてから コンパイルすると変化する瞬間が見れます。
いくつかのデザインを用意してからキャンバスに非表示設定で並べておいて、つなぎ替えるだけで、結構手軽に見た目を変えられます。しかもスタイルだけを差し替えるので、On Clicked などのButtonイベントはそのままなのもうれしい。
デザインを検討している段階では助けになるのではないでしょうか。
不要なアセットが増えるのと、Get → Set の処理が負荷になるのを放置するのはプロジェクトにとって迷惑になる恐れがあるので、デザインがFIXしたら、タイミングを見てお掃除はしたほうがよさそう。
削除する際に警告が出ることがあります。
誰か使ってそうだけど、ほんとに消していいの?
最近のAAAクラスのゲームは、アクセシビリティにも配慮をするのが当たり前みたいな空気になってきた感があるので、この方法を使うと、色覚特性に合わせたボタンデザインを用意して、オプション設定を見て差し替えることができます。むしろモダンな実装方法になっていくかもしれない。
適用していく
とりあえずで、この3つの方法で一番試しやすそうな方法を実践してみてください。
今回の用意した緑のボタンデザインは、画面遷移を伴うところに使いたいので、クイズの回答ボタンは別のデザインが好ましいです。
改めて
メイン画面の 回答ボタン のテクスチャも用意しました。
このデザインだと、Margin の値は 0.25 ではなく 0.5 になります。
Button のサイズも 縦方向は このテクスチャに合わせて 84 に変更したりしてます。
その辺は 見た目にボケたり、ガビったりしなければOK。
差し替えが面倒に感じたので、ひとまずブループリントで差し替える方法を採用してみました。SetStyle を関数タイプをにすると便利です
このようになりました
いかがでしたでしょうか。
ずいぶん 見た目に仮っぽさが減ったと思います。
このUMGの Buttonコンポーネントについては、まだ知らない仕様や賢い使い方がありそうなので、発見できれば記事にしていこうと思います。
個人的にこの手の『エンジンに最初から付いてるコンポーネント』系は、機能が豊富すぎて敬遠してしまいがちなんですが、そこはEpicさんなんで無駄のない仕様に仕上がっていると信じてます。いまのところは。
今回のおまけ編はここまでです
わかりにくいとことか、ツッコミなどあれば、このブログのコメント機能か、Twitterにてお待ちしています。
あとリクエストとかもあればお待ちしてます。
ではでは
ステキなUMGライフを!
そうだ、UE4でQUIZゲームを作ろう《おまけ編》いーなむで切り替えをわかりやすく
いーなむを使って遷移をわかりやすくしよう。します。いや、しませんか?提案させてください。ということで、先日のクイズゲームを作ろうという記事で、WidgetSwitcher を使って複数のWidgetを紙芝居的に入れ替えるというのを作りました。そのとき 0、1、2みたいに番号で管理していたので、後から忘れてしまっても大丈夫で、わかりやすくするしくみを導入したいと思います。
今回は 開発手法的なテクニックの紹介になるので、特に機能が増えたり、見た目が変わることはありません。変わるのはブループリントのノードです。関数ノードを作ります。
今回の内容は、以下の2つの記事の内容を作り終えた後のおまけ編となっています。
目次
いーなむを作ろう
まず専用のアセットを用意します。
コンテンツブラウザの何もないところで右クリックします。
リストメニューから Blueprints > Enumeration を選択。
EScreenState という名前にしました。
EngineContent内を見てみると、頭に大文字の E を付けるのが慣例っぽいです。
これをダブルクリックしてエディタを開きます。
右上の New ボタンをクリック。
今回は 4つの画面Widgetがあるので 4回クリック。
Display Name と書かれたところに、いい感じに分かりやすくなるような名前を入力します。
WidgetSwitcher に登録した順番を元に名前を付けます
wb_Title (タイトル画面) → Title
wb_Main(クイズ画面) → Main
wb_Right(正解演出) → Right
wb_Wrong(誤答演出) → Wrong
後でWidgetSwitcherの順番を入れ替えることになった場合でも、入力しなおす必要はありません。右端の▲▼ボタンでここのリストも順番の入れ替えが可能です。
ここでしっかりと 1 対 1 に紐づいていれば大丈夫。
例えば、wb_Main2 が増えました~ しかも最後じゃなくてMain の次にしたいです~
という事態になっても、ヒエラルキーの順番を見ながら修正します。
いーなむを使ってみよう
QuizGame のWidgetを編集します。
エディタを開いたら、Graphモードにします。
タイトル画面のボタンとバインドしている部分、ここのカスタムイベントにつながっているノードをまとめて選択状態にしたら、
その上で右クリックして メニューから Collapse to Function を選択。
すると、New Function ~ という1つのノードに変化しました。
これは、関数化 という操作になります。
同じ仕事(タスク)を別の場所なんかで繰り返し行うときに、関数(ファンクション)としてまとめておくと、何度も同じノードをたくさん置かなくても、関数を一つ取り出してつなぐだけでスッキリします。しかも仕事の内容に名前を付けることにもなるので、あとからブループリントを見たときにも何をしてるかわかりやすくなります。
Photoshop で例えると アクション がイメージ的に近いです。
まずは New Function の名前を変更します。
変え方は 2通り
エディタ左 My Blueprintタブ の Functions リストから 右クリック > Rename する方法
選択して F2キーでも同じようにリスト内で変更することができます。
もう一つは、この関数を編集している最中に変える方法。
このノードをダブルクリックします。
関数の中だけのグラフに切り替わります。
左端にある紫色のノードを選択して、右上の Detailsタブからリネームできます。
関数化したら、そのまま編集する流れになることが多いので、ひと手間減るかどうかぐらいの差でしかないですが、一応 2通りの方法を紹介しました。
ここは、WidgetSwitcher の切り替えを行っていたので、changeScreenState としましょうか。
続いて、そのまま 少し下にある、 Inputs というカテゴリの +ボタンをクリックします。
マウスオーバーすると +New Parameter というボタンに変わります。
クリックすると、何やら出てきました。
イベントディスパッチャーを追加したときにも、同じような操作をしました。
これはこの関数が受け取るパラメータ(引数=ひきすうとも言います)を追加する操作です。
デフォルトだと Boolean 型が出てくるのですが、すでに別のタイプをいじったことがあると、上の画像と違うのが出ることがあります。
この ▼ のついた プルダウンリストを開けて、用意してある いーなむ を探します。
これを選択します。ついでに 名前も NewScreenState に変更しました。
グラフ上ではこうなります
最終的に、この NewScreenState のピンと、 Index のピンをつなぐわけですが、方が違うので、キャスト(型変換)が必要です。
ところが、ドラッグしても 互換性がないと突っぱねられます。
ここは自動ではやってくれないのです。
しかたがないので、手動でキャストするノードを取り出します。
toint で検索。Integer形へ という英文で書くと to Int が元です。
変換アダプタのようなものですね。
無事つながりました。
これで関数は完成です。
元のグラフに戻るには、タブを切り替えます。
最初からあるグラフは、イベントグラフ EventGraph という名前がついてます。
ブループリントの編集に慣れてくると、いろんな関数やらマクロやらを開くことが多くなるので、編集が終わったら閉じるようにするとEventGraphに戻りやすくなります。
戻ってきてみると、
あらまぁ 少し見ない間に~ てな見た目になります。
かつての様子 ↓
いーなむ を入力のパラメータにすると、プルダウンメニューが追加されました。
ここはタイトル画面のスタートボタンが押されたら、メインの画面に遷移する という処理なので、Title を Main に変えます。
ここで再生して問題ないかテストしてみましょうか。
問題なく動作すればOK。 あとは 似たような処理を見つけて入れ替えていきます。
グラフを見てみるとWidgetSwitcher の切り替えをしているところが3か所あります。
ここを入れ替えていきましょう。
関数ノードを取り出すのは、リストからドロップしてもいいし、
何もないところで右クリックして検索、でも取り出せます。
すごくわかりやすくなったと思いますが、いかがでしょうか?
いーなむは、インデックスのように数字で管理しているものに対して順番にわかりやすく名前を付けることができるのです。
今後 切り替えるものが増えた時も、いーなむアセットを編集するだけで済みます。
これで 切り替えるときに、あれ?何番だっけ?という悩みが解決します。
さらに、このいーなむエディタの Description (説明)欄に書き込んだメモが、マウスオーバーで表示されるという新設設計。
ぜひご活用ください
今回のおまけ編はここまでです
ここからはおまけのおまけ
逆引きで確認する方法 (おまけのおまけ)
デバッグするときの確認で便利なのが、番号を 文字 に戻す方法。
関数が呼ばれるたびに、PrintString したいと思います。
関数の中身に細工します。
ちょっと引くぐらいキャストしまくってます。
WidgetSeitcher をGetタイプで取り出して、そこから
Get Active Widget Index で現在アクティブな(表示されている)番号を取り出して、
いったん Byte型(バイト)にキャスト
今度はバイトから いーなむ EScreenState にキャスト
そこからようやく String型(ストリング=文字列)にキャストしてもいいし、
PrintString ノードにつないでも、ここは自動でキャストしてくれます。
こういう機能追加や改造がしやすいのも関数化するメリットだったりします。
再生して確認してみましょう。
なんか開発してるって感じになりませんか?
PrintString はいろいろ動作確認のときに大変便利なノードです。
今どうなってる? って時に役に立ちます。
今回は以上です
わからないところや突っ込み、リクエストなどなどあれば、このブログのコメント機能かTwitterにて
ではでは
ステキななUMGライフを!