UMGパーティクル(なんちゃって)
Widgetでパーティクルっぽいやつを作ったのでTwitterに動画をあげたら、いいねしてもらえたので記事にしてみます。さくっとParticleをWidgetに乗せる方法が見つけられなかったのでUMGで作ってみた次第です。
変数の型に Box2Dというのがありまして、
これを配列にして管理したらいいんじゃね?ってなったのが作り始めたきっかけ。
BoxなんでRectangle(長方形)の4頂点のうちの対角の2点を扱うための型です。
2D座標ということで xとy を扱うVector2D が2つ束ねられているかたちです。
今回これをFloat型が4つあるという認識で扱うことにしました。
ちなみに 他に (x, y, z)の Vector型で2点をまとめた Box型や、BoxSphereBounds型というのもあるようです。ピンの色的にStructure(構造体)の類ですよね。
パーティクル用に格納する値は3つ。最終回転角と目的地の座標です。
ここに粒ごとのゴール位置と何回転するかの値を入れていきます。
Construct Object From Classノードで Widgetの Image を量産して配列に格納。ついでにパーティクルのパラメータを設定。右端のマクロの中身はこんな感じ。
発生源から ±500(50.0刻み)の範囲がゴール位置。±3 ~ ±7 回転。
次にキャンバスに追加。
OffsetPos と、 Color はこのパーティクルWidgetを呼び出すやつがセットするので、受け取るための設定をします。
パーティクルが発生してから、2秒後に消滅するようにタイマーをセット。Tickのためのフラグを立てて、フェードアニメーションを呼び出します。
フェードアニメーションは、キャンバスに対して Render Oapicty を 1.0 → 0.0 にすることでパーティクルを一斉に消します。
移動処理のTickは以下。
一粒ずつ処理します。ポジションと回転は、WidgetのRenderTransform を使います。それぞれの更新に必要な計算はマクロでやってます。
getNewTranslation
getNewAngle
これでパーティクルのWidgetは完成。
あとは呼び出すだけ。
クリックしてヒットしたときの処理に追加します。
パーティクルのカラーは数字の色同様に、あらかじめ配列で持っておきます。
できあがりです。
一応動画を確認するためのツイートを貼っておきます。
クリックゲームにエフェクト付けてみた。UMGでなんちゃってパーティクルを再現。需要あるかな pic.twitter.com/lVcuvE3SDa
— みつまめ杏仁 (@MMAn_nin) February 13, 2019
パラメータを工夫すると散り加減が変えられたりして楽しめます。
テクスチャを貼るとまた違った印象になると思います。
ではでは
今回はこの辺で
ステキなUMGパーティクルライフを!
クリックするだけの簡単なゲーム 《改造編》
さっそく前回作ったやつを改造していこうと思います。
改造ポイントは4つ。
- 繰り返し遊べるようにする
- タイマーのガクガク震えるのをなんとかする
- ちょっと難易度を上げてみる
- おまけ
まずは、クリアしたあとにボタンを表示したいので、キャンバスにButtonを追加します。作り方は開始時の Click To Start ボタンと同じ作りでいきます。
配置したら、Visibility を Collapsed にして非表示にしておきます。
このボタンを表示する処理をクリア時の処理に追加します。
ボタンが表示された次は、ボタンがクリックされた際の処理です。
エディタの Variables の欄に、Buttonが追加されているので選択して Details タブを更新します。
Events の項目に On Clicked があるのでクリックしてイベントノードを取り出します。
イベントノードを取り出したら、ひとまずボタンは用済みなので再び非表示に戻してやります。合わせて Complete! の文字も消します。
この後、いろいろと初期化と再セットをしていくのですが、一旦寄り道して、タイマーの改造を進めます。とはいえちょっとブループリントが賑やかになってきたので、改造というよりは新しくタイマー用のWidgetを作ることにします。
いつものようにキャンバスにパーツを配置していくのですが、Epicがストアで無料で提供してくれているプロジェクトを見ていて最近学習した方法があるのでそれでいきます。今頃ですが・・・
キャンバスのサイズはでデフォルトだと 1080pだったりしてPC環境やコンソールのゲーム機だとそのままで良かったりで、あまり気にしてなかったのですが、Widgetで部品パーツを作る際はなんかもったいないような気がしてました。
この右上の Fill Screen を 変更できることができます。
固定サイズなら、Custom、 中身次第にするなら Desired に切り替えるだけ。
タイマーはデジタル的に表示したいのでTextBlockをキャンバスパネルの子供にして表示桁ぶん並べます。
Desired にしていると キャンバスのサイズは自動的に合わせられます。
ピリオド以外は Is Variable のチェックを付けておきます。
ブループリントを編集します。
小数点の値を受け取って桁を分解してテキストを更新する関数を用意します。
桁の分解は、小数点の扱いがポイントになります。Fraction で整数部分を、Floor で小数以下を切り捨てることで、小数点を境界にして大きく2つに分けることができます。 あとはそれぞれで整数にして計算します。
図で解説してる過去記事を貼っておきます。
これで、数字の文字幅によるブルブルがなくなります。
この関数は外から呼ばれますが、初期値のセットも兼ねて、グラフにつないでおきます。
タイマーは完成。
メインのWidgetに戻って、配置します。
もとのTextBlockで簡易的に作ったタイマーをこのWidgetに差し替えます。
中央揃えの場合、Size To Content にチェックを付けるとレイアウトしやすいです。
タイマーの更新処理のところも、このWidgetの関数に置き換えます。
100以上の桁表示を作っていないので、99.99でカンストするようにしています。表示は見た目に止まりますが変数へのカウントアップは止めていないので、必要であればもう一つフラグを用意した方がよいかも。数字の桁を足す場合、 000.00 となるので、ゼロサプレスの処理が欲しくなる。この辺りは、ランキングの仕組みや、100秒を上回るのが当たり前のような難易度かどうかなどを考えると決まってくると思います。
で、寄り道で中断していたRetryボタンの処理の続きです。
変数を初期値に戻します。このあと難易度を上げるところで理由を書きますが、ここで StepCount の値を 1始まりではなく 0 にします。
あとは、出現イベントから始めれば、通常のゲームループに戻れるはずです。
そのためのカスタムイベントを作って、出現処理のところに脇からつなぎます。
このカスタムイベントを呼び出すのが、さきほどの ForEachLoop処理の Completeピンです。
これで大体できました。仕上げに難易度を上げる処理です。
その準備として、Integer型の配列変数をもう一つ追加します。
次に、先に作っていた NumberPool という Int型の配列も少し初期化の部分を変更します。Event Pre Construct の部分に、配列のサイズを設定するノードを挿入。
数字を初期化する関数を改造します。
まず、前回と同じく 1~25 までの数字で遊ぶ場合。
先に配列のサイズを決めているので、ここでは Addノードは使えません。代わりにSet Array Elem という、指定した 場所の内容を書き変えるノードを使います。
今回新しく追加したチェックのための配列をクリアしてから、シャッフルする前の状態を保存しておきます。このクリアは、すぐあとのコピーのために行っていて繰り返し遊ぶときに重要になります。上のようなシンプルなパターンでは、効果が薄いのですが、効果を発揮するのが次のパターン。
もっと難しくする場合。
1~99 までの 25個の数字で遊びます。まんべん無く数字が使われるようにしたかったので25枚目のやつだけループ処理が終わってから乱数をセットしています。
このパターンだと1~25のように パネルの番号とクリックの順番が一致しなくなるので、チェックしやすくするためシャッフルする前の状態が必要になります。
そこで、チェックのところが、下のように変わります。
配列から値を順番に0から取り出してチェックしてゆくので、変数StepCountをゼロ始まりに変更しています。
これで改造ポイント1~3完了です。
再生して数字の並びを確認してみます。
一番小さい数字からクリックしていくのですが、これが結構難しくて、なかなか30秒切れなかった。
ここからはおまけです。
上に貼った動画ですでに入ってますが、マウスのホバー処理とエフェクトを出してみます。
クリックしたときにエフェクトを出せないかと実験してみたけど、ParticleSysytemは、Screen描画してるWidgetよりも手前に描画できないようなので、とりあえず数字がヒットしたタイミングでプリセットの爆発エフェクトをSpawn してみたら派手で面白かったのでそのまま動画を撮りました。
Spawn Emitter at Locationを一つ追加するだけ。(雑い・・・)
再生時のカメラの視界に入るように座標を調整してるだけ。カメラが動くと当然ズレますが何か? WidgetをWorldに置くか、エフェクトをRenderTargetTextureにしてWidgetで利用するしかない感じ?
次はホバーエフェクト。当然タッチオペレーションだと無意味なやつです。マウスオペレーションならではですね。動画キャプチャにマウスカーソルが写り込まないので作ってみました。
そのための仕込みとして、色を付けるための Image をキャンバスに追加します。
カラーは数字の色に合わせて5色用意したいのですが、カラーごとにアニメーションを作るのはナンセンス。というわけで間に1枚専用のパーツを追加して、Render Opacity でアニメーションさせよう、って腹です。カラーは数字の色と同様に配列を用意します。型はLinearColor型。
(Font は SlateColor なのは何か理由があるのかな?地味に不便な気がする。)
この配列からカラーを取り出してセットする処理を 数字を受け取る関数に追加します。
このあと、マウスカーソルが乗った時、離れた時、のイベントを用意するのですが、その前に、マウスカーソルが離れたとき用のアニメーションを作っておきます。
RenderOpacity が追加されたことで、Colorのフェードを触らなくていいのが嬉しいです。この RenderOpacity の初期値は 0.0 にしておきます。
あとは、マウスイベントをOverrideします。
長いプルダウンリストから、 On Mouse Enter と On Mouse Leave を選択します。
On Mouse Leave は自身の上にマウスカーソルが乗ってる状態から、離れた瞬間に呼び出されるイベントです。
ただ再生するだけでOK。
一方、On Mouse Enter は自身の上に マウスカーソルが乗った(入った)瞬間に呼び出されるイベントです。ここでようやく Render Opacity が 1.0 になります。
このイベントが呼び出されたときに、アニメーションが残っている可能性があるので、強制的に止めるようにしています。
これで、カーソルが乗った瞬間に Render Opacity が 1.0 になって、カーソルがはなれたら、0.0に向かってアニメーションするようになります。
このくらいでいいかな~と思ったのですが、もう一つ思いついたので作ってみました。
数字の配列を作る関数をちょっといじります。
0~25までの 26個の数字を配列に入れるのですが、25個までと決めてあるので、この関数内だけで使えるローカル変数を使います。
26個の数字から、一つだけランダムで抜いて25個したら完成です。
次に、数字パネルのWidgetに手を加えます。
こんなマクロを用意します。
もうお分かりですね。アルファベットバージョンです。
しかも、大文字小文字が混在です。A~Z 26文字のうち、1文字だけ足りないので、ジジ抜きのような微妙な緊張感も一緒に楽しめるというやつです。
で、このマクロを無理やり↓挿しこみます。
どんな感じかというと、
ほどよい難易度でなかなか新鮮です。
いかがだったでしょうか。ひとまず今回の改造はここまでにしておきます。
始まりから終わりまで一つのゲームルールができると、いろいろアイデアも出やすい気がします。リザルト画面とかランキングとかネームエントリーとか作ると、どんどんアプリっぽくなっていきそうです。お手付きをカウントしたり一定の間隔以内だったらコンボボーナスとか。一定時間ごとに「急いで!」とか急かしてみたり。ヒント的にパネルが揺れたりしてもいいかもしれない。ベルトコンベアになってても面白そう。もうルールを変えて、何らかのペアをクリックして消しいくのもいいかもしれない。
イマドキのUE4のトレンドとは逆行してる気がしないではないけど、まぁ楽しんで作れるのが何よりも一番大事だと思う。
ではでは
ステキはパネルクリックライフを!
クリックするだけの簡単なゲーム
前回の記事で、パラパラとパネルを出現させるやつで遊んでたら、ビンゴカード ぽい見た目になったりしたので、さらに調子に乗ってミニゲームにしてみた。もう10年以上前?かな、一度流行ったことがあって、それの再現です。
ルールは、1~25までの数字が書かれたパネルを、順にクリックして全部クリックするまでにかかった時間を競うというものです。あぁ アレか・・・という溜息が聞こえてきそうですが、わりと手軽?に作れたので記事にしてみようかと。ただ記事的にちょっとボリュームがあるかも・・・
作りはシンプルにいきます。作るWidgetは2つだけ。
まずは1個目のWidget。キャンバスから。
ここに 120x120の CanvasPanel を一つ。
このキャンバスに対して、Image(下敷き) と TextBlock(数字)、Image(カバー用)を子供に します。
ここまでは前回と同じ。
このパーツたちに対してアニメーションを用意します。
今回用意したのは以下の4つ。
FADE_IN
最初の出現用です。この時点で数字はわかりません。
COVER_OUT
ゲーム開始時に 上の FADE_IN 再生後の状態から続く感じで再生します。インパクトを出すために青いカバーを白くします。
このタイミングで数字と下敷きを表示するのですが、ちょっとだけ仕込みが必要。
このあと数字を拡大して消すときに、隣のWidgetにかぶさるとクリック判定を邪魔してしまうのを防ぐために、TextBlockだけは Hit Test Invisible を選択します。
REMOVE_PANEL
クリックして順番が正解した際の演出です。数字だけを消しています。
FADE_OUT
全てのパネルを消した後の演出です。
キャンバスはここまで。
次はWidgetブループリント。
まずは変数を4つほど用意。
ちょっと色気を出すためにSlateColor型の変数を配列にします。
適当に5色をセット。
次に値を受け取る関数を用意します。
Integer型のInputピンを2つ。受け取った値は、後で別の場所でも利用するので、すぐに用意しておいた変数にお取り置きします。Indexはパネルの場所を表す値。左上が0、右下が24です。
このIndex番号を %5(5で割った余り) すると、下のようになります。
この計算した数値をフォントの文字カラーとして配列から引っぱり出しすと左の列から順に色が変化することになります。
Numberは表示するための数字です。
関数の次は、イベントディスパッチャーを2つ用意します。
アニメーションの終了通知と、クリックされた際の応答に使うためです。
応答用のは、値を送り返すのでInputピンを2つ追加します。
アニメーションを再生するためのカスタムイベントを用意していきます。
最初の登場演出用。
出現時、まだクリックを受け付けたくないので、フラグを False にしておきます。
※このフラグは後述のマウスイベントの処理のところで活躍します。
このアニメーションの後に、『 Click to Start 』って出したいのでアニメーション終了通知のために、イベントディスパッチャーを呼び出し(Call)ています。
このWidgetは画面に 25個も表示するので、通知処理は最後の1個だけで十分です。前の24個は通知処理しなくてもいいので、カスタムイベントのInputピンにBooleanを一つ追加しています。
ちなみに、このイベントを呼び出す際は下のようなノードになります。
次は、ゲーム開始のための表示演出イベント。
ここから、マウスクリックを受け付けるので、フラグを True にします。
次は、数字がヒットして、消えるときの表示演出イベント。
ここで マウスクリックを受け付けたくないのでフラグを False にします。
次のイベントは、ゲーム終了時の表示演出イベント。
これで4つのアニメーション再生の準備は整いました。
最後に、マウスクリック時の処理を用意します。
関数を作るところに Override ボタンがあるのでそこから
On Mouse Button Down を選択してから編集します。
クリック受付するかしないかのフラグをみて分岐させます。
これでパネル用Widgetが用意できました。
メインになる親のWidgetを用意します。
キャンバスには パネルを並べるための WrapBoxを一つ。
中央になるように調整します。
パネル1枚が 120x120なので、 120x5 + スキマ から指定幅を計算。指定幅を越えたら改行するので、6個目が並ばないように指定します。
Size To Content にチェック付けると、 Size XとSize Y は無意味なので初期値でOK。
次に、タイマー用の TextBlockを配置します。
とりあえず、WrapBoxのすぐ上あたりに。
次は、クリアしたときの「 Complete! 」をTextBlockで配置。
これはクリアしたときまで取っておくので、配置したらVisibilityの設定を非表示(Collapsed)にしておきます。
あとは、Click To Start のボタンを配置します。
覆うように大きく大胆に広げます。
配置したButtonの子供にTextBlockを置くと、中にテキストを置けます。
Hierarchyはこんな感じ。
このままだと後ろが見えないので、Buttonのカラーをカスタマイズします。
とりあえずカラーは 黒 でアルファが 0.5 くらいにします。
Buttonコンポーネントは専用のイベントをいくつか持っているので、Normal、Hovered、Pressed の3か所を触ります。
あくまでもボタンの地の色で、テキストブロックのカラーは変化しません。
Buttonを大きくするのは、数字のパネルがクリックを受け付けてしまうのを防ぐ目的もあります。
キャンバスはこの辺にして、ブループリントを編集します。
まずは、数字を配列にセットする関数。
配列のIndex番号は 0(ゼロ)始まりだけど、ゲームの表示は 1(イチ)からなので、ForLoopを使って、1~25 の数字を格納。
配列をシャッフルする関数。
配列にShuffleノードをつなぐだけで配列の中身をシャッフルしてくれます。
上の2つの関数を Event Pre Construct につないでおきます。
クリックしたパネルの数字が順番通りかどうかチェックするための変数もここで初期値をセットしておきます。
このイベントは 、ブループリントをコンパイルした時点で一度処理されるという特殊仕様です。結構便利な使い方ができるので後ほどご紹介します。
次は数字パネルのWidgetを並べる処理。
真ん中付近の配列の作り方は、
Create Widget ノードの ReturnValue ピンから Promote to Variable(変数へ昇格) して、一旦 "専用の型"の変数を作成します。
Variablesのリストに追加されているので、Detailsから配列に変更します。
この操作は、一旦グラフから取り除いてからやると、エラーチェックが走らないのでオススメ。つながった状態でやると警告されます。
で、数字パネルをWrapBoxに追加した後は、数字パネルを出現させます。
出現開始をする前に、アニメーション終了の通知が欲しいので最後の数字パネルにバインドを施しておきます。そして再び ForLoopを使って一斉に数字パネル全部に数字を渡しつつFADE_INのアニメーションを再生させる関数を呼び出しています。
その関数はこちら。
と、その前にカスタムイベントを準備。
数字パネルのバインドで送り返してもらう値がInteger型で2つあります。それを受けるのであらかじめこちらでも2つのInputピンを装備させます。このイベントは後から作っても大丈夫。でも少しだけ手数が増えます。その理由はこれ↓
関数の中にカスタムイベントを置くことはできないので、替わりに CreateEventノードをつなぎます。
バインドノード経由のイベントをつなぐと、Select Function というプルダウンリストが出てきます。そこでイベントディスパッチャーの値を渡せるイベントを指定するのですが、Inputピンの型と数が一致していないと、プルダウンに出てきてくれないのです。
そのカスタムイベントの続きはこんな感じになります。
クリックした数字パネルから応答が返ってきた値を比較して、同じなら成功なので、クリックした数字パネルの演出イベントを呼び出します。まだ数字パネルが残っているかどうかチェックして、残っていれば順番をチェックするカウンター用の変数を一つ進めます。
なんとか 25までクリックできたら、終了処理に移ります。続き。
Complete! のテキストを表示して、残ったパネルを消すイベントを呼びます。
そろそろ大詰め。ゲーム開始用のイベント。
編集モードをDesignerに変えて、Click To Start 用のButtonパーツを選択。
Detailsタブの一番下に、グラフにイベントを生成するボタンがあるので、On Clicked をクリック。
ここにゲーム開始の処理をつないでいきます。
最後にフラグを True にしていますが、これはタイマー表示に使います。
タイマーは ゼロから始まる~ のでEvent Tick で Float型の変数に加算していきます。
ここで便利なノードが、 FormatTextノード。
「単位」をくっつけたり、文章の途中に任意のテキストを挿入するといった使い方ができます。
詳細は公式サイトにあります→ ユーザーに表示するテキストの書式設定
こちらのブログエントリーもおすすめです→ UE4ワイルドカードを使った書式設定で文字列を作成する
イメージしやすそうな例で説明を書いてみます。
取り出した直後はこんな形。
ここにピンを増やすのですが、増やし方が変わっています。
例えば、こんな文章があったとします。
おおトンヌラよ、しんでしまうとは ふがいない。
この名前のところを、半角の中括弧(波括弧)でピン名を挟んだ形、{ ピン名 }に置き換えます。
おお{CharacterName}よ、しんでしまうとは ふがいない。
これを FormatText に入力してみると、こうなります。
新たに入力ピンが追加されます。
{ピン名} はいくつでも配置できます。
今回は、簡単に Float型の値に、 sec をくっつけています。
{Time}sec
これで一通りの用意ができました。
レベルブループリントからViewportに追加して、マウス制御用のノードをつないで、
再生してみます。
マウスカーソルがキャプチャできなかったのでちょっとわかりにくいかもですが、一通り遊べてます。
も少し整えないといけないところがあるのですが、そのあたりは次回に回そうと思います。
何度かチャレンジしてみて、なかなか12秒を切れなかった。パネルの大きさとかも影響するかもしれない。数字の色とか、フォントをいじるとまた違った難易度になると思います。一度つくってみるといろいろアレンジしたくなるので楽しいですね。
ではでは
ステキなパネルクリックライフを!
パラパラとアイテムが表示されるやつ
節分も過ぎて少しづつ暖かくなっていくのは嬉しい。今までこんな風に感じたことなかったのは、寒さに弱くなったせいかもしれない。
さてさて今回は、登場時に動きのあるUIを考えてみました。
グリッド状に並んだパネルが順にめくられていくやつ。
作りは結構単純です。
まずは、パネル1個分のWidgetを用意します。
表示する内容はパネルのWidgetが受け取るものによって変わります。今回はナンバー受け取る仕様で作りました。アイコンだったらテクスチャを受け取ってもいいです。
さっそくキャンバスから。
サイズは 120x120にしました。
このキャンバスに対して、Image(下敷き) と TextBlock(数字)、Image(カバー用)を子供に します。
これにアニメーションを付けます。
手前に表示するカバー用のImageが最大サイズになったら、その下に隠すように他のパーツを表示開始。
カバー用のImageは、Pivotを左上にしています。
キャンバスはこれで終わり。次はブループリント。
値を受け取ってキャンバスに反映させる関数を用意します。
つぎに、イベントディスパッチャーを用意します。
これはアニメーションの終わりを通知するためのものです。
つぎに、アニメーションを再生するイベント。
時間差を作るためにDelayノードに頑張ってもらいます。
こういったアニメーションなど演出に時間が存在する場合、ちゃんと終わってから操作を受け付けないとカッコ悪いUIになってしまいます。また、アニメーションの尺調整をしても長さに応じて自動で処理したいもの。そこで setTimerByEventノードを使って、アニメーションの終了タイミングで通知をします。とはいうものの、たくさんのパネルを動かすことになるので、このタイマーイベントを全てのパネルで実行するのは処理負荷になります。最後のパネルだけでいいので、やるやらないを分岐するためのブール値も受け取れるようにしました。ちょっと処理が増えましたが、このパネルのWidgetが汎用的に利用できるようになります。
これで、パネルのWidgetは完成です。
つぎにパネルを並べるための親Widgetを作っていきます。
キャンバスに Wrap Box を一つ配置します。
5x5 の 25枚を並べるので、Details タブ で調整します。
Explicit Wrap Width にチェックを付けると、指定した幅(Wrap Width)を越えないように改行してくれます。今回用意したパネルは幅が 120 なので、120x5=600
ここにパネル間のスキマ 2x4=8 を合わせて 610 にしています。
ではBlueprintでここにパネルを追加していきます。
ForLoopでパネルを生成しつつ、順次配列に追加していきます。配列に積んでおくことで、このForLoop処理を抜けた後でも扱うことができます。
この段階で、25枚の透明のパネルたちが、数値をもらって待機完了です。
パネルに待ち時間をセットする関数を用意します。
剰余(%)と除算(÷)を使って左上から右下に向かうように計算しています。
Index番号は左上 0 で始まって、右下に向かって増えていきます。
Index番号に対して、5の剰余を求めると、
一方の除算はというと、
整数の割り算は端数は切り捨てられます。
この2つを加算すると、
これに適当な小数を掛けて待ち時間にしてやるのです。
この関数をパネルの数ぶん呼び出すことになります。
カスタムイベントにしました。
全25枚のパネルのうち、24枚を ForLoop ノードで回して、最後のパネルだけ手動でやる感じ。ここでようやくイベントディスパッチャーをバインドします。
これで並べるWidgetは完成です。
Viewportに追加してテストしてみます。
レベルブループリントを編集。
再生して、ウィンドウをフォーカスした後、スペースキーを押すとこんな感じ。
剰余(%)のみだった場合。
除算(÷)のみの場合。
アニメーションでも見た目をアレンジできるので楽しいですね。
試しに数字をシャッフルすると・・・
なんだかBINGOカードに見えてきたので、BINGOカードにでもしてみようかな。
今回はこの辺で
ではでは
ステキなパネル出現ライフを!
ついでに丸い形のミニマップ表示を作ってみる
前回作ったミニマップ表示は Widgetの Pivot を使ったので、 今回マテリアルを使って試してみました。マテリアルだとマスクが使いやすいのでカタチを丸くしたりできま
す。でもマスクできるのはマテリアル内のテクスチャなので、マーカー等の目印やアイコンを置きにくいのが難点。Widgetのキャンバスにマスクが適用できればいいのですが・・・。
仕組みはシンプル。UVはそもそもが 0~1 なので Pivotと同じような座標で扱えます。前回作ったWidgetを複製して改造することにします。改めてリンクを貼っておきます。
今回の記事だけでも試せるように構成してみたつもりですが、細かい説明は端折っていたりするので、興味のある方は過去記事も覗いてみてください。
拡縮については、TexCoord の Tiling を変更します。ただ このTiling の扱いが、Scale と違うので少し計算が必要になります。
まずはマテリアルを新しく用意。
テクスチャを受け取る Texture Sample Prameter 2D と、Scalar Param が3つ。
それぞれUV範囲の大きさと位置を受け取ります。正方形を想定してるので、サイズに関してのパラメータは一つです。
まん丸のマップにしたいので、キャンバスパネルを正方形にします。
中の Image には 新しく用意したマテリアルをセットします。
今回マテリアルで拡縮するので サイズは固定です。
変数は以下を用意しました。上の4つはVector2D型です。
あとからここにもう一つ加えます。
setMapTexture
テクスチャを受け取る関数を改造します。
マテリアルを扱うので、Get Dynamic Materialノードで、マテリアル インスタンス ダイナミックを作って、変数化しています。計算用の値をここで準備しています。
キャンバスのサイズは、一旦キャストしないと参照できません。
最終的に決め打ちで固定の値をセットしてやればいいと思いますが、開発中はレイアウト調整で細かく変更したくなると思うので、ブループリントのことを気にせず触るようにするためのテクニックです。
updateTranslation
次はマップを任意の位置に移動させる関数。
XY,座標を受け取って、テクスチャのUVに変換しています。実際はもっと補正が必要だと思います。今回は左上が (0, 0) の座標空間で、マップの中心が テクスチャサイズの 1/2 を前提としてます。マップテクスチャの1ピクセルがワールド空間のどれくらいかによって補正を掛けることになるので、テクスチャのサイズ選びはビジュアルも含めて、UI設計が難しいお仕事の中でも上位レベルになります。
前回のはマウスで操作するので、用途としてはインベントリ画面とかポーズメニュー画面なんかでマップ確認というイメージを想定していました。
Pivot の計算は不要なので、関数 updatePivot は不要になりました。
替わりに、新しくマクロを作ります。マウスドラッグに追随する際の表示倍率に合わせた移動値を計算します。
このマクロは、ポジションを変更するイベントで使います。
ここからはマウスイベント用の関数を見ていきます。
OnMouseButtonDown
これはそのまま変更なしです。
OnMouseButtonUp
これもそのまま。
OnMouseMove
ここに、新しく作ったマクロを仕込みます。Pivotの更新する関数は外しました。
マクロにしなくてもいい気がしますが、コンパクトになるので。
OnMouseWheel
ホイールを転がしてスケールを変更する際の符号が逆になります。
テクスチャを切り出すときのサイズは、Tiling で指定します。表示サイズが変わらないので、値が小さいほど切り出す範囲が小さくなって、結果的に拡大することになります。
例えば 1024x1024のテクスチャがあったとします。表示サイズはちょうどその 1/2の 512x512だった場合、下図のようなイメージになります。
表示倍率が上がるほど、Tilingの値は小さくなっていきます。虫眼鏡で拡大するのと同じ要領ですね。
最後の関数
OnMouseLeave
EventGraphにおかれるやつ。
最後にあったPivot更新の関数は不要になりました。
これで、関数&イベントの改造は終了です。
仕上げに、 EventConstruct にあった Pivotの更新を、マテリアルへの更新に変えます。
マップの表示の縦横比が 1:1 の正方形用なので、Vector2D の持つ値を一つだけ使います。
これでWidgetは完成。
テスト用のレベルブループリントも少しだけ変更になります。
今回作ったWidget用だけにまとめるとこんな感じです。
テストしてみましょう。
うまく動いているようです。
丸い形をしているので、回転させることができます。
この形だとたいていはHUDに置かれるので、マウスで操作できなくてもよいですね。
ということでもう少し遊んでみます。
レベルブループリントに変数を一つ追加。スタート位置 兼 補正値です。
関数の前でセットしてそのまま関数に渡してます。
あとは、EventTick でカメラ位置と回転値を拾って、Widgetに渡します。
マップには、 Plane のメッシュを中央に置いて、XY方向に それぞれ100倍して、マップのテクスチャを貼りつけます。
もともと置いてあった Floor はこんなサイズ。
Plane を 100倍すると、端っこのポジションは ±5000 になります。1辺の長さが 10000 になるので、テクスチャサイズ 2048 を割ると、 0.2048 になります。取得したカメラポジションに対して掛けてから、Widgetに渡します。
あと、マップのWidgetには 中央に ▲ を置いておきます。
プレイしてみると・・・。
静止画ですが、いい感じにグリグリと連動して動いてます。
マップ部分だけGIF化しました。
FPSカメラなので、床から離れているのもあって見た目とマップ位置のギャップを感じますが、いろいろ補正を試してみると何か発見があるかもしれません。
TPSだと、キャラの向きとカメラの向きを反映させるために、カメラのFOVに合わせた扇形の図形を重ねたりします。
なんとか形になったので、今回はこの辺で。
ではでは
ステキなミニマップライフを!
マウスで操作できるミニマップ表示
2019年が明けました。公私共にいろいろ節目的な年になりそうですが、アンリアルエンジンを使ったUIの研究だけは続けていこうと思うので、今年も引き続きよろしくお願いいたします。
今回マウスで操作するミニマップを考えてみました。ふと、Pivotをちゃんと計算してやったらいけるんとちゃう? と思いついたのがきっかけです。考えたといってもごく普通のよく見かけるやつです。大事なのはUE4で作ってみること。UMGで作るとしたらどのように?という疑問に一つでも答えを見つけておきたいという使命感のようなものでやってます。需要があればいいのですが・・・。
仕様としては、
- 任意の長方形でクリッピング可能
- 見えているのはマップの一部分
- マップ上にマウスカーソルがあるときだけ操作可能
- ドラッグでスクロール
- ホイールで拡大縮小
です。
まずはキャンバスから。
新たに CanvasPanel を追加します。
クリッピングは この CanvasPanel で行うので、適当なサイズで用意。
上のサンプルでは何となく 640x480にしてます。
このCanvasPanelの Clipping 設定を、 Clip to Bounds に変更します。
これで、はみ出た部分を隠せます。
この CanvasPanel に2つの Image を子供として追加します。 下敷き と マップ画像 です。
マップ画像用のImageパーツには、Size to Content にチェックを付けておきます。
マップ画像によってサイズが変動しても対応できるようになります。
UMGは以上です。
Widgetブループリントを編集していきます。
まず用意した変数は以下。上段の4つは Vector2D型です。
関数を用意します。
マップテクスチャを受け取ってセット、そしてそのサイズを変数に格納するお仕事。
画像サイズを取得するために、 Match Size にチェックを付けます。
次は、マップImage のポジションを受け取って変数に格納。さらにマイナスにしてから反映するお仕事。変数の値は プラスのままで扱います
マップの画像はCanvasPanelの下では、左上が(0, 0)なので、マイナスの値にしてやる必要があります。
マイナスにしないといけないのは、あくまでもUMG側の表示都合なので、変数には加工しない状態で扱っておく方が考えやすいし、デバッグしやすいと思います。
次の関数は Pivot の計算と反映。
CanvasPanel=見えている範囲 の中心を基準に拡大縮小するので、Pivot 位置が常に変動します。Map画像のポジションに、計算して求めておいた OffsetPivot を足すと、CanvasPanel の中心に相当する場所が決まります。それを Map画像のサイズで割ると、Map画像における Pivot位置が新しく決定します。
OffsetPivot は下図のイメージ。
これとMap画像のポジションを足すと、 Map画像の Pivot 位置が 座標として判明します。
判明したPivot位置は 0.0 ~1.0 の範囲で扱わないといけないので、Map画像のサイズで割ってやります。テクスチャのUV値を求めるのと同じ考え方ですね。
ここからは、マウスの挙動に関する関数。
すでに用意されているので、Override して進めます。使うのは5つ。
長いリストメニューがポップするので、必要なやつを選択します。
OnMouseButtonDown
マウスのボタンが押下されると呼び出される関数。
ブランチノードで判定してるのは、押したのが 左ボタン で、且つキャンバスの上にカーソルが乗っているかどうかです。2つ以上の条件を同時に判定する場合は、AND Boolean ノードを使うとブランチノードが1つにまとめられるのでお得。
無事条件をクリアしたら、フラグを立てて、ボタンを押したポジションを変数に取り置きします。
is Mouse Button Down ノードを使うと、何のボタンが押されたかを調べることができます。 調べ方は、指定したボタン毎に問い合わせて確認することになります。今回は左ボタンのみの扱いなので、左ボタンについてだけ確認しています。
ちなみにマウスボタンは5つまで判定できるようです。最近のマウスは親指のあたりに 「進む」 と「 戻る」に相当する機能が振られたボタンが付いてることが多いです。
OnMouseButtonUp
マウスのボタンから指が離れたときに呼び出される関数。
マウスのボタンが解放されたということは、ドラッグが終わったということで、フラグを False に戻します。
OnMouseMove
マウスの移動を検出すると呼び出されます。
というのが一般的だと思うのですが、UE4の場合、この関数は常時走ってるようです。マウスから手を放しても呼び出されるので、人間が感知できないほどの微細な振動を拾ってるのか、マウスの仕様がそうなっているのか、UE4の仕様かはわかりません。
常時処理されるとそれなりに負荷になるので、前述の OnMouseButtonDown でフラグを立てた後、そのフラグが倒れるまでの間だけ処理するようにします。さらに、マウスが動いていないと判断された場合は何もしません。
作ってみて気づいたのですが、スケールが掛かっていると、座標の計算がおかしくなります。UMGがレンダリングする際の計算順序に依存します。またマウス座標の取得はViewportの解像度は関係なくモニタの解像度に依存します。なので、ちょっとややこしい計算して誤差がなくなるようにしています。
補正が無い場合、マウスカーソルに追随しません。
Map画像の表示スケールによってはさらに誤差がでます。
OnMouseWheel
ホイールを転がすと呼び出されます。と思いきや、これもコロコロしていない時でも常時呼ばれ続けます。
なので、CanvasPanelにマウスカーソルが乗っているのを条件にします。
最後の関数。
OnMouseLeave
これは先の4つとは違ってEventノードとして、グラフ上に置かれます。
マウスが自身(このWidget)から離れたら呼び出されます。
ドラッグ中に限り判定したいので、フラグが True の時だけ処理します。
このイベントは、ドラッグしたままMap画像をどこまでも持っていけてしまうのを防ぐのがお仕事です。
これで、関数&イベントの準備は終了です。
仕上げに、EventConstruct にPivot計算の関数をつないでおきます。
これでWidgetは完成です。
テスト用にレベルブループリントを ごにょごにょします。
Level Blueprint
まず、いつもの Create Widget ノードでWidgetブループリントを読み込んで、キャンバスに追加する流れですが、Add to Viewport する前に、マップのテクスチャと、初期座標をセットしておきます。今回用意したマップ画像は "1024x1024" と "2048x2048" の2枚。
この時点では、まだWidgetは目覚めていません。寝ながら関数が画像と初期位置を受け取ってます。まだレンダリングが開始されていない状態です。
なので、お目覚めの Add to Viewport を。
Viewport内のポジションを変更して、マウス操作用の設定をしておきます。
準備はこれで完了。
テストしてみます。
マシンスペックなのか、ちょっと遅れてついてきますが、ズレて止まらないので悪くはないと思う。
2枚並べて操作するとこんな感じ。
キャプチャのレートが 15fps なのと、リサイズしてるのでちょっと粗い動きですが。
2種類の全く違う絵柄で、”間違い” または ”同じとこ” 探しゲームが作れそう。
Angleを変更する関数も用意すれば、回転もできそうです。
Render Transform をいじくればOK。
マウスの操作部分をなくせば、普通にHUDのミニマップ表示としても使えそうです。
丸く抜くのはちょっと大変?
今回試してみて気づいたこと
Viewportに追加する前に、表示を開始する前にテクスチャを渡すことが可能なので、ダミーの超軽量テクスチャでWidgetブループリントを用意しておくことができます。これはアセットのサイズも小さくできるし、汎用性も上がります。また画像などの重いアセットで、ゲーム進行によって読み替えが起こるような場合も、ロード周りの管理をWidgetから切り離すことで安全性が上がります。ということでMap画像を外から渡してもらう仕様にしました。
ところが、このViewport追加の前に画像を受け取る方式で、ちょっと問題がありました。
Map画像を移動した後にPivot 位置を再計算しないと、拡縮の中心がおかしくなるので、移動用の関数の最後の方にPivot位置を再計算させていたのですが、Viewportに追加する前にPivotを計算しても、エンジンがテクスチャのサイズに対する認識を更新してくれていない様子。
Viewportに追加して画面に表示された直後に拡縮を行うと、え?という場所を中心にズームします。Viewportに追加した後に Pivot位置の再計算を行うと正常になります。なので今回 Event Constructのところで Pivot位置を計算することで一旦解決しました。
Widgetブループリントは Add to Viewport の前後で、処理の振る舞いが変わる感じなので、まだまだ研究の余地はありますね。
◇ ◇ ◇
今回は以上です。
ではでは
ステキなマップ操作ライフを!
クリックした場所にメニューを 3
リングメニューの表示がそれなりにカタチになったと思うけど、もう少しだけ作り込んでみます。リングメニューの呼び出し時にメニュー項目を任意のセットにできるようにして、閉じた後の通知部分を作ってなかったので追加します。あと選択中の見た目を強調するためのハイライト表示も追加します。
改造スタート
まずハイライト表示用のパーツを準備。リングの大きさをマテリアルで調整できる作りなので、同じ要領で専用のマテリアルを作ります。
マテリアルができたら、前回作ったWidgetのキャンバスに Image を追加して、このハイライト表示用のマテリアルをセット。
今回 表示のたびにアイコンを新しく生成して並べて、終わったら消すという作りを採用したので、メニューアイテム用(アイコンが並ぶ)の CanvasPanel を追加しておきます。これはアイコンをクリアして消す際に、Clear Children というノードを使うためです。
処理的に不都合があれば、あらかじめ最初に最大個数のImageを生成しておいて、ずっと使い続ける方法もあります。
キャンバスは以上です。
リングメニューを呼び出す際に、必要なアイテムをチョイスして呼び出せるようにします。呼び出す際に必要な情報として Integer型の配列変数を使ったやり取りです。
前回までのつくりは、Viewportに置いた時点で表示するメニューアイテムは固定になってて、あとは表示用のカスタムイベントが呼ばれるのを待っていました。メニューの数が表示のたびに変動する仕様にするので、定数を設定をやめます。なので、Event Pre Constructはシンプルになります。
Event Construct もスッキリです。
新しくセットアップするためのイベントを用意します。ここで各種変数の初期化を行います。前回 定数初期化としていた部分を持ってきます。
受け取った値を保持する配列変数と、ハイライト表示用の変数(Float)を追加してます。続きに前回 Event Construct につないでいた処理を持ってきますが、配列を扱うので、ForLoop ノード から ForEachLoopノードに切り替えてます。
Loop処理が終わったら、ハイライトをメニューアイテムの数に応じた大きさにするために、マテリアルに数値を渡します(囲み部分)。
リング1周ぶん(360°)が 1.0なので、アイテムの個数をで割ると割合が出ます。
上図の続き。
ForEachLoop ノードを使うと、配列の番号(Index)と中身(Element)が同時に取り出せるので便利です。
随分大きくなりました。
次は、Event Tick
ハイライト表示パーツを回転させる処理を間に追加。
以下は続き。
ここも ForEachLoop に差し替えました。
表示が終わると、次に備えて片付けが必要になります。
退出のイベントを編集していきます。出現と同じくBranchノードを追加します。
前回忘れていたわけではないのですが、ボタンの入力検出が必ず
押した(Pressed) → 離した(Release) になる前提で考えてました。 後でいろいろ実験しているうちに問題が発覚。コンテキストメニュー(Windowsの右クリック)みたいにしてみようと思って、メニューを出せる場所と出せない場所を意図的に作ったら、出現イベントはキャンセルできても退出のイベントが走ってしまったので、Branchノードで、リングが出現していたら、という条件をここに追加することで解決できました。
リングを消したあとの処理を追加します。
まず退出のアニメーションにイベントトラック追加して、最終フレームで関数を呼び出します。
まず、 +Trackボタン > Event Track してトラックを追加したら、再生ヘッダーを最終フレームに移動させて、Add key(←◎→ の◎をクリック)すると、あまり目立たない色でマーカーが打たれます。
そこで 右クリック > Proparties > Event のプルダウンを Unbound から関数を選択しますが、まだ用意してないので Create New Endpoint に切り替えると、関数が作られます。キーマーカーをダブルクリックしても関数は作られます。
見た目に関数と同じ色なのに、イベントっぽい扱いのようです。
ここでフラグを戻して通知してから配列変数の後片付けと、キャンバスの掃除です。
「変数」は一つの《値》しか持てなくて、内容が変化してもメモリの使用量は変わりません。一方「配列変数」は複数の《値》を持つことができる上に、その数が変動するので、当然メモリの占有量が増減します。Addノードを使うと配列変数の《値》を動的に増やすことができるので、気をつけて扱わないとメモリを食いつぶしてしまっているということが起こります。使い終わったらクリアしておきます。あと、ForEachLoop とか、Length ノードで、配列変数の持っている《値》の数を利用するので、次に備えて適宜リセットしないと計算があわなくなります。
イベントディスパッチャーは最後にハイライトしていたメニューアイテムのIndex番号を通知するのが目的です。Inputsに通知したい値の型を登録して使います。
これで一通り改造終了です。
動かしてみます。
いい感じになってきました。
あとは選択結果を目立たせる演出を入れるか、Disable状態なアイテムの対応といったところでしょうか。
今回ここまで作ってきて、マウスのようなポインティングデバイスのUIは難しい、と改めて思いました。
ゲームパッドのような方向キーを使ってカーソルを移動させるタイプUIは、カーソルの表示されている位置が、そのままモードや状態を明示していて内部的にもモード遷移のコントロールがしやすい。それに比べてマウスなどタッチオペレーションとなると、モード遷移が一方向ではないので、状態の維持管理や連携の難易度がはね上がる印象。タスクベースな作りに逃げたくなる気持ちはすごく解る、気がする。今時のUIプログラマさんはスゴイなぁという話を皆でしていけば、UI開発のモチベーションも上がるし、UI開発のコスト意識も変わるといいなぁ、と夢想してみたり。
マウスのボタンを押すときに、Viewportに置かれているパーツによってリングメニューの内容を変えるというのを実際作ってみたのですが、ちゃんと動作させるとこまでできなかったのが心残りです。仕様次第なところもあるので、これ以上作りこむのは汎用性を考えると、まだまだ検証に時間がかかりそうというということで、ひとまずここまでにします。急にひらめいたら記事にするかもしれません。
ではでは
ステキなリングメニューライフを!
おまけ
テスト用に、3種の配列をを用意して、ローテーションするように組んでいます。
関数の中ということで、ローカル変数(上図の New Local Var 0)を利用しています。配列をクリアしなくても、この関数の外に出たら中身は破棄されるので安心。適当な名前ですが上図の New Var 1 についてはカウンタとして利用したいので普通の変数です。値が常に保持されています。
マテリアルをちょこっといじるとハイライト表現の見た目を変えることができます。
エッジがシャープになりました。