複数の UI (ユーザーインターフェース) の画面間を移動することはかなり一般的です。ここでは、遷移の作成と制御の詳細について述べ、アニメーションやステートマシンを使用して各画面を動かし管理することについて説明します。
大まかにいうと、各画面には 2 つの ステート (Open と Closed) とブーリアン パラメーター (Open) を伴う アニメーターコントローラー があります。画面間を遷移するのに必要なのは、単に、現在開いている画面を閉じ、開きたいものを開くということだけです。このプロセスを容易にするために、簡易なクラス ScreenManager を作成し、すでに開いている画面の追跡や、閉じるという処理を行います。遷移をトリガーするボタンで、望む画面を開けるように ScreenManager に依頼するだけなのです。
UI 要素のコントローラー/キーボードのナビゲーションをサポートする予定の場合、念頭に置くべき重要なことがいくつかあります。まず、画面外に選択可能な要素を置かないようにします。なぜなら、それにより、プレーヤーがオフ画面の要素を選択することが可能になってしまうからです。それを回避するには、すべてのオフ画面のヒエラルキーを無効にします。また、新しい画面を表示するとき、間違いなく要素が 1 つ選択されているように設定する必要があります。さもなければ、プレイヤーは新しい画面に移動することができません。これらの操作は、後述の ScreenManager クラスで詳しく説明します。
アニメーションコントローラーが画面遷移を行うための、もっとも一般的な最小限の設定を見てみましょう。コントローラーには、ブーリアンパラメーター (Open) と 2 つのステート (Open と Closed) が必要で、各ステートには、1 つだけキーフレームを持つアニメーションが必要です。このようにすると、ステートマシンは遷移ブレンドを行います。
今、両方のステート間の アニメーション遷移 を作成する必要があります。それではオープンからクローズへの移行から始め、適切に条件を設定してみましょう。パラメーターの Open が false に設定されているときには、Open から Closed に移動します。今度は Closed からOpen に遷移を作成し、パラメーター Open が true であるとき、Closed から Open に行くための条件を設定します。
上記のすべての設定で、唯一不足しているものは、遷移したいスクリーンアニメーターのパラメーター Open を true に設定し、現在開いているスクリーンアニメーターで Open を false に設定することです。そのために、それを処理する小さいスクリプトを作成します。
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
using System.Collections.Generic;
public class ScreenManager : MonoBehaviour {
//シーンの開始時に自動的に開く画面
public Animator initiallyOpen;
//現在開いているシーン
private Animator m_Open;
//遷移を制御するために使用するパラメーターのハッシュ
private int m_OpenParameterId;
//現在の画面を開く前に選択されたゲームオブジェクト
//画面を閉じるのに使用。すると、それを開いたボタンにもどります。
private GameObject m_PreviouslySelected;
//確認すべきアニメーターステートと遷移名
const string k_OpenTransitionName = "Open";
const string k_ClosedStateName = "Closed";
public void OnEnable()
{
//ハッシュを "Open" パラメーターにキャッシュし、Animator.SetBool に供給できます。
m_OpenParameterId = Animator.StringToHash (k_OpenTransitionName);
//設定すると、最初の画面が開きます。
if (initiallyOpen == null)
return;
OpenPanel(initiallyOpen);
}
//現在開いているパネルを閉じ、指定したものを開きます。
//さらに、ナビゲーションの処理と新しく選択された要素の設定もおこないます。
public void OpenPanel (Animator anim)
{
if (m_Open == anim)
return;
//新しい画面のヒエラルキーをアクティベートして、それを動かすことができます。
anim.gameObject.SetActive(true);
//画面を開くのに使用した現在選択中のボタンを保存します (CloseCurrent はそれを変更します)
var newPreviouslySelected = EventSystem.current.currentSelectedGameObject;
//画面を前方に移動
anim.transform.SetAsLastSibling();
CloseCurrent();
m_PreviouslySelected = newPreviouslySelected;
//新しい画面を設定し、開きます
m_Open = anim;
//画面を開くアニメーションを開始
m_Open.SetBool(m_OpenParameterId, true);
//新しく選択されたものとして、新しい画面に要素を設定します
GameObject go = FindFirstEnabledSelectable(anim.gameObject);
SetSelected(go);
}
//与えられたヒエラルキー内で最初の選択可能な要素を見つけます
static GameObject FindFirstEnabledSelectable (GameObject gameObject)
{
GameObject go = null;
var selectables = gameObject.GetComponentsInChildren<Selectable> (true);
foreach (var selectable in selectables) {
if (selectable.IsActive () && selectable.IsInteractable ()) {
go = selectable.gameObject;
break;
}
}
return go;
}
//現在開いた画面を閉じます。
//ナビゲーションも処理します。
//現在の画面を開く前に使用した選択可能な要素に戻します。
public void CloseCurrent()
{
if (m_Open == null)
return;
//画面を閉じるアニメーションを始めます
m_Open.SetBool(m_OpenParameterId, false);
//現在の画面を開く前に使用した選択可能な要素に戻します。
SetSelected(m_PreviouslySelected);
//画面を閉じるアニメーションが終了するときに、ヒエラルキーを無効にするコルーチンを開始します。
StartCoroutine(DisablePanelDeleyed(m_Open));
//画面をすべて閉じます。
m_Open = null;
}
// 画面を閉じるアニメーションの終了を検知し、
//ヒエラルキーをディアクティベートするコルーチン。
IEnumerator DisablePanelDeleyed(Animator anim)
{
bool closedStateReached = false;
bool wantToClose = true;
while (!closedStateReached && wantToClose)
{
if (!anim.IsInTransition(0))
closedStateReached = anim.GetCurrentAnimatorStateInfo(0).IsName(k_ClosedStateName);
wantToClose = !anim.GetBool(m_OpenParameterId);
yield return new WaitForEndOfFrame();
}
if (wantToClose)
anim.gameObject.SetActive(false);
}
//指定したゲームオブジェクトを選択させます。
//マウス/タッチパネルを使用しているときは、実際には前に選択したものとして設定し、
//現在の選択には何も設定しません。
private void SetSelected(GameObject go)
{
//ゲームオブジェクトを選択します。
EventSystem.current.SetSelectedGameObject(go);
//たった今、キーボードを使っているのなら、それだけで十分です。
var standaloneInputModule = EventSystem.current.currentInputModule as StandaloneInputModule;
if (standaloneInputModule != null)
return;
//ポインターデバイスを使用しているので、何も選択する必要はありません。
//しかし、ユーザーがキーボードに切り替えると、提供されたゲームオブジェクトから移動を開始する必要があります。
//ここでは、現在の選択を nullに設定します。それにより、指定したゲームオブジェクトが EventSystem の最後の選択になります。
EventSystem.current.SetSelectedGameObject(null);
}
}
それではこのスクリプトについて説明しましょう。まず、新しいゲームオブジェクトを作成し、その名前を例えば ScreenManager と変更します。そしてそれにコンポーネントを追加します。それを最初の画面に指定し、シーンの開始時に開きます。
今度は、画面の終了部分のために、ボタン を作ってみましょう。画面遷移を起動するボタンを選択し、インスペクターで On Click () リストの下に新しいアクションを追加します。先ほどオブジェクトフィールドで作成した ScreenManager ゲームオブジェクトをドラッグし、ドロップダウンで ScreenManager->OpenPanel (Animator) を選択します。そして、ユーザーがボタンをクリックすると、開くようにしたいパネルをオブジェクトフィールドにドラッグ&ドロップします。
この技術は、各画面をを動作させるために Open のパラメーターと Closed のステートさえあれば良く、画面やステートマシンがどうのように構築されているかに関係ありません。また、この方法は入れ子になった画面にも使えます。どのようにするかというと、入れ子になったレベルごとに 1 つの ScreenManager を作ればよいのです。
上で設定したステートマシンは Closed のデフォルトのステートを持っていたので、このコントローラーを使用する画面はすべて閉まった状態で開始します。ScreenManager に initiallyOpen のプロパティがあるので、どの画面で開始するかを指定することができます。