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を触ってます。今のところ、「え?これどうやってるの?」というのは見つけてないですが、そのうち出会ったら、また目コピーに挑んでみるかもしれないです。
ではでは
すてきなテキスト演出ライフを!