ゲームの演出を華やかに彩るUnityのTimeline(タイムライン)機能ですが、必殺技の発動カットシーンやボス登場の演出など、Timelineが初めて再生されたその瞬間に画面が一瞬「カクッ」と止まるプチフリーズ(処理落ちスパイク)に悩まされたことはないでしょうか。2回目以降の再生や、同じ演出のループ再生時には何事もなかったかのようにスムーズに動くため、原因の特定が難しく放置されがちな問題です。しかし、プレイヤーにとって「一番カッコよく見せたい演出の最初の一歩」で画面が引っかかるのは、ゲームの体験価値を著しく損ねる重大なバグと言えます。本記事では、この初回再生スパイクが発生する内部的なメカニズムを、初心者にもわかりやすい「劇場の舞台裏」の例え話を交えて解説し、実務で今すぐ使える解決手順とチェックリストを提示します。

1. 例え話で理解する:『劇場の開演ベルと大慌ての衣装係』

この不具合の本質を理解するために、『劇場の開演ベルが鳴った瞬間に、役者がステージ裏で大慌てで衣装を縫い合わせたり、舞台セットを力仕事で組み立てている状態』を想像してみましょう。

劇場の観客(プレイヤー)が席につき、開演のベル(Timelineの再生開始)が鳴り響きました。幕が上がったその瞬間、ステージ裏(GPU/CPU)は大パニックに陥っています。役者(エフェクト)が着るべき豪華な衣装(シェーダー)はまだ型紙の状態で、衣装係(コンパイラ)がミシンをフル回転させてその場で縫い合わせています。大道具係(パーティクル計算)も、劇が始まってから背景の雲や火花(パーティクル)を大急ぎで組み立てています。当然、劇の最初の数秒間は不自然な沈黙やもたつき(フレームレートの低下)が生じてしまいます。しかし、一度劇が始まってしまえば、衣装は完成し、道具も組み立て終わっているため、2回目(2ループ目)からは何の問題もなくスムーズに進行します。

これが、Timelineにおける『初回再生スパイク』の正体です。事前に衣装を着て舞台袖でスタンバイ(ウォームアップ)しておくという「前準備」を怠ったために、本番が始まった瞬間にすべての負荷が一極集中してしまうのです。

2. 根本原因:シェーダーコンパイルとShurikenのPrewarm負荷

技術的にこのスパイクを引き起こしている主な要因は、以下の2つに大別されます。

原因①:GPUシェーダーコンパイル(PSOの構築)

Unityは、エフェクトに使用されるマテリアルのシェーダーを、それが初めて画面に描画される瞬間(またはカメラの視野に入る瞬間)に、グラフィックスAPI(Vulkan, Metal, DirectX等)を通じてGPUが理解できる形式へコンパイルします。このコンパイル処理は極めて重く、メインスレッドを一瞬でロックします。これが「2回目以降は軽くなる」最大の理由です(一度コンパイルされたシェーダーはメモリ上にキャッシュされるため)。

原因②:Shuriken Particle SystemのPrewarm(事前シミュレーション)

パーティクルシステムには、再生開始時にすでにパーティクルが画面いっぱいに広がった状態から始められる「Prewarm(プレウォーム)」という便利な機能があります。しかし、Prewarmが有効なパーティクルがTimelineのコントロールトラックによってアクティブ化されると、Unityは**再生開始の最初の1フレーム内で、パーティクルの「寿命数秒分」の物理シミュレーションをCPU上で一気に力任せに計算**します。例えば、1秒間に100個のパーティクルが出る設定で寿命が3秒の場合、最初の1フレームで300回分の座標計算が走り、CPUスレッドが完全に悲鳴を上げます。

3. 具体的な解決方法とC#実装手順

アプローチA:ShaderVariantCollectionによるシェーダー温め

ゲームロード時やシーン遷移の暗転中に、使用するエフェクトのシェーダーを強制的にコンパイルしておく「Shader Warmup」が最も重要です。Unityの `ShaderVariantCollection` を使用して、事前に温めておきます。

using UnityEngine;

public class ShaderWarmupController : MonoBehaviour
{
    [SerializeField] private ShaderVariantCollection targetCollection;

    void Start()
    {
        if (targetCollection != null)
        {
            // ロード画面やシーン遷移のタイミングで呼び出す
            targetCollection.WarmUp();
            Debug.Log("すべての登録シェーダーバリアントの事前コンパイルが完了しました。");
        }
    }
}

アプローチB:Timeline再生前の「1フレーム強制評価」による非同期プレウォーム

エフェクトがカメラに映る前に、Timelineオブジェクトをアクティブにし、再生ヘッドを1フレームだけ進めて `Evaluate`(評価)を呼んでおくことで、スパイクをロード画面の中に隠蔽します。これにより、ゲームプレイ中にカクつくのを防ぎます。

using UnityEngine;
using UnityEngine.Playables;

public class TimelinePrewarm : MonoBehaviour
{
    [SerializeField] private PlayableDirector director;

    void Awake()
    {
        if (director != null)
        {
            // 1. 一度オブジェクトを有効化し、再生準備を行う
            director.gameObject.SetActive(true);
            
            // 2. タイムラインの時間を極小値(または0)に設定
            director.time = 0.0;
            
            // 3. 1フレーム分強制的にグラフィックスとパーティクルを評価(Evaluate)する
            // これにより、内部のパーティクル生成やシェーダー読み込みがこの時点でトリガーされます
            director.Evaluate();
            
            // 4. 自動再生をオフにしている場合は、必要になるまで一時停止させておく
            director.Pause();
            
            Debug.Log("Timelineの初回ダミー評価によるプレウォームが成功しました。");
        }
    }

    public void PlayTimeline()
    {
        if (director != null)
        {
            director.Play();
        }
    }
}

4. 実務で使えるパフォーマンスチェックリスト

Timelineにパーティクルを組み込む際は、以下の設定をワークフローのルールとして徹底してください。

チェック項目 確認すべき設定 対策アクション
Prewarmの設定確認 Particle System -> Prewarm 不要な場合は必ずOffにする。必要な場合はC#による事前 Evaluate() を挟む。
最大粒子数の制限 Max Particles デフォルトの1000のまま放置せず、演出に必要な最小限の数値(例:50など)に制限する。
シェーダーの事前ロード ShaderVariantCollection ビルド設定でエフェクト用シェーダーをアセットに登録し、起動時またはシーンロード時に WarmUp() する。