UnityのTimelineにおいて、指定したタイミングでゲームオブジェクトを出現させたり消し去ったりする「Activation Track(アクティベーショントラック)」は、イベントのトリガーやカットシーン進行において最も手軽に使える便利な機能の1点です。しかし、「Timelineを使って再生中にオブジェクト(例:壊れる壁や敵キャラクター)を非表示にしたはずなのに、Timelineの再生が最後まで到達した瞬間、消えたオブジェクトが突然画面上に再出現し、開始前の状態に戻ってしまう」という不自然な挙動に直面したことはないでしょうか。これはバグではなく、Timelineのライフサイクルと状態復元の仕様によるものです。本記事では、このアクティブ状態リセットのメカニズムと、初心者にも分かりやすい例え話、そしてシーン内の状態を思い通りに永続維持するための具体的な解決手順とC#制御コードを徹底解説します。

1. 例え話で理解する:『舞台裏のおせっかいな黒衣』

Timeline再生終了後のリセット現象を直感的に理解するために、「演劇のステージと舞台裏の黒衣(くろご)」を例えに考えてみましょう。

あなたは劇の演出家(開発者)です。劇の途中で、黒衣(Activation Track)に対して「劇の中盤になったら、ステージ中央にある邪魔な岩(ゲームオブジェクト)を舞台袖に片付けて(非アクティブ化)見えなくしてくれ」と指示しました。黒衣はその指示通り、劇の最中に見事に岩を片付け、劇は素晴らしいクライマックスを迎えました。

しかし、劇の終わり(Timelineの終了)を告げる幕が下りた瞬間、おせっかいな黒衣は「劇が終わったから、すべての道具を『開演前』の初期状態に綺麗に戻さなくちゃ!」と急いでステージ中央に岩をドスンと置き直してしまいます。観客(プレイヤー)の視点からすれば、幕が上がった途端、さっき消え去ったはずの岩が何事もなかったかのように同じ場所に出現していることになり、魔法が解けたような強い違和感を覚えます。

これがTimelineにおける「None(リセット)挙動」の正体です。黒衣に対して、「この劇が終わった後も、片付けた岩はそのままにしておいて(Hold)」「勝手に元に戻さないで」と明確にルールを設定し直す必要があります。

2. 根本的な原因:Wrap Mode の初期仕様と Out of Bounds

技術的に言うと、Timelineを管理する PlayableDirector には、タイムライン全体のループや終端での振る舞いを決定する Wrap Mode(ラップモード) というプロパティが存在します。この初期設定は None になっています。

Wrap Mode = None の場合、Timelineの現在の再生位置が終了フレーム(Out of Bounds:範囲外)に達すると、Timeline全体の制御が終了し、対象オブジェクトのパラメータ(TransformやActive状態など)が「Timeline再生開始前の状態」に強制的に復元(リセット)されます。これにより、Timeline再生による一時的な変化がシーン内に残らないようにしています。

しかし、「壁を破壊した後にその壁を永久に消し去りたい」というような永続的な状態遷移を行いたい場合、この復元動作が仇となり、「消したオブジェクトが再出現する」バグとして顕在化します。

3. 実務で採用すべき3つの解決アプローチ

状況やゲームのシステム設計に合わせて、最適なアプローチを使い分けましょう。

解決アプローチA:PlayableDirector の Wrap Mode を「Hold」にする(最も簡単)

最も手軽な方法です。Timeline終了時の最後の状態をそのまま維持させます。

  1. シーン内の PlayableDirector コンポーネントを選択します。
  2. インスペクターで Wrap ModeNone(デフォルト)から `Hold` に変更します。

※これにより、タイムラインが終了した際、最初の状態にリセットされることなく、最終フレームの状態(オブジェクトが非表示なら非表示のまま)で一時停止状態になります。

解決アプローチB:C#スクリプトから状態を強制ロックする(推奨)

「他のタイムラインを同じオブジェクトで続けて再生したい」「再生終了時にメモリを解放したい」などの理由で Wrap Mode = Hold にしたくない場合は、Timeline終了のイベントを検知し、C#コードから明示的にオブジェクトの状態を上書き設定するのが最も堅牢です。

using UnityEngine;
using UnityEngine.Playables;

[RequireComponent(typeof(PlayableDirector))]
public class TimelineStateClamper : MonoBehaviour
{
    public GameObject targetObject;       // 消した状態を維持したいオブジェクト
    public bool targetFinalActiveState = false; // 終了時のアクティブ状態(消したいなら false)
    private PlayableDirector director;

    void Start()
    {
        director = GetComponent<PlayableDirector>();
        
        // Timelineの再生完了(停止)イベントに関数を登録
        if (director != null)
        {
            director.stopped += OnTimelineStopped;
        }
    }

    void OnTimelineStopped(PlayableDirector pd)
    {
        if (targetObject != null)
        {
            // Timelineがリセット処理を走らせた直後に、C#側からアクティブ状態を上書き決定する
            targetObject.SetActive(targetFinalActiveState);
            Debug.Log($"Object active state forced to {targetFinalActiveState} on Timeline stop.");
        }
    }

    void OnDestroy()
    {
        if (director != null)
        {
            director.stopped -= OnTimelineStopped;
        }
    }
}

解決アプローチC:Signal Receiver を使ってゲームステートとして管理する

より大規模なゲームでは、Timelineの「表示・非表示(Activation)」で直接物理的な壁の有り無しを管理するのは避け、Timeline内の特定のタイミング(Signal)でゲームロジック側の「破壊フラグ」を立てるべきです。Signalを用いてC#のマネージャークラスを呼び出し、マネージャー側で Destroy(wallObject) を実行したり、セーブデータへの書き込みを行うことで、Timelineの再生状態に関係なくオブジェクトの状態変化が永続化されます。

4. 実務用アクティブ制御トラブルシューティング・チェックリスト

状態維持が上手くいかない場合は、以下の項目を確認してください。

  • 親オブジェクトのアクティブ状態: 制御対象のオブジェクト自体はActiveになっているが、その親(Parent)のGameObjectが非アクティブ化されているため、子である対象が期待通りに表示されない、またはその逆の現象が発生していないか。
  • 複数トラックの競合: 同一のオブジェクトが、異なる複数のActivation Track、あるいはAnimation Track(m_IsActive プロパティ)から同時に制御されていないか。競合している場合、最後に評価されたトラックの値で上書きされます。