Search Unity

Animancer - Less Animator Controller, More Animator Control

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

  1. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    Hi there -- been giving Animancer Lite a try. I'm quite liking it so far!

    My old Mecanim animation had a layer for actions, separate from the locomotion layer. The action layer's "idle" state wasn't given an animation clip at all, so that the layer would do nothing when in that state.

    I tried doing the same thing with a ClipTransition with no animation clip, but I got an exception about the clip being null.

    So, instead, I'm just fading the action layer's weight to zero when an action is not being performed. This requires that I 1) stop the current state and 2) use StartFade to fade the layer out.

    Is that the "right" way to handle this?
     
  2. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Yes, that's how I'd handle it.

    You shouldn't need to stop the current state though. Just let it keep playing while it fades out and it will be stopped automatically when the layer reaches 0.

    It would be really easy to implement a dummy state to act like an empty state in an Animator Controller, but I haven't yet seen a real need for it. In this case it would achieve the same result but you'd end up with a layer playing the dummy state instead of a layer with no weight, which would just waste a bit of performance.
     
  3. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    It's a non-looping animation, so it'd just kinda halt at the end (and then I get a warning because my end event didn't stop the animation!)

    Actually, that does remind me of a second thing that I might've just missed while reading the documentation.

    Normally, I'd set up my Mecanim transitions so that one animation blends into another before the first animation is done.

    Would the equivalent here be to change the End Time on the TransitionClip that I'm blending from, and to then respond to the end event by playing the next transition?

    It seems a bit weird, since I might want to adjust this end time depending on which animation I'm transitioning to. Normally, I'd just be able to configure that per-transition in the Mecanim animator.
     
  4. brokenspiralstudios

    brokenspiralstudios

    Joined:
    Oct 20, 2021
    Posts:
    45
    yes can confirm that FinalIK works great PLUS works with Animancer. A great combo!
     
  5. Spikebor

    Spikebor

    Joined:
    May 30, 2019
    Posts:
    280
    Will buy on the sale, thanks guy.
    It's fine, for me since I think IK can helps with development a lot.
    But imagine telling newbies "You need this 90$ asset and this 90$ asset to play animation and IK in Unity because Unity 's Animation Rigging is causing trouble" they will riot :p
     
  6. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    @chemicalcrux Fading out the layer should prevent the warning about an end event not ending the animation.

    Changing the end time of the previous animation is the correct way to determine when you want to start the next transition (since it's your code that will be starting the next transition). If you want to adjust that time, you can modify it at runtime or you could add multiple Animancer Events which each check different conditions to decide what to play next.

    The idea of Transition Sets which define pairs of from->to transitions is something I'd like to add sometime. Possibly for the next major version, but I haven't started work on it yet and there are a few other key features I could work on.

    @Spikebor that's probably accurate as to how people would react, but it's not really a reasonable reaction. Unity wouldn't be such a popular engine without such good support for plugins and Animancer wouldn't be anywhere near as developed as it is if I wasn't able to make money off it. Unity being free is pretty ridiculous in the first place, and that skews people's perception of how much the tools of their trade (or even hobby) are actually worth. That much money is nothing compared to what you'd be spending on other hobbies.
     
    chemicalcrux likes this.
  7. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    Got it, thank you!

    (and I'm using FinalIK right now! it's a very useful add-on)
     
  8. alexblee

    alexblee

    Joined:
    Dec 16, 2021
    Posts:
    3
    Hi there, been using Animancer on our project for a while.

    We are making an FPS game, and we were wondering if caching and re-applying states to an AnimancerComponent is easily done.

    Right now when we switch from a primary weapon to a secondary weapon, our flow for the hands goes like this:
    Switch -> Destroy current States -> Create new states for new weapon -> Apply it to the AnimancerComponent

    Is it possible to do it without destroying the states, and rather, caching them for use later? I've attempted it myself simply by taking the states and doing an Apply(), but it always had some weird behavior with things like parameters and such. Is there a more proper way?
     
  9. jenkins22

    jenkins22

    Joined:
    Nov 15, 2014
    Posts:
    10
    Hi, what is the recommended way to reconcile the Layers example with the FSM / Brain example? Specifically, to play an Action while the character is moving. Should you 1) Create a
    MovingActionState
    counterpart to the
    ActionState
    , which handles both the movement and the action, or 2) handle all movement outside the FSM? Or perhaps another solution entirely?

    The problems I see with 1) is that if the character stops moving in the middle of an action, there needs to be a seamless transition from the
    MovingActionState
    , where the action animation plays on a layer, to the
    ActionState
    , where it plays on the whole character. Also, since
    MovingActionState
    can't inherit from both the locomotion and the action states, there'll be some logic duplication somewhere.

    The problems I see with 2) is that the locomotion animations need to be handled outside the FSM, but some states might wish to prevent movement (e.g. flinching), so the movement script becomes dependent on the current state. The states also need to know whether the character is moving or not, to know whether to play on a layer or on the whole character. Finally, states still need to handle the seamless transition between playing an animation on the whole body or on a layer if the character starts/stops moving in the middle of an action.

    Any help / advice is appreciated!
     
  10. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    @alexblee Something like this?

    Code (CSharp):
    1. public readonly struct StateDetails
    2. {
    3.     public readonly AnimancerState State;
    4.     public readonly float Time;
    5.     // Weight, TargetWeight, FadeDuration, etc.
    6.  
    7.     public StateDetails(AnimancerState state)
    8.     {
    9.         State = state;
    10.         Time = state.Time;
    11.     }
    12.  
    13.     public void Apply()
    14.     {
    15.         state.Time = Time;
    16.     }
    17. }
    18.  
    19. private List<StateDetails> _StoredDetails;
    20.  
    21. // Store.
    22. _StoredDetails = new List<StateDetails>();
    23. foreach state in the weapon
    24.     _StoredDetails.Add(new StateDetails(state));
    25.  
    26. // Apply.
    27. for (int i = 0; i < _StoredDetails.Count; i++)
    28.     _StoredDetails[i].Apply();
    @jenkins22 I've never actually done it, but in theory you should be able to make a LayeredAnimationManager like in the Dynamic Layers Example then have all your states play their animations through it instead of letting them directly interact with Animancer.
     
  11. AnthonyOvermatch

    AnthonyOvermatch

    Joined:
    Nov 18, 2021
    Posts:
    2
    Hello,

    I'm currently trapped in the AnimatorController web hell and Animancer looks to be an awesome package to improve the scalability and modularity of animations in our app.

    Problem is we use networking and I need to ensure that I can support that before attempting a cutover. We use the Mirror API which has a Network Animator Controller that handles synchronization of our Animator Controllers. (Check out: Network Animator - Mirror (gitbook.io)).

    I was wondering if you or anyone else has had any experience either with Animancer and Mirror directly, or networking Animancer in general.

    Thanks!
     
  12. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    I have very little networking experience, but this thread has some insights about networking Animancer and Photon Fusion has a tech demo for networking Animancer using their system.
     
  13. AnthonyOvermatch

    AnthonyOvermatch

    Joined:
    Nov 18, 2021
    Posts:
    2
    Thanks Kybernetik, I'll check them out!
     
  14. jiraphatK

    jiraphatK

    Joined:
    Sep 29, 2018
    Posts:
    300
    I'm thinking of migrating my quite complex animator controller (300+) animations to animancer(due to some unfortunate limitation of the controller system). I'd appreciate any suggestion on how to organize that many animations...
     
  15. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    The Conversion page explains how Animator Controller concepts translate to Animancer, but for general organization I'd recommend trying to find logical ways of grouping the animations. If you have various different weapons with their own animations, store those animations with their weapon like in the Weapons Example. If you have a common pattern that gets repeated multiple times, come up with a data structure like Directional Animation Sets to hold them.
     
    jiraphatK likes this.
  16. MikeChr

    MikeChr

    Joined:
    Feb 2, 2018
    Posts:
    43
    Just purchased Pro version. I wish the animation to start from the start on each play() without a transition from the animation positions at the end of the previous play(). The animation plays correctly the first play, but each next play has a (fast) transition from the end of the previous play. Thanks.
     
  17. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
  18. jiraphatK

    jiraphatK

    Joined:
    Sep 29, 2018
    Posts:
    300
    It's only been a day and I am so glad I decided to take the leap of faith. It's not an easy choice because we are in our mid production and our PlayerController are quite complex but turns out it is not that our animator is complex but Animator Controller adds complexity to every damn thing and it doesn't scale very well XD. Having the ability to manipulate normalized time or getting the correct data right away without waiting for the animator to sync the state save me SO MUCH PAIN. I can get rid of all the coroutine waiting for player data to sync and tens of int parameters and enum representation in code and just PLAY the animation.
    Thank you for creating this amazing asset.
    I only have a problem with MatchTarget() as there doesn't seem to be any straightforward equavalent in Animancer but that's solvable with just some code.
     
  19. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    It shouldn't be a leap of faith because Animancer Lite lets you try out all the features for free.

    And yes, target matching is unfortunately not supported by the Playables API. I've never actually used it so I don't really know how it works, but I'd be happy to work with you to implement something similar.
     
  20. MikeChr

    MikeChr

    Joined:
    Feb 2, 2018
    Posts:
    43
    Never mind... FYI, here is what I wanted ...

    public class PlayClip : MonoBehaviour {
    [SerializeField] private AnimancerComponent _Animancer;
    [SerializeField] private AnimationClip animationClip;
    private AnimancerState astate = null;

    public void Start() {
    astate = new ClipState(animationClip);
    }

    private void OnEnd() {
    astate.IsPlaying = false;
    }

    public void playClip() {
    astate.Time = 0;
    astate = _Animancer.Play(astate);
    astate.Events.OnEnd = OnEnd;
    }
    }
     
  21. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    You could simplify that a bit by using AnimancerEvent.CurrentState to avoid needing to create and store the state yourself:
    Code (CSharp):
    1. public class PlayClip : MonoBehaviour
    2. {
    3.     [SerializeField] private AnimancerComponent _Animancer;
    4.     [SerializeField] private AnimationClip _AnimationClip;
    5.  
    6.     public void PlayClip()
    7.     {
    8.         var state = _Animancer.Play(_AnimationClip);
    9.         state.Time = 0;
    10.         state.Events.OnEnd = OnEnd;
    11.     }
    12.  
    13.     private void OnEnd()
    14.     {
    15.         AnimancerEvent.CurrentState.IsPlaying = false;
    16.     }
    17. }
    And if the animation isn't looping, you don't need the end event either because it will naturally pause at the end.
     
  22. jiraphatK

    jiraphatK

    Joined:
    Sep 29, 2018
    Posts:
    300
    Here is how I do it on my end with Animancer. I only need MatchTarget() for parkour animations (dynamically match the animation jump height to the obstacles) so I only care about matching the root of the animation and OnAnimatorMove work fine like this. But Unity api let you specify which part of the humanoid body you want to match using an AvatarTarget . Not sure how they do that. their code is in the C++ side.

    Code (CSharp):
    1. using Animancer;
    2. using UnityEngine;
    3.  
    4. [RequireComponent(typeof(AnimancerComponent), typeof(Animator))]
    5. public class CustomMatchTarget : MonoBehaviour
    6. {
    7.     [Header("MatchTarget Setting")]
    8.     public bool enableMatchTarget;
    9.     [SerializeField] private Transform targetPose;
    10.     [SerializeField] private float startMatchAtNormTime;
    11.     [SerializeField] private float endMatchAtNormTime;
    12.     [SerializeField,Range(0,1)] private float positionWeight;
    13.     [SerializeField,Range(0,1)] private float rotationWeight;
    14.  
    15.     [Header("References")]
    16.     [SerializeField] private AnimationClip clip;
    17.  
    18.     //cache
    19.     private AnimancerComponent animancer;
    20.     private AnimancerLayer baseAnimLayer;
    21.     private AnimancerState clipState;
    22.  
    23.     //internal data
    24.     private bool _isMatchingTarget;
    25.  
    26.     private Vector3 _posWhenStartMatch;
    27.     private Quaternion _rotWhenStartMatch;
    28.  
    29.     private Vector3 _currentFrameAnimationPos;
    30.     private Quaternion _currentFrameAnimationRot;
    31.  
    32.     private Vector3 _currentFrameMatchTargetPos;
    33.     private Quaternion _currentFrameMatchTargetRot;
    34.  
    35.     private void Awake()
    36.     {
    37.         animancer = GetComponent<AnimancerComponent>();
    38.     }
    39.  
    40.     void Start()
    41.     {
    42.         baseAnimLayer = animancer.Layers[0];
    43.         Play();
    44.     }
    45.  
    46.     [ContextMenu("Play")]
    47.     public void Play()
    48.     {
    49.         //reset the pose
    50.         transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
    51.         clipState = baseAnimLayer.Play(clip,0f,FadeMode.FromStart);
    52.     }
    53.  
    54.     void Update()
    55.     {
    56.         if (baseAnimLayer.IsPlayingClip(clip) && baseAnimLayer.CurrentState == clipState)
    57.         {
    58.             if (!_isMatchingTarget && clipState.NormalizedTime > startMatchAtNormTime)
    59.             {
    60.                 _posWhenStartMatch = transform.position;
    61.                 _rotWhenStartMatch = transform.rotation;
    62.                 _currentFrameAnimationPos = _posWhenStartMatch;
    63.                 _currentFrameAnimationRot = _rotWhenStartMatch;
    64.             }
    65.  
    66.             if (clipState.NormalizedTime >= startMatchAtNormTime && clipState.NormalizedTime <= endMatchAtNormTime)
    67.             {
    68.                 _isMatchingTarget = true;
    69.                 //possible divide by zero
    70.                 var t = (clipState.NormalizedTime - startMatchAtNormTime) / (endMatchAtNormTime - startMatchAtNormTime);
    71.                 _currentFrameMatchTargetPos = Vector3.Lerp(_posWhenStartMatch, targetPose.position, t);
    72.                 _currentFrameMatchTargetRot = Quaternion.Lerp(_rotWhenStartMatch, targetPose.rotation, t);
    73.             }
    74.             else if (_isMatchingTarget && clipState.NormalizedTime > endMatchAtNormTime)
    75.             {
    76.                 _isMatchingTarget = false;
    77.             }
    78.         }
    79.     }
    80.  
    81.  
    82.     private void OnAnimatorMove()
    83.     {
    84.         if (enableMatchTarget && _isMatchingTarget)
    85.         {
    86.             _currentFrameAnimationPos += animancer.Animator.deltaPosition;
    87.             _currentFrameAnimationRot *= animancer.Animator.deltaRotation;
    88.  
    89.             transform.position = Vector3.Lerp(_currentFrameAnimationPos, _currentFrameMatchTargetPos, positionWeight);
    90.             transform.rotation = Quaternion.Lerp(_currentFrameAnimationRot, _currentFrameMatchTargetRot, rotationWeight);
    91.         }
    92.         else
    93.         {
    94.             transform.position += animancer.Animator.deltaPosition;
    95.             transform.rotation *= animancer.Animator.deltaRotation;
    96.         }
    97.     }
    98. }
    99.  
    video clip of the behaviour

     
  23. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
  24. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    Is there a description of how to work with nested mixers somewhere? I found this link earlier -- https://kybernetik.com.au/animancer/docs/manual/blending/mixers/#nesting -- but it looks like that section was removed from the page (the page also comes up if I search for "nested", but the word isn't anywhere to be found).

    I've got a LinearMixer that mixes between an idle animation and a MixerTransition2D for walk animations. The mixers are stored in an asset, and I'm using the UnShared variant.

    I'm unclear on how to access the MixerTransition2D and control it. It sounds reasonable to give my component a reference to the MixerTransition2DAsset (again as an UnShared).

    I see that Example 03-03 talks about grabbing the mixer state in Awake, and then using that later. So, I tried doing that:

    Code (CSharp):
    1.  
    2.  
    3.     void Awake()
    4.     {
    5.         LocomotionLayer = animancer.Layers[0];
    6.         ActionLayer = animancer.Layers[1];
    7.         IdleLayer = animancer.Layers[2];
    8.  
    9.         walkState = (MixerState<Vector2>) animancer.States.GetOrCreate(walkMixer);
    10.     }
    Since I'm using layers, I also tried doing it this way:

    Code (CSharp):
    1.         walkState = (MixerState<Vector2>) LocomotionLayer.GetOrCreateState(walkMixer);
    2.  
    Then, in Update, I set the parameter:

    Code (CSharp):
    1. walkState.Parameter = new Vector2(0, (Time.time / 3) % 1);
    Alas, I don't see anything happening! I don't believe I could check this through the inspector -- the mixer is an asset, not stored directly in the component, so I can't see the current value anywhere.

    I did try using the non-UnShared types, but I'm getting the same results.

    How should I be handling this? I've gotten all of my states mixed up, I think...

    My understanding is that each mixer will have a relevant State -- so the linear one will have a LinearMixerState, and the 2D one will have a MixerState<Vector2>. I think I'm misunderstanding how to get the state for the 2D mixer that the linear mixer created. I hope that's what I'm looking for, at least :p

    edit -- after going back and looking at the LinearMixerState documentation, I realized you can just ask it for its child states. That's what I needed!

    It is a little weird to just grab a child state by index, since that would break if I rearranged the mixer. So, I guess my question is now just whether this is the best way to handle things.
     
    Last edited: Feb 7, 2023
  25. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    The Nesting section was moved to a sub-page. I'll track down that broken link.

    I agree that needing to grab the children by index and cast them isn't ideal. I'm considering implementing some sort of parameter system which mixers could bind to like the parameters in Animator Controllers, but I have quite a lot of features under consideration and I'm not sure what I'll end up working on next. That would also open up the potential rabbit hole of people wanting to set up conditional transitions and all the other stuff that Animator Controllers do.
     
  26. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    Got it. Is there any way that I could check if a certain state was produced by a certain transition? That would let me do something like...

    Code (CSharp):
    1. LinearMixerState topLevelState = animancer.GetOrCreate(topLevelMixer);
    2. MixerState<Vector2> walkState = topLevelState.FindState(walkMixer);
    (FindState would iterate over the children to see if any of them match; I could just do the iteration myself, of course).

    That would be very handy.
     
  27. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    I have just started studying this plugin which I have the gut feeling is what we need. Unfortunately, I know nothing about Unity animation api, plus I am working with code using Animation legacy code.

    At this moment I am trying to figure out the correct overlap between Animation component and the Animancer component.

    1) once I play a clip with animancer, will the animation states be ignored and so I have to use the animancer ones?
    2) I am not fully sure when AnimationComponent states are created, but since our goal is to move away from legacy clips, I have the impression that the Animation component, while can hold generic clips, is not creating the states for them. I am then wondering how can I check if an Animation holds a clip or not before using it with Animancer as explained in your tutorials.
     
    Last edited: Feb 8, 2023
  28. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    @chemicalcrux states don't keep anything that tells them how they were created, but if you assume the child mixers all have the same meaning for their parameters, you could try something like this:
    Code (CSharp):
    1. public static class MixerExtensions
    2. {
    3.     public static void SetChildParameters<TParameter>(this ManualMixerState parent, TParameter parameter)
    4.     {
    5.         var childCount = parent.ChildCount;
    6.         for (int i = 0; i < childCount; i++)
    7.             if (parent.GetChild(i) is MixerState<TParameter> mixer)
    8.                 mixer.Parameter = parameter;
    9.     }
    10. }
    @sebas77
    1. I'm not really sure what you're asking, but you can only use one animation system at a time. Whichever one happens to execute last will be the one you see the result of and the other will just be wasting performance. If you use Animancer, you won't be using Legacy Animation component or its states.
    2. The Legacy Animation system can only play Legacy clips and the Mecanim Animator system (and Animancer) can only play non-legacy clips. Animancer adds a context menu function to convert clips between Legacy and Generic.
    If you want to know whether something will work with Animancer, you can just download Animancer Lite and try it out.
     
    chemicalcrux likes this.
  29. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    Ah, that's a good idea! I forgot that all of these mixers had identical meanings for their parameters (standing movement, crouched movement, and eventually prone movement).

    One other thing -- I'm setting up events to play footstep sounds. In my old Mecanim-based system, I had this special component called AnimationEventHandler. I'd make every single event call a "HandleEvent" function with a string argument. Components would register to be notified when an event with a certain string was fired.

    This was very convenient for footsteps. A bunch of animations would trigger the "Footstep:Left" and "Footstep:Right" strings.

    So, instead, I'm putting Animancer events onto clip transitions. I need to set those callbacks to call the function that plays footstep noises.

    I had the thought of recursively walking the children of my top-level mixer, but not all of the states have this event on them, and I don't see a way to ask if an event with a specific name exists. I guess you could

    How would you suggest handling that? I guess I could just catch the exceptions you get from using an invalid name, but that sounds a bit silly...
     
    Last edited: Feb 9, 2023
  30. florianBrn

    florianBrn

    Joined:
    Jul 31, 2019
    Posts:
    53
    Don't know if that's what you are thinking about, but I'd love a system with Scriptable Objects that just contain a float/Vector2 parameter that you could use as a Mixer's parameter. You could then reference this parameter SO whenever needed, which would help for things that are currently pretty cumbersome to do (like needing to set a generic parameter without necessarily knowing what state is currently playing, setting parameters for multiple states/child states at the same time...).

    Edit: Now that I'm thinking about it, the same could be applied for events: for example, create a "Footstep Left" Event Scriptable Object, reference it everywhere you want to play the left footstep sound, then simply subscribe to this Event SO once at startup with a method that just plays the footstep sound. That would be so much easier than having to scan through the whole list of events, comparing their names, subscribing on play, then unsubscribing again when end event is called...
    Don't know how hard/time consuming that would be for you though! :D
     
    Last edited: Feb 9, 2023
  31. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    Kybernetik: Fair enough, I suspected my questions were not clear enough, so let me add all the details so you can get where I am coming from:

    We are working on a project that still uses legacy animation, we want to use Animancer as the fastest way to move away from them without overhauling the project.
    I was trying to not ask the artists to change the prefabs and then still use the AnimationComponent just as a AnimationClip holder and then change the code to play them with Animancer.
    However, my understanding now is I cannot do this and I have to ask to convert the AnimationComponents into AnimancerComponents. The main reason is that there isn't a way to know if a clip is present by name under these conditions. Probably we should even move away from the named clip lookup.
     
  32. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    @chemicalcrux
    ...Events.IndexOf("Event Name")
    will return -1 if no event is found, then you can set the event callback using that index.

    @florianBrn Directly using Scriptable Objects would only work if every character references a different one. If you instantiate multiple copies of a character, they'd all be referencing the same ScriptableObject so they'd all get the same parameter value.

    But Scriptable Objects may be part of the solution. I just wrote this post outlining my general ideas.

    @sebas77 You could theoretically keep the Legacy Animation components disabled and leave them there just to hold your clips, then at runtime get the clips from there and give them to Animancer. But that would probably take more effort than just removing them and has no real advantages.

    If you want a Legacy-like system, you can use a NamedAnimancerComponent which has an Animations array in the Inspector just like the Legacy Animation component and lets you play things by name.

    But in general, I agree that you should move away from name based lookups. A NamedAnimancerComponent might be good for this project if you don't want to spend too much time reworking things, but I wouldn't recommend that approach when starting a new project.
     
    florianBrn, sebas77 and chemicalcrux like this.
  33. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Methods like
    animancerComponent.States.GetOrCreate
    and
    TryGet
    can be used to get states that have already been created and create new ones.

    States won't be registered by name unless you use a NamedAnimancerComponent or explicitly set a state's Key to be its clip name.
     
  34. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    Oh, wow, I just glossed right over that one! Thanks, that works perfectly :D
     
  35. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    Hello again,

    is there a way to control the wrap mode of clips in Animancer? Programmatically something like this doesn't work

    Code (CSharp):
    1. if ((playState = _animancer.Layers[(int)_requestedAnimState.Layer].TryPlay(_requestedAnimState.AnimName)) != null)
    2.     playState.Clip.wrapMode = WrapMode.Loop;
    I am sure I am missing something here due to ignorance, but I am not sure how to loop animations with animancer.

    Edit: All of a sudden the animation started to work correctly without changing data. Confused. Is looping supposed to be the default behaviour for generic clips?
     
    Last edited: Feb 9, 2023
  36. jenkins22

    jenkins22

    Joined:
    Nov 15, 2014
    Posts:
    10
    If a character can either walk or run, no in-between, is it recommended to use 2 separate
    MixerTransition2D
    s (one for walking and one for running), or a single one that will have all 16 thresholds (8 running directions + 8 directions walking)?
     
  37. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    @sebas77 Wrap modes are only used by the Legacy animation system. Non-legacy animations use the isLooping property which is a bool so the only two modes are Loop and Clamp. It's a read-only function so it can't be changed at runtime and there isn't even a good way to set it in the Unity Editor.
    AnimancerEditorUtilities.SetLooping
    will do it, but for some reason you have to actually restart the editor for it to take effect. So if you can, it's generally easiest to just tick the Is Looping field in the Inspector.

    @jenkins22 Having them separate would mean you don't get Time Synchronization when transitioning between them. Having them all in one would be simplest, but if that doesn't give a good result you could also try having them separate and Nesting them inside a linear mixer so they still get synchronized.
     
  38. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    thanks, everything works fine now! Seems like we are going to use your plugin!!
     
  39. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    What are these extra checkboxes for? I'm looking at the ones immediately below the "Speed" dropdown in this image. They're on a MixerTransition2D.

    upload_2023-2-10_18-21-25.png

    I was trying to get a 2D mixer to blend between several different speeds of the same animation (since you can't ask it to extrapolate like a linear mixer would). It doesn't seem like the speed setting matters, though: all of the walk animations are kept in sync, no matter what the parameter's Y-value is.

    Unchecking the box resets the speed to 1x.

    I might just switch to a linear mixer, since I'm not actually using the X-axis at the moment, though.

    edit -- I swapped the ClipTransitions out for plain animation clips, and the speed works as expected there. I then noticed that I had checked the Speed checkbox on the transitions, which was overriding the mixer!

    So, I don't have a problem anymore; I'm just curious about those checkboxes.
     
  40. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Since it affects the speed (and lines up with the speed header) it will have the same meaning as explained in the Time Fields section. It's built into the method I use to draw the field with the "x" suffix, but it's optional and should be disabled for the Speed fields in mixer transitions.

    I just had a look through all Unity versions I've got (2019.4, 2020.3, 2021.3, 2022.2) and it's not there in any of them. Which Unity version are you using and do you have anything else that might interfere with GUI stuff (Odin Inspector in particular loves to break stuff)?
     
  41. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    I'm using Unity 2022.2.4f1 on macOS. I don't think I have anything installed that would affect the inspector -- but here's my manifest.json

    Code (CSharp):
    1. {
    2.   "dependencies": {
    3.     "ca.andydbc.unity-texture-packer": "https://github.com/andydbc/unity-texture-packer.git",
    4.     "com.coffee.softmask-for-ugui": "1.0.2",
    5.     "com.github-glitchenzo.nugetforunity": "https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity",
    6.     "com.github.superunitybuild.buildactions": "2.2.0",
    7.     "com.github.superunitybuild.buildtool": "5.0.4",
    8.     "com.unity.ai.navigation": "1.1.1",
    9.     "com.unity.cinemachine": "2.9.4",
    10.     "com.unity.collections": "1.2.4",
    11.     "com.unity.feature.development": "1.0.1",
    12.     "com.unity.ide.rider": "3.0.18",
    13.     "com.unity.ide.visualstudio": "2.0.17",
    14.     "com.unity.ide.vscode": "1.2.5",
    15.     "com.unity.inputsystem": "1.4.4",
    16.     "com.unity.localization": "1.3.2",
    17.     "com.unity.memoryprofiler": "1.0.0",
    18.     "com.unity.recorder": "4.0.0",
    19.     "com.unity.render-pipelines.high-definition": "14.0.5",
    20.     "com.unity.services.cloud-diagnostics": "1.0.3",
    21.     "com.unity.terrain-tools": "5.0.2",
    22.     "com.unity.test-framework": "1.1.33",
    23.     "com.unity.textmeshpro": "3.0.6",
    24.     "com.unity.timeline": "1.7.2",
    25.     "com.unity.ugui": "1.0.0",
    26.     "com.unity.vectorgraphics": "2.0.0-preview.20",
    27.     "com.unity.visualscripting": "1.8.0",
    28.     "com.unity.modules.ai": "1.0.0",
    29.     "com.unity.modules.androidjni": "1.0.0",
    30.     "com.unity.modules.animation": "1.0.0",
    31.     "com.unity.modules.assetbundle": "1.0.0",
    32.     "com.unity.modules.audio": "1.0.0",
    33.     "com.unity.modules.cloth": "1.0.0",
    34.     "com.unity.modules.director": "1.0.0",
    35.     "com.unity.modules.imageconversion": "1.0.0",
    36.     "com.unity.modules.imgui": "1.0.0",
    37.     "com.unity.modules.jsonserialize": "1.0.0",
    38.     "com.unity.modules.particlesystem": "1.0.0",
    39.     "com.unity.modules.physics": "1.0.0",
    40.     "com.unity.modules.physics2d": "1.0.0",
    41.     "com.unity.modules.screencapture": "1.0.0",
    42.     "com.unity.modules.terrain": "1.0.0",
    43.     "com.unity.modules.terrainphysics": "1.0.0",
    44.     "com.unity.modules.tilemap": "1.0.0",
    45.     "com.unity.modules.ui": "1.0.0",
    46.     "com.unity.modules.uielements": "1.0.0",
    47.     "com.unity.modules.umbra": "1.0.0",
    48.     "com.unity.modules.unityanalytics": "1.0.0",
    49.     "com.unity.modules.unitywebrequest": "1.0.0",
    50.     "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
    51.     "com.unity.modules.unitywebrequestaudio": "1.0.0",
    52.     "com.unity.modules.unitywebrequesttexture": "1.0.0",
    53.     "com.unity.modules.unitywebrequestwww": "1.0.0",
    54.     "com.unity.modules.vehicles": "1.0.0",
    55.     "com.unity.modules.video": "1.0.0",
    56.     "com.unity.modules.vr": "1.0.0",
    57.     "com.unity.modules.wind": "1.0.0",
    58.     "com.unity.modules.xr": "1.0.0"
    59.   },
    60.   "scopedRegistries": [
    61.     {
    62.       "name": "package.openupm.com",
    63.       "url": "https://package.openupm.com",
    64.       "scopes": [
    65.         "com.coffee.softmask-for-ugui",
    66.         "com.github.superunitybuild.buildactions",
    67.         "com.github.superunitybuild.buildtool",
    68.         "com.openupm"
    69.       ]
    70.     }
    71.   ]
    72. }
    73.  
     
  42. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Yeah, nothing in there looks like it would affect this.

    The method that draws it is
    DoOptionalAfterGUI
    in UnitsAttribute.cs, but I don't see any way it could be getting there for that field. Would you mind putting a log in it and posting the stack trace?
     
  43. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    Here's a trace:

    Code (CSharp):
    1. Animancer.Units.UnitsAttribute:DoOptionalAfterGUI (bool,UnityEngine.Rect,single&,single,bool,single) (at Assets/Plugins/Animancer/Internal/Editor/Attributes/Units/UnitsAttribute.cs:312)
    2. Animancer.Units.UnitsAttribute:DoFieldGUI (UnityEngine.Rect,UnityEngine.GUIContent,single&) (at Assets/Plugins/Animancer/Internal/Editor/Attributes/Units/UnitsAttribute.cs:229)
    3. Animancer.Units.UnitsAttribute:OnGUI (UnityEngine.Rect,UnityEditor.SerializedProperty,UnityEngine.GUIContent) (at Assets/Plugins/Animancer/Internal/Editor/Attributes/Units/UnitsAttribute.cs:141)
    4. Animancer.Editor.SelfDrawerDrawer:OnGUI (UnityEngine.Rect,UnityEditor.SerializedProperty,UnityEngine.GUIContent) (at Assets/Plugins/Animancer/Internal/Editor/Attributes/SelfDrawerAttribute.cs:70)
    5. UnityEditor.EditorGUI:PropertyField (UnityEngine.Rect,UnityEditor.SerializedProperty,UnityEngine.GUIContent,bool)
    6. Animancer.Editor.TransitionDrawer:DoChildPropertyGUI (UnityEngine.Rect&,UnityEditor.SerializedProperty,UnityEditor.SerializedProperty,UnityEngine.GUIContent) (at Assets/Plugins/Animancer/Internal/Editor/GUI/TransitionDrawer.cs:478)
    7. Animancer.ManualMixerTransition/Drawer:DoChildPropertyGUI (UnityEngine.Rect&,UnityEditor.SerializedProperty,UnityEditor.SerializedProperty,UnityEngine.GUIContent) (at Assets/Plugins/Animancer/Utilities/Transitions/ManualMixerTransition.cs:284)
    8. Animancer.MixerTransitionDrawer:DoChildPropertyGUI (UnityEngine.Rect&,UnityEditor.SerializedProperty,UnityEditor.SerializedProperty,UnityEngine.GUIContent) (at Assets/Plugins/Animancer/Utilities/Transitions/MixerTransition.cs:188)
    9. Animancer.Editor.TransitionDrawer:DoChildPropertiesGUI (UnityEngine.Rect,UnityEditor.SerializedProperty,UnityEditor.SerializedProperty,bool) (at Assets/Plugins/Animancer/Internal/Editor/GUI/TransitionDrawer.cs:437)
    10. Animancer.Editor.TransitionDrawer:DoPropertyGUI (UnityEngine.Rect,UnityEditor.SerializedProperty,UnityEngine.GUIContent,bool) (at Assets/Plugins/Animancer/Internal/Editor/GUI/TransitionDrawer.cs:147)
    11. Animancer.Editor.TransitionDrawer:OnGUI (UnityEngine.Rect,UnityEditor.SerializedProperty,UnityEngine.GUIContent) (at Assets/Plugins/Animancer/Internal/Editor/GUI/TransitionDrawer.cs:125)
    12. Animancer.ManualMixerTransition/Drawer:OnGUI (UnityEngine.Rect,UnityEditor.SerializedProperty,UnityEngine.GUIContent) (at Assets/Plugins/Animancer/Utilities/Transitions/ManualMixerTransition.cs:226)
    13. UnityEditor.Editor:OnInspectorGUI ()
    14. Animancer.Editor.ScriptableObjectEditor:OnInspectorGUI () (at Assets/Plugins/Animancer/Internal/Editor/GUI/ScriptableObjectEditor.cs:31)
    15. UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&) (at /Users/bokken/build/output/unity/unity/Modules/IMGUI/GUIUtility.cs:189)
    16.  
    I don't have to do anything special to make it behave that way, and it works for LinearMixerTransitions as well.

    It looks like IsOptional is erroneously getting set to true...but only when one of the speeds isn't 1!
     
  44. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Well that was annoying. The fact that your screenshot makes it look like the toggle is in the Speeds column led me to waste a bunch of time looking in the wrong place, but it turns out I just wasn't seeing it because the toggle is a fixed distance from the start of the field and my Inspector was thinner than yours so the toggle was just off the end of the screen for me. So they were always there but no one ever saw them before because I only just added Two Line mode in Animancer v7.4 and one line mode limits the width of that field.

    It also only happens while you have speed values other than 1 because otherwise it actually sets the speeds array size to 0 and just fakes the GUI field until you give it a value.

    Easy fix though, I've posted the fix here.
     
    chemicalcrux likes this.
  45. brokenspiralstudios

    brokenspiralstudios

    Joined:
    Oct 20, 2021
    Posts:
    45
    Trying to get a CanExitState working for a MeleeState. The goal is if (_stateComplete) it means that the attack animation has ended and then the character can return to default state. OR if the dist > attackRange && !IsAttacking, then can exit. Unfortunately, in the snippet below only the distance part works but not the stateComplete part. If only stateComplete is true, then the Debug.Log("Can Exit") does not fire and the ForceSetDefaultState() does not get triggered
    Code (CSharp):
    1. public override bool CanExitState {
    2.             get {
    3.                 float dist = Vector3.Distance(transform.position, _target.position);
    4.                 if (_stateComplete || dist > _build.AttackRange && !_npc.Parameters.IsAttacking) {
    5.                     Debug.Log("Can Exit");
    6.                     _npc.StateMachine.ForceSetDefaultState();
    7.                     return true;
    8.                 }
    9.                 return false;
    10.             }
    11.         }
    However, this snippet DOES work. How do I get the equivalent in CanExitState? Thanks!

    Code (CSharp):
    1. private void Update() {
    2.      CheckExitState();
    3. }
    4.  
    5. private void CheckExitState() {
    6.             float dist = Vector3.Distance(transform.position, _target.position);
    7.             if (_stateComplete || dist > _build.AttackRange && !_npc.Parameters.IsAttacking) {
    8.                 _npc.StateMachine.ForceSetDefaultState();
    9.             }
    10.         }
     
  46. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    CanExitState is only checked when something actually tries to change the state. So to use it you would need something to try to set the default state (not force) every frame.

    It also doesn't make sense to call ForceSetDefaultState within CanExitState because something is already trying to change the state.
     
  47. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    Great, thanks! That fixed it up.
     
  48. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    I'm having problems with Unshared transitions and their events. I'm using this page as a reference.

    I have multiple characters who all reference the same LinearMixerTransitionAsset. That transition has two MixerTransition2DAssets as children, which, in turn, reference ClipTransitions. Those ClipTransitions have events I am interested in.

    Since I have multiple characters using the same asset, I gave them an Unshared field for the mixer.

    I was originally setting up their locomotion and events like this:

    Code (CSharp):
    1. StanceState = LocomotionLayer.GetOrCreateState(stanceMixer) as LinearMixerState;
    2. WalkState = StanceState.GetChild(0) as MixerState<Vector2>;
    3. CrouchState = StanceState.GetChild(1) as MixerState<Vector2>;
    4.  
    5. StanceState.SetCallbackRecursive("Left Footstep", Footstep);
    ...where SetCallbackRecursive is this extension method:

    Code (CSharp):
    1.     public static void SetCallbackRecursive(this AnimancerState state, string key, Action action)
    2.     {
    3.         if (state.Events.IndexOf(key) != -1)
    4.         {
    5.             state.Events.SetCallback(key, action);
    6.         }
    7.  
    8.         for (int i = 0; i < state.ChildCount; ++i)
    9.         {
    10.             state.GetChild(i).SetCallbackRecursive(key, action);
    11.         }
    12.     }
    Footstep is a member function on the character that plays a sound.

    However, if I spawn two characters, then destroy the second one, the first one's footsteps start causing errors: the events are looking for the now-destroyed character component! It sounds like everyone is looking at the same list of events.

    I realized that the Unshared classes store their own State and Events, so I tried adjusting it a bit:

    Code (CSharp):
    1. LocomotionLayer.GetOrCreateState(stanceMixer);
    2. StanceState = stanceMixer.State;
    3. WalkState = StanceState.GetChild(0) as MixerState<Vector2>;
    4. CrouchState = StanceState.GetChild(1) as MixerState<Vector2>;
    5.  
    6. StanceSet.SetCallbackRecursive("Left Footstep", Footstep);
    This behaves the same. I tried not storing the State in a variable, just in case that mattered somehow, but that didn't do anything.

    I get warnings about duplicate events, but that's because every callback uses the same Footstep function. I don't get any extra warnings when spawning a second character.

    I also get warnings about modifying a sequence that was created by a Transition (including when the first instance of the character spawns). I figured this was fine -- I add the events once during the character's Start() method. Perhaps this is where the problem is coming from, but I don't understand how else I could set up events at runtime!

    I see that the Unshared class has an Events field. Adding a callback for the end event behaves as expected: each character is separate. However, that doesn't help me; I need to set callbacks on the children of that top-level mixer.

    I also tried instantiating the transition asset and using the resulting copy, but that gives the same behavior.

    What can I do here? The characters are sharing something, but I'm not sure what the culprit is. Perhaps Unshared doesn't give you unique child states?

    edit -- I had a look at the hash codes of various bits, like the (grand-)child states and their Events. From the example above, I see that
    WalkState.Events
    has the same hashcode for every character I spawn (except the first one; I'm guessing the code is just changing after adding the events or something).

    I found that, if I recursively walk the child states and make a copy of the Events, everything starts working:

    Code (CSharp):
    1.     public static void MakeEventsUnique(this AnimancerState state)
    2.     {
    3.         state.Events = new AnimancerEvent.Sequence(state.Events);
    4.         for (int i = 0; i < state.ChildCount; ++i)
    5.         {
    6.             state.GetChild(i).SetShouldNotModifyReasonRecursive(reason);
    7.         }
    8.     }
    Am I breaking any rules here? The solution makes sense to me, at least :p
     
    Last edited: Feb 11, 2023
  49. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Nesting makes everything way more complicated. :(

    When you instantiate a Component/GameObject it makes copies of the whole hierarchy, but ScriptableObjects don't have a hierarchy so instantiating a copy of the parent Transition Asset will give you a new Transition Asset which still references the same un-copied objects for its child animations.

    The UnShared system has a similar issue. It doesn't know anything about the children so it can only affect the events on the parent.

    So either way your child states are all being created from the same Transition Assets and given the same Event Sequence, then when they modify it they all share the same modifications so every character will trigger events for every character.

    Your solution is reasonable, but might not work depending on how you play the mixer.
    • If you play the parent state directly (i.e. call
      animancer.Play(StanceState, stanceMixer.FadeDuration)
      ) then it should work. The system that clears events automatically whenever you play something else doesn't actually clear the events from mixer children, so the new event sequences you made on startup should stay assigned to the children. This actually seems like a bug, because it doesn't seem right for child states to not clear their events like everything else.
    • Ideally you would be able to play the transition properly (i.e. call
      animancer.Play(stanceMixer)
      ), but every time you do that the parent and child transitions will all assign their events to the states again, which would replace your new event sequences with the original shared ones.
    I actually came up with an idea over in this thread yesterday which would avoid the need for your MakeEventsUnique method by having the Events property do it automatically (and adding a SharedEvents property if you don't want that, similar to how Renderer.material and Renderer.sharedMaterial work). Unfortunately, that still runs into the issue I just described where playing a mixer transition will reset the child states to the original shared event sequences.

    It's very clear that nested mixers need some serious changes to be easier to use, but I haven't yet figured out exactly how to achieve that.
     
    chemicalcrux likes this.
  50. jenkins22

    jenkins22

    Joined:
    Nov 15, 2014
    Posts:
    10
    Does a
    DirectionalMixerState
    still use n² computation for blending if the parameter is exactly one of the thresholds? For example if there's an Idle animation at (0,0), and you set the param to (0,0), will it still perform the full blending algorithm?