格ゲーでおなじみのカウントダウンタイマー表示を作っていて、シンプルだけど形になったのでメモ。
カウントダウンなので残り少なくなるとプレイヤーを急かすように主張しないといけません。最初はたっぷりあるので、チラっと確認できればいい程度の目立ち具合で問題ないのですが、夢中になっているプレイヤーにタイムアップを唐突に告げるのはかなり不親切な印象を与えてしまいます。
ゲームというのは制限時間があるからストーリーやドラマが生まれて面白くなるのです。プレイする側は時間という対価を支払うことで充実した体験を得られ、また次の展開に期待しつつ時間を提供し・・・、というループがプレイサイクルとして成立しやすいように、制作者はいろいろと知恵を絞ることになります。
長くなりそうなので、この辺にしておきます。
まず数字のテクスチャから。サイズは 128x1024。
右の図はUVの範囲が分かるように色分けしたものです。
単に赤くするだけなら、マテリアルなどでカラーをなんとかするという手もあるのですが、デザインによってはカラーがいい感じに乗らないこともあります。
ここはシンプルに2種類の数字を用意してUVアニメーションで切り替えることにします。テクスチャ容量はもったいない気もしますが、デザイナーがテクスチャを更新するだけでデザインの変更ができるのでラクです。
数字の大きさは一文字につき 64x96。UVの値は 0~1 で扱うのでテクスチャサイズで割算したときになるべく割り切れるようにしておいた方が誤差が出にくくなります。
UV値の計算は下図をイメージして計算します。
テクスチャができたらアンリアルエンジンにインポートします。
インポートできたら、コンテンツブラウザのテクスチャアセットのアイコンの上で右クリックすると簡単に作成できます。> Create Material
2種類のマテリアルを作成しますが、まずは簡単な『TIME』の方から。
今回UMGで構成するのでマテリアルドメインをUser Interfaceにします。
アルファチャンネルを使うので、Blend Mode を Translucent にするとOpacity がつながるようになります。
UVを操作しないのでシンプルです。パラメータも使っていません。
TexCoordinate ノード(左端)の設定値は、VTilingのところに 64 ÷ 1024 = 0.0625
左の方のオリーブ色のノードは、コンスタントのV2ノード(Float2)で、キーボードの2を押しながらマウスの左クリックで簡単に呼び出せます。これには 960 ÷ 1024 = 0.9375 なので、
という値を入れたら、はい一丁上がり。
続いて『数字』のマテリアルですが、横着して今できたばかりのやつを複製します。
V2ノードの代わりに スカラーパラメータをつなぎます。
TexCoordinateから出てくる情報は Floatが2つで 2Vector という状態なので、スカラーパラメータを加算するには、同じくVector型にしてやる必要があります。そこでAppendノードを使って、2つのスカラーパラメータをVector型にしてやります。
Add は『加算』ですが、Append は『追加』 という意味です。
TexCoordinteの設定値は以下。
TexCoordinateノードのTiling値には、テクスチャから部分的に切り出したいサイズを小数点で指定します。ちなみに 1.0以上の値を入れると見た目にリピートすることになります(タイルのように繰り返すのでこれこそがタイリング。例えば 5.0 だと5回繰り返します)。
スカラーパラメータはそれぞれに、"OffsetU"と"OffsetV"という名前をつけて、OffsetUの方には初期値として 0.84375 をセットしておきます。これは数字の9の位置です。
マテリアルは準備できました。
続いてWidget。
Imageパーツを3つ配置します。
画面の中央に置きたいので、Anchorsを中央上端にしています。
ImageパーツはDetailsタブの Appearance > Brush > Image のところにあるプルダウンから選択するか、コンテンツブラウザのアセットをフォーカスしてしてから白い矢印をクリックしてセットします。
数字はブループリントで扱うので、分りやすい名前を付けておきます。
パーツは以上なので、
編集モードをGraphにしてブループリントをエディットしていきます。
まずは EventConstruct から。
マテリアルパラメータを使ってUVを触るので、Dynamic Materialを準備しておきます。まず配置したImageパーツが変数リストにいるので、ドラッグします。
そこから get Dynamic Material ノードをつないで、戻り値(ReturnValue)を変数化(Promote to variable)します。
つぎに、タイマーの数字を更新する関数を作ります。
この関数は別のブループリントやプログラマの作ったシステムから呼び出される想定なので、Int型の引数をひとつ持たせます。
受け取った値を保存しておいて後でチェックに使うための変数も新たに用意します。
上の図は10の位の処理です。Int型の数は割り算で端数がでても切り捨てられます。なので、10で割るといい感じに10の位の数字だけがゲットできます。
そこにテクスチャの文字サイズ(96÷1024=0.09375)を掛け算するだけで、UV座標が決まります。あとは、マテリアルパラメータに値を渡します。
次に1の位。(上の図の右とつながっています)
こちらは、%(剰余)を使って1の位を取り出しています。
これでこの関数に0~99の数値を渡すと画面に反映できるとこまでできました。
ようやく本題の部分。点滅処理です。
これは、EventTick で行います。EventTickノードはデルタタイムという値を取り出すことができます。アンリアルエンジンのTick処理はFlashの ENTER_FRAME みたいに常時ほぼ一定の間隔で実行処理が流れます。このほぼ一定間隔の間隔、つまるところ直前の処理からの経過時間がデルタタイムです。取り出したデルタタイムを加算していくと時間経過が分かるようになります。というわけで加算したデルタタイムを保持しておく変数を一つ用意します。デルタタイムはPINの色からわかるようにFloat型です。とりあえずBrinkTimeという名前にしました。
経過時間が判るんだから、なぜデルタタイムをカウントダウンにつかわないのか?
これには大人の事情がいろいろあるのですが、
・レトロハードでは処理能力の問題でひきつり(処理落ち)をごまかす必要があった
・ネットワークプレイも同様にリアルタイムを完全同期で表記するのは難しい
・体感的な時間と表示時間のギャップは遊んでいると気にならない
・”分”、”秒” という単位を付けてしまうと嘘がつけなくなる
などなど
ぶっちゃけ正確な時間を利用して面白くなるゲームはレースゲームのようなタイムアタック系のものだけです。あとは将棋?
それに比べて格ゲーなどは、タイムアップは誰も望んでいないのでほんとは無くてもいいのですが、アーケードゲームから始まった文化なのでもはや伝統的なものという気がします。とはいえ制限時間があった方がゲームらしくなります。
さてさて
大人の事情により、システム側からいいタイミングで関数を呼び出してもらうシステムなので、数字の切り替えはそちらに任せることにして、あくまでも点滅は演出なので、Widgetの持つTickで処理しようというわけです。
現在のカウントがどうなっているかは、関数内で保持したCurentCount変数が持っているので、その値を比較します。「30以下だったら」ということでtrueの続きはこうなってます。
ひたすら加算し続ける変数 BrinkTime を %(剰余)を使って、 0.0~0.5 にします。
その値が 0.25以上だったら、マテリアルパラメータの OffsetU を 0.5 にして(右側の列)0.25未満だったら OffsetUの値は 0.0 (左側の列)にします。
これで、0.25秒単位でテクスチャのUVを切り替えるアニメーションができました。
Widgetは完成です。
うまくできてるかどうか試しみます。
キー入力のイベントが置けるので、とりあえずレベルブループリントで確認です。
Create WidgetノードでWidgetアセットをセットしたら、定番の Add to Viewortノードでビューポートに追加します。
カウントを保持しておくInt型の変数を一つ用意します。
キー操作でその変数が増減するようにつないだら、WIdgetに作った関数をつないで値を渡してやります。
++ と -- は『インクリメント』と『デクリメント』です。プログラマにはお馴染みだと思います。これは◆の参照PINにつながれた変数の中の値を±1するものです。
普通につなぐと↓のようになります。
Coun変数の初期値は 99 を入れておきます。
再生するとこんな感じ。
キー操作してみると数字が増えたり減ったりするのが確認できます。
30以下まで減らしてみると、
Tickのところで、0.5 とか 0.25 とかやってる部分を調整すると速くしたり、ゆっくりにしたりできます。
ちなみに、
UVアニメーションではなく、2種類の数字をあらかじめ2つ画面に置いておいて、Visibleの On/Off で切り替えるという方法もアリです。
どちらにせよ、カウントダウンの処理と点滅の処理を共存させることができれば機能要件としては達成できそうです。
ではでは今回はこの辺で~