【Unity】TimelineのTrackを別のTimelineにコピーする

C#
スポンサーリンク
スポンサーリンク

はじめに

https://docs.unity3d.com/ja/2018.4/uploads/Main/timeline_splash.jpg

TimelineAsset内のTrackを、別のTimelineにコピーしなければいけない時があり、
その際に他のTimelineにデータをコピーする方法を調べたのでまとめます。

この方法だと、TimelineのBind設定も引き継いでくれます(例外あり)。
引き継げないものに関しては後述します。

実装の注意点

Unity2019.2.17f1にて実装しております。

System.Reflectionを使用しているため、Unity2018以前などではネームスペースが変更されておりMethodInfo等が取得できない可能性があります。

実装方法

C#
/// <summary>
/// Timelineの複製
/// </summary>
///
/// <paramname="fromDirector"> 複製元のPlayableDirector </param>
/// <paramname="toDirector"> 複製先のPlayableDirector </param>
privatevoidDuplicateTimeline(PlayableDirector fromDirector,PlayableDirector toDirector )
{
   if( toDirector.playableAssetisTimelineAsset targetTimelineAsset )
    {
       if( fromDirector.playableAssetisTimelineAsset timelineAsset )
        {
           Assembly assembly= Assembly.Load("Unity.Timeline.Editor" );
           
           // AssemblyからTimelineEditorの機能をReflectionで取得してくる
           var trackExtensionsType= assembly.GetType("UnityEditor.Timeline.TrackExtensions" );
           if( trackExtensionsType==null )
            {
                Debug.LogWarning("TrackExtensions not found." );
            }
           var duplicateInfo= trackExtensionsType.GetMethod("Duplicate",
            BindingFlags.DeclaredOnly| BindingFlags.NonPublic| BindingFlags.Static );
           if( duplicateInfo==null )
            {
                Debug.LogWarning("TrackExtensions.Duplicate not found." );
            }
           
           // Duplicateメソッドに渡す引数の配列をあらかじめ作成
           // Duplicateメソッドを呼ぶ際はそれぞれの複製物をindex:0に代入して渡す
           // TrackAsset, ターゲットのPlayableDirector, null, コピー先のTimelineAssetの順
           var duplicateArgs=new [] { (object )null, toDirector,null, targetTimelineAsset };
           
           // タイムラインアセットのルートトラックを全て複製する
           // ルートトラックを複製する際に、そのトラックの子トラックも複製されるみたい
           foreach(TrackAsset sourcein timelineAsset.GetRootTracks() )
            {
               // 設定していないのにMarkerTrackが作成されることがあるのでスキップする
               // この部分は各自カスタマイズしてください。
               if( sourceisMarkerTrack )
                {
                   continue;
                }
               
               // パラメータ配列の先頭をコピーしたいトラックに置き換える
                duplicateArgs [0]= source;
               // TrackをCloneして、作成したグループトラックに紐づける
               TrackAsset clone= duplicateInfo.Invoke(null, duplicateArgs )asTrackAsset;
               // Bind設定を反映させる
               CopyBindings( source, clone, fromDirector, toDirector );
            }
           
           // リフレッシュしないとTimeline Editorに反映されないらしいので実行する
            TimelineEditor.Refresh( RefreshReason.ContentsAddedOrRemoved );
        }
    }
}

/// <summary>
/// バインド設定をコピーする
/// </summary>
///
/// <paramname="fromTrack"> 複製元のトラック </param>
/// <paramname="toTrack"> 複製先のトラック </param>
/// <paramname="fromDirector"> 複製元のPlayableDirector </param>
/// <paramname="toDirector"> 複製先のPlayableDirector </param>
privatevoidCopyBindings(TrackAsset fromTrack,TrackAsset toTrack,PlayableDirector fromDirector,
PlayableDirector toDirector )
{
    toDirector.SetGenericBinding( toTrack, fromDirector.GetGenericBinding( fromTrack ) );
   if( fromTrack.GetChildTracks().Any() )
    {
       // 子トラックも複製されているはずなので、それぞれ再帰的にバインドする
       var fromChildren= fromTrack.GetChildTracks().ToArray();
       var toChildren= toTrack.GetChildTracks().ToArray();
       for(var i=0; i< fromChildren.Length; i++ )
        {
           CopyBindings( fromChildren[i], toChildren[i], fromDirector, toDirector );
        }
    }
}

解説

Reflectionで内部APIにアクセスする

C#
Assembly assembly= Assembly.Load("Unity.Timeline.Editor" );

// AssemblyからTimelineEditorの機能をReflectionで取得してくる
var trackExtensionsType= assembly.GetType("UnityEditor.Timeline.TrackExtensions" );
if( trackExtensionsType==null )
{
    Debug.LogWarning("TrackExtensions not found." );
}

var duplicateInfo= trackExtensionsType.GetMethod("Duplicate",
BindingFlags.DeclaredOnly| BindingFlags.NonPublic| BindingFlags.Static );

if( duplicateInfo==null )
{
    Debug.LogWarning("TrackExtensions.Duplicate not found." );
}

Assembly.Loadを使用して、通常では呼び出せないクラスやメソッドにアクセスしています。

“Unity.Timeline.Editor”や”UnityEditor.Timeline.TrackExtensions”の部分はUnityのバージョンによって変わることがあります。
(現にUnity2018では別の名前になっていたはずです…)

内部APIを確認するには、下記の方法で行うと見つかります。
アクセスできない場合は試してみてください。

tsubakit1.hateblo.jp

Duplicateメソッドを呼ぶ

C#
var duplicateArgs=new [] { (object )null, toDirector,null, targetTimelineAsset };

この一文で、Duplicateメソッドの引数を配列として定義しています。

Reflectionで無理やり呼び出す場合、いつものように引数を一つずつ指定することはできず、
引数を配列として作成し、渡してあげなければ動きません。

index0に、コピーしたいTrackAssetを、index3に、コピー先のTimelineAssetを入れるようにしてください。

C#
// パラメータ配列の先頭をコピーしたいトラックに置き換える
duplicateArgs [0]= source;

// TrackをCloneして、作成したグループトラックに紐づける
TrackAsset clone= duplicateInfo.Invoke(null, duplicateArgs )asTrackAsset;

この部分で、foreach文で取得してきたTrackAssetを先ほどの配列のindex0に代入し、その配列を元にDuplicateメソッドを呼びます。
Invokeメソッドの引数は

C#
publicobjectInvoke (object obj,object[] parameters);

となっているため、一番目にnullを、二番目にDuplicateの引数をobject配列にしたものを入れています。

詳細は下記のドキュメントを参照してください。
docs.microsoft.com

Bindのコピー

C#
toDirector.SetGenericBinding( toTrack, fromDirector.GetGenericBinding( fromTrack ) );

この部分で、複製元のBind情報を元に、複製先のPlayableDirectorにBind情報をコピーしています。
ただ、この方法だと、Bindしているオブジェクトが複製元のTimelineのPrefab内にある、と言った場合に参照切れになってしまいます。

解決方法を模索したのですが上手い方法が思いつきませんでしたのでいったんこの形にしています。
(AnimationTrack内のClipにBindされているAnimationClipのBind情報などはPrefabに関係ないため上手くコピーできます。)

また、この方法だとCinemachineTrack内のClipにBindされているVirtualCameraの参照が上手くコピーできませんでした。
これについては別途記事にします。

コメント

タイトルとURLをコピーしました