Search Unity

timeline keeps swapping out my ExposedReferences

Discussion in 'Timeline' started by VoodooDetective, Dec 9, 2019.

  1. VoodooDetective

    VoodooDetective

    Joined:
    Oct 11, 2019
    Posts:
    239
    I've got the following code in my PlayableAsset:

    Code (csharp):
    1.  
    2.         public byte fadeSteps;
    3.         public string localizationId;
    4.         public float fontSize;
    5.         public float appearToWaitRatio;
    6.         public ExposedReference<TextMeshProUGUI> textScriptRef;
    7.         public ExposedReference<RichTextAnimationTagRunner> richTextAnimationTagRunnerRef;
    8.  
    9.         public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
    10.         {
    11.             // Create playable
    12.             ScriptPlayable<RecitationCharacterByPage> playable = ScriptPlayable<RecitationCharacterByPage>.Create(graph);
    13.             RecitationCharacterByPage playableBehaviour = playable.GetBehaviour();
    14.             string text = Singletons.LocalTextLoader.GetText(localizationId);
    15.             TextMeshProUGUI textScript = textScriptRef.Resolve(graph.GetResolver());
    16.             RichTextAnimationTagRunner richTextAnimationTagRunner = richTextAnimationTagRunnerRef.Resolve(graph.GetResolver());
    17.             playableBehaviour.Setup(textScript, text, fontSize, fadeSteps, startTime, endTime, appearToWaitRatio, richTextAnimationTagRunner);
    18.             return playable;
    19.         }
    20.  
    Code (csharp):
    1.  
    2.     [TrackClipType(typeof(RecitationPlayableAsset))]
    3.     // [TrackBindingType(typeof(TextMeshProUGUI))]
    4.     public class RecitationTrack : TrackAsset
    5.     {
    6.         public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
    7.         {
    8.             foreach (TimelineClip clip in GetClips())
    9.             {
    10.                 IClipTimeAware clipInfoAware = clip.asset as IClipTimeAware;
    11.                 if (clipInfoAware != null)
    12.                 {
    13.                     clipInfoAware.SetStartTime(clip.start);
    14.                     clipInfoAware.SetEndTime(clip.end);
    15.                 }
    16.             }
    17.             return base.CreateTrackMixer(graph, go, inputCount);
    18.         }
    19.     }
    20.  
    I have several objects that have this type of track. Whenever the playable director's playable is set to any one of the tracks, at least one of the other tracks has its ExposedReferences replaced which breaks the game.

    The only thing I'm doing that's maybe a little odd is I've added a Duplicate Timeline (with bindings) button to my menu.

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

    I'm I doing something insane here, or do I need to file a bug?
     
  2. VoodooDetective

    VoodooDetective

    Joined:
    Oct 11, 2019
    Posts:
    239
    Ah this only happens for tracks that were duplicated using that last script. What can I do to fix it?
     
  3. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    The problem is the exposed references are simply a name, and the name is getting duplicated between the copies of the tracks.

    To fix it, you will need to generate new names for the new copies of the playable asset, with something like the following:

    var oldName = MyPlayableAsset.MyExposedRefererence.exposedName;
    var newName = UnityEditor.GUID.Generate().ToString();
    MyPlayableAsset.MyExposedRefererence.exposedName = newName;

    and copy the exposed reference binding in the playable director
    playableDirector.SetReferenceValue(newName, playableDirector.GetReferenceValue(oldName));
     
    VoodooDetective likes this.
  4. VoodooDetective

    VoodooDetective

    Joined:
    Oct 11, 2019
    Posts:
    239
    Hey when you say MyPlayableAsset.MyExposedRefererence.exposedName;, what do you mean? It's not a static member. Can you explain that? What am I trying to get a reference to and how? Sorry if I'm missing something basic.
     
  5. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    It's the path to the exposed reference on your playable asset, which will depend your custom types. For example, for a control track, it's clips use a playable asset (ControlPlayableAsset) with an exposed reference called sourceGameObject. So, to display the all the names you could iterate through the clips as follows

    Code (CSharp):
    1. foreach (var clip in track.GetClips())
    2. {
    3.       var playableAsset = (ControlPlayableAsset) clip.asset;
    4.       Debug.Log("The exposed reference has a current name of " +  playableAsset.sourceGameObject.exposedName);
    5. }
     
    VoodooDetective likes this.
  6. VoodooDetective

    VoodooDetective

    Joined:
    Oct 11, 2019
    Posts:
    239
    Hey thanks for the explanation. Here's what I came up with, but it looks like the problem is still happening. Did I miss something?
    Code (csharp):
    1.  
    2.             playableDirector.playableAsset = newTimelineAsset;
    3.             foreach (TrackAsset newTrackAsset in newTimelineAsset.GetRootTracks())
    4.             {
    5.                 foreach (TimelineClip newClip in newTrackAsset.GetClips())
    6.                 {
    7.                     foreach (FieldInfo fieldInfo in newClip.asset.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
    8.                     {
    9.                         if (fieldInfo.FieldType.IsGenericType && fieldInfo.FieldType.GetGenericTypeDefinition() == typeof(ExposedReference<>))
    10.                         {
    11.  
    12.                             // object newExposedReference = fieldInfo.GetValue(newClip.asset);
    13.                             PropertyName oldExposedName = (PropertyName)fieldInfo.FieldType
    14.                                 .GetField("exposedName")
    15.                                 .GetValue(fieldInfo.GetValue(newClip.asset));
    16.                             Debug.Log("old name = " + oldExposedName);
    17.                             bool isValid;
    18.                             Object oldExposedValue = playableDirector.GetReferenceValue(oldExposedName, out isValid);
    19.                             if (!isValid)
    20.                             {
    21.                                 Debug.LogError("Failed to copy exposed references to duplicate timeline. Could not find: " + oldExposedName);
    22.                                 return;
    23.                             }
    24.                             PropertyName newExposedName = new PropertyName(UnityEditor.GUID.Generate().ToString());
    25.                             fieldInfo.FieldType
    26.                                 .GetField("exposedName")
    27.                                 .SetValue(fieldInfo.GetValue(newClip.asset), newExposedName);
    28.                             Debug.Log("new name = " + fieldInfo.FieldType
    29.                                 .GetField("exposedName")
    30.                                 .GetValue(fieldInfo.GetValue(newClip.asset)));
    31.                             playableDirector.SetReferenceValue(newExposedName, oldExposedValue);
    32.                         }
    33.                     }
    34.                 }
    35.             }
    36.  
    Oddly enough, this exact code prints out:
    Code (csharp):
    1.  
    2. old name = 3005e127ad8ee9b4ab91a57b70870bf4:-2024053486
    3. new name = 3005e127ad8ee9b4ab91a57b70870bf4:-2024053486
    4.  
    So for some reason the exposed name isn't getting set?
     
    Last edited: Dec 10, 2019
  7. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    It is probably because ExposedReference is a struct, so the call to fieldInfo.GetValue(newClip.asset) is returning a temporary value.

    You will either need to set the entire exposed reference using reflection
    e.g.
    var exposedReference = new ExposedReference<GameObject>();
    exposedReference.exposedName = newExposedName;
    fieldInfo.SetValue(newClip.asset, exposedReference);

    or you can look in the timeline package at TimelineHelpers.CloneExposedReferences, where we use the unity serialization system to clone them.
     
  8. VoodooDetective

    VoodooDetective

    Joined:
    Oct 11, 2019
    Posts:
    239
    Thanks so much for your help! It works now. Here's the final code for posterity:

    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.  
     
    seant_unity likes this.