UnityのUI画面を表示した時に複数のボタンがある時、キー操作やマウス操作、ゲームパッドで、選択しているボタンを変更する事が出来ますが、
マウス操作以外はあらかじめボタン1つがフォーカスされていないと他のボタンの選択が出来ません。
EventSystemに最初に選択するボタンの設定は出来ますが、ステータス画面等を開いたり閉じたりする場合最初の1回しか作用してくれません。
(Animationを使った場合はそもそもゲームオブジェクトをオン・オフするのではなくずっとある状態なので大丈夫かもしれません)
そこで画面UIを開いた時にスクリプトで決められたボタンをフォーカスするようにしてみます。
以前の記事
で、すでに同様の内容を記述してありますが、なんせ25000文字近くのボリュームの記事で、全体を通して見ないとわからないので、今回はより単純なサンプルを作成してフォーカス機能を作成してみます。
先ほどの2万文字近く&画像も多数の記事はめちゃくちゃ時間かかりましたけど、たいしてアクセスがないという悲しい結末になってしまいました(今後アクセスが増えるかもしれませんが・・・増えませんでした・・・(T_T)/~~~)
(+_+)
今回作成するサンプル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を作成した時にヒエラルキーにはEventSystemが作成されていますが、インスペクタのEventSystemのFirst Selectedには何も設定せずスクリプトから設定するようにします。
ボタンUIのinteractableを操作するActivateButtonスクリプト
それではボタンのインタラクティブに関するActivateButtonスクリプトを書いていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | using UnityEngine; using System.Collections; using UnityEngine.UI; using UnityEngine.EventSystems; public class ActivateButton : MonoBehaviour { // 最初にフォーカスするゲームオブジェクト [SerializeField] private GameObject firstSelect; public void ActivateOrNotActivate(bool flag) { for(var i = 0; i < transform.childCount; i++) { transform.GetChild(i).GetComponent<Button>().interactable = flag; } if (flag) { EventSystem.current.SetSelectedGameObject (firstSelect); } } } |
firstSelectはBackground1とBackground2それぞれがアクティブになった時に最初にフォーカスするゲームオブジェクトをインスペクタで設定します。
ActivateOrNotActivateメソッドでは受け取った引数のbool値を自身の子要素のボタンのinteractableに設定します。
引数で受け取ったbool値がtrueだった時は最初にフォーカスする対象を設定します。
ActivateButtonスクリプトはBackground1とBackground2に設定します。
1 2 3 | EventSystem.current.SetSelectedGameObject(firstSelect) |
とする事で、firstSelectに指定されたゲームオブジェクトをフォーカスさせる事が出来ます。
ここでフォーカス対象を指定しない場合キー操作やゲームパッド操作が不能となってしまいます。
スクリプトの記述が終わったらBackground1とBackground2のButtonInteractableスクリプトのインスペクタのfirstSelectにそれぞれのButton(1)を設定してください。
Background1の子要素のボタン全部のButtonコンポーネントのOnClickの部分でAcitivateButtonスクリプトのActivateOrNotActivateメソッドの呼び出しを行いますが、
Background1の子要素のボタンを押した時はBackground1の子要素のボタンのinteractableをfalseにし、Background2の子要素のボタンのinteractableをtrueにする必要があります。
その為、Background1の子要素のボタンのOnClickは
↑のようにBackground1のActivateButtonのActivateOrNotActivateメソッドを呼び出し、boolはfalse、
Background2のActivateButtonのActivateOrNotActivateメソッドを呼び出し、boolはtrueを渡します。
Background2の子要素のボタンは逆になります。
ボタンイベントの設定に関しては、
等を参照してください。
ステータス画面のオン・オフをするActivateUIObjスクリプト
最後にステータス画面のオン・オフをするスクリプトを作成します。
UnityのAnimationを使ったステータス画面のオン・オフに関しては前回の記事
を参照してください。
画面UIのオン・オフスクリプトは名前をActivateUIObjとして、Main Cameraに設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | using UnityEngine; using System.Collections; using UnityEngine.EventSystems; public class ActivateUIObj : MonoBehaviour { // 画面UI [SerializeField] private GameObject statusWindow; // ボタンのインタラクティブに関する処理が書かれているスクリプト [SerializeField] private ActivateButton select1; [SerializeField] private ActivateButton select2; void Update () { // スペースキーを押したら画面UIのオン・オフ if (Input.GetKeyDown ("space")) { statusWindow.SetActive (!statusWindow.activeSelf); // 画面を開いた時にBackground1のボタンのインタラクティブをtrue、Background2のボタンのインタラクティブをfalseにする if (statusWindow.activeSelf) { select1.ActivateOrNotActivate (true); select2.ActivateOrNotActivate (false); // 画面を閉じたら選択を解除 } else { EventSystem.current.SetSelectedGameObject (null); } } } } |
select1とselect2にはBackground1とBackground2のActivateButtonスクリプトを設定します。
画面を開いた時にselect1のActivateOrNotActivateにtrue、select2のActivateOrNotActivateにfalseを渡し呼び出します。
それぞれのメソッドについてはさきほど作ったので問題はないと思います。
画面UIを閉じた時は
1 2 3 | EventSystem.current.SetSelectedGameObject(null) |
でnullを指定しフォーカスを解除しています。
ボタンのフォーカスの機能を確認する
これでボタンをフォーカスする機能が完成したので、Unityの実行ボタンを押して確認してみましょう。
画像のサイズの問題でボタンのサイズを半分にしました。
画面UIを表示すると左側のボタンの1番目がフォーカスされ、左側のボタンのどれかを押すと左側のボタンを操作出来なくなり、右側のボタンを操作出来るようになります。
また右側のボタンが操作出来るようになったら右側の一番左のボタンがフォーカスされます。
画面UIを非表示にした後、再度表示させると左側のボタンが操作出来、一番左のボタンがフォーカスされた状態になります。
Canvas Groupコンポーネントを使ったUIの有効化・無効化
ここまででそれぞれのUI要素の有効化・無効化が出来ましたが、Background1とBackground2にAdd Component→Layout→Canvas Groupを追加すると
もっと簡単にCanvas Groupを追加したゲームオブジェクトの子要素のUIを有効化・無効化する事が出来ます。
↑がBackground1にCanvas Groupとボタンの有効化・無効化&フォーカスをさせる新しいスクリプトActivateButton2を追加した状態です。
Background2も同じようになります。
Canvas Groupコンポーネントが追加されたゲームオブジェクトの子要素のUI要素はCanvas Groupの影響を受けるので、Canvas Groupのinteractableのチェックを外すと子要素であるButtonはすべて無効化されます。
Canvas Group単位でUIの有効化・無効化をするActivateButton2スクリプト
これを利用したのがActivateButton2スクリプトです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | using UnityEngine; using System.Collections; using UnityEngine.EventSystems; public class ActivateButton2 : MonoBehaviour { [SerializeField] private GameObject firstSelect; private CanvasGroup canvasGroup; void OnEnable () { if (canvasGroup == null) { canvasGroup = GetComponent<CanvasGroup> (); } } public void ActivateOrNotActivate(bool flag) { canvasGroup.interactable = flag; if (flag) { EventSystem.current.SetSelectedGameObject (firstSelect); } } } |
OnEnableメソッドで自身のCanvasGroupを取得し、ActivateOrNotActivateメソッド内でCanvasGroupのinteractableを受け取った引数の値に設定します。
ボタンのOnClickの設定等は同じように設定します。
ActivateButtonのように個別のボタンの有効・無効を指定したい場合ではなく、
所属するグループ全体の有効・無効を指定したい場合は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で設定した名前)とすれば横に移動出来るようになります。
↑のようにInputManagerにTabという名前でTabキーを押した時の設定を作り、Standalone Input Moduleの設定を変更すると横移動がTabキーで出来るようになります。
が、これだと矢印キーでの移動が出来なくなってしまうので今回は別のやり方をしたいと思います。
Standalone Input Moduleの設定は元のままにしておきます。
まずはサンプルのUIを作成します。
Panelを2つ作成し、名前をBackgroud3、Background4とします。
↑のようにBackground3にはボタンを3つ作成し、Background4にはToggleを2つ作成します。
↑が表示サンプルです。
Background3のインスペクタは↑のようにGrid Layout Groupを設定します。
Background4にも同じように設定します。
次にボタンやトグルが選択されている時に実行するスクリプトを作成します。
ボタンやトグルが選択されている時にTabキーを押されたら次のボタンやトグルをフォーカスする為のスクリプトになります。
TabSelectedスクリプトは以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | using UnityEngine; using System.Collections; using UnityEngine.UI; using UnityEngine.EventSystems; public class TabSelected : MonoBehaviour { // 自身のボタンやトグル private Selectable mySelectable; void Start() { mySelectable = GetComponent<Selectable>(); } public void SetSelectable() { // タブキーを押されたらSelectOnRightに選択された物をフォーカスする if(Input.GetKeyDown(KeyCode.Tab)) { EventSystem.current.SetSelectedGameObject(mySelectable.navigation.selectOnRight.gameObject); } } } |
自身のボタンであるmySelectableをButtonと設定してしまうとトグルで使用出来なくなる為ButtonやToggleの親クラスであるSelectableを使用します。
UI要素全部の親クラスであるSelectableを使用する事によってすべてのUI要素で使えるようにしています。
SetSelectableメソッドは自身のボタンやトグルが選択されている時に呼ばれるようにするメソッドです。
イベントを発生させるのは後でやります。
SetSelectableメソッドではタブキーが押された時にボタン等のUI要素のSelectOnRightに選択されている要素をフォーカスするようにしています。
次にボタンやトグルのSelectOnRightにTabキーを押した時に選択する要素を指定していきましょう。
ButtonとToggleのNavigationをExplicitにします。
Button1のSelectOnRightにはButton2
Button2のSelectOnRightにはButton3
Button3のSelectOnRightにはToggle1
をドラッグ&ドロップしていってください。
またそれぞれのボタンとトグルにAdd Component→Event→Event Triggerを追加し、Add New Event Typeをクリックして、UpdateSelectedを選択します。
UpdateSelectedイベントはそのボタンやトグル等が選択されている時に常に呼ばれるUpdateメソッド的なものです。
ゲームオブジェクトにはそれぞれのボタンやトグル自身をドラッグ&ドロップし、呼び出すメソッドはTabSelectedスクリプトのSetSelectableメソッドを設定します。
Button1は↑のように設定します。
他のボタン等も設定してください。
最後にEventSystemのFirst SelectedにButton1をドラッグ&ドロップし最初にButton1が選択されるようにしておきます。
これで設定が完了しました。
Unityの実行ボタンを押して確認してみましょう。
Tabキーでフォーカス対象が変わるようになりました。
(^^)v
Shiftキー+Tabキーで逆方向にフォーカスする
WebブラウザではTabキーを押すと右→下という流れでフォーカスが進みますが、Shiftキーを押しながらTabキーを押すと左→上という風に逆方向にフォーカス対象が変わります。
そこでボタンやトグルのSelectOnLeftに反対側のボタンやトグルを設定し、TabSelectedでShiftキーの検知も行いShift+Tabを押した時に逆向きにフォーカスしていくようにしたいと思います。
1 2 3 4 5 6 7 8 9 10 11 | public void SetSelectable() { // 左のシフトキー、右のシフトキーを押しながらTabキーを押した時は反対向きにフォーカスを移動する if(Input.GetKeyDown(KeyCode.Tab) && (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift))) { EventSystem.current.SetSelectedGameObject(mySelectable.navigation.selectOnLeft.gameObject); // タブキーを押されたらSelectOnRightに選択された物をフォーカスする } else if(Input.GetKeyDown(KeyCode.Tab)) { EventSystem.current.SetSelectedGameObject(mySelectable.navigation.selectOnRight.gameObject); } } |
Tabキーだけの判定条件はShift+Tabの条件より後に行います。
シフトキーが押されているかどうかは左右のどちらかが押されていればOKです。
ただInput.GetButtonDownだと押された時の1フレームしか検知出来ないのでInput.GetKeyを使います。
Input.GetKeyは押されている間ずっとtrueになります。
Shift+Tabキーの検知が出来た時は、SelectOnLeftに指定されているUIの要素にフォーカスを当てるようにします。
これでShift+Tabキーで逆方向への移動も可能となりました。
今回のボタンのフォーカスの記事を書いた理由
これでUnityのUIでボタンのフォーカスとフォーカス対象の設定が出来るようになりました。
(UnityのAnimationでPanelのScaleだけを操作している場合はまた別の対処が必要になります)
キーやゲームパッド等で選択しているボタンを変更すると、たまにフォーカスされてない!?なんて事があります。
そんな時は別のUIを作成していてそのボタンのUI等(ボタン以外のUIでもフォーカス出来るものがあります)がフォーカスされてしまっているという事もあります。
今回この記事を書いたのは
の記事で今回と同じようにボタンのinteractableを操作していた時にボタンがフォーカスされているはずなのに、ボタンがHighlight表示にならなくて
ボタンを継承してその問題に対処していた部分を抽出して取り上げようと思ったんですが、今回はそれが発生しませんでした・・・・(^_^;)
正直なところその記事の内容もだいぶ忘れているので、どんな状況下で起きた現象か忘れてしまったんですよね・・・。
不具合を発生させる事が出来ないので、今回はボタンのフォーカスのみの記事となりました・・・・。
(-.-)