Search Unity

Animancer - Less Animator Controller, More Animator Control

Discussion in 'Assets and Asset Store' started by Kybernetik, Oct 8, 2018.

  1. brokenspiralstudios

    brokenspiralstudios

    Joined:
    Oct 20, 2021
    Posts:
    45
    hmm strange because consistently the toggling of Start Time on/off, causes the footstep sounds to either work or not work. I'll have to dig deeper as to why. The reason why I went this route is because after following the Footstep Events guide in your Examples I couldn't get any functions to trigger. I'll have to run through it again
     
  2. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    If you can replicate it, please send me a minimal reproduction project so I can take a look. I just tried it in the Footstep Events example and wasn't able to find any issue with it.
     
  3. MoonBeam91

    MoonBeam91

    Joined:
    Jan 28, 2021
    Posts:
    35
    Hello I can't find these files in in Animancer Pro, I can only see Brains-Root Motion Redirect
    upload_2023-3-6_0-39-36.png
     
  4. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    You must be using an old version of Animancer.

    If you don't want to update, just import the new version in a new project and grab those files.
     
  5. jarrodspurrier

    jarrodspurrier

    Joined:
    Jan 3, 2023
    Posts:
    5
    How would you go about making a 4-way DirectionalAnimationSet that is represented as LinearMixerTransitionAssets instead of clips? That way we can blend from idle -> walk -> run in each direction. I'm leaning towards just doing away with the DirectionalAnimationSet since it just appears to be simply a helper class for resolving input direction into its respective animation.

    Also, does it makes sense to use a TimeSychronizationGroup for syncing the animations across each mixer (i.e. forward, left, right, backward) or would that be redundant?
     
  6. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    I'd probably make a serializable class which has an array of DirectionalAnimationSets and creates a linear mixer for each direction at runtime.

    Those mixers will only synchronize their own animations so it still makes sense to synchronize them when you swap between them, but you might want to try the New Time Synchronizer System instead of the old TimeSychronizationGroups. And please let me know which system you end up using because I'll probably remove the old groups in the next major version unless people are actually using them.
     
  7. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    Hello there!

    I have solved the issue with this code, however I would like to avoid the allocation due to the lambda variable catching. Any suggestion?

    Code (CSharp):
    1.   if ((playState = _animancer.Layers[(int)entityAnimLayer].TryPlay(_requestedAnimState.AnimName, 0.25f)) != null)
    2.                     {
    3.                         if (playState.Clip.isLooping == false)
    4.                             playState.Events.OnEnd = () => playState.Layer.Stop();
     
  8. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    The Event Utilities example explains how you can access the current state during an event and how to cache the delegate to avoid re-allocating it every time.
     
    sebas77 likes this.
  9. Leslie-Young

    Leslie-Young

    Joined:
    Dec 24, 2008
    Posts:
    1,148
    Please fix this. Do not force us to enable components for things like a readme to not throw errors in a project. I've deleted the related scripts for now.

    Assets\Plugins\Animancer\Internal\Editor\ReadMe.cs(263,30): error CS1069: The type name 'UnityWebRequest' could not be found in the namespace 'UnityEngine.Networking'.
     
  10. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    According to the documentation, UnityEngine.Networking.UnityWebRequest exists in every version of Unity supported by Animancer and it's in a built-in module that is enabled by default in every project. Which version of Unity are you using?
     
    Last edited: Mar 9, 2023
  11. jarrodspurrier

    jarrodspurrier

    Joined:
    Jan 3, 2023
    Posts:
    5
    That's a clever idea, thanks! Also, we went with your new TimeSynchronizer. It was however difficult figuring out if it was working properly. Watching the inspector was not giving us the results we expected nor was logging the NormalizedTime before and after synchronizing the state.
     
  12. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Can you describe what you were seeing in any more detail? Is there anything you think could be changed to make it easier to use and verify that it's working?
     
  13. jarrodspurrier

    jarrodspurrier

    Joined:
    Jan 3, 2023
    Posts:
    5
    Sure, I was expecting the inspector to look something like the synchronized example here: https://kybernetik.com.au/animancer/docs/examples/directional-sprites/character/ (which it didn't). The mixers I was trying to synchronize appeared to always start at 0 when transitioning between them which made me think I wasn't using it properly.

    I essentially had to just set break points and evaluate the normalized time of the state before storing, ensure the time is actually stored in the TimeSynchronizer with the provided key, then make sure that the stored time was applied to the new mixer after synchronizing by evaluating the state after playing.

    A few ideas:
    • I would love some way to see which animations are synchronized in the inspector, perhaps the animations with the same key can provided a unique color so instead of the green progress bar for instance, I could have a Locomotion group that's yellow so I can quickly see which animations are being synchronized together.
    • Show just a text overlay in the inspector identifying which group they belong to.
    • Or even more simply, some togglable logging i.e. "AnimationX of type Locomotion synchronized time to 0.25s"
     
  14. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Using colours would randomly be misleading whenever different groups happen to get similar ones so I'd probably go for text. Adding in some optional logging is also a good idea. Thanks for the suggestions.
     
  15. brokenspiralstudios

    brokenspiralstudios

    Joined:
    Oct 20, 2021
    Posts:
    45
    Hello, I created a scriptable object that lives on a Weapon class gameObject. Inside the scriptable object are ClipTransitions which are attack animations. Problem is when I add events to the clips, I can't reference scene objects for the Unity Events to call functions from. Is there a way to make this work with scriptable objects storing the data? Thanks
     
  16. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Assets can't serialize references to scene objects because the scene might not be loaded when you load the asset.

    I generally recommend the Hybrid approach to using events, i.e. give them names and set their callbacks in code instead of the Inspector.
     
  17. jiraphatK

    jiraphatK

    Joined:
    Sep 29, 2018
    Posts:
    300
    With the set up like
    Left - idle - right
    -1 0 1

    if the value change from -1 to 1, the character will temporarily change to idle state in the -0.5 - 0.5 value range.
    This is fine when I know the target of value is 0 (when player didn't press the move button)
    but when they press A then D, with the target that is not 0, I want the mixer to completely skip the idle state to make the animation go from left -> right straightaway.

    Is there a way I can temporarily disable the idle state with code to prevent this?

    I tried to set the weight of the idle state to 0 (both .Weight and SetWeight()) but it seems like it didn't have any effect as the idle weight in the inspector still get increase and player still evaluate its animation

    Edit: Current working solution: play the clip on another layer.
     
    Last edited: Mar 18, 2023
  18. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    You can add and remove states from a mixer as necessary, but that's not really what you want.

    Try something like this:
    Code (CSharp):
    1. public static class MixerChildFade
    2. {
    3.     private static readonly List<float>
    4.         ChildWeights = new List<float>();
    5.  
    6.     public static void FadeChildWeights<TParameter>(
    7.         this MixerState<TParameter> mixer,
    8.         TParameter parameter,
    9.         float fadeDuration)
    10.     {
    11.         ChildWeights.Clear();
    12.  
    13.         var childCount = mixer.ChildCount;
    14.         for (int i = 0; i < childCount; i++)
    15.             ChildWeights.Add(mixer.GetChild(i).Weight);
    16.  
    17.         mixer.Parameter = parameter;
    18.         if (!mixer.RecalculateWeights())
    19.             return;
    20.  
    21.         for (int i = 0; i < childCount; i++)
    22.         {
    23.             var child = mixer.GetChild(i);
    24.             child.SetWeight(ChildWeights[i]);
    25.             child.StartFade(child.TargetWeight, fadeDuration);
    26.         }
    27.     }
    28. }
    That will let you call
    mixer.FadeChildWeights(parameter, fadeDuration);
    and will have it fade the child state weights instead of fading the parameter, meaning it won't have to go through the idle.

    Let me know how it goes. If it works well, I'll add it to the next version of Animancer.
     
    jiraphatK likes this.
  19. jiraphatK

    jiraphatK

    Joined:
    Sep 29, 2018
    Posts:
    300
    It works. thank you.
    Here's a comparison video of the feature.


    Although I have to modify the code you give me

    Code (CSharp):
    1.  
    2. for (int i = 0; i < childCount; i++)
    3.             {
    4.                 var child = mixer.GetChild(i);
    5.                 //min value to prevent weight at complete 0 which make child state normalize time not progressing
    6.                 child.SetWeight(ChildWeights[i] == 0f ? 0.0000001f : ChildWeights[i]);
    7.                 child.StartFade(child.TargetWeight, fadeDuration);
    8.                
    9.                 //don't know why they are not playing
    10.                 if (!child.IsPlaying)
    11.                 {
    12.                     child.IsPlaying = true;
    13.                 }
    14.             }
    15.  
    Also, I think this need to have optimize overload to let user provide the list of weight themself for optimal performance.
    Overall, it's a great feature to have in the official version! no more feet sliding to idle state XD
     
  20. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Just change the StartFade line to this:

    child.StartFade(Math.Max(child.TargetWeight, float.Epsilon), fadeDuration);


    If a fade ends at 0 it stops the state but mixers aren't expecting their children to be stopped so they wouldn't restart on their own until you play the mixer again. So fading to epsilon (the smallest possible float value above 0) avoids that. Letting them pause would mess with the time synchronization when it unpauses a child.

    Passing in your own list wouldn't provide any benefit. The list is static and will be reused every time you call the method. It doesn't actually store anything outside that method, it just grabs the weights before the RecalculateWeights call so they can be re-applied after.
     
    jiraphatK likes this.
  21. jiraphatK

    jiraphatK

    Joined:
    Sep 29, 2018
    Posts:
    300
    Ahh yeah that's fix all the issues.
    About providing my own array. I thought List would create garbage every time the .Add() is called, since my understanding was that it resized the internal array.
    But Erh, after some googling it apparently doesn't work like that.
    Thanks for the help :)
     
  22. brokenspiralstudios

    brokenspiralstudios

    Joined:
    Oct 20, 2021
    Posts:
    45
    The debug is not firing. The animation is confirmed to be playing and events have been placed in the inspector.

    Code (CSharp):
    1. public class AnimationHandler : MonoBehaviour {
    2.  
    3. private const string Step = "Step";
    4. [EventNames(Step)] [SerializeField] private ClipTransition _walk;
    5.  
    6. private void Awake() {
    7.    _walk.Events.SetCallback(Step, PlayStepSfx);
    8. }
    9.  
    10. private void PlayStepSfx()  {
    11.    Debug.Log("Step Sfx");
    12. }
     
  23. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    You aren't playing the animation there. Even if another script happens to be playing the same clip, it won't have the event you added to the transition in this script.
     
  24. brokenspiralstudios

    brokenspiralstudios

    Joined:
    Oct 20, 2021
    Posts:
    45
    sorry forgot to include that part of the code
    Code (CSharp):
    1. private void Update() {
    2.    HandleMovement();
    3. }
    4.  
    5. private void HandleMovement() {
    6.             ClipTransition moveAnim = null;
    7.             if (_itemInHand != null) {
    8.                 moveAnim = _parameters.CanRun()
    9.                 ? _itemInHand.Data.Run
    10.                 : _itemInHand.Data.Walk;
    11.             }
    12.             else {
    13.                 moveAnim = _parameters.CanRun()
    14.                 ? _run
    15.                 : _walk;
    16.             }
    17.             PlayBase(moveAnim, false, AnimationType.Movement);
    18.         }
     
  25. hugo-purespark

    hugo-purespark

    Joined:
    Oct 29, 2022
    Posts:
    15
    Hi Using the IK Puppet example.

    How can I add more spheres to control other body parts?
     
  26. ununion

    ununion

    Joined:
    Dec 2, 2018
    Posts:
    275
    simple problem before purchase:
    Can i define difference transition to on clip? like Idle->Run Walk->Run Attack->Run and they have difference settings.
     
  27. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    @brokenspiralstudios In that case your code looks like it should work fine so the problem is likely somewhere else in your scripts or scene setup.

    @hugo-purespark You can control knees and elbows by calling the IK Hint methods (e.g. SetIKHintPosition instead of SetIKPosition). So for that example you could make a copy of IKPuppetTarget which uses the hint methods and use it similarly in the IKPuppet script. For anything other than that you'd need to use another system like Animation Rigging or Final IK because Unity's inbuilt IK only works on the standard Humanoid bones.

    @ununion Yes, but perhaps not in the way you're thinking. Animancer's transitions only define the destination, not the source. So you can define as many transitions to Run as you want, but it's up to your code to choose which one you tell Animancer to play.
     
  28. ununion

    ununion

    Joined:
    Dec 2, 2018
    Posts:
    275
    really thanks to your support,i m trying to convert my animator controller hell to animancer lit and then judging if i will purchase in a bussiness project. finally a question here,will animancer create runtime gc during playing?(we can ignore the gc created by lazy init)
     
  29. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Animancer lazy initializes basically everything but doesn't drop any garbage during regular use.

    Your scripts using delegates for Animancer Events can lead to garbage though. If you play a clip and add events to it like in the Basic Action Example, the delegate you give it will be garbage collected when you play something else and it clears that event. But if you instead use Transitions like in the Transitions Example, you only need to set up the event callback once on startup and the transition will reuse it every time it's played so it will only get garbage collected along with everything else when your whole script is destroyed.
     
    ununion likes this.
  30. ununion

    ununion

    Joined:
    Dec 2, 2018
    Posts:
    275
    Thanks Kybernetik,that's mean 0gc for me.
     
  31. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    When I'm previewing an animation, it starts partway through the clip. For example, if I quickly play and pause, I wind up with something like this:

    upload_2023-3-22_7-56-14.png

    It doesn't seem to be related to the Start Time or Fade Duration. Is there something else that causes this?
     
  32. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    If the Start Time isn't set and the animation was already playing, it will continue from its previous time. Other than that, there isn't really anything that would change an animation's time.
     
  33. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    It looks like it jumps ahead every time I hit play, actually, by an amount proportional to the preview speed:

    upload_2023-3-22_8-57-4.png

    After playing and pausing quickly...

    upload_2023-3-22_8-57-10.png

    It's not that the editor is hanging -- it skips ahead immediately.
     
  34. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Looks like a bug in the preview window. I'll look into fixing it tomorrow.

    Are you seeing that behaviour outside the preview window?
     
  35. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    Nope, it's strictly a problem with the preview itself. The animations are playing properly.

    (or is there some other way to play the animation in-editor that I should be checking?)
     
  36. hugo-purespark

    hugo-purespark

    Joined:
    Oct 29, 2022
    Posts:
    15
    Thanks for the info.

    I'm building a VR application that records the position of the spheres as you move them and then you can replay the animation. Replaying move the position and rotation of the sphere according to the recorded history. The problem that I have is that I cannot assign a sphere to other bones like the fingers. I've tried pragmatically rotating the finger bones, but it seems that ApplyAnimatorIK is locking every bone while the animation is playing and setting the bone rotation does nothing. I can disable ApplyAnimatorIK but then the sphere do not control the movement anymore.

    From your answer you suggest another system like Final IK. Can I use that in combo with Animancer to achieve this?
     
  37. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    That's not due to IK, it's just how all of Unity's animation systems work. If you want to control the bones of an animated character in a script, you need to set them every LateUpdate which runs after the animation system updates and applies its values to the bones.

    Final IK can be used with Animancer (it doesn't care what animation system you use) and would likely help do what you want, but I've never really used it so I can't be sure.
     
  38. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    Also, I just wanted to say that I'm really liking this thing! I made a little Soulslike game in a game jam last weekend, and I took the opportunity to learn to use your state machine system. I had to stare at the docs for a little while until it clicked, but it worked really nicely.

    (splitting input into a separate Brain class was also a very good idea)

    I'm working on a second, much-less-rushed game now, and it feels a lot easier to write coherent logic! Directly controlling animations from the individual states makes things a lot more predictable.
     
  39. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    Although, I do remember that I had one question -- a problem I've started to notice goes something like:
    • State A sets an animation's OnEnd event to go back to the default state
    • State A plays that animation on layer 1
    • State B is entered
    • State B plays an animation on layer 2, which completely overrides layer 1
    • The animation on layer 1 finishes, and the state machine snaps back to the default state
    Should I just clear the animation's OnEnd when exiting State A? I don't want to just stop the animation, since that'll be jarring.
     
  40. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Looks like the preview thing is a Unity bug. When unpausing a PlayableGraph in Edit Mode, its first update gives it the maximum possible delta time value (0.3333333) which causes a massive jump. I've reported it to them.

    For your layering issue, you could either have the State A End Event do nothing if Layer 2 is playing something or you could have State B simply set
    animancer.Layers[0].CurrentState.Events = null;
    (or just clear its End Event if it might have other important events).

    A more complex solution would depend on your specific use case. For example, it might make sense to simply have a separate StateMachine for each layer.

    Is there any particular part of the FSM documentation that you found hard to follow? I'd like to make it easier to understand, but it's hard for me to get into that mindset when I'm so intimately familiar with the whole system.
     
  41. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    I'm not sure if any part was abnormally hard to follow -- it's just a fair number of concepts all at once!

    Part of it might be unfamiliarity with some of the patterns you use a lot, like serialized private fields and extension-method GetComponent* stuff.

    But that's just a small hiccup.

    I'm very glad you chose to do detailed written docs instead of making a bunch of video tutorials. They're very comprehensive.

    re: clearing the events, I see that one solution is to do this:

    Code (CSharp):
    1. public override bool OnExitState() {
    2.   base.OnExitState();
    3.   Entity.Animancer.States[anim].Events = null;
    4. }
    This would ensure that no events run once the state exits. I guess this could be overkill for some cases (especially if the animation takes a long time to fade out), but it seems like a good default.

    In this case, the state is for a dodge roll, and the "overriding" state is for something like a death animation. So, it makes sense for absolutely nothing to happen once the death state triggers.
     
  42. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    That should be up to the DeathState to have its CanExitState return false. You want the death animation to interrupt the previous animation so it doesn't really make sense to play it on another layer where it would let the previous animation keep playing. Not being able to interrupt death is a logical mechanic so it should be in the logical state machine rather than the animation system. Your approach would work of course, it just doesn't seem like the right place for that responsibility.
     
    chemicalcrux likes this.
  43. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    The dodge state exits using ForceSetDefaultState, though, since otherwise it wouldn't be possible to go from a higher-priority state (like the dodge state) to a lower-priority state (like the idle state).

    I suppose I could have each state indicate if it's "over" and then ignore priority if the state being exited has that flag set to true.
     
  44. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    That's the problem with using force, it's easy for simple cases but you don't get enough control for a more complex setup. The Interruptions Example explains a few ways you can set up a proper priority system.
     
  45. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    Yeah, I'm going to see if I can replace all of the instances of Force functions with regular ones.

    I originally figured that it wouldn't be an issue, because:
    • Only one state is active at a time
    • If a state is active, then the animation it played must be active, or it would've been replaced
    • So, if the animation's end event fires, then the state must still be active
    • So, it must be okay to force the state
    ...and then I started using layers :p
     
  46. ununion

    ununion

    Joined:
    Dec 2, 2018
    Posts:
    275
    Hi Kybernetik,maybe a bug about rootmotion
    i create a prefab via this static factory,it is very simple,and you can ignore GameObjectUtil,it just create prefab and GetOrCreate a Monobehavior
    then i set a custom position for the new gameobject
    finally i use animancerComponent.Play
    but the strange thing is that every time i create model it will show in random position,sometimes it will show at (0,0,0) and some time it will show at the specific position.
    I set ApplyRootMotion=true in animator
    and if i disable ApplyRootMotion,everything will be correct
    upload_2023-3-29_0-1-1.png
     
    Last edited: Mar 28, 2023
  47. ununion

    ununion

    Joined:
    Dec 2, 2018
    Posts:
    275
    upload_2023-3-29_0-4-39.png
    and this is the idle clip settings,it will always cause random position whatever setting,only without enable the rootmotion in animator then the position will be right.
     
  48. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    Is there a way I can use the same event name more than once? I might have several points in an animation that need to do the same thing, and it's a little tedious to name them "X 1", "X 2", "X 3", and so forth.

    It looks like that, if you have duplicate event names, only the first one gets counted.

    I'd like all of the same-named events to trigger the same function.

    I guess this might be a place where Animation Events make more sense -- in this case, I'm triggering camera shake at certain points in an animation. It's very generic behavior, so I can just slap a component on the animator's object that deals with it.
     
  49. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    @ununion When you say "random position" do you mean it changes every time or it's just the same unexpected position every time?

    Animations will control the local position of the Animator so if it's your character's root object then it will be moved relative to its parent. I generally recommend having the model/Animator as a child of the character's root and other components and redirecting the root motion (if you want to use root motion).

    @chemicalcrux If you're using Animancer v7.4, there are SetCallbacks/RemoveCallbacks/etc. methods which will affect all events with the specified name.
     
  50. ununion

    ununion

    Joined:
    Dec 2, 2018
    Posts:
    275
    I mean it will change every time between these two position,one is (0,0,0) in world axis and another is the position set in static factory
    the result is fully random,I cannot know which pos will give next time.
    the animator and animancer component and character controller is in the root object of my human.