[Unity]スパイダーバース感!?モーションFPS制御によるアニメチックな映像表現

# アニメっぽい表現

アウトラインを付けたり影を閾値で分けたりすることで、3Dモデルをアニメっぽいビジュアルにする、いわゆる **セルルック** とか **トゥーンシェーダー** と呼ばれる表現は随分前から使われています。

昨今はその技術もかなり一般化されていて、MMDのモデルに適用したり、Vtuberさんのモデルで使われるなど、非商業作品の中でも目にすることが多い印象です。
<img width="300" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184294/f4ccdb1c-c916-4519-4bfe-e6a0e5ef092f.png">
▲[ユニティちゃんトゥーンシェーダー](https://unity-chan.com/download/)のお世話になった人は多いはず

# 映像としてのアニメっぽい表現

そんな中、よりアニメっぽい絵作りをするためにこれから普及してきそうなテクニックが**「フレーム落とし」**かなーと個人的に思ってます。
まずはこちらをご覧ください。

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">What’s up danger? <a href="https://twitter.com/hashtag/MilesMoralesPS5?src=hash&amp;ref_src=twsrc%5Etfw">#MilesMoralesPS5</a> <a href="https://t.co/a657EjeHnY">pic.twitter.com/a657EjeHnY</a></p>&mdash; Miguel Lozada (@MLozada) <a href="https://twitter.com/MLozada/status/1327093975375773696?ref_src=twsrc%5Etfw">November 13, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

絶賛プレイ中の人も多いはず。PS5のスパイダーマンのスパイダーバースリスペクトな衣装でのみ見れる表現（たぶん。誰かPS5ください）


<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ドラゴンボールファイターズの感動は忘れられない！原作再現度もさることながら、アークのセルルックグラフィックのこだわりは尋常じゃない。ギルティ新作も楽しみ…！<br> <a href="https://twitter.com/hashtag/%E3%82%B0%E3%83%A9%E3%83%95%E3%82%A3%E3%83%83%E3%82%AF%E6%9C%80%E5%BC%B7%E3%82%B2%E3%83%BC%E3%83%A0%E9%81%B8%E6%89%8B%E6%A8%A9?src=hash&amp;ref_src=twsrc%5Etfw">#グラフィック最強ゲーム選手権</a> <a href="https://t.co/h4VR22iYhN">pic.twitter.com/h4VR22iYhN</a></p>&mdash; いつものバーガー (@flankids) <a href="https://twitter.com/flankids/status/1295521988748820481?ref_src=twsrc%5Etfw">August 18, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

ドラゴンボールファイターズのアニメーション。カット演出では特に顕著に見れる表現。

お分かりいただけたでしょうか？
つまり、**`カメラやオブジェクトの位置・角度変化のfpsはそのままに、モーションのfpsのみを落として、テレビアニメっぽい雰囲気を出す`**というネタです。

最近はアニメの品質がどんどん上がってきて、フレームレートが高くぬるぬる動くものもよく見かけますが、とはいえゲームで主流の60fpsで動くアニメはほぼ無いはずです。（雑に調べたかぎり、普通は8fps、フルアニメーションで12fpsだとか）
ここで紹介したものは、そういう現状を汲んで**「キャラクターの動きのフレームレートを落とすと普段目にするアニメに近い雰囲気になる」**という文脈の表現だと勝手に解釈してます。

### 本記事より扱いやすい版を作りました！（2021/6/21追記）
https://twitter.com/flankids/status/1406972014523424772

まったく同じ機能でこちらのほうが扱いやすいのでこっち推奨です！↑

# というわけでやってみた


▼これが・・・
<blockquote class="twitter-tweet"><p lang="und" dir="ltr">10FPS <a href="https://t.co/OYP3wSjdo8">pic.twitter.com/OYP3wSjdo8</a></p>&mdash; いつものバーガー (@flankids) <a href="https://twitter.com/flankids/status/1328759202227974146?ref_src=twsrc%5Etfw">November 17, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
▲こうなりました。

なんとなく[スパイダーバース](https://twitter.com/SonyPicturesJP/status/1326732392694861824?s=20)っぽくなったような気がしません？どうでしょう。

# 実装方法

指定した AnimationClip を任意のfpsで再生する `AnimationFPSController.cs` を作ります。

```C#:AnimationFPSController.cs
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Animations;

[RequireComponent(typeof(Animator))]
public class AnimationFPSController : MonoBehaviour {
    [SerializeField]
    AnimationClip _clip;

    [SerializeField, Range(1, 30)]
    int _fps = 30;

    Animator _animator;

    PlayableGraph _graph;

    AnimationClipPlayable _clipPlayable;

    /// <summary>しきい値時間</summary>
    float _thresholdTime;

    /// <summary>スキップされた更新時間</summary>
    float _skippedTime;

    void Awake() {
        _animator = GetComponent<Animator>();
        _graph = PlayableGraph.Create();
        _clipPlayable = AnimationClipPlayable.Create(_graph, _clip);

        AnimationPlayableOutput output = AnimationPlayableOutput.Create(_graph, "output", _animator);
        output.SetSourcePlayable(_clipPlayable);

        InitializeThresholdTime();

        _graph.Play();
    }

    /// <summary>
    /// しきい値時間の初期化
    /// </summary>
    void InitializeThresholdTime() {
        _thresholdTime = 1f / _fps;
    }

    /// <summary>
    /// 更新処理
    /// </summary>
    void Update() {
        if (_fps >= 30) {
            return;
        }

        // Playable側で自動更新しないようにポーズにする
        if (_skippedTime < Mathf.Epsilon) {
            _clipPlayable.Pause();
        }

        if (_thresholdTime > _skippedTime) {
            _skippedTime += Time.deltaTime * (float)_clipPlayable.GetSpeed();
            return;
        }

        // アニメーションの時間を計算する
        double currentTime = _clipPlayable.GetTime() + _skippedTime + Time.deltaTime * (float)_clipPlayable.GetSpeed();
        _skippedTime = 0f;

        if (currentTime > _clip.length) {
            currentTime -= _clip.length;
        }
        _clipPlayable.SetTime(currentTime);
    }

    /// <summary>
    /// オブジェクト削除時の処理
    /// </summary>
    void OnDestroy() {
        if (_graph.IsValid()) {
            _graph.Destroy();
        }
    }

    /// <summary>
    /// Inspectorの値変更時の処理
    /// </summary>
    void OnValidate() {
        InitializeThresholdTime();
    }
}
```

モデルを動かすAnimatorオブジェクトにコンポーネントを追加します。

![AnimationFPSController.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184294/f46b1646-3d3a-3e31-5b2f-35f00bec2ca8.png)

* Clip：再生したいアニメーションファイル
* Fps：再生時のフレームレート

※AnimatorにはAnimatorControllerを設定しないでください

# 簡単な解説

Animatorの低レイヤーな部分を制御できる、`PlayableAPI`を使っています。
（詳細はテラシュールブログさんにお任せします・・・）

* [[テラシュールブログ]アニメーション制御に色々と良さそうな"Playable API"について云々](http://tsubakit1.hateblo.jp/entry/2017/07/30/032008#%E5%9F%BA%E6%9C%AC%E7%9A%84%E3%81%AB%E3%81%AFAnimationController%E3%81%A7%E5%BF%85%E8%A6%81%E3%81%AA%E6%99%82%E3%81%A0%E3%81%91AnimationClip%E3%82%92%E8%B6%B3%E3%81%99)

Updateメソッド内でモーションの更新処理をスキップさせ、設定した`Fps`に応じてタイミングが来たら更新を実行しています。
ポイントは、**「スキップした時間を保持して、更新タイミングでその分アニメーション時間を進める」** というところです。

# その他

* トゥーンシェーダーを使ったモデルとの親和性は非常に高いと思います。是非組み合わせてみてください
* ストップモーションアニメ風とも取れるので、クレイアニメっぽい雰囲気も作れそう（ニャッキとかピングーみたいな）
* SpringBoneなどの揺れものをつかったモデルの場合、揺れものの更新fpsも一致させないと違和感が出そうです。注意！
* 表現としてだけでなく、LOD的な用途にも向いてると思います。近年のゲームで結構見かけます
    * マリオオデッセイのニュードンクシティの街の人々
    * モンハンワールドの一部の敵
    * ドラクエモンスターズ2 3DS版のフィールドの敵シンボル
* `PlayableAPI` だけでのモーションの制御は難易度が高そう…うまくやればもっと汎用的にできる？