みつまめ杏仁

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

チャットUIを作る

10

 翌日は晴れて澄んだ青空が広がっていた。抱えた屈託が全て溶けてクリアになるようなそんな色。こんな青空を見ていると、どこか遠くに行きたくなる病が発症しそうになる。なんとかチャット画面のモックがそれとなく出来上がってきたのを思い出し、続きを作らないといけない使命感をサルベージしながら移動。ようやく会社にたどり着いた。

 今日は少し早く着いたのでいつもよりフロアは静かだった。PCを起動しUE4のエディタが開くまでの間に昨日帰ってから頭の中で考えていた構造を思い出す。

 テキスト入力を左右に分離させたバージョンを作るにあたって、用意するWidgetはいくつか考えてみた。

 最初、左右反転した見た目になるのであれば、フキダシWidgetのように、片側だけ作って2個置けば?というアイデアが浮かんだ。ただし片方でテキスト入力中、もう一方は触ることができない状態にする必要がある。2つが同時にアクティブになることはない。左右の入力Widget間で相互にやり取りするのはとても面倒だ。かといって親のレイアウトWidget経由も手間が増えるだけでまどろっこしい印象。

 

 ここは一つのWidgetにキャンバスパネルによって左右2つの入力部分を管理するのがよさそうだと思い至った。さらに親のレイアウトWidgetで受け取る際にバインドしている処理も変えなくても済む。

 

f:id:hiyokosabrey:20191002191728p:plain

 

 

 まずは今のテキスト入力Widgetを複製して、これを改造していこう。コンテンツブラウザからテキスト入力Widgetを複製する。

f:id:hiyokosabrey:20190929212152p:plain

f:id:hiyokosabrey:20190929212453p:plain

 とりあえず名前を wb_WriteBox にしておこう。

 使えそうなパーツは流用する。入力用の下敷きは、いまのフキダシと同じテクスチャにして調整。ボタンは一つだけでいいので片方を消す。テキスト入力は1行入力だからMultiLineじゃないやつに差し替える。

f:id:hiyokosabrey:20190929103234p:plain

 大きさはフキダシの許容文字数分横に伸ばしておいてっと・・・こんなもんかな。顔アイコンは適当なやつをセットしておこう。

 

 入力開始ボタンのテクスチャはこんな感じでいいかな。

f:id:hiyokosabrey:20190928230958p:plain

 

 しばらくパーツの調整に手間取ったが、なんとか整った。このテキスト入力で必要な「状態」は3つ。

 

● テキスト入力中

f:id:hiyokosabrey:20190928002555p:plain

 ユーザーがコメントを入力中はこの状態。

 

 

● 待機中  [DEFAULT] 

f:id:hiyokosabrey:20190928002621p:plain

 入力する前の状態。左右どちらも同じ。この状態のみ入力を開始するための +ボタンが表示される。

 

 

● 非アクティブ [N/A] 

f:id:hiyokosabrey:20190928002639p:plain

 反対側で入力を行っている間の状態。見た目に存在感を弱く。CanvasPanelの透明度を変えるとラクチン。

 

 

 ヒエラルキーはこんな状態。

f:id:hiyokosabrey:20190928003255p:plain

 反対側も同じ構成になるので、キャンバスパネルを一つ追加してある。

このCanvasPanel_Left をまるっと複製して 名前を CanvasPanel_Right に変更。

見た目にシンメトリーになるように調整。

f:id:hiyokosabrey:20190928003811p:plain

 ヒエラルキーはこうなった。

f:id:hiyokosabrey:20190928004527p:plain

 

 入力中、待機中、非アクティブと、見た目にこの3つの状態を遷移させるためにアニメーションを使って切り替える。いきなり切り替わると味気ないので、入力開始と終了時くらいは短いながらもアニメーションを作ってみよう。状態遷移のイメージはこんな感じになると思う。

 

f:id:hiyokosabrey:20190929004248p:plain 

 

 この遷移イメージを元に、左と右それぞれにアニメーションを用意する。待機と非アクティブについては、状態の記憶として利用するので、尺は不要。開始 0.0 の位置にバシっと確実に状態を切り替えるためのキーを打つだけでいい。

 

f:id:hiyokosabrey:20190929092607p:plain

 


 これをブループリントの方で組んでいこう。

 +ボタンは、前のWidgetから残したパーツで、Buttonコンポーネントだから OnClickイベントが有効だ。そこへアニメーションの再生ノードをつなぐ。まずは左側のクリック。

f:id:hiyokosabrey:20190929095541p:plain

 右側のクリックも同じ要領で、再生するアニメーションが変わる。

f:id:hiyokosabrey:20190929095654p:plain

 Widget の Buttonコンポーネントは、画面に表示されている間はクリックを受け付けてくれる。なので、クリックした直後にアニメーションで Visibility を Collapse にするキーを打っておくと必要なクリックを防ぐことができて安全。

 Play Animationノードは、再生のトリガーでしかないので、再生を開始したらすぐに次のノードに処理が流れてくれる。ただ基本的に終わりを待ってくれないので、終わってから何かする場合だけ、特別な処理が必要になる。

 

 次は投稿の処理だけど、送信ボタンが無いので何をトリガーにしようか。確かEnterキーを判定するイベントがあったはず。

f:id:hiyokosabrey:20190929113203p:plain

 

 あった。たぶんこれだ。

f:id:hiyokosabrey:20190929202851p:plain

  Commit Method は ENumのピンだから、判定用の Equalノードが使えそう。

f:id:hiyokosabrey:20190929203134p:plain

 あった!

 ということは、さっそくプルダウンを見てみると・・・

f:id:hiyokosabrey:20190929203325p:plain

 On Enter !

 これをブランチノードで判定すればよさそうな気がする。True の時に、用意してあった、イベントディスパッチャーにテキストのピンをつなげれば入力した内容が送信できるはず。

f:id:hiyokosabrey:20190929204312p:plain

 左側用だから、isLeft を True にしておく。

 右側のも同じように作ってから、テストしてみよう。

f:id:hiyokosabrey:20190929204326p:plain

 

 テストするためには、レイアウトWidgetに配置してやらなければいけない。

 と、その前にコイツをひとまずどかす。

f:id:hiyokosabrey:20190929204705p:plain

 Visibility を Collapse にしてしまえばいいのだけど、ここは Event Pre Construct を使おう。

f:id:hiyokosabrey:20190929204848p:plain

 これで、コンパイルした瞬間にキャンバスから消えてくれる。

f:id:hiyokosabrey:20190929205135p:plain

 ヒエラルキーの可視オプションを見えないようにすればいい。存在を戻すときはノードの接続を切るだけなので安全。

f:id:hiyokosabrey:20190929205629p:plain

 これで、改めて新しい入力Widget を配置できる。

f:id:hiyokosabrey:20190929205847p:plain

 

 テキストをバインドしてイベントディスパッチャーから受け取っているところがあったから、そこをつなぎ変えないと。

 今こうなってる。

f:id:hiyokosabrey:20190929213531p:plain


 wb_WriteBoxCenter のバインドを一時的に外して、今回追加した wb_WriteBox からバインドノードを取り出す。

f:id:hiyokosabrey:20190929213355p:plain

 最終的にどちらの入力方法で行くか決まれば、片方は消滅することになる。

 よし、再生してみるか。

f:id:hiyokosabrey:20190929224404g:plain

 よしよし、うまくいってる。

 

 でも、フキダシの開始位置が低くて重なってしまっているので、VerticalBox の位置を上にあげよう。

f:id:hiyokosabrey:20190929224747p:plain

 あとは、OnEnterで送信した後の処理だな。元の待機状態に戻さないと。レイアウトWidgetコンパイルして編集完了。続いてテキスト入力Widgetの方。

 

 この続きに、閉じて元に戻す処理をつないでいく。

f:id:hiyokosabrey:20190929231149p:plain

f:id:hiyokosabrey:20190929232341p:plain

 左と右、順番に気を付けながら元に戻すためのアニメーションを再生していく。

 

 イベント全体はこんな感じ。

f:id:hiyokosabrey:20190929232604p:plain

 

 再生してみる。 

f:id:hiyokosabrey:20190930230650g:plain

  特に問題はなさそうだ。あとは顔アイコンのセットと入力の途中キャンセル、改行ができたら見せられるかな。

 とりあえず顔アイコンを渡すところを作ってしまおう。テキスト入力Widgetに受け取り用の関数を用意すればいいだろう。 

f:id:hiyokosabrey:20190930233310p:plain

 今はこの関数をに対してレイアウトWidgetから顔アイコンのテクスチャ渡すようにすればいい。

f:id:hiyokosabrey:20190930233732p:plain

 このあたりは画面の初期化処理としてイベントにしておいた方がいいかもしれない。Event Constructで処理している場合、Widget がViewportに追加された段階でデータが渡されていないといけないからだ。データの受け渡しのタイミングはネットワークが絡むと思うようにはコントロールできないことが多い。後でプログラマと相談しよう。

 

 UI表示を作る場合、Viewport に追加された瞬間に動き出すやつと、そうでないやつがある。追加された瞬間動きだすのはワンショット的に役目を果たすと消滅するタイプ。主にエフェクトとか演出系の表示。そうでない方は、基本的に常駐することになる。もう少し細かく分けると、常時表示されていて更新されていくタイプと、適宜必要に応じて呼び出されて表示するタイプに分けられる。これらのタイプを用途に合わせて設計する。プログラマに相談する際にも、この辺の分類を念頭に入れて話すとスムーズに仕様を決めることができる。と考えているのだけれど、なかなかこういった話ができるUIデザイナーが周りにいないのはさみしい。

 さて、わかりやすくコメントをつけておいて、あとで改造しやすくしたところで、次はキャンセル処理か。

 入力用Widgetの外をクリックしたら入力をキャンセルして閉じるようにしてみよう。感触が良くなかったら、キャンセルボタンを置くことになるかな。

 

 テキスト入力エリア以外のクリック判定なので レイアウトWidgetに判定させるのがよさそう。

 OnMouseButtonDown 関数をオーバーライドしてと、

f:id:hiyokosabrey:20191001085914p:plain

f:id:hiyokosabrey:20191001085414p:plain

 ここに判定用のパーツを持ってくるわけだけど、Widgetのクリック検出は、Visible状態じゃないと判定してくれないので、キャンバスに何も置いていないとこの関数が呼び出されることはない。というわけで、キャンバスにテキスト入力Widgetを避けて、CanvasPanelを配置する。

f:id:hiyokosabrey:20191001091446p:plain

 このCanbasPanel の Visibility の設定をVisibleに 変更しておく。

f:id:hiyokosabrey:20191001091653p:plain

 名前を CanvasPanel_CancelArea としておこう。

 これをさっきオーバーライドした関数の中に置く。

f:id:hiyokosabrey:20191001092051p:plain

 レイアウトWidgetのキャンバスに置かれているすべてのパーツが検出対象で、何かあるところでマウスボタンを押すと、この関数が呼び出される。特定のパーツのみを対象にしたい。ここで判定してはじくために isHoverd ノードを使う。hover は WebページのデザインでCSSを触ったことがあるならピンとくるはず。このノードはカーソルが乗っているかどうかをブーリアンの値で返してくれるノード。テストするためにPrintStringをつないでみる。

f:id:hiyokosabrey:20191001093119p:plain

 コンパイルするとWarningが出た。

f:id:hiyokosabrey:20191001093414p:plain

 そうだ、ReturnValue が空いてるとダメなのだった。Handled ノードをつないで祈る。鎮まり給へ!

f:id:hiyokosabrey:20191001093603p:plain

 鎮まった。

 テストしてみよう。

f:id:hiyokosabrey:20191001151550p:plain

 よし、ちゃんと反応してる。

 

 今度は閉じるイベント作って、それを呼び出せばいい。テキスト入力Widgetを編集しよう。

 ただ呼び出すだけでいいのか?勝手にいろんなタイミングでクリックされるから、都合の悪い時がある。あと、入力中のやつはキャンセルするけど、非アクティブの方はすでに閉じてる。となると、フラグが要るな。ブーリアン型の変数を2つ用意しよう。

f:id:hiyokosabrey:20191001152559p:plain

 まずこの値をセットするはここだ。+ のButtonをクリック処理するところ。

f:id:hiyokosabrey:20191001153024p:plain

f:id:hiyokosabrey:20191001153041p:plain

 

 このフラグを元にカスタムイベントで閉じることが可能かチェックする。

f:id:hiyokosabrey:20191001153615p:plain

 このあとに閉じるアニメーションをつなぐんだけど、ちょうどいい場所があった。

f:id:hiyokosabrey:20191001153839p:plain

 すっぽり!

 送信した直後だから、ただ閉じる処理しかしていない。で、この処理の一番最後尾でフラグを元に戻せばいい。

f:id:hiyokosabrey:20191001154238p:plain

 これで、テキスト入力Widget側の準備はできた。レイアウトWidgetの OnMouseButtonDown 関数に戻る。テストでPrintStringをつないでいたところを差し替える。さっき準備したカスタムイベントを呼び出す。f:id:hiyokosabrey:20191001154455p:plain

 これでOK。

 テストしてみよう。

f:id:hiyokosabrey:20191001160146g:plain

 よし、これで良さそうだ。あとはフキダシの改行表示かな。うまくいくといいけど。

 

 

 しばらくキャンバスのパーツを触っていると、SizeBoxにそれっぽいパラメータを発見。あと、TextBlock の改行設定を入れておく必要があった。

 SizeBoxの子供の TextBlock_Body に改行無しのダミーテキストを入れて、Wrappingの設定を変更する。

f:id:hiyokosabrey:20191001181436p:plain

f:id:hiyokosabrey:20191001181642p:plain

f:id:hiyokosabrey:20191002174005p:plain


 で、SiizeBoxの方も設定を変える。

f:id:hiyokosabrey:20191001175946p:plain

 まず SizeBox の Size To Content を一時的に無効にする。

f:id:hiyokosabrey:20191001180237p:plain

  いい感じのサイズになるようにSizeXとYを調整。

 そして、Child Layout カテゴリの  Max Desired WidthMax Desired Height を有効にして Size X と Size Y を移植する。Size To Content のチェックを戻せばOK。

f:id:hiyokosabrey:20191001180755p:plain

 これで大丈夫。

 テストしてみよう。

f:id:hiyokosabrey:20191001200706g:plain

 よかった。なんとかできた。

 

 あとはプレゼンに向けてちょっと細工を。レイアウトWidgetの送信処理のところを、2種類のテキスト入力Widgetを両方有効にしておく。

f:id:hiyokosabrey:20191001222245p:plain

 Event Pre Construct のところで、切り替えられるようにする。

f:id:hiyokosabrey:20191001223509p:plain

 コンパイルが必要だけど、ブランチノードのCondition を切り替えてコンパイルするだけで切り替えができる。確認しておこう。

 

 ?!・・・あれ?なんかスクロールがおかしいな。

 

 

つづく