リングメニューの表示がそれなりにカタチになったと思うけど、もう少しだけ作り込んでみます。リングメニューの呼び出し時にメニュー項目を任意のセットにできるようにして、閉じた後の通知部分を作ってなかったので追加します。あと選択中の見た目を強調するためのハイライト表示も追加します。
改造スタート
まずハイライト表示用のパーツを準備。リングの大きさをマテリアルで調整できる作りなので、同じ要領で専用のマテリアルを作ります。
マテリアルができたら、前回作った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 についてはカウンタとして利用したいので普通の変数です。値が常に保持されています。
マテリアルをちょこっといじるとハイライト表現の見た目を変えることができます。
エッジがシャープになりました。