引き続きクイズゲームを作っていきます。
ここでいったん過去記事リンク
『そうだ、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つ用意しました。
「あるとき」左と「ないとき」右。
← tex_circle_0.png
← tex_circle_1.png
ブログの下敷きと同じ白なので見えないけどいます。
右クリックで保存してお使いください。
画像を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の方からメッセージいただけると対応します。お気軽にどうぞ~
ではでは
ステキなカウンター表示ライフを!