UrMotion でめっちゃ簡単にモーションを作る for Unity

[椿](https://twitter.com/tsubaki_t1)さんの↓が面白そうだったので作ってみました。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"><a href="https://twitter.com/tsubaki_t1">@tsubaki_t1</a> いや、自分の求めてる動きは多分こっちか <a href="https://t.co/3F7uEoCfLd">pic.twitter.com/3F7uEoCfLd</a></p>&mdash; 椿 (@tsubaki_t1) <a href="https://twitter.com/tsubaki_t1/status/709758162530738177">2016年3月15日</a></blockquote>

↓完成したのがこちら

![spiral_2.gif](https://qiita-image-store.s3.amazonaws.com/0/112019/a65462fb-3d8c-15a8-c9df-4ae64c4b9b9a.gif)

## Step1 GameObject 作成

とりあえず12個 `Instantiate` して作っておきます。全部同じ位置に生成してるので重なってます。

```csharp
for (var i = 0; i < 12; ++i) {
	g = GameObject.Instantiate(prefab);
	g.transform.SetParent(prefab.transform.parent);
	g.transform.localPosition = Vector3.zero;
	g.transform.localScale = Vector3.one;
}
```

![step1.gif](https://qiita-image-store.s3.amazonaws.com/0/112019/5f3f5539-2e62-20dc-af65-dd635b9d529d.gif)

## Step2 回す

`.MotionP()` を使うと XY 座標を動かすモーションを定義出来ます。これにメソッドチェインする形で動き方を指定していきます。まずは `.Circular()` を指定しました。Circular は円周を回る動きになります。パラメータは半径と速度です。とりあえず適当な値を指定しておきます。

```csharp
for (var i = 0; i < 12; ++i) {
	g = GameObject.Instantiate(prefab);
	g.transform.SetParent(prefab.transform.parent);
	g.transform.localPosition = Vector3.zero;
	g.transform.localScale = Vector3.one;
	
	g.MotionP().Circular(83f, 0.75f);
}
```

![step2.gif](https://qiita-image-store.s3.amazonaws.com/0/112019/69f6fbf9-3557-cab6-e32e-f998e38b6e3e.gif)

### Step3 円状に並べる

`.Angle()` を使用して個々の GameObject の動きを 30 度ずつずらします。これにより、円状に並ぶことになります。

```csharp
for (var i = 0; i < 12; ++i) {
	g = GameObject.Instantiate(prefab);
	g.transform.SetParent(prefab.transform.parent);
	g.transform.localPosition = Vector3.zero;
	g.transform.localScale = Vector3.one;
	
	var angle = 30f * i;
	g.MotionP().Circular(83f, 0.75f).Angle(angle);
}
```

![step3.gif](https://qiita-image-store.s3.amazonaws.com/0/112019/b68baec4-8128-0c06-34fc-11536b4f9195.gif)

### Step4 半径を変化させる

各パラメータは定数だけでなく、時間変化する値を指定することもできます。これを利用して、`.Circular()` の半径を時間と共に小さくするようにします。

`Velocity.AccelByRatio(v, r)` は毎フレーム `v` に `r` が乗算された値を生成できます。`r > 1.0f` とすれば増幅する値を生成でき、`r < 1.0f` とすれば減衰していく値を生成できます。ここでは `218f` から始まって、毎フレーム `0.92f` の割合で小さくなっていくようにしています。

これだと最終的に値が `0f` に収束してしまうので、 `.Offset(83f)` で `83f` を足すことで、最小でも半径が `83f` になるようにします。

```csharp
for (var i = 0; i < 12; ++i) {
	g = GameObject.Instantiate(prefab);
	g.transform.SetParent(prefab.transform.parent);
	g.transform.localPosition = Vector3.zero;
	g.transform.localScale = Vector3.one;
	
	var angle = 30f * i;
	var radius = Velocity.AccelByRatio(218f, Source.Constant(0.92f)).Offset(83f);
	g.MotionP().Circular(radius, 0.75f).Angle(angle);
}
```

![step4.gif](https://qiita-image-store.s3.amazonaws.com/0/112019/c92e51a7-f365-7108-23a5-fcb926ddad04.gif)

### Step5 速度を変化させる

同様にして、速度も減衰させるようにします。だいぶそれっぽくなってきましたね！

```csharp
for (var i = 0; i < 12; ++i) {
	g = GameObject.Instantiate(prefab);
	g.transform.SetParent(prefab.transform.parent);
	g.transform.localPosition = Vector3.zero;
	g.transform.localScale = Vector3.one;
	
	var angle = 30f * i;
	var radius = Velocity.AccelByRatio(218f, Source.Constant(0.92f)).Offset(83f);
	var speed = Velocity.AccelByRatio(0.75f, Source.Constant(0.94f));
	g.MotionP().Circular(radius, speed).Angle(angle);
}
```

![step5.gif](https://qiita-image-store.s3.amazonaws.com/0/112019/0af5578f-89d6-1097-a2a7-6f083d808c6a.gif)

### Step6 味付け

最後に少し味付けしていきます。まず、速度に `.Offset(0.01f)` することで、定位置に着いた後も余韻としてちょっとだけ回転を続けるようにします。

```csharp
for (var i = 0; i < 12; ++i) {
	g = GameObject.Instantiate(prefab);
	g.transform.SetParent(prefab.transform.parent);
	g.transform.localPosition = Vector3.zero;
	g.transform.localScale = Vector3.one;
	
	var angle = 30f * i;
	var radius = Velocity.AccelByRatio(218f, Source.Constant(0.92f)).Offset(83f);
	var speed = Velocity.AccelByRatio(0.75f, Source.Constant(0.94f)).Offset(0.01f);;
	g.MotionP().Circular(radius, speed).Angle(angle);
}
```

![step6.gif](https://qiita-image-store.s3.amazonaws.com/0/112019/a6358193-6a53-db02-ec1a-ae5bf5dcfb13.gif)

次に、[Fractional Brownian Motion](https://ja.wikipedia.org/wiki/%E9%9D%9E%E6%95%B4%E6%95%B0%E3%83%96%E3%83%A9%E3%82%A6%E3%83%B3%E9%81%8B%E5%8B%95) `.Fbm()` で揺らぎを生成して、`.AmplifyComponents()` でその揺らぎを X 方向に `0f` 倍、Y 方向に `2f` 倍することで、縦方向だけに少々うにょうにょさせます。

```csharp
for (var i = 0; i < 12; ++i) {
	g = GameObject.Instantiate(prefab);
	g.transform.SetParent(prefab.transform.parent);
	g.transform.localPosition = Vector3.zero;
	g.transform.localScale = Vector3.one;
	
	var angle = 30f * i;
	var radius = Velocity.AccelByRatio(218f, Source.Constant(0.92f)).Offset(83f);
	var speed = Velocity.AccelByRatio(0.75f, Source.Constant(0.94f)).Offset(0.01f);;
	g.MotionP().Circular(radius, speed).Angle(angle).Fbm(new Vector2(0f, 1f), 3).AmplifyComponents(new Vector2(0f, 0.3f));
}
```

![spiral_2.gif](https://qiita-image-store.s3.amazonaws.com/0/112019/a65462fb-3d8c-15a8-c9df-4ae64c4b9b9a.gif)

これにて完成です！組み合わせとパラメータの調整で面白い動きを数行で作れるのが UrMotion の強みですね。

UrMotion のソースとその他のサンプルは以下からどうぞ：

 * https://github.com/oinkgms/urmotion