LoginSignup
14
11

More than 5 years have passed since last update.

UniRxでTweenアニメーション

Last updated at Posted at 2015-10-16

UniRXとは

UnityでReactive Extensionsが使えるようになる素晴らしいアセット。ぜひ使いましょう。
UniRx - Reactive Extensions for Unity

イージングの計算式

開始位置, 移動距離, Tweenの持続時間, 経過時間
から現在の位置を計算する必要がある。

こちらを参考にさせてもらった。
イージング処理の計算式 - 強火で進め
Robert Penner's Easing Functions

とりあえず今回はEaseInOutExpoを作成してみる。

public static class Tween
{
    /// <param name="time">経過時間</param>
    /// <param name="initial">初期値</param>
    /// <param name="delta">終了値と初期値の差分</param>
    /// <param name="duration">存続時間</param>
    public static Vector3 EaseInOutExpo(float time, Vector3 initial, Vector3 delta, float duration)
    {
        if (time <= 0f)
        {
            return initial;
        }

        if (time >= duration)
        {
            return initial + delta;
        }

        time /= (duration / 2f);
        if (time < 1f)
        {
            return delta / 2f * Mathf.Pow(2f, 10f * (time - 1f)) + initial;
        }

        time -= 1f;
        return delta / 2f * (-1f * Mathf.Pow(2f, -10f * time) + 2f) + initial;
    }
}

アニメーションさせてみる

Tweenの持続時間の間だけ(TakeWhile)経過時間を流す(Select)

経過時間から現在位置を計算(Select)

計算した現在位置を反映する(Subscribe)

という手順でやってみる。

void Start()
{
    var start = Time.time;               // 開始時刻
    var initial = transform.position;    // 開始位置
    var delta = new Vector3(5f, 0f, 0f); // 移動距離
    var duration = 3f;                   // Tweenの持続時間

    gameObject.UpdateAsObservable()
        .Select(_ => Time.time - start) // 経過時間を流す
        .TakeWhile(time => time <= duration) // 持続時間の間だけ
        .Select(time => Tween.EaseInOutExpo(time, initial, delta, duration)) // 経過時間から現在位置を計算
        .Subscribe(pos => transform.position = pos); // 計算した値を反映する
}

出来た!
cube_1.gif

Repeatしたい

void Start()
{
    var start = Time.time;               // 開始時刻
    var initial = transform.position;    // 開始位置
    var delta = new Vector3(5f, 0f, 0f); // 移動距離
    var duration = 3f;                   // Tweenの持続時間

    gameObject.UpdateAsObservable()
        .Select(_ => Time.time - start) // 経過時間を流す
        .TakeWhile(time => time <= duration) // 持続時間の間だけ
        .Select(time => Tween.EaseInOutExpo(time, initial, delta, duration)) // 経過時間から現在位置を計算
        .Repeat() // 繰り返し
        .Subscribe(pos => transform.position = pos); // 計算した値を反映する
}

Repeatを追加しただけでは上手くいかない。
Subscribeする前に開始時刻を取得してしまっているので繰り返し2回目以降のTakeWhileの判定式が常に偽になってしまう。
そこで開始時刻の取得タイミングを修正してみる。

void Start()
{
    var initial = transform.position;    // 開始位置
    var delta = new Vector3(5f, 0f, 0f); // 移動距離
    var duration = 3f;                   // Tweenの持続時間

    Observable.Empty<float>().StartWith(() => Time.time) // 繰り返しごとに開始時刻を再取得する
        .SelectMany(start => gameObject.UpdateAsObservable().Select(_ => Time.time - start)) // 経過時間を流す
        .TakeWhile(time => time <= duration) // 持続時間の間だけ
        .Select(time => Tween.EaseInOutExpo(time, initial, delta, duration)) // 経過時間から現在位置を計算
        .Repeat() // 繰り返し
        .Subscribe(pos => transform.position = pos); // 計算した値を反映する
}

Repeat出来た!!

IObservableの拡張メソッド化すると捗りそう

public static class Tween
{
    /// <param name="time">経過時間</param>
    /// <param name="initial">初期値</param>
    /// <param name="delta">終了値と初期値の差分</param>
    /// <param name="duration">存続時間</param>
    public static Vector3 EaseInOutExpo(float time, Vector3 initial, Vector3 delta, float duration)
    {
        /* 省略 */
    }

    /// <param name="initial">初期値</param>
    /// <param name="delta">終了値と初期値の差分</param>
    /// <param name="duration">存続時間</param>
    public static IObservable<Vector3> EaseInOutExpo<T>(this IObservable<T> observable, 
                                                        Vector3 initial, Vector3 delta, float duration)
    {
        return Observable.Empty<float>().StartWith(() => Time.time)
            .SelectMany(start => observable.Select(_ => Time.time - start))
            .TakeWhile(time => time <= duration)
            .Select(time => EaseInOutExpo(time, initial, delta, duration));
    }

    /// <param name="dest">移動先</param>
    /// <param name="duration">持続時間</param>
    public static IObservable<Vector3> MoveTo<T>(this IObservable<T> observable, 
                                                 GameObject gameObject, Vector3 dest, float duration, EaseKind kind)
    {
        return Observable.Empty<Vector3>().StartWith(() => gameObject.transform.position)
            .SelectMany(src => observable..Ease(src, dest - src, duration, kind))
            .Do(x => gameObject.transform.position = x);
    }
}

こうする

void Start()
{
    gameObject.UpdateAsObservable()
        .EaseInOutExpo(transform.position, new Vector3(5f, 0f, 0f), 3f)
        .Repeat()
        .Subscribe(pos => transform.position = pos);
}
void Start()
{
    gameObject.UpdateAsObservable()
        .MoveTo(gameObject, new Vector3(5f, 0f, 0f), 3f, Tween.EaseKind.EaseInCubic)
        .Subscribe(x => Debug.Log("アニメーション動作中"), () => Debug.Log("アニメーション終了!"));
}

UniRX面白いですね。

14
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
11