映画のような豪華なカットシーンや、キャラクターのセリフ(ボイス)に合わせた正確な口の動き(リップシンク)、さらには音楽ゲームやリズム演出など、音と映像が完全に一致していることが要求されるコンテンツにおいて、UnityのTimeline(タイムライン)は必要不可欠です。しかし、実機テストや少し負荷の高いシーンにおいて、「ゲームのフレームレートが低下(処理落ち)した際、映像の進行に対してBGMやキャラクターの音声(ボイス)がコンマ数秒先走りしたり、逆に遅れたりして、タイミングがバラバラに破綻していく」という問題(音ズレバグ)に悩まされたことはないでしょうか。本記事では、この同期ズレが発生する内部的なオーディオクロックの仕組みと、初心者にも分かりやすい例え話、そして映像と音を確実に同期させるための設定手順とポーズ処理用C#コードを徹底解説します。

1. 例え話で理解する:『別々に動く映写機と蓄音機』

この「フレームレート低下による音ズレ」を直感的に理解するために、「古い映画館の映写機と蓄音機(レコードプレーヤー)」を例えに考えてみましょう。

昔の映画館では、フィルムを回す「映写機(映像)」と、音を鳴らす「蓄音機(音声)」が物理的に分かれており、それぞれが別々のモーターで動いていました。

映画の最初(0秒時点)で、映写機のスタートボタンと蓄音機の針を同時に落とします。最初は完璧にタイミングが合っています。しかし、途中で映写機のモーターが熱を持ち、一時的に回転速度がカクンと落ちて(フレームドロップ)しまいました。このとき、蓄音機は「私は映写機とは関係なく、自分のペースで回っているだけですから」と、一定の速度でレコードを回し続けます。結果として、映像が1秒遅れているにもかかわらず、音声だけがどんどん先に進んでしまい、役者が喋り出す前に声が流れるという致命的な「音ズレ」が発生します。

Unityにおける標準設定(Update Mode = Game Time)もまさにこれと同じです。映像(ゲームフレーム)が処理落ちでカクつくと、Timelineの時間軸の進行も遅れますが、PCやスマートフォンのサウンドカードの内部(DSPクロック)で再生されている音声は、処理落ちの影響を受けずに一定のピッチで鳴り続けます。この「独立した2つの時計」が原因で、時間の経過とともにズレが拡大していくのです。これを解決するためには、「蓄音機の再生ペース(DSPクロック)に合わせて、映写機のフィルムを手動で巻き進めたり遅らせたりして、常に同期させる紐」を結ぶ必要があります。

2. 根本的な原因:Game Time と DSP Clock の乖離

技術的に言うと、Unityの通常のタイムライン進行は Time.time(ゲームクロック)を基準に評価されます。これはCPUのメインループ(Update)の処理間隔(フレームレート)に基づいて時間更新が計算されるため、処理落ちが発生すると、1フレームあたりに進む時間が不均一になります。

一方で、Unityのオーディオシステム(内部的にはFMOD)は、OSのオーディオバッファを一定のサンプリングレート(例:48kHz)で満たし続ける 「DSP(Digital Signal Processing)クロック」 というハードウェア固有の非常に高精度な別個の時計で動いています。

ゲームのFPSが60から20に低下したとき、ゲームクロックの進行はガタガタになりますが、DSPクロックは物理時間通りに正確に1秒を刻み続けます。そのため、タイムラインの AudioTrack に配置された音声ファイルは実時間(DSP)で再生が完了するのに対し、映像トラックの評価はフレーム更新の遅れによって実時間より遅れ、両者のタイムライン評価位置に「同期のズレ(乖離)」が生じるのです。

3. 解決法:PlayableDirector の Update Mode を変更する

この同期の乖離を根本から防ぐために、Timelineを制御する PlayableDirector のタイムソースを変更します。

手順1:Update Mode を DSP Clock に変更する

  1. シーン内の PlayableDirector コンポーネントがアタッチされているゲームオブジェクトを選択します。
  2. インスペクターで Update Mode プロパティを探します。
  3. 値を Game Time(デフォルト)から `DSP Clock` に変更します。

※これにより、タイムライン全体の進行度を評価するためのマスタークロックが、ゲームフレーム時間から「サウンドカードのオーディオ再生サンプル数」に変更されます。万が一映像フレームがドロップしても、タイムラインは音声の再生位置に追従してスキップ(コマ送り)されるため、リップシンクなどが絶対にズレなくなります。

DSP Clock使用時の注意点とポーズ処理用C#コード

Update Mode を DSP Clock に変更すると、タイムラインの時計がゲーム時間(Time.timeScale)から切り離されるため、Time.timeScale = 0 にしてゲーム全体をポーズ(一時停止)させても、タイムラインの音声とアニメーションが止まらずに進み続けてしまう」という重大な副作用が発生します。

これを回避するためには、メニュー画面などを開く一時停止処理の際、以下のようにC#スクリプトから PlayableDirector に対して明示的に Pause() を呼び出す実装が必要です。

using UnityEngine;
using UnityEngine.Playables;

public class TimelinePauseManager : MonoBehaviour
{
    public PlayableDirector playableDirector;
    private bool isGamePaused = false;

    void Update()
    {
        // エスケープキーでゲームの一時停止をトグル切り替え
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            TogglePause();
        }
    }

    public void TogglePause()
    {
        isGamePaused = !isGamePaused;

        if (isGamePaused)
        {
            // ゲーム全体の物理やスクリプトを一時停止
            Time.timeScale = 0f;

            // DSP ClockモードのPlayableDirectorを明示的にポーズする(必須)
            if (playableDirector != null && playableDirector.state == PlayState.Playing)
            {
                playableDirector.Pause();
                Debug.Log("Timeline paused via script.");
            }
        }
        else
        {
            // ゲームの時間を再開
            Time.timeScale = 1.0f;

            // PlayableDirector を再開
            if (playableDirector != null)
            {
                playableDirector.Play();
                Debug.Log("Timeline resumed.");
            }
        }
    }
}

4. 音ズレを防ぐための実務オーディオチェックリスト

プラットフォームを問わず音と映像の完璧な一体感を実現するため、オーディオ設定を確認してください。

  • Audio Latency の設定: Project Settings > Audio を開き、DSPSize(バッファサイズ)を Best Latency(低遅延)に設定することで、音声がトリガーされてから実際に発音されるまでのレイテンシを極小化します。
  • Clipの再トリガー(再開)設定: タイムラインを途中でシーク(スキップ)した際、Audio Sourceの再生がシーク先に正しくジャンプするよう、各Audio Clipのインスペクターで再生設定が適切に行われているか確認します。