みつまめ杏仁

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

チャットUIを作る

7

 調べてみたら、テキスト入力を扱った記事が見つかった。思ってたよりむずかしくなさそうだ。よしさっそく参考にしながら作ってみよう。そういえば、入力のスタイルを2種類作ってみる、などと谷山田との会話で豪語していたのを思い出した。仕方がない、まずは中央のボックスバージョンからつくろう。

 

 画面のラフデザインはこれだ。

f:id:hiyokosabrey:20190511100114j:plain

 

 新しくWidgetを作成する。名前は wb_WriteBoxCenter にしておこうか。下敷きの見た目はフキダシ用のテクスチャを代用する。あと左右の矢印ボタンを用意すれば作れそうだ。

 

  Photoshopで右向きの矢印を作る。べき乗が大好きなので大きさは 64x128px。左向きは反転すればいい。背景と距離感を出すためにフチドリを付けておこう。

 f:id:hiyokosabrey:20190819234606p:plain

  デザインはテクスチャサイズいっぱいまで詰めない。何事もゆとりは大事。

 

 テクスチャをインポートしたら、新しく作った Widget を開く。まっさらなキャンバスに向かい合いレイアウトのイメージを頭に思い出す。画面の下にそれほど占有しないので、フルスクリーンのキャンバスは必要ないだろう。エディタ右上の設定を変更する。画面のラフデザインから大体のサイズを測ってキャンバスサイズとして設定。

f:id:hiyokosabrey:20190820000828p:plain

 これでUMGに最初から置かれているデフォルトの CanvasPanel のサイズが変更できる。

f:id:hiyokosabrey:20190820001126p:plain

 

 ここに下敷きと、左右の矢印、テキストボックスを配置していこう。

 まずは 下敷きを中央に。Image を置いて、テクスチャにフキダシで使っているやつをセットする。自分専用なのでカラーを変えておこう。配置はストレッチにするけど、左右にボタンをおくから余白を開けておく。

f:id:hiyokosabrey:20190820233644p:plain

f:id:hiyokosabrey:20190820234243p:plain

この設定にしておけば、あとからキャンバスのサイズを変更しても追随するから安心。


 次は左右の矢印。矢印はクリックを受け付けるので、Imageではなく Buttonで配置する。テクスチャと同じくサイズを64x128にしてレイアウトしておこう。

f:id:hiyokosabrey:20190821011755p:plain

 アンカーを右端中央にして、Pivotを(1.0, 0.5)にして、Positionを (0, 0)。あとは、Buttonに画像をセットする。ふるまいに合わせて画像を変えられるけど、今回は同じ画像にしてカラー変更で対応しておこう。通常時の Normal、マウスカーソルが乗った時の Hovered、そしてクリックしたときのPressedの3箇所に矢印のテクスチャ画像をセットする。

f:id:hiyokosabrey:20190821013224p:plain

 右側ができたから、このButtonを複製して左側に配置する。今度はアンカーを左端中央にして、Pivotを(0.0, 0.5)にして、これもPositionを (0, 0) にする。

f:id:hiyokosabrey:20190820004505p:plain

 左側の RenderTransform の Size X を -1.0 にして左右を反転すると、

f:id:hiyokosabrey:20190820004815p:plain

 よしできた。

 あとは、この上にテキストボックスを載せるだけ。えっとどれだっけ?

EditableText (Multi-Line) こっちだな。

f:id:hiyokosabrey:20190821010250p:plain

 これも下敷きと同じようにアンカーはストレッチでいこう。フォントとMarginを設定して、

f:id:hiyokosabrey:20190821010600p:plain

 よし。こんなもんでいいかな。ヒエラルキーはこんな感じになった。

f:id:hiyokosabrey:20190822225631p:plain

 いったん保存して、レイアウトしてみよう。

 

 レイアウト用のWidgetを開いて、User Created のカテゴリから、作ったばかりのテキスト入力Widgetを探すとすぐに見つかった。早速キャンバスに配置する。

 

あれ?

f:id:hiyokosabrey:20190823232533p:plain

 Size To Content にチェックを付けるとつぶれてしまった。あのエディタ右上のキャンバスサイズはあくまでも作業のための設定ということか。仕方がないので手入力でサイズをセットする。 アンカーは中央下端。

f:id:hiyokosabrey:20190822230218p:plain

 試しに変なサイズにしても、ちゃんと問題なく合わせてくれる。

f:id:hiyokosabrey:20190823233236p:plain

 

 

 再生してみると、特に問題なくレイアウトが確認できた。

f:id:hiyokosabrey:20190823002220j:plain

 さて、ボタンをクリックしたときにメッセージをサーバーに飛ばすことになるんだけど、そういえば、左右の振り分けとテキストの送信については相談してなかったな。

 席を立ち、プログラマの南河原さんのところに向かう。プロジェクトがまだ小規模なので、そんなに席が離れていない。背後からおつかれさまですと声をかける。

 「今、大丈夫ですか?」

 彼はコードを吟味していた手を止め「はいはい。」と振り向いた。

 そこで、

 「例のチャット表示を作ってるんですけど、」と切り出し、ユーザーの発言毎に、画面の左右に表示位置を振り分けたいということと、そのユーザーが任意で右か左かを選べるということを再度確認の意味を込めて話した後、

 「テキストと一緒に左か右かの情報をくっつけて送ることになると思うんですよね。もう準備とか始められてたりします?」

 「いや、まだなんも手ぇ付けてない。来週あたりサーバー担当と話しよかなって。」

 うーん、と言ってつかのま考えてから、

 「そうやなぁ、他にも ユーザーID とかアイコン情報とか諸々決めなアカンねんけど、すぐ決めた方がいい?」

 そう訊かれたので、

 「いえ、大丈夫です。プロトタイプなんで適当にやっときます。」

 と答えると、

 「ゴメンな。決まったらまた共有するし。」

 「はい、お願いします。」

 

 ということで、自席に戻って考えてみる。おそらくサーバーへ送信するための関数を用意してもらえると思うので、それをイメージしたテスト用の関数をこちらで用意しよう。あとは受け取り用のフキダシ追加イベントのパラメータを拡張していけばいいだろう。

 今は実装方法を詰めたいわけではなく、とにかくプロトタイプを触って操作感や挙動について検証し、最終的なゴールをプロジェクト内で共有するのが目的だ。そこで見つかった問題を解決し、「余分」と「余白」について検討する。「余分」は「余白」を最大化するための伸びしろだと捉えるようにしている。一方の「余白」は将来の「余裕」と見るか、「不足」と見るかは判断が難しいところだけど、仕様変更や調整の波をかぶりやすいUIにとっては「保険」という意味合いも含ませていいと個人的には考えている。他人には「物足りない」という印象を与えてしまうリスクがあるけれど、「あ、まだ仮なんで」という決めゼリフがあるので問題ない。ちなみにこの決めゼリフを本実装のあとで吐くことになると辛い。声が震えるのを抑えながら言うことになる。

 

 さてと、フキダシとしてサーバーから送られてくるコメントを表示するにあたって、フキダシWidgetが欲しい情報で思いつくのは以下の 5つ。

 

 ①ユーザーID

 ②コメント本文

 ③左か右

 ④顔アイコン

 ⑤自分かどうか

 

 ローカライズはしないのと、加工や整形ができるので ①と②はString型でいいと思う。③と⑤は選択肢としては2つしかないのでBoolean型でいけそう。④はTexture型で。ひとまずこれでストラクチャを使ってみよう。

 

 ストラクチャはコンテンツブラウザで右クリック、Blueprints の中にある。

 chatComment と命名。あとでちゃんとしたやつが来るので、それまでの暫定ネーム。

f:id:hiyokosabrey:20190824204134p:plain

ダブルクリックして中身を編集。

f:id:hiyokosabrey:20190823235936p:plain

 保存して、レイアウト用Widgetに戻る。

 

 まずセットするのは、フキダシを追加するイベントのところ。

f:id:hiyokosabrey:20190824001605p:plain

 イベントノードをフォーカスして、Detailsタブの項目 Inputs からピンを追加する。型は、さっき作ったストラクチャの名前で検索するとリストから見つかるのでそれを選択。

f:id:hiyokosabrey:20190824203838p:plain


 適当に名前を付けると、カスタムイベントのノードに濃い青色のピンが追加された。

 ストラクチャは、複数のデータの寄せ集めな状態なので、一つのピンから情報を個別に取り出す必要がある。そこで Breakノードの出番。

f:id:hiyokosabrey:20190824211609g:plain

 

 Breakノードで分解されたピンから、ストラクチャで自分が追加した CommentBody があるので、選んでダミーテキストをつないでいたところに刺す。

f:id:hiyokosabrey:20190824204640p:plain

 String型とText型は型が違う。このように違う型同士をつなぐ場合、キャスト(型変換)してからつなぐのが儀式なんだけど、UE4のブループリントエディタは直接つなぐことができて、さらに自動的にキャストノードを間に入れてくれるので、ラクチンだ。

 よし、このままフキダシの左右レイアウト対応もやっておこうか。

 まずはダミーコメントの表示で使ってたカウンター処理を外す。

f:id:hiyokosabrey:20190824212844p:plain

 この右端の外したところに、水平方向のレイアウトで、右寄せ、左寄せを決める set Horizontal Alignment ノードをつなぐんだけど、えっとどこからだっけ? VerticalBoxの設定になるから、Add Child to Vertical Box ノードにある、Return Valueから探せば、あ、あったあった。

f:id:hiyokosabrey:20190824213423p:plain

 なんだか、Vertical(垂直)、Horizontal(水平)と混ざり合ってややこしいけど、カテゴリが、 Layout > Vertical Box Slot になってるからここで間違いない。

 出てきたノードをつないで、ラインを整える。

f:id:hiyokosabrey:20190824214406p:plain

 で、この濃い緑のピンにつなぐのは、ストラクチャの isLeft というBoolean型のピンだけど、こんなときは Select ノードの出番。この濃い緑のピンからドラッグして、Select で検索する。

f:id:hiyokosabrey:20190824220501p:plain

 いい感じにノードが見つかって取り出すとき、思わずドラえもんの声真似をしてしまう。大丈夫、声には出てない。このSelectノードの 一番下にあるピンとストラクチャに設定した isLeft ピンとつなぐ。

f:id:hiyokosabrey:20190824221334p:plain

  どう見ても、Integer型のピンだし、Index って書いてあるけど、ここも気にせずつなげることができる。先にプルダウンを開けてBooleanに変えておいてもいい。

 

f:id:hiyokosabrey:20190824222020p:plain

 つなぐと、Selectノードの 左にある入力ピンのラベルが変更される。isLeft の内容が True なら 左。Falseならその逆の右 になるので、それぞれのプルダウンを変更する。

f:id:hiyokosabrey:20190824222518p:plain

 これで、コメントのフキダシが左右に振られるようになったはず。

 

 

 ここで、現状のコメントが表示されている流れを確認しておこう。

 

 スペースキーが押されたかどうかの検出を、レベルブループリントで行っている。

キー入力系のイベントは、Widget内に置けないからだ。

 

f:id:hiyokosabrey:20190825125518p:plain

  レベルブループリントから、レイアウトWidgetにあるフキダシ追加の関数を呼び出され実行している。コメントはダミーで、レイアウトWidgetが内部で持っている。

f:id:hiyokosabrey:20190825125754p:plain

 

 ここまではコメントの表示方法を検証してきたのでこれでも問題なかったけど、コメントを作成して送る表示ができたので、この仕組みを変える必要が出てきた。

 

 受信時はレイアウトWidget経由で、コメントが渡されることになるのは今の状態とそれほど変わらない。一方の送信時はテキスト入力Widgetがトリガーとなって最初に動くことになる。だから機能として追加しないといけないのが、テキスト入力Widgetからの送信を受け付ける流れ。

f:id:hiyokosabrey:20190825161213p:plain



 フキダシWidgetから親であるレイアウトWidgetに通知するには、イベントディスパッチャーで行う。コメントを入力し終わって、左右のボタンをクリックしたら、入力した内容と左右どちらのボタンを押したかの情報を共に通知する。

 

 さっそく、イベントディスパッチャーを追加する。

f:id:hiyokosabrey:20190825161614p:plain

通知するパラメータに Boolean型の isLeft 、String型の commentBody 2つを追加する。

 

 このイベントディスパッチャーを呼び出すのは、左右ボタンをクリックした時だから、OnClicked のイベントを用意しよう。Buttonで作っておいたので、イベントの追加は簡単だ。

f:id:hiyokosabrey:20190825162725p:plain

 緑のボタンをクリックすると、イベントノードをグラフに置いてくれる。

f:id:hiyokosabrey:20190825162936p:plain

 ここにさっき追加したイベントディスパッチャーを ”Call” のカタチで呼び出してつなげる。このイベントはすでに右か左かハッキリしてるので、isLeft のところもあらかじめチェックを付けておくことができる。

f:id:hiyokosabrey:20190825163439p:plain

 あとはにコメントの内容をゲットしてつなげれば出来上がりだ。

 

 コメント自身は、MultiLineEditableText が持っている。そこからGetしよう。

f:id:hiyokosabrey:20190825164251p:plain

 これ最低限の処理はできたけど、判定とかも加えたいので、さらにこれをマクロにする。マクロの方が関数より分岐がやりやすい。GetText~ノードとキャストノードをフォーカスしておいて、右クリック > Collapse to Macroだ。

f:id:hiyokosabrey:20190825165445p:plain

 

 できたマクロを編集する。

f:id:hiyokosabrey:20190825165817p:plain

 まずは、Inputs に Execute ピン を追加する。同じくInputs にある Self ピンの名前を変えておく。 EditableText あたりでいいだろう。

f:id:hiyokosabrey:20190825170420p:plain

 ブランチノードを追加して、Execピンとつなぐ。

f:id:hiyokosabrey:20190825170811p:plain

 続けてブランチノードの右側のピンを、マクロのOutputsにドラッグ&ドロップする。

f:id:hiyokosabrey:20190825171237g:plain

 ピンの順番はここで変更できる。

f:id:hiyokosabrey:20190825171515p:plain

 今ここで判定したいのは、コメントが空だった場合。

 内容が空っぽかどうかを調べるノードとして Text is Empty ノードが用意されているので、これを使う。

f:id:hiyokosabrey:20190825172021p:plain

 これでマクロはひとまずできあがり。マクロの名前は getSubmitText にしておこう。

 

 イベントグラフに戻ってつなぎなおす。

f:id:hiyokosabrey:20190825172455p:plain

 コメントが空の場合、Text is Empty が True(真) になるので、コメントが入っていると、False(偽)になる。理解していてもイベントグラフだけを見るとなんか気持ち悪い。Outputsのピン名を変えよう。もう一度マクロを編集する。

f:id:hiyokosabrey:20190825173033p:plain

 True を Failur(失敗)、FalseをSuccess(成功)にしよう。

f:id:hiyokosabrey:20190825173147p:plain

 うん。解りやすくなった。

 送信した後は、空にしておかないと。

f:id:hiyokosabrey:20190825195921p:plain

 

 これでテキスト入力Widgetは編集完了。次はレイアウトWidgetでバインドしてコメントを受け取れるようしよう。

 

 レイアウトWidgetのキャンバスに置いた、テキスト入力Widget を グラフに取り出して、ed_submitComment にバインドする。そしてバインドノードの Eventピンからカスタムイベントノードを取り出す。

f:id:hiyokosabrey:20190825200546p:plain

 ここにフキダシ追加イベントをつないでみよう。ひとまず表示テストはできるはず。

 

 フキダシ追加イベント addFukidashi は、コメントをストラクチャで受け取るようにしたばかりなので、渡せるようにMakeノードを使う。

f:id:hiyokosabrey:20190825201353p:plain

  ストラクチャへの受け渡しは、まんま同じ型ならそのままつながるけど、単品で値を書き換えたり読み出したりする場合は、 BreakノードとMakeノードを駆使する。ここでは一部分だけ渡したいのでMakeノードが便利。

f:id:hiyokosabrey:20190825201809p:plain

 

 早速テストしてみよう。

f:id:hiyokosabrey:20190825203309j:plain

 左のボタンをクリックすると、

f:id:hiyokosabrey:20190825203335j:plain

 お、いい感じ。適当に飛ばしてみる。

f:id:hiyokosabrey:20190825203352j:plain

  うん大丈夫そうだ。ちゃんと左右に割り振れてる。できてきた感!

 

 あとは、入力中のテキストに文字数制限処理入れたり、入力中かどうかのハイライト処理とかもあった方がいいかな。その前にいい加減アイコン出せるようにしようか。UIDも出さないとな。フキダシの尻尾もちゃんと作らないといけないし。とりあえず休憩しよう。席を立ち空調の効いたフロアを出る。

 

 

つづく

テキスト入力を試してみる

 最近帰りが遅くて、駅から家までの経路に田んぼがあるんですが、毎晩カエルの大合唱を聞きながら歩いて帰宅しています。 月の映る水面をチラ見しながら歩きつつ、ダンジョンメーカーに勤しんでる今日この頃です。はい、歩きスマホはダメですよ~。

 さてさて、訳あってUMGのテキスト入力を試すことにしました。公式のWebドキュメントを見ていると、何やらいくつか種類がある様子。とりあえず最近のエンジンのバージョンで見てみるとこんな感じ。特に変わった様子はない。

f:id:hiyokosabrey:20190627001306p:plain

 公式ではPrimitiveの中にカテゴライズされた画像が貼ってある。いつのVerだろうか。

 

 

見た目

とりあえず、Text で Box なやつをピックアップして並べてみた。

f:id:hiyokosabrey:20190627223539p:plain

 一見すると背景有り無しと、改行ありの複数行タイプか、改行なしの1行のみのタイプ。名前的に EditableText TextBox この2タイプに分けられている理由が分かれば話が早いんだけど、公式ではこれといって言及されてないっぽい。公式以外で触れている情報が少ない。まぁだいたい理由は想像つくけど。

 

 実際に表示してみて試してみると、ほとんど差が分からない。大きな差があるのは、Style という設定項目を持っている TextBox(Multi-Line) 。こいつはスクロールバーも持っている。それ以外の3つは、いつもお世話になっているTextBlock と同じような Appearance を備える程度。

f:id:hiyokosabrey:20190628180632p:plain

f:id:hiyokosabrey:20190628180642p:plain

f:id:hiyokosabrey:20190628180650p:plain

f:id:hiyokosabrey:20190628180659p:plain

 

 機能要件の差とコンポーネントとして作られ実装された時期によるものだと思うけど、さすがにこれはちょっと躊躇う。TextBox(Multi-Line)だけは、Appearance の項目スッキリしていて、見た目の調整は Style という設定カテゴリにまとめてある。

 

f:id:hiyokosabrey:20190628181611p:plain

 

 自分の作りたい見た目が実現可能かどうか、実際に試してみながら判断するしかなさそう。

 

ちなみに、

キャンバスに置くと、is Variable に最初からチェックが付いてて、どちらもブループリントで触れるようになるんだけど、デフォルトの名前とアイコンがこれ↓

 

f:id:hiyokosabrey:20190628213137p:plain

ほとんど同じ。Box 付きは 実線で、 Box無しは 破線のアイコン。

 

 

イベント

 用意されているバインドできるイベントは4種類とも  OnTextChanged と OnTextCommitted の2つ。

 

f:id:hiyokosabrey:20190628174735p:plain

 

 全部置いてみたらこうなった↓

f:id:hiyokosabrey:20190628214027p:plain

 

OnTextChanged~ は内容が変更されるたびに呼ばれるイベント。

OnTextCommitted~ は内容が確定(編集終了という意味合いだと思う)した際に呼ばれるイベント。このボタンからイベントを作って試してみたら、このWidgetのフォーカスが外れた際に、確定したと認識されるらしい。

 

 変数化したオブジェクトから、 イベントを検索してみても専用のやつは上の2種類だけみたい。

Widget Event というカテゴリにある。

f:id:hiyokosabrey:20190628215206p:plain

 

 ところがTextBox だけは違ってて、Text Box というカテゴリが用意されてる。さすがCommonカテゴリに分類されるだけのことはあるとうことか・・・

f:id:hiyokosabrey:20190628215047p:plain

 

 ブループリントからも見た目をいじることができて、Get Style ノードが用意されているけれど、EditableText (Multi-Line) だけは なぜか、 Get Widget Style という名前のノードで別のカテゴリに分類されている。グラフに取り出してみるとこの通り。

f:id:hiyokosabrey:20190628223031p:plain

 こうやって比較してみると、ややこしい事この上ない。まさに 混ぜるなキケン!

 というやつか。

 

 ここまできて、今更ながら思ってたより深い沼だったことが判ってしまった。そっ閉じしてあとはEpicの猫のひとかhistoria様のブログに期待を寄せる方がいいかもしれない。などと弱音を吐いてみる。

 とりあえず、このまま比較していくと、全て検証を終えた時には夏休みも過ぎ2学期が始まってしまっているかもしれないので、どれかに決めて次に進もう。

 

 改行処理を試したいので複数行が扱える EditableText(Multi-Line) に決める。

 

 

 いじる

 背景があった方が大きさが分かりやすいので、テクスチャを用意。

f:id:hiyokosabrey:20190629101837p:plain

この64x64のテクスチャを Image にセットして、Box で描画する。

f:id:hiyokosabrey:20190629103025p:plain

 

とりあえず必要そうなパーツを置いてそれっぽくする。

f:id:hiyokosabrey:20190629102835p:plain

ヒエラルキーは ↓のような状態。

 f:id:hiyokosabrey:20190629103226p:plain

 

 今回、EditableText(Multi-Line) が主人公なのでサイズが固定。これに合わせて他のパーツを調整することになるので、背景(Image)のサイズをEditableTextと一緒にしておき、マージン設定で内側に余白をつくり、見た目の枠と距離をとるようにしています。

f:id:hiyokosabrey:20190629105047p:plain

 EditableTextのサイズが変わっても、サイズをコピペするだけなので、頭使わなくていいというメリットがあります。この辺は好みの分かれるところかもしれないですね。

 

 改行(Wrapping)の設定もチェック。

 Default Wrapping だと、単語と単語の区切りで改行してくれるけど、日本語のように半角のスペースを入れない文字列だと、意図的に改行しない限り、ず~と右にはみ出してしまう。なので、 Allow Per Character Wrapping に変更します。

 f:id:hiyokosabrey:20190630000030p:plain

 

 ボタンをクリックしたときに実行されるOnClickedイベントをバインド。PrintString につないで確認してみる。

f:id:hiyokosabrey:20190629144331p:plain

 

f:id:hiyokosabrey:20190629145601g:plain

 

 改行文字を置換できるか実験。Get Text ノードから PrintStringノードまでを関数にする。

f:id:hiyokosabrey:20190629152105p:plain

 

 文字列をゴニョゴニョするには、Text型を 一旦 String型にしてやる必要がある。関数にすると、ローカル変数が使えるから一時的にStringの処理をするにはうってつけ。

 関数の中身はこんな感じ。TempStr というのがローカル変数。

f:id:hiyokosabrey:20190629152521p:plain

 Replace ノードは From に置換対象の文字列を入れて、To に 入れ替える文字列を入れて使う。Shift キーを押しながら、Enter キーを押すと、改行文字が入力できるので、From に改行文字を、 To に  <> を入れて試してみる。

 

f:id:hiyokosabrey:20190629153145p:plain

 

 無事置換できた。

 

 次は、行数に制限をかける方法を考えたい。

f:id:hiyokosabrey:20190629154714p:plain

 

 それっぽいノードや設定項目はなさそう。

 とりあえずWidget Reflector で見てみると、

f:id:hiyokosabrey:20190629172843p:plain

 Desired Size が入力した文字によって変動しているのが判った。

 内容が変更されると呼ばれるイベントでGet Desired Size ノードで調べて表示させてみる。

f:id:hiyokosabrey:20190629173223p:plain

 1行・・・52.590

 2行・・・89.181 (+36.591)

 3行・・・125.771 (+36.59)

 4行・・・162.362 (+36.591)

 5行・・・198.952 (+36.59)

 6行・・・235.543 (+36.591)

 

いったん一つ前の内容をキャッシュしておいて、オーバーしたら、強制的にキャッシュで上書きするというのを試してみる。

f:id:hiyokosabrey:20190629180422p:plain

  どうやらDesired Size の値が遅れていることが判明。内容が変化したタイミングでは、Desired Size が更新されていない様子。Reflectorではきっちり取れてたっぽいのだけど。

 

ということで Force Layout Prepass を入れてみたらうまくいった。

f:id:hiyokosabrey:20190629181235p:plain

 

 改行文字を置換する関数を少し整えて、結果をReturnValueにして外に出すようにする。ついでに、キャッシュ用の変数も更新。関数名をFormatTextToStringに変更。

f:id:hiyokosabrey:20190629201931p:plain

 

 ちょっとした演出を作ってみます。

 

まずは、文字を削る関数を用意します。

f:id:hiyokosabrey:20190629203733p:plain

ここでもEditableTextの内容を一旦ローカル変数に入れて、 Right Chop というノードで一文字削って また元に戻しています。

 

この関数を動かすために、カスタムイベントを新しく作ります。

f:id:hiyokosabrey:20190629204050p:plain

結果を見て、引き続き実行するかしないかを分岐します。文字の数だけこのカスタムイベントが実行されます。

 

あとは、送るボタンを押した時に、このカスタムイベントを呼べばいいだけ。

f:id:hiyokosabrey:20190629204310p:plain

 

Playしてみると。

f:id:hiyokosabrey:20190629205131g:plain

送ってる感が出てる気がする。

 

ひとまずこんなとこかな。

 

 

おまけ

ビヘイビアの設定をちょっとだけ確認してみた。

 

■ Select All Text when Focused   初期値:false

これを有効にすると、フォーカスした際に、テキストが全選択された状態になる。

コピペさせたい時に使えそう。

 

■ Clear Text Selection on Focus Loss  初期値:true

選択中の文字列がハイライトされた状態を維持するかどうか。デフォルトだと、フォーカスが他に移った時点でハイライト(Selection)がクリアされる。

 

■ Revert Text on Escape  初期値:false

入力中のテキストを一つ前の状態に戻すかどうか。フォーカスアウトした際にテキストの内容が確定してどこかしらに保存されるようで、内容を書き換えて ESCキーを押すと、フォーカスアウトした時点に戻る。書きかけでも安心。

 

■ Clear Keyboard Focus on Commite  初期値:true

キーボードでのフォーカス遷移に関係してそうだけど、うまく検証できなかった

 

■ Allow Context Menu  初期値:true

 いわゆる右クリックメニュー。有効にするかしないか。

f:id:hiyokosabrey:20190629224351p:plain

 

そういえば、 Appearance の設定で気になるのがあって、まだ使い方が分かってないんだけど、あれは リッチテキスト用かな?また機会があれば触ってみたいと思います。

 

 

最後に

今回の文字入力用Widgetについてあまり記事が上がっていない件で、思いつく範囲で少し触れてみたいと思います。

使い方はそれほど難しいものでもない。

というのが、あえて記事に取り上げる意欲が湧かない要因だと思いますが、ゲームでユーザーのテキスト入力を扱うということは、とてもたくさんのリスクを作り手側が負うことになるので、慎重にならざるを得ないのが実情で、これによって扱わない開発者も多いのでは、というのも結構大きな要因かと思います。

まずこのリスクをいくつか挙げておきます。

 

 アジア圏の文字種の多さ。これは多言語対応する場合はフォントの選択肢が現状ほぼありません。表示できない文字が出てくる。

 フォントの契約によっては、ユーザーが自由に文字組を編集できるということがライセンス許諾外になることが多く、その場合追加のライセンス料が発生することがある。

 差別や性的、侮辱的、ドラッグ絡みなどのNGワードの置き換え処理が大変。オンラインだと多言語での対応になるので。日々ネットスラングが生まれる現状でセンシティブな内容にどこまでシステムがフォローできるかは頭の痛い問題。置き換えをあきらめた場合は、ダイアログウィンドウで事前に告知したり、不快な文章を発信しているユーザーを報告したりキックできる仕組みが必須になります。

 オンラインの場合、UGC(UserGeneratedContents)になりうるので、これもゲームの場合、CEROESRBなど、レーティングが上がることになります。

 

 ちょっと前に流行ったのは自由文ではなく、定型文の組みあわせによる文字列生成。

これはフォントの問題は解決できるし、事前に翻訳できるので多言語対応しやすい。

最近ではスタンプもよく見かけます。

 

 ユーザーが文字入力できるというのはそれなりに覚悟がいるので、実装経験が少ないということだろうと、勝手に推測しています。

 

 

ではでは

今回はこの辺で

ステキな文字入力ライフを!

 

 

 

 

 

チャットUIを作る

6

 ListViewはまた今度時間作っていじってみようと心に決め、VerticalBoxでスクロールアウトした部分の処理について考えることにする。できれば削除せずににひたすらスクロールさせれることができればいいのだけど。短いとはいえ2分程度の観戦時間でもかなりの量のフキダシを抱えることになるだろうから、適度に軽くしてやらないといけない。プログラマにお願いしたいところだけど、見た目の調整も含め設計をやっている以上こちらである程度カタチにしてからの方が、問題点を見つけやすいし改善点についても相談しやすくなる。デザイナーが何をしようとしているのか、どういう部分の見た目にこだわっているのか、といったようなことが口頭だと伝わりにくいので、それなりに動くプロトタイプは結果的に無駄な連携コストを下げることができると思う。実をいうと単に作ってみたいだけだったりする。

 

 まずは確実に画面外に出たかどうかが分かれば、その要素を削除して、間が詰まったことを悟らせないようにする。まず思いつくのは移動した分を戻す方法。

 

f:id:hiyokosabrey:20190615214502p:plain

 

 要素が削除されると当然次の子要素が上がってくる。上に詰められた高さぶんを瞬間的に移動させて、何事もなかったかのようにしないといけない。 

 

f:id:hiyokosabrey:20190616201829p:plain

 

 配列の削除と位置移動を瞬間的に行うことになるので処理落ちした時に大丈夫か心配にはなるけど、その辺の危険性はこのプロトタイプができてからプログラマに訊いてみよう。もっと素敵な方法があればあとから構造を変えればいい。あらかじめ一番上にスペーサーを入れておいて、フキダシを消してからサイズを変更するとか、Paddingの設定でスキマを調整するのもありかもしれない。それはそれで、投稿数が多くなれば危険か。VerticalBoxを使わないというのもありかもしれない。おっといけない。プロトタイプは作ってから検証しないとな。どんどん作ろう。

 

 画面から出たかどうかの判定はどうするか。フキダシ毎に高さが変わるので、フキダシの高さを保存しておく変数が必要だ。画面にいくつも並ぶからそのぶんの変数が要るとなると配列だな。

f:id:hiyokosabrey:20190615212243p:plain

  これは、イベントディスパッチャーから返ってきた値を受け取るイベントがあったから、そこで積もう。

f:id:hiyokosabrey:20190615220503p:plain

  保存した高さは、画面外に出た量と比較するときに使って、また位置を戻すときの量にもなる。設定したMarginの分も入れとかないと。

 

 次は削除イベント作ろうかな。まずはカスタムイベントを置いて、と。

f:id:hiyokosabrey:20190615224133p:plain

 VerticalBoxから一番上に積んであるフキダシ、つまり子要素の削除だから、child で検索すると・・・あった。Remove Child Atノード。

f:id:hiyokosabrey:20190615224555p:plain

 Index番号は常に先頭を削除するから 0のままでOKだ。

 削除したぶんの高さを移動するから、Set Render Translation ノードをつないで。

f:id:hiyokosabrey:20190615225327p:plain

 VerticalBoxの座標は、positionCurrent 変数が持ってるから、そこから差し引けばいいかな。VerticalBoxの子要素削除と移動は処理に間を空けたくないから、事前に計算しておこう。

f:id:hiyokosabrey:20190615230138p:plain

 

 あとは、高さの配列も先頭のやつを削除しておかないとズレてしまう。

f:id:hiyokosabrey:20190616003817p:plain

 

 現在地の座標を下げたら、目的地の座標も下げないと。

f:id:hiyokosabrey:20190616004123p:plain

 よし、ひとまずこれで必要そうな処理はできたはず。このイベントを呼ぶためにチェックするのは座標を更新してるTickで。

f:id:hiyokosabrey:20190616005631p:plain

 

 さてどう比較するかだけど、VerticalBox は画面の下から上に向かって伸びていく。Translation の値は0から増えていくから、はみ出すころには画面の高さを越えてるはず。今回解像度は1080p想定だから、これをスクロール量から引けばいいのか。

f:id:hiyokosabrey:20190616012144p:plain

  こうかな。

f:id:hiyokosabrey:20190616014648p:plain

 よし試してみよう。

f:id:hiyokosabrey:20190616111906g:plain

 ってあれ?

f:id:hiyokosabrey:20190616193504g:plain

 なぜ戻る?目的地と現在地を一緒に戻してるからズレないはずだけど・・・

 

 あ、ここか!

f:id:hiyokosabrey:20190616195708p:plain

 高さをキャッシュしている配列の先頭がいなくなってから目的地を戻してる。それはズレるな。悔しい。順番を逆にしないと。

f:id:hiyokosabrey:20190616195924p:plain

 ふう、これで大丈夫。再生してみても問題なし。

 今は処理に余裕があるから問題ないように見えているだけかもしれないけど。

 

 そうだ、フキダシの追加と削除は随時いろんなタイミングで行われるから、 Tick処理の停止を入れておこう。

 Booleanでフラグを作って。

f:id:hiyokosabrey:20190616112749p:plain

 まずはここで止めねば。

f:id:hiyokosabrey:20190616114019p:plain

 そして初期値が false だからこのWidgetがViewortに置かれた時点ではTickの処理はここで止まり、先へは走らないようになった。となると、このTickを動くようにする蛇口に相当するトリガーのようなものが必要になる。それは、フキダシを追加した後だな。

 バインドしたイベントディスパッチャーの受け取りイベントの最後で、フラグを true にする。ただしここはフキダシが追加される度に来るから、DoOnce ノード入れておこう。

f:id:hiyokosabrey:20190616181131p:plain

 これで、初めてフキダシが追加されて、高さの値が返ってきたらTick開始となる。

 あとは、フラグを false にする部分だけど、フキダシの削除が決定したBrachのところが最適だろう。Tickは常時ものすごく短い間隔で処理されるから少しでもタイムラグをなくしたい。

f:id:hiyokosabrey:20190616181847p:plain

 

 この辺で一旦流れを確認してみよう。

 

1.このWidgetが Viewportに 追加されるとき isAnimEnable は false だからTickは動かない

2.フキダシ「追加」のイベントが呼ばれる まだ isAnimEnable は false

3.バインドしたフキダシWidgetから値が返ってきた ここで isAnimEnable を true

  ※初回の1回だけ

4.Tick が動きだして、VerticalBox が移動を始め、はみだしチェック常時行う

5.はみだし量が先頭のフキダシの高さを超えたので isAnimEnable を false にして Tick止める

6.フキダシ「削除」のイベントしてVerticalBoxの位置を戻す

7.フキダシは随時追加されるたびにスクロールの目的地は増え続けている

8.Tick再開・・・

 

 おっと、 isAnimEnable を 再び true にするの忘れてた。

 フキダシ削除イベントの最後でいいかな。

f:id:hiyokosabrey:20190616200127p:plain

 よし、これで動かしてみて問題なさそうだったら次に進める。

 

f:id:hiyokosabrey:20190616200502g:plain

 大丈夫そう。ちょっとグラフを眺めてみるか。基本の処理はスクロール担当の EventTick と、フキダシの追加と削除の 3つだ。

 

・EventTick

f:id:hiyokosabrey:20190616223018p:plain

・addFukidashi

f:id:hiyokosabrey:20190616223234p:plain

・removeFukidashi

f:id:hiyokosabrey:20190616223531p:plain


あとは、ダミーテキスト用の関数。

f:id:hiyokosabrey:20190616223913p:plain

 

 フキダシの表示は大体できたも同然だな。左右のレイアウト切り替えと顔アイコン、フキダシのしっぽは、パーツを追加すればいい程度だしそんなに心配するような難しさじゃないと思いたい。やっぱりいよいよ入力フォームかな。なんだか緊張する。

 硬くなった筋をほぐすように首と肩を動かす。プログラマたちの談笑が聞こえる。そういえばずっと聞こえていたような気がするが、それだけ集中できていたということだろう。

 さた、フォームはやったことないから、調べないとな。

 

 

つづく

 

 

 

 

 

 

 

 

チャットUIを作る

5

 外に出たら雨だった。昼休み時間であちこちのオフィスからこぼれ出るように出てきたスーツ姿の人々で、通りは賑やかになる。歩行者用信号待ちでひしめく傘を避けつつ書店へと向かう。ネットで発売日を調べれば無駄足にならないことは分かっているけど、なんとなく発見があるような気がして定期的に通っている。

 店に着いて、最初にPC関連の棚へと向かう。相変わらずアンリアルエンジン関連の書籍は少ない。いつもと同じ顔ぶれだ。技術書などは勢力の移り変わりが激しいので見ていて飽きない。棚の許容量は変わらないので、新しいものが出てくると何かがひっそりと姿を消しているということになる。書棚のタイムラプス動画があったら面白そうだ。ひととおり巡って最後に文庫の棚へ向かう。途中の棚でも、何かステキなゲームのネタやらデザインのヒントになりそうなものに出会えれるかもしれないので、見るとはなしに見る。タイトルや帯の惹句なんかも目に飛び込んでくるものは、それなりに計算して作られているように思う。並べられ方や観察するだけでトレンドが見えてくるようで、本屋は面白い。今日はこれといって収穫はなかったが散歩としては十分満足できた。

 書店を出ると雨は相変わらず降っていたが出てきた時より空が明るい。帰るまでに止むかもしれない。

 フロアに戻ってくると、コンビニで買ってきたおにぎりを食べながらアンリアルエンジンを触る。時折カードゲームをプレイしているグループから叫び声が聞こえてくる。谷山田もそこに混じっているようだ。

 

 えっと、どこまで作ってたかな。フキダシのサイズを送り返してもらうところまでだったから、次はスクロールする部分だな。とりあえず動かすということはポジションを管理することになるから変数を用意する。座標を扱うので基本的にFloat型だ。

 アニメーションさせたいから、目的地と現在地用の2つ。

f:id:hiyokosabrey:20190604222131p:plain

 いつも気になるのが、この単語の間のスペース。実際の変数名には半角のスペースは入っていない。

f:id:hiyokosabrey:20190604222259p:plain

 小文字で始めても勝手に大文字始まりにしてくれさえもする。エンジニア文化に馴染みが無いメンバーでも読みやすく、扱いやすくなるようにという意図で設計されているのだろうか。逆にスペースが入ってない方が気持ち悪いという声が多いとか?それなりに手間を掛けてるはず。ぜひともアンリアルエンジンを作っているスタッフの話が聞けるなら聞いてみたい。公式のブログとかで連載してくれないかな。ぜひ日本語で。

 

 さっそくこの変数をつないでいく。まずは値を受け取って加算するところ。

f:id:hiyokosabrey:20190604224540p:plain

 Print String ノードを置いていたところにつなぐ。フキダシからこのイベント経由で値が送られてくるので、そのたびに目的地用の変数に加算していく。フキダシが追加されるたびに目的地は先へ先へと進んでいくことになる。

 一方、現在地用の変数はEvent Tick で頑張ることになる。

f:id:hiyokosabrey:20190604225259p:plain

  目的地から現在地を引いて、その距離の何%か を現在地に加算することで徐々に目的地に近づいていくやつ。近づくにつれて減速するような動きになる。ブログ『みつまめ杏仁』で何度か見たと思う。まずは 0.125 くらいでいってみよう。方向は下から上に移動させないといけない。0 から引き算するとマイナスの値にできるから、これを VerticalBox の Translation にセットしてやる。

 あとは、VerticalBox の位置を画面の下に配置しなおす。

 アンカーを変更して、

f:id:hiyokosabrey:20190604231155p:plain

 Position Y を 0 にすればいい。

f:id:hiyokosabrey:20190604230918p:plain

 

 よし、再生してみるか。あ、ついでにフキダシの文言も変えよう。

f:id:hiyokosabrey:20190604234802g:plain

 なんか徐々にズレてきてない?計算おかしかったかな?

f:id:hiyokosabrey:20190604235611p:plain

 あ、Set Padding というか Margin の分を足すの忘れてた。

f:id:hiyokosabrey:20190605000350p:plain

 これでどうかな?

f:id:hiyokosabrey:20190605004541g:plain

 なかなかいい感じ。

 

 ただこのままフキダシが足され続けると重くなりそうだから対策を考えないといけないな。今回巻き戻さなくていいので、スクロールアウトした部分は要らなくなる。ListView を使えばいいのか?確か詳しく書かれた記事があったはず。あったこれだ。

[UE4]UE4.20で追加されたListViewウィジェットについて|株式会社ヒストリア

アニメーションはうまくいくかな。さっそく試してみよう。

 

 ふむ。まずはListVew の 中身にあたるのが フキダシWidget だから、こっちに Interface を追加するのか。UserObjectListEntryを探して・・・、あった。

f:id:hiyokosabrey:20190609011023p:plain

 次は?

 

インターフェースのイベントであるEventOnListItemObjectSetを実装します。

 

 とあるな。こんな感じか?

f:id:hiyokosabrey:20190609223210p:plain

 ブログではEntryWidgetが使い回され、みたいなことが書いてあるけど、

 

このイベントは、ListViewのスクロールによって使い回されるウィジェットの内容を更新するときに呼び出されるイベントです。更新する項目の情報を保持するオブジェクトが引数として与えられるので、そのオブジェクトに基づいてウィジェットの内容を更新する処理を実装します。

 

 そうか。ListViewの仕組みが何となく見えてきた。たぶんこういうことだろう。

 見えている部分のEntryWidget はスクロールアウトして消えるのではなく、EntryWidgetそのものは必要最小限のぶんが画面に残り続けている。そして中身であるところの “内容” だけが、どこかにリスト的に格納されていて、適宜見えているWidgetを選んで再セットされる。このとき再セットの指示をもらって “内容” を受け取るのが Event On List Item Obeject Set ということだ。だからこのイベントが無いと中身が更新されない。

 となると、フキダシWidgetと中身は1対1でない可能性がでてくるな。 なんだか今回のチャットUIには向いていない気がしてきた。とりあえず動くところを見てからにしよう。

 よし、このイベントを外して実験だ。接続を切っておこう。

f:id:hiyokosabrey:20190609232047p:plain

 フキダシWidgetは一旦編集終了だな。

 

 次は表示側のレイアウトWidget。ListView を追加してと。とりあえず様子を見るために、キャンバスの真ん中に置いておく。

f:id:hiyokosabrey:20190609232633p:plain

 

 List Entries のとこに、リストの中に並べる Widget Class だから フキダシWidgetをセットする。あれ?プルダウンに何もないな。となるとアセットから矢印ボタンでセットできないか試してみよう。

f:id:hiyokosabrey:20190609201627p:plain

 ちょっと間があったけどできたっぽい。

f:id:hiyokosabrey:20190609221040p:plain

 へぇ、プレビューしてくれるのか。これで表示範囲の調整ができるな。なんという心遣い。でも今回サイズが可変だった。

 

 ここまでできたら、あとはVerticalBox との入れ替えだけだな。

 

f:id:hiyokosabrey:20190609221345p:plain

 ここは問題なさそう。

 

 次はリスト要素を追加する部分。VerticalBox に Add Child してるところだけど、どうやら Add Child ~ のノードが見当たらない。代わりに Add Item を使えばいいようだけど、やはり Return Value ピンが無いな。1対1じゃないのは確実だろう。Add Child~ という名前にしなかったのは差別化を図ったのだと思う。実際の意図はどうなんだろう。まあ、今回大して困るわけではないので、Set Padding を飛ばせばいい。ListViewの設定に Entry Spacing というのがあったし。

f:id:hiyokosabrey:20190609222026p:plain

 再生してみると。

f:id:hiyokosabrey:20190609233533j:plain

 なんと!Event Construct が動いてない? フキダシWidgetの方に Hello を追加してみよう。

f:id:hiyokosabrey:20190609234046p:plain

 

f:id:hiyokosabrey:20190609234236j:plain

 動いてる?これって、どういう・・・

 ブレイクポイント 貼って Step実行してみるか。

f:id:hiyokosabrey:20190610000813p:plain

 

 ・・・

 PrintStringノードの次でエンジンがクラッシュ。

 

 

 とりあえずイベント使ってみようか。

 

 再起動したら、BreakPoint が Disable 状態にされていた。

f:id:hiyokosabrey:20190609235548p:plain

 Removeしておこう。これは触れてはいけない闇に触れてしまったのか・・・

 

 Event Construct につないでいた内容を Event On List Item Object Set  に切り替えてみよう。

f:id:hiyokosabrey:20190610001237p:plain

 これでどうかな。

f:id:hiyokosabrey:20190610001418j:plain

 うまく表示された。中身の更新は確かに例のイベントがやってくれるみたいだけど、デリゲートが動いてない感じ。そうか、既に1対1じゃないからBindしても無駄ということか・・・。

 

 ListView もっと検証したい気もするけど、とりあえずこの辺にしておこう。まだまだ用意しないといけない機能もあるし。VerticalBox でプロトタイプを作りきって、処理が問題になるようだったら、改めてListViewで作るのを検討しよう。

 ふぅ、ちょっと休憩。

 

 いつの間にか昼休憩の時間はとっくに過ぎていた。コーヒーを買いに行こうと席を立つ。

 自動販売機の前でアイスかホットか悩んでいると、後ろから声を掛けられた。

「おつかれさまです」

 明るい声だ。

「おつかれさま」

 すかさず返す。別のプロジェクトでUIを担当している後輩の立木坂だ。

「今日はムンムンしますね」

 ムンムンって。

「相変わらず言葉のチョイスが不可解なやつだな」

 お金を入れてアイスコーヒーのボタンを探しながら言ってやる。

「雨降ってるからじゃない?」

「え、雨降ってるんですか?わたし傘持ってこなかった。やべぇ」

 ボタンを押すと氷が吐き出される音が聞こえてくる。

「そういえばアンリアルエンジン使ってるんでしたっけ?」

「うん」

「うちのプロジェクト、今どっち使うかで戦争が起こりそうなんです」

 戦争って、そんな大層な。

「どっちって?」

「ユニティかアンリアルか」

 アンリアルエンジンはハイエンドな印象からモバイル開発とは縁遠いと思われてるフシが強いが、ようやく選択肢として名が挙がるようになってきたのは嬉しい。とはいえプログラマが開発の主導権を握るプロジェクトでは、アンリアルは選ばれにくいというのも個人的な印象としてはある。プログラムを書けばいいのに何故ブループリントなんぞを触らにゃいかんのか?といった声も聞いたことがある。

 扉が開いて紙コップがライトアップされる。取り出して一口啜る。まだ氷と馴染んでなくてぬるい。

「確かモバイルタイトルだよね?」

 前にうっすら聞いていたのを思い出した。

「そうなんですよね~」

 コインを入れ お砂糖 ボタンを連打しながら彼女は答えた。そして、ホットのほうじ茶ラテのボタンを押すと、

「アンリアルに決まったら、イロイロ教えて下さいね。あんなことや、そんなことも」

 とにこやかな表情で言う。びくっと思わず体が硬くなった気がした。動揺を隠すように、

「別にいいけど、小野杉さんは?」

「あのお方はユニティ派ですね」

 出てきた紙コップをそっと取り出し、あちあちと言いながら答えた。

 

 狭い自動販売機コーナーでそんな会話を交わしていると、話し声が近づいてきたのでフロアに戻ることにする。共用スペースなので他のテナントの会社員たちも利用するのだ。立木坂と「じゃまた」と言って別れたあと、自席に向かう足が少し軽くなったような気がした。

 

 

 

 

 

 

チャットUIを作る

4

 ミーティング中に出た話はほとんど頭に入ってこなかった。珍しくいつもより短く20分ほどで解散となったが、早く続きを触ってみたくて落ち着かなかったのだ。何となくイメージはするものの、ブループリントは直接ノードを手で並べながら試行錯誤したい。どんな便利な道具や部品に出会えるか想像するだけでワクワクして気が逸る。終わるとすぐ席に戻ってきて再生。さっきまで触ってた感触を取り戻した感じがして落ち着いた。f:id:hiyokosabrey:20190519110126g:plain

 ふむ。下からせり上がるようにするためには、まずはVerticalBoxの位置を画面下に移動させる。そこからフキダシの高さ毎に、せり上げていく処理が必要だ。

 フキダシWidgetは、文字列を受け取って表示するだけだから、文字列をフキダシに流し込んだあと自身の高さを調べて返せばいい。それを返してもらったレイアウト用のWidgetがその高さを元に移動量を計算して動かしてやればいいだろう。

 

f:id:hiyokosabrey:20190526122640p:plain



 

 まずは、フキダシから高さを返してもらう部分を作るか。

 フキダシから返事をもらうにはイベントディスパッチャーを使えばいいだろう。フキダシWidgetを開いてイベントディスパッチャーを追加する。名前は ed_resultHeight でいいかな。Inputsのピンを一つ追加して、返す値は整数(Int)じゃないだろうから Float型で。名前はとりあえず Height としておこう。

f:id:hiyokosabrey:20190524235925p:plain

 これを、テキストをセットしているノードの続きに Call でつなぐ。

f:id:hiyokosabrey:20190525004801p:plain

 このままだと、0.0 しか返さないのでランダムな数字を出すようにしたい。

f:id:hiyokosabrey:20190525004621p:plain

 これで、60、120、180 の3種類を返すはずだ。いったんコンパイルして保存する。

 次は、レイアウトWidget側でイベントディスパッチャーの値を受け取る準備をしないとな。フキダシを追加するときに、CreateWidgetノードを使ってたからこのReturnValueピンからバインドしよう。

f:id:hiyokosabrey:20190525001535p:plain

 ReturnValueピンからドラッグして bind と入力して検索する。

f:id:hiyokosabrey:20190525002114p:plain

 あった。これをどこにつなごうか。この左右に振り分けてるのどうせいらないから、ここにしよう。

f:id:hiyokosabrey:20190525002946p:plain

 値をもらって処理するのはイベントだから、カスタムイベントをつないで、PrintStringで確認してみよう。

f:id:hiyokosabrey:20190525004935p:plain

 よし、これで再生だ。

f:id:hiyokosabrey:20190525005421g:plain

 あれ?PrintStringは?動いてない?おかしいな、とりあえずBreakPoint貼ってみるか。

f:id:hiyokosabrey:20190525005721p:plain

 

 ・・・

 

 さっきと同じで変化はない。うーんなぜか止まらない。コンソールにも Error や Warning の類は出ていない。イベントディスパッチャーをCallしてるとこにPrint Stringを置いてみよう。

f:id:hiyokosabrey:20190525010332p:plain

 どうかな?

f:id:hiyokosabrey:20190525010548g:plain

 ここはちゃんと動いてる?

 そうか、こんなに華麗にスルーされているということはフキダシ側のイベントディスパッチャーのノードが処理されるタイミングだと、まだレイアウトWidget側のバインド処理が走っていないという可能性が高そう。であればバインド処理を前に持ってきてみよう。

f:id:hiyokosabrey:20190525012148p:plain

 CreateWidgetの直後に置いたらどうなるか・・・

f:id:hiyokosabrey:20190525012451g:plain

 ばっちりだ。これは次から気をつけないとな。

 自分だけ待ち合わせ時間を間違って記憶してて、遅刻して置いてけぼりくらってるのに気づかず「みんな遅いな」というシチュエーションがふと頭に浮かんだ。

 よし、少し前進。教訓も得た。次は、値を返す仕組みはこのままでいけそうだ。フキダシのサイズ調整を作るか。たしかブログで見た気がする。これなんか使えそう?

 limesode.hatenablog.com

 ふむふむ、まずはフキダシのベースになるテクスチャを作ろう。

 Photoshopでフキダシっぽい感じのイメージを作ってみる。ここからテクスチャ用にRGBと透過部分のアルファチャンネルを用意してTarga形式で書き出す。

 

f:id:hiyokosabrey:20190525133453p:plain

 

 これをインポートしてUMGで使えるようにする。さっそくフキダシの下敷きにしていたImageにセットしよう。 Draw As を Box にしてMargin を 0.5 にすればOK。

 f:id:hiyokosabrey:20190525194814p:plain

 Imageは、サイズが可変だから、アンカーをタテヨコ両方向のストレッチにしておかないと。

f:id:hiyokosabrey:20190525195115p:plain

 ストレッチにするなら、ぴったりフィットさせないと意味がないから、Offsetは全て0にする。

f:id:hiyokosabrey:20190525201225p:plain

 Image は子供を持つことができないので、 Image と TextBlock 同士で直接干渉し合うこともできない。親である Canvas は子供の Size を反映することができる。したがって Image は親のキャンバスに合わせればよいということになる。Image は TextBlockのサイズなんかどうでもよくて、親のサイズにひたすら最大まで合わせればOKなのだ。

 TextBlock は TextBlock で流し込まれた自身の持つフォントの大きさとテキストの量で自動的にサイズが変わってほしいから、Size To Content にチェックを付けてやる。Canvas は、そんな TextBlock の可変を、さらに外側から暖かく寛容に包み込んでほしいので、Canvas の Size To Content も有効にしなければ・・・

 

f:id:hiyokosabrey:20190525195429p:plain

 そうだった、一番上の階層にあるCanvasって Slot の項目がないんだった。 これじゃSize To Contentが有効にできない。しかたがないもう一段Canvasパネルで括るか。ついでに名前も整えておこう。

f:id:hiyokosabrey:20190525200016p:plain

 UMGで配置したパーツは変数として扱うこともできる。グラフに置いた時に全部同じカラーで見た目に分かりにくくなるので、パーツの種別名を残して命名するようにしているが、我ながらナイスアイディアだと思う。

f:id:hiyokosabrey:20190525200429p:plain

 アンダーバーが消えるのは何故だろう。フォントのせい? これはこれで いいね してる人がいるからこの仕様なんだろうけども、誰か知っているなら明快な答えを教えてほしいものだ。

 で、Size To Content にチェックをつけると・・・

f:id:hiyokosabrey:20190525212731p:plain

 うーん、テキストの大きさには追随してる感じだけど余白が無いな。

 テキストを変えてみる。改行も入れてみよう。

f:id:hiyokosabrey:20190525212849p:plain

 ぴったりフィットしすぎだろう。余白付きでフキダシの真ん中に来てほしいからなぁ。ブログではブループリントで TextBlock のサイズを調べて調整してるけど、なんか他に方法ないかな。

 そうだ SizeBox を試してみよう。TextBlock を SizeBox の子供にして。SizeBox 自身にも Size To Content の設定をしておかないと。確か 子供の方に Padding の設定があったはず・・・よし、あったあった。

f:id:hiyokosabrey:20190525213308p:plain

 おお、これはいい感じ!SizeBox ステキすぎる。できれば Canvas にこの設定があればよかったんだけどな。

 

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

f:id:hiyokosabrey:20190525213835p:plain

  あとは、この Canvas のサイズを調べてイベントディスパッチャーに渡せばいい。サイズを調べるには、この Force Layout Prepass を間に入れて、Get Desired Sizeか。

f:id:hiyokosabrey:20190525222641p:plain

 さっそくテストしてみる。

f:id:hiyokosabrey:20190525223243g:plain

 これじゃ、分からないな。ダミーのテキストを用意してみるか。

 

 こういう場合は、まず関数を一つ新しく作る。カウントアップする変数があるので、それをもらって返すようにしよう。配列型のテキストは、もちろんローカル辺変数にする。関数の中だけで完結するようにしておけば、他からの参照もないので要らなくなったら簡単に捨てられる。

f:id:hiyokosabrey:20190525231725p:plain
  エラーが出ても面倒なので、配列が用意されていなければ固定のテキストを出すようにする。

 

 さらにこの変数を ピュア型にすると使い勝手もよくなるはず。

f:id:hiyokosabrey:20190525225214p:plain

 グラフに戻ってつないでみる。

f:id:hiyokosabrey:20190525224913p:plain

 ピュア型にすると白いラインとは別のところに置けてスッキリするので便利。

f:id:hiyokosabrey:20190525230009g:plain

 おお、なんかできてきた感ある。ログを見てみよう。

f:id:hiyokosabrey:20190525232255p:plain

 それなりに値を受け取って来られているようだ。

 

 いい手応えを感じる。このまま今日中に作り切れるといいな。そう思いながら体を伸ばして軽くストレッチをする。ふと時計を見ると間もなく12時だ。今日は紀伊國屋に行かないと。午後からは VerticalBox を動かすところに進もう。

 人がばらばらと席を立って動きだす。自分もサンダルから靴に履き替えてフロアを出る。エレベーターを待っていると、どこからか雨の匂いがした。

 

 

つづく

チャットUIを作る

3 

 翌朝出社してメールをチェックすると、最近よく名前を目にするようになったDCCツール関連の案内くらいしか来てなかった。誰かがフロアに入ってくる気配がしたので振り向くと谷山田がこちらに向かってくるところだった。気だるげに挨拶を交わした後、

「昨日はあれからなかなか鳥囃子さんがつかまらなかったんスけど、何とか見ていただけました」

 と報告してきた。静かでなんとなく澱んだようなフロアの空気が、少しだけ動き出したような気がした。まだボンヤリした頭で訊き返す。

「反応は?」

「OKもらいましたよ」

「それはよかった」

 なんて淡々とした会話だろう。安堵したのは確かだが。まあ、これでバトンを受け取ったことになるのだから仕方がない。次に走るのは私で、彼ではない。テンションを上げるより、緊張する方が先に来た感じ。ここでようやく血が巡り出した感覚を覚える。

「鳥囃子さんにちょこっと言われたんスけど」

「はい」

 背筋を伸ばして返事をする。何だろうこの言い回しだとそれなりにツッコミを受けた感じだな。

「ちょこっとでいいんでLINEぽ過ぎるのなんとかならん?って」

 どきっとした。試行錯誤していないところを指摘された気まずさで口の中が酸っぱくなった気がした。

「何となく言われそうな気はしてたのよね」

 動揺を見せないように返す。ふと 谷山田にラフを見せた時に訊いた会話がよみがえる。彼はデザインについては目を向けていなかったのだろう。なんとなくその時の印象もあって、その時はデザインをいじるよりは仕様確認だからという言い訳を考えていたような気がする。どうせそのままという訳にはいかないのを分かってはいたものの、あそこまでデザインが出来上がった雰囲気を醸し出してしまうと、逆にデザイナーはこれで行こうとしている、という認識を持たれるのは今までの経験からも簡単に予想できたはずなのだが。

 一方でプランナーがExcelのシェイプで作った仕様書の場合、OKもらってからUIデザイナーがデザインに落とすときに解釈で悩むことが多い。プランナーの記号化に対するロジックや表組みの意味などヒアリングするなりして理解していないと、どこまでが仕様の範囲か拙いシェイプやどこかからコピペしてきたアイコンからは掴みかねるのだ。面倒だからと、ほぼ仕様書のまんまで見た目だけ整えて見せると、プランナーの性格にもよるが素直に喜ばれないことも多い。谷山田は頭の中のリファレンスとして、既存の画面イメージをLINEから持ってきていたので、ラフを見せたタイミングでは答え合わせとしては及第点だったのだ。このあたりは毎回チーム編成が変わるごとに小さいながらも対応を変えていかないと、ぼたんの掛け違いが起こりやすくなる。気を付けないと。

 

「他には?」

「他は特に言われてないっス。先に絵ができてたんで説明しやすかったっス」

 仕様がクリアできたのは谷山田のプレゼン能力はもちろんだが、少しは絵の説得力が助け舟として役に立ったということだろう。

「了解、まぁ見た目は追々考えるとして、さっそくモックを作り始めるよ」

「あざっす!よろしくお願いします」

 そう言って自席に戻っていった。

 

  時計を見ると始業開始が近い。ようやくわらわらと人が流れるように入ってくる。始業開始と共に、フロアのあちこちで小さな朝会が開かれる。それが済むと自分の仕事時間がスタートするのだ。

 

 さて、実際作るとなるとどこから手を付けようか。スクロールさせるのは少しやったことあるけど、あまり自身は無い。最近ネットでもUMGのことを書いてるページ増えて来ているし、ヒストリアのブログでそれらしい記事を見かけた気がする。EpicGamesの猫アイコンの人がUMGの資料を公開してたはず、お気に入りのフォルダをいくつか開け閉めしているとブックマークを発見した。ひとまずこのささやかな安心感を胸にアンリアルエンジンを立ち上げようではないか同士諸君!などと無理やり自分自身を鼓舞してみる。

 テスト用プロジェクトのスプラッシュを眺めながら、まだほんのり暖かさの残ったコーヒーをちびちび飲む。カウントアップする数字を見ていると、ふと昔の風景が頭に浮かんだ。ゲームエンジンが無かったころは、画面のデザイン画を作ったらまずプログラマのとこに行って、パーツの構成と動きを相談しながらすり合わせて決める。そして可能な限り、テクスチャの差し替えと最小限の値だけで調整できるように知恵をしぼり、工夫してデータを用意する。頭の中でシミュレーションしたタイミングの動きや演出の流れを指定書に書いてプログラマに持っていくといった仕事のやり方だった。仮データ作成に1日、指定書に2、3日というのが普通な時代があったのだ。

 それが今は、アンリアルエンジンのおかげで指定書の類がほぼ不要になったのだ。

 まず基本的な最小限の動作をするモックアップを作り、仕様のチェックを経たあとは組み込んでもらって自分の席で動かしながら調整して仕上げる。といった工程で進めることができるので、表現の試行錯誤にプログラマを巻き込んで申し訳ない気持ちになることは無くなったが、その時にプログラマにゲームを動かす仕組みやらデバッグのコツなんかを教えてもらうことができたのは、今となっては貴重な体験だったとしみじみ思う。そんなセピアな感傷に浸っていると、いつもの画面が現れた。

f:id:hiyokosabrey:20190518005631j:plain

  StarterContents付きのプロジェクト。前にこのテーブルの上に乗っているオブジェクトについて暑い(×熱い)議論が交わされたことがあった。よく見ると、とても官能的な形状をしている。と、チームの誰かが言い出して、その後しばらく肯定派とそれ以外という何の足しにもならない派閥が生まれたことがあった。それはさておき、さっそくアセットを作ろう。

 

 まずはフキダシの並ぶ方向方法を考えてみる。やっぱり上から下に向かって伸びるイメージ? Photoshopのタイムラインツールで、フレームアニメーションを作ってみよう。

f:id:hiyokosabrey:20190518085409g:plainf:id:hiyokosabrey:20190518085420g:plain

 画面が埋まるまでしか違わないが、下から現れて上に消えていく方が素直な印象を受ける。新しいフキダシは常に下から、という印象付けができる。背景が動いていないせいもあるだろう。実際の画面でも背景とは連動しないので、下から積み上がっていく方を採用する。

 UMGには、縦に要素を並べるVerticalBox というパネルがあるのでこれを使うのがよさそうだ。これを試すために、フキダシパーツの簡単なやつを用意しよう。機能としては、四角の下敷きと順番を分かりやすくするための TextBlockがあれば十分だろう。

 コンテンツブラウザから、WIdgetを一つ生成してエディット開始だ。

 フキダシはパーツのひとつでレイアウトされる側ということになる。画面上の位置は特に気にする必要がないので、キャンバスの右上にあるプルダウンをDesiredにすれば余計なガイド線が出なくなるのでおススメだ。

f:id:hiyokosabrey:20190518101748p:plain

Image を置いて適当な大きさにする。とりあえず 400x100。ちょこっと半透明。

TextBlock を手前に配置。

f:id:hiyokosabrey:20190518122208p:plain

 TextBlockは Is Variable にチェックを付けておく。

f:id:hiyokosabrey:20190518123128p:plain

 ひとまずキャンバスはこのくらいでいいだろう。つぎはブループリントの編集に移る。

 構想としては、このフキダシ用のWidget にいろいろ情報を渡してからVerticalBoxに追加しようと考えている。情報を渡してもらうのはいつか?やはりCreate Widget した時の方がシンプルでよさそう。ということでText型の変数を一つ用意する。

f:id:hiyokosabrey:20190518212222p:plain

 Instance Editable と Expose on Spawn にチェックを付けておくのを忘れないようにっと。あとはこの変数をTextBlockの内容を書き換えるために使えばいいだけだ。イベントは、このWidgetが生成されたときに実行されればいいので、Pre~ではなくこっちにつなぐ。

f:id:hiyokosabrey:20190518212744p:plain

 ようし、こんなもんでいいだろう。保存して閉じて、次のWidgetを用意する。

 

 とりあえずVerticalBoxを置いてみる。上から下に向かって並ぶはずだから、いったん画面の上端に置くとしてセンタリングしたいから、Anchorはこれにしてみようかな。

f:id:hiyokosabrey:20190518215558p:plain

Slotの内容は、Alignment の X を 0.5 にして中央に持ってくるようにして、横幅は広めにとって、おっとIs Variableが付いていないじゃないか。

f:id:hiyokosabrey:20190518215640p:plain

 こんな感じでど真ん中に。

f:id:hiyokosabrey:20190518214912p:plain

  ふう。

 首と肩が緊張しているので軽くほぐす。VerticalBoxを置いたし、次はブループリントだな。まずフキダシに渡して表示する数字をカウントする変数を用意しておこう。

f:id:hiyokosabrey:20190518223705p:plain

 で、次は外から呼ばれたらフキダシを生成する関数。ここはイベントでいいか。

f:id:hiyokosabrey:20190518225016p:plain

ここから、CreateWidgetノードにつないで、VerticalBox に AddChild だ。

f:id:hiyokosabrey:20190518230157p:plain

この型が違うピン同士でもつないでくれる仕様は便利。

f:id:hiyokosabrey:20190518230525p:plain

 これでテストの準備は大体できたはず。仕上げはレベルブループリントでAdd To Viewportすれば。

f:id:hiyokosabrey:20190518231204p:plain

 レイアウト用のWidgetを Viewportに追加して、スペースキーを押したらイベントを呼ぶようにする。

 これで再生してみよう。何か忘れているような気がするけど・・・。

f:id:hiyokosabrey:20190518233714g:plain

 しまった。カウントアップ忘れてた。あと、スキマも空けよう。スキマは Set Padding ノードで。カウント用の変数は+1して格納しておけば、次回には変化した数字で渡されることになる。

f:id:hiyokosabrey:20190518234014p:plain

 よし、これでどうかな。

f:id:hiyokosabrey:20190519001102g:plain

 よしよし、それほど悩まなくもいいかもしれないという、甘い手ごたえを感じる。ちょっと左右の振り分けをやってみよう。

 Add Child ノードの Return Valueからそれっぽいノードを探す。レイアウト関連のキーワードと言えば Alignment だから、 Ali と入力すると、あったあった。

f:id:hiyokosabrey:20190519094839p:plain

 水平方向だからこの Set Horizontal Algnmentノードを取り出して、

f:id:hiyokosabrey:20190519094402p:plain

 Set Padding の後ろに挿入する。カウントアップは次に備えての処理だから最後でいい。このレイアウト変更はすぐに反映したいのだ。

f:id:hiyokosabrey:20190519095602p:plain

 プルダウンリストで水平方向のどこに寄せるか設定することができる。濃い緑色の入力ピンは Enum(イーナム)型だ。ランダムのノードを使ってみよう。右か左かの2択だから・・・これにしよう Random Bool。 

f:id:hiyokosabrey:20190519002150p:plain

 さてこれを何とかして、Enum型の Left と Right に錬金するわけだが、Enumは定義された順番を番号として扱うこともできる便利なやつなので、プルダウンに並んでいる順を確認してみると、上から Fill、Left、Center、Right となっている。

f:id:hiyokosabrey:20190519092028p:plain

  とりあえず、Left と Right  が設定できればいいので、1と3 か。ふたつの数字を扱うには Selectノードの出番だな。False と True に 1と3 を入れて。

f:id:hiyokosabrey:20190519092321p:plain

  まずは Bool(ブール)値 が Integer型に変化した。まだ Enum型のピンにつなげるにはもう一回型変換が必要だったはず。えっとなんだっけ。確か Byte(バイト)型だったっけ。To Byte(Integer) ノードでキャストして、

f:id:hiyokosabrey:20190519093121p:plain

 たぶんこれでつながるはず。

f:id:hiyokosabrey:20190519095950g:plain
 ばっちりだ。

 

 これで再生してみよう。Altキーを押しながらP。スペースキーを叩く。

f:id:hiyokosabrey:20190519110126g:plain

 実際はフキダシの無い方には  > がくるから、左右の振り分けはいらないな。絵的に左バージョンと右バージョンを作って、普通に Add Child するだけで良さそうだ。まあこういう手軽に試せるところがアンリアルエンジンのいい所。時計をみると定例ミーティングの時間が近い。何人かが立ち上がってフロアを出ていく。いったんここまでか。席を立ち肩を回しながらミーティングルームへ向かう。次はどこから手を付けようか考えながら。

 

つづく

 

 

チャットUIを作る

1

「おつかれさまです」

 後ろから声を掛けられ座ったまま振り向くと、プランナーの谷山田が立っていた。

「このあいだちょこっと相談させていただいたアレなんスけど」

 なんだっけ?記憶を辿ってみたけど急に思考を中断されたせいかすぐに出てこない。こちらが一瞬固まったのを見て

「観戦モードの・・・」

「あぁ! チャット的なやつがやりたい、とか言ってたよね」

「ええ、あれからちょこっと考えたんスけど、LINEみたいにできないっスかね」

 谷山田は、今開発中の対戦ゲームで観戦モードを担当している。

 入社2年目にしてチームに配属されるも、話題が豊富でチームメンバーと打ち解けるのが早かったのを覚えている。社歴も浅く、チームに入ってそんなに経ってもいないのに、受け答えの正確さと、先回りして会話の流れを誘導しているところなど、すでに古参のような風格を漂わせている。クセのある先輩ばかりのチームだと、下っ端根性が抜けないヤツが多いのに、うまく立ち回っているのを見るととても頼もしい気持ちになる。話術の巧みさは認めるがゲーム開発の経験値ではまだまだ、というところを思い知らさねばなるまい。

 そんなカワイイ後輩プランナーではであるが、やたら「ちょこっと」というコトバを入れてくるので、ちょこっとクンというあだ名を流行らせようか本気で思っている。

「観戦中やることないんで、その瞬間の気持ちとか書き込めると楽しいと思うんスよ」

「そうね。それで?入力した文字が、画面に流れたりとか?」

 私が手をひらひらと水平に動かしながら言うと

弾幕もちょこっと考えたんスけど、さすがにパクった感あるしちょこっと古いかなって。それと誰がいつ発言したか判らないのはチャットとは言わないかなって」

「チャットというと、テキストがずらずら~と」

 私が手をひらひらと下から上に動かしながら言うと

「そんなの喜ぶのオッサンだけっス」

 なぜ存じ上げておるのだ?

 まあこの辺にしておくか。とどめとして聞いておく。

「やっぱフキダシとか? キドク とかついたりするやつ?」

「ええ、ちょこっとでも使い慣れたアプリの雰囲気が漂ってるだけで、いろいろ説明しなくてもいいしラクだと思うんスよね」

 こうやってさりげなく会話を進めながら仕様の確認も怠らない。どこまで許容できるのか、どのあたりまで見通しているのか、プランナーの頭の中のイメージを言語化させるのだ。彼らプランナーはじつに幅広く様々なゲームなんかを体験し蓄積している。それらを自分の考えたゲーム画面として頭の中でコラージュする。そのつぎはぎだらけのゲーム画面を見ながら話をしてくるのだ。そしていち早く今作っているゲームにマッチするかどうかを確認したがっている。

 もう最初にLINEみたいな、と言葉になっている時点でほぼ選択肢がないのは気づいてはいたが、とりあえず判ったこととして、古典的なチャットスタイルはもちろんダメで、ニ〇ニコ動画風もやりたいと言われたらどうしようか悩むとこだったがこれもNGだ。使い慣れたアプリといえば、日本では言わずもがなだ。あとはイマドキのメッセージ系やチャット系アプリを調べてデザインを何案か出せばいいだろう。あまり言語化され過ぎて、これから作るもののカタチが鮮明になってしまうと、デザインの案を出すのは易しくなるが、デザインの幅(=遊び)が無くて、アイデアをデザインに落とし込む側としては面白くなくなる。ある程度の方向性を絞れたら潮時だ。

「わかった、とりあえずデザイン考えてみる」

「すんませんがヨロシクお願いします~」

 私は最初から企画資料はあまり重要視しない主義だ。軽い思い付きでいいから持ってきて、と常々言っている。ちょっとくらい陳腐だったり既視感溢れる企画でも、一度は餅の絵を描いてみせる。ここはなるべく時間を掛けずに手早く共有することが大事で、口頭で説明を聞き、会話をするようにビジュアルを作って反応を見る。間違いに気づかせたり、進もうとしている方向を確認させたり、自分自身の勘違いも正せたり、そこから新しい閃きや欲が出ることもある。ついでに今のプロジェクトのデザインスタイルを取り込んでラフデザインを作ると、完成形がイメージしやすくなって、こいつに任せておけば安心という印象を与えることもできるし、スケジュールを相談するときも聞き入れてもらい易い。

「じゃ、それで」となってからようやく細部のデザインやトーン&マナーを整えてアートを監修する立場の人間と交渉すればいい。

 

 よろしくお願いされたので、さっそく資料を漁る。なんとなく予感はしていたが、似たようなチャット系のアプリはたくさん出てきた。どれも大して差が無い印象だ。表示に必要な情報が少ないのもあると思うが、意図的にアレンジの幅を小さくしているようにも思える。見た目で乗り換えのハードルを下げている狙いもあるのかもしれない。

 今回オーダーがあったのは観戦モード中のチャット機能だ。メンバー(Host)が部屋を立ててその部屋が解散するまでのもの。ログは見れた方がいいのかな?その場合検索機能は必要?通報機能は?ラフができたら後で確認しよう。

 開発中のゲームは、海外配信も視野に入れているので、カタカナではなく英語でも検索する。いくつかメジャーそうなのを眺めていく。アイコンがあったりなかったり。たいていフキダシがあって、自分だけ色や向きで差別化されているパターンが多い。

 困った、どれもだいたい同じ見た目だ。ということはちょっとくらい似ててもツッコまれない安心感はあるけど、どうせなら新しい感は出したいという想いがむくむくと立ち上がってくるのを感じるが、ここで悩んでいるのはよろしくない。まずはラフをサクッと作って報告しよう。まずはLINE風とのまんまコラボを見せて、ありやなしやを確認する。

 

それっぽいのができた。仕様の答え合わせなら画像を彼の席に送りつけるところだが、今回はアイデアをもとにブラッシュアップしたいので、自席に彼を呼ぶ。

 

 「おぉ、もうできたんスか?」

f:id:hiyokosabrey:20190511085420j:plain

「フキダシの幅が一律なのはどのくらいまで隠せるかと思って、とりあえず」

サイズ調整が面倒だったというのは黙っておく。

少し考えこむ様子を見せてから

「・・・まんまッスね」

「いろいろ見てみたけど、どれも似たり寄ったりなんで。ここはスタンダードに倣おうかと」

「そうッスね・・・」

多少予測していたけど、何だろうこの物足りない感を露わにしたような間は。もう少し言葉を待ってみる。

「いや、思ってた通りなんでいいんスけど、」

画面をみつめたままで言う。なんとなく空気が止まってる感じ。

「まんま過ぎる?」

「そこは問題ないと思うんスけど、ちょこっと気になるとこがあって。」

なんだか歯切れの悪い感じのやり取り。

「どのあたり?」

「自分のコメントだけ離れているのって、仲間外れみたいじゃありません?あと、左に寄せたほうが読みやすいと思うんスけど、今回の対戦形式って画面の左右にプレイヤーが向かい合うように立つじゃないですか?フキダシを左右どちらかに表示するとしたらどっちがいいかなぁと思って」

なんだレイアウトが気になっていたのか。

「横画面で広いからね。レイアウトしてて気にはなってた」

そこでPhotoshopで自分以外のフキダシをいくつ2つほど選んで左右反転してやる。

f:id:hiyokosabrey:20190511093131j:plain

「あ、いいかもですね!」

表情に明るさが戻ってきた。

「入力フォームはどこっスか?」

あ、忘れてたという態度を悟らせないように

「この辺でいいかと思うんだけど」

と左下付近を指でくるくる回しながら言うと

「左右に振り分けるのをいつやったらいいか考えてみたんスけど、入力フォームの位置を操作できるようにすると、操作の説明が必要になりますしね」

なるほど、私は少し考えて

「普段はフォーム自体はアイコンボタンにしてコンパクトにしておけば・・・」

f:id:hiyokosabrey:20190511094939j:plain

「おお分かりやすいッスね!」

「真ん中で書いたあとで、左右どちらかに飛ばす、というのもいいかもしれない」

f:id:hiyokosabrey:20190511100114j:plain

「ちょっと ボタン感 がないけど」

「なるほど~、これはこれでシンプルでいいですね。フォームの位置が安定してていい感じっス。ちょこっと中立のコメントも出したくなりますが。」

「さすがに画面中央はやりすぎだろ」

「ですね。左右にボタンがある方が、どっちもがんばれ~みたいな中立の発言はやりにくいけど発言のベクトルが分かりやすくなっていですね」

 書いてから考えるか、考えてから書くか。

「じゃとりあえず両方作ってみようか?」

「マジっスか!ぜひぜひ!」

 いい感じにまとまった感があるので、ここでいくつか控えておいた懸念を確認しておきたいが、プログラマもいた方が話が速いので呼びに行ってもらうことにする。

「南河原さんにも話ししておいた方がいいと思うんで呼んできてくれる?」

 南河原さんはプログラマで主にUI全体を取りまとめてくれている。いつもぼんやりした感じの雰囲気をまとっていて、必要最小限のやり取りで十分こちらの意図をくみ取ってくれるし、調整作業でも嫌な顔をせず付き合ってくれる。とても頼りになる先輩だ。積極的に話をするタイプではないようで、仕事以外の話題を交わしたことはない。こちらが年長者に対して気後れするとかではなく、いつも何となく話が転がらないだけである。ちょこっとクンに呼びに行ってもらってる間にフキダシの幅を調整しておこう。

 

 「呼んで来ました」

 画面を見せて改めて説明をする。

 ・・・

 というわけで一通り話を聞いてもらった後、

「で、いくつか確認したいんだけど、顔アイコンいるよね?」

「この黒い四角のとこッスよね?無い方がいいとかっスか?」

「いやロビー画面とゲーム中とはメモリの余裕が違うんでプログラマに訊いてみないとなって思って。最悪 IDだけになるかもしれないんで」

 と私が言うと、南河原さんは腕組みをしたまま天井を見上げるように少し考える仕草をしてから

「うーん、観戦できる人数が16人から増えなければ・・・」

 ちらっとプランナーを見てから続ける

「たぶん大丈夫やと思う。ロビーで顔アイコンをキャッシュしてるし、それ使えるようにしとくワ」

「それと観戦からロビーに帰ってきたら、このメッセージってもっかい見れたりすんの?」

 あ、訊こうと思ってたやつ。

「リプレイが見れるんでそこで表示したらいいと思うんスけど、いけますよね?」

 谷山田が返すと南河原さんは少し考えてから

「じゃそれ」

 しれっと仕事増やしてくれているのが気に入らないけど、納得できたので首肯しておく。谷山田は一瞬嬉しそうな表情を見せたが、床に視線を落としながら

「実はヤバイ発言とかをどうしようかなっていうのはちょこっと考えてて、即座にアクション出来るようにするか、戻ってきてから振り返ってアクションするか・・・」

 と力なく言う。

 確かに、自由文の入力となるので不快な発言を放置できない。

「それな、禁止ワード対策はするとして、通報とかミュートの仕組みが要るんやったらUIも考えなアカンよね」

 と言いながらこちらを窺うように顔を向けてきた。

「そうですねぇ」

 と私が言いかけたところに谷山田が被せてきた。

「通報は即時対応がそもそも期待されてないと思うので、機能としては観戦中じゃなくても大丈夫だと思うんスよ。フレンドリストからでもいいのかなって」

 ここに負けじと私も

「ミュートはすぐに効果があった方がいいので、観戦中に操作できた方がいいと思う」

 口を動かしながら手はPhotoshopを操作する

「フキダシの横にアイコン載せたらどうかなって」

f:id:hiyokosabrey:20190512000658j:plain

「ああ、ツイッターにあるやつですね」

「これなら、通報 とか いいね とかもできていいと思う」

 コンテキストメニューを作ることになってしまったけど、これでミュートと通報の機能を持たせられるし、いいね もできるし善しとするか。

「じゃこれで」

 南河原さんが腕組みを解いて言った。プログラマが「これで」と言った時は、もう頭の中でどういった処理が必要か、またどのような利害が予想されるのかなどの懸念を一通り検討した結果であることがほとんどだ。これほど頼もしい言葉があるだろうかというくらい強い一言だと私は勝手に思っている。

「もうちょっと整えてからこの画像渡すんで、仕様書の方よろしくね」

 と谷山田の方を向いて言うと、彼は晴れやかな顔して、

「了解っス!」

 この返事が合図になって緊急チャット画面検討会は解散となった。

 

2

 2人の背中を見た後、私は自販機のコーヒーを買いにフロアを出る。エレベーターのボタンを押してカゴを待つ間に、頭の中で先ほど出たアイデアのデザインを詰める。  ミュートの表現どうしよう、フキダシを閉じるか?上からバツ印でも乗せるか?スミベタとかってできるかな?UMGでできたら面白そうだな。通報はサーバーに送信すると思うけど、ミュートってあくまでも個人的なものだからな、どこにセーブするんだろう。ちょっと不吉な予感がするけど、まぁ絵には影響なさそうだし、南河原さんが何とかするでしょう。扉が開いたので条件反射的に乗り込んで行先ボタンを押す。あ、そういえば ID を置くの忘れてた。フキダシの上あたりか、中だったら色を変えれば置けそうな気がする。フキダシを中に入れるとフキダシの面積が大きくなるし、メッセージの文字列よりIDの方が長くなることもあるだろう。やっぱりフキダシの外かな。

 目的の階についたので、コーヒーのボタンを押して出てくるのを待つ。ブーンガタガタという音をボンヤリと聞きつつ頭の中で画面を作っていく。

 フキダシが左右に散るとタイムライン感が弱くなりそう。縦のベクトルを維持するようなのが欲しいかも。ラインを引くと画面を切ってしまって狭くなるしな・・・これは戻ってから試してみよう。縦にスクロールするよな。フォームのエリアを確保しないとな。仮の顔アイコンもどっかから調達しないと。

 席に戻るまで細かい部分をイメージしていく。傍から見たら不気味に映ったことだろう。ボンヤリしたまま椅子に座りコーヒーが冷めるのも忘れてラフに手を入れていく。

 まずはアカウントアイコンを適当に拾ってきて重ねる。ユーザーIDも適当に考えたやつを入れてっと。手を動かしながら頭の中で独り言を言いながら進めていく

f:id:hiyokosabrey:20190513224732j:plain

 よし、ここにラインを入れてみるか。

 

 白だと目立つから黒かな。

f:id:hiyokosabrey:20190513224946j:plain
うーん、軸は分かりやすくなったけど、画面を切るし、なんかぞんざいな感じがする。

 

ならばこれはどうだ。

f:id:hiyokosabrey:20190513225138j:plain

なんか上から落ちてきてタイミングよくボタンを押すアレみたいだな。

 

そうだ!これならどうだ?

f:id:hiyokosabrey:20190513225244j:plain

お、いいかもしれない。

程よく縦を感じさせつつ、同時に反対側にも意識を向けさせる。これだ。

となると、コンテキストメニューのアイコンが気になるな。同じ見た目の記号を違う意味合いで使うのは避けないといけないし、左右の配置で機能が違うというのは無いから下向きにしてみよう。

f:id:hiyokosabrey:20190513225415j:plain

よしよし。

 

次はコンテキストメニューか。小さくポップアップする感じで。

f:id:hiyokosabrey:20190513230055j:plain


観戦中で流れていく可能性が高いから、アイコン付けた方がいいかな。

f:id:hiyokosabrey:20190513230210j:plain

 こんなとこかな。解除の場合は文言とアイコンを差し替えればいいだろう。

 

ミュートした時の見た目はこんな感じかな。

f:id:hiyokosabrey:20190513230401j:plain

 確か自分以外のユーザーにはミュートしたという情報が伝わらない仕組みだったと記憶してる。ミュートされたユーザーが詫びを入れることは無いと思うが、一応解除できるようにフキダシは残しておこう。

 あ、そうなると、相手に「いいね」も通知できないかもしれない。勢いでコンテキストメニューの項目を並べてみたけど、あとで南河原さんに確認してみよう。最悪、ミュート機能だけになるかもしれないけど、その時はその時で。

 「いいね」のようなポジティブな要素は受け入れられ易いが、誰かからミュートされたという事実を気づかせてやったら、慎み深く言葉を選ぶようになるだろうか?自分の発したフキダシに、No Good を表すアイコンが付くとどんな気分になるだろう。そういえばYoutubeの動画には付いてるけど、不特定多数だし、相手の名前が一切判らないからまだ穏やかでいられそうな気がする。名前と人数が判明している場合は、むしろ匿名の方が殺伐としそうだ。

 ユーザーに対してミュートした場合は、出るフキダシ全部をスミベタにすればいい。ブロックはかなりのヘイト状態じゃないと選択しないと思うし、そもそも赦す機会は無いと思われるので戻す必要はないから、非表示でも問題ないだろう。

 そこまでさせるユーザーには出会いたくはないけど、こういったUIが必要とされてるのも事実。できれば今回のゲームではミュートもブロックもいらないチャットであってほしい。この表示が要らないなら無くせばいいだけだし、とりあえず後から慌てて実装というのもスマートじゃないし、作ってる間に何かしら議論すればいいことだった。今は仕様を確実なものにするためにデザインを整えるのを優先しよう。無いものを考えるより不要なものをなくす方がはるかに簡単なのだ。

 

 いいね はこれくらいのさりげなさでいいかな。

f:id:hiyokosabrey:20190513230731j:plain

 色付けたのもいいね。

f:id:hiyokosabrey:20190513230748j:plain

 とりあえず気づきやすそうなピンクにしておこう。最初からハートアイコンを置いておくのもいいかもしれない。フキダシをクリックまたはタップすれば色がつくという仕様。いいねできそうな雰囲気を出せるので、一石二鳥か。ただ全てのフキダシにハートマークがつくことになるので、最初から付けるならアイコンのデザインは考え直した方がいいような気がしてきた。

 

 できた。

f:id:hiyokosabrey:20190513232429j:plain

 さっそく谷山田に送信する。すっかり冷めてしまったコーヒーをすすっていると返事が返ってきた。「ありがとうございます!急いで資料まとめて鳥囃子Dのとこに凸ってきます!」と書いてあるメッセージの右上にあるメニューボタンからいいねを押して、がんばれよ、と頭の中で応援する。

 自信というほどではないがここまでの絵を作っておけば恐らく大丈夫だろう。あとは谷山田がうまくやるはず。

 

 鳥囃子ディレクターと組むのは今回が初めてではない。彼はあまりデザインに口を出すことはない。だが、それが逆にプレッシャーになったりする。OKを出す人間がいるとついそこに甘えるやつが出てくるのを嫌ってか、彼はそのあたり、ゆるいOKしか出さない。やたらと目を光らせて細かいところまでうるさいディレクターだと、スタッフはいいものを作るのがゴールではなく、このディレクターに怒られないように、嫌われないようにするのが目的になる。言うとおりに作れば、怒られないし、終わってから酷評されてもディレクターの責任だと言ってしまえる。

 彼の場合、それぞれの職能を持つ者に責任を感じさせて積極的な姿勢を促すというやりかたを取る。そこをくみ取れない者は彼のようなタイプを「決めないディレクター」だとして不満を漏らす。この手の不満を言う輩は、たいてい自分は悪くないというポジションから動こうとしない。お任せを丸投げとしか捉えられないのは悲しい。

 私自身はプレッシャーに弱く、任されると恐縮してしまう性格だ。彼との仕事は緊張はするけど、その緊張がそのままやり甲斐になって返ってくるのがとてもありがたい。私は彼が投げっぱなしということをしないのを知っている。ダメ出しするときはダメ出しをする。だからOKが出たら、それだけ期待されているということなのだ。

 

 冷たくなってしまったコーヒーを一気に飲み干して紙コップをゴミ箱に捨てる。省電力で真っ黒になったモニターにはぼやけた顔が写っていてこちらを見つめていた。慌ててマウスを動かす。まだ谷山田からの返信は無かった。

 

 

 つづく