HorizontalBoxの中の表示間隔を調整する
今回応募したぷちコン作品では、プレイヤーを操作して、浮いているタイルを踏んだ数をカウントしてスコアに加算しています。リザルト画面で表示する際に、ただ数字を書くだけだと面白くないし、「いっぱい踏んだなぁ」「あまり踏めてないなぁ」という実感を噛みしめるためには、ビジュアルで示すのが効果的です。このとき踏む数が想定よりも多かった場合にスキマを詰める処理を実装しました。今回の記事は事例紹介的な感じで書いていきます。
10個までは一定の間隔で並べて↑
10個以上になると数に応じて詰めます↓
細かい仕様ですが、0個の時は半透明にしてます。
ちなみにこの文字の種類による点数差は「屈辱点」てやつで、昔のアーケードゲームでよく見かけた仕様を取り入れてみました。点数獲得の単位が100点刻みだと、普通にプレイすると10の位まで0が並びます。ところがランキング画面(今だとLearderboardsっていう方が通るかな)をよく見ると、1の位に数字が入っていたりします。これは「コンティニューしてる・・・金の力でクリアしやがった」、百以下が 00だと、「お!ノーコンクリア!すげぇ!」となるやつです。コンティニューすると1点もらえる!って素直に喜んじゃいけないのです。
実はランキング画面も作るつもりだったのですが、ステージのタイルがランダムだとスコアを競う気がなくなるのでやめました。高得点のルートが決まるのも面白くないのでランダムは残すことにした結果、ただの運ゲーになってますw
ちょっと脱線してしまいましたが、Widgetの中身について書いていきます。
キャンバスにはキャラを並べる HorizontalBox と スコア用の TextBlock を並べています。
テクスチャは 64x64が4枚。
これらを、配列に収納してしまいます。
配列に入れておくと、Index番号で扱えるようになるので、ある程度機械的に処理できて便利です。後から要素を追加しても同じように処理できます。また、アナログ的に並んでいるものでも順序を決めて管理できるのも魅力です。
上の図では、Add to Viewport するたびに走るので、Do Once ノードを入れています。リザルト画面が終わったら、Remove from Parent していますが、描画されなくてもメモリには残すようにしているためです。
この配列たちは一度作ったら、中身が変化せず、繰り返し使用するタイプなので破棄する必要がないのも理由のひとつです。
配列の準備ができたので、次はスコアをセットしていく部分です。
別の関数ですでに「ぷ」「ぶ」「ち」「さ」のそれぞれの個数を受け取っています。
それを↓この配列に保持してあります。
この配列の中身を ForEachLoop ノードで取り出しながら処理していきます。
真ん中付近にいる Pure型の関数 が表示間隔を計算する関数です。
並べる文字のテクスチャサイズは 64x64です。 それが10個で640という幅が、表示限界幅とします。
まず 引数(Inputs)で受け取った個数で、表示限界幅 640 を割ります。
640 ÷ 個数 で1個あたりの幅が計算できます。そこから 64 を引いた値が、基本の表示間隔 = 64 に対しての補正値となります。
なぜこんな補正値が必要かというと・・・
例えば、 11個だった場合。
640 ÷ 11 で、58.1818181818・・・ となるので、1個が 58.181818・・・の間隔で並べばいいのです。
ところが、HorizontalBox は追加した 子パーツ の幅をそのまま維持して並べようとします。58.18181818・・・ずつ、といっても聞いてくれません。そこで、SetPadding ノードを使います。
ここに渡す値を用意するために、もう少し計算が必要です。
58.1818181818・・・からさらに 64を引くと、
-5.81818181818・・・・ となります。(符号はマイナスになりましたが、小数点が一つ動いただけに見えて面白い結果ですね。)
これで補正値が計算できました。
この補正値を HorizontalBox にテクスチャ文字を追加する際にPaddingの値として渡せばいいのです。
その仕事をするマクロがあります。
先の ForEachLoop で処理していたところの、真ん中付近、少し上と右に計2つあります。
このマクロの中身はこんなかんじ。
HorizontalBox に追加した 子パーツ に対して(正確にはAdd Child ノードの戻り値)、HorizontalBoxSlot にキャスト(型変換)すると、SetPadding ノードがつながります。その In Padding Right に補正値を入れてやると、次に追加された子パーツ がその補正された場所に追加される仕組みです。
ついでに個数が ゼロ だった時の処理も入っています。
実際に 8、9、10、11 個の場合の表示はこうなります。
状況に合わせて動的に子要素を追加する場合、エディタで事前に予測して調整できればいいのですが、臨機応変な対応が必要となるとブループリントでどうにかすることになります。SetPaddingノードを使うと表示間隔を自在に調整できるので、今回の仕様が実装できました。VerticalBoxでも同様のことができます。
ではでは
今回はここまで
ステキな表示間隔ライフを!
追加するとスクロールするやつ
今回のぷちコンで作ったUIの中から、スクロールするリストについて書こうと思います。
ゲーム内でグレイマンがタイルを踏むたびに、画面下部のリストに文字が追加されていきます。画面いっぱいになると、文字が追加されるたびに、一文字ぶん左にスクロールアウトするという仕様です。
実は結構ハマった部分なので、ちょっと難しい説明が出てきたりしますが、ひとまず中身について書いていきます。
この仕様のためのWidgetは2つ。文字を指示通りに切り替えるだけのWidgetと並べてスクロールさせる表示用のWidgetです。
制限時間のあるゲームとはいえ、追加し続けるとメモリを圧迫するのと、HorizontalBoxのサイズが大きくなりすぎて不具合が出たりしても怖いので、スクロールさせつつ端から文字を消していってます。こうすることでいくらでも足していけます。
今回難しかったのは、この消しつつスクロールする処理の 『タイミング』 です。
流れを図にするとこんな感じ。
さっそく一つ目のWidgetから見ていきます。
キャンバスには文字と▼を配置。
今回文字については、ステージに並べたタイルで使っているテクスチャ(1024x1024)を使い回してるので、赤い × は別のテクスチャにしました。×はこのWidget専用でそんなに解像度も必要ないので、あらかじめキャンバスに置いておいて、文字とスイッチすることにしました。
テクスチャはこんな感じ。アルファチャンネルを合成表示してるので、ピンクのところは抜きになります。
マテリアルでトリミングして差し替えるようにしています。
WIdgetブループリントのキャンバスの配置は以上で、 EventGraph はというと、Event Construct のみ。
Event Construct はカスタムイベントと違って(ビルトインというやつになるのかな)引数を設定することができません。なので、Int型の変数を一つ用意してExpose on Spawnの設定にします。このときInstance Editable も一緒にチェック付けないとあとで注意されます。
この変数が文字の種類を数字で受け取ります。
数字と文字の対応はこんな風。
-1= ×
0 = ぷ
1 = ぶ
2 = ち
3 = さ
0以下(ようするにマイナス)かどうかで、表示を切り替えています。
これでこのWidgetは完成です。
次は並べる方のWidget。
まずはキャンバスから。
HorizontalBox を配置するのですが、クリッピング(見える範囲を限定する)のためにCanvasPanel の子供にしています。
この CanvasPanel の Clipping 設定を、Clip to Bounds に変更します。
HorizontalBox の方は Inherit のままでOK.
これで CanvasPanel の外には何も表示されなくなります。
HorizontalBox の設定はこんな感じ。アンカーは左上。
この HorizontalBox のアニメーションを用意しています。
0.25秒で、-90移動させています。これは一文字ぶんの長さから。
再生するとこうなります。
CanvasPanel の外にはみ出させています。
キャンバスはこれで完成。次はブループリントの編集。
外から追加のリクエストをもらう形なので、カスタムイベントからつないでいきます。
Create Widget ノードに、先に用意しておいた 文字のWidgetをセットしています。
その戻り値(Return Value)を、HorizontalBox に Add Child すると自動的に横方向に並べてくれます。
EventTickで スクロール処理中(isScrollingフラグ)かどうかをチェックして、スクロール処理していなければ、HorizontalBox が抱えている子供の数をチェックして一定数(今回は20個)を越えていればスクロールの処理を行います。
上図の続き
スクロールしたら、Remove Child して、ポジションリセット
するだけで いけるかと思ったんだけど、タイミング的な問題で アニメーションの再生を止める処理を追加しました。ここが今回てこずった部分です。
最初原因が判らずいろいろ調べていたのですが、どうやらアニメーションの終了時間を取得してタイマーを動かしているのですが、その時点でまだアニメーションが終了しきっていないことがあるのです。アニメーションが終了しないまま、ポジションリセットを行って、アニメーションで上書きされて・・・ということが起こっていました。正常にタイマーと同期がとれるのは10回のうち1回あるかないかです。
アニメーションとブループリントとで同じプロパティをいじっているから、このようなバッティングが発生しやすいのです。といっても数フレームでのタッチの差みたいなものなので、強制的にアニメーションを止めても、見た目にはわからない程度だというのもあり、上のような処理で落ち着きました。
とりあえず以上で完成です。
説明用のサンプルを動かしてみたのがこれ。
ちなみに、EventTrackで
スクロール終了イベントを呼ぶようにしてみたけど、アニメーションの終了よりも、EventTrackの方が先に動くようなので、バッティングは避けられないようです。
ここからちょっと難しい話。
アニメーションが完全に終了してから、次が呼ばれるのが理想ですが、プレイヤーのアクションによって動作する部分なので予測が難しいです。イベントドリブンな設計では、いつどのようなタイミングで呼ばれても問題なく動くようにしておくのが大事だなと改めて思いました。
当たり前ですが、アニメーションには『時間』があります。想定の時間より短い間隔で呼びだされると、アニメーションが衝突することになります。
UMGの Play Animation ノードは、実行すると必ずタイムラインの先頭から再生します。また、Set Timer by Event ノードも 再実行すると、残り時間が再セットされます。
なので、呼び出されたイベントで、
文字追加 → アニメーション再生 → 終わったら1文字削除とポジションリセット
としていると、短い間に何度も実行した場合に、呼び出された回数と一致しなくなるのです。
文字追加 は確実に処理されてるのに、 アニメーション再生 は再セットされてタイマー延長、という状態に陥るのです。
例えば3回立て続けにイベントが呼ばれると、追加処理は3回、3文字追加されますが、アニメーションは1回です。
演出が終わっていない間に受け付けたものはどう処理するかをよく検討する必要があります。と言っても選択肢はあまりないですが、
- 無視する(先のやつ優先で後のやつ以降はなかったことにする)
- 先のやつを止めて、改めて後からのやつを処理(後のやつ優先)
- 一時的にスタックしておいて順次処理してゆく
- 追加分として一緒にまとめる
くらいでしょうか。
UI的に見せ方や重要度によって選択することになります。今回は3つ目のスタックするやつを実装しています。これは、文字を追加するリクエスト処理と、スクロールさせる処理を別々にすることで可能になりました。
追加するだけさせておいて、EventTickでいつ追加されるかのタイミングに関係なく溢れが解消するまで、監視しつつスクロールを処理します。ゲーム的にある程度のリアルタイム性は維持したいので、アニメーションの尺を短かくしています。
今回の台風はなかなかでした。結局7時間ほどで復電しましたが、記事を書いている途中でも何度か瞬間的な停電があって、ノートPCさまさまと思ってたのですが、ルータが落ちたら記事の保存ができなくなるので、結局おとなしく嵐が過ぎるのを待つことに。夏場の長時間停電で暑いわ暗いわ冷蔵庫の中身は心配だわ、で結構精神的に削られました。電気って素晴らしいですね!
ではでは
今回はこの辺で。
ステキなUMGアニメーションライフを!
サウンドの再生管理にもEnumを使ってみた
途中経過はいくらかTwitterで公開していぷちコン作品ですが、今日ようやく応募できました。一段落したのでブログの更新ペースが上がるはず。自信ないけど・・・。
応募の条件としてYoutubeとかニコ動とかに動画をアップするというのがあるので、応募に使った動画のリンクを貼っておきます。
UI周りは今まで記事にしてきたネタを復習してみた感じなので、目新しいネタが無いですが、もし、「コレどうなってんの?」みたいなのあれば、コメントなど頂けるとブループリント晒しながら「これこのようになっておりまする。」と記事にさせていただきたく思う所存。
さてさて
今回の記事は、UIじゃないんですが「試してみたら以外に良かった」やつです。
サウンド関係については、完全に素人なので、手探りでの実装になったのですが、今のところお気に入りということで記事にして晒します。もっとベストな方法や、問題箇所の指摘などあればぜひ、ツッコミいただけるとありがたいです。
まず Enum を作るところから。
コンテンツブラウザの空いているところを右クリックして、
Blueprints > Enumeration を選択します。
適当に名前を付けて中身を編集します。
今回は2曲しか使ってませんが数が増えたらここで増やせます。
次に、サウンド用ブループリント。
コンテンツブラウザで右クリックして、
Blueprint Class > AmbientSound
これを編集します。
ブループリントエディタが開いたら、ポーズ対策。
ClassDefaultから
Detailsタブの Actor Tick にある Tick Even when Paused にチェックを付けます。
これで、ゲームがポーズ中でもこのブループリントの Event Tick が止まることはなくなります。
この辺りはこちらのブログが参考になりました。
では、EventGraphへいざ。
鳴らしたいBGMのキューをSoundBase型の配列にセットします。
こういった変数の《型》はどうやって調べるかというと、BGMを再生させる場合はPlaySound2D という再生用のノードが用意されていて、そいつの入力ピンから変数に昇格させると簡単に用意できます。
あとは、詳細タブ(Details)から配列に変更したりできるようになります。
曲を再生するための関数を用意しました。
このブループリントには最初から AudioComponent が用意されているので、そのコンポーネントに対して再生の指示をします。がその前にどの曲を?となるので、上図のような構成になります。
関数の引数(Inputs)がポイントなのですが、上の青いのは AudioComponent型、下はEnum型です。ピンを追加して型を検索すると、先に用意しておいたEnumが見つかります。
Enum型は名前(表示名)で扱ったり、Byte型という値(ほぼ数字)で扱ったりできる特殊なやつです。Int型に変換(キャスト)して使うと、 0~255 の正の数が扱えます。
曲を配列から選んで再生できる関数が用意できました。
ちなみにこの関数はこのブループリント内で利用します。
次に外から呼び出してもらうための各種イベントを3つ用意しています。
ポーズ中でも、曲の再生状態をチェックしたいので、
Event Tick を↓のような感じにしてます。
再生中かどうかのチェックは、常にやりたいわけではないので、状況に応じて開け閉めできる Gate というノードを使用。
誰かがチェックするぜい! となったら、左にあるカスタムイベント wait を呼び出して ゲートを開けます。
すると Tickのパルスが流れ始めるので、今なってる曲が止まるまでチェックし続けます。曲が止まったらすぐにゲートを閉じてやらないと、Falseの先の処理が何度も走ってしまいます。
右端のは再生用に用意した関数です。
Enumを変数化しておいてつないでいます。中は鳴らしたい曲が保持されています。
次に、再生指示を受け取るイベント。
まず再生したい曲をEnum型で受け取って変数に入れています。
Branchが2個もあってちょっとややこしい感じですが、機能としては3つ。
- 何か曲が鳴ってなければ指定された曲を即再生
- 何か曲が鳴ってたらそれを止めて指定された曲を再生
- 何か曲が鳴ってたら、その曲が止まるのを待って指定された曲を再生
2つ目の Branch は イベントノードの 引数で分岐するようにしています。
最後は、フェードアウトするイベント。このノードは結構便利かも。
これで最低限の仕様を持ったBGM再生用のブループリントが完成。
あとはこれを、ワールドにスポーンさせて、中のイベントを呼び出すだけです。
おっと、その前にAudioComonento の設定を確認。
コンポーネントリストから選択して、詳細タブをチェック。
ポーズ中にも再生できるように is UISound にチェックを付けます。
念のため Auto Activate のチェックを外しておきます。
この辺りはこちらのブログを参考にさせていただきました。
UE4 サウンドキューを配置して再生する(Audio Component) 凛(kagring)のUE4とUnityとQt勉強中ブログ
曲を再生するとこはどうなってるかというと、あらかじめスポーンしたのを変数化しておいて、
適当なタイミングでイベントを呼び出します。
下は、タイムアップになったので、曲をフェードアウトさせて、曲が停止したらリザルトの曲を流しなさい。というものです。
右端のやつは、別のEnumですが、Enumで扱うとアセットの名前とかじゃなくで、シチュエーションごとの名前とかで指示できるので、全体のフローを制御するときに分かりやすくなると思います。とりあえず「リザルト」の曲流しておいてよ。という指示でOKなのはとても気楽にセットできて安心できます。
実際に流す曲自体は曲の管理を担当するメンバーが、このブループリントとEnumを編集すればいいので、それなりに分業もしやすいのではと思います。
あと、ランダムにしたり、一定の条件で曲を変化させたりといったことも、サウンドブループリントの方をいじればOK.。今回は曲の配列とEnumの順番を一致させていますが、一致させない場合は配列を使わなくても問題ないです。
個人的に結構気に入ってるこの仕組みいかがでしょうか。
もっとナイスなベストプラクティスとかありそうですが、ひとまずこれを推してみます。
今回ぷちコン作品を作るにあたっていろんなブログ様に助けていただきました。この場を借りてお礼申し上げます。ありがとうございました。
ではでは
ステキなBGMライフを!
[3D]頂点移動でタブウィンドウ的な何かをつくる《拡張版》
今年の4月にメッシュでできたウィンドウ的な何かを頂点変形させる記事を書きました。その内容に関してコメントいただきましたので解決方法を《拡張版》としてを書くことにしました。「まず横幅を広げた後で縦方向に広げたい」という案件です。
過去記事では、タブの水平移動と縦方向に広がるウィンドウを、メッシュの頂点にテクスチャでマスクすることで実現しました。その時のマスクテクスチャがこちら。
64x32 の実寸です。
これに対して今回用意したのはこれ。
拡大すると、こんな感じ。
メッシュのUVが分かりやすくなるようにオレンジのラインを入れています。
左右に緑色の四角を塗って、レイヤーブレンドを『覆い焼き(リニア)』にします。
こうすることで、青く塗った部分(R:0, G:0, B:255)を壊さずに緑成分を重ねることができます。
赤や青同様にこの緑成分を横方向の拡張に利用します。
さてさて、
これをUE4にインポートしたら、マテリアルを改造します。
前回はこんな風でした。
今回はここに、頂点のプラスマイナスを判定してから緑チャンネルと乗算する処理を追加します。
まず最初に頂点座標のうち、X座標について IF ノードで判定しています。
この値とテクスチャの緑成分を掛け算することによって、動かしたい頂点と動かしたくない頂点を分けてています。
さらに
IFノードの判定によって出てくる、-1、 0 、 1 という3つの値を利用することで、あとから足し算する際の方向をそれぞれの頂点位置に合わせることができます。
プラス方向に動いてほしい頂点には、プラスの値を足し算して、
マイナス方向に動いてほしい頂点には、マイナスの値を足し算する必要があります。
最初実験していた時、
テクスチャでプラとスマイナスの値を作って加算していました。
何となくいい感じだったのですが、タブが一緒に動いていました。
黒の部分の値が計算されてしまって、マスクがうまく効いてないという状況でした。
今のところ、テクスチャの役割としては、カラーのある部分の頂点を動かす、ということになります。
以上でマテリアルは完成です。
次にアニメーション部分の改造です。ブループリントは前回こうなっていました。
ここに、横幅を広げるタイムラインと、ノードを追加します。
タイムラインに横幅変化用のトラックを一つ追加。
下段が横幅用。上段が縦幅用。 値は 0 → 1.0 に変化しています。
トラックが追加できたら、イベントグラフの方。
カスタムイベントの引数(Inputs)に横幅を受け取るピンを追加して、タイムラインの値と掛け算しています。
このカスタムイベントを呼ぶところで、パラメータをセット。
これで準備完了です。
テストしてみましょう。
なんとか、うまく動いてくれました。
ちょっとややこしい作りになりますが、うまく応用できればいろいろ表現の幅が広がりそうです。
いかがだったでしょうか?
最近、小ネタをTwitterの方に呟くようになったので、ブログの更新ペースが伸びませんが、何かあれば当ブログにコメントいただくか、Twitterの方からつついていただけるとありがたいです。
ではでは
ステキなウィンドウライフを!
「ぷち」っとタイマーを作る
UMGでタイマー表示をつくってみたので、「ぷち」っとご紹介。ぶっちゃけ「ぷちコン」用の制作メモみたいなものです。過去にテクスチャを使ったやつを記事にしているんだけど、今回はNo Texture です。UMGです。
タイマー表示にもいろいろあるのですが、今回は 100分の1秒を表示するタイプです。細かい数字がせわしなく動くと、急かされる印象を与えることができます。
さらに残り時間を感覚的に伝えるために、数字だけでなく円ゲージも合わせて動かします。
さっそく タイマー表示用のWidgetブループリントを作成。
キャンバスに TextBlock を3つ配置。 30秒想定です。
外側のリングは、Image で、マテリアルをセットします。
マテリアルは以下。
Value と書かれたノードは Scalar Parameter です。リングの太さや大きさを調整する場合は、Add ノードで、少しだけ値を足してやります。
すると、プレビューウィンドウ内が↓のようになります。
調整が終われば Add ノードは 削除します。
Valueの値が 0.0 ~ 1.0 に変化するとこうなります。
ブループリントを編集していきます。
まず残り時間を管理する変数を3つ用意します。Float型です。
0.2 を掛けているのは、残り 20%以下になったら点滅させるので、その判定用の変数です。
マテリアルのパラメーターをいじるので、早い段階でDynamic Material Instance 化しておきます。
この場合、Add to Viewport した直後になります。
このWidgetが表示されてすぐにタイマーが動き出すのを防ぐために、Boolean型の変数を1つ用意してフラグとして使います。
カウントダウン開始は、他のブループリントからの呼び出しが合図です。
そのためのカスタムイベントを用意してフラグを立てるようにします。
カウントダウン開始で「Ready?」 から 「Go!!!」に変えるのですが、少ししたらフェードアウトするように、アニメーションを作って再生しています。
タイマーの心臓部を作っていきます。
続き
タイマーがゼロになったら、次の表示に進めるために、イベントディスパッチャーを用意してCall(呼び出し)でつないでいます。
グラフにドラッグ&ドロップするときに選択します。
表示更新用の関数はこんな感じ。
続いて 小数部分。
何をやっているかというと、小数点を境に数字を取り分けています。
例えば 23.4567 という値があったとしたら、まずは整数部分。
次は 小数部分。
最後は、リングゲージへの反映です。
これで出来上がりです。
テストしてみます。
実際は、HUDクラスのブループリントを用意して、
そこからカウントダウン開始のカスタムイベントを呼び出します。HUDブループリントの Event BeginPlay で、Create Widget して ReturnValue を変数化しておくのがポイント。
で HUD用のWidgetをまとめて Add to Viewport します。
タイマー作動させるイベント。
カウントダウンが終了したらリザルト表示に移行。タイマー作動と同時にイベントディスパッチャーに対してバインド(紐づけ)しておけば、勝手に流れてくれます。
最終的に、このHUDのタイマー作動イベントを呼ぶヤツが必要になるのですが、何がトリガーになるかはゲームの内容次第なので、ひとまず今回はこの辺までにしておくことにします。
思ったより「ぷち」っという感じにならなかったですね。テキストの表示だけだったらシンプルに作れますが、ちょっと欲張ってしまいました。
ではでは
ステキなタイマーライフを!
オブジェクトをグリッド状に並べる
毎日の暑さ辛いですね。近所で稲の葉先が黄色くなってる畑をいくつか見かけました。この熱エネルギーをやる気に変換するマシンか何かあれば、と思ったけど、やる気に溢れすぎて炎天下に飛び出したり、汗だくで何やら叫びつつ暴れたりする人のイメージが沸いてしまい、危険なマシンになりそうだったのでそのような妄想は忘れることにします。だれか早くクーラードリンク開発してください。
さてさて、ぷちコンのお題が発表されましたね。今回は10回目ということで「ぷち」だそうです。私も何か作ってみようかと思い立ったので、いろいろ試していくことにしました。形になるかわかりませんが。
何となくタイルを並べてみることにしました。
プロジェクトのテンプレはThirdPersonです。
いきなり動き回れて、カメラぐりぐり回せるので楽しいセットですよね。
このフロアをスッキリ片づけます。階段だけ残しました。
最初から置かれているメッシュたちは、Mobility設定が、Static になっているので、ワールドから削除しても、床に影が焼き付いています。
Buildツールの Build Lighting Only を実行すると、影が再計算されます。
で、床のサイズと階段の場所とスタート位置を調整。
グレイマンの載ってる床はCubeを平たくして階段の下に置いています。そのままだと馴染まないので、階段と同じマテリアルをアサイン。
この床にブロックを並べてみようかと。
並べるブロックは生のStaticMesh だけだと面白くない(いろいろ遊びを仕込みたい)ので、Blueprint Actorにします。新しく Actorクラスのブループリントを作成。
空っぽなので、AddComponent で StaticMesh を追加。
DetailタブにあるStaticMeshから、適当なキューブメッシュをセット。
Viewportで確認しながら、サイズを調整したらひとまず完成。
これを並べるためのブループリントを用意していきます。
新しく、Actorクラスのブループリントを作成。
並べるための関数を作ります。↓前半部分。
引数(Inputピン)は Int型と、Float型の2つ。とりあえず 正方形に並べるので1辺ぶんの数と、配置間隔。
開始位置でいろいろ計算してるのは、センタリングするためです。
ForLoopで機械的に並べていくので、最初の起点となる座標さえ判ればあとは繰り返し処理だけでOKです。
例えば、ヨコに5個並ぶ場合、
並べたい数 5 から 1つ減らした数が、オブジェクト間のスキマの数。これにスキマの距離を掛けると、端から端までの全体の距離になります。これの半分の距離を、中心から引いた場所が 起点 になるのです。
これでどんな数でもセンタリングできます。
グリッド状に並べるのですが、引数は1辺の最大数を受け取って処理します。例えば5という数字を受け取ったら、 5x5 = 25 です。 UE4の ForLoopは LastIndexで指定した数字までカウントして処理します。0ゼロスタートだと、0~24 が正解。
コードで書くと
for ( int i = 0; i <= 24; i++ ) { }
です。
for ( int i = 0; i < 25; i++ ) { }
だったらいいのに・・・
どうせ First Index のピンが空いてるんだから、 そこを 1 始まりにすればいいじゃん。
と思われるかもしれません。確かに回数的には問題ありません。(恐らくゼロ始まりに馴染みに無いノンプログラマ向けの仕様だと思います。Epicの優しさ?)
でも、このゼロ始まりの方が、この後のループ処理に便利なのです。
後半部分はこんな感じ。
まずポジションの計算をするので、1 始まりだと、ズレたところから始まってしまうのです。
右端の Spawn Actor From Classノードのは、先に用意してあった、キューブの入ったブループリントをセットしています。
関数に並べる仕事をさせてみる。
関数にパラメータを渡して呼び出すと並べてくれます。
Event Begin Play につないだら、コンパイルして保存。
ワールドにこのブループリントをドラッグ&ドロップします。
再生してみます。
成功です。
ファンクションキーの F8 を押して、それぞれのオブジェクトのポジションを確認してみると計算が間違ってないかどうか確認できます。
配置が完成したみたいだけど。
問題なく並ぶことが分かったので、もう少し数を増やして、あと上面にもう一枚板ポリを載せたりしてできたのがこれ。
9x9=81個 並んでます。
上手に「ぷ」→「ち」の順に繰り返し踏んでいくアクションゲームに、なったらいいな。
というわけで、今回はこれまで。
ではでは
ステキなセンタリングライフを!
テキストのフェード演出を作ってみる
OCTOPATH TRAVELER遊んでます。面白いですよ。「オクトパス」とカタカナで書くと「octopus」だと間違われるかもしれないので、あえてアルファベットで書きます。ということを語りたくなるくらいオススメしたくなるRPGです。
ゲーム中に挿入されるイントロ表現をUMGで作ってみたくなったので試してみることにしまた。4.20もインストールできたし。
構造としては、
1行分のWidgetを用意。フェードのアニメーション付き。
文字列を行ごとに分解して行ごとのWidgetに託してフェードを管理するWidgetを作る。
以上の2つでできそう。
フェードは、アルファチャンネルマスクをUVスクロールしようか迷ったけど、結局NoTextureでやってみることにしました。
マテリアル
フェードのキモとなる部分です。テクスチャは使ってません。
左端のScalarParameterノードの値を -1.0 ~ 1.0 にするとグラデーションが動きます。
左から白い部分が増えてきて最後は真っ白になります。1-x(OneMinus)ノードで左右の向きを逆にしています。Clampは 0.0 ~ 1.0 の範囲を越えた値をカットして揃えます。
マテリアルは完成です。
1行分のWidget
まずはテキストブロックを一つ。左上に配置します。
Is Variable と Size to Content にチェックを付けて、用意したマテリアルをフォントマテリアルとしてセットします。
次にブループリント
変数2つを初期化して、テキストブロックにセットしたマテリアルを、ダイナミック化します。このノードをつなぐと、テキストブロックにセットしてあったマテリアルが、マテリアル インスタンス ダイナミック に差し替わります。
マテリアルは通常スタティックなものとして扱われますが、ブループリントから動的にアクセスする場合は、ダイナミックな状態にする必要があるのです。
問題発生の話
はじめ、この初期化処理を Event Pre Construct につないでいました。
Event Pre Construct はコンパイルした際に、実行されるイベントです。
(Add to Viewport した際も処理は走ります)なので、コンパイルした瞬間にフォントマテリアルは差し替わってしまうのです。
これに気づかずに進めていて、ふと Font サイズを調整してコンパイルし、確認のため再生してみると、なんと、すべてのテキストが一斉にフェードするではありませんか。
一旦下図の状態でコンパイルしたあと、
再び 戻してコンパイルすると。
Before の状態に戻れば 問題ないのですが、Appearance の Fontセクションをいじってコンパイルしていると、最初にセットしたマテリアルに戻らなくなります。
まだ全部を作ってないので、確認するのは難しいですが、仕様のようなバグのような挙動だったので、一応書いておくことにしました。
とりあえず
Event Pre Construct につなぐのはやめました。
カスタムイベントを用意していきます。まずテキストを受け取るイベント。
つぎに、フェードを開始するイベント。
処理自体はTickで動かすのでフラグを立てるだけです。
Event Tick で このブーリアンの値をチェックします。
文章の長い短いに関係なくフェードの時間を均一にしてしまうと、見た目にとてもテンポが不自然になるので、テキストブロックの長さを元に速度調整用の値を作ってDeltaTimeに掛け算することでブーストすることにしました。短い文章ほど、値の変化が大きくなって短時間にフェードが完了するしくみです。
右の続きの部分はこちら。
フェードの最終値は -1.0 なので、それより大きい状態のときは、素直にフェードの値を更新。-1.0よりも小さくなったら終了なので、Tickの処理を止めるために最優先でフラグを折ります。そして-1.0以下の行き過ぎた値をなくすために、きっちり-1.0にします。
右の端は、イベントディスパッチャーです。
これを用意して呼び出してやることで、自身のフェード完了を親のWidgetに通知します。
これで完成。
全てを表示するWidget
キャンバスの中央にテキストを格納するVerticalBoxパネルを配置します。
どんな行数でも上下方向にセンタリングさせたいので、アンカーは
ひとまず幅は1280になるようにします。
お試しならVerticalBoxがあればOKですが、見た目に雰囲気を出したいので、画面全体の下敷きとフェードインのアニメーションもつくりました。
次にブループリントを編集します。
カスタムイベントを追加して、テキストを受け取って行ごとにバラす処理です。
分解判定用文字列の『デリミタ』には 改行を示す 文字がひとつ入っています。
改行文字は Shift + Enter で入力できます。
配列に入れたら、その数ぶんループ処理を回します。
CreateWidget した結果をVerticalBoxの子供に追加。表示するテキストを渡します。
全ての行(Widget)を追加し終えたら、ForEachLoopノード Completeピンから伸ばして画面全体のフェードイン開始です。
フェードインのアニメーションが再生し終えてから、行ごとにフェードを開始するので、アニメーションの終了を待つ必要があります。そのためのノードが、Get End Time と Set Timer by Event ノードのコンビネーションです。
Set Timer by Event を使わずに アニメーションの Event Track を使う方法でも大丈夫ですが、個人的にSet Timer ~ を使う形のほうが、あとからブループリントを見たときに流れが追いやすいのでおススメです。 アニメの尺が変わっても自動で対応してくれます。
Event Track の方は尺が変わってもついてこないのと、呼び出す関数やイベントの名前を文字列で入力するのでタイプミスできないのが気を遣うのであまり好きになれません。このイベントいつ誰が呼んでるの?となった時も調べにくいです。
つぎに各行のフェード開始処理です。新しくカスタムイベントを追加して、Set Timer by Event ノードのEventピンとつなぎます。
VerticalBoxパネルに追加したWidgetはIndex番号で管理されていて、番号を指定して取り出すことができます。取り出したままでは使えないので、キャスト(型変換)してやります。追加したときのWidgetがその変換する型になります。
「さぁフェードするのじゃ!」と関数を呼び出したあとで、「終わったら連絡するように」というのが右端のバインドノードです。イベントディスパッチャーを作っておけば、bind で検索すると出てきます。
で、終了の連絡を受けたら次の処理。
順番にフェードするためのカウント用の変数を一つ加算して、次に進めるかどうかの判定をしています。まだ次があったら、アンバインド Unbind して、最初のイベントを呼びます。アンバインドは、終了通知が1回でいいのと、一度通知を受ければ連絡待機の処理が要らなくなるのが理由です。
基本部分はこれでできたので、オマケの部分を作ります。
せっかちな人向けの処理です。ゲームにも入ってました。
Wd_Sentence のGetノードは、キャストしたタイミングのやつが入ってるので、常にフェード中のWidget が格納されています。
次に、テキスト全部を消してVerticalBoxをキレイにするイベントです。
ただ消すだけだとかっこ悪いので、フェードアニメーションを付けます。
VerticalBoxパネルのビヘイビアにRender Opacity というとても嬉しいパラメータがあります。
これを 1.0 から 0.0 に下げると簡単にフェードアニメーションが実装できます。
子供として追加した個々のWidgetにフェードアウトを用意しなくてもいいのでとても便利です。
このフェードアウトのアニメーションを作ったら、カスタムイベントで再生するようにします。
再生が終わって画面から見た目に消えたら、真の消去を行います。Clear Children という物騒なノードが用意されています。
最後に変数リストを載せておきます。
完成です。
再生して確認
テスト再生なので、いつものように便利なレベルブループリントから行います。
レベルブループリントは以下。
空行には半角のスペースが入れてあります。
では、いざ再生。
スペースキーで表示開始、[B]キーでフェード終了、 [N]キーで全消し。
なんとか形になりました。
ブログに動画を直接貼れないので、あとでTwitterの方にアップしてみます。
(解像度が心配だけど・・・)
ということで今回はここまでです。
OCTOPATH TRAVELER はUE4で開発されているので、UE4で真似できるはず。そう思いながらUIを触ってます。今のところ、「え?これどうやってるの?」というのは見つけてないですが、そのうち出会ったら、また目コピーに挑んでみるかもしれないです。
ではでは
すてきなテキスト演出ライフを!