Unityでイージングをいじいじ with DOTween

この記事は[UT-virtual Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ut-virtual)の5日目の記事です．僕はUnityで簡単にイージングを実装できるDOTweenについて寄稿します．

# イージングとはなんぞや？
そもそも**イージング（Easing）**とはなんなのでしょうか？イージングとは，**移動等のアニメーションにおいて，その動きを加速・減速することにより，変化に緩急をつけること**です．これを用いると，スタートからゴールへの移動について

- スタートとゴールでは速度がほぼ0だが間では高速
- ゴールで勢い余って通り過ぎ，振動してゴールに収束

などといった表現が可能になります．

例えばこちらの2つの動画を見比べてください．
<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">Advent Calendarのネタを考えていたらUnity1weekのネタが出来上がりました <a href="https://t.co/J3kCRPsTPb">pic.twitter.com/J3kCRPsTPb</a></p>&mdash; らでぃっしゅ (@radi_bow) <a href="https://twitter.com/radi_bow/status/1064806407772356608?ref_src=twsrc%5Etfw">November 20, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">イージングをちゃんとやるとこんな感じにぬるぬる動いて良き．<a href="https://t.co/QPdeJu4BDo">https://t.co/QPdeJu4BDo</a> <a href="https://t.co/BVezfci94G">pic.twitter.com/BVezfci94G</a></p>&mdash; らでぃっしゅ (@radi_bow) <a href="https://twitter.com/radi_bow/status/1067330202143940608?ref_src=twsrc%5Etfw">November 27, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

前者がイージング無し，後者がイージング有りの動画です．後者の方が，前者よりもオブジェクトがぬるぬる動いているのが見て取れると思います．

イージングの具体的な様子を掴むために，ここでは[Easing Function早見表](https://easings.net/ja)を引用しておきます．経過時間に対してどのくらい変位するかを表す**イージング関数**を定めることで，イージングは表現されます．先に述べた2つの例は，それぞれ`easeInOutExpo`・`easeOutElastic`に対応したものです．

もちろん自分でイージング関数を設計して実装するのも可能ですが，[Easing Function早見表](https://easings.net/ja)で取り上げられているイージング関数はWebデザイン等の文脈でどれも一般的なものです．この先人の知恵を有難く使うことで，手軽にクールなアニメーションを作成することが可能です．

# DOTweenの使い方
**DOTween**とは，イージングをUnityで簡単に実装できるようになるアセットです．AssetStoreから[無料版](https://assetstore.unity.com/packages/tools/animation/dotween-hotween-v2-27676)をダウンロードできます[^1]．以下ではDOTweenをインポート済みのUnityプロジェクト内で，どのようにDOTweenを使えばいいのかを説明していきます．

## 関数
DOTweenをスクリプト内で使用するには，専用の名前空間を宣言する必要があります．すなわち，**スクリプトの最初で**

```cs
using DG.Tweening;
```

**と宣言する必要があります．**以下のコード例ではこの宣言を省略していますが，忘れずに書きましょう．

関数を用いてアニメーションを実装するのですが，その使い方には，ラムダ式を用いる方法とそれを省略した方法の2つがあります．ラムダ式を使うならば，

```cs
DOTween.To(()=> val, x=> val = x, to, duration);
```

とします．ここで`val`はアニメーションをつけたい値，`to`はアニメーションの終点の値（`val`の型に合わせて，`int`とか`Vector3`とか…），`duration`はアニメーションの持続時間を表す`float`の値です．この`DOTween.To()`関数を使えば，移動やスケール変換など，どのような状況でもアニメーションをつけることが可能です．

一方，この書き方を省略した書き方もあります．例えばある`transform`を`duration`だけ時間を使って，`val`までx軸方向に動かしたいときは，`DOMoveX()`関数を使って

```cs
transform.DOMoveX(val, duration);
```

としたり，スケールを`duration`だけ時間を使って`val`に変えたいときは，`DOScale()`関数を使って

```cs
transform.DOScale(val, duration);
```

としたりする，といった具合です．ラムダ式で書く`DOTween.To()`関数と比べて一般性が失われる一方で，可読性が高いという特徴があります．

## イージングの設定
次にイージングの設定方法を説明します．

```cs
transform.DOMoveX(0, 2.0f)
         .SetEase(Ease.OutQuint);
```

のように，関数の後ろに`.SetEase()`をつけることで，イージングを設定することが可能です．この引数には，`Ease`クラスに含まれるイージング関数の名前を指定します．[Easing Function早見表](https://easings.net/ja)から，自分の実現したいアニメーションに対応するイージング関数を探し，それを指定しましょう．

# 実践例
実際にゲームのどこでイージングが使えるか解説するために，今回は[Unity1週間ゲームジャム](https://unityroom.com/unity1weeks)に筆者が投稿したゲーム：[10Counts](https://unityroom.com/games/10counts)を例として挙げます[^2]．
<img width=60% alt="title.png" src="https://qiita-image-store.s3.amazonaws.com/0/212951/08a082ca-0fbd-8567-8a3b-db07fc46e92c.png">
これは上から落ちてくる長方形を，左右に移動し避け続けて，高いレベルを目指すゲームです．このゲームの様々なところでイージングを用いています．これを1つずつ見ていきましょう．

## 移動
自機の正方形の移動ですが，ここには`Ease.InOutElastic`を指定してスチャっとした左右の移動アニメーションを実現しています．
<img src="https://qiita-image-store.s3.amazonaws.com/0/212951/6aee7ba5-c97b-9149-32ee-aa7584c4a948.gif" width = 60% alt="player.png">
具体的には次の通りに実装しています．

``` PlayerController.cs
public int field; // 自機の場所を離散的に示す，0-9の値

void Update () {
    if(Input.GetKeyDown(KeyCode.LeftArrow) && field > 0){
        field--;
        this.GetComponent<Transform>().DOMove(CalculateField(field), 0.2f)
            .SetEase(Ease.InOutElastic);
    }
    if(Input.GetKeyDown(KeyCode.RightArrow) && field < 9){
        field++;
        this.GetComponent<Transform>().DOMove(CalculateField(field), 0.2f)
            .SetEase(Ease.InOutElastic);
    }
}

Vector3 CalculateField(int num){
    // numから画面上の実際の位置を計算する関数
}
```

また，上から落ちてくる長方形の落ち方には，黄色には`Ease.InElastic`，青色には`Ease.Linear`，緑色には`Ease.InExpo`をそれぞれ適用しています．
<img src="https://qiita-image-store.s3.amazonaws.com/0/212951/cd3c5349-4c13-fac6-49f3-7c35d82fdd1a.gif" width = 60% alt="drop.png">
具体的には次のようなことを記述したスクリプトを長方形Prefabにつけておき，生成しています．

``` YellowBulletDrop.cs
void Start () {
    this.GetComponent<Transform>().DOMoveY(-5.0f, 3.0f)
        .SetEase(Ease.InElastic);
}
```

``` BlueBulletDrop.cs
void Start () {
    this.GetComponent<Transform>().DOMoveY(-5.5f, 1.5f)
        .SetEase(Ease.Linear);
}
```

``` GreenBulletDrop.cs
void Start () {
    this.GetComponent<Transform>().DOMoveY(-5.5f, 1.5f)
        .SetEase(Ease.InExpo);
}
```

`DOMoveY()`関数の，終点を決める第一引数や持続時間を決める第二引数の値は，実際に実行しながら調節しました．

## 色
DOTweenではオブジェクトの移動だけではなく，画像の色や音などについてもイージングさせることが可能です．今回は，ゲームスタートから10秒経過する度に，背景色に`Ease.InOutElastic`を適用してレベルアップを表現しています．

``` CameraLightController.cs
public void Flash(){
    this.GetComponent<Camera>().DOColor(Color.white, 1.0f)
        .SetLoops(2, LoopType.Yoyo)
        .SetEase(Ease.InOutElastic);
}
```

特に`SetLoops(2, LoopType.Yoyo)`とすることで，1回背景色を白くした後，ヨーヨーのように逆再生で元の色に戻るという動きを実現しました．
<img alt="levelup.gif" width=60% src="https://qiita-image-store.s3.amazonaws.com/0/212951/6882a8be-9ec7-85aa-22d1-b36204bb0d12.gif">


## Sequenceと回転・スケール変換 
自機に長方形が当たったときの，回転しながら小さくなっていくアニメーションもDOTweenで作成しています．
<img alt="gameover.gif" width=60% src="https://qiita-image-store.s3.amazonaws.com/0/212951/4423688b-11ed-1b7d-2c18-d1bc2e1bd106.gif">
ここでは**Sequence**というDOTweenの機能を使いました．

``` PlayerController.cs
public Sequence sequence;

private void OnTriggerEnter2D(Collider2D collision){
    if (collision.gameObject.tag == "Bullet"){
        sequence = DOTween.Sequence()
                          .Append(this.GetComponent<Transform>().DOScale(0.6f, 1.0f).SetEase(Ease.InExpo))
                          .Join(this.GetComponent<Rigidbody2D>().DORotate(360.0f, 1.0f).SetEase(Ease.OutExpo));
        sequence.Play();
        audioSource.PlayOneShot(destroySound); // 衝突時のサウンド再生
        Destroy(collision.gameObject);
    }
}
```

Sequenceを使うと，複数のアニメーションを同時に再生したり，あるアニメーションの後に数秒待って別のアニメーションを再生したり，といったことが可能です．ここでは`Append()`関数で空のSequenceにスケール変換のアニメーションを追加し，その後に`Join()`関数を用いて，回転のアニメーションを同時再生するようにしています．

# おわりに
この記事では，DOTweenの使い方の基本中の基本を扱いました．Unityプロジェクトは[筆者のGitHubリポジトリ](https://github.com/radi-bow/TenCounts)で公開しているので，是非参考にしてください．ここで紹介した他にも，ゴールの値ではなくスタートの値が指定できるようになる`From()`や，終了時のコールバックを定める`OnComplete()`・`OnKill()`など，様々な機能があります．是非[公式のドキュメント](http://dotween.demigiant.com/documentation.php)を見ながら，自分で遊んでみてください．

# 参考文献
DOTweenの使い方については，[公式サイト](http://dotween.demigiant.com/index.php)が充実していてわかりやすいです．特に[ドキュメント](http://dotween.demigiant.com/documentation.php)には使用可能な全ての関数が載っているので，これを見ながら遊ぶのがいいと思います．

イージング関数の種類は，本文中でも引用しましたが[Easing Function早見表](https://easings.net/ja)を見るのがお勧めです．各イージング関数の形だけではなく，それによってどのようなアニメーションが実現できるかまで直感的に理解できます．

[^1]: 無料版と[有料版](https://assetstore.unity.com/packages/tools/visual-scripting/dotween-pro-32416)の大きな違いの1つは，有料版だとTextMeshProにもアニメーションを付けられることです．はあ課金しよかな…
[^2]: Unity1週間ゲームジャムは，[Unity製ゲーム投稿サイトunityroom](https://unityroom.com/)において不定期に開催される，共通のお題に対して約1週間でゲームを作るイベントです．今回のお題は「10」でした．是非遊んで評価お願いします！