I’ve been diving into the Timeline and Playables for the past few days but I’ve been unable to come up with a clean solution to what I think is a simple/common use case.
The simplified version of what I want to be able to do:
-
Have a standard animator which defines idle and movement blend states for a character. (e.g. two different movement blend states that transition between themselves via a bool parameter (like a wounded vs.healthy movestate, with the transition handled via a ‘Damaged’ bool).
-
Have a suite of items which have associated abilities. (e.g. ‘dagger’ item which defines an ‘attack’ ability).
-
These abilities take the form of timelines, which have associated animations and gameplay triggers (e.g. on the dagger prefab there is a timeline on which a ‘dagger attack’ animation is placed. This way it can be scrubbed by the designer to identify proper time to trigger gameplay events).
^This much is very easy and straightforward to accomplish. However, I do not want the ‘dagger attack’ timeline to interrupt any movement animation or root motion that may or may not be playing via the active animator. The attack animation should instead have an upper-body AvatarMask applied when moving, so that the animator is always driving the legs/root motion.
This goal of conditional masking seems like it should be easily achieved via
SetLayerMaskFromAvatarMask() function on AnimationLayerMixerPlayable, as is seen in the manual. In this case we use the layerMixer with two inputs, the first is the character’s runtimeAnimatorController wrapped in a AnimatorControllerPlayable, and the other input is the animation clip. Easy enough, but here I’ve run into a hurdle:
- I am unsure how to sync the AnimatorControllerPlayable with the already running animator. This issue is twofold:
- There is a hiccup when activating the AnimatorControllerPlayable, as the character’s animator is already, say, halfway through it’s current animation, but the animatorControllerPlayable starts at the beginning. I’m unsure how to sync the timing. I’ve tried the following, with SetPropagateSetTime(), but it does not appear to work:
var source =animator.playableGraph.GetOutputByType<AnimationPlayableOutput>(0).GetSourcePlayable();
double time = source.GetTime();
ctrlPlayable.SetTime(time);
- If the AnimatorControllerPlayable is built after the animator has been running and it’s parameters have been changed, then the animatorControllerPlayable inherits none of the changed parameters. SO lets say the animator has been running and the bool ‘Damaged’ has changed from the default ‘false’ to ‘true’. If we then create a new AnimatorControllerPlayable at this time, then the ‘Damaged’ value will be the default ‘false’. What’s odd is that from this point on, the new AnimatorControllerPlayable will then match any new changes to the Animator’s parameters. It seems like the bindings have been set, but not initialized to match the associated animator.
Am I going about this all wrong? Again, I wish to have timeline-defined abilities which interface with the animator in such a way that the player is able to move and attack at the same time, and the animator is able to handle the movement animation, while the timeline handles the attack aimation. If there is an alternative or intended way of achieving this, please let me know.
edit: I should mention that I am aware that I could conditionally rebuild the character’s AnimationOverrideController based on what abilities are equipped, and then use the timeline to activate bool values on the animator instead of playing the actual animations themselves, but this significantly increases the amount of maitenance of the animator, and it also obfuscates the workflow. This solution could work but it seems like the Timeline can be put to better use here.