前回の続きです。
マウス座標を取得して、そこにWidgetを表示することができました。次はアイコンを並べていきます。
アイコンのテクスチャとマテリアル
↓このようなアイコンのテクスチャを用意。サイズは256x256で、1つが 64x64なので16個分持つことができます。
これをマテリアルを使って切り出せるようにします。
アイコンの並びは、開発途中で、入れ替わったり使わなくなることがよくあるので、Index番号で管理できると便利です。その番号をマテリアルが受け取って計算するようにしています。Fmodは 剰余(割った余り)Divide は割り算、Floor は小数以下切り捨てをするノードです。
マテリアルができたら、ブループリントの編集です。
Widgetブループリント
Event Pre Construction で変数の初期化をしています。
新しく、Integer型が1つ、Float型が2つ、Text型が1つ、計4つの変数を用意。
「定数」は、一度定義したら最初から最後まで値の変わらない値です。メニューアイテムの数が決まっている前提で、アイコン同士の間隔(角度)と、マウス判定用の範囲(角度)をここで事前に計算させています。「角度」と「ラジアン」混在してたりするので頭悪い感じですが、その辺はいったん棚に上げておいていただけると助かりまする。
前回の記事で書いていませんが、今回のリングメニューはいつでも呼び出せるので、Viewportに常駐させる想定で作っています。出現と退出は Visible ←→ Collapse を使っているので、この初期化処理は1回だけ走ることになります。コンテキストメニューのように動的にアイテム数が変わるようにするのであれば、関数化すれば対応できます。
次に、Event Construct でアイコンを並べます。
ちょっと大きいので画像を分割して、前半部分から。
For Loop でループさせます。 ループ回数の指定が Last Index なので、アイテムの個数から -1 しないといけないのが面倒です・・・。First Index を1にするという手もあるのですが、Index番号を 計算に使用するので、 0 始まりの方が都合がいいのです。
画像の右端にあるのは、Create Dynamic Material Instance ノードで、指定したマテリアル アセットから、マテリアルインスタンスダイナミックを生成します。WIdgetブループリントでよく使う Get Dynamic Material ノードとは、引数にマテリアルのついたUMGのImageパーツを渡して使うところが異なっています。今回は、Imageパーツをキャンバスに置かずにブループリントから生成しているので、このようなマテリアルの扱い方になっています。
後半部分。
今回 アイコンの番号は素直にForLoop の Index を使っていますが、別途配列を用意すればアイコンの並びを管理できます。例えば下図のような感じになります。
マクロの中身はこんな感じ。三角関数のサインとコサインが登場します。
Sinθ と Cosθ の θに角度(ラジアン)を入れてXとYの値にすると円周上のポイントになります。Sin、Cos はそのままだと -1 ~ 1 の値を返すので、半径を掛けて円を大きくしています。
いろいろ補正しているので、ややこしい感じですが、補正を無しだと ↓ こうなります。スッキリ。
これをキャンバスで確認すると、
キャンバスの左上が (0, 0)原点なので当然ズレます。
リングの大きさを 320 としているので、その半分のサイズがズレ幅になります。ここも最初に定数化しておく方がいいと思います。右下に +160 足せばポジションは収まりそうです。メニューの開始位置を上にしたいので、90度 = π/2 = π*0.5 を足しています。
最後に、好みですが、逆時計回りにするために、Y座標を求める Sin にマイナスの半径を掛けています。
ここで一旦確認してみます。
よしよし。
最後の仕上げ
マウスの座用からハイライトさせる
EventTick で処理します。常時という訳にはいかないので、フラグでコントロール。
リングメニューが出ている間だけ動くようにして、結果を新しく用意した変数に取り置き。
関数の中身はこうなってます。
最初にマウスのボタンを押した場所をVector2D型の変数に保存しているので、今の場所から引き算することで差分が出ます。つまり出現場所からどれだけマウスが動いたかが分かります。その場所から、 atan2 という演算ノードを使うことで、角度を求めることができるのです。
戻り値(ReturnValue)は Radians(θ)とDegrees(°)の2タイプあります。
ラジアンだとうまく説明できる気がしないので、Degreesの方を選択しています。
基本的な扱い方なら、 A と B の軸と角度の関係は↓のようになります。
Viewportの座標空間は左上が原点で、見えているのはプラスの領域になります。なのでここで差分をとると↓のようになります。
Y軸の符号が逆になります。(現在位置からスタート位置を引いているのでX軸は問題なし)
今回リングのてっぺんをIndexの0にしたりしてるので、いろいろ試してみた結果、アレンジして atan2 ノードに渡すことにしました。アレンジの内容はY軸の符号を逆にして、A=Y / B=X のところを、 A=X / B=Y に変えています。出てきた角度は下図のようになってます。
角度をIndex番号にする際に、マイナスの値は使いづらいので、+180します。
これを、最初に定数化しておいた値で計算していくのですが、簡単な例で図にしていきます。
まずメニューアイコンが 8個の場合だと、45という数値が得られます。
360° ÷ 8個 = 45°
この範囲にマウスカーソルの角度がある場合、そのアイコンを選択していることにすればいいのです。なのでカーソルのいる場所を角度にして、45で割って小数点以下切り捨てれば、メニューのIndexにマッチした数が得られそうです。
でも、上の図だとアイコンのまん中に区切り線がきています。
本当は下図のようにしたいのです。
さぁ、困ったぞ。判定用に角度を調節するか、と考えたけど、これ以上角度をいじるのは灰色の頭脳がとろけるチーズになりそうだったので、そこは諦めることにして、最終的に「もう半分にしたらどうか?」という発想にたどり着きました。
45を得てから2で割るのもノードが増えて処理がもったいないので、360ではなく180を割ることにします。
180° ÷ 8個 = 22.5°
半分なるとどうなるかというと、
この考え方で、角度を 22.5で割って、小数点以下切り捨てると下図のようになります。
例えば、 atan2 が 30°という値を返してきたら、30 ÷ 22.5=1.3333・・・
なので、小数以下切り捨てると 1 という数字になります。
ここまでの説明はマクロの中身では下の明るくなっている部分にあたります。
まだ 0~15 なので、これをどうにかして 0~7 にしないといけません。これがマクロの右上の部分。
0~15 を半分にして、小数点以下を切り上げ、それをアイテムの個数で割った余りが答えになります。
まず半分(BPでは 0.5を掛けてます)にして・・・
小数点以下切り上げます。Ceil(シール)を使います。
だいぶいい感じです。
あとは一番上の 8 が 0 になってくれれば完璧。
8はアイテムの個数と同じ、ここは 剰余先生の出番です。
ようやく、角度がいい感じにIndex値になりました。
この値をフォーカスの判定に使います。イベントグラフに戻って続き。
左端にマクロがあります。
マクロで得た値は一旦変数に保存します。
ForLoopで何度も判定するので、マクロから戻り値を直接つなげてしまうと、何度も無駄にマクロの計算が走ってしまいます。
ForLoopで0から順番にチェックしていって、同じ値のものだけハイライト処理をします。
ではさっそく確認。
5個のとき。
16個!
なんとか完成です。
アイコンにマウスイベントをセットすると、四角形でしか判定できないし、アイコンの無い部分の判定が難しくなります。また重なると優先の問題が発生します。
ちょっと計算が多くて大変そうに見えますが、角度で判定すればマウスイベントは不要にできます。さらに外側にサブメニューなんかのリングを出す場合は、中心からの距離を判定に加えればよさそうです。なんかリングメニューが攻略できた気になってきた。
もっと頭のいい方法があると思うけど、いったんこれで満足しておこう。
ふぅ。
いつものことだけど、作ってからいろいろ思いついたりするので、次回もう少しだけ続きを書こうと思います。
ラジオからはクリスマスソングが流れてます。
ではでは
ステキな リングメニューライフを!