[Unity] DOTweenとUniTaskでキャラクターのカットイン演出を作る

# はじめに
本記事は[Life is Tech ! Advent Calendar 2023](https://qiita.com/advent-calendar/2023/life-is-tech)の22日目の記事です。

こんにちは！[Life is Tech!](https://life-is-tech.com/) でメンターをしているみったにです。
本記事では、DOTweenとUniTaskを使ってキャラクターのカットイン演出を作る方法を紹介します。

### 完成イメージ
![Movie_021.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/729021/27766e3f-62b2-dad6-5e42-d8841f54e4f5.gif)


### 検証環境
Unity : 2022.3.9f1
UniTask : Ver.2.5.0
DOTween (HOTween v2)
Windows11

## ライブラリについて
本記事で使用するUniTaskは、Unityの非同期処理を強力にサポートするライブラリです。
一方、DOTweenはUnityでのアニメーションとトゥイーン処理を手軽に扱うためのライブラリです。DOTweenを用いることで、位置やスケールの変更、色の遷移など、多様なアニメーションを簡単なコードで実現することができます。DOTweenは直接的には非同期処理を提供するわけではありませんが、その動作は非同期処理と類似しています。
そこにUniTaskを使用することで、DOTweenによるアニメーションの完了を非同期的に待つことが可能になります。

# 実装
### 1. ライブラリの導入
以下のURLからDOTweenとUniTaskを自分のUnityプロジェクトに入れておいてください。
#### DOTween
https://assetstore.unity.com/packages/tools/animation/dotween-hotween-v2-27676

DOTweenのインストール方法は以下URLを参考にすると分かりやすいです。

https://zenn.dev/ohbashunsuke/books/20200924-dotween-complete/viewer/dotween-03

#### UniTask
UniTaskのインストール方法は他のAssetとは少し勝手が違います。
Package Managerからインポートする手順は[READMEに記載](https://github.com/Cysharp/UniTask#upm-package)があるので参考にしてください。

### 2. DOTweenでUniTaskを使えるようにする
DOTweenとUniTaskをプロジェクトにインポートしただけではDOTweenをUniTaskで使うことができません。

Edit > Project Settings... > Player > Other Settings > Script Compilation > Script Define Symbols に ```UNITASK_DOTWEEN_SUPPORT``` を入力してApplyを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/729021/56d30662-155e-3e74-e033-249908faa439.png)

### 3. カットインのUIを作成する 
まずはシーン上にCanvasを作成します。
UI Scale Mode を ```Scale With Screen Size``` に設定し、Reference Resolution を 1920×1080に設定してください。

<img width="300" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/729021/ab52f1b8-c42e-19cc-f8aa-e9331d8a5233.png">

次に、カットインのUIを作成します。
以下のような入れ子構造でUIの各パーツを制作しましょう。RectTransformの値は各個人の開発プロジェクトに合わせて作成してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/729021/dabd20b7-9062-9287-97a7-fcd61f0aea66.png)

> **CutInUI**

CutInUIは CreateEmpty から作ると良いです。
Anchor Presetsは両方stretchを選択しましょう。

<img width="300" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/729021/cd666a9f-34dc-081a-9f07-da098e1737d0.png">

> **CutInMask**

CutInMaskは UI > Image から作成すると良いです。
また、CutInMaskにはMaskコンポーネントを追加してください。こうすることで、CutInMaskの子要素の表示がCutInMaskのImageの範囲のみに限定されます。

<img width="300" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/729021/f271dd2c-7312-f700-7ed0-89af410a01c9.png">
    
> **CutInBackground**

まずはCutInMaskの子オブジェクトではなく、同じ階層に UI > Image から作成しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/729021/3c0629d4-b957-5d17-a6c2-7bd0406079f9.png)

今回は以下の画像のような1920px × 1080px のサイズの市松模様の画像を作成し、使用しました。

![cutin_background.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/729021/6b0cdff3-bbc6-b4ed-0fd2-777736e3e88d.png)

今回この市松模様の画像を傾けて使用したので、CutInBackgroundのRectTransformの設定が少しややこしくなっています。以下の画像のように、まずはRectTransformの値を設定してみましょう。

<img width="300" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/729021/29891a5e-a1d4-0342-0686-efc6be6bd3ff.png">

次に、Position Constraintコンポーネントを追加し、 Constraint Settings > Sources に先ほど作成したCutInUIを設定します。
こうすることで、CutInMaskをDOTweenでアニメーションさせても、CutInBackgroundはCutInUIの座標を追従するので、CutInBackgroundが動いていないように見えます。

<img width="300" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/729021/30f1ad86-d7b8-bd8b-88f0-0f70be538d68.png">

Position Constraintの設定ができたら、CutInBackgroundをCutInMaskの子オブジェクトにしましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/729021/dabd20b7-9062-9287-97a7-fcd61f0aea66.png)

> **CharacterImage**

CharacterImageは、キャラクターの画像を設定します。
今回はユニティちゃんの画像を画面外右からカットインさせるので、以下のように設定しています。

<img width="300" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/729021/8ddfe613-361c-d259-7c75-03d238f09759.png">

ここまでカットインのUIを設定すれば、Scene上では以下の画像のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/729021/3e8c79fa-4c8d-6e71-562c-96f567f941a9.png)

### 4. カットインアニメーションのコードの実装
実際にDOTweenとUniTaskを用いてコードを書いていきます。
```CutInAnimationView.cs
using System.Threading;
using Cysharp.Threading.Tasks;
using DG.Tweening;
using UnityEngine;
using UnityEngine.UI;

public class CutInAnimationView : MonoBehaviour
{
    [SerializeField] private GameObject cutInUI;
    [SerializeField] private Image cutInCharacterImage;
    [SerializeField] private Image cutInBackgroundMask;

    public async UniTask StartCutIn(CancellationToken token)
    {
        var backgroundMaskRect = cutInBackgroundMask.GetComponent<RectTransform>();
        var characterRect = cutInCharacterImage.GetComponent<RectTransform>();
        cutInUI.SetActive(true);

        // 位置を初期化
        await UniTask.WhenAll(
            characterRect.DOAnchorPos(new Vector2(1600, -40), 0.0f).ToUniTask(cancellationToken: token),
            backgroundMaskRect.DOAnchorPos(new Vector2(300, 375), 0.0f).ToUniTask(cancellationToken: token),
            backgroundMaskRect.DOSizeDelta(new Vector2(0, 2500), 0.0f).ToUniTask(cancellationToken: token)
        );

        // カットインアニメーションの再生
        await UniTask.WhenAll(
            characterRect.DOAnchorPos(new Vector2(-400, -40), 0.5f).SetEase(Ease.OutCubic)
                .ToUniTask(cancellationToken: token),
            backgroundMaskRect.DOSizeDelta(new Vector2(2000, 2500), 0.5f).SetEase(Ease.OutCubic)
                .ToUniTask(cancellationToken: token)
        );

        // カットイン待機
        await UniTask.Delay(700, cancellationToken: token); // 700ミリ秒待機

        // カットインの終了アニメーションの再生
        await UniTask.WhenAll(
            characterRect.DOAnchorPos(new Vector2(-1500, -40), 0.3f).SetEase(Ease.InCubic)
                .ToUniTask(cancellationToken: token),
            backgroundMaskRect.DOAnchorPos(new Vector2(-1500, 375), 0.3f).SetEase(Ease.InCubic)
                .OnComplete(() => { cutInUI.SetActive(false); })
                .ToUniTask(cancellationToken: token)
        );
    }
}
```

このようにTweenをawaitするだけでDOTweenをasync/awaitすることができます。
また、ToUniTaskメソッドにCancellationTokenを渡すことで、キャンセル可能なasync/awaitのDOTweenを作成することができます。
また、UniTaskはDOTweenのキャンセル挙動も設定することができます。ここでは、```OnComplete```を呼ぶことで、Tween終了時にCutInUIを非アクティブにしています。

詳しくは以下の記事で紹介されているので、参考にしてください。

https://shibuya24.info/entry/unity-dotween-unitask

### 5. カットインを再生するコードの実装
カットインを再生するスクリプトも作成しましょう。
```CutInAnimationPresenter.cs
using System.Threading;
using UnityEngine;

public class CutInAnimationPresenter : MonoBehaviour
{
    [SerializeField] private CutInAnimationView view;

    private async void Start()
    {
        // CancellationTokenSourceを作成
        var cts = new CancellationTokenSource(); 
        // CancellationTokenを取得 
        var token = cts.Token;                      

        await view.StartCutIn(token);

        cts.Cancel();
    }
}
```
CancellationTokenSourceを作成し、CancellationTokenを取得します。そしてそのCancellationTokenをCutInAnimationViewのToUniTaskメソッドでTweenにTokenを渡します。

# 確認
シーン上に空のゲームオブジェクトを作成し、先ほど作成したCutInAnimationViewとCutInAnimationPresenterをアタッチしましょう。
アタッチしたら、Inspector Viewに各ゲームオブジェクトなどを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/729021/925fa896-105a-a979-cdbe-28497eb6d23a.png)

アタッチができたら、実際に再生してカットインを確認しましょう。


### 動作確認
![Movie_021.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/729021/dfde9a9e-5865-7a60-e7e1-f24c9599f7b2.gif)


# さいごに
いかがだったでしょうか？

本記事では、DOTweenとUniTaskでUIをアニメーションさせる方法の一例を紹介しました。
UIのアニメーションはDOTweenだけではなく、UnityのTimelineなども用いられて実装される場合もあるので、あくまでも一例として見ていただければ幸いです。
また、DOTweenとUniTaskは今回紹介したもの以外も様々な機能があり、それを駆使すればリッチな表現が可能になります。
本記事を見て少しでもDOTweenとUniTaskについて参考にしていただけたらと思います。

さて、明日のLife is Tech ! Advent Calendar 2023は @zawanume の記事になります。
どうやらMinecraftに関する記事のようです。自分もMinecraftは普段から遊んだり、MOD開発などをしているのでとても楽しみです！

以上、Life is Tech! メンターのみったにでした。


 
# 参考記事
- [打田恭平『UniRx/UniTask完全理解　より高度なUnity C#プログラミング』（株式会社ドワンゴ、2020）](https://www.kadokawa.co.jp/product/302009000670/)
- [DOTweenでasync/awaitを利用する](https://zenn.dev/karaageeeee/articles/1c8c9ba3343135)

 © Unity Technologies Japan/UCL