UniTaskを使ったポップアップの作成
UniTaskを使ったポップアップ(ダイヤログ)の作り方を紹介します。
ポップアップ内での選択結果で処理を分岐するのに適しています。
また、ポップアップのアニメーションは共通化しておき、ポップアップの内容だけ変えたい場合にも使えます。
サンプルアニメーションです。
UniTaskを使ったYesOrNoの処理
UniTaksのWhenAnyは何番目のタスクが完了したかを返してくれるので、WhenAnyの結果からどのボタンが押されたかが判断できます。
var isYes = await UniTask.WhenAny(noButton.OnClickAsync(), yesButton.OnClickAsync()) == 1;
noButtonはIndex=0なので押されるとfalseが、yesButtonはindex=1なので押されるとtrueが返ります。
また、複数ボタンにも対応しています。
var index = await UniTask.WhenAny(buttons.Select(b => b.OnClickAsync()).ToArray());
どれか1つを押すと次に進むポップアップはこの仕組みと相性がよいです。
ポップアップでの使われ方イメージ
// 1. ポップアップ生成 var popup = await PopupCreator.CreateAsync<生成するポップアップの型>(); // 2. 表示アニメーション→入力待ち→閉じるアニメーション var isYes= await popup.ShowAsync(); // 3. ポップアップでの入力を受け取り外で分岐処理 if (isYes) "Yes時の処理~"
awaitすることにより同期的に書けるので、コールバックが不要になります。
// awaitを使わない場合のコールバック例 popup.ShowAsync(() => Yesの処理, () => Noの処理); // awaitを使った処理 var isYes = await popup.ShowAsync(); if (isYes) Yesの処理~ else Noの処理~
実装編
共通クラスの実装
- PopupCreator
ポップアップ作成クラス
PopupCreator.CreateAsync<生成するポップアップの型>()でポップアップを作る。
using UniRx.Async; using UnityEngine; /// <summary> /// ポップアップ生成クラス /// </summary> public static class PopupCreator { /// <summary> /// PopupとContentをロードしポップアップを生成する /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public static async UniTask<T> CreateAsync<T>() where T : PopupContentBase { var popupContainer = await Resources.LoadAsync<PopupContainer>("Popup/PopupContainer") as PopupContainer; if (!popupContainer) { Debug.LogAssertion("PopupContainerが見つかりません"); return null; } var popupInstance = Object.Instantiate(popupContainer); var content = await Resources.LoadAsync<T>("Popup/Content/" + typeof(T).Name) as T; if (!content) { Debug.LogAssertion($"{typeof(T).Name}が見つかりません"); return null; } var contentInstance = Object.Instantiate(content); popupInstance.SetContent(contentInstance); return contentInstance; } }
- PopupContainer
ポップアップの親となるクラス。
ポップアップのフレーム部分に相当。
using UnityEngine; /// <summary> /// ポップアップの格納、フレーム部分 /// アニメーションへの参照と要素の紐づけを行う /// </summary> public class PopupContainer : MonoBehaviour { [SerializeField] private PopupAnimator animator; public PopupAnimator PopupAnimator => animator; /// <summary> /// ポップアップの要素を設定する /// </summary> /// <param name="content"></param> public void SetContent(PopupContentBase content) { content.transform.SetParent(animator.transform, false); content.Initialize(this); } }
- PopupAnimator
ポップアップ開閉時のアニメーションクラス。
Animation/Animatorでアニメーションしてもいいし、DoTweenなどのスクリプトでアニメーションしてもよいです。 実際はインターフェースのほうがいいかも。
ShowとHideがawait可能なように記述することでアニメーションが終わるまで次の処理を待つことができます。
サンプルとしてAnimatorでShowとHideのアニメーションを設定しています。
using UniRx.Async; using UnityEngine; /// <summary> /// ポップアップアニメーション /// </summary> public class PopupAnimator : MonoBehaviour { // ShowPopupとHidePopupのアニメーションが設定されている [SerializeField] private Animator animator; public async UniTask PlayAnimationAsync() { animator.Play("ShowPopup"); await UniTask.WaitUntil(IsFinish); } public async UniTask HideAnimationAsync() { animator.Play("HidePopup"); await UniTask.WaitUntil(IsFinish); } private bool IsFinish() { return animator.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1; } }
- PopupContentBase
ポップアップの中身に相当し、各ポップアップごとにPopupContentBaseを継承してプレハブとして作ります。
using UniRx.Async; using UnityEngine; /// <summary> /// ポップアップの中身 /// 実際は継承して使われる /// </summary> public abstract class PopupContentBase : MonoBehaviour { private PopupContainer container; /// <summary> /// 初期化 /// </summary> /// <param name="container"></param> public void Initialize(PopupContainer container) { this.container = container; } /// <summary> /// 表示する /// </summary> /// <returns></returns> protected async UniTask ShowAnimationAsync() { await container.PopupAnimator.PlayAnimationAsync(); } /// <summary> /// 非表示にする /// </summary> /// <returns></returns> protected async UniTask HideAnimationAsync() { await container.PopupAnimator.HideAnimationAsync(); Destroy(container.transform.gameObject); } }
PopupContentの実装例
確認メッセージがあり、Yes or Noで選択するポップアップは以下のようになります。
using UniRx.Async; using UnityEngine; using UnityEngine.UI; /// <summary> /// 確認メッセージがあり、Yes or Noで選択するポップアップ /// </summary> public class ConfirmPopupContent : PopupContentBase { [SerializeField] private Text _confirmMessage; // noが左、yesが右想定 [SerializeField] private Button noButton; [SerializeField] private Button okButton; public void Initialize(string message) { _confirmMessage.text = message; } public async UniTask<bool> ShowAsync() { await ShowAnimationAsync(); var isYes = await UniTask.WhenAny(noButton.OnClickAsync(), okButton.OnClickAsync()) == 1; await HideAnimationAsync(); return isYes; } }
使用例
上記のクラスを使ったポップアップ生成までの実装例です。
プレハブの作成
PopupContainerプレハブを以下のように作り、Resources/Popupフォルダ内に設置します。
Animationを担当するオブジェクト。
フェードイン/フェードアウトはCanvasGroupのAlphaを変更することで実現しています。
YesOrNo(Confirm)のポップアップはクラス名とプレハブ名を合わせてResource/Popup/Contentフォルダ内に設置します。
見た目は適当に。
ShowとHideのアニメーションも設定しておきます。
ポップアップの呼び出し
ボタンのOnClickイベントに合わせ以下のように呼び出します。
confirmButton.onClick.AddListener(async () => { var popup = await PopupCreator.CreateAsync<ConfirmPopupContent>(); popup.Initialize("ポップアップ確認?"); var isYes = await popup.ShowAsync(); if (isYes) { text.text = "Yesが押された"; } else { text.text = "Noが押された"; } });
これでポップアップ生成→アニメーション→入力待ち→処理の分岐が行えました。
応用編
そのほかのポップアップ
複数選択肢があるポップアップは以下のようになります。
ボタンのラベル変更機能があり、複数ボタンのクリック待ちをLINQでUniTask化していること以外は、ほぼYesOrNoのポップアップと同じです。
using System.Linq; using UniRx.Async; using UnityEngine; using UnityEngine.UI; /// <summary> /// メッセージと複数選択肢があるポップアップ /// </summary> public class SelectionPopupContent : PopupContentBase { [SerializeField] private Text confirmMessage; [SerializeField] private Button[] buttons; public void Initialize(string message, string[] buttonLabels) { confirmMessage.text = message; var length = Mathf.Min(buttons.Length, buttonLabels.Length); for (var i = 0; i < length; i++) { buttons[i].GetComponentInChildren<Text>().text = buttonLabels[i]; } } /// <summary> /// Yes or Noのポップアップを表示 /// </summary> /// <returns></returns> public async UniTask<int> ShowAsync() { await ShowAnimationAsync(); var index = await UniTask.WhenAny(buttons.Select(b => b.OnClickAsync()).ToArray()); await HideAnimationAsync(); return index; } }
使い方
selectionButton.onClick.AddListener(async () => { var popup = await PopupCreator.CreateAsync<SelectionPopupContent>(); var labels = new string[]{"A","B","C"}; popup.Initialize("選択肢を表示します", labels); var index = await popup.ShowAsync(); text.text = $"{labels[index]}ボタンが押された"; });
ポップアップ表示の入力ブロック
アニメーション中にユーザーの入力を禁止したいときは、AnimationでGanvasGroupのInteractableをオフにすると簡単です。
アニメーション終了時に再度interactableをオンにするのを忘れずに。
また、ポップアップ表示中に後ろのUIへの入力を禁止したいときは、Parentオブジェクト(Animatorがアタッチされたオブジェクト)に透明なImageコンポーネントをアタッチすることで可能です。
環境
Unity:Unity2018.4
UniTask:Ver 1.2.0
今回紹介したサンプルはGitHubで公開しています