UniRxでTweenアニメーション


## UniRXとは ##

UnityでReactive Extensionsが使えるようになる素晴らしいアセット。ぜひ使いましょう。
[UniRx - Reactive Extensions for Unity](https://www.assetstore.unity3d.com/jp/#!/content/17276)


## イージングの計算式 ##

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

こちらを参考にさせてもらった。
[イージング処理の計算式 - 強火で進め](http://d.hatena.ne.jp/nakamura001/20111117/1321539246)
[Robert Penner's Easing Functions](http://robertpenner.com/easing)


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

```C#
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)

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

```C#
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](https://qiita-image-store.s3.amazonaws.com/0/67518/9d5cb7fb-4b05-15ec-91d9-108382bae492.gif "cube_1.gif")

## Repeatしたい ##

```C#
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の判定式が常に偽になってしまう。
そこで開始時刻の取得タイミングを修正してみる。

```C#
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の拡張メソッド化すると捗りそう ##

```C#
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);
    }
}
```

こうする

```C#
void Start()
{
	gameObject.UpdateAsObservable()
		.EaseInOutExpo(transform.position, new Vector3(5f, 0f, 0f), 3f)
		.Repeat()
		.Subscribe(pos => transform.position = pos);
}
```

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

UniRX面白いですね。