imog

主にUnityとかの事を書いています

Unityでシンプルなポップアップを実装する

スマホゲーとかでよく出るやつを作ってみた。

f:id:adarapata328:20160725232256g:plain

github.com

最近だと横からニュッと出るやつとか下からシュッと出るやつとかあるけど、今回は昔からあるダイアログ形式のやつを実装してみた。

まずは、シンプルに任意の時間でオブジェクトを拡大縮小させるTweenScaleクラスを作る。

UniRxがインポートされている前提なのでいますぐアセットストアから落としてこよう。

TweenScale.cs

using UnityEngine;
using System.Collections;
using UniRx;
using UniRx.Triggers;
using UnityEngine.Events;
using System;

[Serializable]
public class TweenScale {
    public Vector3 from, to;
    public float duration;
    public AnimationCurve curve = new AnimationCurve(new Keyframe(0,0), new Keyframe(1,1));
    public UnityEvent onBegin, onEnd;
    private GameObject target;

    private Subject<Unit> scaleStartStream = new Subject<Unit>();
    public IObservable<Unit> scaleStartAsObservable { get { return scaleStartStream.AsObservable(); } }

    private Subject<Unit> scaleEndStream = new Subject<Unit>();
    public IObservable<Unit> scaleEndAsObservable { get { return scaleEndStream.AsObservable(); } }

    public void Setup(GameObject t)
    {
        target = t;
        scaleStartAsObservable.Subscribe(_ => onBegin.Invoke());
        scaleEndAsObservable.Subscribe(_ => onEnd.Invoke());
        scaleEndAsObservable.Subscribe(_ => target.transform.localScale = Vector3.Lerp(from, to, curve.Evaluate(1.0F)));
    }

    public void Play()
    {
        scaleStartStream.OnNext(Unit.Default);
        Observable.EveryFixedUpdate()
            .Take(System.TimeSpan.FromSeconds(duration))
            .Select(_ => Time.fixedDeltaTime)
            .Scan((acc, current) => acc + current)
            .Subscribe(time => {
                float t = time / duration;
                target.transform.localScale = Vector3.Lerp(from, to, curve.Evaluate(t));
            },
            _ => {},
            () => scaleEndStream.OnNext(Unit.Default)
            ).AddTo(target);
    }
}

結構ゴチャゴチャしてるけど、実際の拡大縮小処理はこのストリームにまとまっている

scaleStartStream.OnNext(Unit.Default);
Observable.EveryFixedUpdate()
    .Take(System.TimeSpan.FromSeconds(duration))
    .Select(_ => Time.fixedDeltaTime)
    .Scan((acc, current) => acc + current)
    .Subscribe(time => {
        float t = time / duration;
        target.transform.localScale = Vector3.Lerp(from, to, curve.Evaluate(t));
    },
    _ => {},
    () => scaleEndStream.OnNext(Unit.Default)
    ).AddTo(target);

呼ばれた時点で scaleEndStream.OnNext(Unit.Default) を呼んでスケーリング開始時のイベントを発行する。 画像だとその時点で暗転用のオブジェクトをActiveにしている。

線形補間でスムーズにスケーリングさせるので、経過時間(time) / スケーリングにかける時間(duration)で現在の位置tを出す。

tを出したら挙動はAnimationCurveが持ってるので curve.Evalute(t) で現在の値を返すようにする。

duration秒経過したらストリームが終了するのでその時点で scaleEndStream.OnNext(Unit.Default) を呼んで終了時のイベントを発行する。

で、開くときと閉じる時の挙動は別々で用意したいことが多い気がしたので、上記クラスを二つ持たせたPopupコンポーネントを定義する。

Popup.cs

using UnityEngine;
using System.Collections;
using UniRx;

public class Popup : MonoBehaviour {
    public enum State { Open, Close, UnUsed }

    public State state { get; private set; }
    public TweenScale open, close;
    void Start()
    {
        open.Setup(gameObject);
        open.scaleEndAsObservable.Subscribe(_ => state = State.Open);
        close.Setup(gameObject);
        close.scaleEndAsObservable.Subscribe(_ => state = State.Close);
    }

    public void Open()
    {
        open.Play();
    }
    public void Close()
    {
        close.Play();
    }

    public void Toggle() {
        switch(state)
        {
        case State.UnUsed:
        case State.Close:
            open.Play();
            break;
        case State.Open:
            close.Play();
            break;
        }
    }
}

このコンポーネントはOpenとCloseしかできないシンプルなコンポーネント。 Toggleは開いてたら閉じる、閉じてたら開くけどあんまり使わないかもしれない。

実際にポップアップ使うとなると、ただオブジェクトが飛び出てくるだけでなくそれに付随した様々な処理が入ってくると思う。サンプルみたいに他の部分をタッチできないように暗幕を置いたりするなど。

その辺を色々考慮し始めるとキリがないので、UnityEventsを使ってインスペクタから別のオブジェクトにメッセージ飛ばせるようにしている。

https://i.gyazo.com/f7b488f32f4cc744dafe6154da94fa57.png

こっちの方がデザイナが演出とかの組み込みがやりやすそう。

スクリプトで書きたいなら scaleStartAsObservablescaleEndAsObservable プロパティがあるのでそこから購読できる。

ちなみにUnityEventsを使ってはいるが、最終的には scaleStartStreamscaleEndStream の二本のストリームに集約しているので若干コードはすっきりした。

https://i.gyazo.com/dac543bd6dabb94eb69350503f247ec7.gif

入れ子みたいにしても気持ちいい。

UnityPackageにしたのでご自由にお使いください。 https://github.com/adarapata/SimplyPopup/releases/download/0.1.0/SimplyPopup_0_1.unitypackage

おまけ

生成したポップアップオブジェクトのscaleEndAsObservableを購読して数珠繋ぎに消す