みつまめ杏仁

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

チャット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が出たら、それだけ期待されているということなのだ。

 

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

 

 

 つづく

 

 

 

 

 



 

 

 

UI ボタンでカメラを切り替える《後編》

前回の続きです。

limesode.hatenablog.com

前編ではカメラを切り替えるUIをWidgetで作って、レベルにオブジェクトと切り替えるためのカメラを設置しました。

後編は実際のカメラ制御の部分を作ります。

 

Blueprint Interface

まず、Widgetからメインカメラ用のブループリントにアクセスするためのインターフェイスを用意します。

コンテンツブラウザから右クリックで、Blueprints > Blueprint Interface を選択。

f:id:hiyokosabrey:20190423014445p:plain

このブループリントインターフェイスの中身は 関数名 とその 入出力ピン の設定のみで構成されています。

f:id:hiyokosabrey:20190423015038p:plain

今回の関数名 SwitchCamera を作成して、Inputs に Integer型のピンを1つだけ追加したらコンパイルして保存します。

 

 

メインカメラ用Actor

次に、メインカメラ用のブループリントを用意します。

f:id:hiyokosabrey:20190423015850p:plain

今度は Blueprints  > Blueprint Class を選択。

ダイアログから、Actor を選択します。

f:id:hiyokosabrey:20190423020113p:plain

 

さっそく編集していきます。その前にさっき作ったブループリントインターフェイスをセットします。

 できたブループリントを開いて、ツールバーClass Settings をクリック。

f:id:hiyokosabrey:20190423221925p:plain

 

Details(詳細)パネルから Interface の項目を探してセットします。

f:id:hiyokosabrey:20190423222504p:plain

 

ブループリントインターフェイスがセットできたら、次はメインのカメラを追加します。

まずは左上の Componentタブから、+Add Componentボタンを押して Camera を選択します。

f:id:hiyokosabrey:20190423020338p:plain

 

次は変数を用意します。

f:id:hiyokosabrey:20190423235223p:plain
一番上の CameraActor型の配列変数は、 レベルに設置したカメラにアクセスするためのものです。これだけ目玉のアイコンが付いているのは、以下のチェックボックスにチェックを付けているからです。

f:id:hiyokosabrey:20190423224751p:plain

この2つにチェックを付けると、このブループリントを Spawnする際に値を受け取れるようになります。(Expose on Spawn だけだと 警告が出る)

 

 変数が用意できたら次は関数を2つほど用意します。

 配列に格納されたカメラの情報を取り出すのと、メインカメラにパラメータをセットする関数です。

 

まずは情報を取り出す方。getCameraPosition命名

f:id:hiyokosabrey:20190423230004p:plain

中央下のノードは、ちょっと見つけにくいです。

get ノードからドラッグして、 get comp あたりで検索をかけて下の方。

f:id:hiyokosabrey:20190423230225p:plain

カメラのアイコン付きを選択。

あとは、get Field Of View ノードと、 getActorTransform ノードです。

その二つを、Returnノードの Outputsピンから出します。

また、このGetCameraPosition関数は Pure型にした状態で作っていきます。

f:id:hiyokosabrey:20190608114644p:plain

 

 

次は情報をセットする方。setCameraPosition命名

f:id:hiyokosabrey:20190423231001p:plain

 Transform と FOVを setするだけです。セットする先は別になります。

 

変数と関数が用意できたので、次はイベント。

 

Event BeginPlay

まずカメラを乗っ取ります。

f:id:hiyokosabrey:20190423231616p:plain

GetCameraPosition関数は Pure型で利用しています。

 

Set View Target with Blend ノードに Self ノードをつないでいます。このSet View Target with Blendノードは、先に get Player Controller を取り出してからだとすぐに見つかります。

あとは、用意した関数をつないで、開始時のカメラ位置をセット。

 

 

Event Switch Camera

ブループリントインターフェイスに仕込んでおいた名前だけの関数みたいなやつを、イベントとして利用します。

グラフで右クリックして SwitchCamera を探すと見つかります。

f:id:hiyokosabrey:20190423232712p:plain

これはイベントとしておくことで呼び出してもらうかたちとなります。

右上にブループリントインターフェイスのアイコンが付いたノードが出てきます。

f:id:hiyokosabrey:20190423233019p:plain

ここにカメラ切り替えの処理をつないでいきます。

 

まずは前半部分。あらかじめ用意した変数と関数を並べるだけ。

f:id:hiyokosabrey:20190423235805p:plain

GetCameraPosition関数は Pure型で利用しています。


今のカメラ位置(Old)から、新しいカメラ位置(New)へ移動させるために、それぞれの値を一旦変数に取り置きします。

 

常に最新のカメラ番号を保持させる Current Index 変数ですが、更新するタイミングがとても重要。つなぐ順番によって結果が変わるからです。

f:id:hiyokosabrey:20190423235532p:plain



 

残りの後半部分。

いよいよタイムラインノード登場です。

f:id:hiyokosabrey:20190424000037p:plain

 

タイムラインノードのトラックは Floatタイプ が一つだけ。

f:id:hiyokosabrey:20190424000304p:plain

 

これでメインカメラActorの完成です。

 

 

ボタンWidgetを並べるWidgetの再編集

メインカメラのActorも用意できたので、Widgetの方からアクセスする仕組みを作ります。ボタンWidgetを並べるためのWidgetを編集します。

 

 カメラにアクセスするために、Actor型の変数を新しく追加します。

f:id:hiyokosabrey:20190424011144p:plain

Instance EditableExpose on Spawn にチェックを付けておきます。

この段階ではまだどんな Blueprint Actor が入るか判りません。ただのActor型です。

あたりまえですが、中身にアクセスしたり関数を呼び出したりはできません。

 

さてこれを、放っておいたバインドからのカスタムイベントにつなぎます。

f:id:hiyokosabrey:20190424220809p:plain

この空っぽのActor型のオブジェクトに対して、SwitchCamera の関数を取り出してつなぎます。右上に手紙のアイコンが付いたノードが取り出せます。

 

これは、プロジェクト内にブループリントインターフェイスが存在すると、見つけることができるというちょっと特殊な関係性によるもの。

f:id:hiyokosabrey:20190424221539p:plain

f:id:hiyokosabrey:20190424221302p:plain

 その対象がなんであるかも構わず、中に関数を持ってるかどうかも関係なくつなぐことができます。

こんな状態でコンパイルが通ってしまうのです。

試しに適当なオブジェクトにつないでみてもコンパイルは正常です。

f:id:hiyokosabrey:20190424222646p:plain

再生してもエラーは出ません。

ブループリントインターフェイスにはこんな便利な仕組みが備わっているのです。

 

Widgetの再編集はこれだけです。

 

では最後の仕上げとまいりましょう。

 

Level Blueprint

レベルブループリントを編集します。

まず Spawn Actor From Classノードを取り出してEvent BeginPlay につなぎます。

f:id:hiyokosabrey:20190424225105p:plain

f:id:hiyokosabrey:20190424224923p:plain

Class のところに、用意しておいた BP_MainCamera をセットします。

すると Expose on Spawn にチェックをつけておいた配列が入力ピンとなって現れます。

f:id:hiyokosabrey:20190424225634p:plain

ここにカメラを配列にしてつなぎます。

最近知ったのですが公式に便利な操作方法が載ってました。

まず、レベルに置いたカメラたちを全部、複数選択状態にします。

f:id:hiyokosabrey:20190424223754p:plain

そしてレベルブループリントのグラフ上で右クリックして、

Create References to X selected Actors を探します。

f:id:hiyokosabrey:20190424224129p:plain

結構紛れて探しにくいのですが、 cr ref で検索するとすぐ出てきます。

これを選択すると・・・

f:id:hiyokosabrey:20190424224430p:plain

結構便利かも。

 

このうちの一つから、Make Array ノードを取り出してつなぎます。

f:id:hiyokosabrey:20190424230004p:plain

これで配列化できました。

これをさっきのSpawn ノードにつなぎます。

f:id:hiyokosabrey:20190424230228p:plain

Spawn Transform ピンに何もつながれていないとコンパイルした時にエラーがでます。

f:id:hiyokosabrey:20190424230424p:plain

f:id:hiyokosabrey:20190424230818p:plain

これはTransformについて適当な初期値を渡してやれば消えてくれます。

オレンジ色のピンからドラッグして、Make Transform を選択します。

f:id:hiyokosabrey:20190424231303p:plain

こうなります。

f:id:hiyokosabrey:20190424231430p:plain

これで、再生したときにメインカメラのブループリントがワールドに誕生します。

あとはWidgetをViewportに描けば完成です。

 

Widget を表示するのは、おなじみ Create Widget ノードと Add to Viewport ノードのコンボです。

Create Widgetノードも Class を変えると入力ピンが増えます。

f:id:hiyokosabrey:20190424232120p:plain

ここと、先ほどの Spawn Actor from Class の Return Value とをつなぎます。

f:id:hiyokosabrey:20190424231831p:plain

ようやく完成です。

 

再生してみます。

f:id:hiyokosabrey:20190424234258j:plain

 

静止画だとタイムラインの動作が伝わらないので動画にしました。

youtu.be

使っているタイムラインは1つです。

ブループリントを触らなくても、カメラのアングルやFOVはワールドに置いてあるカメラを直接触ればすぐに反映してくれます。気に入ったアングルはレベルを保存するだけです。

 

いかがでしょうか?

キャラクリエイションとかで使えそうな気がします。

例えばキャラの身長に合わせてカメラの距離を調整したりとかも、今回の方法ならタイムラインノードをいじらずに臨機応変に補正ができます。

 

結構長くなりましたが、今回はここまで

ではでは

ステキなカメラ切り替えライフを!

UI ボタンでカメラを切り替える《前編》

突然ですが、ブループリントのタイムラインノードってものすごく便利なのです。

f:id:hiyokosabrey:20190418232904p:plain

UMGには専用のタイムラインが装備されていますが、UMG以外でUIのインタラクションを表現する際に利用します。

ただトラックに直接的な値のKeyを打ってしまうと、とたんに汎用性が無くなってしまいます。

先日のぷちコン第11回でも使った方法なんですが、ちょっと扱い方を工夫するだけで汎用性が高くなってさらに調整がラクになる方法があるので記事にしておくことにします。

 

賢明な読者諸兄はすぐに気づかれるかと思いますが、種明かしをするとこれです。

 

f:id:hiyokosabrey:20190417213555p:plain
 

タイムラインノードから出てくる値は、0.0 ~ 1.0 のみ

 この1個のタイムラインを値を煮るなり焼くなりして使い回すのです。

 

掛け算したり、

f:id:hiyokosabrey:20190417214708p:plain

↑この場合、0.0 ~ 5000.0 の値でアニメーションします。

 

また 0.5から引き算して、2倍すると、 -1.0 ~ 1.0 の値のアニメーションが可能。

f:id:hiyokosabrey:20190417215237p:plain

 

しかも、カーブを調整すれば好みのイージングも作れます。

f:id:hiyokosabrey:20190417214336p:plainf:id:hiyokosabrey:20190417214343p:plain

 変に計算が増えると処理への負荷が無視できなくなりますが、ほどほどに、汎用的に使えるカーブを効率よく使えば、調整にかかる時間が減るのでおススメです。

 

さらにこれを便利に活用するためのノードが、Lerp 系のノード

f:id:hiyokosabrey:20190418233447p:plain

 

扱う型専用にいくつか用意されているようです。

入力ピンの Alpha に 0 ~ 1.0 の数値を入れることで 同じく入力ピン A と B の2つの違う値についてその途中の値を取り出すことができます。例えば、Alphaに 0.5 と入れると、A と B のちょうど中間の値がReturnValueから取り出せます。0 だと A に、1 だと B に近い値になります。

 

Vector や Color のような複数の値を持っている場合で、同時にアニメーションさせる場合にとても効果的なのです。

 

ちなみに Lerp は Linear Interpolate の略で、 漢字で書くと「線形補間」です。

ですがタイムラインのカーブを使えば、変化量が線形じゃなくなります。

 

今回この仕組みを使って、UIのメニューに合わせてカメラを切り替えるのをやってみます。

 

切り替えUIメニュー

まずメニュー用のWidgetから。メニューといっても簡単に四角いボタン的なものが並んでいるだけのものです。

そのボタン的なものは、さくっと簡単に UMG の Button というコンポーネントで作ることにします。まず Button を左のリストから探してキャンバスに 配置します

f:id:hiyokosabrey:20190608111755p:plain

 Anchor は ストレッチ(親要素に合わせて最大まで拡大)でドーンとスクリーンサイズに。Anchor を変えただけでは変化しないのでパラメータを変えます。

Button の Slot(Canvas Panel Slot) は ↓ こんな感じ。

f:id:hiyokosabrey:20190419234722p:plain

 

中にTextBlockを子供として持たせます。

f:id:hiyokosabrey:20190419234037p:plain

TextBlockは ↓ こんな感じ

f:id:hiyokosabrey:20190419235053p:plain

階層構造は↓下のようになってます。

f:id:hiyokosabrey:20190419234201p:plain

 

ここで、クリックイベントを作成します。

配置した Button のDetails(詳細)パネルの下の方に緑色のボタンが並んでいるので、

On Clicked のボタンをクリックします。

f:id:hiyokosabrey:20190419235431p:plain

 強制的に編集モードがGraphモードに切り替わります。

f:id:hiyokosabrey:20190420003037p:plain

イベントノードが置かれていますが、一旦置いておきます。

 

Integer型の変数をひとつ追加します。

f:id:hiyokosabrey:20190420003738p:plain

自身が何番目のボタン番号かを受け取っておくためのものです。

 

次にカスタムイベントを用意します。

Inputピンに、Integer型とText型を追加して、作っておいた 変数 と TextBlock にそれぞれつなぎます。

f:id:hiyokosabrey:20190420004111p:plain

 

次はイベントディスパッチャーを用意します。

f:id:hiyokosabrey:20190420004632p:plain

Inputsのところで+ ボタンを押してInteger型のピンを追加します。

このイベントディスパッチャーを、On Clicked イベントにつなぎます。

イベントディスパッチャーをグラフにドロップすると小さなコンテキストメニューがポップするので、 Call を選択します。

f:id:hiyokosabrey:20190420005034p:plain

f:id:hiyokosabrey:20190420005134p:plain

クリックされたら、自分が持たされているボタン番号を返す仕組みです。

コンパイルして保存します。これでボタンWidgetは完成です。

f:id:hiyokosabrey:20190608112158p:plain

 

 

ボタンWidgetを配置するWidget

次にボタンを並べるための親になるWidgetブループリントを用意します。

キャンバスに HorizontalPanel をひとつ配置します。

f:id:hiyokosabrey:20190420005733p:plain

Slotはこんな感じ。

f:id:hiyokosabrey:20190420010124p:plain

このHorozontalPanelに、先に作ってあったボタンのWidgetを追加します。

既に作成済みのWidgetブループリントはアセットになっているので、

UserCreated カテゴリで探すと見つかるはずです。

それを Hierarchey パネルの HorizontalBox 以下にドロップします。

とりあえずカメラを5台置く予定なのでその数ぶん追加します。

f:id:hiyokosabrey:20190420010933p:plain

追加したらそのまま、まとめて選択状態にします。

f:id:hiyokosabrey:20190420015453p:plain

レイアウトの設定を変更します。

Size が 初期状態でAuto になってるのを Fill に切り替えます。

f:id:hiyokosabrey:20190420015626p:plain

HorizontalPanelにぴっちり並んでくれるはず。

f:id:hiyokosabrey:20190420015842p:plain


キャンバスはこのくらいで。

 

グラフの方を編集します。

まずは並べたボタンたちを一旦配列にします。配列にした方がForEachLoopが使えるのでこ後でちょっとラクができます。

ざっくりですが手順をGIFにしてみました。

f:id:hiyokosabrey:20190421092553g:plain


次にボタンのラベルをテキスト型の配列で用意します。

f:id:hiyokosabrey:20190421170012p:plain

一度コンパイルすると、DefaultValue(初期値)がセットできるようになります。

 

ここで ForEachLoopの出番です。

f:id:hiyokosabrey:20190421202616p:plain

右端のノードは、イベントディスパッチャーが存在することが前提で、そのイベントディスパッチャーにBind(バインド:紐づけ)することが可能になります。そうすることで、ボタンのWidgetからイベントディスパッチャー経由で通知を受け取ることができます。要するにボタンが押されたら、カスタムイベントが実行されます。

 カスタムイベントにはとりあえず今は何もつなげないで、コンパイルしてみます。

すると、キャンバスが変化します。

f:id:hiyokosabrey:20190421203109p:plain

Event Pre Cnstruct につないだ処理は、Play していなくてもコンパイルした時点で、キャンバスに配置したものに影響を与えることができます。


 Widgetは一旦ここまでにします。

 

 

 

カメラの設置

次はカメラとステージを 用意します。

準備としてレベルに、カッコイ風景とカッコイイオブジェクトを設置します。

 

f:id:hiyokosabrey:20190423011937j:plain


レベルに直接カメラを5台配置。アングルを決めます。Field Of View(視野角)も調整します。

 

前編はここまで。

後編は、メインカメラの作成と制御を作っていきます。タイムラインは後編で登場します。

 

ではでは

 

 

 

ゆらゆらするマテリアル

 なんとか第11回ぷちコンに応募することができました。今回はギリギリまでかかってしまってキツかった。今使ってるPCではパワー不足だということが思い知らされた感があって寂しくはあるけど、あきらめた表現がたくさんあったのもまた事実で、もう買い替えをためらっている場合ではないのだと強く思った次第です。なんてことを書いてますが、かなりの部分は自分の頭の悪さだったりして・・・すみませんマシンパワーのせいにしました。てへぺろ

 実際シェーダーコンパイルに時間がかかり過ぎたのと、半透明に落とす影の調整に時間がかかり過ぎました。

 

 応募した動画を貼っておきます。


【第11回UE4ぷちコン応募作品】 ちいさなうきわ。

 

 動画編集はいまだに不慣れなので、編集に時間を掛けられないと判断。実際にプレイしたキャプチャをそのままノーカットでアップしました。さすがに前後の黒い余白時間をカットするくらいはしてますが、ほんとそれだけです。

 なので、画面遷移やUI表示は職業柄それなりに頑張って作ったつもりです。

 

まぁそんなこんなで、ブログの更新が止まっていたわけですが、久しぶりに書こうと思います。ネタはぷちコンからで、仕組み自体は過去記事でも紹介したものの応用になります。

f:id:hiyokosabrey:20190403223820g:plain

ゴールした時の誉め言葉表示です。

この水の屈折のような表現。ディストーション(歪み)エフェクトとも呼ばれるようですがマテリアルで簡単に作ることができます。

 

 まずは ゆらゆら させるためにはランダムな2D素材が必要です。Noise ノードを使うとテクスチャが不要にできるのですが、やっぱり滑らかさが欲しいので今回 Photoshopを使ってテクスチャを用意するところから説明していこうと思います。

 

あの有名なフィルタを攻略する

ランダムといっても大きなテクスチャはそのままテクスチャメモリの負担となるのでなるべく小さく、かつタイリングできるように作ります。

そこで登場するのがおなじみのフィルター「雲模様1

f:id:hiyokosabrey:20190403230040p:plain

ちなみに↑これは、 512x512 で作成したものを 50%に縮小した状態です。

このフィルターは、2のべき乗で作成すると上下左右がつながったシームレスな状態になります。

フィルター > その他 > スクロール を使うとつながりが確認できます。

f:id:hiyokosabrey:20190403230611p:plain

2のべき乗というのは、2を何乗かした値で、2、4、8、16、32、64、128、256、512、1024・・・という数字です。縦横をこの数の解像度にすると見事シームレスなテクスチャの完成です。ちなみに他の解像度で雲模様を作ると、つながらなくなります。

f:id:hiyokosabrey:20190403231252p:plain

これは 250x250で作成してスクロールさせた状態。よく見るとつながっていないのが判ります。下の図はニアレストネイバーで400%に拡大した状態。

f:id:hiyokosabrey:20190403231641p:plain

 

これが「雲模様1」の特徴です。

ちなみに、この「雲模様1」は、フィルタを掛ける際に、描画色背景色の2色を使って作られます。なので、今回のようにモノクロでしっかりと階調差を出したい場合は、直前に D キーを押して、白黒にしておくのをお勧めします。

f:id:hiyokosabrey:20190403232223p:plain

 

あと、注意したいのが「解像度」です。雲模様フィルタは、密度の調整ができないので、解像度でコントロールすることになります。

f:id:hiyokosabrey:20190403233518p:plain

ヒストグラムで比較するとさらに差が分かりやすくなります。

という訳で濃淡の散らばり具合を考慮して今回は 1024x1024で進めます。

 

 

素のままだとノイジーなので、ここから加工していきます。

水の揺らめきなので、もっと滑らかな感じが理想。

ボカしたり、明るさの中間値を掛けると速いのですが、この状態でかけると、せっかくシームレスにしたのが台無しになってしまいます。フィルタはドキュメントの上下左右がつながっているなんて気にしないからです。

そこで、面倒ですが一旦 手動でシームレスな状態にします。

 

まず 新規ドキュメントを 1024x1024で作成して「雲模様1」を使用します。

f:id:hiyokosabrey:20190403235506p:plain

この状態で、全選択(Ctrl+A) からの コピー(Ctrl+C)までを実行。クリップボードに入れた状態で、カンバスサイズを変更します。

f:id:hiyokosabrey:20190403235842p:plain

するとこうなります。↓

f:id:hiyokosabrey:20190404000004p:plain

ここからクリップボードに入れたのをペーストしていくのですが、ちょっとだけ時短できるぷちテクニックを紹介しておきます。選択範囲を作ったペースト方法です。

まず左上に貼り付けたい場合、マウスを下図のように外に向かって適当に動かします。

f:id:hiyokosabrey:20190404001021p:plain

すると、

このような選択範囲になるので、ここでペースト(Ctrl+V)します。

f:id:hiyokosabrey:20190404001305g:plain

これを4隅で行います。

f:id:hiyokosabrey:20190404001717p:plain

上下左右も同じ要領で適当に選択範囲を作って、

f:id:hiyokosabrey:20190404203509g:plain

ペースト&ペースト。

f:id:hiyokosabrey:20190404203731p:plain

 

多少ずれても中央揃え機能を使えばビシっと隙間なく並びます。

f:id:hiyokosabrey:20190404002452p:plain

アナログな手操作を確実でブレのない結果にするのが大好きなので紹介しましたが、この時点でピクセルがずれていなければどんな方法でもOK。Photoshopは作業中の表示倍率がキリの悪いときはかなりの確率でピクセルスナップが甘くなるので、こういった倍率に関係なく確実な操作は重要です。

 

 この9枚がビシっと並んでいる状態でようやくフィルタを掛けます。

せっかくタイリングするように並べたので、ランダム系のフィルタはNG。あくまでもピクセルに合わせて均等に掛かるタイプのフィルタを使います。

 

今回は、明るさの中間値 を掛けてから ぼかし(ガウス)を掛けました。

f:id:hiyokosabrey:20190404003607p:plain

ここで解像度を落とします。

最終的に 256x256 のテクスチャにしたいので、256x3 = 768

768x768 に縮小します。もちろん縮小されるときにもピクセルにフィルタが掛かります。

f:id:hiyokosabrey:20190404004304p:plain

ここで、カンバスサイズを使って 周りの 8枚をカット、中央の1枚だけにします。

f:id:hiyokosabrey:20190404004525p:plain

シームレスになっているかどうか、「スクロール」を使って確認してみましょう。

f:id:hiyokosabrey:20190404004917p:plain

つなぎ目が見えなければ完璧です。

 

これをリニアなグレースケールテクスチャとして、UE4に持っていきたいので、ドキュメントをグレースケールに変更します。イメージ > モード > グレースケール を選択。

f:id:hiyokosabrey:20190404005241p:plain

 

おそらくポップアップが表示されるので、カラー情報は破棄します。

f:id:hiyokosabrey:20190404005606p:plain

 

チャンネルが 「グレー」だけになったらOK。

f:id:hiyokosabrey:20190404005812p:plain

 

 あとは、これをTarga フォーマット(拡張子 .tga)で書き出せば UE4 にインポートできます。

 PNG形式はチャンネルが一つだけ(SingleChannel)という仕様を持っていないため、PNGで保存した際にRGBA32ビットに戻されてしまいます。なのでグレースケールテクスチャの場合は、PNGは使っちゃダメです。チャンネルが一つということはそれだけテクスチャのメモリが1/4になるということで節約になります。

 

今回よりランダム感を出したいので、もう一枚用意します。

 

f:id:hiyokosabrey:20190404205000p:plain f:id:hiyokosabrey:20190404205011p:plain

 ↑ Web用にPNG形式になってますが右クリックしてDLしてもらってOK

この2枚をUE4にインポートします。 

 

いざUE4

 インポート設定はこんな感じです。

f:id:hiyokosabrey:20190404213401p:plain

今回 sRGB のチェックは外しているので、リニアグレースケールテクスチャとして扱うという意味になります。これはPhotoshopでのカラー設定によって書き出す際の補正があるか無いかで決めます。特に何も設定をいじっていなければsRGBにチェックが付いていると思いますが、今回は特に問題ありません。

 

 ちょうど一年前の記事ですが一応過去記事を貼っておきます。

limesode.hatenablog.com

これを使ってマテリアルを作ります。

Create Material して中にテクスチャをドロップします。

f:id:hiyokosabrey:20190404220627p:plain

 

あ、揺らすためのテクスチャを忘れていました。せっかくなんで。

 

f:id:hiyokosabrey:20190404223919p:plain

 

これをつなぐとこうなります。

f:id:hiyokosabrey:20190404223544p:plain

 速度は左のPannerノード で調整しつつ、揺れ幅というか揺れの度合いは、TexCoordノードで調整します。

参考までに今回の値。

f:id:hiyokosabrey:20190404224930p:plain

さらに適応する量は、真ん中のScalarParameterValueで調整します。このパラメータノードは、Widgetブループリントから値を受け取って反映する役割のノードです。

雲模様のコントラストや濃淡によりますが、今回の値はこんな感じ。

まず、Amount: 0.15

f:id:hiyokosabrey:20190404225807g:plain

かなり揺らめいてます。

次は Amount: 0.1 だいぶ落ち着いてきました。

f:id:hiyokosabrey:20190404230015g:plain

Amount: 0.025

f:id:hiyokosabrey:20190404230110g:plain

もちろん 0 にすると静止します。

 

ちなみに 0.5 はまったく読めません。

f:id:hiyokosabrey:20190404230555g:plain

これだけで一つミニゲームが作れそうです。

なんて書いてあるでしょうか?さぁ早押しでお答えください! みたいな。

ただあまり揺らすと端の切れてる部分が目につくようになるので注意が必要です。

 

真ん中上の、Subtract(引き算)ノードでは、グレースケールテクスチャからやってきた値を調整しています。

f:id:hiyokosabrey:20190404232821p:plain
ここでいう調整というのは、0~1 という範囲を、幅を変えずに -0.5~+0.5 にすることです。テクスチャの黒は 0.0、白は 1.0なので、このまま足し算して使うと、揺れはしますがプラスの方向にしか揺れないので、結果的にテクスチャ全体がプラス側に寄ってしまいます。それをプラスとマイナスの距離が均等になるようにしているのです。

 

マテリアルはこれで完成です。

 

ちなみに、↓こんなテクスチャだと。

f:id:hiyokosabrey:20190404233848p:plain

こんな感じにできます。

f:id:hiyokosabrey:20190404234059g:plain



 

Widgetでフェード

まずキャンバスに、Imageを配置します。

f:id:hiyokosabrey:20190404234621p:plain

配置したら、Detailタブから、Appearance > Brush > Image の部分に用意したマテリアルをセットします。

f:id:hiyokosabrey:20190405000649p:plain

 

さらにもう一つ、Imageをキャンバスに追加します。

f:id:hiyokosabrey:20190405003508p:plain

これは描画しないのでVisibilityを Collapseにしておきます。

f:id:hiyokosabrey:20190405003342p:plain

ここでアニメーションを用意します。

 

アニメーションさせるのは2つ。

一つは、Collapseにした方の Pivot。もう一つは、表示する方の Render Opacity

f:id:hiyokosabrey:20190405010528p:plain



つぎにブループリントを用意していきます。

 

初めから置かれているイベントノードをそのまま利用することにします。

Event Pre Constructから。

f:id:hiyokosabrey:20190405010744p:plain

Image にセットしているマテリアル(Static)をいじるには、マテリアルを、Material Instance Dynamic型 にする必要があって、Get Dynamic Material ノードを使うと型変換しつつ、置き換えてくれます。この変換処理を頻繁に行いたくなくて、一度きりで十分なので、ReturnValueから Promote to Variable(変数に昇格)させています。

 

Event Construct。

f:id:hiyokosabrey:20190405011947p:plain

アニメーションの再生が終わったら、後片付けしてViewportから姿を消します。後片付けといっても変数を空にしてマテリアルへの参照関係を解消するだけです。

 

最後に、Event Tick。

f:id:hiyokosabrey:20190405013151p:plain

 

Widgetは完成です。

 

テスト用の新規レベルを作ってテストしてみましょう。

レベルブループリントに Create Widget ノード と Add to Viewport ノードをつなぎます。繰り返し確認したいので、Inputノードで何度でもWidgetを呼び出せるようにします。とりあえずスペースキー。

f:id:hiyokosabrey:20190405013900p:plain

 

再生してみます。

f:id:hiyokosabrey:20190405014037g:plain

いい感じです。青い背景を置いたので、マテリアルのBlend Mode は Additive に変更しています。

 

 

シームレステクスチャのパターンを工夫したり、タイリングや揺れ加減を調整したり、まだまだ応用できそうです。

 

ではでは

今回はこの辺で

ステキなゆれゆれマテリアルライフを!

 

3Dオブジェクトの上にWidgetをマーカーのように表示できたけどどうなってるの?なんか計算がややこしいのだけれど。

という長いタイトルを付けるのが最近のトレンドぢゃ、とじっちゃが言ってたのでさっそく実践してみたけど、なんというか全然キャッチーにならないのはセンスがないから?などと架空の祖父まで召喚してくだらない言い訳をつらつらと書きながら花粉の猛襲に苛まれる目をひたすら擦りながらキータイプもままならない状態でややこしい記事を書こうとしている折、皆様ますますご健勝のことと存じます。

さてさて前回はとりあえず動くところまでのチュートリアル的な記事を書きました。

少ない脳みそを駆使して解説をしてみようと思います。実際はこうやって書きながら自分の頭を整理してログを残すのが目的だったりします。

 

 さてさて

今回の処理で扱う重要な数値は以下の5つ

  • 描画のための画面サイズ  → Get Viewport Size
  • 追随するWidgetのサイズ  → Get Desired Size
  • 表示スケール  → Get Viewport Scale
  • アクターの表示位置  → ConvertWorldLocationToScreenLocation
  • 最終表示位置Set Position

 

 これらの値があれば計算できるのですが、ひとつ厄介な問題があります。それは最終的な表示サイズが必ずしも画面の大きさと同じとは限らないという問題です。

 たとえば開発中のエディタ解像度を 1920x1080 で進めたとしても、実際遊ばれる環境は、1080pじゃない場合が普通にありえます。フルスクリーンにしろウィンドウにしろユーザーに任されていて遊ぶ環境によって変わるため、UE4では表示する際に、表示されている解像度に応じて一定量のスケール値を掛けることで、元のレイアウトが崩れないように描画してくれます。

 また表示する際に指定する座標は、描画のための画面サイズであるエディタ解像度に準拠するので、スケールを掛けないように指定する必要があります。

 スケールが掛かっていたり掛かっていなかったりする値をうまく整理しながら計算しないといけないのです。

 

開発環境にてエディタ解像度を 1920x1080 にして、画面には 1280x720で表示している場合、上にあげた5つのノードで扱う値は、以下のようになっています。

 

f:id:hiyokosabrey:20190310012023p:plain

Get Viewpor Size は表示されている解像度です。(基本的に可変します)

Get Desired Size にはキャンバスに置いたときの値で、スケールが掛かっていません。

 

f:id:hiyokosabrey:20190310010520p:plain

 

f:id:hiyokosabrey:20190310012429p:plain

 

ここまではGET。最後にSET。

 

f:id:hiyokosabrey:20190310013029p:plain

 

このようになっているので、@ghosticgames さんの手順をみてみると、基本的に スケールの掛かった ViewportSize ベースで計算して、表示位置が確定した後に ViewportScale で割り算して、1920x1080空間用に合うようにしています。

f:id:hiyokosabrey:20190310014617p:plain

 

Get Desired Sizeで得た追随用パーツのサイズに ViewportScaleを掛けているのは、スケールを揃えないとこの後の計算がおかしくなるからです。

f:id:hiyokosabrey:20190310014943p:plain

 

ではこの辺で Clampさんのお仕事紹介と行きましょう。

Clampは3つの入力ピンがあります。

f:id:hiyokosabrey:20190310125803p:plain

Value から入った値が、Min と Max の範囲を越えても、Min(下限) と Max(上限) の範囲から出ないことを保証してくれるという代物。

下図はイメージ。

 

f:id:hiyokosabrey:20190310131747p:plain

例えば、Max に 1.0 が設定されている時に、 Value が 2.5 だとすると、強制的に 1.0 になります。

 

 この仕組みを使って、表示座標に制限を付けるのです。

 

 

f:id:hiyokosabrey:20190310133059p:plain

 アクターの表示位置を3D→2Dに変換する ConvertWorldLocationToScreenLocationノードは、スクリーン座標の限界を超えた値も返してくるので、Clampノードにスクリーン座標の最小値、最大値を設定してやります。

 このとき、追随するWidgetがはみ出さないないようもしたいので、そのぶん内側に制限を付けることになります。

 

f:id:hiyokosabrey:20190310145306p:plain

 

ViewportSize から 追随するWidgetの表示サイズの半分のサイズを内側に入れればよいということになります。改めてノードを見てみるとこうなってました。

f:id:hiyokosabrey:20190310145815p:plain

Local Desired Screen Location はアクターのスクリーン座標です。Viewportの左端は 0 から始まるので、 Min は 追随するWidgetの半分のサイズになります。Max はViewportサイズから 追随するWidgetの半分を引き算すれば求まります。

 

これで、画面内から絶対はみ出さない仕組みが実現できます。

 

 

ということで、ここからは、オマケの仕組みを組み込んでいきます。

f:id:hiyokosabrey:20190310154211p:plain

グラフを開いて、空いているところに、表示する値を受け取るためのカスタムイベントを準備します。

f:id:hiyokosabrey:20190310193804p:plain

カスタムイベントは Float型の ピンを追加します。

f:id:hiyokosabrey:20190310194218p:plain

 

数値に単位の文字「 m 」をくっつけたいので、FormatTextノードを使います。

このノードは、定型のテキストに変数を差し込むことが出る便利なノードです。

{ }でピン名を囲って使います。上の例では、{0}m と入力しています。⑤

 

過去記事にもう少し詳しく書いています。

limesode.hatenablog.com

 

UE4の単位は、Project Settings > Editor > Appearance で設定されています。 

f:id:hiyokosabrey:20190310194925p:plain

 デフォルトでは cm(センチメートル)に設定されています。

 今回 単位を m(メートル)にしたいので、100で割ります。⑥

 割り算の結果と FormatTextノードを直接つなぐことが可能なのですが、小数点の桁数を変更したいので、 ToText (Float)ノード を利用します。⑦

この ToTextノードは詳細設定ができます。

f:id:hiyokosabrey:20190310200846p:plain

Minimum Fractional Digits を 1 にすると、キリのいい値のときでも、0 を表示するようになります。

 

これでWidgetの編集は完了です。

コンパイルして閉じます。

 

次は、このWidgetに距離を調べて渡す部分。

レベルブループリントを編集します。

すでにつないである部分2つ目の Create Widget のReturn Value ピンで右クリックします。

f:id:hiyokosabrey:20190310205114p:plain

Remote To Variable (変数へ昇格)を選択して、変数化します。

f:id:hiyokosabrey:20190310205410p:plain

これに名前を付けて、間に挟み込みます。

f:id:hiyokosabrey:20190310205521p:plain

この操作で、追随するWidgetに対してアクセスが可能になります。

 

これをEventTickで使用します。

f:id:hiyokosabrey:20190310210459p:plain

対象の3Dオブジェクトと、プレイヤーカメラの距離を表示してみます。

双方のポジションを取得して引き算すると差分が得られます。それを VectorLength ノードが距離として返してくれるので、これを利用します。

 

 最初3次元上の2点の距離を調べる公式を使ってノードを組んでいたのですが、

f:id:hiyokosabrey:20190310212922p:plain

f:id:hiyokosabrey:20190310212023p:plain

ブループリントで数式を組むとなんだか不思議な感じがするのは気のせい?

 

検証している途中でステキな記事を見つけたので、シンプルな VectorLength を使った形に変えました。

qiita.com

 

 これで完成です。

 

 再生して確認してみると、

f:id:hiyokosabrey:20190310211607g:plain

ここまで来たらあとは見た目をゴニョゴニョするだけですね。

 

 

いかがだったでしょうか?

実際に組み込もうとすると、もっと機能性や汎用性を持たせた方がいいと思いますが、ゲームメカニクス次第なところもあってこれ以上は踏み込むのは難しいかな。

とはいえ結構個人的に面白いネタだと思うので、もう少し遊んでみようと考えています。

分かりにくいとことかあれば↓コメントかツイッターで質問OKです。

 

こんこんと泉のごとく沸き続ける鼻水と目のかゆみと闘いながら変なテンションで書いています。

いちいち眼鏡外さなくてもさりげなく点眼する方法って開発されないかな。

 

ではでは

ステキなマーカーライフを!

3Dオブジェクトの上にWidgetをマーカーのように表示する。画面外にいっても大丈夫。操作手順もできるだけ丁寧に書いてみたけどどうかな?

前回の更新からちょっと間が空きましたが、ぷちコン作品を作ってました。

で、先日とあるツイートに目がとまったのです。

 

 

 これは、Worldに存在する Actor の上に Widget を表示するという内容で、さらに対象の Actor が画面外に出ても、画面の端に張り付くようにするという 素晴らしいものです。

 3Dオブジェクトの上にWidgetを表示するという方法は、すでにブログ等で公開されているので、目新しさは無いですが、画面の外に出ないようにする工夫に心惹かれました。

  ツイ主の @ghosticgames さんにブログで説明してもよいかコメントで確認したら、"Sure! "(もちろん!)という返事を頂けたので、さっそく当ブログで説明させていただくことにしたわけです。質問箱からもブログのネタにどうかという提案(いい感じに解釈)もあってこれはもうやってやるしかないなと。

 

 

まずは、さっそくツイート画像を見ながら全く同じように試してみました。

その結果がこちら。

 (このツイート、「メディア」にはログとして残ってるけど「ツイート」には出てこなくなった・・・)

 

 基本的な原理はとてもシンプルです。さっそく他の方法はないかとアレコレ実験してみたんだけど、扱い方次第になると思うので、今回は手を加えずに教材として説明してみます。

 

 ツイートされてる画像とレイアウトや変数名などが若干違うものがありますが、基本のロジックは同じ構造です。基本的な動作を作った後でアレンジとして対象までの距離を表示するとこまでやろうと思います。

f:id:hiyokosabrey:20190306231002j:plain

今回はUMGやブループリント編集に慣れていない方でも作れるように操作方法をなるべく細かく書くようにしてみました。(作業の能率がいいので英語環境をオススメします)

 

 

 

用意するWidgetは2つ。

f:id:hiyokosabrey:20190306231618p:plain

 まずは1つ目のWidgetから

 

キャンバスだけのWidget

コンテンツブラウザから、 右クリック > User InterfaceWidget Blueprint

で作成できます。

 

できたらダブルクリックしてエディタを開きます。

エディタウィンドウ左にある、Hierarchy タブの中を確認すると、あらかじめ Canvas Panel_0 というのが置かれているので、クリックして選択。

f:id:hiyokosabrey:20190306233841p:plain


続いて右上の Is Variable というチェックボックスにチェックを付けます。

f:id:hiyokosabrey:20190306233045p:plain

付けることで、ブループリントからこの CanvasPanel_0 に対して直接いじることができるようになります。キャンバスパネル は UIパーツを描画するため使われます。

 

念のためキャンバスの描画サイズを確認。

f:id:hiyokosabrey:20190306234311p:plain

これはこのWidgetを、画面いっぱいまで使って描くよ、という意味合いです。

 

このWidgetブループリントはこれで完成です。

コンパイルして保存したら閉じてもOK.

f:id:hiyokosabrey:20190307002412p:plain

 

 

 

次は2つ目のWidget

 

Actorをフォロー(追随)するWidget

今度はキャンバスのサイズを指定します。

f:id:hiyokosabrey:20190307003540p:plain

Fill Screen と書かれている部分は、プルダウンメニューになっているので、Custom を選択します。

幅(Width)と高さ(Height)を指定できるようになるので表示したい適当なサイズ(単位はピクセル)を入力します。今回は100x100で作りました。

f:id:hiyokosabrey:20190307003734p:plain

 

次にキャンバスに、2つのパーツ Image と TextBlock を配置します。まずは Hierarchyパネル で ドラッグ&ドロップします。

f:id:hiyokosabrey:20190307004520p:plain

ドロップしたら、エディタウィンドウ右のDetails パネルで設定をいじっていきます。

 

 Image の方から。

Anchor(アンカー)はそのままでOK。

f:id:hiyokosabrey:20190307223852p:plain

キャンバスの中めいっぱいになるように、Sizeをキャンバスの大きさと同じ値を入力します。

あとは Detailsタブの Appearance > Tint で好きなカラーをセットしたら下敷きは完成。

 

次にTextBlock。

こちらもAnchorを設定します。

f:id:hiyokosabrey:20190307221700p:plain

中央揃えにしたいので、『Top-Center(勝手に命名)』 を選択。

パラメータは以下。

f:id:hiyokosabrey:20190307222529p:plain

テキストのカラーと文字サイズを決めて、レイアウトをセンタリングにします。

f:id:hiyokosabrey:20190307222916p:plain

あと、これもブループリントから内容を書き換えるので、Is Variable にチェックを付けておきます。

 

キャンバスがこんな感じになればOK。

f:id:hiyokosabrey:20190307222732p:plain

 

いよいよブループリントを編集します。

編集モードを切り替えます。

f:id:hiyokosabrey:20190307224355p:plain

 

切り替わったら、グラフの何もない所で、右クリックします。

f:id:hiyokosabrey:20190307224712p:plain

Add Custom Event を探します。Search のところで検索ワードを入力するか、一番上にAdd Event というカテゴリを開くと見つかります。(←英語環境)

見つけて選択するとノードが現れるので、

f:id:hiyokosabrey:20190307224914p:plain

InitFancyFollowingWidget と名付けておきます。

ここにパラメータを受け取るための 入力ピン を追加します。

このノードを選択したまま(オレンジの枠線をつけた状態)Detailsタブを見てみるとInputs という項目があるので、右にある + ボタンを 静かに2回クリック。

f:id:hiyokosabrey:20190307225733p:plain

適当な型の設定項目が2つ並ぶので、上からプルダウンを開けて、

Actor を探します。

f:id:hiyokosabrey:20190307230610p:plain

2つ目は

Canvas Panel Slotを探して選択します。

f:id:hiyokosabrey:20190307230832p:plain

下のようになればOK。

f:id:hiyokosabrey:20190307225945p:plain

PINの目的(役割)を後から思い出せるように、それぞれに名前を付けておきます。

これでグラフに置いたノードが変化します。

f:id:hiyokosabrey:20190307231151p:plain

次は変数を用意します。この青いピンからドラッグして作成します。

f:id:hiyokosabrey:20190307232229p:plainPromote to variable(変数へ昇格)を選ぶと、ピンと同じ型の変数が、SETノードの形で現れます。

f:id:hiyokosabrey:20190307232352p:plain

名前を付け直したら、2つ目のピンも同じようにして変数化します。

f:id:hiyokosabrey:20190307232719p:plain

これで、ターゲットとなる Actor と配置用の キャンバスパネルスロット にアクセスするための準備ができました。まだこの時点では中身はなくて空っぽな状態です。型が決まった器があるだけです。

変数化できたので、エディタウィンドウ左にある、Variables のリストに追加されているのが確認できます。そこから、グラフにノードとして取り出します。

f:id:hiyokosabrey:20190307235600p:plain

Ctrlキーを押しながらドロップすると、いきなり GET のカタチで取り出せます。

ちなみに Altキーだと、SETのカタチで取り出せます。

 

このノードの中身が入っているか確認してから処理するので、確認用のノード is Valid をつなぎます。

f:id:hiyokosabrey:20190308000317p:plain

なんらかの不具合や処理順などで受け取りを失敗することがあります。その時は中身が無効になることがあるので、このノードでチェックするのが安全です。今回は 両方が有効の場合のみ 処理したいのでAND(Boorean)を使います。これは論理式というやつで『 A かつ B 』という意味になります。両方のIsValid ノードの結果が true になってようやく true になります。

これらを、グラフに最初から置かれている赤いイベントノード、Event Tick ノードにつなぎます。

f:id:hiyokosabrey:20190308000109p:plain

 

右端は、表示位置をセットするノードです。変数CanvasSlotをもう一つGETで取り出すと、そこから安全に取り出せます。

f:id:hiyokosabrey:20190308001627p:plain

f:id:hiyokosabrey:20190308001745p:plain

仕上げに、ポジションを計算する関数を用意します。

今回の要の部分です。

 

座標計算用の関数

MyBlueprintタブの Functions のところにある +ボタンをクリックして新しい関数を準備していきます。

f:id:hiyokosabrey:20190308002405p:plain

 名前を ProjectWorldToScreenClamed としておきます。

f:id:hiyokosabrey:20190308231337p:plain

グラフが切り替わっているので、ノードをつないでいきます。ちょっと長めです。

 

まず、①Get Player Controller 、そのReturn Value からは、②Get Viewport Size

f:id:hiyokosabrey:20190308232618p:plain

 右にある2つの変数は ローカル変数です。この関数内だけという限定された範囲で使うためのものです。関数の外では利用できない変数です。別の見方をするとこの関数以外での利用価値が無い、とも言えます。今回一時的な計算のためだけに存在するのでローカル変数を使用します。

 作り方は、Get Viewport Size ノードの SizeX と SizeY のピンからドラッグ&ドロップするとポップアップメニューが出るので Promote to Local variable を選択すると簡単です。

f:id:hiyokosabrey:20190308233407p:plain

  エディタ左の MyBlueprintタブに、関数を編集している時にだけ現れる Local Variables であらかじめ用意しておくこともできます。

f:id:hiyokosabrey:20190308233822p:plain

ドラッグして作る方が、VariableType(変数の型)が確実に設定されるのでオススメ。

 

まず Viewport の大きさ(画面表示サイズ)を変数に入れておきます。

 

 

今度はこのWidgetのキャンバスに置いたパーツのサイズを取得して、これもローカル変数に取り置きします。

f:id:hiyokosabrey:20190309105044p:plain

 

まず①Get a reference to self ノードから始めます。

f:id:hiyokosabrey:20190309001902p:plain

Get Desired Size表示しようとしているサイズを返します。Self(自身)に対してつないでいるので、キャンバスに配置したすべてのパーツを含めた最終的な表示サイズのことを指します。

③は vector2d と Float を掛け算するノードです。ドラッグ&ドロップして  *アスタリスク)で検索すると見つかります。

f:id:hiyokosabrey:20190309002428p:plain

 

④Get Viewport Scale は、ディスプレイ解像度を 1 として、UE4実行時に実際に表示されているウィンドウの表示倍率を返してくれます。

f:id:hiyokosabrey:20190309105223p:plain

ディスプレイの解像度設定は公式ドキュメントにあるように設定ファイル "GameUserSettings.ini" に書かれています。

 

⑥は 複数の型で構成されているVector系のデータを分解してくれるノードです。Vector2D 型 は Float型のデータを2つ束ねています。主に XY座標を扱う場合などで使います。

⑦と⑧は これも ローカル変数に格納します。

 

 

 まだまだ続きます。

これはワールドにあるアクターの座標をScreen座標に変換します。

f:id:hiyokosabrey:20190309113742p:plain

 用意しておいたカスタムイベント InitFancyFollowWidgetで受け取っている変数オブジェクトを変数リストからドラッグ&ドロップして始めます。

f:id:hiyokosabrey:20190309114328p:plain

そこから② Get Actor Location でアクターのワールド座標を取得 → ちょっとだけ位置をずらします。

ConvertWorldLocationToScreenLocation というノードで、3次元のワールド座標を画面で見ている2次元のスクリーン座標に変換します。このノードは、先にGet Player Controller ノードを取り出しておくとスムーズに取り出せます。④→⑤

ここにアクターのポジションを渡してやります。

これをまたローカル変数に取り置きします。⑧、⑨

 

右端のは Returnノードです。 関数には基本このリターンノードを置きます。関数というからには何かしら計算や処理した結果があるので、この Return ノードから、外に出してやるのです。ノード検索キーワードに ピリオド 「 . 」 を入力するとすぐ見つかります。

このリターンノードには アウトプット用のピンを追加できます。リターンノードを選択して、Detailsタブから追加します。

f:id:hiyokosabrey:20190309120515p:plain

 

このリターンノードで返す値がこちら。

f:id:hiyokosabrey:20190309121102p:plain

取り置きしてきた座標やサイズの値をここで一気に計算します。

上段が X座標、下段がY座標用です。それぞれ Clampノードではみだしをカットしています。今回の一番重要な処理だと思います。

Integer型 と Float型 の引き算はそのままではできないので、Integer型をFloat型にキャスト(型変換)してやります。

ノード検索で「 to 」と入力すると ToFloat(Integer) が選択できます。

f:id:hiyokosabrey:20190309122206p:plain

こんなノードが出てきます。

f:id:hiyokosabrey:20190309122432p:plain

全体図はこんな感じ。

f:id:hiyokosabrey:20190309122909p:plain

@ghosticgamesさんのと同じ内容ですが、説明用にコンパクトにしました。

 

これで関数は完成ですが、最後の仕上げとして、この関数をPure型にします。

f:id:hiyokosabrey:20190309125648p:plain

Pureにチェックを付けると・・・

f:id:hiyokosabrey:20190309130113p:plain

グラフにノードとして取り出した際に、実行ピン(白色)がない状態になります。

これを途中だった EventTick処理につないだら完成です。

f:id:hiyokosabrey:20190309130359p:plain

コンパイルして保存したらと閉じてもOK。

 

表示を確認してみる

ワールドに適当なオブジェクトを配置します。

f:id:hiyokosabrey:20190309131522j:plain

実験では、Transformを持ってさえいれば動作すると思います。

配置できたら、手っ取り早いのでレベルブループリントを使って確認します。

 

レベルブループリントの編集方法はこちらから。

f:id:hiyokosabrey:20190309131921j:plain

グラフ編集エディタが開きます。

 ①Create Widget からつないでいきます。

f:id:hiyokosabrey:20190309173918p:plain

アクターに追随するWidgetは、Viewort に追加せずに、先にAdd to Viewport したキャンバスだけのWidget内に持っている CanvasPanel の子供にします。

さらに続き、

f:id:hiyokosabrey:20190309174710p:plain

スムーズにつなげるコツは、白い線は気にせずに、Return Value からドラッグして検索することです。つながるのが約束されているノードが取り出せるので安心です。

 

⑧Set Auto Sizeノードは Inb Auto Size にチェックを付けておきます。

 

⑩はワールドに置いたオブジェクトをドラッグします。
f:id:hiyokosabrey:20190309180355p:plain

この方法は可能なブループリントが限られます。

 

これですべての準備は整いました。

再生してみましょう。適当にカメラを動かしてみてください。

f:id:hiyokosabrey:20190309182035j:plain

対象のオブジェクトが画面から消えても残り続けます。

f:id:hiyokosabrey:20190309182308j:plain

これが Clampを使った移動制限です。

 

一応動画のURLを貼っておきます。

みつまめ杏仁Ver

@ghosticgames さん版

 

細かくオペレーションを書いていたら思ったより長くなってしまいましたが、今回はこの辺まででいったん筆を置こうと思います。

 

次回、計算の仕組みを軽く解説して、オマケの「距離」を表示する部分を書きます。

@ghosticgames さん ステキな教材をありがとう。

 

ではでは

ステキなターゲット表示ライフを!