Help us understand the problem. What is going on with this article?

UnityのuGUIの表示パネル切り替えを簡単にできるように

More than 1 year has passed since last update.

この記事は SLP KBIT Advent Calendar 2018 の20日目の記事です。

メニュー画面みたいなもの作るときとか、1シーンだけで完結するようなものとか作ってたりすると必然的にUIの切り替えが多発するもので。
切り替えのときに簡易的な動きは欲しいけどいちいち書くのも面倒なのでInspectorで値いじれば簡単にできるようなものを作ってみようかなと思ったわけです。

というのが建前で本当はInspector内部の動的な切り替えにはエディタ拡張使うことを知って面白そうだなと思ったのが始まりです。

はじめに

既存の便利なコンポーネントが存在するのかもしれませんがとりあえず自作を目指して。
ついでに簡単にエディタ拡張も使用してみます。

なお現段階はあくまでおためし気分で作った結果なのでいろいろと機能が不足しています。

スクリプト

パネル切り替え

パネルを切り替えるスクリプトです。
表示するパネルそれぞれにアタッチします。

※ 『戻る』、『キャンセル』に相当するメソッドはないです。『閉じる』はあくまで切り替えの際に切り替える対象がないものと同義となっています。

メソッド 内容
void PanelAppear() パネルを出現させます
void PanelChange(string _name) 同階層内の_nameと同じ名前のパネルに切り替えます
void PanelClose() パネルを閉じます
PanelChanger.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

[RequireComponent(typeof(CanvasGroup))]
public class PanelChanger : MonoBehaviour {
    public enum FadeMode {
        None,
        Fade
    }

    public enum FadeDirection {
        Top,
        Right,
        Left,
        Bottom,
        None
    }

    private enum PlayingState {
        Stop,
        FadeIn,
        FadeOut
    }

    private RectTransform rtf;
    private GameObject parent;
    private string panel_name;
    private bool isPlaying = false;
    private float progress = 0.0f;
    private PlayingState nowState = PlayingState.Stop;
    private Coroutine nowCoroutine = null;
    private int directionX = 0, directionY = 0;
    private CanvasGroup canvasGroup;

    public FadeMode fadeInMode = FadeMode.None;
    public FadeDirection fadeIn = FadeDirection.Top;
    public float fadeInTime = 1.0f;
    public float fadeInDelay = 0.0f;
    public float fadeInDistance = 800.0f;
    public bool fadeInAlpha = true;
    public AnimationCurve fadeInAnimationCurve = AnimationCurve.Linear(0,0,1,1);
    public UnityEvent onStart;
    public UnityEvent onFinishFadeIn;

    public FadeMode fadeOutMode = FadeMode.None;
    public FadeDirection fadeOut = FadeDirection.Bottom;
    public float fadeOutTime = 1.0f;
    public float fadeOutDelay = 0.0f;
    public float fadeOutDistance = 800.0f;
    public bool fadeOutAlpha = true;
    public AnimationCurve fadeOutAnimationCurve = AnimationCurve.Linear(0,0,1,1);
    public UnityEvent onStartFadeOut;
    public UnityEvent onFinish;

    void Awake () {
        rtf = this.gameObject.GetComponent<RectTransform>();
        parent = transform.parent.gameObject;
        canvasGroup = this.gameObject.GetComponent<CanvasGroup>();  
    }

    void OnEnable() {
        StartChange();
    }

    void OnDisable() {
        Stop();
    }

    void Update () {
        if ( !isPlaying ) { return; }
        switch(nowState) {
            case PlayingState.Stop : return;
            case PlayingState.FadeIn : {
                float curve = fadeInAnimationCurve.Evaluate(progress);
                rtf.anchoredPosition = new Vector2(directionX*fadeInDistance*(1.0f - curve), directionY*fadeInDistance*(1.0f - curve));
                if ( fadeInAlpha ) { canvasGroup.alpha = curve; }
                break;
            }
            case PlayingState.FadeOut : {
                float curve = fadeInAnimationCurve.Evaluate(progress);
                rtf.anchoredPosition = new Vector2(directionX*fadeInDistance*curve, directionY*fadeInDistance*curve);
                if ( fadeOutAlpha ) { canvasGroup.alpha = (1.0f - curve); }
                break;
            }
        }
    }

    //-- パネルの出現
    public void PanelAppear() {
        this.gameObject.SetActive(true);
        StartChange();
    }

    //-- パネルを切り替える(_name : 切り替え先のパネルオブジェクト名)
    public void PanelChange(string _name) {
        panel_name = _name;
        if ( fadeOutMode == FadeMode.None ) { FinishOutChange(); }
        else { FadeOutPanel(); }
    }

    //-- パネルを閉じる
    public void PanelClose() {
        panel_name = "";
        if ( fadeOutMode == FadeMode.None ) { FinishOutChange(); }
        else { FadeOutPanel(); }
    }

    public void changeFadeIn(FadeDirection _direction) {
        fadeIn = _direction;
    }

    public void changeFadeOut(FadeDirection _direction) {
        fadeOut = _direction;
    }

    public void Stop() {
        if ( nowCoroutine != null ) StopCoroutine(nowCoroutine);
        isPlaying = false;
        nowState = PlayingState.Stop;
        nowCoroutine = null;
    }

    private void StartChange() {
        if ( onStart != null ) onStart.Invoke();
        if ( fadeInMode == FadeMode.None ) { FinishInChange(); }
        else { FadeInPanel(); }
    }

    private void FinishInChange() {
        canvasGroup.alpha = 1.0f;
        canvasGroup.interactable = true;
        rtf.anchoredPosition = new Vector2(0, 0);
    }

    private void FinishOutChange() {
        canvasGroup.alpha = 0.0f;
        if ( panel_name != "" ) {
            parent.transform.Find(panel_name).gameObject.SetActive(true);
        }
        if ( onFinish != null ) onFinish.Invoke();
        if ( panel_name == "" ) {
            parent.GetComponent<PanelChangerRoot>().PanelClose();
        }
        this.gameObject.SetActive(false);
    }

    private void FadeInPanel() {
        progress = 0.0f;
        canvasGroup.alpha = 0.0f;
        canvasGroup.interactable = false;
        setDirection(fadeIn);
        nowCoroutine = StartCoroutine(PlayFadeInCoroutine());
    }

    private void FadeOutPanel() {
        progress = 0.0f;
        canvasGroup.alpha = 1.0f;
        canvasGroup.interactable = false;
        setDirection(fadeOut);
        nowCoroutine = StartCoroutine(PlayFadeOutCoroutine());
    }

    private void setDirection(FadeDirection _direction) {
        switch(_direction) {
            case FadeDirection.Top    : directionX =  0; directionY =  1; break;
            case FadeDirection.Right  : directionX =  1; directionY =  0; break;
            case FadeDirection.Left   : directionX = -1; directionY =  0; break;
            case FadeDirection.Bottom : directionX =  0; directionY = -1; break;
        }
    }

    IEnumerator PlayFadeInCoroutine ()
    {
        if ( fadeInDelay > 0 ) { yield return new WaitForSeconds(fadeInDelay); }
        if ( isPlaying ) { yield break; }
        nowState = PlayingState.FadeIn;
        isPlaying = true;

        while ( progress < 1.0f ) {
            progress += Time.deltaTime / fadeInTime;
            yield return null;
        }

        isPlaying = false;
        nowState = PlayingState.Stop;
        progress = 1.0f;
        if ( onFinishFadeIn != null ) onFinishFadeIn.Invoke();
        FinishInChange();
    }

    IEnumerator PlayFadeOutCoroutine ()
    {
        if ( fadeOutDelay > 0 ) { yield return new WaitForSeconds(fadeOutDelay); }
        if ( isPlaying ) { yield break; }
        nowState = PlayingState.FadeOut;
        isPlaying = true;
        if(onStartFadeOut != null) onStartFadeOut.Invoke();

        while ( progress < 1.0f ) {
            progress += Time.deltaTime / fadeOutTime;
            yield return null;
        }

        isPlaying = false;
        nowState = PlayingState.Stop;
        progress = 1.0f;
        FinishOutChange();
    }
}

パネル管理用(親オブジェクト用)

このコンポーネント内のメソッドを通じてパネルの開閉を管理します。
PanelChanger.cs をアタッチしたパネルオブジェクトの親オブジェクトにアタッチします。

メソッド 内容
void PanelOpen() パネルを開きます
PanelChangerRoot.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class PanelChangerRoot : MonoBehaviour {

    public string Top;
    public UnityEvent onOpen;
    public UnityEvent onClose;

    //-- パネルを開く
    public void PanelOpen() {
        this.gameObject.SetActive(true);
        foreach ( Transform child in this.transform ) {
            child.gameObject.SetActive(false);
        }
        if ( onOpen != null ) { onOpen.Invoke(); }
        this.gameObject.transform.Find(Top).GetComponent<PanelChanger>().PanelAppear();
    }

    //-- パネルを閉じる
    public void PanelClose() {
        if ( onClose != null ) { onClose.Invoke(); }
        this.gameObject.SetActive(false);
    }
}

エディタ拡張

PanelChanger.cs のInspector上での変数表示変更を行います。
Assets/Editor/ 下に配置してください。

※ なくても動きます。

PanelChangerEditor.cs
using UnityEngine;
using UnityEngine.Events;
using System.Collections;
using UnityEditor;

[CustomEditor (typeof(PanelChanger))]
public class PanelChangerEditor : Editor
{
    public override void OnInspectorGUI() {
        PanelChanger _panel = target as PanelChanger;

        _panel.fadeInMode = (PanelChanger.FadeMode)EditorGUILayout.EnumPopup("fadeInMode", _panel.fadeInMode);
        if ( _panel.fadeInMode == PanelChanger.FadeMode.Fade ) {
            _panel.fadeIn = (PanelChanger.FadeDirection)EditorGUILayout.EnumPopup("fadeIn", _panel.fadeIn);
            _panel.fadeInTime = EditorGUILayout.FloatField("fadeInTime", _panel.fadeInTime);
            _panel.fadeInDelay = EditorGUILayout.FloatField("fadeinDelay", _panel.fadeInDelay);
            _panel.fadeInDistance = EditorGUILayout.FloatField("fadeinDistance", _panel.fadeInDistance);
            _panel.fadeInAlpha = EditorGUILayout.Toggle("fadeinAlpha", _panel.fadeInAlpha);
            _panel.fadeInAnimationCurve = EditorGUILayout.CurveField("fadeInAnimationCurve", _panel.fadeInAnimationCurve);
        }
        SerializedProperty onStart = serializedObject.FindProperty("onStart");
        EditorGUILayout.PropertyField(onStart);
        if ( _panel.fadeInMode == PanelChanger.FadeMode.Fade ) {
            SerializedProperty onFinishFadeIn = serializedObject.FindProperty("onFinishFadeIn");
            EditorGUILayout.PropertyField(onFinishFadeIn);
        }

        _panel.fadeOutMode = (PanelChanger.FadeMode)EditorGUILayout.EnumPopup("fadeOutMode", _panel.fadeOutMode);
        if ( _panel.fadeOutMode == PanelChanger.FadeMode.Fade ) {
            _panel.fadeOut = (PanelChanger.FadeDirection)EditorGUILayout.EnumPopup("fadeOut", _panel.fadeOut);
            _panel.fadeOutTime = EditorGUILayout.FloatField("fadeOutTime", _panel.fadeOutTime);
            _panel.fadeOutDelay = EditorGUILayout.FloatField("fadeOutDelay", _panel.fadeOutDelay);
            _panel.fadeOutDistance = EditorGUILayout.FloatField("fadeOutDistance", _panel.fadeOutDistance);
            _panel.fadeOutAlpha = EditorGUILayout.Toggle("fadeOutAlpha", _panel.fadeOutAlpha);
            _panel.fadeOutAnimationCurve = EditorGUILayout.CurveField("fadeOutAnimationCurve", _panel.fadeOutAnimationCurve);
            SerializedProperty onStartFadeOut = serializedObject.FindProperty("onStartFadeOut");
            EditorGUILayout.PropertyField(onStartFadeOut);
        }
        SerializedProperty onFinish = serializedObject.FindProperty("onFinish");
        EditorGUILayout.PropertyField(onFinish);

        if (GUI.changed) {
            serializedObject.ApplyModifiedProperties();
        }

        EditorUtility.SetDirty( target );
    }

}

使い方

準備

上記の3つのスクリプトをあらかじめ用意しておきます。
(なお PanelChangerEditor.cs に関しては Assets/Editor/PanelChangerEditor.cs となるように配置しておいてください。

表示させたいパネルを用意します。
今回は Menu を親として MenuTopMenuNext を用意しました。
a13a7caa83f52bce2bcaf9e4671fc406.png

親オブジェクト Menu

今回は分かりやすくするために MenuTopMenuNext それぞれに色付けを行うので透明にしてあります。
実際には Menu 下のパネル表示の際の背景となります。
1d2f48ebe8e38afa62084efb72818f45.png

子オブジェクト MenuTop

MenuTop 自体は透明な枠として使用しており、その下の階層に様々なUIを自由に配置してしまいます。
今回は MenuNext に遷移するためのボタンと閉じるボタンの2つのみを用意。
Panel は分かりやすくするために赤色背景にしてあります。
f9f3e0f7f6a6d88fcd4f719b57fd4386.png

子オブジェクト MenuNext

上に同じく。
Panel は分かりやすくするために青色背景にしてあります。
6027b7c8744e78105e6a8e2a8e52b783.png

メニューを開くためのボタン

メニューを開くためのイベントが必要なので今回はとりあえず UI という名前のパネルにボタンをのっけておきます。
e1464c26d80b53a2bca36d3ca830b276.png

コンポーネントのアタッチ

親オブジェクト Menu

PanelChangerRoot.cs をアタッチします。
Inspector上に Top という文字列変数が存在するのでここにパネルを開いた際に最初に表示されるパネルのオブジェクト名(今回だと MenuTop )を記述しておきます。
以下の2つのイベントがあります。

イベント名 タイミング
OnStart() そのオブジェクト以下のパネルが開かれた時
OnClose() そのオブジェクト以下のパネルを閉じる時

6d39f6b949592c452b283d63cc959cd9.png

子オブジェクト

PanelChanger.cs をアタッチします。
基本的にアタッチしておくだけで問題ないです。同時に CanvasGroup というコンポーネントが追加されます。
MenuNext の方にもつけておきます。
初期状態では以下の2つのイベントがあります。

イベント名 タイミング
OnStart() そのパネルが開き始め、アクティブになる時
OnFinish() そのパネルが切り替わり、非アクティブになる時

310b60ff281602a5b799be9b20a07d57.png

メソッドをたたく

パネル切り替え

パネルを切り替えるためのメソッドを叩きます。
ということで用意してあった Button のイベント OnClick() から そのタイミングで表示されているパネル( MenuTop )PanelChanger.cs にある PanelChange(string _name) を叩きに行きます。
引数ですが切り替えたい対象のパネルのオブジェクト名( "MenuNext" )を与えてやります。
同様にして PanelNext の方にもつけてやります。
878bdb5d9076fb9d258fb5e464ff37c4.png

パネルを閉じる

パネルを閉じるためのメソッドを叩きます。
といっても先ほどの切り替えとさほど変わりません。
同様にして ButtonClose のイベント OnClick() から PanelChanger.cs にある PanelClose() を叩きに行きます。
同様にして PanelNext の方にもつけてやります。
1fbae71e9e726df4c871ea0810f7d712.png

パネルを開く

パネルを開くためのメソッドを叩きます。
ということで用意してあった UI -> Button のイベント OnClick() から先ほどのパネルたちの親オブジェクト( Menu )の PanelChangerRoot.cs にある PanelOpen() を叩きに行きます。
0695029ff6269bbfa2c771ec12091a49.png

動作確認

以上で動きます。
9c953a64d48002b4bbe508f0718e0f4e.gif

切り替えアニメーション

Inspector上で PanelChanger.cs の変数等をいじることで簡易なアニメーションをさせられます。
310b60ff281602a5b799be9b20a07d57.png

fadeInMode , fadeOutModeNone から Fade に切り替えるとにゅっと色々な要素が出てきます。ここだけがエディタ拡張の仕事です
04b2c1c6d6e217ad5dee8f0e8651dfa8.png

それぞれの変数は以下の通りです。
各パネルごとに別々に調整が可能なので好き勝手いじると動いてくれます。

変数名 内容
fadeIn フェードイン時に入ってくる方向
fadeInTime フェードインにかかる時間
fadeInDelay フェードイン開始タイミング
fadeInDistance フェードインの距離
fadeInAlpha フェードインの際にalpha値(透明度)も変化させるか
fadeInAnimationCurve フェードインの移動量変化の遷移
fadeOut フェードアウト時に入ってくる方向
fadeOutTime フェードアウトにかかる時間
fadeOutDelay フェードアウト開始タイミング
fadeOutDistance フェードアウトの距離
fadeOutAlpha フェードアウトの際にalpha値(透明度)も変化させるか
fadeOutAnimationCurve フェードアウトの移動量変化の遷移

またその他にも以下の2つのイベントが追加されます。

イベント名 タイミング
OnFinishFadeIn() フェードインが終了した時
OnStartFadeOut() フェードアウトが開始した時

フェード例

4f0369d95f2e1dc3101a056ea41c219a.png
bad43b181452cdef82af840c718f8ed4.gif

おわりに

正直なところ現状『戻る』、『キャンセル』ボタンに対応できるような フェードインの時とは逆向きに戻っていく 処理ができていませんし、何なら4方向のフェードしかできません。
ということで現状は

  • 戻るメソッド
  • 独自のアニメーションに対応
  • その他切り替え方法の簡易設定

あたりを追加してみたいなと思ったりはしていますが予定は未定です。
特にAssetは使ってないのでgithubに。

赤青背景を透明にして親オブジェクトを若干黒めな背景にしたりするとそれっぽい。
77c39f95958b808d5622101635c12313.gif

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした