Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

How to have both Timeline and a custom playable graph play nice?

Discussion in 'Timeline' started by DinosCharmGames, May 17, 2018.

  1. DinosCharmGames

    DinosCharmGames

    Joined:
    May 9, 2018
    Posts:
    25
    We are trying to create an idle system for a character which builds a PlayableGraph that plugs a number of timelines into an AnimationMixer which then gets piped out to an AnimationOutputPlayable attached to an Animator. We want to be able to override the idle system output with timelines in the scene that drive narrative sequences. The narrative sequences are authored as plain old AnimationTracks so that our designers/artists get full use of the native UI. The problem is that if we target the same Animator from our custom PlayableGraph as well as the AnimationTrack in the timeline, the timeline no longer plays. The playable director attached to the timeline reports that its graph is invalid.

    From what I understand the Animator is supposed to layer these inputs and the custom PlayableGraph is supposed to override the timeline so it sounds like this is a proper use case. Is there anything else we should consider here?

    The idle system's graph and narrative timeline graph are attached below in respective order. The red nodes on the left are AnimationPlayableOutputs.

    CharacterAnimatorGraph.PNG NarrativeSequenceGraph.PNG
     
  2. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    How are you creating the playable graphs? Your post mentioned
    "The playable director attached to the timeline reports that its graph is invalid"

    Are you using the playable director and then modifying it's graph, or are you compiling timeline as part of a different graph?

    In either case, timeline will create it's own animation outputs. The best approach would probably be to unbind those using animationPlayableOutput.SetTarget(null). Then create your own animation playable output and set the source playable.

    What you are attempting can work, but will be a bit tricky to get everything setup correctly.
     
  3. DinosCharmGames

    DinosCharmGames

    Joined:
    May 9, 2018
    Posts:
    25
    The idle system graph is created like this:

    Code (CSharp):
    1.  
    2.         playableGraph = PlayableGraph.Create();
    3.  
    4.         // Graph outputs to the animator driving the character
    5.        AnimationPlayableOutput playableOutput =
    6.             AnimationPlayableOutput.Create(playableGraph,
    7.                                            "Character Animator",
    8.                                            animator);
    9.  
    10.         // Create a mixer that will blend any registered playables. Should start
    11.         // with 0 inputs.
    12.         animMixer = AnimationMixerPlayable.Create(playableGraph, numMixerInputs);
    13.  
    14.         playableOutput.SetSourcePlayable(animMixer);
    15.         playableOutput.SetSourceInputPort(0);
    16.  
    The idle system has several subsystems that each have their own mixer but those subsystem mixer outputs are connected to the inputs of the mixer listed above like this:

    Code (CSharp):
    1.  
    2.     public void ConnectPlayable<T>(T playable, int outputPort)
    3.         where T : struct, IPlayable
    4.     {
    5.         // Hook up the playable to the next available input in the mixer
    6.         animMixer.SetInputCount(numMixerInputs + 1);
    7.         playableGraph.Connect(playable, outputPort, animMixer, numMixerInputs);
    8.         numMixerInputs++;
    9.     }
    10.  
    Where a subsystem would call ConnectPlayable like this:

    Code (CSharp):
    1.     ConnectPlayable(subsystemMixerPlayable, 0);
    The narrative sequence timelines are separate graphs that output to the same Animator and are being driven by a playable director. As part of our debugging efforts we called IsValid() on the playable director's graph which returns true at init time (Awake) but once the director tries to start playing it returns false. Attempting to perform operations on any playables that were part of this graph also elicit the following error in the console:

    InvalidOperationException: This PlayableOutput is invalid. It may have been deleted.

    I saw that you mentioned rewiring the output in this thread:
    https://forum.unity.com/threads/fade-out-a-playabledirector-so-an-animator-can-take-over.513800/

    We tried applying that to the narrative timeline graph like so and calling it on Start:
    Code (CSharp):
    1.  
    2.         // Redirect the timeline graph output to another output that we control
    3.         PlayableGraph nisGraph = nisDirector.playableGraph;
    4.  
    5.         if (nisGraph.IsValid()) {
    6.             AnimationPlayableOutput oldOutput = (AnimationPlayableOutput) nisGraph.GetOutputByType<AnimationPlayableOutput>(0);
    7.             if (oldOutput.IsOutputValid() && oldOutput.GetTarget() != null) {
    8.                 output = AnimationPlayableOutput.Create(nisGraph, "fake", oldOutput.GetTarget());
    9.                 output.SetSourcePlayable(oldOutput.GetSourcePlayable());
    10.                 output.SetSourceInputPort(oldOutput.GetSourceInputPort());
    11.                 output.SetWeight(1f);
    12.                 oldOutput.SetTarget(null);
    13.             }
    14.         }
    15.  
    This doesn't change anything though. The narrative timeline still doesn't play and the nisDirector's graph becomes invalid and stops after it starts to play.

    I should also mention we are on 2017.4.2f2
     
  4. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    So the part I am a bit confused about is in the two playable graphs that are shown, the first one (the CharacterAnimationGraph) has two nested timelines. Are you using playable directors to create that somehow? Or are you using TimelineAsset.CreatePlayable() to generate those subgraphs?

    Overall, are you trying to have one playable graph that controls everything?

    If you are using TimelineAsset.CreatePlayable() to nest timeline graphs (instead of using playable directors), that is the best approach. Not the easiest, as the playable director is there to simplify graph setup. Trying to connect Playables or Outputs that reside in different graphs is prohibited and should gives you errors. If you are trying to interleave your graph with the graph generated by a playable director, that's is likely the reason you are getting InvalidOperation errors.

    To avoid the playable director, you can try to use TimelineAsset.CreatePlayable(). It will return the root level playable, and you will need to the set the wrap mode and duration on that playable. Also, it will create PlayableOutputs, but they will come back unbound. You can leave those unbound (or even destroy them), if you are going to create your own to bind your root level mixer to.

    That code disables the old outputs, and redirects to new outputs to allow explicit weight control on the output. If you are going this route, you should only have one bound output on the entire graph and the source playable should point to your mixer (no need to call SetSourceInputPort, since your output should process the entire graph). You may need to call SetPropogateSetTime(true) on your animation mixers so that the time that's set on the mixers (when the graph is played) gets automatically passed to it's inputs. By default, time does not propagate down the graph.

    ---

    Or an alternate solution (i.e. more standard):
    Have a state machine or playable graph that just plays the idle (i.e. non-timeline parts) animation.
    In your timelines, optionally ease-in the first clip, ease-out the last clip (to get blending).
    Turn off play on awake on your playable directors.
    When you want a narrative sequence to play, simple call playableDirector.Play() on that timeline. It should transition to the animation track, and return to the previous graph/state machine when complete (assume the wrap mode is none).


    I hope all my rambling helps clarify....
     
  5. DinosCharmGames

    DinosCharmGames

    Joined:
    May 9, 2018
    Posts:
    25
    The timelines in the idle system are created using TimelineAsset.CreatePlayable(). The idle system also creates a mixer for blending between multiple idle timelines and then the output of that mixer is plugged into the CharacterAnimator's playable graph. So all of that is one graph.

    The narrative sequence is driven by a PlayableDirector and will generally be part of a timeline that could control a number of characters across multiple moments. I think that means the narrative sequence needs to stay a separate graph unless it's possible to rewire the outputs of just some of the tracks as inputs into the mixer on the CharacterAnimator's graph.

    Our designers would like to be able to author idle states with timelines so that they can synchronize audio tracks and other things easily with the idle animations. If there's no other way to do it we could fall back on the state machine route for these but it's definitely not our first choice.

    So given all of these factors what is the right way to hook this up?
     
  6. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    No, that's not possible. But if the idle system is playing, playing the narrative sequences from the playable director should transition to and from the idle. If that's not working, it may have to do with order the animator plays them. The order AnimationPlayableOutput.SetTarget is called sets the priority, the last one having highest priority.

    This should be possible, but any you will need to explictly set the bindings for all tracks on the PlayableOutputs created by TimelineAsset.CreatePlayable() except the animation track you want to mix. In addition, you will also need to manage the duration of the timelines, they won't automatically stop.
     
  7. CharmGames

    CharmGames

    Joined:
    Jun 1, 2017
    Posts:
    9
    Ok so that means I would have to call SetTarget on the CharacterAnimator's graph before any PlayableDirectors that are driving narrative sequence timelines setup their graphs?

    This also still doesn't explain why the narrative sequence graph becomes invalid. If both the CharacterAnimator's graph and the narrative sequence PlayableDirector have graphs with AnimationPlayableOutputs targeting the same animator, is it possible that calling SetTarget on one AnimationPlayableOutput could invalidate the graph of the other AnimationPlayableOutput?
     
  8. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    Yes, your graph should be set up before any playable directors start playing.


    The playable directors graph becomes invalid when the timeline is stopped - either automatically when it’s complete (I.e. it’s time goes past the duration and wrap mode is none), or when playableDirector.Stop() is called. Is either of those cases possible?
     
  9. DinosCharmGames

    DinosCharmGames

    Joined:
    May 9, 2018
    Posts:
    25
    Unlikely. We don't call Stop() on the playableDirector anywhere and since the timeline never even starts playing it doesn't reach the end. I just tried not starting the PlayableDirector at all and it seems that sometime after swapping the output, the graph's playable handle turns null. Any idea what could cause that?
     
  10. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    I suspect that something is causing the timeline playable time to get set past it's duration (that would trigger an auto-stop). If you set the playable director wrap mode to Loop, does it still happen?
     
  11. DinosCharmGames

    DinosCharmGames

    Joined:
    May 9, 2018
    Posts:
    25
    So after some more digging I found that the root of the problem is that one of the idle subsystems which does some procedural animation is calling SetCurve on a generated AnimationClip. After this call, the narrative sequence's timeline becomes invalid. Is this expected behaviour? If it is, what would be the recommended way to blend procedurally generated animation (through either linear interpolation or a skinned mesh) with other animation clips?
     
  12. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    It's expected behaviour if the timeline is open in the Timeline Editor. The editor will rebuild the active timeline when any animation clip is modified. The editor forcing a rebuild on the graph occurs during various modifications.

    A workaround could be to you could listen to AnimationUtility.OnCurveModified and adapt if the graph becomes invalid. Although, modifying an animation clip is not supported at runtime, so depending on your application you may run into an issue there as well.

    Depending on what you are doing, you could do something simple like blend in a LateUpdate call, or if you are using 2018.2 beta you could try C# animation jobs (https://forum.unity.com/threads/animation-c-jobs-in-2018-2a5.525012/).
     
  13. DinosCharmGames

    DinosCharmGames

    Joined:
    May 9, 2018
    Posts:
    25
    2018.2 beta is unfortunately not an option based on where we are in our dev cycle. We've been experimenting with blending in a LateUpdate but the trouble is we want to blend the procedural anim with the resulting pose after the idle system and any narrative sequence timelines have all run. We can't find a good place to capture this state after the idle system and timelines. Any recommendations?
     
  14. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    What problem is introduced when using LateUpdate? Both the state-machine and Timeline are updated between Update and LateUpdate calls.
     
  15. DinosCharmGames

    DinosCharmGames

    Joined:
    May 9, 2018
    Posts:
    25
    I guess the real challenge is that we're trying to blend poses by interpolating the resulting quaternion from the state-machine and Timeline update with a procedurally generated pose calculated in LateUpdate. The discrepancy is in the fact that a transform that has been touched by the Animator will have been "reset" at the start of the LateUpdate call by the state-machine/Timeline update whereas a transform that was not touched during that update will retain the procedural pose from the previous LateUpdate call. We can't find a reasonable way to differentiate between the two cases. We tried using Transform.hasChanged but that is not robust as it is set to true for any number of reasons. Is there a better way to approach this that we're not thinking of?
     
  16. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    There's nothing I'm aware of that's better, other than maybe putting custom tracks and state machine callbacks to notify that an update occurred.
     
  17. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,904
    @seant_unity by this do you mean use this https://docs.unity3d.com/Manual/TimelineEasingClips.html to adjust start and end of clip to be sloped start / end for blending, and then play a timeline on the director when needed?

    If the timeline finishes will that mean the graph is now null / empty? or will it transition back to the graph that was playing. trying to get my head around what having a playable director component does, as I know it has its own graph. Does playing a timeline asset on a animator via a director replace the graph or simply connect new things to it?

    Essentially from the quoted part above, I think I have massively misunderstood playables and timelines and believed them to be far more complicated than they actually are.

    I just want an idle animation system, but one where I can trigger one shot timeline clips that have audio and other tracks, have those clips play, then transition back to the animation system. What you wrote makes it seem like that is very much in grasp and without too much headaches so eager to clarify this! :)

    EDIT: Anyone interested, the answer is yes. It is this easy. Color me happy :)
     
    Last edited: Jun 6, 2018
    seant_unity likes this.
  18. panta

    panta

    Joined:
    Aug 10, 2012
    Posts:
    71
    @seant_unity @mikew_unity
    I have a question about the part where you say to call TimelineAsset.CreatePlayable() and to bind the PlayableOutputs.

    I'm able to create the playable from the timeline, connect it to my PlayableGraph, and get it to play my animations and particle effects that are stored in the timeline, but I'm not able to get the sounds to play. Do you have any samples of how to bind the PlayableOutputs when using TimelineAsset.CreatePlayable?
    upload_2018-8-8_19-40-58.png

    I was previously just playing my PlayableDirectors directly instead of trying to put them into the PlayableGraph, which worked pretty well, but I wasn't able to blend out of the Timeline at an arbitrary point, hence why I'm trying to switch to using a PlayableGraph to run the timelines.

    I followed @mikew_unity 's example to do Timeline Output binding from this forum post which worked well for PlayableDirectors, but I can't figure out how to bind outputs for when I use TimelineAsset.CreatePlayable :(
     
  19. panta

    panta

    Joined:
    Aug 10, 2012
    Posts:
    71
    For reference, here's what it looks like when I play the Timeline via TimelineAsset.CreatePlayable connected to my PlayableGraph:
    upload_2018-8-9_1-25-54.png

    Perhaps the reason I don't hear any audio when I'm playing via the PlayableGraph is because I'm not connecting the audio playables to an audio output? It seems wrong that I'm connecting the audio playables to the AnimationOutput root.



    And here's what it looks like when I play the same Timeline directly through a PlayableDirector:
    upload_2018-8-9_1-29-29.png
     
  20. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    You can get the graphs audio outputs by using PlayableGraph.GetOutputByType(). If there isn't one you can create one.

    Then you should set the SourcePlayable to the TimelinePlayable in the graph (PlayableOutput.SetSourcePlayable()) and PlayableOutput.SetSourceOutputPort to the index of the input of the audio mixer of the timeline playable.

    The output is what determines what actually gets traversed in the graph and by who. Pointing it to the timeline playable tells it where to start traversing from and makes sure the clips are correctly activated at the right time, and SetSourceOutputPort (this may be SetSourceInputPort, depending on the version) tells it to only traverse the audio subgraph.

    Even if there is a subgraph of audio nodes, if they aren't attached to an audio output, no audio will be played.
     
  21. panta

    panta

    Joined:
    Aug 10, 2012
    Posts:
    71
    @seant_unity

    I can't thank you enough! I did what you said, and it totally worked:
    upload_2018-8-9_12-41-43.png

    The red circle at the bottom of the graph is my shiny new AudioOutput, and sounds from my TimelineAssets are successfully playing! I had to remove the PlayableDirectors from all my gameobjects, since the Editor was crashing when both my PlayableGraph and the PlayableDirector were trying to play the same audio track (some crash in FMOD). I see you mentioned that playing both is a bad idea above, but I wouldn't have expected the whole editor to crash from doing this (instead, I'd hope the editor would report an error to the Console instructing on the best practice, since everyone might not have the luck to find this awesome forum post). Here's the callstack in case it's helpful:
    Code (csharp):
    1. ========== OUTPUTTING STACK TRACE ==================
    2.  
    3. 0x0000000141C3DF33 (Unity) OnApplyFMOD
    4. 0x00000001406D65F2 (Unity) DirectorManager::ExecuteStage
    5. 0x00000001406D69A2 (Unity) `DirectorManager::InitializeClass'::`2'::PostLateUpdateDirectorLateUpdateRegistrator::Forward
    6. 0x0000000140967E07 (Unity) ExecutePlayerLoop
    7. 0x0000000140967EB6 (Unity) ExecutePlayerLoop
    8. 0x000000014096A75C (Unity) PlayerLoop
    9. 0x0000000141447E4A (Unity) PlayerLoopController::UpdateScene
    10. 0x0000000141446063 (Unity) Application::TickTimer
    11. 0x00000001415E45A5 (Unity) MainMessageLoop
    12. 0x00000001415E698C (Unity) WinMain
    13. 0x0000000142423D0A (Unity) __scrt_common_main_seh
    14. 0x00007FFABFC33034 (KERNEL32) BaseThreadInitThunk
    15. 0x00007FFAC1ED1431 (ntdll) RtlUserThreadStart
    16.  
    17. ========== END OF STACKTRACE ===========
    and here's a snippet for anyone else trying to do this:
    Essentially, I was looping through the timelinePlayable inputs (mostly for debugging to know what I had to work with) and then wiring up to the AudioSource when I found one.
    The inner for (j) loop can mostly be ignored - the important items to hook up to the graph were found at the root of the TimelinePlayable, thankfully.
    Code (csharp):
    1.  
    2.         Playable timelinePlayable = timelineAsset.CreatePlayable(m_Graph, gameObject);
    3.         int count = timelinePlayable.GetInputCount();
    4.         for (int i = 0; i < count; i++)
    5.         {
    6.             Playable curPlayable = timelinePlayable.GetInput(i);
    7.             if (curPlayable.IsValid())
    8.             {
    9.                 Type curPlayableType = curPlayable.GetPlayableType();
    10.                 if (curPlayable.IsPlayableOfType<AudioMixerPlayable>())
    11.                 {
    12.                     // TODO: check for the existence of an AudioPlayableOutput instead of just creating one directly
    13.                     AudioMixerPlayable audioMixer = (AudioMixerPlayable) curPlayable;
    14.                     AudioSource audioSource = this.GetComponent<AudioSource>();
    15.                     var output = AudioPlayableOutput.Create(m_Graph, string.Empty, audioSource);
    16.                     output.SetSourcePlayable(audioMixer);
    17.                 }
    18.                 int innerCount = curPlayable.GetInputCount();
    19.                 for (int j = 0; j < innerCount; j++)
    20.                 {
    21.                     Playable curInnerPlayable = curPlayable.GetInput(j);
    22.                     if (curInnerPlayable.IsValid())
    23.                     {
    24.                         Type curInnerPlayableType = curInnerPlayable.GetPlayableType();
    25.                     }
    26.                 }
    27.             }
    28.         }
    29.  
     

    Attached Files:

  22. panta

    panta

    Joined:
    Aug 10, 2012
    Posts:
    71
    I still have 1 question - what is an "UnityEngine.Animations.AnimationMotionXToDeltaPlayable"? It seems protected, and I'm wondering if I can remove the AnimationMotionXToDeltaPlayable, AnimationOffset, and the AnimationLayer to avoid all of the strange root motion offsets from the Timeline?
     
  23. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    Your assumption is correct, those are special playables for timeline root motion (and offsets). Removing them will definitely change how root motion is applied in timeline. If I recall correctly you will lose the ability to offset the animations from one another, which, depending on the application might not be a limitation.
     
  24. panta

    panta

    Joined:
    Aug 10, 2012
    Posts:
    71
    @seant_unity that's great! thank you! I'll experiment with trying to remove those, since our application of Timelines is purely gameplay so we aren't ever using offsets.

    I'm still having some trouble wrapping my head around how to blend TimelinePlayables together, mostly when it comes to VFX now.

    Just for some context, my goal is to create a PlayableGraph that is completely responsible for coordination of character animations, character foley audio, VFX, and gameplay events. I want our team's artists to be able to author timelines that contain all of these elements for the different "Skills/Abilities" in our game (a character action RPG), and whenever a skill is activated, use the PlayableGraph to blend from a base AnimationControllerPlayable that contains our locomotion blend tree into a TimelinePlayable that contains all of the visual and gameplay aspects of an ability. I need to be able to blend out of an ability TimelinePlayable into another TimelinePlayable (for combos) or back into the base AnimationControllerPlayable if we get cancellation input (e.g., pressure on the joystick to break out of an ability early). These breakouts/blends can happen at any point in the TimelinePlayable, not just at the end or at a fixed point. I've written custom tracks (the red tracks in the image below) that define these breakout windows.

    Here's an example of one of our Ability timelines:
    upload_2018-8-9_16-59-39.png
    I was able to get the first animation track ("@Attack_Combo1") to be inserted into my large AnimationOutput graph and blend the animation by using TimelineAsset.CreatePlayable and hooking that into the AnimationOutput.

    I was also able to get the audio track ("run_combat_attack_sw...") into my graph via the method you proposed (creating a new AudioOutput and wiring it up to the AudioMixerPlayable from the Timeline.

    Which brings us to VFX - I'm not sure how to play my VFX tracks without using the PlayableDirector. All of the VFX objects are parented under a child GameObject of my character, so I'm not sure how get my custom PlayableGraph to control the particle systems/VFX objects.
    upload_2018-8-9_17-5-33.png

    I see 3 possible paths to move forward: (I ordered them in terms of how difficult I perceive them)
    1. Continue to use PlayableDirectors, but when I instantiate the PlayableDirector gameobject into my Character's hierarchy, I need to iterate through the outputs, fetch the first track (the Character animation), destroy it from the PlayableDirector (so that the director doesn't touch the Character's Animator and just does gameplay events, audio, and VFX) and insert the TimelinePlayable from the PlayableDirector.playableAsset.CreatePlayable into my custom PlayableGraph that does the blending with the base AnimatorControllerPlayable/other TimelinePlayables
    2. Somehow take all of the outputs (incl. the ScriptOutputs and AudioOutputs) from the PlayableDirector, and move them into my PlayableGraph, making sure to take the AnimationOutput that controls the Character's Animator and slot that into my existing AnimationOutput so that I can blend it
    3. Remove the usage of PlayableDirectors from my project entirely and somehow figure out how to mimic the way the PlayableDirector enables/disables gameobjects and controls particle systems

    What would be your suggestion for how to get a TimelineAsset's VFX tracks to play when using a custom PlayableGraph instead of PlayableDirectors? I'm not sure which APIs I should be using to let the ScriptPlayableOutput to "know" which VFX gameobjects it should be controlling. I had a look at `scriptOutput.SetUserData` and `scriptOutput.SetReferenceObject` but those didn't seem to do anything.

    Thanks again!
    - John
     
  25. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    For track bindings other than Animation and Audio, ScriptPlayableOutput.SetUserData is the correct call. For clips bindings such as control tracks, they use exposed references.

    To set those, you first need to get the name from each clip:
    e.g. var name = ((ControlPlayableAsset) clip.asset).sourceGameObject.exposedName;

    To have those get resolved, you need to assign a IExposedPropertyTable to the graph using PlayableGraph.SetResolver before calling TimelinePlayable.CreatePlayable(). The exposed property table is simply a name/object lookup table, and would need to contain an entry for 'name'.

    PlayableDirector implements IExposedPropertyTable, and you could create one just to store the table, or have your own class that implements it.

    A small aside is you can assign the exposedName in script on control track clips, so you can author a timeline to say 'these 5 clips all reference the same object'. We don't have that exposed in the UI, but in your case that might be useful to give exposed reference names based on the VFX's purpose and the table could be shared among multiple timelines.

    Hope that helps
     
    panta likes this.
  26. panta

    panta

    Joined:
    Aug 10, 2012
    Posts:
    71
    @seant_unity you're the best man, thank you

    If you come to Unite LA this year, please let me buy you a coffee or a beer :)

    I actually managed to get my option #1 to work pretty well last night. My approach was basically this:
    1. On instantiation of the PlayableDirector's GameObject that holds my Character's ability's animations, events, audio, and VFX,
    2. Search the outputs of the playableDirector.playableGraph for the first AnimationPlayableOutput (as you can see in my image above of my Timeline structure, the first track is always the character animation)
    3. Disconnect that AnimationPlayableOutput's Playable from the graph (so that the PlayableDirector is no longer in charge of Character animation)
    4. Then use playableDirector.playableAsset.CreatePlayable to connect the Timeline into my custom PlayableGraph that holds my other AnimatorControllerPlayables and TimelinePlayables that are used for animation blending
    The code looks like this:
    upload_2018-8-10_14-2-59.png

    And the resulting before/after graphs:
    upload_2018-8-10_14-9-49.png

    upload_2018-8-10_14-14-51.png

    Hopefully there's nothing glaringly wrong with this approach... It's actually quite nice since I can blend away from a Timeline's character animation in my PlayableGraph, and have separate control over whether I destroy the PlayableDirector that's playing out the audio/VFX. In a lot cases, I still want the VFX or audio to hang around after the animation is done playing, e.g. for foley audio on a bunch of quick combos in succession. The part I don't like about it is that I'm not sure if the Timeline in my custom PlayableGraph is actually synced up still to the PlayableDirector's graph.

    FYI, at first I tried calling playableDirector.playableGraph.DestroyOutput on the AnimationPlayableOutput and I got tons of errors from some internal part of PlayableDirector. I don't think it can handle it when developers DestroyOutputs, but calling Disconnect didn't seem to cause any issues... As much as I love all this new Playables stuff, it seems like it's very easy for a developer to poke around at Playables and either a) get LogErrors from within Unity internal libraries that you can't address or b) bring down the whole editor. When there's not a lot of example projects around, it's pretty easy to run into this kind of issue.

    If I was to go a little deeper with option #3, what you mentioned is key for me to be able to do that, thank you! It seems a little daunting to recreate the entire functionality of the PlayableDirector.

    Is there any easy way to do my option #2 where I grab all of the PlayableOutputs from the PlayableDirector's graph and dump them into my custom PlayableGraph? I couldn't find an easy way to copy a PlayableOutput from one PlayableGraph to another without recreating them...
     
  27. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    Nice work! Glad to help and happy that you are having (at least some) success with Playables.

    For option #2, what might be useful is to use the list of outputs from playableDirector.playableAsset.outputs. This is what the playable director uses to get the description of the outputs that gets created in the graph. It will return you a list of PlayableBindings, which is a description of a required output. If you want to mess around with reflection, PlayableBinding contains an internal method ( internal PlayableOutput CreateOutput(PlayableGraph graph) ) that will create the necessary output in a graph. What is currently missing is a generic way to set the target binding, but for timeline right now it's either an Animation, Audio, or Script output.

    The sourceObject of playableBinding is actually a reference to the track, which is what is used in the PlayableDirector for Set/GetGenericBinding(). You could either use the playable director, or store that table yourself in a custom component.

    And at this point you are pretty close to option #3 ;)
     
    MikeMarcin and panta like this.