UnityのUIのボタンのフォーカスとフォーカス対象の設定

UnityのUI画面を表示した時に複数のボタンがある時、キー操作やマウス操作、ゲームパッドで、選択しているボタンを変更する事が出来ますが、
マウス操作以外はあらかじめボタン1つがフォーカスされていないと他のボタンの選択が出来ません。

EventSystemに最初に選択するボタンの設定は出来ますが、ステータス画面等を開いたり閉じたりする場合最初の1回しか作用してくれません。
(Animationを使った場合はそもそもゲームオブジェクトをオン・オフするのではなくずっとある状態なので大丈夫かもしれません)

そこで画面UIを開いた時にスクリプトで決められたボタンをフォーカスするようにしてみます。

以前の記事

Unityでキーやゲームパッドで操作するステータス画面の作成
UnityのゲームでPS3コントローラー(ゲームパッド)で操作出来るステータス画面を作成していきます。

で、すでに同様の内容を記述してありますが、なんせ2万文字近く(現在は3万5千文字・・・)のボリュームの記事で、全体を通して見ないとわからないので、今回はより単純なサンプルを作成してフォーカス機能を作成してみます。

先ほどの2万文字近く&画像も多数の記事はめちゃくちゃ時間かかりましたけど、たいしてアクセスがないという悲しい結末になってしまいました(今後アクセスが増えるかもしれませんが・・・)

(+_+)

スポンサーリンク

今回作成するサンプルUIの説明

まずはサンプルのUIを見てみます。

画面UIのサンプル

↑が今回作成する画面UIです。

画面UIの階層

↑のような階層でCanvas(GameObjectOnOff)、Text(Title)、Panel(Background1とBackground2)とButton(それぞれ1~4)を作成してください。
Background1とBackground2にGrid Layout Groupを追加し、子要素であるButtonが整列するようにします。

レイアウトコンポーネントの追加

Background1とBackground2のパネルにAdd Component→Layout→Grid Layout Groupを追加します。

Cell SizeやSpacingでボタンの大きさと距離を指定します。

ボタンは中央に表示させたいのでChild AlignmentをMiddle Centerに設定します。

ConstraintsはFixed Column Countに設定し、Constraint Countは5に設定します。

これは列の数を5に制限してBackground1やBackground2の子要素がその数を超えた時は行を変えるようになります。

今回の場合はそれぞれのボタンが4つしかないので意味はありません・・・(最初10個ずつにしていたんですがそんなにあっても意味ないのでやめました)。

画面UIの操作について

これでサンプルのUI画面は作成出来たので、次にこの画面UIの操作を考えてみます。

今回のUI画面は画面を開いた時にBackground1のボタンのインタラクティブを全部trueにしてマウス操作やキー操作で反応するようにして、Background2のボタンのインタラクティブはfalseにし反応しないようにします。

つまり最初操作が出来るのはBackground1のボタンのみになります。

ボタンをインタラクティブにするかしないかはUI.Buttonコンポーネントを取得しプロパティのinteractableにtrueやfalseを入れると出来ます。

Background1のボタン(どれでもよい)を押したらBackground1のボタンのインタラクティブをfalseにし、Background2のボタンのインタラクティブをtrueに変更するようにします。

ボタンUIのinteractableを操作するButtonInteractableスクリプト

それではボタンのインタラクティブに関するButtonInteractableスクリプトを書いていきます。

ButtonActiveがボタンのインタラクティブをtrueにする関数で、ButtonNotActiveがfalseにする関数です。

ButtonInteractableスクリプトはBackground1とBackground2に設定します。

firstSelect変数はそれぞれのボタンのインタラクティブがtrueになった時に最初にフォーカスされるボタンをインスペクタで設定出来るようにしています。

ButtonActiveとButtonNotActive関数はほぼ同じ処理をしていて、

transform.GetChild(i)でBackground1、Background2の子要素を取得し、
transform.GetChild(i).GetComponent.<UI.Button>.interactableでUI.Buttonコンポーネントを取得しinteractableプロパティの値を変更しています。

ButtonActive関数ではボタンのインタラクティブをすべてtrueにした後に、

EventSystems.EventSystem.current.SetSelectedGameObject(firstSelect)

とする事で、firstSelectに指定されたゲームオブジェクトをフォーカスさせる事が出来ます。

ここでフォーカス対象を指定しない場合キー操作やゲームパッド操作が不能となってしまいます。

スクリプトの記述が終わったらBackground1とBackground2のButtonInteractableスクリプトのインスペクタのfirstSelectにそれぞれのButton1を設定してください。

Background1の子要素のボタン全部のButtonコンポーネントのOnClickの部分でBackground1ゲームオブジェクトを設定し、ButtonNotActive関数を呼び、Background2ゲームオブジェクトを設定し、ButtonActive関数の呼び出し設定をします。

Background2の子要素のボタンは逆で

Background1のButtonActive
Background2のButtonNotActive

を設定します。

ボタンイベントの設定に関しては、

Unityでスタートボタン、ゲーム終了ボタンのUIを作成する
Unityでタイトル画面に表示するスタートボタン、ゲーム終了ボタンのUIを作成していきます。シーン移動や画面遷移をしたい時にボタンを押す事で移動出来るようにします

等を参照してください。

ステータス画面のオン・オフをするGameObjectOnOffスクリプト

最後にステータス画面のオン・オフをするスクリプトを作成します。

UnityのAnimationを使ったステータス画面のオン・オフに関しては前回の記事

ステータス画面UIのオンオフをUnityのAnimationで行う
ステータス画面UIのオンオフをUnityのAnimationで行います。UIゲームオブジェクトのオン・オフで単純に消したり登場させたり出来ますが、少しだけ演出を加えてUIを登場させてみましょう。

を参照してください。

画面UIのオン・オフスクリプトは名前をGameObjectOnOffとして、空オブジェクトで作成したScriptに設定します。

select1とselect2にはBackground1とBackground2のButtonInteractableを設定します。

画面を開いた時(openFlagがfalseでstatusWindowがアクティブ)にselect1のButtonActive関数を呼び、select2のButtonNotActive関数を呼び出します。

それぞれの関数についてはさきほど作ったので問題はないと思います。

openFlagが条件に入っているのは一度ボタンのインタラクティブの処理をしたら閉じるまでは同じ処理をしたくない為です。

openFlagがないとボタンのインタラクティブの設定を常に行ってしまいます。

画面UIを閉じた時は

EventSystems.EventSystem.current.SetSelectedGameObject(null)

でnullを指定しフォーカスを解除しています。

ボタンのフォーカスの機能を確認する

これでボタンをフォーカスする機能が完成したので、Unityの実行ボタンを押して確認してみましょう。

画面UIのボタンをフォーカスするサンプル

画像のサイズの問題でボタンのサイズを半分にしました。

画面UIを表示すると左側のボタンの1番目がフォーカスされ、左側のボタンのどれかを押すと左側のボタンを操作出来なくなり、右側のボタンを操作出来るようになります。

また右側のボタンが操作出来るようになったら右側の一番左のボタンがフォーカスされます。

画面UIを非表示にした後、再度表示させると左側のボタンが操作出来、一番左のボタンがフォーカスされた状態になります。

Canvas Groupコンポーネントを使ったUIの有効化・無効化

ここまででそれぞれのUI要素の有効化・無効化が出来ましたが、Background1とBackground2にAdd Component→Layout→Canvas Groupを追加すると
もっと簡単にCanvas Groupを追加したゲームオブジェクトの子要素のUIを有効化・無効化する事が出来ます。

Canvas Groupを追加して一括でボタンの有効化・無効化をする

↑がBackground1にCanvas Groupとボタンの有効化・無効化&フォーカスをさせる新しいスクリプトButtonInteractable2を追加した状態です。

Background2も同じようになります。

Canvas Groupコンポーネントが追加されたゲームオブジェクトの子要素のUI要素はCanvas Groupの影響を受けるので、Canvas Groupのinteractableのチェックを外すと子要素であるButtonはすべて無効化されます。

Canvas Group単位でUIの有効化・無効化をするButtonInteractable2スクリプト

これを利用したのがButtonInteractable2スクリプトです。

自身のCanvasGroupを取得し、ButtonActive関数内でCanvasGroupのinteractableをtrueにします。
逆にButtonNotActive関数ではfalseにしています。

ButtonInteractableのように個別のボタンの有効・無効を指定したい場合ではなく、
所属するグループ全体の有効・無効を指定したい場合はCanvas Groupコンポーネントを取りつけた方が便利ですね。

Tab(タブ)キーでフォーカスするアイテムを変更する

ブラウザ等でボタン等のUIがある場合Tabキーを使ってフォーカスするアイテムを変更する事が出来ます。

そういったキーボードでのUIの操作に慣れている人用にゲーム内でもUI要素をTabキーで変更出来ると便利です。

フォーカスするアイテムを変更するにはキーボードの矢印キーやマウスの移動等で出来ますが、これはEventSystemの
Standalone Input ModuleでHorizontal AxisとVertical AxisでHorizontalとVerticalが設定されている為に、出来るようになっています。

つまりInputManagerのHorizontalやVerticalで設定されたキー等が押された時に横や縦に移動する事になります。

その為、例えば横に移動させる時にTabキーの設定をInputManagerに作りHorizontal AxisにTab(InputManagerで設定した名前)とすれば横に移動出来るようになります。

Tabという名前でTabキーを押した時の設定を作った場合

↑のようにInputManagerにTabという名前でTabキーを押した時の設定を作り、Standalone Input Moduleの設定を変更すると横移動がTabキーで出来るようになります。

が、これだと矢印キーでの移動が出来なくなってしまうので今回は別のやり方をしたいと思います。

Standalone Input Moduleの設定は元のままにしておきます。

まずはサンプルのUIを作成します。

Panelを2つ作成し、名前をBackgroud3、Background4とします。

サンプルのBackground3とBackground4を設置したヒエラルキー

↑のようにBackground3にはボタンを3つ作成し、Background4にはToggleを2つ作成します。

Tabキー移動表示サンプルUI

↑が表示サンプルです。

Background3のインスペクタ

Background3のインスペクタは↑のようにGrid Layout Groupを設定します。
Background4にも同じように設定します。

次にボタンやトグルが選択されている時に実行するスクリプトを作成します。

ボタンやトグルが選択されている時にTabキーを押されたら次のボタンやトグルをフォーカスする為のスクリプトになります。

TabSelectedスクリプトは以下のようになります。

自身のボタンであるmySelectableをUI.Buttonと設定してしまうとトグルで使用出来なくなる為ButtonやToggleの親クラスであるSelectableを使用します。

UI要素全部の親クラスであるUi.Selectableを使用する事によってすべてのUI要素で使えるようにしています。

SetSelectable関数は自身のボタンやトグルが選択されている時に呼ばれるようにする関数です。

イベントを発生させるのは後でやります。

SetSelectable関数ではタブキーが押された時にボタン等のUI要素のSelectOnRightに選択されている要素をフォーカスするようにしています。

次にボタンやトグルのSelectOnRightにTabキーを押した時に選択する要素を指定していきましょう。

Button1のSelectOnRightにはButton2
Button2のSelectOnRightにはButton3
Button3のSelectOnRightにはToggle1

をドラッグ&ドロップしていってください。

またそれぞれのボタンとトグルにAdd Component→Event→Event Triggerを追加し、Add New Event Typeをクリックして、UpdateSelectedを選択します。

UpdateSelectedイベントはそのボタンやトグル等が選択されている時に常に呼ばれるUpdate関数的なものです。

ゲームオブジェクトにはそれぞれのボタンやトグル自身をドラッグ&ドロップし、呼び出す関数はTabSelectedスクリプトのSetSelectable関数を設定します。

ボタン1に設定を施した後のインスペクタ

Button1は↑のように設定します。
他のボタン等も設定してください。

最後にEventSystemのFirst SelectedにButton1をドラッグ&ドロップし最初にButton1が選択されるようにしておきます。

これで設定が完了しました。

Unityの実行ボタンを押して確認してみましょう。

Tabキーでフォーカスを変更するサンプル動画

Tabキーでフォーカス対象が変わるようになりました。
(^^)v

Shiftキー+Tabキーで逆方向にフォーカスする

WebブラウザではTabキーを押すと右→下という流れでフォーカスが進みますが、Shiftキーを押しながらTabキーを押すと左→上という風に逆方向にフォーカス対象が変わります。

そこでボタンやトグルのSelectOnLeftに反対側のボタンやトグルを設定し、TabSelectedでShiftキーの検知も行いShift+Tabを押した時に逆向きにフォーカスしていくようにしたいと思います。

Tabキーだけの判定条件はShift+Tabの条件より後に行います。

シフトキーが押されているかどうかは左右のどちらかが押されていればOKです。

ただInput.GetButtonDownだと押された時の1フレームしか検知出来ないのでInput.GetKeyを使います。

Input.GetKeyは押されている間ずっとtrueになります。

Shift+Tabキーの検知が出来た時は、SelectOnLeftに指定されているUIの要素にフォーカスを当てるようにします。

Shift+Tabキーで逆方向にフォーカスしていくサンプル

これでShift+Tabキーで逆方向への移動も可能となりました。

今回のボタンのフォーカスの記事を書いた理由

これでUnityのUIでボタンのフォーカスとフォーカス対象の設定が出来るようになりました。
(UnityのAnimationでPanelのScaleだけを操作している場合はまた別の対処が必要になります)

キーやゲームパッド等で選択しているボタンを変更すると、たまにフォーカスされてない!?なんて事があります。

そんな時は別のUIを作成していてそのボタンのUI等(ボタン以外のUIでもフォーカス出来るものがあります)がフォーカスされてしまっているという事もあります。

今回この記事を書いたのは

Unityでキーやゲームパッドで操作するステータス画面の作成
UnityのゲームでPS3コントローラー(ゲームパッド)で操作出来るステータス画面を作成していきます。

の記事で今回と同じようにボタンのinteractableを操作していた時にボタンがフォーカスされているはずなのに、ボタンがHighlight表示にならなくて
ボタンを継承してその問題に対処していた部分を抽出して取り上げようと思ったんですが、今回はそれが発生しませんでした・・・・(^_^;)

正直なところその記事の内容もだいぶ忘れているので、どんな状況下で起きた現象か忘れてしまったんですよね・・・。

不具合を発生させる事が出来ないので、今回はボタンのフォーカスのみの記事となりました・・・・。
(-.-)