PS5の予約が始まりましたね。初代機が発売されて四半世紀が経過。あの四角形のポリゴンを傾けたときのテクスチャの歪みに抗おうとしてた頃が懐かしいです。
ふと当時が思い出されたので、Photoshopでドットを描いて再現してみました。
今はもう、少ないパレットカラーでレンガを作ることもないのですね。
さて、しみじみしたところで、今回は、ツール的なものをネタに書いていきます。
ジャンル的にADVでいいのかな。大昔のPCゲームでアドベンチャーゲームといえば、アドベンチャーな世界で、アドベンチャーなストーリー展開でまさにアドベンチャーしていたとかすかに記憶していますが、ヴィジュアルノベルとかノベルゲームの方がアドベンチャーゲームの系譜に近い気がする。実は世間ではすでにアドベンチャーゲームってなんだよ、意味わかんねーよw、と言われているような気がしないでもない。そんなADVゲーム的なゲームで必ずといっていいほど見かけるのが セリフ枠 とか メッセージ枠 とか呼ばれるやつ。「枠」は「ウィンドウ」に置き換えて メッセージウィンドウ という呼び方もします。メインのゲームがADVじゃなくても途中で挿入される会話劇なんかで今でもよく見かけます。
美しい立ち絵を眺めながら小芝居と会話劇を堪能するためのセリフ表示システムとしての役割で扱われている印象が強いゲームのテキスト表示とその周辺のUI。
位置固定式フキダシ表示システム とでも言いましょうか。Googleの画像検索でスクショを眺めてみると、タイトルを印象付けるユニークなデザインになっている様子がうかがえます。UIデザイナーとしては頑張り甲斐のあるところですね。
ゲームプレイ全体で目にする時間も長いので、デザインも凝ったものが多く、またいろんな機能がついていたりします。中でも当たり前にあるのが表示速度調整。コンフィグとかの設定をみると、ゆっくり~はやい などでだいたい3,4段階というところでしょうか。
調整機能を入れつつも、タイピング中にボタンを押すと最後まで一気に表示する仕組みも今では普通に実装されていて、さらに連打やうっかり押しちゃってさっきのセリフなんだっけ?となったときのために会話ログが閲覧できるようになっていたりと、UI的にもかなりの進化を遂げていると思います。
だいぶ前置きが長くなりましたが、今回作ったのはこの調整機能を実装するために段階を確定するための検証用の仕組みになります。ややこしい。
つまり開発用です。プロトタイプとしてサクッと作ってみました。
ではさっそくベースになるセリフ表示のパーツから
まずはタイプに使う文字。
キータイプっぽい見せ方をしたかったのでカーソルの残像が残るようなアニメーションから作っていきます。
新しいWidgetをアセットブラウザ上で作成。
ダブルクリックして開いたら、
キャンバスに 新しく CanvasPanel を置きます。
キャンバスの方にドロップしてもいいのですが、ポジションをリセットするのが面倒なので、ポジションを調整しない場合はヒエラルキーパネルの方にドロップすると楽ちんです。
置いたら、 Size To Content を有効にします。
続けて
CanvasPanelの中に TextBlock をひとつ追加して、
書体と文字のサイズを決めたら、これも Size To Content を有効にしておきます。
これで、文字の大きさによってキャンバスパネルが可変してくれるようになります。
さらに Is Variable も有効にします。
これを有効にすると、ブループリントから触れるようになります。
TextBlockの上に Image を被せます。
Imageのアンカーは右下のやつ。
Offsetはすべて ゼロ
これでCanvasPanelの大きさに追随してくれます。
結果的に 文字の大きさに合わせて Image のサイズも変化することになります。
3つのパーツを追加しました。ヒエラルキーは以下のようになります。
次はアニメーション。
Image が透明になって消えるだけです。 ループはしません。
キャンバスの編集はこれで終わり。
ブループリントを編集していきます。
文字を受け取るために、Text型の変数を一つ追加。
記号なども含めて「文字」のことを英語でキャラクターというので、Character から4文字いただきました。
Expose on Spawn と Instance Editable を有効にしておきます。
このWidgetが生成される際に、同時に値を受け取ることができます。
Event Construct にノードをつないでいきます。
Delay ノードは必要というわけではないので無くても大丈夫。
雰囲気的に調整する目的で置いてますが、0 を入れるとスルーします。
この待ち時間が必要と決まれば、アニメーションの方を編集して尺を伸ばし、このDelayノードは削除しましょう。
このWidgetアセットを アセットブラウザ上で複製します。
名前は 待機用ということで wb_wait にします。
中身をいじっていきます。
キャンバスの TextBlock を 見えなくします。
アニメーションも削除します。
次にブループリント。
Event Construct につなっがているノードをごっそり消します。
Text型の変数 Char も不要なので削除します。
スッキリしたところで、カスタムイベントを追加。
点滅する処理を作ります。
ループアニメーションを使うと、ノードはシンプルになりますが、ただ点滅させるだけなのでタイマーを使います。
Set Timer by Event ノードの 赤いラインは、ちょっと変わっていて イベントとつなぐために使います。つないだイベントを呼び出すことができるのです。なので、この場合時間が来たら再び先頭のイベントが呼ばれるのでまた自身に処理がやってきます。いわゆるループです。
FlipFlopノードは、処理が来るたびに、出口がAとB交互に切り替わるので結果的に点滅することになります。
Set Timer by Event ノードが呼ばれなくなったら実質ループ終了ということになりますが上の場合特に何も条件を付けていないので、放っておくと無限にループしてしまいます。さすがに無限ループはご法度なので、しっかり止める仕組みを入れます。それがタイマーハンドラー。
タイマーハンドラーは、Set Timer by Event ノードの Return Value ピンから作ります。
ドラッグしてPromote to Variable を選択すると、変数が作られます。
これはタイマーイベントをハンドリング(制御)するために用意する変数で、タイマーがセットされて定刻になるまでに中止するときや、一時的にポーズするときなどに使います。
今回は、このWidgetが消されるときに、タイマーを止める処理で使います。
最初から用意されているイベント Event Destruct ノード を取り出して、タイマーを止める Clear and Invalidate Timer by Handle ノードをつなぎます。
仕上げに、点滅用のカスタムイベントを Event Construct につないで2つ目のWidget完成。
そして3つ目のWidget
ゲーム画面ぽいやつ
キャンバスにそれっぽいのをレイアウトします。
必要なパーツは WrapBox
これは子供の要素を左上詰めで並べていって、決めておいた幅を越えるのがわかると、改行してくれるパネルです。ガンガン Add Childs しても右に はみ出すことはありません。
ここにテキストを流し込むので、それ以外は自由なデザインで。
キャンバスがいい感じになったら、グラフの編集。
変数を4タイプ6つ新しく用意します。
文字をタイプしていくための カスタムイベントを用意します。
ブループリントから動的にオブジェクトを生成する Construct Object from Class ノードに、
文字表示用に作っておいた wb_char をセットすると、
Expose on Spawn した変数が入力ピンとして自動的に出てきます。
Outerピンにはそのままだとコンパイルエラーになるので、 Self ノードをつないでおきます。
この生成された結果が、WrapBoxの子供として追加されるようにしています。
このイベントが呼び出されるたびに、一文字追加というイベントになります。
その文字を一文字を抽出しているのが Midノード。
Text型はあまりいじることができないのでString型の状態で扱います。
Stringを操作するノードがたくさんある中に Mid と GetSubstring が用意されています。
ヒストリアさんがきれいにまとめてくださってます。
JavaScriptだと charAt() に相当するやつを探してみたけど用意されていないっぽいですね。
で、続き
ここでも Set Timer by Event が出てきました。タイマーハンドラーもここで作っています。
最後までテキストを表示し終わったら、次のテキストへの待機表現が必要です。
右端は イベントディスパッチャー を Call しています。
作り方は、My Blueprintタブの一番下
特別なことはしないので、名前だけわかりやすくしておけばいいと思います。
取り出すときにポップアップが出るので Call を選択。
全体のノードはこんな状態。
次に
テキストを受け取る関数を用意します。
タイピング中にこの関数が呼ばれることは無いと思いたいけど、isTypingフラグを用意したので、念のためにブランチノードで判定。このフラグは今回はこれくらいの役割しかないけど、タイピングを途中でスキップする時に使えると思います。
WrapBoxはリセットしないと、どんどん追加されてしまうので、このタイミングでクリアにします。
今回見やすくするためにマクロにしました。
あとちょっと
最後の仕上げに、カスタムイベントを2つ用意します。
外から書き込まれるイベントです。受け取った値を変数に入れるだけです。
これでメインのテキスト表示システムは完成。
長くなったので、残りは《後編》に持ち越すことにします。
が一応テストしておきましょう。
適当なレベルを作ってWidgetを表示してみます。
Fileメニューから New Level を選択。
レベルブループリントの編集は ↓ ここから
実行の前に wb_TextType で用意した変数の初期値を確認。
ClassDefaultをクリックすると編集がラクチンです。
これで準備完了。
さっそく表示テストしましょう。
ファイルサイズが大きすぎて全画面の動画は上げられなかったので、部分だけGIFにしました。
↑ 森見登美彦著『有頂天家族』(幻冬舎文庫) 淀川教授のセリフから引用
今回はこの辺で
次回《後編》で調整用のUIを実装します お楽しみに~