UIをそれっぽく組み立ててみる

 あけましておめでとうございます

今年も残すところあと4か月半ですがいかがお過ごしでしょうか?

業務多忙でUnityを完全放置していたが、Unityも2020になりUnreal Engineを猛追しつつあるので、これではイカンということで今夏季休暇を利用してUIの設計(と実装)をした次第。

とりあえず

こちらをご覧いただこう。

vimeo.com

尚、タイトル画面だけモザイクなのかは当該画面で使用しているフォントのライセンス料を払ってないからで特に深い意味はない。タイトルも仮である。

ちなみにメニュー中のフォントにはMeguフォントを利用した。

https://mix-mplus-ipa.osdn.jp/migu/

本当は、別のフォント候補があったのだけれど、商用ライセンスの有無が不透明なため、採用を見送った。ちなみにMeguフォントはIPAライセンスである(以上、免責)。

中途半端ではあるが、これ以上はゲームデザインを本格的に進めないと表示内容が定まらないので、とりあえず雛形が出来たということで、今回その内容について紹介してみたいと思う。

尚、uGUIを理解していることを前提に話を進めるので悪しからず。
といっても、せいぜいCanvasとButtonとImageとTextがナニか知っていれば問題ない程度の話である。

今週のビックリドッキリメカ

今回使用のUnityは2019.4.8f1 (64-bit)である。

また、Assetについては、

assetstore.unity.com

手軽にゲームパッドを認識させる上では欠かせない(と勝手に感じている)"InControl"と、

assetstore.unity.com

去年、何かのセールで買ったまま存在を忘れていた"DoozyUI"である。
UI設計/実装支援Assetであるが今回初めて使用するので、これの紹介も兼ねたい。

assetstore.unity.com

尚、"DoozyUI"は"DOTween"の使用前提としているので、こちらも導入が必要である。

以上のAsset導入については説明が面倒くさいので、以下を参考にしてほしい。

himatsubushi-industry.hatenablog.com

www.slideshare.net

使用方法まで説明頂いてるので本ページの存在意義が問われるが、とりあえずは話を進めよう。

InControlでUI制御を?

できらぁ!!

というわけで、こちらである。

www.gallantgames.com

いわゆるuGUIをInControlで操作するための作法についての説明である。
ちなみにuGUI以前のUIシステムについては知らん。

uGUIではUI実装上、”EventSystem"が必ず一つ必要となる。
で、これにはもれなく"StandaloneInputModule"Componentがついてくるわけだが、今回これを無効、あるいは削除し、代わりに"In Control Input Module"Componentを追加する。
設定はデフォルトで構わないだろう。気に入らなければ適宜修正を。

但し、メニュー操作が左スティックだけでは扱いづらい人もいようということで、

    void CreateActions()
    {
        actions = new InputModuleActions();

        actions.Submit.AddDefaultBinding(InputControlType.Action1);
        actions.Submit.AddDefaultBinding(Key.Space);
        actions.Submit.AddDefaultBinding(Key.Return);

        actions.Cancel.AddDefaultBinding(InputControlType.Action2);
        actions.Cancel.AddDefaultBinding(Key.Escape);

        actions.Up.AddDefaultBinding(InputControlType.LeftStickUp);
        actions.Up.AddDefaultBinding(InputControlType.DPadUp);
        actions.Up.AddDefaultBinding(Key.UpArrow);

        actions.Down.AddDefaultBinding(InputControlType.LeftStickDown);
        actions.Down.AddDefaultBinding(InputControlType.DPadDown);
        actions.Down.AddDefaultBinding(Key.DownArrow);

        actions.Left.AddDefaultBinding(InputControlType.LeftStickLeft);
        actions.Left.AddDefaultBinding(InputControlType.DPadLeft);
        actions.Left.AddDefaultBinding(Key.LeftArrow);

        actions.Right.AddDefaultBinding(InputControlType.LeftStickRight);
        actions.Right.AddDefaultBinding(InputControlType.DPadRight);
        actions.Right.AddDefaultBinding(Key.RightArrow);
    }

パッドの十字キー入力もバインドした。アナログスティックでは連打できんしな(尚、キーリピートはデフォルトで実装)。

さておき、これでInControl経由でのUI操作は可能となる。

DoozyUIができること

ありすぎて全体像のすべてを把握していないのだが、大雑把に言うと

  • 画面遷移
  • アニメーション再生
  • SE/BGM再生
  • イベント発行

を、ノーコーディングで行うことができるAssetである。

ちなみに冒頭のUIサンプル動画は一部コーディングしている。
完全ノーコーディングでできるかもしれんが、やり方を探すよりコーディングした方が速いので、コーディングに逃げた次第。
まぁ、全部コーディングしなくて良いだけでも有用である。実際便利。

DoozyUIの構成

DoozuUIの全体像をHierarkhyで表すとこんな感じである。

□MasterCanvas

 □UIView (1)

  □UIButton (1)

  □UIButton (2)

  □UIButton (3)

 □UIView (2)

  □UIButton (1)

  □UIButton (2)

  □UIButton (3)

 □Graph Controller

MasterCanvas

まず、"MasterCanvas"は該当SceneにおけるUIの根幹となるGaemObjectである。Component拡張されているが実態はuGUIのCanvasである。

DoozyUIの管理下に置かれるUI要素は、すべて"MasterCanvas"の配下に構成されることになる。
但し、"Graph Controller"についてはその限りではないのだが、管理上"MasterCanvas"下に置いた方が関係性が分かりやすいだろうということで、上記のとおりとする。

UIView

次に"UIView"だが、これもnGUIのCanvasに相当する。

"MasterCanvas"との違いは、"Canvas Scaler"と"Canvas Group"の有無と、それぞれにぶら下がっているDoogyUIのcomponentだ。

"Canvas Scaler"は"MasterCanvas"にしかないため、UIのレイアウト基準はここで設定することになる。

"Canvas Group"については本筋ではないためここでの説明は省く。
"UIView"には必要で"MasterCanvas"には必要ないと理解してもらえると良い。

そして、Componentとしての"MasterCanvas"は特に設定できることはないが、"UIView"では、画面全体に関わるアニメーション、SE/BGM再生、イベント発行をコントロールする手段が用意されている。

例えば、画面をフェードイン、フェードアウトさせたければ、"UIView"Componentの"Show View"と"Hide View"でそれぞれプリセットのアニメーションを選択し、パラメータに反映すれば、コーディング不要で実現可能である。
選択の仕方は、前述のスライド参照されたい。

パワポでスライド作る程のお手軽さである。

また、Particle SystemやAnimatorをコントロールできるので、やろうと思えば画面遷移の度に爆発演出も可能であろう(多分)。

尚、"ULView"は予めユニークな名前を付ける必要がある。

メニューバーより"Tools"→"Doozy"→"Control Panel"を開き、"Views"を選択して"Category"と"Name"を登録した後、"ULView"Componentの"View Category"と"View Name"にそれぞれ登録した"Category"と"Name"を指定するだけである。

併せて"Rename GameObject to..."をクリックするとGameObjectとしての"Name"も変更できるので、是非やっておきたい。

ULButton

 "ULButton"はnGUIのButtonに相当する。
オリジナルとの違いは"ULButton"Componentの有無であり、"UIView"と同様にアニメーション、SE/BGM再生、イベント発行をコントロールする手段が用意されている。

"ULView"Componentとの違いは効果範囲が(基本的には)ボタンに限定されること、制御のトリガーとなる事象が異なることである。

ボタンなので「押されたらナニかする」というのは当然で、フォーカスの有無やダブルクリック、長押しに至るまでコト細やかに設定できる。また、何もしていないときのアニメーションも設定可能である。

"ULButton"も予めユニークな名前を付ける必要がある。
但しこちらは、"ULView"下でユニークであれば良いので、例えば"Return"などは一つだけ登録し、複数の"ULView"下で使いまわせば良い。

メニューバーより"Tools"→"Doozy"→"Control Panel"を開き、"Buttons"を選択。
後の手順は"ULView"と同じである。

Graph Controller

 "Graph Controller"はこれまでのDoozuUIのGameObjectとは違い、画面遷移をコントロールするためのものである。

画面遷移の設計は、専用のノードベースエディタ"Nody"を使用する。
メニューバーより"Tools"→"Doozy"→"Nody"を開けば、"Nody"がアクティブになる。

基本的な使い方は、UIを構成する画面に対応する"UINode"を置いて、ノードごとの"Input Connection"と"Output Connection"を遷移線で結んで、"Output Connection"が何によってトリガされるか指定。
あとは、"OnEnterNode"と"OnExitNode"に表示したいシーン、または消したいシーンを指定すると、ひとまず画面遷移は実現できる。

詳細は、前述のスライドを参照されたい。

Q:ボタンのフォーカスに関係なく特定のキーを押したら画面の遷移を行うにはどうしたらいいですか?

 さて、上述のスライドも含め、これまでの説明は「画面上の」ボタン押下をトリガに話を進めてきたが、例えばフォーカスの位置に関わらずキャンセルキーで一つ上の階層に戻りたいというニーズもあるかと思う。

そんな時に使いたいGameObjectが”KeyToAction"と"KeyToGameEvent"である。

”KeyToAction"は"KeyToGameEvent"共に「ナニかのキーが押されたらナニかする」を指定するものだが、両者の違いは”KeyToAction"が"UIButton"のようにアニメーション等のトリガになるのに対して、"KeyToGameEvent"は"GameEvent"発行しかできない点である。

ちなみに"GameEvent"は、DoogyUIが管理する抽象化されたUnityEventと理解すればよいと思う。
"GameEvent"にはSceneユニークなシンボル名を付けておこう。

で、これを遷移に反映させたい場合は、"Nody"にて対象の"UINode"に、先ほど指定したシンボル名の"GameEvent"をトリガとする"Output Connection"を追加し、遷移線を結べば実現可能である。

Q:パッドで入力してると画面の遷移ごとにフォーカスが迷子になるんですがそれは

キーボード+マウス入力かタッチ入力を主体に考えられているのか、パッドの扱いはイマイチと常日頃感じるが、足りないモノは自分で用意するしかない。

というわけで、用意した。

PointerFocusAdjuster.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class PointerFocusAdjuster : MonoBehaviour
{
    [System.Serializable]
    public class AdjustInfo{
        public GameObject Canvas;
        public GameObject Target;
    }

    [SerializeField]
    AdjustInfo[] Infos = null;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        GameObject target = null;

        foreach (AdjustInfo info in Infos)
        {
            if (info.Canvas.activeSelf)
            {
                // Stop focus transition when multiple canvases are displayed
                if (target != null)
                {
                    target = null;
                    break;
                }
                else
                {
                    target = info.Target;
                }
            }
        }

        EventSystem eventSystem = EventSystem.current;
        GameObject current = eventSystem.currentSelectedGameObject;
        if (current == null)
        {
            EventSystem.current.SetSelectedGameObject(target);
        }
        else if (target == null)
        {
            EventSystem.current.SetSelectedGameObject(target);
        }
        GameObject currentParent = FindParentView(current);
        GameObject targetParent = FindParentView(target);
        if ((currentParent != null) && (targetParent != null))
        {
            if (currentParent.transform.parent.gameObject.name != targetParent.transform.parent.gameObject.name)
            {
                EventSystem.current.SetSelectedGameObject(target);
            }
        }
    }

    GameObject FindParentView(GameObject item)
    {
        GameObject result;

        if (item != null)
        {
            result = item.transform.parent.gameObject;
            if (result != null)
            {
                if (!result.name.Contains("View - "))
                {
                    result = FindParentView(result);
                }
            }
        }
        else
        {
            result = null;
        }
        return result;
    }
}

"EventSystem"のComponentとして登録しておけば良いだろう。

事前に"ULView"と各"ULView"遷移時のフォーカス移動先を登録する面倒があるが、これで遷移と同時に自動的にフォーカス移動できる。

尚、画面遷移アニメーション中は遷移先と遷移元が共存しているため、フォーカスを外すよう細工している。

Q:InputSystem使わんの?

docs.unity3d.com

DoozyUIが対応しとらんのだわ。

Q:Rewired使わんの?

assetstore.unity.com

XInputさえ対応してれば大半は事足りる。InControlでも問題ないやろ?(威圧)。

 Q:ARMORED COREの新作はまだですか?

「Last Labyrinth」を買えばワンチャンあるかも。

というわけで

今回はここまでとする。

限られたコーディング量でここまでできるのは素晴らしいと思うので、DoozyUIは今後も活用していきたい。