キーボードとゲームパッドでUIを操作しよう:選択とNavigation【Unityスタートガイド9-8】

左下の「たたかう・まほう・どうぐ・にげる」のメニューで、▶が↓キーで下に動き、灰色の「どうぐ」は飛ばして「にげる」から「たたかう」へ戻る。「まほう」でEnterを押すと右に「ファイア・ヒール・もどる」が開き、「ヒール を となえた!」のあとEscで閉じて「まほう」に戻る。メニューの右に押したキーが出ている

RPGのコマンドメニューやタイトル画面のように、マウスを使わずにキーボードの矢印キーやゲームパッドの十字キーで項目を選んで、決定ボタンで決める。コンシューマー機やSteamのゲームでは当たり前の操作ですが、UnityのUIは最初のままだと、矢印キーを押しても何も起きません。今回は、UIの「選択」の仕組みとNavigationの設定、そしてスクリプトで選択を動かす方法を、RPGのコマンドメニューを作りながらご紹介します!

目次

この記事で学べること

  • UIの「選択」とは何か、キーボードとゲームパッドのどのボタンで動くのか
  • EventSystemの「First Selected」で、最初に選ぶボタンを決める方法
  • 選んでいるボタンを目立たせる「Selected Color」
  • Navigationで、カーソルが動く方向を決める方法
  • 選ばれたときに▶のカーソルを出す方法(ISelectHandler)
  • スクリプトで選択を動かして、サブメニューを開け閉めする方法

前提知識

プロジェクトの確認

  • Unityのバージョンは6.3 LTS
  • テンプレートはUniversal 2D
  • Input Systemを使う設定(Unity 6の新しいプロジェクトは最初からこの設定です)
    • EventSystemには「Input System UI Input Module」が付いている

UIの「選択」ってどんなもの?

マウスで操作するときは、押したいボタンを直接クリックしますよね。キーボードやゲームパッドには、画面の場所を指すものがありません。そこでUIには、「今どのボタンを選んでいるか」が常に1つだけある、という仕組みがあります。

  • 矢印キーや十字キーで、選んでいるボタンを上下左右に動かす
  • 決定キーで、選んでいるボタンを押す(クリックしたのと同じ)
  • 今どれを選んでいるかは、EventSystemが覚えている

どのキーで動くかは、InputSystem_ActionsのUIのAction Mapで決まっています。最初の割り当ては次のとおりです(Unity 6.3で確認)。

操作Actionキーボードゲームパッド
選択を動かすNavigate矢印キー・WASD十字キー・左右のスティック
決定SubmitEnter下のボタン(Xboxの「A」、PlayStationの「×」)
キャンセルCancelEsc右のボタン(Xboxの「B」、PlayStationの「○」)

キーボードのSpaceキーは、最初の割り当てでは決定になりません。Spaceでも決定したいときは、InputSystem_ActionsのUIのSubmitに、Keyboardのspaceを足してください。

コマンドメニューを作ろう

まずは、RPGのバトルによくある「たたかう・まほう・どうぐ・にげる」のメニューを作ります。

  • Hierarchyで右クリック>UI (Canvas)>Panel で「CommandPanel」を作る
    • Canvasの左下に置き、Width:360、Height:400ぐらい
    • Add Component>Vertical Layout Group を付けて、Padding:各20、Spacing:12、Control Child SizeのWidth・Heightにチェック
  • CommandPanelの子に、右クリック>UI (Canvas)>Button – TextMeshPro で4つのボタンを作る
    • 名前は「AttackButton」「MagicButton」「ItemButton」「RunButton」
    • 中の文字は「たたかう」「まほう」「どうぐ」「にげる」(日本語のフォントは9-3の記事)
  • 画面の上の方に、UI (Canvas)>Text – TextMeshPro で「MessageText」を作る(選んだコマンドを表示するのに使います)
HierarchyのCanvas>CommandPanelの下にAttackButton・MagicButton・ItemButton・RunButton。Gameビューの左下に4つのボタンが縦に並び、CommandPanelのVertical Layout GroupはSpacing 12、Control Child SizeのWidth・Heightにチェック

キーボードで動かしてみよう!

この状態で再生して、↓キーを押してみてください。・・・何も起きませんね。

これは、今どのボタンも選ばれていないからです。選んでいるものが無いので、動かす相手がいません。(試しにボタンを1つクリックしてから↓キーを押すと、動くはずです。クリックすると、そのボタンが選ばれるからです。)

First Selectedで、最初に選ぶボタンを決めよう

  • HierarchyでEventSystemを選ぶ
  • InspectorのEvent Systemの「First Selected」に、AttackButtonをドラッグする
EventSystemのInspector。Event SystemのFirst SelectedにAttackButtonが入っている

再生すると、最初からたたかうが選ばれた状態になり、↓キーで下に、↑キーで上に動くようになります。Enterキーを押すと、選んでいるボタンが押されます(onClickが呼ばれます)。

選んでいるボタンを目立たせよう

動くようにはなりましたが、今どれを選んでいるのか、ほとんど見分けがつきません。ButtonのSelected Color(選ばれているときの色)が、最初は白に近い色(245, 245, 245)になっているからです。

  • Hierarchyで4つのボタンをまとめて選ぶ(Ctrlを押しながらクリック)
  • InspectorのButtonの「Selected Color」を、目立つ色(黄色など)にする
    • ついでに「Highlighted Color」(マウスを乗せたときの色)も同じ色にしておくと、マウスとキーボードで見た目がそろいます
4つのボタンをまとめて選んだInspector。ButtonのHighlighted ColorとSelected Colorが黄色になっている
黄色いボタンが↓キーで「たたかう」→「まほう」→「どうぐ」→「にげる」と下に動き、「にげる」で↓を押してもそれ以上は動かない。↑キーで上に戻る

Selected(選ばれている)とHighlighted(マウスが乗っている)は別の状態です。キーボードやゲームパッドで操作するゲームでは、Selected Colorを必ず目立つ色にしておきましょう。

Navigationで、動く方向を決めよう

矢印キーを押したときに「次にどのボタンへ動くか」を決めているのが、ButtonのNavigationです。

Navigation動き方
Automatic(最初の設定)画面の位置を見て、押した方向にある一番近いボタンへ動く
Vertical上下のキーだけで動く
Horizontal左右のキーだけで動く
Explicit上下左右それぞれ、どのボタンへ動くかを自分で指定する
Noneキーでは動かない(このボタンには止まらない)

InspectorのNavigationの下にある「Visualize」ボタンを押すと、Sceneビューに、どのボタンからどのボタンへ動くかが矢印で表示されます。

左はSceneビューで、4つのボタンの間に黄色い小さな矢印が出ている。右はAttackButtonのInspectorで、Navigation:AutomaticとVisualizeボタンが押された状態
スライム

Visualizeの矢印、小さくて見えにくいです・・・Sceneビューをぐっと拡大すると見やすくなります

一番下から一番上に戻れるようにしよう

Automaticのままだと、にげるで↓キーを押しても、それ以上は動きません。RPGのメニューでは、一番下で↓を押したら一番上に戻ると便利ですよね。

  • 4つのボタンをまとめて選び、Navigationを「Vertical」にする
  • その下に出てくる「Wrap Around」にチェックを入れる
4つのボタンをまとめて選んだInspector。Navigation:Verticalで、Wrap Aroundにチェックが入っている。どうぐのInteractableがオフなので、Interactableは「-」表示

これで、にげるで↓を押すとたたかうへ、たたかうで↑を押すとにげるへ、ぐるっと回るようになります。

使えないボタンは飛ばされる

アイテムを持っていないときの「どうぐ」のように、今は選べないボタンもあります。

  • ItemButtonのButtonの「Interactable」のチェックを外す

Interactableがオフのボタンは、灰色になって、Navigationでも飛ばされます。まほうで↓を押すと、どうぐを飛ばしてにげるへ動きます。

▶のカーソルを付けよう

色だけでなく、RPGらしく選んでいる項目の左に▶を出してみましょう。ボタンが選ばれた・外れたは、9-7で使ったのと同じイベントのインターフェースで受け取れます。

  • 各ボタンの子に、UI (Canvas)>Text – TextMeshPro で「Cursor」を作り、文字を「▶」にして、ボタンの左はしに置く
  • 「MenuCursor」というスクリプトを作り、次のように書く
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

// 選ばれているあいだだけ、▶のカーソルを出す
public class MenuCursor : MonoBehaviour, ISelectHandler, IDeselectHandler, IPointerEnterHandler
{
    [SerializeField] private GameObject cursor;

    private Selectable selectable;

    private void Awake()
    {
        selectable = GetComponent<Selectable>();
        cursor.SetActive(false);
    }

    private void OnDisable()
    {
        // 選ばれたまま隠れたときに、▶が残らないようにする
        cursor.SetActive(false);
    }

    // 選ばれたとき(キーボード・ゲームパッド・クリック)
    public void OnSelect(BaseEventData eventData)
    {
        cursor.SetActive(true);
    }

    // 選択が外れたとき
    public void OnDeselect(BaseEventData eventData)
    {
        cursor.SetActive(false);
    }

    // マウスを乗せたら、そのボタンを選ぶ(マウスとキーボードで、▶の場所をそろえる)
    public void OnPointerEnter(PointerEventData eventData)
    {
        if (selectable.IsInteractable())
        {
            EventSystem.current.SetSelectedGameObject(gameObject);
        }
    }
}
  • ISelectHandler / IDeselectHandler
    • OnSelectは選ばれたとき、OnDeselectは選択が外れたときに呼ばれる。キーボードでもゲームパッドでもクリックでも、選ばれ方に関係なく呼ばれる
  • IPointerEnterHandler
    • マウスを乗せたときに、そのボタンを選ぶ。こうしておくと、マウスで乗せた項目とキーで選んでいる項目が、いつも同じになる
    • Interactableがオフのボタンは選ばない(IsInteractable())
  • OnDisable
    • メニューごと隠したとき、OnDeselectは呼ばれない。▶が次に開いたときに残らないよう、ここで消しておく

4つのボタンにMenuCursorを付けて、それぞれのCursorに、自分の子のCursorをドラッグします。

HierarchyでAttackButtonの子にCursorがある。AttackButtonのInspectorで、Menu CursorのCursorに子のCursorが入っている
スライム

4つのボタンに1つずつ付けて、1つずつドラッグするの、大変です・・・

フェアリー

1つのボタンを作り込んでからPrefabにすると楽ちんだよ!ボタンが増えるゲームなら、最初からPrefabにしておくのがオススメ

スクリプトで選択を動かそう

First Selectedが効くのは、ゲームが始まったときの1回だけです。サブメニューを開いたとき・閉じたときのように、あとから選択を動かしたいときは、スクリプトで選びます。

EventSystem.current.SetSelectedGameObject(選びたいボタンのgameObject);

メニューを閉じて開き直したときや、パネルを切り替えたときは、First Selectedは効きません。選択していたボタンが隠れると、何も選ばれていない状態になって操作できなくなるので、開いたらスクリプトで選び直しましょう。

「まほう」を選ぶと右にまほうの一覧が開き、キャンセルで閉じて「まほう」に戻る、という動きを作ってみましょう。

  • CommandPanelをコピーして「MagicPanel」にし、CommandPanelの右に置く
    • 中のボタンを3つにして、名前を「FireButton」「HealButton」「BackButton」、文字を「ファイア」「ヒール」「もどる」にする
    • ボタンのMenuCursorはそのまま使える(コピーした分は、Cursorの欄が自分の子を指しているか確認)
  • CommandPanelにAdd Component>Canvas Group を付ける
  • 空のGameObject「CommandMenu」を作る

「CommandMenu」というスクリプトを作り、次のように書きます。

using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

// コマンドメニューの選択と、まほうのメニューの開け閉め
public class CommandMenu : MonoBehaviour
{
    [SerializeField] private CanvasGroup commandGroup;
    [SerializeField] private Button attackButton;
    [SerializeField] private Button magicButton;
    [SerializeField] private Button runButton;
    [SerializeField] private GameObject magicPanel;
    [SerializeField] private Button fireButton;
    [SerializeField] private Button healButton;
    [SerializeField] private Button backButton;
    [SerializeField] private TMP_Text messageText;

    private InputSystem_Actions inputActions;
    private GameObject lastSelected;

    private void Awake()
    {
        inputActions = new InputSystem_Actions();

        attackButton.onClick.AddListener(() => ShowMessage("たたかう を えらんだ!"));
        magicButton.onClick.AddListener(OpenMagic);
        runButton.onClick.AddListener(() => ShowMessage("にげる を えらんだ!"));
        fireButton.onClick.AddListener(() => ShowMessage("ファイア を となえた!"));
        healButton.onClick.AddListener(() => ShowMessage("ヒール を となえた!"));
        backButton.onClick.AddListener(CloseMagic);
    }

    private void OnEnable()
    {
        inputActions.Enable();
    }

    private void OnDisable()
    {
        inputActions.Disable();
    }

    private void Start()
    {
        magicPanel.SetActive(false);
        Select(attackButton);
    }

    private void Update()
    {
        // キャンセル(Esc・ゲームパッドの右のボタン)で、まほうのメニューを閉じる
        if (inputActions.UI.Cancel.WasPressedThisFrame() && magicPanel.activeSelf)
        {
            CloseMagic();
        }

        // 何もないところをクリックして選択が外れたら、前に選んでいたボタンを選び直す
        GameObject current = EventSystem.current.currentSelectedGameObject;
        if (current == null)
        {
            if (lastSelected != null && lastSelected.activeInHierarchy)
            {
                EventSystem.current.SetSelectedGameObject(lastSelected);
            }
        }
        else
        {
            lastSelected = current;
        }
    }

    // まほうのメニューを開いて、ファイアを選ぶ
    private void OpenMagic()
    {
        commandGroup.interactable = false;
        magicPanel.SetActive(true);
        Select(fireButton);
        ShowMessage("どの まほうを つかう?");
    }

    // まほうのメニューを閉じて、まほうのボタンに戻る
    private void CloseMagic()
    {
        magicPanel.SetActive(false);
        commandGroup.interactable = true;
        Select(magicButton);
        ShowMessage("");
    }

    private void Select(Selectable target)
    {
        EventSystem.current.SetSelectedGameObject(target.gameObject);
    }

    private void ShowMessage(string message)
    {
        messageText.text = message;
    }
}
  • EventSystem.current.SetSelectedGameObject(…)
    • 選択を、そのボタンに動かす。前に選ばれていたボタンにはOnDeselect、新しいボタンにはOnSelectが届く
  • commandGroup.interactable = false;
    • Canvas GroupのInteractableをオフにすると、その中のボタンが全部オフになる。まほうのメニューを開いている間に、カーソルがコマンドの方へ動いてしまわないようにする
  • inputActions.UI.Cancel.WasPressedThisFrame()
    • キャンセルのAction(Esc・ゲームパッドの右のボタン)が押されたか。6-7と同じ書き方
  • BackButton
    • マウスだけで遊ぶ人のために、キャンセルと同じことをするボタンも用意しておく
  • lastSelected
    • マウスで何もないところをクリックすると、選択が外れて、キーボードで動かせなくなります。外れていたら、前に選んでいたボタンを選び直す

サブメニューを開いたら中の最初のボタンを、閉じたら開く前のボタンを選ぶ。この2つを守るだけで、キーボードでもゲームパッドでも迷子にならないメニューになります。

Canvas GroupのInteractableは、子のボタン全部のInteractableとかけ合わせて効きます。どうぐのようにもともとオフのボタンは、Canvas Groupをオンに戻してもオフのままです。

CommandMenuにスクリプトを付け、それぞれの欄に、CommandPanel(Canvas Group)・各ボタン・MagicPanel・MessageTextをドラッグします。スクリプトで最初のボタンを選ぶので、EventSystemのFirst Selectedは空に戻してOKです。

CommandMenuのInspector。Command Group・Attack Button・Magic Button・Run Button・Magic Panel・Fire Button・Heal Button・Back Button・Message Textがすべて埋まっている

動作確認!

再生して、キーボードで操作してみましょう。

↓キーで▶が動き、「まほう」でEnterを押すとまほうのメニューが開いて「ファイア」に▶が移る。↓で「ヒール」、Enterで「ヒール を となえた!」、Escで閉じて▶が「まほう」に戻る
  • 再生すると、最初から「たたかう」に▶が付いている
  • ↓キーで下に動き、「どうぐ」は飛ばされる。「にげる」で↓を押すと「たたかう」に戻る
  • 「まほう」でEnterを押すと、まほうのメニューが開いて「ファイア」が選ばれる。左のコマンドは灰色になる
  • Escキーか「もどる」で閉じると、▶が「まほう」に戻る
  • マウスを乗せたボタンに▶が動く。何もないところをクリックしても、▶は消えない

ゲームパッドをつないでいれば、十字キーで動かして、下のボタン(Aや×)で決定、右のボタン(Bや○)でキャンセルが出来ます。InputSystem_ActionsのUIのAction Mapに最初から入っているので、スクリプトは何も変えなくてOKです。

ゲームパッドの十字キーの↓で▶が「まほう」に動き、A(×)ボタンでまほうのメニューが開き、十字キーの↓で「ヒール」、B(○)ボタンで閉じて「まほう」に戻る

よくあるつまずき

矢印キーを押しても何も選ばれない

  • 原因:どのボタンも選ばれていない
  • 対処法:EventSystemのFirst Selectedを設定するか、スクリプトでSetSelectedGameObjectを呼ぶ

選んでいるボタンが分からない

  • 原因:Selected Colorが最初の白っぽい色のまま
  • 対処法:Selected Colorを目立つ色にする。▶のような目印を出すのもオススメ

マウスでクリックしたら、キーボードで動かなくなった

  • 原因:何もないところをクリックすると、選択が外れる
  • 対処法:選択が外れたら、前のボタンを選び直す(CommandMenuのlastSelected)

カーソルが思わぬボタンへ飛ぶ

  • 原因:NavigationがAutomaticで、画面の位置から近いボタンを選んでいる
  • 対処法:メニューごとにVerticalやHorizontalにする。複雑な並びならExplicitで1つずつ指定する。Visualizeで矢印を確かめる

Spaceキーで決定できない

  • 原因:最初の割り当てでは、決定(Submit)はEnterだけ
  • 対処法:InputSystem_ActionsのUIのSubmitに、Keyboardのspaceを足す

まとめ

  • キーボードやゲームパッドで操作するには、「今どのボタンを選んでいるか」が要る。最初の1つはFirst Selected、あとからはSetSelectedGameObjectで選ぶ
  • 動かすのはNavigate(矢印キー・十字キー)、決定はSubmit(Enter・下のボタン)、キャンセルはCancel(Esc・右のボタン)
  • Selected Colorは必ず目立つ色に
  • Navigationで動く方向を決める。VerticalとWrap Aroundで、上下にぐるっと回るメニューになる
  • ISelectHandler・IDeselectHandlerで、選ばれたときの見た目(▶)を作れる

今回のコマンドメニューは、ターン制RPGの作り方シリーズで、バトルのコマンドとして使っていく予定です!

次のステップ

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次