クイズゲームには時間制限は当たり前ということで、作ってみたいと思います。
表現方法によっていろんな作り方があるので、どういう形にしようか迷ってしまいました。
ただ一口にタイマーといっても、目立ち具合というか、主張する強度みたいなのがあって、レースゲームみたいなタイムアタック系のゲームと脱出劇や爆弾解除みたいな時限系のシチュエーションとは与える印象は変えた方がいいですよね。この辺またどこかで記事が書けたら書こうと思います。
チュートリアルって最適解なものを無駄なく教える場というのが求められているんだろうなとは感じつつ、ゲーム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にてお待ちしています。
ではでは
今回はこの辺で
ステキなタイマーライフを!