Search Unity

Animancer - Less Animator Controller, More Animator Control

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

  1. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Hey everyone, Animancer v7.1 is now up on the Asset Store and itch.io. It has a couple of improvements to Mixer Transition Drawers, but mostly consists of minor bug fixes (including the .NET Framework version issue @itadakiass found).
     
    itadakiass, hopeful and ratking like this.
  2. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    350
    Did something change with OnEnd in 7.1? Suddenly the code I call gets called permanently instead of once...
     
  3. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    The first dot point in the Fixes section relates to End Events, but it doesn't seem like it should be the cause of what you're describing and all my Unit Tests still pass.

    Can you send a minimal reproduction project to animancer@kybernetik.com.au so I can take a look at it?
     
  4. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    350
    I currently have no time to do a test case (sorry!), but I seem to have fixed it. In 7.0 and before I used animState.IsPlaying = false; inside the OnEnd() event to stop it from getting called more than once. I replaced this line with animState.Stop(); now and it works as before.
     
  5. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    I've fixed that issue and re-uploaded v7.1. Didn't increase the version number since it shouldn't affect most people.
     
    ratking likes this.
  6. Flavelius

    Flavelius

    Joined:
    Jul 8, 2012
    Posts:
    945
    Is there a way to cheaply check if a layer has a state registered by key or better yet retrieve it (or null) without implicitly creating it?
    I'm looking up transitions/clips from a list and want to avoid that cost when accessing the state to update it incase it already exists.
    So far i only see the indexer and GetOrCreate.
     
  7. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    There's only one dictionary so
    animancer.States.TryGet
    then check if its on the correct layer.
     
  8. Flavelius

    Flavelius

    Joined:
    Jul 8, 2012
    Posts:
    945
    Ok, i guess my intention may just be wrong. Now that i think about it, it's probably better to have only overriding clips/transitions on the upper body layer and just fade it in/out accordingly, and not try to sync them otherwise.
     
  9. Flavelius

    Flavelius

    Joined:
    Jul 8, 2012
    Posts:
    945
    I still have a conceptual hurdle. This means, if the character does a full-body animation, i play it on the base layer (upper body with 0 weight), and when he begins to move while still performing that animation, i have to stop it, play it on the second masked layer (weight 1), setting the time to the same progress value and start playing the movement animation on the base layer. When the character stops again while still performing the action, i have to stop the base layer and the upperbody layer, play/continue the action animation on the base layer again also transferring its active time value. This seemingly wouldn't allow me to fade the base layer's animations then (moving lower to fullbody action), or the upper body (as played in the base layer with 0 weight for upper body layer) would skip or fade too. The documentation shows how to enter the upper body action animation, but fading back to fullbody is my problem.
    Do i miss a crucial step somewhere or is there an alternative way to achieve what i intend (smoothly transition in and out of fullbody-action and lower/upper movement+action separation)?
     
    Last edited: Aug 15, 2021
  10. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    The Layers example now supports swapping between moving and idle while performing the action. Basically, it uses FadeMode.FromStart to create a new state if the existing one has non-zero weight so that the new state can fade in on the desired layer while the old state fades out on the old layer. It seems a bit hacky, but I haven't been able to come up with a better solution.
     
    Flavelius likes this.
  11. cajphrase

    cajphrase

    Joined:
    Apr 24, 2021
    Posts:
    12
    I've got my character set up with a mixer to blend between walk/idle/run animations, and I want to be able to change these animations at runtime when the character picks up an item. Is there a way to substitute the clips inside the mixer (like what would be done with an Animator Override Controller in mechanim)?

    Also to the discussion on swapping between moving and idle - I solved this by always playing the action on the upper body layer and enabling/disabling the upper body layer mask when the character moves. There is no blending but my animations don't require it as the lower body movements in my action are subtle.
     
  12. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    mixer.GetChild(x).Clip = ...
    would do it.

    Or if you're replacing all the animations, just make a separate mixer for each set.
     
  13. cajphrase

    cajphrase

    Joined:
    Apr 24, 2021
    Posts:
    12
    Thanks, that did the trick!
    For anyone else with the same issue - I was using a LinearMixerTransition rather than a LinearMixerState so I actually had to call
    mixer.State.GetChild(x).Clip
    to find the clips.
     
  14. b1gry4n

    b1gry4n

    Joined:
    Sep 11, 2013
    Posts:
    146
    Ive been looking around at the animancer website and inside animancer source code but cant seem to find an answer to a question i have, so here i am.

    I have animations set up with events. The events are set up inside the animation file itself through unitys animation import settings. AnimationA has events. AnimationB doesnt. I am interupting AnimationA with AnimationB, however the events from AnimationA are still triggering. Ive tried
    animancer.Layers[layer].CurrentState.Events.Clear();
    but it doesnt seem like its working. Im wondering if this is only for
    AnimancerEvent
    ? if this is true, how/where exactly are the unity events that are set up through the import settings being triggered? any way I can stop them from triggering when interupting and blending to a new animation or should i be manually setting up
    AnimancerEvent
    instead of in the animation import settings?

    EDIT*
    the more i think about it the more it seems like these events have nothing to do with animancer and they are simply unity events. i think i will go ahead and try implementing these events as animancer events instead

    EDIT2*
    replaced unity events with
    AnimancerEvent
    . using animancer events instead solved my issue. now animations that are interupted also have their events interupted.
     
    Last edited: Aug 17, 2021
  15. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Yeah, Animation Events are entirely separate from Animancer Events.

    Animation Events keep getting triggered while the animation has any weight as it fades out.

    Animancer Events are Cleared Automatically whenever you play something.
     
  16. kostyaunity

    kostyaunity

    Joined:
    Feb 12, 2021
    Posts:
    5
    I'm using Version 7.1 but when in
    Update()
    I call:

    Code (CSharp):
    1. animancerComponent.Play(walk);
    I get this error:


    ArgumentOutOfRangeException: AnimancerLayer.GetOrCreateWeightlessState has created 5 states for a single clip. This is most likely a result of calling the method repeatedly on consecutive frames. This can be avoided by using a different FadeMode or calling AnimancerLayer.SetMaxStateDepth to increase the threshold for this warning.


    Is this the same issue or did I do something wrong? Before 7.1 if I remember correctly I could call
    Play()
    multiple times with the same clip and it wouldn't restart it.
     
  17. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    If walk is a transition with its Start Time field enabled and it's already playing, it will create a new copy of the state to fade in while the old one fades out. So if you don't want to restart it, just untick the Start Time.
     
  18. kostyaunity

    kostyaunity

    Joined:
    Feb 12, 2021
    Posts:
    5
    Ah, thanks! I noticed that Start Time is checked by default now. Unchecking it solved my issue. Btw, thank you very much for C# in Unity! It helped me a lot in learning C#.
     
  19. Flavelius

    Flavelius

    Joined:
    Jul 8, 2012
    Posts:
    945
    Good to know, i had the same problem and for now thought i had to find the error somewhere in my code.
     
  20. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Do you think it's better to have it checked by default or should I revert it to unchecked? I find myself needing it checked more often, but I suppose it is more likely to cause problems if left on unintentionally than if left off unintentionally.
     
    Flavelius likes this.
  21. theghostronaut

    theghostronaut

    Joined:
    Jul 27, 2018
    Posts:
    41
    Hi, I was wondering if it is possible to ease the pausing of an animation? I guess one way would be lerping the speed, but is there a built-in function that I haven't found yet? I'm pausing the animation if a specific button is *not* pressed, and it would be nice if it wouldn't pause immediately but with some easing.
     
  22. kostyaunity

    kostyaunity

    Joined:
    Feb 12, 2021
    Posts:
    5
    Well, it caught me by surprise and I couldn't find the cause of the issue so I looked for help on the forum. When default was unchecked and I tried to re-start animation and it didn't work it was clear that by default the animation is not restarted and I just needed to read the docs. Also first animations I started using were running and idle so I guess it's a better default for beginners.
     
  23. kostyaunity

    kostyaunity

    Joined:
    Feb 12, 2021
    Posts:
    5
    Another question, it seems that transitions are not affected by Time.timeScale. Is there another way to see them in slow motion? The animations themselves are played in slow motion, however. It's just transitioning between them that happens at the same speed.

    UPD: Fixed by removing
    UNITY_2021_1_OR_NEWER
    in
    AnimancerPlayable.UpdateAll
    . (I was using 2020.3)
     
    Last edited: Aug 19, 2021
  24. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    @theghostronaut There's nothing inbuilt, but you could make a class that implements IUpdatable to lerp the speed over time like the MixerParameterTween script.

    @kostyaunity Which version of Unity are you using? There's a bug in 2021 that causes it to not take time scale into account so I had to manually multiply it, but if the bug is also in an earlier version then my fix won't be applied. It's in AnimancerPlayable.UpdateAll.
     
  25. JakartaIlI

    JakartaIlI

    Joined:
    Feb 6, 2015
    Posts:
    30
    looks like new transition preview window not work with odin

    upd:
    yep,
    [DrawWithUnity] fixed it
     
    Last edited: Aug 19, 2021
  26. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
  27. kostyaunity

    kostyaunity

    Joined:
    Feb 12, 2021
    Posts:
    5
    I'm using Unity 2020.3.14f1 and removing the
    if UNITY_2021_1_OR_NEWER
    condition has fixed my issue. Thanks for the help!
     
    Kybernetik likes this.
  28. JohnTomorrow

    JohnTomorrow

    Joined:
    Apr 19, 2013
    Posts:
    135
    With AnimancerLayer.CreateState<T> removed, what is the proper way to add a state? In the older version I was adding all states prior to playing them at initialization to work around a Unity bug with AnimationRigging resetting values when the playable graph changes. I do not see a way to add LinearMixerState states anymore.

    Edit: looked up the old source, looks like I should use AddChild()
     
  29. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    You can just make a
    new LinearMixerState()
    then use
    animancer.Layers[x].AddChild
    to attach it to a specific layer.
     
  30. JohnTomorrow

    JohnTomorrow

    Joined:
    Apr 19, 2013
    Posts:
    135
    Wondering if anyone has added async/await support to Animancer? I didn't see anything about so figured I would give it a shot and it was surprising easy to implement.

    I added this script to the source code.

    Code (CSharp):
    1. using System;
    2. using System.Runtime.CompilerServices;
    3.  
    4. namespace Animancer {
    5.     partial class AnimancerState : INotifyCompletion {
    6.         private Action _continuation;
    7.  
    8.         public bool IsCompleted {
    9.             get {
    10.                 return !IsPlaying && TargetWeight == 0;
    11.             }
    12.         }
    13.  
    14.         public void GetResult() { }
    15.  
    16.         public void OnCompleted(Action continuation) {
    17.             _continuation = continuation;
    18.         }
    19.  
    20.         private void OnRequestCompleted() {
    21.             Events.OnEnd -= OnRequestCompleted;
    22.             _continuation();
    23.         }
    24.  
    25.         public AnimancerState GetAwaiter() {
    26.             Events.OnEnd += OnRequestCompleted;
    27.             return this;
    28.         }
    29.     }
    30. }
    and now I can write async code, here's an example:

    Code (CSharp):
    1.         async Task PlayLandedAnimationAsync() {
    2.             await Animancer.Layers[HumanoidEntity.LowerBodyLayer].Play(landingAdditiveAnimation);
    3.             Animancer.Layers[HumanoidEntity.LowerBodyLayer].StartFade(targetWeight: 0f, fadeDuration: 0.25f);
    4.         }
    5.  
    6.         void Landed() {
    7.             PlayLandedAnimationAsync();
    8.         }
    I will experiment with using UniTask next as it works much nicer in Unity (no allocations, tied into runtime much better). This might be nice to add to the asset :)
     
  31. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    What happens if you play something else during that animation? Would it cancel the fade out properly?

    And are you sure it doesn't allocate any garbage? I'm pretty sure
    += OnRequestCompleted;
    will always allocate a new delegate and
    -=
    might too.

    Also, why not just use the OnEnd event directly instead of adding the _continuation field? I'm not familiar with async stuff so there could be something I'm missing, but if you're accessing the OnEnd event anyway it seems like you might as well just use it directly.
     
  32. JohnTomorrow

    JohnTomorrow

    Joined:
    Apr 19, 2013
    Posts:
    135
    Yeah, the above code was done really quickly without much testing, it does have some problems, but I think they are solvable. Probably more changes to Animancer would be required to support async/await in a better way.

    I was using OnEnd as a way to simply know when to call
    _continuation
    once an animation completes. This is how the async/await generated code knows how to continue execution after being blocked. I would like to avoid using this but haven't dug through enough of the code to know where to modify. All I need to know is a specific place in Animancer to call the
    OnRequestComplete
    and I will no longer need to deal with the Action event.
     
  33. JohnTomorrow

    JohnTomorrow

    Joined:
    Apr 19, 2013
    Posts:
    135
    I think I see what you are saying. This might actually work.

    Code (CSharp):
    1. using System;
    2. using System.Runtime.CompilerServices;
    3.  
    4. namespace Animancer {
    5.     partial class AnimancerState : INotifyCompletion {
    6.  
    7.         public bool IsCompleted {
    8.             get {
    9.                 return !IsActive;
    10.             }
    11.         }
    12.  
    13.         public void GetResult() { }
    14.  
    15.         public void OnCompleted(Action continuation) {
    16.             Events.OnEnd = continuation;
    17.         }
    18.  
    19.         public AnimancerState GetAwaiter() {
    20.             return this;
    21.         }
    22.     }
    23. }
     
  34. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    I gave that a go and it seemed to mostly work, but according to the Profiler the following script is allocating 480 bytes every frame:
    Code (CSharp):
    1.     public AnimancerComponent animancer;
    2.     public AnimationClip action;
    3.  
    4.     async void Update()
    5.     {
    6.         await PlayAnimationAsync();
    7.     }
    8.  
    9.     async UniTask PlayAnimationAsync()
    10.     {
    11.         await animancer.Play(action);
    12.     }
    I'm also unsure how it's supposed to know if the awaited task has been cancelled. If you play something else the old End Event will be cleared (unless
    AnimancerState.AutomaticallyClearEvents
    is set to false), but it isn't doing anything to inform the task that the
    continuation
    delegate will never get called. The only possibility I can think of is that it simply allows the delegate to get garbage collected, but in that case it's obviously impossible for the system to have no allocations.
     
  35. JohnTomorrow

    JohnTomorrow

    Joined:
    Apr 19, 2013
    Posts:
    135
    C#'s implementation of async/await will allocate a mini state machine so that is probably what you are seeing. I believe if tried with UniTask this will be resolved as it supports async/await without allocating. It is also has cancelation support, but I have not dug deep into it yet.
     
  36. Flavelius

    Flavelius

    Joined:
    Jul 8, 2012
    Posts:
    945
    In this case, what is the states' key for duplicates if i wanted to access/check for them from somewhere else?, i thought key's had to be unique.
     
  37. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Each duplicate uses the previous state as its key so they form a chain. It's implemented in the AnimancerLayer.GetOrCreateWeightlessState method.
     
    Flavelius likes this.
  38. renman3000

    renman3000

    Joined:
    Nov 7, 2011
    Posts:
    6,697
    @Kybernetik
    [SOLVED]
    I had a particular condition misfiring.
    Thank you


    -----

    Hi there!
    Occasionally my avatar goes into the T-Pose, and I am unsure why ( I must have some leak somewhere). What I am wondering is, can I notify myself when this occurs, some way?

    Thank you
     
    Last edited: Aug 25, 2021
  39. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,824
    G'day! Just updated to 7.1. I'm running into this issue with events,

    NullReferenceException: Object reference not set to an instance of an object
    Animancer.AnimancerState+EventDispatcher.NextEvent (System.Int32 playDirectionInt) (at Assets/Plugins/Animancer/Internal/Core/AnimancerState.EventDispatcher.cs:643)
    Animancer.AnimancerState+EventDispatcher.CheckGeneralEvents (System.Single currentTime) (at Assets/Plugins/Animancer/Internal/Core/AnimancerState.EventDispatcher.cs:530)
    Animancer.AnimancerState+EventDispatcher.Animancer.IUpdatable.Update () (at Assets/Plugins/Animancer/Internal/Core/AnimancerState.EventDispatcher.cs:264)
    Animancer.AnimancerPlayable.UpdateAll (Animancer.Key+KeyedList`1[T] updatables, System.Single deltaTime) (at Assets/Plugins/Animancer/Internal/Core/AnimancerPlayable.cs:1194)
    UnityEngine.Debug:LogException(Exception, Object)
    Animancer.AnimancerPlayable:UpdateAll(KeyedList`1, Single) (at Assets/Plugins/Animancer/Internal/Core/AnimancerPlayable.cs:1199)
    Animancer.PostUpdate:prepareFrame(Playable, FrameData) (at Assets/Plugins/Animancer/Internal/Core/AnimancerPlayable.cs:1293)

    I'm not sure what's causing it but seems to be related to this -
    Code (CSharp):
    1.     public void Test()
    2.     {
    3.         anim.Play(clips.testClip, 0.15f).Events.Add(new AnimancerEvent(0.5f, () => TestEvent(0.25f)));
    4.     }
    5.  
    6.     public void TestEvent(float blendTime)
    7.     {
    8.         anim.Play(clips.eventClip, blendTime);
    9.     }
    10.    
    All of the clips exist, do you know what might cause that?
    Thanks!
    Pete
     
  40. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
  41. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,824
    That seems better, thanks!
    Hey one other thing I've noticed, the animancer component doesn't seem to update very well in the editor for me while playing the game. It only seems to update once if I mode the mouse over the component. Is there a way to force that to update, it would be handy for debugging stuff.
     
  42. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Right Click any state or layer in the Inspector and enable Display Options -> Repaint Constantly. It should be enabled by default though.
     
  43. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,824
    Hey thanks, that's on but it doesn't seem to make a difference. o_O
    Some cool options I didn't know about in there though. :)
     
    Last edited: Aug 28, 2021
  44. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Try it in an empty project, maybe something else is interfering with it.
     
  45. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,824
    Yep, cool I’ll do that and get back to ya!
     
  46. theghostronaut

    theghostronaut

    Joined:
    Jul 27, 2018
    Posts:
    41
    When I have triggered an animation using state.Play(), do I need to stop the animation at some point? I noticed the Normalized Time counter in the inspector keeps running after the animation has finished.
     
  47. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    If you have a non-looping animation and you want to leave it on its last frame for a while you could call animancerComponent.Playable.PauseGraph() to save a bit of performance, but otherwise there's no real harm in just letting it continue.
     
  48. theghostronaut

    theghostronaut

    Joined:
    Jul 27, 2018
    Posts:
    41
    Thank you!
     
  49. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,824
    Hey @Kybernetik, is there a way to exclude a specific object from updating with Animancer? I have a few objects in the rig that I use purely for secondary animation (applied after everything), and they seem to be affected sometimes when I play a new clip.
     
  50. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Not that I know of. The Playables API always controls every field that any clip in the graph affects regardless of what's playing and I've never seen anything that would let you exclude certain objects.