TATIKUNLOG.

主にゲーム作りについて書き留めるブログ。

アニメーションの更新レートを下げて負荷軽減を試す【Unity】


PR

もうすぐで星のカービィディスカバリーが発売されますが、その体験版で気になったこととして、遠くのキャラクターのアニメーションの更新頻度を抑えるという表現がありました。

※後ろの羽の生えてるキャラとウサギのキャラの動きを見ると分かりやすいです

恐らく近年のトゥーン調ゲームでよくある、表現のためのアニメーション省略ではなく、負荷軽減のためにしているのではないかと見ています。

ということで、Unityでもアニメーションを間引いて、実際に負荷軽減できるのか試してみようと思いました。

環境

  • Unity 2021.2.16f1

  • Intel Core i7-4790

  • NVIDIA GeForce GTX 1050Ti

実装

実験として、Unityちゃんを大量においたシーンを使用します。

f:id:tat1kun:20220320011741j:plain

Unityちゃんを選んだ理由としては、3Dモデルが動くことによる負荷がそこそこかかりそうな点と、アニメーションが同梱されていて扱いやすかったからです。

アニメーションのフレームレートの制御にはこちらのプログラムをお借りしました。

結果

更新レートが30fpsの場合と、15fpsの場合、5fpsの場合で比較してみました。

比較の差にはProfilerを用いて、ビルド後の環境で確認しました。

結果は画像の通りです。

f:id:tat1kun:20220320013143j:plain

縦軸の尺度が全然違うので、読みづらくなってしまいましたが、 結果としては、一応負荷軽減にはなっていそうです。

グラフの見方が分からない場合は、波のようなグラフの立ち上がり方が負荷だと思ってください。

ただ、アニメーション更新のタイミングのずらしが上手くいっていないのか、あまり上手く負荷が分散できていないようにも見えました。

カメラとの距離でフレームレートを変えてみる

試しにカメラの距離によってフレームレートが可変するようにしてみました。

f:id:tat1kun:20220320015358g:plain

using UnityEngine;

[RequireComponent(typeof(Animator))]
public class AnimatorLOD : MonoBehaviour
{
    [SerializeField,Range(1,60)]
    int m_highFps = 30;
    [SerializeField,Range(1,60)]
    int m_lowFps = 10;

    [SerializeField]
    float m_lowFpsDistance = 3f;

    bool m_fpsIsHigh = true;

    int m_fps = 10;
    Animator m_animator;
    float m_thresholdTime;
    float m_skippedTime;

    void Awake()
    {
        m_animator = GetComponent<Animator>();
        m_animator.enabled = false; // 更新タイミングは制御するので無効にする
        InitializeThresholdTime();
    }

    // 閾値時間の初期化
    void InitializeThresholdTime()
    {
        m_thresholdTime = 1f / m_fps;
        // 更新タイミングがバラつくように初期値に乱数を与える
        m_skippedTime = Random.Range(0f, m_thresholdTime);
    }


    private void Update()
    {
        m_skippedTime += Time.deltaTime;

        if (m_thresholdTime > m_skippedTime)
        {
            return;
        }

        // 閾値時間を超えたら、閾値時間分のアニメーションを飛ばして更新
        m_animator.Update(m_skippedTime);
        m_skippedTime = 0f;

        // カメラとの距離を計算
        var distance = Vector3.Distance(transform.position,Camera.main.transform.position);

        if(m_fpsIsHigh && distance > m_lowFpsDistance){
            m_fps = m_lowFps;
            m_fpsIsHigh = false;
            InitializeThresholdTime();
        }else if(!m_fpsIsHigh && distance <= m_lowFpsDistance){
            m_fps = m_highFps;
            m_fpsIsHigh = true;
            InitializeThresholdTime();
        }
    }
}


さいごに

何の工夫もせずに実験してしまいましたが、効果自体は見られて良かったです。 これが実践で活きてくるかは分かりませんが。

近年はオープンワールドゲーの流行もあり、遠くの物含めて、 大量の物体を一度に表示するのが求められるようになってきてるのを感じますね。

その際に作り手側は如何に表現を落とさず、処理負荷を軽減できるかが腕の見せ所になるんでしょうね。

参考

[Unity]Animatorを任意のフレームレートで動作させる - Qiita

© Unity Technologies Japan/UCL