Search Unity

Duplicating a Timeline loses all the Bindings Unity v2017.2.0b6

Discussion in 'Timeline' started by tezza2k1, Aug 14, 2017.

  1. tezza2k1

    tezza2k1

    Joined:
    Feb 16, 2017
    Posts:
    17
    (Unity v2017.2.0b6)

    I dread making a new timeline because I will need to hunt for all the hierarchy objects and re-bind them. It should be a stress free process but it is not.

    There seems no particular reason that the duplication mechanism could not also duplicate the bindings.

    It makes workflow that much slower, because you have to re-find and re-bind the targets. I have chosen 2 bindings here, but my production Timlines have many more.

    I have a scripted sequence using a few common objects. So the Timelines are very similar, but I have at least 15 timelines with many tracks each.

    If you can follow my simplified example timeline:

    1. Audio Track - One speaks - Bound to an AudioSource A
    2. Animation Track - Another animates - Bound to an AnimationController B

    There is a problem, that when I duplicate a Timeline, the bindings are lost.

    So when I duplicate the example above, there is a Timeline and it has two tracks. 1. AudioTrack, 2. Animation Track. But the duplicate is bound to neither AudioSource A nor AnimationController B

    I have to hit the heirarchy pane, locate both AudioSource A and AnimationController B and drag them to their respective slots.
     
    Ali_V_Quest likes this.
  2. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    The reason for this is the asset does not include any bindings. The tracks are themselves assets, and new tracks are made. We should add a 'duplicate with bindings option', but to help you out here is a helper script that will copy a playableDirector, timeline, and all the bindings.



    Code (CSharp):
    1.    
    2. public class DuplicateTimeline
    3. {
    4.     [MenuItem("Timeline/Duplicate With Bindings", true)]
    5.     public static bool DuplicateWithBindingsValidate()
    6.     {
    7.         if (UnityEditor.Selection.activeGameObject == null)
    8.             return false;
    9.  
    10.         var playableDirector = UnityEditor.Selection.activeGameObject.GetComponent<PlayableDirector>();
    11.         if (playableDirector == null)
    12.             return false;
    13.  
    14.         var playableAsset = playableDirector.playableAsset;
    15.         if (playableAsset == null)
    16.             return false;
    17.  
    18.         var path = AssetDatabase.GetAssetPath(playableAsset);
    19.         if (string.IsNullOrEmpty(path))
    20.             return false;
    21.  
    22.         return true;
    23.     }
    24.  
    25.     [MenuItem("Timeline/Duplicate With Bindings")]
    26.     public static void DuplicateWithBindings()
    27.     {
    28.         if (UnityEditor.Selection.activeGameObject == null)
    29.             return;
    30.  
    31.         var playableDirector = UnityEditor.Selection.activeGameObject.GetComponent<PlayableDirector>();
    32.         if (playableDirector == null)
    33.             return;
    34.  
    35.         var playableAsset = playableDirector.playableAsset;
    36.         if (playableAsset == null)
    37.             return;
    38.  
    39.         var path = AssetDatabase.GetAssetPath(playableAsset);
    40.         if (string.IsNullOrEmpty(path))
    41.             return;
    42.  
    43.         string newPath = path.Replace(".", "(Clone).");
    44.         if (!AssetDatabase.CopyAsset(path, newPath))
    45.         {
    46.             Debug.LogError("Couldn't Clone Asset");
    47.             return;
    48.         }
    49.  
    50.         var newPlayableAsset = AssetDatabase.LoadMainAssetAtPath(newPath) as PlayableAsset;
    51.         var gameObject = GameObject.Instantiate(UnityEditor.Selection.activeGameObject);
    52.         var newPlayableDirector = gameObject.GetComponent<PlayableDirector>();
    53.         newPlayableDirector.playableAsset = newPlayableAsset;
    54.  
    55.         var oldBindings = playableAsset.outputs.ToArray();
    56.         var newBindings = newPlayableAsset.outputs.ToArray();
    57.  
    58.         for (int i = 0; i < oldBindings.Length; i++)
    59.         {
    60.             newPlayableDirector.SetGenericBinding(newBindings[i].sourceObject,
    61.                     playableDirector.GetGenericBinding(oldBindings[i].sourceObject)
    62.                 );
    63.         }
    64.     }
    65. }
    66.  
     
  3. tezza2k1

    tezza2k1

    Joined:
    Feb 16, 2017
    Posts:
    17
    Thanks a lot Sean !! ( I guess from your username ). That worked a treat. There was a small compilation error in your script, but I am using the experimental 4.6 .Net version so that may be it. There was no ToArray() method on the outputs.

    For other forum users, I have included my fixes here :

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using UnityEngine.Playables ;
    5.  
    6. ---------------------
    7.  
    8. var oldBindings = playableAsset.    outputs.GetEnumerator();
    9. var newBindings = newPlayableAsset. outputs.GetEnumerator();
    10.  
    11.  
    12. while( oldBindings.MoveNext() )
    13. {
    14.     var oldBindings_sourceObject = oldBindings.Current.sourceObject ;
    15.  
    16.     newBindings.MoveNext();
    17.  
    18.     var newBindings_sourceObject = newBindings.Current.sourceObject ;
    19.  
    20.  
    21.     newPlayableDirector.SetGenericBinding(
    22.         newBindings_sourceObject,
    23.         playableDirector.GetGenericBinding( oldBindings_sourceObject )
    24.     );
    25. }
     
    Ali_V_Quest, Flurgle and JumpingGuy like this.
  4. JumpingGuy

    JumpingGuy

    Joined:
    Jan 2, 2016
    Posts:
    69
    So awesome. Thank you.... This is exactly what I was looking for...

    Question though - where would I put / paste this code?
     
  5. JumpingGuy

    JumpingGuy

    Joined:
    Jan 2, 2016
    Posts:
    69
    Got it working.. Here's the combination of the two scripts for those interested.
    Thanks to @Tezza and @seant_unity for the code.

    Unity Timeline is amazing!!!!

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using UnityEngine.Playables ;
    4.  
    5. public class DuplicateTimeline
    6. {
    7.     [MenuItem("Timeline/Duplicate With Bindings", true)]
    8.     public static bool DuplicateWithBindingsValidate()
    9.     {
    10.         if (UnityEditor.Selection.activeGameObject == null)
    11.             return false;
    12.  
    13.         var playableDirector = UnityEditor.Selection.activeGameObject.GetComponent<PlayableDirector>();
    14.         if (playableDirector == null)
    15.             return false;
    16.  
    17.         var playableAsset = playableDirector.playableAsset;
    18.         if (playableAsset == null)
    19.             return false;
    20.  
    21.         var path = AssetDatabase.GetAssetPath(playableAsset);
    22.         if (string.IsNullOrEmpty(path))
    23.             return false;
    24.  
    25.         return true;
    26.     }
    27.  
    28.     [MenuItem("Timeline/Duplicate With Bindings")]
    29.     public static void DuplicateWithBindings()
    30.     {
    31.         if (UnityEditor.Selection.activeGameObject == null)
    32.             return;
    33.  
    34.         var playableDirector = UnityEditor.Selection.activeGameObject.GetComponent<PlayableDirector>();
    35.         if (playableDirector == null)
    36.             return;
    37.  
    38.         var playableAsset = playableDirector.playableAsset;
    39.         if (playableAsset == null)
    40.             return;
    41.  
    42.         var path = AssetDatabase.GetAssetPath(playableAsset);
    43.         if (string.IsNullOrEmpty(path))
    44.             return;
    45.  
    46.         string newPath = path.Replace(".", "(Clone).");
    47.         if (!AssetDatabase.CopyAsset(path, newPath))
    48.         {
    49.             Debug.LogError("Couldn't Clone Asset");
    50.             return;
    51.         }
    52.  
    53.         var newPlayableAsset = AssetDatabase.LoadMainAssetAtPath(newPath) as PlayableAsset;
    54.         var gameObject = GameObject.Instantiate(UnityEditor.Selection.activeGameObject);
    55.         var newPlayableDirector = gameObject.GetComponent<PlayableDirector>();
    56.         newPlayableDirector.playableAsset = newPlayableAsset;
    57.  
    58.         var oldBindings = playableAsset.    outputs.GetEnumerator();
    59.         var newBindings = newPlayableAsset. outputs.GetEnumerator();
    60.  
    61.  
    62.         while( oldBindings.MoveNext() )
    63.         {
    64.             var oldBindings_sourceObject = oldBindings.Current.sourceObject ;
    65.  
    66.             newBindings.MoveNext();
    67.  
    68.             var newBindings_sourceObject = newBindings.Current.sourceObject ;
    69.  
    70.  
    71.             newPlayableDirector.SetGenericBinding(
    72.                 newBindings_sourceObject,
    73.                 playableDirector.GetGenericBinding( oldBindings_sourceObject )
    74.             );
    75.         }
    76.     }
    77. }
     
  6. awasem

    awasem

    Joined:
    Nov 25, 2015
    Posts:
    3
    Create a C# script, copy and paste this code into MonoDevelop when it opens, and name it something like duplicateTimeline. Now with your timeline selected from the Timeline menu at the top there is an option to Duplicate With Bindings. Awesome! Thanks @Tezza and @seant_unity for the code.
     
  7. awasem

    awasem

    Joined:
    Nov 25, 2015
    Posts:
    3
    Any way to edit this script to keep the duplicated timeline gameObject on the canvas? When duplicate a timeline asset it moves out of canvas. Then when I bring it back into the canvas its the wrong size.
     
  8. grossimatte

    grossimatte

    Joined:
    Mar 15, 2013
    Posts:
    43
    Not working on Unity 2018.2.1f1
     
  9. AdamBL

    AdamBL

    Joined:
    Oct 27, 2016
    Posts:
    29
    Hey @GonzoGan I think it is working in 2018.2, it just works in a kind of weird way. Some of the issues I had were:

    1. You need to have the GameObject that has the Playable Director component selected in your scene in order to duplicate it (not in your Project window)
    2. When you duplicate it, it creates a Clone GameObject in your scene with the duplicated Timeline in its PlayableDirector slot (rather than modifying the original GameObject)
    3. Most confusing is the fact that the duplicated Timeline still references the original GameObjects, even if it created new ones.
    So in our scene, we have all our animated elements as children of the object with the Playable Director component, and when we duplicated the Timeline it duplicated all those objects. However, the new Timeline still controlled the original objects, so instead of using the Cloned object, I simply copied the Playable Director component and pasted it on the original parent object.
     
    Filip8429 likes this.
  10. Kareeem

    Kareeem

    Joined:
    Mar 1, 2013
    Posts:
    37
    The script works without any issues in 2018.2.15f1
     
  11. Flurgle

    Flurgle

    Joined:
    May 16, 2016
    Posts:
    389
    Duplicating with bindings is not working in Unity 2019.1a10 either
     
  12. MikeFish

    MikeFish

    Joined:
    Oct 24, 2015
    Posts:
    12
    I could need some help.
    It's not working for me

    Unity 2018.3.4f1

    It clones the Timeline but without any bindings, it is doing exactly the same as ctrl D in the project window.
    I followed all instructions I could find here without any positive result.

    any help would be very appreciated.
    thanks,
    Mike

    ----------------------------
    Edit:
    my bad,
    wasn't noticing that only the new created Director hold all bindings,
    what is logical when you think about it.
    So, yeah it is working.
     
    Last edited: Feb 6, 2019
    thierry_unity likes this.
  13. PawleyBoboli

    PawleyBoboli

    Joined:
    Jan 27, 2015
    Posts:
    10
    This works great and I get no errors compiling it or using it during production. The Timeline Menu Item shows up in my editor and the duplication-with-bindings works perfect. Thanks for this!

    However, I do get errors when trying to publish a Build to WebGL. The build fails with this error in the console:

    "Assets/Timelines/DuplicateTimeline.cs(7,6): error CS0246: The type or namespace name 'MenuItem' could not be found (are you missing a using directive or an assembly reference?)"

    I cannot find any problems with the script, and I found that I can build if I just remove it from the project. Not a big issue, but if anyone has a solution so the script does not need removing, I would appreciate any info.

    Thanks.


     
  14. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    Put the script in a folder named Editor. It will be excluded from player builds then. Or if you need it in a player, wrap the using UnityEditor; and [MenuItem(..)] lines with a #if UNITY_EDITOR / #endif.
     
    PawleyBoboli likes this.
  15. PawleyBoboli

    PawleyBoboli

    Joined:
    Jan 27, 2015
    Posts:
    10
    Thanks Sean. Putting it into one of the many "Editor" folders that already existed in the project worked as you described.
     
  16. TotalReality

    TotalReality

    Joined:
    Oct 13, 2016
    Posts:
    11
    This doesnt work, all it does is duplicates my hierarchy objects and makes a timeline clone, then when i try to reassign the timeline clone to a different playable component all my bindings are gone again, so basically making this script useless. why is this so hard, all i want to be able to do is copy a timeline and then edit it so i have several different variations of the same timeline that can be assigned to a single playable object but every time i try to do this i loose all object bindings, my timeline has at least 30 object bindings and i need to copy it 7 times, it is far too tedious to manually reassign all the bindings every time i make a duplicate timeline, please create a solution for this, it has been an issue since the timeline was implemented, for a tool that is supposed to make you life easier every time i find myself using timeline i have issues like this that make me absolutely hat it, the system just feels broken and illogical to me. Not to mention all the other problems i have with the design of this tool, ( why is every new track put to the bottom of the timeline list) Little things like this make the timeline system a complete pain to use and i dread anytime i need to make an animated sequence with this tool please fix it Unity.
     
    SwingNinja and BaLala_Bala like this.
  17. Cleverlie

    Cleverlie

    Joined:
    Dec 23, 2013
    Posts:
    219
    for those who say it doesn't work because it copies the hierarchy of objects, of course it does! you don't read the code you just copy paste it and run it without trying to have at least a grasp of what it does?? I mean it's ok for non programmers because an artist might not even know what the code is doing and he just wants things to get done, but devs, please.

    the code is instantiating the original gameObject ALONG with all it's children, and then rebinds the director bindings in the duplicate director, to the bindings in the original director, meaning that if you are using objects that are children of the game object that has the director component, then you will have a duplicate hierarchy of objects that are doing nothing, you still have bindings to the original objects.

    what you want to do is: instead of instantiate(), just create a new gameobject, add a playableDirector to it, and then only do the rebinding part of the code.
     
    mandisaw likes this.
  18. Cleverlie

    Cleverlie

    Joined:
    Dec 23, 2013
    Posts:
    219

    P.S: this should really be an out of the box feature, at least when we right click the PlayableDirector component and do "Copy Component" then "Paste Component Values" should automatically rebind the bindings, which is not the case
     
    SwingNinja likes this.
  19. romi-fauzi

    romi-fauzi

    Joined:
    Aug 16, 2013
    Posts:
    161
    I created a simple script to do this, to use it simply copy the Playable Asset on the project, you'll see it will lose all bindings when applied to the PlayableDirector Component, add this script below, and assign the PlayableDirector component to the TimelineComponent slot, the source PlayableAsset (with working binding) and the target PlayableAsset (the copied one), and then choose "Duplicate Timeline" from the context menu. After this, you'll be able to assign the copied timeline to the original PlayableDirector component, with the right bindings intact.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Playables;
    3.  
    4. public class CopyTimeline : MonoBehaviour
    5. {
    6.     [SerializeField] PlayableDirector timelineComponent;
    7.     [SerializeField] PlayableAsset sourceTimeline, targetTimeline;
    8.  
    9.     [ContextMenu("Duplicate Timeline")]
    10.     public void Duplicate()
    11.     {
    12.         var oldBindings = sourceTimeline.outputs.GetEnumerator();
    13.         var newBindings = targetTimeline.outputs.GetEnumerator();
    14.  
    15.         while (oldBindings.MoveNext())
    16.         {
    17.             var oldBindings_sourceObject = oldBindings.Current.sourceObject;
    18.  
    19.             newBindings.MoveNext();
    20.  
    21.             var newBindings_sourceObject = newBindings.Current.sourceObject;
    22.  
    23.  
    24.             timelineComponent.SetGenericBinding(
    25.                 newBindings_sourceObject,
    26.                 timelineComponent.GetGenericBinding(oldBindings_sourceObject)
    27.             );
    28.         }
    29.     }
    30. }
     
  20. VoodooDetective

    VoodooDetective

    Joined:
    Oct 11, 2019
    Posts:
    239
    I've changed the script to operate on one playable director:

    Code (csharp):
    1.  
    2.     public class DuplicateTimeline
    3.     {
    4.         [MenuItem("Assets/Duplicate Timeline", true)]
    5.         public static bool DupTimelineValidate()
    6.         {
    7.             if (UnityEditor.Selection.activeObject == null)
    8.                 return false;
    9.  
    10.             TimelineAsset timelineAsset = UnityEditor.Selection.activeObject as TimelineAsset;
    11.             if (timelineAsset == null)
    12.                 return false;
    13.  
    14.             string path = AssetDatabase.GetAssetPath(timelineAsset);
    15.             if (string.IsNullOrEmpty(path))
    16.                 return false;
    17.  
    18.             GameObject currentRoom = GameObject.Find(Helpers.PascalToSpaces(SceneManager.GetActiveScene().name));
    19.             if (currentRoom == null)
    20.                 return false;
    21.  
    22.             PlayableDirector playableDirector = currentRoom.GetComponent<PlayableDirector>();
    23.             if (playableDirector == null)
    24.                 return false;
    25.  
    26.             return true;
    27.         }
    28.         [MenuItem("Assets/Duplicate Timeline")]
    29.         public static void DupTimeline()
    30.         {
    31.             // Fetch playable director, fetch timeline
    32.             TimelineAsset timelineAsset = UnityEditor.Selection.activeObject as TimelineAsset;
    33.             PlayableDirector playableDirector = GameObject
    34.                 .Find(Helpers.PascalToSpaces(SceneManager.GetActiveScene().name))
    35.                 .GetComponent<PlayableDirector>();
    36.  
    37.             // Duplicate
    38.             string path = AssetDatabase.GetAssetPath(timelineAsset);
    39.             string newPath = path.Replace(".", "(Clone).");
    40.             if (!AssetDatabase.CopyAsset(path, newPath))
    41.             {
    42.                 Debug.LogError("Couldn't Clone Asset");
    43.                 return;
    44.             }
    45.  
    46.             // Copy Bindings
    47.             TimelineAsset newTimelineAsset = AssetDatabase.LoadMainAssetAtPath(newPath) as TimelineAsset;
    48.             PlayableBinding[] oldBindings = timelineAsset.outputs.ToArray();
    49.             PlayableBinding[] newBindings = newTimelineAsset.outputs.ToArray();
    50.             for (int i = 0; i < oldBindings.Length; i++)
    51.             {
    52.                 playableDirector.playableAsset = timelineAsset;
    53.                 Object boundTo = playableDirector.GetGenericBinding(oldBindings[i].sourceObject);
    54.  
    55.                 playableDirector.playableAsset = newTimelineAsset;
    56.                 playableDirector.SetGenericBinding(newBindings[i].sourceObject, boundTo);
    57.             }
    58.         }
    59.     }
    60.  
    But now all of my ExposedReferences on my duplicated timelines are linked. Changing one changes them all. Is there a way to fix that?
     
  21. VoodooDetective

    VoodooDetective

    Joined:
    Oct 11, 2019
    Posts:
    239
    Got it all sorted out:
    Code (csharp):
    1.  
    2.         public static void DupTimeline()
    3.         {
    4.             // Fetch playable director, fetch timeline
    5.             TimelineAsset timelineAsset = UnityEditor.Selection.activeObject as TimelineAsset;
    6.             PlayableDirector playableDirector = GameObject
    7.                 .Find(Helpers.PascalToSpaces(SceneManager.GetActiveScene().name))
    8.                 .GetComponent<PlayableDirector>();
    9.  
    10.             // Duplicate
    11.             string path = AssetDatabase.GetAssetPath(timelineAsset);
    12.             string newPath = path.Replace(".", "(Clone).");
    13.             if (!AssetDatabase.CopyAsset(path, newPath))
    14.             {
    15.                 Debug.LogError("Couldn't Clone Asset");
    16.                 return;
    17.             }
    18.  
    19.             // Copy Bindings
    20.             TimelineAsset newTimelineAsset = AssetDatabase.LoadMainAssetAtPath(newPath) as TimelineAsset;
    21.             PlayableBinding[] oldBindings = timelineAsset.outputs.ToArray();
    22.             PlayableBinding[] newBindings = newTimelineAsset.outputs.ToArray();
    23.             for (int i = 0; i < oldBindings.Length; i++)
    24.             {
    25.                 playableDirector.playableAsset = timelineAsset;
    26.                 Object boundTo = playableDirector.GetGenericBinding(oldBindings[i].sourceObject);
    27.  
    28.                 playableDirector.playableAsset = newTimelineAsset;
    29.                 playableDirector.SetGenericBinding(newBindings[i].sourceObject, boundTo);
    30.             }
    31.  
    32.             // Copy Exposed References
    33.             playableDirector.playableAsset = newTimelineAsset;
    34.             foreach (TrackAsset newTrackAsset in newTimelineAsset.GetRootTracks())
    35.             {
    36.                 foreach (TimelineClip newClip in newTrackAsset.GetClips())
    37.                 {
    38.                     foreach (FieldInfo fieldInfo in newClip.asset.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
    39.                     {
    40.                         if (fieldInfo.FieldType.IsGenericType && fieldInfo.FieldType.GetGenericTypeDefinition() == typeof(ExposedReference<>))
    41.                         {
    42.                             // Fetch Old Exposed Name
    43.                             object exposedReference = fieldInfo.GetValue(newClip.asset);
    44.                             PropertyName oldExposedName = (PropertyName)fieldInfo.FieldType
    45.                                 .GetField("exposedName")
    46.                                 .GetValue(exposedReference);
    47.                             bool isValid;
    48.  
    49.                             // Fetch Old Exposed Value
    50.                             Object oldExposedValue = playableDirector.GetReferenceValue(oldExposedName, out isValid);
    51.                             if (!isValid)
    52.                             {
    53.                                 Debug.LogError("Failed to copy exposed references to duplicate timeline. Could not find: " + oldExposedName);
    54.                                 return;
    55.                             }
    56.  
    57.                             // Replace exposedName on struct
    58.                             PropertyName newExposedName = new PropertyName(UnityEditor.GUID.Generate().ToString());
    59.                             fieldInfo.FieldType
    60.                                 .GetField("exposedName")
    61.                                 .SetValue(exposedReference, newExposedName);
    62.  
    63.                             // Set ExposedReference
    64.                             fieldInfo.SetValue(newClip.asset, exposedReference);
    65.  
    66.                             // Set Reference on Playable Director
    67.                             playableDirector.SetReferenceValue(newExposedName, oldExposedValue);
    68.                         }
    69.                     }
    70.                 }
    71.             }
    72.         }
    73.  
     
  22. AgileLens

    AgileLens

    Joined:
    Mar 5, 2019
    Posts:
    10
    Hmmm trying to get this working in 2019.3. Everything gives me compilation errors, but with @VoodooDetective script I get the fewest after I add the following namespaces:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Timeline;
    5. using UnityEngine.Playables;
    6. using UnityEditor;
    7. using UnityEngine.SceneManagement;
    8. using System.Linq;
    9. using System.Reflection;
    Still, even then the script on its own has no reference for
    Code (CSharp):
    1. Helpers.PascalToSpaces
    Any ideas?
     
  23. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    did you?
     
  24. Rib

    Rib

    Joined:
    Nov 7, 2013
    Posts:
    39
    Here's a tweaked script based on @VoodooDetective's which doesn't refer to Helpers.PascalToSpaces which I guess is some game-specific private API:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Reflection;
    5.  
    6. using UnityEngine;
    7. using UnityEngine.Playables;
    8. using UnityEngine.Timeline;
    9. using UnityEngine.SceneManagement;
    10.  
    11. using UnityEditor;
    12.  
    13. public class DuplicateTimeline : MonoBehaviour
    14. {
    15.     [MenuItem("Assets/Duplicate Timeline", true)]
    16.     private static bool DupTimelineValidate()
    17.     {
    18.         if (UnityEditor.Selection.activeObject as GameObject == null)
    19.         {
    20.             Debug.Log("Null active object");
    21.             return false;
    22.         }
    23.  
    24.         GameObject playableDirectorObj = UnityEditor.Selection.activeObject as GameObject;
    25.  
    26.         PlayableDirector playableDirector = playableDirectorObj.GetComponent<PlayableDirector>();
    27.         if (playableDirector == null)
    28.         {
    29.             Debug.Log("Null playableDirector");
    30.             return false;
    31.         }
    32.  
    33.         TimelineAsset timelineAsset = playableDirector.playableAsset as TimelineAsset;
    34.         if (timelineAsset == null)
    35.         {
    36.             Debug.Log("Null timelineAsset");
    37.             return false;
    38.         }
    39.  
    40.         string path = AssetDatabase.GetAssetPath(timelineAsset);
    41.         if (string.IsNullOrEmpty(path))
    42.         {
    43.             Debug.Log("Null timeline asset path");
    44.             return false;
    45.         }
    46.  
    47.         return true;
    48.     }
    49.  
    50.     [MenuItem("Assets/Duplicate Timeline")]
    51.     public static void DupTimeline()
    52.     {
    53.         // Fetch playable director, fetch timeline
    54.         GameObject playableDirectorObj = UnityEditor.Selection.activeObject as GameObject;
    55.         PlayableDirector playableDirector = playableDirectorObj.GetComponent<PlayableDirector>();
    56.         TimelineAsset timelineAsset = playableDirector.playableAsset as TimelineAsset;
    57.  
    58.         // Duplicate
    59.         string path = AssetDatabase.GetAssetPath(timelineAsset);
    60.         string newPath = path.Replace(".", "(Clone).");
    61.         if (!AssetDatabase.CopyAsset(path, newPath))
    62.         {
    63.             Debug.LogError("Couldn't Clone Asset");
    64.             return;
    65.         }
    66.  
    67.         // Copy Bindings
    68.         TimelineAsset newTimelineAsset = AssetDatabase.LoadMainAssetAtPath(newPath) as TimelineAsset;
    69.         PlayableBinding[] oldBindings = timelineAsset.outputs.ToArray();
    70.         PlayableBinding[] newBindings = newTimelineAsset.outputs.ToArray();
    71.         for (int i = 0; i < oldBindings.Length; i++)
    72.         {
    73.             playableDirector.playableAsset = timelineAsset;
    74.             Object boundTo = playableDirector.GetGenericBinding(oldBindings[i].sourceObject);
    75.  
    76.             playableDirector.playableAsset = newTimelineAsset;
    77.             playableDirector.SetGenericBinding(newBindings[i].sourceObject, boundTo);
    78.         }
    79.  
    80.         // Copy Exposed References
    81.         playableDirector.playableAsset = newTimelineAsset;
    82.         foreach (TrackAsset newTrackAsset in newTimelineAsset.GetRootTracks())
    83.         {
    84.             foreach (TimelineClip newClip in newTrackAsset.GetClips())
    85.             {
    86.                 foreach (FieldInfo fieldInfo in newClip.asset.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
    87.                 {
    88.                     if (fieldInfo.FieldType.IsGenericType && fieldInfo.FieldType.GetGenericTypeDefinition() == typeof(ExposedReference<>))
    89.                     {
    90.                         // Fetch Old Exposed Name
    91.                         object exposedReference = fieldInfo.GetValue(newClip.asset);
    92.                         PropertyName oldExposedName = (PropertyName)fieldInfo.FieldType
    93.                             .GetField("exposedName")
    94.                             .GetValue(exposedReference);
    95.                         bool isValid;
    96.  
    97.                         // Fetch Old Exposed Value
    98.                         Object oldExposedValue = playableDirector.GetReferenceValue(oldExposedName, out isValid);
    99.                         if (!isValid)
    100.                         {
    101.                             Debug.LogError("Failed to copy exposed references to duplicate timeline. Could not find: " + oldExposedName);
    102.                             return;
    103.                         }
    104.  
    105.                         // Replace exposedName on struct
    106.                         PropertyName newExposedName = new PropertyName(UnityEditor.GUID.Generate().ToString());
    107.                         fieldInfo.FieldType
    108.                             .GetField("exposedName")
    109.                             .SetValue(exposedReference, newExposedName);
    110.  
    111.                         // Set ExposedReference
    112.                         fieldInfo.SetValue(newClip.asset, exposedReference);
    113.  
    114.                         // Set Reference on Playable Director
    115.                         playableDirector.SetReferenceValue(newExposedName, oldExposedValue);
    116.                     }
    117.                 }
    118.             }
    119.         }
    120.     }
    121. }
     
  25. Slx_GZ

    Slx_GZ

    Joined:
    Dec 12, 2013
    Posts:
    5
    Hello.
    I'm one of those "Artists",
    could someone please explain in which exactly folder Rib's script should be placed
    and how can it be used?
    As placed in Assets folder adds no menu item in any menu.
    Thank you.
     
  26. Slx_GZ

    Slx_GZ

    Joined:
    Dec 12, 2013
    Posts:
    5
    Update:
    I think that script added a "Duplicate Timeline" command in Assets menu on the top of Unity menu bar,
    and that command creates a (clone) of selected timeline Playable, and clone has empty bindings.
    Unity 2019.2.19f1
     
  27. BlueSpirit

    BlueSpirit

    Joined:
    Jun 10, 2013
    Posts:
    33
    Awesome! Works like a charm. Thank you guys so much! :)
     
  28. Enigma229

    Enigma229

    Joined:
    Aug 6, 2019
    Posts:
    135
    I couldn't get this to work (Artist here). I copied the Rib script into Notepad and saved it out as Timeline_duplicate.cs (yes I did Save as type to All files) and pasted it into root assets (don't know where to put it). I see no option to duplicate timeline in Assets menu or anywhere else.

    Help!

    Disregard. I created a folder named Editor and now it shows up in the Assets menu.
     
    Last edited: May 24, 2020
  29. Cloud-Ninja

    Cloud-Ninja

    Joined:
    May 5, 2015
    Posts:
    32
    @Rib - first of all, thank you for modifying and polishing the original script (thank you to @VoodooDetective for providing the original script)

    I used Rib's script in 2019.4.of1 (LTS) and it worked great - like magic!. Then I used it again for different scene/timeline I had in the same project and it didn't duplicate the bindings?

    I tried comparing the timeline/scene that worked to the scene/timeline and troubleshooting but couldn't find why there would be any difference.

    Have you noticed that the script duplicates the bindings on some timelines and not others?

    Any thoughts or recommendations it would be greatly appreciated.

    thank you,
    .cn
     
  30. multimediamark

    multimediamark

    Joined:
    Mar 17, 2017
    Posts:
    30
    Anyone got the script working on 2020.3.16f1? Also, Unity Developers, could you please put it in because it's essential things to have.
     
    WeaselOnaStick likes this.
  31. WeaselOnaStick

    WeaselOnaStick

    Joined:
    Dec 29, 2017
    Posts:
    3
    Yes PLEASE. I honestly did not expect this to be an issue that needed user scripts to solve. Very vital and very basic feature. tsk tsk tsk, Unity
     
  32. WojtekR

    WojtekR

    Joined:
    May 3, 2019
    Posts:
    7
    Unity version 2021
    Solution/steps
    1. Add this script to your Assets/Editor/
    2. Select in Hierarchy Window your Timeline object (GameObject containing Playable Director component)
    3. Use new menu item : Timeline > Duplicate With Bindings
     

    Attached Files:

    bootchec and aaronc_unity like this.
  33. Brainslum

    Brainslum

    Joined:
    Jan 10, 2017
    Posts:
    57
    appreciate it but this doesnt really work? I guess when your gameobject is inactive it fails to bind.

    Also Unity please add this functionality by default...
    This makes iteration of animation in Unity incredibly hard. Not everybody is gonna source control and artists do not wanna touch revert etc... Plain and simple v1, v2, v3 are what we need as artists


     
    akent99 likes this.
  34. julienb

    julienb

    Unity Technologies

    Joined:
    Sep 9, 2016
    Posts:
    177
    Since version 1.4.0, bindings are duplicated when pasting a track. It is possible to copy all tracks in a timeline and paste those to a new timeline; the bindings will be duplicated on the new timeline.

    NB: a fix was made in version 1.6.4 related to bindings and group tracks.
     
  35. brian-nielsen

    brian-nielsen

    Joined:
    Apr 18, 2018
    Posts:
    15
    Really glad I read all the way to the bottom. I was about to get deep into the coding myself. Duplicating the Playable Director, swapping out the timeline with a new one and then copy/paste from old to new worked perfectly.

    Unity 2019.4.2f1 with Timeline version 1.8.0

    Thank you for this :)