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); // 計算した値を反映する
}
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面白いですね。