みつまめ杏仁

アンリアルエンジン(UE4)でGUIを作るためにゴニョゴニョしてます。UIデザイナーの皆様の助けになれば幸いです。

テキストのフェード演出を作ってみる

OCTOPATH TRAVELER遊んでます。面白いですよ。「オクトパス」とカタカナで書くと「octopus」だと間違われるかもしれないので、あえてアルファベットで書きます。ということを語りたくなるくらいオススメしたくなるRPGです。

ゲーム中に挿入されるイントロ表現をUMGで作ってみたくなったので試してみることにしまた。4.20もインストールできたし。

 

構造としては、

1行分のWidgetを用意。フェードのアニメーション付き。

文字列を行ごとに分解して行ごとのWidgetに託してフェードを管理するWidgetを作る。

以上の2つでできそう。

フェードは、アルファチャンネルマスクをUVスクロールしようか迷ったけど、結局NoTextureでやってみることにしました。

 

 マテリアル

フェードのキモとなる部分です。テクスチャは使ってません。

f:id:hiyokosabrey:20180721193338p:plain

 左端のScalarParameterノードの値を -1.0 ~ 1.0 にするとグラデーションが動きます。

f:id:hiyokosabrey:20180721203248p:plain

左から白い部分が増えてきて最後は真っ白になります。1-x(OneMinus)ノードで左右の向きを逆にしています。Clampは 0.0 ~ 1.0 の範囲を越えた値をカットして揃えます。

マテリアルは完成です。

f:id:hiyokosabrey:20180721223655p:plain

 

1行分のWidget

まずはテキストブロックを一つ。左上に配置します。

f:id:hiyokosabrey:20180721195041p:plain

Is Variable と Size to Content にチェックを付けて、用意したマテリアルをフォントマテリアルとしてセットします。

f:id:hiyokosabrey:20180721195458p:plain

 

次にブループリント

f:id:hiyokosabrey:20180721215427p:plain

変数2つを初期化して、テキストブロックにセットしたマテリアルを、ダイナミック化します。このノードをつなぐと、テキストブロックにセットしてあったマテリアルが、マテリアル インスタンス ダイナミック に差し替わります。

マテリアルは通常スタティックなものとして扱われますが、ブループリントから動的にアクセスする場合は、ダイナミックな状態にする必要があるのです。

 

問題発生の話

はじめ、この初期化処理を Event Pre Construct につないでいました。

 Event Pre Construct はコンパイルした際に、実行されるイベントです。

(Add to Viewport  した際も処理は走ります)なので、コンパイルした瞬間にフォントマテリアルは差し替わってしまうのです。

f:id:hiyokosabrey:20180721205046p:plain

これに気づかずに進めていて、ふと Font サイズを調整してコンパイルし、確認のため再生してみると、なんと、すべてのテキストが一斉にフェードするではありませんか。

一旦下図の状態でコンパイルしたあと、

f:id:hiyokosabrey:20180721210612p:plain

再び 戻してコンパイルすると。

f:id:hiyokosabrey:20180721210911p:plain

Before の状態に戻れば 問題ないのですが、Appearance の Fontセクションをいじってコンパイルしていると、最初にセットしたマテリアルに戻らなくなります

f:id:hiyokosabrey:20180721211307p:plain

まだ全部を作ってないので、確認するのは難しいですが、仕様のようなバグのような挙動だったので、一応書いておくことにしました。

 

とりあえず

Event Pre Construct につなぐのはやめました。

カスタムイベントを用意していきます。まずテキストを受け取るイベント。

f:id:hiyokosabrey:20180721212334p:plain

つぎに、フェードを開始するイベント。

f:id:hiyokosabrey:20180721212454p:plain

処理自体はTickで動かすのでフラグを立てるだけです。

Event Tick で このブーリアンの値をチェックします。

f:id:hiyokosabrey:20180721213330p:plain

文章の長い短いに関係なくフェードの時間を均一にしてしまうと、見た目にとてもテンポが不自然になるので、テキストブロックの長さを元に速度調整用の値を作ってDeltaTimeに掛け算することでブーストすることにしました。短い文章ほど、値の変化が大きくなって短時間にフェードが完了するしくみです。

右の続きの部分はこちら。

f:id:hiyokosabrey:20180721215408p:plain

 フェードの最終値は -1.0 なので、それより大きい状態のときは、素直にフェードの値を更新。-1.0よりも小さくなったら終了なので、Tickの処理を止めるために最優先でフラグを折ります。そして-1.0以下の行き過ぎた値をなくすために、きっちり-1.0にします。

右の端は、イベントディスパッチャーです。

これを用意して呼び出してやることで、自身のフェード完了を親のWidgetに通知します。

f:id:hiyokosabrey:20180721215546p:plain

これで完成。

f:id:hiyokosabrey:20180721223633p:plain

 

全てを表示するWidget

キャンバスの中央にテキストを格納するVerticalBoxパネルを配置します。

f:id:hiyokosabrey:20180721223857p:plain

どんな行数でも上下方向にセンタリングさせたいので、アンカーは

f:id:hiyokosabrey:20180721224445p:plain

ひとまず幅は1280になるようにします。

f:id:hiyokosabrey:20180721225001p:plain

お試しならVerticalBoxがあればOKですが、見た目に雰囲気を出したいので、画面全体の下敷きとフェードインのアニメーションもつくりました。

f:id:hiyokosabrey:20180721225601g:plain

 

次にブループリントを編集します。

カスタムイベントを追加して、テキストを受け取って行ごとにバラす処理です。

f:id:hiyokosabrey:20180721231407p:plain

分解判定用文字列の『デリミタ』には 改行を示す 文字がひとつ入っています。

改行文字は Shift + Enter で入力できます。

配列に入れたら、その数ぶんループ処理を回します。

f:id:hiyokosabrey:20180721232212p:plain

 CreateWidget した結果をVerticalBoxの子供に追加。表示するテキストを渡します。

全ての行(Widget)を追加し終えたら、ForEachLoopノード Completeピンから伸ばして画面全体のフェードイン開始です。

f:id:hiyokosabrey:20180721232824p:plain

 フェードインのアニメーションが再生し終えてから、行ごとにフェードを開始するので、アニメーションの終了を待つ必要があります。そのためのノードが、Get End TimeSet Timer by Event ノードのコンビネーションです。

Set Timer by Event を使わずに アニメーションの Event Track を使う方法でも大丈夫ですが、個人的にSet Timer ~ を使う形のほうが、あとからブループリントを見たときに流れが追いやすいのでおススメです。 アニメの尺が変わっても自動で対応してくれます。

Event Track の方は尺が変わってもついてこないのと、呼び出す関数やイベントの名前を文字列で入力するのでタイプミスできないのが気を遣うのであまり好きになれません。このイベントいつ誰が呼んでるの?となった時も調べにくいです。

 

つぎに各行のフェード開始処理です。新しくカスタムイベントを追加して、Set Timer by Event ノードのEventピンとつなぎます。

f:id:hiyokosabrey:20180721235117p:plain

 VerticalBoxパネルに追加したWidgetはIndex番号で管理されていて、番号を指定して取り出すことができます。取り出したままでは使えないので、キャスト(型変換)してやります。追加したときのWidgetがその変換する型になります。

f:id:hiyokosabrey:20180721235709p:plain

「さぁフェードするのじゃ!」と関数を呼び出したあとで、「終わったら連絡するように」というのが右端のバインドノードです。イベントディスパッチャーを作っておけば、bind で検索すると出てきます。

で、終了の連絡を受けたら次の処理。

f:id:hiyokosabrey:20180722000622p:plain

順番にフェードするためのカウント用の変数を一つ加算して、次に進めるかどうかの判定をしています。まだ次があったら、アンバインド Unbind して、最初のイベントを呼びます。アンバインドは、終了通知が1回でいいのと、一度通知を受ければ連絡待機の処理が要らなくなるのが理由です。

f:id:hiyokosabrey:20180722002050p:plain

 

基本部分はこれでできたので、オマケの部分を作ります。

f:id:hiyokosabrey:20180722002014p:plain

せっかちな人向けの処理です。ゲームにも入ってました。

Wd_Sentence のGetノードは、キャストしたタイミングのやつが入ってるので、常にフェード中のWidget が格納されています。

 

次に、テキスト全部を消してVerticalBoxをキレイにするイベントです。

ただ消すだけだとかっこ悪いので、フェードアニメーションを付けます。

VerticalBoxパネルのビヘイビアにRender Opacity というとても嬉しいパラメータがあります。

f:id:hiyokosabrey:20180722002824p:plain

これを 1.0 から 0.0 に下げると簡単にフェードアニメーションが実装できます。

子供として追加した個々のWidgetにフェードアウトを用意しなくてもいいのでとても便利です。

このフェードアウトのアニメーションを作ったら、カスタムイベントで再生するようにします。

f:id:hiyokosabrey:20180722003346p:plain

再生が終わって画面から見た目に消えたら、真の消去を行います。Clear Children という物騒なノードが用意されています。

 

最後に変数リストを載せておきます。

f:id:hiyokosabrey:20180722003939p:plain

完成です。

f:id:hiyokosabrey:20180722004032p:plain

 

再生して確認

テスト再生なので、いつものように便利なレベルブループリントから行います。

レベルブループリントは以下。

f:id:hiyokosabrey:20180722004348p:plain

空行には半角のスペースが入れてあります。

 

では、いざ再生。

スペースキーで表示開始、[B]キーでフェード終了、 [N]キーで全消し。

 

f:id:hiyokosabrey:20180722005129j:plain

f:id:hiyokosabrey:20180722005142j:plain

f:id:hiyokosabrey:20180722005155j:plain

なんとか形になりました。

 

ブログに動画を直接貼れないので、あとでTwitterの方にアップしてみます。

(解像度が心配だけど・・・)

 

ということで今回はここまでです。

 

OCTOPATH TRAVELERUE4で開発されているので、UE4で真似できるはず。そう思いながらUIを触ってます。今のところ、「え?これどうやってるの?」というのは見つけてないですが、そのうち出会ったら、また目コピーに挑んでみるかもしれないです。

 

ではでは

すてきなテキスト演出ライフを!