Search Unity

Animancer - Less Animator Controller, More Animator Control

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

  1. renman3000

    renman3000

    Joined:
    Nov 7, 2011
    Posts:
    6,697
    @Kybernetik
    Hi I am just reposting this as you missed it. Thank you.

     
  2. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
  3. S4UC1SS0N

    S4UC1SS0N

    Joined:
    May 29, 2014
    Posts:
    49
    Hello !

    I'm trying to use Animancer on top of an existing Animation Controller but even after reading the documentation in deep on layers and events, i can't find a way to transition from animancer to animation controller when an animation reach its end (or more precisly, start its transition "out").

    I know how to fade out an animancer layer to get back to the animation controller :

    Code (CSharp):
    1. ActionLayer.StartFade(0, .25f);
    But can't find a way to trigger this StartFade call when the "end transition" of an animation start (I'm using ClipTransition).
    I tried this for example :

    Code (CSharp):
    1. myClipTransition.Events.OnEnd += ActionLayerFadeOut;
    or this :

    Code (CSharp):
    1.  
    2. myClipTransition.Events.EndEvent = new AnimancerEvent(myClipTransition.Events.NormalizedEndTime, ActionLayerFadeOut);
    As the documentation says, it ends up being triggered every frame (by design) (and for ever in my case). Same with the inspector event thing (i'd prefer full code solution anyway).

    But, is there a way to plug some code to be run when a ClipTransition start fading out but only once it happens and not every frame after, in order to achieve my initial goal ?
    Also, is there way to trigger some code and to end of the layer StartFade in order to also stop any animations still playing while weighting 0 ?

    Thank you for reading :)
     
    Last edited: Sep 10, 2022
  4. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    If you have ActionLayerFadeOut call StartFade and also
    AnimancerEvent.CurrentState.Events = null;
    that would remove the events from the state that just ended (as if you had played something else) so the end event won't keep triggering.

    There isn't an inbuilt way to do something when a fade ends but a layer should automatically stop all its animations when it finishes fading to 0. Is that not happening for you?
     
  5. S4UC1SS0N

    S4UC1SS0N

    Joined:
    May 29, 2014
    Posts:
    49
    Thank you,
    Code (CSharp):
    1. AnimancerEvent.CurrentState.Events = null;
    did the trick.
    I guess my previous issue prevented everything from stopping properly but you're right, now that my layer properly fade out, i don't see any remaining playing animation.

    Just an additional question, i'm not a C# expert so there might be some magic i'm not aware here but does
    AnimancerEvent.CurrentState
    guess based on where it's called from wich animancer it's supposed to be ?
    If there is multiple different Animancer running, it won't clear every events on all of them, right ?
     
  6. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    When an event's time is passed, it basically does:
    Code (CSharp):
    1. CurrentState = theStateWithTheEvent;
    2. callback();
    3. CurrentState = null;
    So it's only set while an event is being triggered and won't conflict with other objects because they're all executed one after the other even if they occur in the same frame.
     
    S4UC1SS0N likes this.
  7. riizade

    riizade

    Joined:
    Apr 27, 2019
    Posts:
    3
    Thanks for the clarifications!

    this.animancer.Play(this.blendedAnimation);
    is correct as far as I understand, because
    this.blendedAnimation
    is the root ManualMixerState which has no further parent. Let me know if this is not the case, and instead I should be calling something else to associate the ManualMixerState with the Animancer component that should play the animations.

    I was calling the others just as a debugging measure to see what would happen.

    Similarly, I've removed the calls to
    SetRoot()
    for children. Setting the root for the parent mixer does not fix the issue, the playable is still detected as being invalid.

    In any case, I've just sent you an email (from riizade@gmail.com) with a semi-minimal reproduction project so you can see what's going on. I've included notes on where to find relevant sections of code to hopefully expedite the debugging process if you have time to get to it.

    Thank you for the help!
     
  8. Feldruebe

    Feldruebe

    Joined:
    Jun 11, 2015
    Posts:
    5
    Hello,

    Not sure if I am doing something wrong.
    I have a setup where AnimationTransistions are stored in ScriptableObjects and are used to configure actions
    that a character can do.

    Code (CSharp):
    1.  
    2. public abstract class AnimatedActiveAction : ScriptableObject
    3. {
    4.  
    5.     public ClipTransitionAsset.UnShared AnimationTransition;
    6.  
    7.     private void Initialize()
    8.     {
    9.             //this.AnimationTransition = Instantiate(this.AnimationTransition);
    10.             this.AnimationTransition.Transition.Events.AddCallback(HitEventName, this.OnAnimationHit);
    11.             this.AnimationTransition.Transition.Events.OnEnd = this.OnAnimationEnd;
    12.     }
    13.  
    When I use this I get the warning that the callbacks are added to the same Events.
    Using the Instantiate solution from https://kybernetik.com.au/animancer/docs/manual/events/animancer/shared
    works fine. Is there something I am missing?
     
    Last edited: Sep 15, 2022
  9. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Are you sharing that AnimatedActiveAction between multiple characters? Because that would defeat the purpose of the UnShared system. Each UnShared field creates its own copy of the events so they don't cause conflicts if you have multiple fields referencing the same Transition Asset, but if you only have one UnShared field which you're sharing then it won't work. Similarly, instantiating a copy of the AnimatedActiveAction will mean they each have their own UnShared field which can each have their own copy of the events, which would allow it to work (while they still all reference the same ActionClipTransition asset).

    Unfortunately, I haven't been able to come up with any better way to avoid problems like that when sharing transitions.
     
  10. Feldruebe

    Feldruebe

    Joined:
    Jun 11, 2015
    Posts:
    5
    Thanks. I was misinterpreting how the Unshared variant was working.
     
  11. RobertOne

    RobertOne

    Joined:
    Feb 5, 2014
    Posts:
    259
  12. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
  13. colbydanesaddoris

    colbydanesaddoris

    Joined:
    Nov 3, 2018
    Posts:
    5
    Is there a way to find out if an event exists on a ClipTransition ?
    ex: - the event.Contains(..)


    var s = Character.Animancer.Play(_attack.animation);
    if( s.Events.Contains("DoDamage") )
    s.Events.SetCallback(Attack.DoDamageEventName, _OnDoDamage);
     
  14. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Use IndexOf. If the index is >= 0 then an event with that name exists and you can do SetCallback(index, ...).
     
  15. colbydanesaddoris

    colbydanesaddoris

    Joined:
    Nov 3, 2018
    Posts:
    5
    Thank you.. totally missed that one.
    And for those of us who like to one line code as much as possible.


    public static bool SetCallbackIfExists(this Animancer.AnimancerEvent.Sequence _this, string name, System.Action action)
    {
    if (_this.IndexOf(name) >= 0)
    {
    _this.SetCallback(name, action);
    return true;
    }
    return false;
    }
     
    Rusted_Games likes this.
  16. thedreamer

    thedreamer

    Joined:
    May 13, 2013
    Posts:
    226
    Can third-party assets that use Mecanim, such as dialogue system or Adventure Creator, be used without any configuration?
     
  17. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    It depends on what you're actually trying to achieve. The Animator Controllers page explains the various ways they can be used with Animancer. Other assets would most likely be using a Native Animator Controller which would continue working normally and Animancer would be able to play other separate animations on top, but those assets would have no way of knowing that Animancer is playing something or interacting with it, the Animator Controller would just keep running in the background. I'd recommend just downloading Animancer Lite to see if it meets your needs.
     
  18. yosimba2000

    yosimba2000

    Joined:
    Jun 3, 2021
    Posts:
    25
    Not sure if this is a bug. Seems that multiple Animancer Layers cannot use the same animation file at the same time:

    1) I create two AnimancerLayers, layers [0] and [1].
    2) [0] has an avatar mask that controls ONLY both feet, feet IK, the circle thing below the feet, legs, and torso.
    3) [1] has an avatar mask that controls ONLY both arms, hands, hands IK and head.

    I have one animation file called Anim1 contained within a .FBX that I would like to use for both layers. So layer[0] will receive the legs, feet, and torso movements from Anim1. Layer[1] will receive the arms, hands, and head movements from the same Anim1 file.

    When I play both layers at the same time, the animation does nothing on the character. The layers do not animate anything on the character.

    When I play each layer individually, the animations work correctly. But this isn't suitable for me because I need both layers to play at the same time.

    If I duplicate the animation file to get Anim1 and Anim1_Copy, then assign each layer either Anim1 or Anim1_Copy, then I can play both layers at the same time correctly.

    I have tested this with multiple animations, all gave the same results. Avatar rig and all animations used were Humanoid.
     
  19. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    The Dynamic Layers example demonstrates "How to play one animation on two different layers at the same time". So most likely it's just an issue with your code.
     
  20. Weeros

    Weeros

    Joined:
    Oct 5, 2018
    Posts:
    4
    Hello!

    I'm looking to replace the default Mechanim with Animancer because of the poor way Mechanim handles 2D animation blends. However, I've run into some issues and have some slightly newbie questions. I currently have only the Lite version but I'm planning to upgrade to Pro once I get to making actual builds if Animancer can solve my issues.

    My game is 2D topdown and I have 8-direction sprite animation sets. Some of my animations have three parts: intro, looping animation and outro (e.g. move into guard pose, guard, move back to walking pose). Some of my animations can interrupt the animation immediately, while others must go through the outro (e.g. getting hit and starting to walk, respectively).

    In mechanim, I can easily manage transitions with the state machine, ie. when switching to another animation the state machine will automatically go through the outro animation before allowing to start playing the walk animation. What would be the recommended or easiest way to replicate this functionality with Animancer, particularly with the direction sets? If there's a simple way this could be achieved without the Animancer state machine for instance, I'd like to know.

    Second, I tried using both the state.Events.OnEnd = <functionToPlayNext> structure, as well as the couroutine option (yield return state) to wait for the intro / outro to end before moving into the next animation (whether that's the loop or the new animation), but I see the first frame of the animation flash before the animation actually moves to the new one, as if the wait time would be longer than animation lenght and it would have just enough time to start the animation again, any idea why this would be or where to troubleshoot?

    Thanks so much in advance for any advice!
     
  21. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    I'd try just having multiple Directional Animation Sets. One set for start walk and one for loop walk. Get the start clip for the current direction, play it, and give it an end event. In that event, play the loop animation for the current direction. You might be able to come up with a more convenient way of structuring things, such as to make a class that inherits from DirectionalAnimationSet8 to give it a reference to another DirectionalAnimationSet.

    Events occur when the animation passes a specific time (this case the end of the animation). Unfortunately, once the animation times have been updated and their events checked, it's no longer possible to change the structure of the PlayableGraph such as to connect/disconnect animations by playing a new one so attempting to do so can only take effect on the next frame, meaning the frame where the event triggered was past the end of the animation and therefore looped back to the start. In the vast majority of cases that's not a problem because you wouldn't usually have a looping animation that you only want to play once.

    Unlike Weight changes, Time changes can still take effect though. That's normally bad because playing something from an event would give you a frame of the old animation at Time 0 without showing the new animation yet (because its Weight hasn't taken effect). So the AnimancerState.Time and NormalizedTime properties specifically don't allow that to happen, but if you set the internal Playable's time directly it should work. So when the event occurs, you could force its time back to the end of the animation:
    ((IPlayableWrapper)AnimancerEvent.CurrentState).Playable.SetTime(AnimancerEvent.CurrentState.Length);
    . If that works and you end up getting Animancer Pro, you could just change the
    AnimancerState.RawTime
    property to public (which I'll do for the next version since it could potentially be useful).
     
  22. msmsm

    msmsm

    Joined:
    Feb 1, 2014
    Posts:
    49
    Hi,

    I am incrementing my animation manually using normalized time each update (it is driven by a networked value and this keeps it in step with its interpolation value). However, no unity animation events are fired at all. Is this expected when using normalized time each frame, rather than just playing the animation? I would rather use animancer events but am still figuring out how to implement them in my use case and am not sure if they will solve this issue?
     
  23. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    That's by design, if you want to set the time without ignoring events, you can call MoveTime on the state instead.
     
  24. msmsm

    msmsm

    Joined:
    Feb 1, 2014
    Posts:
    49
    Absolutely perfect, thank you very much (and apologies for missing that in the API!)
     
  25. brokenspiralstudios

    brokenspiralstudios

    Joined:
    Oct 20, 2021
    Posts:
    45
    I'm new to Animancer. I think I managed to setup a ``Mixer`` but no matter the analog stick input value, it doesn't get reflected in the animations. ``_Move.Paramater`` debugs as the correct ``Dot`` values. Also the animation flicks immediately from off to on, it's not smooth. Thanks from a noob.

    Code (CSharp):
    1. public class Player : MonoBehaviour {
    2.   public AnimancerComponent animancer;
    3.   public MixerTransition2D Move;
    4.  
    5.   private void OnEnable() {
    6.     animancer.Play(Move);
    7.   }
    8. }
    9.  
    10. public class MoveState : PlayerState {
    11.   private MixerState<Vector2> _Move;
    12.  
    13.   public override void Enter() {
    14.     var state = player.animancer.States.GetOrCreate(player.Move);
    15.     _Move = (MixerState<Vector2>)state;
    16.   }
    17.  
    18.   var moveDir = input.moveDir.normalized;
    19.   if (input.moveInput.sqrMagnitude > 0) {
    20.     _Move.Parameter = new Vector2(
    21.     Vector3.Dot(player.transform.right, moveDir),
    22.     Vector3.Dot(player.transform.forward, moveDir));
    23.   }
    24.   else _MoveState.Parameter = default;
    25. }
     
  26. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    I'm assuming that last bit of code is in an Update method something, because as posted it wouldn't compile. But other than that, your code looks like it should work so the problem is probably something else. Maybe you don't have those scripts attached to the right objects or something else is playing a different animation.

    Also, changing mixer parameters doesn't include any automatic smoothing, but the Mixer Smoothing page explains how you can apply your own smoothing.
     
  27. brokenspiralstudios

    brokenspiralstudios

    Joined:
    Oct 20, 2021
    Posts:
    45
    Ok I managed to make a lot of progress. The challenge now is in my FSM, I have an AimState, in it the player can DrawBow and Move. When I exit the state by letting go of left trigger, the DrawBow animation doesn't transition into the IdleState animation. I suppose the equivalent in Mecanim would be the Exit Time out of state to fade into the next. ALSO, when ever entering the AimState, the weight in the MoveAnimation starts off around 0.3, even though I'm not pushing the analog stick yet.

    Perhaps this has something to do with the Avatar Masks? I noticed when exiting the state the upper body of the player is frozen. Perhaps because the DrawBow animation uses Upper Body, but the Idle state is the full body??

    Thanks for your help again, this truly is shaping up to be a marvellous asset!

    EDIT: Looks like I fixed the transition problem by using ``moveLayer.SetMask(null);``. Not sure if that is the ideal way of doing it but looks to be working so far. So now the problem that remains is if I enter AimState then the MoveAnimation weight starts at 0.3 and when I stop pushing moveInput, the weight doesn't fade back to 0. Remains at 0.3.

    Code (CSharp):
    1. public class AimState : PlayerState {
    2.    
    3.         private Weapon weapon;
    4.         private MixerState<Vector2> animState;
    5.         private AnimancerLayer moveLayer;
    6.         private AnimancerLayer actionLayer;
    7.  
    8.         public AimState(PlayerSM sm) : base(sm)
    9.         {
    10.         }
    11.  
    12.         public override void Enter() {
    13.             weapon = inventory.weaponInHand;
    14.  
    15.             var state = Animancer.States.GetOrCreate(weapon.AimAnimation);
    16.             animState = (MixerState<Vector2>)state;
    17.  
    18.             moveLayer = Animancer.Layers[0];
    19.             actionLayer = Animancer.Layers[1];
    20.  
    21.             moveLayer.SetMask(weapon.moveMask); // Lower Body
    22.             actionLayer.SetMask(weapon.actionMask); // Upper Body
    23.  
    24.             actionLayer.Play(weapon.DrawWeapon, 0.1f);
    25.  
    26.       public override void Logic() {
    27.             var moveInput = input.moveInput;
    28.             if (input.moveInput.sqrMagnitude > 0) {
    29.                 moveLayer.Play(weapon.AimAnimation, 0.1f);
    30.                 var moveDirAxisSwap = new Vector3(moveInput.x, 0, moveInput.y);
    31.                 animState.Parameter = new Vector2(
    32.                 Vector3.Dot(player.transform.right, moveDirAxisSwap),
    33.                 Vector3.Dot(player.transform.forward, moveDirAxisSwap));
    34.             }
    35.         }
    36.  
    37.       public override void Exit() {
    38.            // what would I put in here to fade out the DrawWeapon animation and fade out the weight of MoveAnimation??
     
    Last edited: Oct 31, 2022
  28. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Most of your issues sound like they're probably logic issues in your scripts, so you'd just need to debug your way through your scripts to figure out what's going on. For example, if your DrawBow animation isn't transitioning into your Idle animation, that means your scripts aren't telling Animancer to play the Idle animation because there isn't anything in Animancer itself that would prevent it from doing what you tell it to.

    But I can offer some general advice:
    • The Dynamic Layers example demonstrates how I'd recommend managing layers in a more complex setup. Basically, make a script which manages the layers and have everything else tell that script what to play instead of controlling Animancer directly.
    • There isn't really any way that Avatar Masks could affect how transitions work. They affect which bones the animations on each layer will control, but they can't prevent a transition from occurring or anything like that.
    • You're passing in 0.1 for the transition duration so if you're running at around 30 FPS that's only 3 frames for the entire transition to occur, in which case going from 0 to 0.3 weight in one frame sounds about right.
     
  29. brokenspiralstudios

    brokenspiralstudios

    Joined:
    Oct 20, 2021
    Posts:
    45
    YES! Dynamic Layers was the solution! Everything is looking good. Only thing now is if the player is walking (Base) and shooting (Action) and then I let go of movement but maintain shooting, then the feet will snap back to idle. The code I'm using is based on your example. Thanks again for the amazing support!

    EDIT: Actually after more testing, when using the left analog stick to control walking, it kinda jitters between the 8 directions in MixerTransition2D as opposed to smoothly moving between the values. Which is in line with the snapping into idle when letting go of the left analog stick
     
    Last edited: Nov 1, 2022
  30. F4t1h

    F4t1h

    Joined:
    Nov 12, 2017
    Posts:
    14
    Hey there, i somehow managed to call "animancer.play" in update() due to some issues that i dont remember. It works with that but now as i stumbled upon that, id like to ask if that is okay performance wise.
    Does it make a huge difference calling animancer.play in update vs once?

    Thanks in advance.
     
  31. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    It's not ideal, but the performance cost is pretty small so I wouldn't bother avoiding it unless you're really trying to optimize everything.
     
    F4t1h likes this.
  32. brokenspiralstudios

    brokenspiralstudios

    Joined:
    Oct 20, 2021
    Posts:
    45
    Hey I'm trying to integrate Multi-Aim Constraint with Animancer. When the player presses the aim button, the constraint weight smoothly goes to 1. But upon releasing the aim button OR when walking while aiming, the weight of the constraint instantly snaps back to 0 even there's no code to implement that. I think it has something to do with the base layer

    I followed your Dynamic Layers example to get to this point. How do I avoid the constraint reset to 0? I think this is the last major problem before purchasing the Pro version. Thanks!

    Code (CSharp):
    1.  
    2. public class AimState : PlayerState {
    3.        
    4.         private Weapon weapon;
    5.         private MixerState<Vector2> animState;
    6.  
    7.         public AimState(PlayerSM sm) : base(sm)
    8.         {
    9.         }
    10.  
    11.         public override void Enter() {
    12.             weapon = inventory.weaponInHand;
    13.  
    14.             var state = Animancer.States.GetOrCreate(weapon.Aim);
    15.             animState = (MixerState<Vector2>)state;
    16.  
    17.             weapon.Draw.Events.OnEnd = animHandler.FadeOutAction;
    18.             player.StartCoroutine(animHandler.AimRig1());
    19.         }
    20.  
    21.         public override void Logic() {
    22.             if (input.moveInput.sqrMagnitude > 0) {
    23.                 animHandler.PlayBase(weapon.AimWalk, false);
    24.                
    25.                 var moveInput = input.moveInput;
    26.                 var moveDirAxisSwap = new Vector3(moveInput.x, 0, moveInput.y);
    27.                 animState.Parameter = new Vector2(
    28.                 Vector3.Dot(player.transform.right, moveDirAxisSwap),
    29.                 Vector3.Dot(player.transform.forward, moveDirAxisSwap));
    30.             }
    31.             else {
    32.                 animHandler.PlayBase(weapon.Idle, false);
    33.             }
    34.             animHandler.PlayAction(weapon.Draw);
    35. }
    36.  
     
  33. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Unfortunately, that's a limitation of the Animation Rigging package when using the Playables API which I haven't found a good fix for. The Background and Workarounds sections of the Animation Rigging page explain the only ways I've come up with to avoid the issue.
     
  34. brokenspiralstudios

    brokenspiralstudios

    Joined:
    Oct 20, 2021
    Posts:
    45
    It's been suggested to me to use Animancer with Final IK to overcome that limitation. I just wanted to double check with you that is true before spending money. Thanks again!
     
  35. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    I haven't used it, but as far as I understand Final IK is completely separate from whatever animation system you use and I haven't received any reports of users having trouble with it. If you want to be totally sure you could ask them about their compatibility with Playables API based systems.
     
  36. brokenspiralstudios

    brokenspiralstudios

    Joined:
    Oct 20, 2021
    Posts:
    45
    After doing a bunch of reading, I believe the process is 1. saving the state 2. then values reset 3. then load the state. Problem is now, I can't figure out the syntax to do this loop. Here's the code again for reference. Thanks
    Code (CSharp):
    1.  
    2. public class AimState : PlayerState {
    3.    
    4.         private Weapon weapon;
    5.         private MixerState<Vector2> animState;
    6.         public AimState(PlayerSM sm) : base(sm)
    7.         {
    8.         }
    9.         public override void Enter() {
    10.             weapon = inventory.weaponInHand;
    11.             var state = Animancer.States.GetOrCreate(weapon.AimWalk);
    12.             animState = (MixerState<Vector2>)state;
    13.             weapon.Draw.Events.OnEnd += animHandler.FadeOutAction;
    14.             player.StartCoroutine(animHandler.IncreaseAimRigWeight());
    15.         }
    16.         public override void Logic() {
    17.             if (input.moveInput.sqrMagnitude > 0) {
    18.                 animHandler.PlayBase(weapon.AimWalk, false);
    19.            
    20.                 var moveInput = input.moveInput;
    21.                 var moveDirAxisSwap = new Vector3(moveInput.x, 0, moveInput.y);
    22.                 animState.Parameter = new Vector2(
    23.                 Vector3.Dot(player.transform.right, moveDirAxisSwap),
    24.                 Vector3.Dot(player.transform.forward, moveDirAxisSwap));
    25.             }
    26.             else {
    27.                 animHandler.PlayBase(weapon.Idle, false);
    28.             }
    29.             animHandler.PlayAction(weapon.Draw);
    30. }
     
    Last edited: Nov 11, 2022
  37. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    I've added a bit more detail to the Workarounds section to explain the idea a bit better. I haven't used Animation Rigging in a real project though, so it might need some experimentation to get it right.
     
  38. brokenspiralstudios

    brokenspiralstudios

    Joined:
    Oct 20, 2021
    Posts:
    45
    hey so I'm making more progress. Got the animation rigging working correctly but now getting several warnings.
    1. "Possible Bug Detected: The AnimancerEvent.Sequence being modified should not be modified because it was created by a Transition."
    2. "Possible Bug Detected: The AnimancerEvent.Sequence.OnEnd callback being invoked contains multiple identical delegates which may mean that they are being unintentionally added multiple times.
    - State: Aim (ClipState)
    - Method: FadeOutAction"

    Code (CSharp):
    1.  
    2. public override void Enter() {
    3.             weapon = inventory.weaponInHand;
    4.  
    5.             BaseLayer = animancer.Layers[0];
    6.             ActionLayer = animancer.Layers[1];
    7.             ActionLayer.SetMask(animHandler.ActionMask);
    8.  
    9.             animancer.Playable.KeepChildrenConnected = true;
    10.             animancer.Layers[0].GetOrCreateState(weapon.AimWalk);
    11.  
    12.             weapon.Aim.Events.OnEnd += animHandler.FadeOutAction;
    13.  
    14. public override void Update() {
    15.             animHandler.PlayAction(weapon.Aim);
    16. }
    17.  
     
  39. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    The first warning is because transitions give their state a direct reference to their events so modifying the state's events will modify the transition's events and affect future plays of that transition as well, which is a common source of bugs. In general, you should configure transition events only once on startup before you play them, but otherwise the warning explains how you can disable it.

    The second warning is because
    weapon.Aim.Events.OnEnd +=
    will add a new copy of the callback every time you enter that state and you don't seem to be removing it.
     
  40. brokenspiralstudios

    brokenspiralstudios

    Joined:
    Oct 20, 2021
    Posts:
    45
    So I managed to get rid of the second warning but can't seem to get past the first warning. I'm really not sure how to configure transitions events only once on startup before I play them. The aim animation exists on the Weapon, and the triggering of the animation exists in the AimState in the FSM. Not sure how to reference or initiate the OnEnd correctly. I've been trying all sorts of attempts to make this work but just not getting it due to my beginner nature. Are you able to show what I need to write to avoid this warning? I'll really like to make this work and get to the next stage. Thanks so much, appreciate your help!

    Code (CSharp):
    1.  
    2. public class Weapon : MonoBehaviour {
    3.        
    4.        [field: SerializeField] public ClipTransition Aim { get; private set; }
    5. }
    6.  
    7. public class AimState : PlayerState {
    8.        
    9.         private Weapon weapon;
    10.      
    11.         public AimState(PlayerSM sm) : base(sm)
    12.         {
    13.         }
    14.  
    15.         public override void Enter() {
    16.             weapon = inventory.weaponInHand;
    17.  
    18.             weapon.Aim.Events.OnEnd += OnAnimationEnd;
    19.             animHandler.PlayAction(weapon.Aim);
    20.         }
    21.  
    22.         private void OnAnimationEnd() {
    23.             weapon.Aim.Events.OnEnd -= OnAnimationEnd;
    24.             // exit state
    25.         }
     
  41. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    As it states in the warning: "This warning can be prevented by calling state.Events.SetShouldNotModifyReason(null); before making any modifications."

    In your case, that's weapon.Aim.Events... on the lines before you add or remove the event.
     
  42. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,433
    Hopefully this isn't a FAQ after 35 pages I didn't read, but I noticed that
    animancer.IsPlaying(animationClip)
    continues to return true even if it has played through an entire non-looping animation clip.

    If I don't want to subscribe to animancer events, but to casually check if the given clip is done, is this a suitable approach? It seems to work for my first test case. I might be missing something about reverse speeds, but I never use those. Anything else to be worried about?

    Code (CSharp):
    1.  
    2.         bool IsDone(AnimationClip clip)
    3.         {
    4.             if (clip == null)
    5.                 return true;
    6.             if (!animancer.IsPlaying(clip))
    7.                 return true;
    8.             object key = animancer.GetKey(clip);
    9.             AnimancerState state = null;
    10.             animancer.States.TryGet(key, out var state);
    11.             if (state == null)
    12.                 return true;
    13.             if (state.NormalizedTime >= 1f && clip.isLooping == false)
    14.                 return true;
    15.             return false;
    16.         }
    17.  
     
  43. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Yes, animations will keep playing and their time will keep increasing until something stops them. That's how the Playables API works and I didn't want to introduce a situation where you ask it if an animation is playing and it says no even though that's still the only animation currently affecting the model.

    I made a couple of changes to your method:

    Code (CSharp):
    1.         public bool IsNotActive(AnimationClip clip)
    2.         {
    3.             if (clip == null)
    4.                 return true;
    5.  
    6.             var key = animancer.GetKey(clip);
    7.             if (!animancer.States.TryGet(key, out var state))
    8.                 return true;
    9.  
    10.             if (!state.IsPlaying)
    11.                 return true;
    12.  
    13.             if (!clip.isLooping && state.NormalizedTime >= state.NormalizedEndTime)
    14.                 return true;
    15.  
    16.             return false;
    17.         }
    • IsDone seems like an inappropriate name if "animation has never been played" is going to return true, so I called it IsNotActive instead.
    • IsPlaying and TryGet both look up the state so rather than call both, I only call one and check the state.IsPlaying.
    • TryGet returns a bool to indicate if it found a state rather than null checking it.
    • I check isLooping before NormalizedTime because it should be faster (since NormalizedTime involves a couple of calculations, especially if it's a Mixer state).
    • state.NormalizedEndTime will account for the time of the End Event if you've set one.
     
    halley likes this.
  44. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,433
    Thanks much, @Kybernetik . I wasn't too sure about how NormalizedEndTime worked but I guess it also flips the scale if you're going backwards, so >= works? I am not too fond of
    var
    so I skipped those changes, but your point about state.IsPlaying ordered after TryGet makes sense to me.
     
  45. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,433
    And in a completely different vein, I did try using Stately as my state machine, instead of using Unity's visual AnimatorController and also not using your optional state machine solution. It works pretty well and I really like how Animancer works for script-oriented use like this.

    This is the state machine setup to recreate 90% of the Unity "Third Person Controller" demo, obviously calling on other functions to keep it so compact, but there's nothing else in the Inspector besides AnimationClip and AudioClip resources and one Animancer 2D Mixer.

    Code (CSharp):
    1.         void Start()
    2.         {
    3.             root = new State("root");
    4.             State grounded = new State("grounded");
    5.             State jumping = new State("jumping");
    6.             State falling = new State("falling");
    7.             State landing = new State("landing");
    8.  
    9.             grounded.OnEnter = () => { StartGrounded(); Play(idleMixer); };
    10.             grounded.OnUpdate = (dT) => { UpdateGroundedMotion(dT); };
    11.             grounded.ChangeTo(jumping).If(() => canJump && input.jump );
    12.             grounded.ChangeTo(falling).If(() => isFalling);
    13.             grounded.OnExit = () => { };
    14.  
    15.             jumping.OnEnter = () => { Jump(); Play(jumpingAnim); };
    16.             jumping.OnUpdate = (dT) => { UpdateUngroundedMotion(dT); };
    17.             jumping.ChangeTo(falling).AfterNFrames(2).AndIf(() => isFalling);
    18.             jumping.ChangeTo(landing).AfterNFrames(2).AndIf(() => isSteady);
    19.  
    20.             falling.OnEnter = () => { Play(fallingAnim); };
    21.             falling.OnUpdate = (dT) => { UpdateUngroundedMotion(dT); };
    22.             falling.ChangeTo(landing).If(() => isSteady);
    23.  
    24.             landing.OnEnter = () => { Play(landingAnim); };
    25.             landing.OnUpdate = (dT) => { UpdateUngroundedMotion(dT); };
    26.             landing.ChangeTo(grounded).AfterNFrames(2).AndIf(() => isSteady);
    27.  
    28.             unrootedVelocity = Vector3.zero;
    29.             root.StartAt(grounded);
    30.             root.Start();
    31.         }
    32.  
    33.         void Update()
    34.         {
    35.             root.Update(Time.deltaTime);
    36.         }
    37.  
    Surely not everybody's cup of tea, but just in case there's any other Stately fans out there, Animancer was what made this boondoggle possible. My next step is to see if I can handle all of the state spaghetti which is in the popular RPG Character Animations pack, with an arsenal of different 1H and 2H weapons, swimming, and other complications, all replaced with Animancer.
     
    hopeful likes this.
  46. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    If you haven't set the end event time, NormalizedEndTime will give 1 or 0 depending on whether the state.Speed is positive or negative, but you'll also need to flip your >= to <= if you want to support negative speed. Otherwise you'd just be doing state.NormalizedTime >= 0.
     
  47. Neodecadente

    Neodecadente

    Joined:
    Oct 7, 2019
    Posts:
    2
    Amazing that you're still helping around @Kybernetik , thanks for that.

    I'm trying to use Animancer with some UI elements, but I can't get it to work. I'm playing an animation like this:

    Code (CSharp):
    1.   [SerializeField] private AnimationClip _diceRollAnim;
    2.   private AnimancerComponent _animancer;
    3.  
    4.   void Start() {
    5.     _animancer.Play(_diceRollAnim);
    6.   }
    If I deactivate Animancer, the Animator plays the animation with no issues, but when I try to do it through Animancer I get the following error:

    Possible Bug Detected: the details of 'DiceRoll (ClipState)' do not match the Rig of 'Dice' so the animation might not work correctly.
    - The Animator has no Avatar.
    - This message has been copied to the clipboard (in case it is too long for Unity to display in the Console).
    - 1 of 1 bindings do not exist in the Rig: [x] = Missing, [o] = Exists
    [x] Image.m_Sprite
    I'm not sure what the problem is. It is true that there's no Avatar showing on my Animator, but as far as I recall, I've never needed one before for either Animator or Animancer. The only difference this time is that I'm trying to use it with an UI element. Not sure if that has anything to do with this.
     
  48. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Having no Avatar is only an error for Humanoid Rigs, otherwise it's just mentioned there to let you know.

    The binding "Image.m_Sprite" without a path before it indicates that the animation is trying to control an Image component on the same object as the Animator and the "[x]" indicates that it couldn't find that component.
     
  49. Neodecadente

    Neodecadente

    Joined:
    Oct 7, 2019
    Posts:
    2
    Thanks for the quick answer. So, if I understood correctly, you're telling me the animation is trying to animate an Image component, but Animancer couldn't find it? That confuses me more. I do have the Image component or else the Animation wouldn't be able to play through the Animator. What am I doing wrong?? What should I look for? This is my dice object (plus some other scripts) if it can help somehow:
     

    Attached Files:

  50. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Your Animator component is disabled so Animancer isn't going to be able to use it to play anything. If you don't want it to play the Animator Controller you should clear that field, but the Animator component needs to stay active.

    I'll add an Inspector warning for that in the next version.
     
    Neodecadente likes this.