みつまめ杏仁

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

セリフの表示の速さを調節するあれのあれ《前編》

PS5の予約が始まりましたね。初代機が発売されて四半世紀が経過。あの四角形のポリゴンを傾けたときのテクスチャの歪みに抗おうとしてた頃が懐かしいです。

ふと当時が思い出されたので、Photoshopでドットを描いて再現してみました。

 

f:id:hiyokosabrey:20200919010108p:plain



今はもう、少ないパレットカラーでレンガを作ることもないのですね。

 

 

 さて、しみじみしたところで、今回は、ツール的なものをネタに書いていきます。

 ジャンル的にADVでいいのかな。大昔のPCゲームでアドベンチャーゲームといえば、アドベンチャーな世界で、アドベンチャーなストーリー展開でまさにアドベンチャーしていたとかすかに記憶していますが、ヴィジュアルノベルとかノベルゲームの方がアドベンチャーゲームの系譜に近い気がする。実は世間ではすでにアドベンチャーゲームってなんだよ、意味わかんねーよw、と言われているような気がしないでもない。そんなADVゲーム的なゲームで必ずといっていいほど見かけるのが セリフ枠 とか メッセージ枠 とか呼ばれるやつ。「枠」は「ウィンドウ」に置き換えて メッセージウィンドウ という呼び方もします。メインのゲームがADVじゃなくても途中で挿入される会話劇なんかで今でもよく見かけます。

 美しい立ち絵を眺めながら小芝居と会話劇を堪能するためのセリフ表示システムとしての役割で扱われている印象が強いゲームのテキスト表示とその周辺のUI。

 位置固定式フキダシ表示システム とでも言いましょうか。Googleの画像検索でスクショを眺めてみると、タイトルを印象付けるユニークなデザインになっている様子がうかがえます。UIデザイナーとしては頑張り甲斐のあるところですね。

 ゲームプレイ全体で目にする時間も長いので、デザインも凝ったものが多く、またいろんな機能がついていたりします。中でも当たり前にあるのが表示速度調整。コンフィグとかの設定をみると、ゆっくり~はやい などでだいたい3,4段階というところでしょうか。

 調整機能を入れつつも、タイピング中にボタンを押すと最後まで一気に表示する仕組みも今では普通に実装されていて、さらに連打やうっかり押しちゃってさっきのセリフなんだっけ?となったときのために会話ログが閲覧できるようになっていたりと、UI的にもかなりの進化を遂げていると思います。

 

 だいぶ前置きが長くなりましたが、今回作ったのはこの調整機能を実装するために段階を確定するための検証用の仕組みになります。ややこしい。

 つまり開発用です。プロトタイプとしてサクッと作ってみました。

f:id:hiyokosabrey:20200919153758j:plain

 

ではさっそくベースになるセリフ表示のパーツから

 

 

まずはタイプに使う文字。

キータイプっぽい見せ方をしたかったのでカーソルの残像が残るようなアニメーションから作っていきます。

 

新しいWidgetをアセットブラウザ上で作成。

f:id:hiyokosabrey:20200919162514p:plain

ダブルクリックして開いたら、

キャンバスに 新しく CanvasPanel を置きます。

f:id:hiyokosabrey:20200919162219p:plain

キャンバスの方にドロップしてもいいのですが、ポジションをリセットするのが面倒なので、ポジションを調整しない場合はヒエラルキーパネルの方にドロップすると楽ちんです。

 

置いたら、 Size To Content を有効にします。

f:id:hiyokosabrey:20200919154808p:plain

 

続けて

CanvasPanelの中に TextBlock をひとつ追加して、

f:id:hiyokosabrey:20200919162905p:plain

書体と文字のサイズを決めたら、これも Size To Content を有効にしておきます。

f:id:hiyokosabrey:20200919154606p:plain

これで、文字の大きさによってキャンバスパネルが可変してくれるようになります。

f:id:hiyokosabrey:20200919155427g:plain

さらに Is Variable も有効にします。

f:id:hiyokosabrey:20200919172605p:plain

これを有効にすると、ブループリントから触れるようになります。

 

TextBlockの上に Image を被せます。

f:id:hiyokosabrey:20200919162920p:plain

 

Imageのアンカーは右下のやつ。

f:id:hiyokosabrey:20200919155819p:plain

Offsetはすべて ゼロ

f:id:hiyokosabrey:20200919160123p:plain

これでCanvasPanelの大きさに追随してくれます。

f:id:hiyokosabrey:20200919155644p:plain

結果的に 文字の大きさに合わせて Image のサイズも変化することになります。

 

3つのパーツを追加しました。ヒエラルキーは以下のようになります。

f:id:hiyokosabrey:20200919160404p:plain

 

次はアニメーション。

f:id:hiyokosabrey:20200919160709g:plain

Image が透明になって消えるだけです。 ループはしません。

キャンバスの編集はこれで終わり。
ブループリントを編集していきます。

 

文字を受け取るために、Text型の変数を一つ追加。

記号なども含めて「文字」のことを英語でキャラクターというので、Character から4文字いただきました。

f:id:hiyokosabrey:20200919163248p:plain

Expose on SpawnInstance Editable を有効にしておきます。

このWidgetが生成される際に、同時に値を受け取ることができます。

 

Event Construct にノードをつないでいきます。

f:id:hiyokosabrey:20200919165743p:plain

Delay ノードは必要というわけではないので無くても大丈夫。

雰囲気的に調整する目的で置いてますが、0 を入れるとスルーします。

この待ち時間が必要と決まれば、アニメーションの方を編集して尺を伸ばし、このDelayノードは削除しましょう。

 

これでWidgetが一つ完成です。wb_char と命名

f:id:hiyokosabrey:20200919171606p:plain

このWidgetアセットを アセットブラウザ上で複製します。

f:id:hiyokosabrey:20200919171756p:plain

 

名前は 待機用ということで wb_wait にします。

f:id:hiyokosabrey:20200919171933p:plain

中身をいじっていきます。

 

キャンバスの TextBlock を 見えなくします。

f:id:hiyokosabrey:20200919172214p:plain

アニメーションも削除します。

 

 

次にブループリント。

Event Construct につなっがているノードをごっそり消します。

f:id:hiyokosabrey:20200919173100p:plain

Text型の変数 Char も不要なので削除します。

 

スッキリしたところで、カスタムイベントを追加。

点滅する処理を作ります。

ループアニメーションを使うと、ノードはシンプルになりますが、ただ点滅させるだけなのでタイマーを使います。

f:id:hiyokosabrey:20200919173847p:plain

Set Timer by Event ノードの 赤いラインは、ちょっと変わっていて イベントとつなぐために使います。つないだイベントを呼び出すことができるのです。なので、この場合時間が来たら再び先頭のイベントが呼ばれるのでまた自身に処理がやってきます。いわゆるループです。

FlipFlopノードは、処理が来るたびに、出口がAとB交互に切り替わるので結果的に点滅することになります。

Set Timer by Event ノードが呼ばれなくなったら実質ループ終了ということになりますが上の場合特に何も条件を付けていないので、放っておくと無限にループしてしまいます。さすがに無限ループはご法度なので、しっかり止める仕組みを入れます。それがタイマーハンドラー。

 

タイマーハンドラーは、Set Timer by Event ノードの Return Value ピンから作ります。

f:id:hiyokosabrey:20200919174444g:plain

ドラッグしてPromote to Variable を選択すると、変数が作られます。

これはタイマーイベントをハンドリング(制御)するために用意する変数で、タイマーがセットされて定刻になるまでに中止するときや、一時的にポーズするときなどに使います。

今回は、このWidgetが消されるときに、タイマーを止める処理で使います。

 

最初から用意されているイベント  Event Destruct ノード を取り出して、タイマーを止める Clear and Invalidate Timer by Handle ノードをつなぎます。

f:id:hiyokosabrey:20200919175324p:plain

仕上げに、点滅用のカスタムイベントを Event Construct につないで2つ目のWidget完成。

f:id:hiyokosabrey:20200919180345p:plain

 

 

 

そして3つ目のWidget

ゲーム画面ぽいやつ

f:id:hiyokosabrey:20200919215109p:plain

キャンバスにそれっぽいのをレイアウトします。

f:id:hiyokosabrey:20200919215806p:plain

必要なパーツは WrapBox

これは子供の要素を左上詰めで並べていって、決めておいた幅を越えるのがわかると、改行してくれるパネルです。ガンガン Add Childs しても右に はみ出すことはありません。

f:id:hiyokosabrey:20200919220015p:plain

ここにテキストを流し込むので、それ以外は自由なデザインで。

 

キャンバスがいい感じになったら、グラフの編集。

 

変数を4タイプ6つ新しく用意します。

f:id:hiyokosabrey:20200920222645p:plain

 

文字をタイプしていくための カスタムイベントを用意します。

f:id:hiyokosabrey:20200920225952p:plain

ブループリントから動的にオブジェクトを生成する Construct Object from Class ノードに、

f:id:hiyokosabrey:20200921101334p:plain

 

文字表示用に作っておいた wb_char をセットすると、

f:id:hiyokosabrey:20200921102040p:plain

Expose on Spawn した変数が入力ピンとして自動的に出てきます。

f:id:hiyokosabrey:20200921101556p:plain

Outerピンにはそのままだとコンパイルエラーになるので、 Self ノードをつないでおきます。

この生成された結果が、WrapBoxの子供として追加されるようにしています。

このイベントが呼び出されるたびに、一文字追加というイベントになります。

 

その文字を一文字を抽出しているのが Midノード。

Text型はあまりいじることができないのでString型の状態で扱います。

Stringを操作するノードがたくさんある中に Mid  と GetSubstring が用意されています。

ヒストリアさんがきれいにまとめてくださってます。

historia.co.jp

JavaScriptだと charAt() に相当するやつを探してみたけど用意されていないっぽいですね。

 

で、続き

f:id:hiyokosabrey:20200921105613p:plain

ここでも Set Timer by Event が出てきました。タイマーハンドラーもここで作っています。

 

最後までテキストを表示し終わったら、次のテキストへの待機表現が必要です。

f:id:hiyokosabrey:20200921111350p:plain

右端は イベントディスパッチャー を Call しています。

作り方は、My Blueprintタブの一番下

f:id:hiyokosabrey:20200921111857p:plain

特別なことはしないので、名前だけわかりやすくしておけばいいと思います。

取り出すときにポップアップが出るので Call を選択。

f:id:hiyokosabrey:20200921112101p:plain

 

 

全体のノードはこんな状態。

f:id:hiyokosabrey:20200921111532p:plain

 

次に

テキストを受け取る関数を用意します。

f:id:hiyokosabrey:20200921120323p:plain

タイピング中にこの関数が呼ばれることは無いと思いたいけど、isTypingフラグを用意したので、念のためにブランチノードで判定。このフラグは今回はこれくらいの役割しかないけど、タイピングを途中でスキップする時に使えると思います。

WrapBoxはリセットしないと、どんどん追加されてしまうので、このタイミングでクリアにします。

今回見やすくするためにマクロにしました。

f:id:hiyokosabrey:20200921121135p:plain

 

あとちょっと

最後の仕上げに、カスタムイベントを2つ用意します。

f:id:hiyokosabrey:20200921122035p:plain

外から書き込まれるイベントです。受け取った値を変数に入れるだけです。

 

これでメインのテキスト表示システムは完成。

長くなったので、残りは《後編》に持ち越すことにします。

が一応テストしておきましょう。

 

適当なレベルを作ってWidgetを表示してみます。

Fileメニューから New Level を選択。

f:id:hiyokosabrey:20200921125151p:plain

レベルブループリントの編集は ↓ ここから

f:id:hiyokosabrey:20200921125457p:plain

f:id:hiyokosabrey:20200921130622p:plain



実行の前に wb_TextType で用意した変数の初期値を確認。

f:id:hiyokosabrey:20200919215109p:plain

 ClassDefaultをクリックすると編集がラクチンです。

f:id:hiyokosabrey:20200921131255p:plain

f:id:hiyokosabrey:20200921131642p:plain

 

これで準備完了。

さっそく表示テストしましょう。

f:id:hiyokosabrey:20200921135250j:plain

f:id:hiyokosabrey:20200921135300j:plainファイルサイズが大きすぎて全画面の動画は上げられなかったので、部分だけGIFにしました。

f:id:hiyokosabrey:20200921140152g:plain

森見登美彦著『有頂天家族』(幻冬舎文庫) 淀川教授のセリフから引用


今回はこの辺で

次回《後編》で調整用のUIを実装します お楽しみに~