次はスライダーを作る《準備》
以前にもスライダーを作って記事にしましたが、今回はマウス操作専用です。
前回のトグルスイッチと同じところに並べて使う想定のUIパーツなので、引き続き同じアセットもいじりつつアップデートしていこうと思います。
で、さっそく
テクスチャをひとまとめにしてしまおう
スライダーのパーツもひっくるめて一枚のテクスチャに収めることにします。
どうせ同じタイミングで読み込まれるんだったら、枚数が少ない方がいろいろ効率的です。
256x256のテクスチャにまとめました。左がRGB、右がアルファチャンネルです。
まだ余裕がある状態ですが、追加要素のためにキープ、ということにしておきます。
テクスチャの切り出しが面倒なのが残念ではありますが、ゲームのロードが少しでも早く終わるなら、ユーザーの利益につながります。必要経費として頑張ります。
UVの範囲はレイヤーで管理しています。
パーツごとに、適当なカラーで四角く塗り分けたレイヤーにパーツ名をしっかり付けて、レイヤーグループにまとめておきます。中のレイヤーは透明度100%でもレイヤーグループを半透明にすると上図のような状態にできます。
テクスチャとして書き出す際は、レイヤーグループをまるっと非表示にすればOK。
この方法だと、パーツの用途がはっきりするし、場所を入れ替えるときも重なりを回避できるし、アルファチャンネルの移動も確実に行えます。部分的に塗りつぶしたりするときもこの範囲レイヤーからの選択範囲が重宝するし、光彩なんかのボケ足の確認もしやすいので、私はいつもこの方法で管理しています。
さてさて
このテクスチャをUE4にインポートしたら、マテリアルを用意します。
切り出しサイズを決める『Tiling』はMultiplyで、切り出す場所を決める『Offset』はAddノードで計算します。それぞれ U と V があるので、Scalar(スカラー)Parameter を4つつないだ方が、パラメーターの名前も付けられて分かりやすくなるのですが、Vector Parameterにすると、ブループリントから一気に4つの値が渡せるのでノードが少なくなります。
下のようにしても問題は無いです。
マテリアルはこれで完成です。
マテリアルインスタンスでパーツを切り出そう
次にこのマテリアルを親にして、インスタンスを作成します。
コンテンツブラウザのマテリアルアセットのアイコンの上で右クリックします。
コンテキストメニューってやつですね。選択しているアイテムに合わせてメニュー内容が変わるという。ここからマテリアルインスタンスを作ります。
作ったらパラメータを編集します。
値の計算方法は前回の記事で説明しているので省きます。
とにかくテクスチャサイズで割ると求められます。
UMGで使用する パーツとして切り出したい数ぶんマテリアルインスタンスを用意していきます。
細かいパーツごとのパラメータは、次回の記事で書いていくとして、前の記事のトグルスイッチのマテリアルが修正になります。
セットされているテクスチャを今回のやつに変更したら、
場所と横幅はそのままでテクスチャの高さだけが変更になったので、TexCoord ノードのパラメータを変更するだけです。
トグルスイッチのマテリアルが変更できたら、
スライダー用のマテリアルを作ろう
いよいよスライダーのマテリアル。作り方はテクスチャアセットアイコンの上で右クリックです。Create Material して、マテリアルドメインを UserInterfaceに変更するとこまでは、目を瞑っててもできるくらいじゃないといけませんね。
私はできませんけどね。
で、内容はこんな感じ。
テクスチャを部分的に切り出すので、TexCoordノードのパラメータは、
場所が少しずれてるので Add ノードを入れて、OffsetV に 0.25 を固定値としてつないでいます。各パラメータをテクスチャに重ねるとこんなかんじ。
スライダーは、ハンドル(つまみ)が左右に移動します。
それをトグルスイッチの時と同様にUVを動かして再現します。
テクスチャ的には、細かい小数点で管理された、完全にデザイナー都合のUV配置なので、ここで大活躍するのが Lerp ノードです。
Lerpノードの、ピンAとB にスライダー両端のOffsetU。AlphaピンをScalarParameterにして、ブループリントから値を受け取れるようにしています。
正式名称は Linear Interpolate といって線形補間してくれる便利なやつです。ちょっとわかりにくいかもしれないですが、 UV移動で スライダーの 0 から 満タン まで移動する場合の値の変化を図にしてみました。
プログラマ的には、テクスチャ内のレイアウトに依存したUVアニメーションの調整なんてマッピラゴメンだと思います。Lerp ノードを使うことで、その辺の責任をしっかり取れるUIデザイナーになれるという訳です。
スライダーで扱う値はたいてい 0~100% のような『割合』が多いので、そのまま 0~1で制御できた方が分かりやすいし扱いやすいです。プログラマからは、0~1で値を受け取って、マテリアルの中で、いい感じに補正してやるのです。あとからテクスチャ内のレイアウトを変更しても、変更するのはマテリアルアセットのみです。UIデザイナーだけで完結できます。
スライダーのマテリアルができたので、Widgetを作っていきます。
スライダーのWidgetをつくろう
まずはTextBlockと共にキャンバスに配置します。
スライダーの部分はトグルスイッチ同様に、マテリアルをセットします。
TextBlockはラベルと数値の2つ。それぞれ Is Variable にチェックを付けておきます。
キャンバスはこれで完成。
Graphに移動してさっそくGet Dynamic Material ノードで、スライダーとしてセットしたImageパーツから、Material Instance Dynamic を作ってお変数化しておきます。
まずはスライダーのハンドルと値を変更するための関数。
Float型の値を、直接Text型にキャストするノードが ToTextノードです。小数部分はいらないので設定を少しいじります。▽をクリックするとこのノードの設定が変更できます。
Rounding Mode の Half to Even は、端数を丸める処理をいくつかある中の一つです。
端数は小数部分で、 0.5 以下は切り捨てで 0 、0.51 以上は切り上げて 1 にしてくれます。
次に、スライダの初期設定をする関数を用意します。
テキストラベルを書き換えて、先に作っておいた、値を更新する関数を取り出してつなぎます。
これでWidgetは完成です。
前回のトグルスイッチと同じところに並べます。
ストラクチャにも専用のパラメータを追加します。
ストラクチャの更新ができたら、スライダーにも初期値をセットしてやります。
ノードの位置を変えていますが、前回作った wb_main のイベントです。
今回はここまでにします。
表示を確認してみます。問題なければ値が反映されているはず。
レイアウトと見た目の都合で、ミニスライダーになってしまいました。
とうわけで
クリックする部分は次回《解決編》で。
ではでは
ステキなスライダーライフを!
ストラクチャの書き換えについて
トグルスイッチをいじってて気づいたメモ。
前回の記事でストラクチャの内容を、一部だけ書き換える際に以下のようにしていたんだけど、
部分だけを更新するノードがありました。
下図のこの部分を、
Set Member in xxxx ノードに置き換えます。
ストラクチャ型の変数ノード(GET)から探すと見つかります。
このままだと値を渡せないので、Set Member ~ノードをフォーカスした状態で、Detailsタブを確認。
チェックボックスが並んでるので変更したい値のチェックボックスをONにします。
ノードに入力ピンが追加されるので、ここに接続すればOK。
Breakノード側は行き場をなくした出力ピンが寂しい状態なのですが、これを隠すことができます。
Breakノードをフォーカスした状態で、Detailsタブを見ると、Hide Unconnected Pins というボタンがあるのでクリック。
これでかなりすっきりします。
パラメータが増えるたびに、面倒な感じになるなと思ってたけど、ちゃんと用意されてました。てへぺろ。
腰を痛めてしまい安静にするしかない状況で、長く座ってると辛いのですが、いくぶん楽になってきたので、記事更新。
スライダーを作っているので、近日公開します。
ではでは
すてきなストラクチャライフを!
イテテ
スライドするトグルスイッチを作る
久しぶりの更新です。まだそんなに秋を堪能してないのに年賀状が売られていたり、おせちの予約受付とか始まってて、年末感が漂ってくるのが切ない・・・梨食べたくなってきた。
さてさて、ネタを探す旅に出てました~てへぺろ、とか言えたらまだ良かったんだけど、実際は毎日帰るのが遅く、仕事で睡魔と戦うのに精一杯で記事を書くエナジーが無かったのです~てへぺろ。
今回は、トグルスイッチを作ります。ゲームのオプション設定とかでよくあるUIで、ON か OFF かの2つの状態を切り替えるスイッチです。チェックボックスも同じ機能を持っているんだけど、アニメーションさせた方がリッチに見えるので、スライドするタイプをマテリアルで表現します。値の管理方法として他の設定項目のことも考えてストラクチャを使ったりしています。また、イベントディスパッチャーやバインドは使いません。最後の方にUI制作のワークフローについて少しだけ触れています。
クリックするたびに切り替わります。
UV移動なので、スライドをやめればいつでもチェックボックスに転用できます。
まずテクスチャを用意します。
Sizeは 256x64 で RGBはこんな感じ。
フリンジが気になる場合は黒い部分をなくしてカラーを入れます。
アルファチャンネルは片側だけを白くして抜きます。
これをマテリアルで制御します。
テクスチャの一部分を切り出すには、TexCoord ノードのタイリングを利用します。
縦方向(V)は全部使うので 1.0 のまま。横方向(U)は以下のように計算します。
切り出したい大きさを、テクスチャのサイズで割ると求められます。
もっと大きなテクスチャでも基本的に計算方法は同じ。
ただし下図のように左上に配置していない場合はオフセットの値が必要になります。
UV値は0~1の値なので、小数を扱うことになります。テクスチャサイズで割り算することになるので、配置場所や面積(パーツのサイズ)は極力偶数にすることをオススメします。奇数だと割り切れないため誤差が生まれやすく一部のピクセルがキレイに描画されなくなったりします。テクスチャ圧縮がかかる場合は、さらに4で割り切れる場所にパーツを配置することをオススメします。後からテクスチャの解像度を変える場合でも偶数にしておけば安心です。
UI表示はカメラに依存せず静止していることがほとんどなので、ピクセルが荒れていると目立ちます。面倒ですがこのあたりは丁寧に扱うに越したことはないです。
切り出すUVの範囲が分かったところで、今度はUVを動かす範囲を計算します。
96をテクスチャサイズで割った値が移動量です。テクスチャサイズが 256pxだと、0.375 になります。
この移動をUMGのアニメーションで動かします。
まずはTextBlockと共にキャンバスに配置します。
トグルスイッチのテクスチャは、画像ではなくマテリアルをセットします。
TextBlockには is Variable のチェックを付けておきます。
次にアニメーションを作成します。
Imageパーツの Pivot の値を 0.0 ~ 0.375 でキーを打ちます。
UMGはここまでです。
エディタウィンドウをGraphに移動してブループリントを編集していきます。
常時走る EventTick を使っていますが、トグルスイッチなどのUIパーツはセッティング画面で使うことが多いので、ポーズ中とかゲームプレイ以外だと、それほど問題にならない、はず。Pivotの値を取り出して、毎フレームマテリアルのパラメータに渡しています。
少しでも処理をシンプルにしたいので、コストの高いGet Dynamic MaterialノードはTickに入れないようにして、Event Construct で最初に変数化しておいたものを利用します。
次に、トグルスイッチを動かすイベント。
UIのインタラクションはユーザーの操作で割り込まれるのが常なので、割り込まれても問題なく動くようにします。 なるべくアニメーションの尺を短く作るのも大事。
最後に、初期値をもらって反映する関数。
トグルスイッチのWidgetは完成です。
次は管理とレイアウトの仕組みを作っていきます。
管理はプログラマでなくても扱える ストラクチャ (構造体)ってやつを使ってみます。
コンテンツブラウザから作成してエディットします。
New Variable ボタンをクリックして名前との型をセットします。
Saveして閉じます。
新しくレイアウト用のWidgetブループリントを用意します。
キャンバスに用意したトグルスイッチのWidgetを並べます。
Graph エディットに移行して、
変数を一つ用意します。
Variable Type を 先ほど作ったストラクチャ にします。
このストラクチャ型の変数をグラフに取り出したら、Breakノード経由で中の値にアクセスできます。今回はブーリアンばっかりですが、いろんな型をひとまとめに扱うこともできます。
値を取り出してキャンバスに並べたトグルスイッチのWidgetに渡します。
これで初期値とラベル名がセットされます。
ここで一旦画面で確認してみます。
このWidgetを保存してレベルブループリントから表示させます。
右の赤いブーリアン型のノードは、 エンジンに最初から用意されているパラメーター設定用のノードで、PlayerController から取り出します。
再生するとこんな感じ。項目の名前とストラクチャの初期値が反映できています。
仕上げにマウスイベント。
クリックしてスイッチを切り替える処理を用意します。
GraphエディタのFunctions 欄 にある Override ボタンを押して、
On Mouse Button Down というのを選択。
特殊な関数を編集できるようになります。
あらかじめ引数と戻り値のピンが設定されていて変更できない作りです。
Warningが出ているので、黙らせるには、Unhandledノードをつないで・・・
コンパイルすると鎮まります。
この関数は、自身のWidgetの上で、マウスのクリックが検出されたら呼び出されます。
白いラインの間にPrint Stringノードを入れて再生してみると分かります。高速でクリックしても反応しないのは、ダブルクリックと区別するためで、ダブルクリックはまた別の関数で処理することができます。
ここに、トグルスイッチのWidgetをクリックしているかどうかの判定と、スイッチの切り替えの処理を並べます。
今回スイッチが4つあって、全体はこんな感じです。
マウスクリック系のイベントはひとまとめにできます。
基本的に、クリックした瞬間、カーソルは誰の上に乗っていたのか?をチェック。
カーソルが乗っていたらどうするか? こうします。
ストラクチャ型の変数から取り出した値をひっくり返して入れ直します。
Not ノードを使うと、ブール値は反転します。
これで完成です。
今回はプログラマとデザイナのブループリント編集がなるべくぶつからないように安全に触れないかな、と考えながら試作しました。
基本的に
デザイナは見た目を担当。受け取った値を反映するとこまで。
プログラマは並んだ各パーツを管理してシステムの値と連携するところ。
今回のアセットをそれぞれで担当を分けるとしたらこんな感じです。
この辺を考慮してイベントディスパッチャーを使わない仕組みです。
UIパーツの方にマウスイベントの検出を入れると、イベントディスパッチャーを Call する処理と、管理側のWidgetBPからバインドが必要で、そこそこ連携しないといけないので、メンテナンスや調整のとき編集がバッティングしやすかったり、デザイナとプログラマとのコミュニケーションコストがそれなりにかかるのが予想されます。
あと、マウスイベント処理をある程度まとめてON・OFFしたり管理できないと、後から条件がややこしくなるので、それを避ける狙いもあります。
UIの見た目は自由にUIパーツの方で受け持って、設定画面を抜けるときに、UIパーツから最終の値を全て回収するという方法も考えられますが、UIパーツの状態を見てリアルタイムに反映させたい場合(サウンドのボリュームとか)は難しいし、値の回収を失敗する可能性を考えるとかえって手間がかかりそうです。
他にも効率的で安全な作り方がありそうですが、いったんこんな感じでいかがでしょうか?
トグルスイッチUIがたまたまいい感じになっただけだと思うので、もうしばらく検証していこうと思います。今までゲームパッドとかのコントローラ操作をメインに考えてきたので、マウスやタッチ操作にも馴染んでいかないとね。マルチプラットフォーム対応はUI泣かせですし。
ではでは
ステキなトグルスイッチライフを!
ぷちコン応募作品のプロジェクト公開について補足
台風24号がこちらに向かっている中記事を書いていますが、また停電は嫌なので遅筆ながら急いで書きます。
UE4ぷちコン第10回、残念ながら選に漏れてしまいました。サクッと作ってみてそこからネタ拾っていけたら記事にしよう、と考えていたのでそこはまぁまぁ達成できたのではないかと。結果発表後の動きで応募作品を公開されている方がいらっしゃるのを見て、私もソワソワしてきて公開することにしました。Twitterにはすでに呟いていたりします。
Puchi_MMAn_nin.7z - Google ドライブ
ファイルサイズは約44.5MB
UE4のVerは 4.20
作品内容についてはこちら↓
公開することについて
結果発表で触れられてもいないのに公開って、どんだけ自信ありまくり?という声が聞こえてきそうですが、内容についていくつかこのブログに載せているのと、その記事を読んで興味を持ってもらえたなら動くサンプルとして見ていただたらいいなと考えて、プロジェクトごと公開するに至った次第です。
なるべく見やすくノードを整理しているつもりですが、ごちゃついていたり、もっと最適化できそうな部分などが結構残ってます。ブループリントのようなビジュアルスクリプティングでは、テキストベースの共有ほど手軽じゃないので、ノード構成や最適化の研究って、直接触りながらの方がいいのかなと思ってたりします。
あくまでもブログ記事と連動したサンプルとして公開してます。UE4での動作確認は行っていますが、プロジェクトデータによる問題等が生じた場合は、Epicではなく当ブログへコメントいただくか、Twitterアカウント @MMAn_nin の方へご連絡いただけると対応します。
わりと簡単にプロジェクトを切り出して「ほい」って上げられるのは嬉しいのですが、いくつか気になったことがあって、急ぎ上げ直すことになりました。その理由としては以下。
ソースパスが見えちゃう
エンジンにインポートする素材は修正したらすぐに手軽に再インポートできるので、こまごまとブラッシュアップするには大変手軽で嬉しい機能ですが、参照している場所の情報「ソースパス」を晒すことになります。
特別に意識して対策していないと、Windowsのログオン名がファイルのパスに含まれてたりするので、うっかり、というのがあります。家だと趣味丸出しの厨二ワードだったり会社だと何かしらの管理IDだったりするかもです。
この辺、外に切り出す際に、受け渡しに不要なフォルダ(Developersとか、IntermediateとかStarterContent)をフィルタリングしたり、ソースパスを偽装するかNullにするようなツールか何かがあるとありがたいと思うのですが、どこかにあるのかな?。
余談ですが、Unityはエディタ内に再インポートの仕組みがないのは、この辺の情報を抱えたくなかったからなのか、という気がしてる。
再配布可能かどうかの確認が必要
プロジェクトデータに限った話ではないですが、外部のフリー素材を使っているコンテンツを公開する場合、あらかじめ利用規約はしっかり理解しておく必要があります。提供元によっては許諾範囲に制限があることがあります。 製品だと、クレジット画面などで表記する機会が用意できますが、プロジェクトデータの状態でとなると、触れる機会が無いので、ひとまずといった対応ですが、著作権表示および提供元サイトへの誘導としてサイト名とURLを書いたテキストファイルを一緒にアップしました。ファイルの場所は プロジェクトフォルダの直下です。
確実に目に留まる場所ではないので、その対策のひとつとしてこの記事を書いています。
今回の公開データで素材が気に入った場合は、ぜひぜひステキな素材提供サイトをご利用ください。
《BGM》
《SE》
作品について
今回ゲームとしてワクワクするところのないアイデアだったので、次回はもっと盛り上がれる要素を入れていけたらいいなと思ったりしています。ちゃんと動くものを作ろうという点ではそれなりにカタチにできたので満足しています。
ボリューム的にいろんなものを短期間で用意しないといけないので、サクッと作れるようにもっと使いこなさなければ、と決意を新たにしました。とはいえ範囲が広すぎるので、気になるところから地道に触っていくしかできませんけどね。参加できる限り参加していって、提供できそうなネタがあれば記事にしていこうと思います。
おっと、ずいぶん台風が迫ってきました。今回も影響が大きそうなので、身の安全を第一に過ぎ去るのを待つことにします。明日みなさまが青空を無事に迎えられますように。
私は月曜の朝から胃カメラの予約があるので憂鬱ですw
ではでは
ステキなUE4ライフを!
HorizontalBoxの中の表示間隔を調整する
今回応募したぷちコン作品では、プレイヤーを操作して、浮いているタイルを踏んだ数をカウントしてスコアに加算しています。リザルト画面で表示する際に、ただ数字を書くだけだと面白くないし、「いっぱい踏んだなぁ」「あまり踏めてないなぁ」という実感を噛みしめるためには、ビジュアルで示すのが効果的です。このとき踏む数が想定よりも多かった場合にスキマを詰める処理を実装しました。今回の記事は事例紹介的な感じで書いていきます。
10個までは一定の間隔で並べて↑
10個以上になると数に応じて詰めます↓
細かい仕様ですが、0個の時は半透明にしてます。
ちなみにこの文字の種類による点数差は「屈辱点」てやつで、昔のアーケードゲームでよく見かけた仕様を取り入れてみました。点数獲得の単位が100点刻みだと、普通にプレイすると10の位まで0が並びます。ところがランキング画面(今だとLearderboardsっていう方が通るかな)をよく見ると、1の位に数字が入っていたりします。これは「コンティニューしてる・・・金の力でクリアしやがった」、百以下が 00だと、「お!ノーコンクリア!すげぇ!」となるやつです。コンティニューすると1点もらえる!って素直に喜んじゃいけないのです。
実はランキング画面も作るつもりだったのですが、ステージのタイルがランダムだとスコアを競う気がなくなるのでやめました。高得点のルートが決まるのも面白くないのでランダムは残すことにした結果、ただの運ゲーになってますw
ちょっと脱線してしまいましたが、Widgetの中身について書いていきます。
キャンバスにはキャラを並べる HorizontalBox と スコア用の TextBlock を並べています。
テクスチャは 64x64が4枚。
これらを、配列に収納してしまいます。
配列に入れておくと、Index番号で扱えるようになるので、ある程度機械的に処理できて便利です。後から要素を追加しても同じように処理できます。また、アナログ的に並んでいるものでも順序を決めて管理できるのも魅力です。
上の図では、Add to Viewport するたびに走るので、Do Once ノードを入れています。リザルト画面が終わったら、Remove from Parent していますが、描画されなくてもメモリには残すようにしているためです。
この配列たちは一度作ったら、中身が変化せず、繰り返し使用するタイプなので破棄する必要がないのも理由のひとつです。
配列の準備ができたので、次はスコアをセットしていく部分です。
別の関数ですでに「ぷ」「ぶ」「ち」「さ」のそれぞれの個数を受け取っています。
それを↓この配列に保持してあります。
この配列の中身を ForEachLoop ノードで取り出しながら処理していきます。
真ん中付近にいる Pure型の関数 が表示間隔を計算する関数です。
並べる文字のテクスチャサイズは 64x64です。 それが10個で640という幅が、表示限界幅とします。
まず 引数(Inputs)で受け取った個数で、表示限界幅 640 を割ります。
640 ÷ 個数 で1個あたりの幅が計算できます。そこから 64 を引いた値が、基本の表示間隔 = 64 に対しての補正値となります。
なぜこんな補正値が必要かというと・・・
例えば、 11個だった場合。
640 ÷ 11 で、58.1818181818・・・ となるので、1個が 58.181818・・・の間隔で並べばいいのです。
ところが、HorizontalBox は追加した 子パーツ の幅をそのまま維持して並べようとします。58.18181818・・・ずつ、といっても聞いてくれません。そこで、SetPadding ノードを使います。
ここに渡す値を用意するために、もう少し計算が必要です。
58.1818181818・・・からさらに 64を引くと、
-5.81818181818・・・・ となります。(符号はマイナスになりましたが、小数点が一つ動いただけに見えて面白い結果ですね。)
これで補正値が計算できました。
この補正値を HorizontalBox にテクスチャ文字を追加する際にPaddingの値として渡せばいいのです。
その仕事をするマクロがあります。
先の ForEachLoop で処理していたところの、真ん中付近、少し上と右に計2つあります。
このマクロの中身はこんなかんじ。
HorizontalBox に追加した 子パーツ に対して(正確にはAdd Child ノードの戻り値)、HorizontalBoxSlot にキャスト(型変換)すると、SetPadding ノードがつながります。その In Padding Right に補正値を入れてやると、次に追加された子パーツ がその補正された場所に追加される仕組みです。
ついでに個数が ゼロ だった時の処理も入っています。
実際に 8、9、10、11 個の場合の表示はこうなります。
状況に合わせて動的に子要素を追加する場合、エディタで事前に予測して調整できればいいのですが、臨機応変な対応が必要となるとブループリントでどうにかすることになります。SetPaddingノードを使うと表示間隔を自在に調整できるので、今回の仕様が実装できました。VerticalBoxでも同様のことができます。
ではでは
今回はここまで
ステキな表示間隔ライフを!
追加するとスクロールするやつ
今回のぷちコンで作ったUIの中から、スクロールするリストについて書こうと思います。
ゲーム内でグレイマンがタイルを踏むたびに、画面下部のリストに文字が追加されていきます。画面いっぱいになると、文字が追加されるたびに、一文字ぶん左にスクロールアウトするという仕様です。
実は結構ハマった部分なので、ちょっと難しい説明が出てきたりしますが、ひとまず中身について書いていきます。
この仕様のためのWidgetは2つ。文字を指示通りに切り替えるだけのWidgetと並べてスクロールさせる表示用のWidgetです。
制限時間のあるゲームとはいえ、追加し続けるとメモリを圧迫するのと、HorizontalBoxのサイズが大きくなりすぎて不具合が出たりしても怖いので、スクロールさせつつ端から文字を消していってます。こうすることでいくらでも足していけます。
今回難しかったのは、この消しつつスクロールする処理の 『タイミング』 です。
流れを図にするとこんな感じ。
さっそく一つ目のWidgetから見ていきます。
キャンバスには文字と▼を配置。
今回文字については、ステージに並べたタイルで使っているテクスチャ(1024x1024)を使い回してるので、赤い × は別のテクスチャにしました。×はこのWidget専用でそんなに解像度も必要ないので、あらかじめキャンバスに置いておいて、文字とスイッチすることにしました。
テクスチャはこんな感じ。アルファチャンネルを合成表示してるので、ピンクのところは抜きになります。
マテリアルでトリミングして差し替えるようにしています。
WIdgetブループリントのキャンバスの配置は以上で、 EventGraph はというと、Event Construct のみ。
Event Construct はカスタムイベントと違って(ビルトインというやつになるのかな)引数を設定することができません。なので、Int型の変数を一つ用意してExpose on Spawnの設定にします。このときInstance Editable も一緒にチェック付けないとあとで注意されます。
この変数が文字の種類を数字で受け取ります。
数字と文字の対応はこんな風。
-1= ×
0 = ぷ
1 = ぶ
2 = ち
3 = さ
0以下(ようするにマイナス)かどうかで、表示を切り替えています。
これでこのWidgetは完成です。
次は並べる方のWidget。
まずはキャンバスから。
HorizontalBox を配置するのですが、クリッピング(見える範囲を限定する)のためにCanvasPanel の子供にしています。
この CanvasPanel の Clipping 設定を、Clip to Bounds に変更します。
HorizontalBox の方は Inherit のままでOK.
これで CanvasPanel の外には何も表示されなくなります。
HorizontalBox の設定はこんな感じ。アンカーは左上。
この HorizontalBox のアニメーションを用意しています。
0.25秒で、-90移動させています。これは一文字ぶんの長さから。
再生するとこうなります。
CanvasPanel の外にはみ出させています。
キャンバスはこれで完成。次はブループリントの編集。
外から追加のリクエストをもらう形なので、カスタムイベントからつないでいきます。
Create Widget ノードに、先に用意しておいた 文字のWidgetをセットしています。
その戻り値(Return Value)を、HorizontalBox に Add Child すると自動的に横方向に並べてくれます。
EventTickで スクロール処理中(isScrollingフラグ)かどうかをチェックして、スクロール処理していなければ、HorizontalBox が抱えている子供の数をチェックして一定数(今回は20個)を越えていればスクロールの処理を行います。
上図の続き
スクロールしたら、Remove Child して、ポジションリセット
するだけで いけるかと思ったんだけど、タイミング的な問題で アニメーションの再生を止める処理を追加しました。ここが今回てこずった部分です。
最初原因が判らずいろいろ調べていたのですが、どうやらアニメーションの終了時間を取得してタイマーを動かしているのですが、その時点でまだアニメーションが終了しきっていないことがあるのです。アニメーションが終了しないまま、ポジションリセットを行って、アニメーションで上書きされて・・・ということが起こっていました。正常にタイマーと同期がとれるのは10回のうち1回あるかないかです。
アニメーションとブループリントとで同じプロパティをいじっているから、このようなバッティングが発生しやすいのです。といっても数フレームでのタッチの差みたいなものなので、強制的にアニメーションを止めても、見た目にはわからない程度だというのもあり、上のような処理で落ち着きました。
とりあえず以上で完成です。
説明用のサンプルを動かしてみたのがこれ。
ちなみに、EventTrackで
スクロール終了イベントを呼ぶようにしてみたけど、アニメーションの終了よりも、EventTrackの方が先に動くようなので、バッティングは避けられないようです。
ここからちょっと難しい話。
アニメーションが完全に終了してから、次が呼ばれるのが理想ですが、プレイヤーのアクションによって動作する部分なので予測が難しいです。イベントドリブンな設計では、いつどのようなタイミングで呼ばれても問題なく動くようにしておくのが大事だなと改めて思いました。
当たり前ですが、アニメーションには『時間』があります。想定の時間より短い間隔で呼びだされると、アニメーションが衝突することになります。
UMGの Play Animation ノードは、実行すると必ずタイムラインの先頭から再生します。また、Set Timer by Event ノードも 再実行すると、残り時間が再セットされます。
なので、呼び出されたイベントで、
文字追加 → アニメーション再生 → 終わったら1文字削除とポジションリセット
としていると、短い間に何度も実行した場合に、呼び出された回数と一致しなくなるのです。
文字追加 は確実に処理されてるのに、 アニメーション再生 は再セットされてタイマー延長、という状態に陥るのです。
例えば3回立て続けにイベントが呼ばれると、追加処理は3回、3文字追加されますが、アニメーションは1回です。
演出が終わっていない間に受け付けたものはどう処理するかをよく検討する必要があります。と言っても選択肢はあまりないですが、
- 無視する(先のやつ優先で後のやつ以降はなかったことにする)
- 先のやつを止めて、改めて後からのやつを処理(後のやつ優先)
- 一時的にスタックしておいて順次処理してゆく
- 追加分として一緒にまとめる
くらいでしょうか。
UI的に見せ方や重要度によって選択することになります。今回は3つ目のスタックするやつを実装しています。これは、文字を追加するリクエスト処理と、スクロールさせる処理を別々にすることで可能になりました。
追加するだけさせておいて、EventTickでいつ追加されるかのタイミングに関係なく溢れが解消するまで、監視しつつスクロールを処理します。ゲーム的にある程度のリアルタイム性は維持したいので、アニメーションの尺を短かくしています。
今回の台風はなかなかでした。結局7時間ほどで復電しましたが、記事を書いている途中でも何度か瞬間的な停電があって、ノートPCさまさまと思ってたのですが、ルータが落ちたら記事の保存ができなくなるので、結局おとなしく嵐が過ぎるのを待つことに。夏場の長時間停電で暑いわ暗いわ冷蔵庫の中身は心配だわ、で結構精神的に削られました。電気って素晴らしいですね!
ではでは
今回はこの辺で。
ステキなUMGアニメーションライフを!
サウンドの再生管理にもEnumを使ってみた
途中経過はいくらかTwitterで公開していぷちコン作品ですが、今日ようやく応募できました。一段落したのでブログの更新ペースが上がるはず。自信ないけど・・・。
応募の条件としてYoutubeとかニコ動とかに動画をアップするというのがあるので、応募に使った動画のリンクを貼っておきます。
UI周りは今まで記事にしてきたネタを復習してみた感じなので、目新しいネタが無いですが、もし、「コレどうなってんの?」みたいなのあれば、コメントなど頂けるとブループリント晒しながら「これこのようになっておりまする。」と記事にさせていただきたく思う所存。
さてさて
今回の記事は、UIじゃないんですが「試してみたら以外に良かった」やつです。
サウンド関係については、完全に素人なので、手探りでの実装になったのですが、今のところお気に入りということで記事にして晒します。もっとベストな方法や、問題箇所の指摘などあればぜひ、ツッコミいただけるとありがたいです。
まず Enum を作るところから。
コンテンツブラウザの空いているところを右クリックして、
Blueprints > Enumeration を選択します。
適当に名前を付けて中身を編集します。
今回は2曲しか使ってませんが数が増えたらここで増やせます。
次に、サウンド用ブループリント。
コンテンツブラウザで右クリックして、
Blueprint Class > AmbientSound
これを編集します。
ブループリントエディタが開いたら、ポーズ対策。
ClassDefaultから
Detailsタブの Actor Tick にある Tick Even when Paused にチェックを付けます。
これで、ゲームがポーズ中でもこのブループリントの Event Tick が止まることはなくなります。
この辺りはこちらのブログが参考になりました。
では、EventGraphへいざ。
鳴らしたいBGMのキューをSoundBase型の配列にセットします。
こういった変数の《型》はどうやって調べるかというと、BGMを再生させる場合はPlaySound2D という再生用のノードが用意されていて、そいつの入力ピンから変数に昇格させると簡単に用意できます。
あとは、詳細タブ(Details)から配列に変更したりできるようになります。
曲を再生するための関数を用意しました。
このブループリントには最初から AudioComponent が用意されているので、そのコンポーネントに対して再生の指示をします。がその前にどの曲を?となるので、上図のような構成になります。
関数の引数(Inputs)がポイントなのですが、上の青いのは AudioComponent型、下はEnum型です。ピンを追加して型を検索すると、先に用意しておいたEnumが見つかります。
Enum型は名前(表示名)で扱ったり、Byte型という値(ほぼ数字)で扱ったりできる特殊なやつです。Int型に変換(キャスト)して使うと、 0~255 の正の数が扱えます。
曲を配列から選んで再生できる関数が用意できました。
ちなみにこの関数はこのブループリント内で利用します。
次に外から呼び出してもらうための各種イベントを3つ用意しています。
ポーズ中でも、曲の再生状態をチェックしたいので、
Event Tick を↓のような感じにしてます。
再生中かどうかのチェックは、常にやりたいわけではないので、状況に応じて開け閉めできる Gate というノードを使用。
誰かがチェックするぜい! となったら、左にあるカスタムイベント wait を呼び出して ゲートを開けます。
すると Tickのパルスが流れ始めるので、今なってる曲が止まるまでチェックし続けます。曲が止まったらすぐにゲートを閉じてやらないと、Falseの先の処理が何度も走ってしまいます。
右端のは再生用に用意した関数です。
Enumを変数化しておいてつないでいます。中は鳴らしたい曲が保持されています。
次に、再生指示を受け取るイベント。
まず再生したい曲をEnum型で受け取って変数に入れています。
Branchが2個もあってちょっとややこしい感じですが、機能としては3つ。
- 何か曲が鳴ってなければ指定された曲を即再生
- 何か曲が鳴ってたらそれを止めて指定された曲を再生
- 何か曲が鳴ってたら、その曲が止まるのを待って指定された曲を再生
2つ目の Branch は イベントノードの 引数で分岐するようにしています。
最後は、フェードアウトするイベント。このノードは結構便利かも。
これで最低限の仕様を持ったBGM再生用のブループリントが完成。
あとはこれを、ワールドにスポーンさせて、中のイベントを呼び出すだけです。
おっと、その前にAudioComonento の設定を確認。
コンポーネントリストから選択して、詳細タブをチェック。
ポーズ中にも再生できるように is UISound にチェックを付けます。
念のため Auto Activate のチェックを外しておきます。
この辺りはこちらのブログを参考にさせていただきました。
UE4 サウンドキューを配置して再生する(Audio Component) 凛(kagring)のUE4とUnityとQt勉強中ブログ
曲を再生するとこはどうなってるかというと、あらかじめスポーンしたのを変数化しておいて、
適当なタイミングでイベントを呼び出します。
下は、タイムアップになったので、曲をフェードアウトさせて、曲が停止したらリザルトの曲を流しなさい。というものです。
右端のやつは、別のEnumですが、Enumで扱うとアセットの名前とかじゃなくで、シチュエーションごとの名前とかで指示できるので、全体のフローを制御するときに分かりやすくなると思います。とりあえず「リザルト」の曲流しておいてよ。という指示でOKなのはとても気楽にセットできて安心できます。
実際に流す曲自体は曲の管理を担当するメンバーが、このブループリントとEnumを編集すればいいので、それなりに分業もしやすいのではと思います。
あと、ランダムにしたり、一定の条件で曲を変化させたりといったことも、サウンドブループリントの方をいじればOK.。今回は曲の配列とEnumの順番を一致させていますが、一致させない場合は配列を使わなくても問題ないです。
個人的に結構気に入ってるこの仕組みいかがでしょうか。
もっとナイスなベストプラクティスとかありそうですが、ひとまずこれを推してみます。
今回ぷちコン作品を作るにあたっていろんなブログ様に助けていただきました。この場を借りてお礼申し上げます。ありがとうございました。
ではでは
ステキなBGMライフを!