Animancer - Less Animator Controller, More Animator Control

If you want a single event when the time reaches the end and you aren’t going to use that event to play something else then yeah, an event time just below 1 is what you need. AnimancerEvent.AlmostOne is the largest possible float value below 1, but 0.99 would give basically the same result.

Hi,
how can set OnEnd Event for AssetsTransition in the start or awake function? I want to set up events in the beginning so I can reduce performance waste.

so after playing the animation, I called this line:

_CurrentState = _BaseLayer.Play(_Light_Landing);

if (base.IsOwner)
{
_CurrentState.Events(SharedOwner).OnEnd ??= OnLandingEnd;
}

is there any way to set up all the OnEnd AssetsTransition before the animation plays? (it was ok with Transition Clip)

also, my character has a lot of animations (maybe more than 1000) is it ok to use AssetsTransition here? cause when I create 80 the inspector, kinda lags with this amount (not really important but could it affect runtime performance?).
animations are split:
Character Animation Layer 1
Gun Animations layer 2, this could be a spear, axe, or anything each of these can have more than 10 animations.
and another layer for specific needs.

the whole point is I have tons of animations here.

Thanks for the new update, really huge deal!

Transition Assets don’t allow you to easily set their events because doing so often causes conflicts when multiple characters reference the same asset as explained in the Animancer v8.0 Change Log.

If you really need to do it, you can do: ((ITransitionWithEvents)transitionAsset.Transition).Events.OnEnd = ...

Having more states will have a very small performance cost which may start to matter as you get into the hundreds so it might be worth destroying the states for the old weapon when you equip a new one.

The runtime performance cost for each state won’t be anything like the lag caused by the Inspector though, that’s just because the Editor GUI system isn’t very efficient and I’m just drawing everything by default. If it’s an issue you can make it only show currently active states by changing the Display Options: enable “Separate Active From Inactive States” and disable “Show Inactive States”.

1 Like

hey noob question, in docs it said doesn’t automatically clear events whenever anything is played
so how we can manually clear events? I found out set the events to null is not an option

You could assign AnimancerEvent.DummyCallback or use any of the Remove methods on the event sequence, but why would you need to do that? I don’t think I’ve ever come across a situation where that was necessary.

1 Like

Hi,
Is there a way to transition to the same state with fade. For example i do have dodge TransitionAsset and sometimes player might want to use it multiple times in a row. Do i just hack it with 2 Transition assets, or is there a more elegant way?

I was using mechanim before buying Animancer, each of my states were run using crossfade and then all of them had default transition to idle state. I’m looking for a way to recreate that using Animancer. Am i missing something in transition assets? I prefer for “idle” state to be ambiguous (meaning let’s say ability won’t know what its transitioning too it would be perfect).

Thanks for the asset!

The Transitions sample demonstrates how to do that.

Hi, I have the following setup:

  • A: Transition Asset with a default fade duration of 0
  • B: Transition Asset with default fade duration of 0.25

In my TransitionLibrary Asset, I’ve overwritten the transition from A to B to 1s. However, this seems to get ignored. The TransitionLibrary has been added to the corresponding field in the Animancer Component. Is there any additional setup required?

I think there might be something wrong with the TransitionLibrary, as I’m seeing these empty gaps as well as getting errors when making changes at runtime:


Screenshot 2024-10-02 150042

There shouldn’t be anything else required for it to work than that. Does the Library Character sample work properly for you? Are you sure you’re calling animancer.Play(transitionAssetB) and not trying to play the state or something? If so, could you please send a minimal reproduction project to animancer@kybernetik.com.au so I can take a look at it?

The empty gaps would be if the library referenced a separate Transition Asset which was deleted so its just a null reference. You could remove the reference if you could select it, but it seems there’s a bug which prevents you from selecting it. The easiest workaround for now would be to just recreate the library from scratch since it doesn’t have much in it.

Once I fix the selection issue I probably won’t release an update just for this so if anyone needs it they can email me their Invoice Number.

Hello,
I am using animancer’s hybrid animancer component to use both the native character controller and the animancer component to play specific one off animations.
However, I have problems with integrating animancer into my mecanim project.


I am trying to use animancer to add customizable attack combos in my game. I am also using the regular animator controller to animate all other movement and behavioural actions in the game. One thing I noticed is that the animancer is giving a warning that “no base layer has been created, which likely means no animations have been played yet”. I’m not sure how to create a base layer for animancer but my animator controller already has a base layer. Why doesn’t it recognize the base layer there?
When I play an attack animation, it transitions to the attack animation and gets stuck at the end of the animation without transitioning back to the idle state of the animator controller. I look at the hybrid animancer component example from the animancer samples, and every setting looks the same as the setting I have on mine. I can’t figure out why it doesn’t transition between animancer animations and the native character controller. Do you know what I’m doing wrong?

Animancer has nothing to do with what’s inside the Animator Controller. It can’t play animations on the Animator Controller’s layers or anything like that, they’re entirely separate.

You’re playing the Animator Controller but haven’t played anything in Animancer yet so Animancer doesn’t have any layers. It will create one when you play something in Animancer.

You mentioned that you’re using a hybrid Animancer component but your screenshot shows that you’re using a Native Animator Controller. Native and Hybrid are mutually exclusive approaches which are each controlled slightly differently as explained on the Animator Controllers page and in the Hybrid Character sample.

What code are you using to tell it to transition between Animancer animations and the native character controller?

I’m not sure if that’s true because in the HybridCharacterAnimations.cs file, you have an example that shows the hybridAnimancerComponent has an Animator property that runs the mecanim animator through the hybridAnimancerComponent

public class HybridCharacterAnimations : MonoBehaviour
{
    /************************************************************************************************************************/

    public static readonly int IsMovingParameter = Animator.StringToHash("IsMoving");

    [SerializeField] private HybridAnimancerComponent _Animancer;
    [SerializeField] private ClipTransition _Action;

    private State _CurrentState;

    private enum State
    {
        NotActing,// Idle and Move can be interrupted.
        Acting,// Action can only be interrupted by itself.
    }

    /************************************************************************************************************************/

    protected virtual void Awake()
    {
        _Action.Events.OnEnd = UpdateMovement;

        // This sample's documentation explains why these warnings exist so we don't need them enabled.
        OptionalWarning.NativeControllerHumanoid.Disable();
        OptionalWarning.NativeControllerHybrid.Disable();
    }

    /************************************************************************************************************************/

    protected virtual void Update()
    {
        switch (_CurrentState)
        {
            case State.NotActing:
                UpdateMovement();
                UpdateAction();
                break;

            case State.Acting:
                UpdateAction();
                break;
        }
    }

    /************************************************************************************************************************/

    private void UpdateMovement()
    {
        _CurrentState = State.NotActing;

        float forward = SampleInput.WASD.y;
        bool isMoving = forward > 0;

        // Native - Animator Controller assigned to the Animator.
        if (_Animancer.Animator.runtimeAnimatorController != null)
        {
            // Return to the Animator Controller by fading out Animancer's layers.
            AnimancerLayer layer = _Animancer.Layers[0];
            if (layer.TargetWeight > 0)
                layer.StartFade(0, 0.25f);

            // Set parameters on the Animator compponent.
            _Animancer.Animator.SetBool(IsMovingParameter, isMoving);
        }
        // Hybrid - Animator Controller assigned to the HybridAnimancerComponent.
        else if (_Animancer.Controller.Controller != null)
        {
            // Return to the Animator Controller by playing the ControllerTransition.
            _Animancer.PlayController();

            // Set parameters on the ControllerState.
            _Animancer.SetBool(IsMovingParameter, isMoving);
        }
        else
        {
            Debug.LogError("No Animator Controller is assigned.", this);
        }
    }

    /************************************************************************************************************************/

    private void UpdateAction()
    {
        if (SampleInput.LeftMouseUp)
        {
            _CurrentState = State.Acting;
            _Animancer.Play(_Action);
        }
    }

    /************************************************************************************************************************/
}

The screenshot doesn’t show it but the hybridAnimancerComponent is actually at the last end of the list of components my character has.


I set up the hybrid character sample scene in a similar way to what I have in my scene based on my understanding on how the components work, by placing the character controller in the mecanim animator component and leaving the HybridAnimancerComponent char controller blank, and it still worked fine in that scene.

I’m using the state pattern to control what states my character is in and what animations play in that state. I have been using mecanim with no transitions all this time, but I had a problem with making customizable combos so I felt animancer would give me options for implementing that feature. I’m aware there is a state machine provided by animancer that I could extend and animate my character with, but I had already built up my own custom state machine and because I’m learning how to use programming patterns I feel like I need to stick with what I’m constructing to get a greater benefit in learning the patterns.
As of right now, I have 14 states all mapped out. unlike the state machine animancer uses, my states are not monobehaviours but are managed and run by a statemachine script that is a monobehaviour. Here is an example of one or two states that I created. They are all fairly similar in functionality.

public class PlayerFreeLookState : PlayerBaseState
{
    private bool shouldFade;
    private readonly int FreeLookBlendTreeHash = Animator.StringToHash("FreeLookBlendTree");
    private readonly int FreeLookSpeedHash = Animator.StringToHash("FreeLookSpeed");

    private const float animatorDampTime = 0.1f;
    private const float crossFadeDuration = 0.3f;

    private float moveAnimFloatValue = 3f;

    private float notGroundedTimer;

    public PlayerFreeLookState(PlayerStateMachine stateMachine, bool shouldFade = true) : base(stateMachine)
    {
        this.shouldFade = shouldFade;
        notGroundedTimer = stateMachine.FallingStateSpeed;
    }

    public override void Enter()
    {
        Vector3 momentum = stateMachine.Controller.velocity;
        //momentum.y = 0;
        float freeLookValue = momentum.normalized.magnitude * moveAnimFloatValue;

        stateMachine.AnimancerAnimator.Animator.SetFloat(FreeLookSpeedHash, freeLookValue, animatorDampTime, Time.deltaTime);
        if (shouldFade)
        {
            stateMachine.AnimancerAnimator.Animator.CrossFadeInFixedTime(FreeLookBlendTreeHash, crossFadeDuration);
        }
        else
        {
            stateMachine.AnimancerAnimator.Animator.Play(FreeLookBlendTreeHash);
        }

        stateMachine.CurrentAnimatorStateLength = 0;
    }

    public override void Tick(float deltaTime)
    {
        Vector3 movement = CalculateMovementRelativeToCamera(MovementValue);

        Move(movement * stateMachine.FreeLookMovementSpeed, deltaTime);

        //UpdateAnimator(deltaTime, movement);

        // Updates the animation
        if (MovementValue == Vector2.zero)
        {
            stateMachine.AnimancerAnimator.Animator.SetFloat(FreeLookSpeedHash, 0, animatorDampTime, deltaTime);
            return;
        }
        float freeLookValue = movement.magnitude * moveAnimFloatValue;
        stateMachine.AnimancerAnimator.Animator.SetFloat(FreeLookSpeedHash, freeLookValue, animatorDampTime, deltaTime);

        RotateCharacter(movement, deltaTime);

        // go back to locomotion
        if (stateMachine.Targeter.CurrentTarget != null)
        {
            stateMachine.SwitchState(new PlayerTargetingState(stateMachine));
        }

        if(!stateMachine.Controller.isGrounded && 
            (Mathf.Abs(stateMachine.Controller.velocity.y) > stateMachine.FallingStateSpeed))
        {
            stateMachine.SwitchState(new PlayerFallingState(stateMachine));
        }

        if(stateMachine.isSprinting)
        {
            stateMachine.SwitchState(new PlayerSprintingState(stateMachine));
        }
    }

    public override void Exit()
    {
    }

}

This state handles default idle/movement blend tree

public class PlayerAttackingState : PlayerBaseState
{
    private static string attackName;

    public readonly int AttackingHash = Animator.StringToHash(attackName);

    private bool alreadyAppliedForce;
    private bool shouldFade;
    public Attack attack { get; private set; }

    private AnimancerState attackAnimationState;

    public PlayerAttackingState(PlayerStateMachine stateMachine, int attackIndex, bool shouldFade = true) : base(stateMachine)
    {
        attack = stateMachine.Attacks[attackIndex];

        attackName = attack.AnimationStateName;

        //stateMachine.Animator.runtimeAnimatorController = attack.AnimatorOverrideController;

        this.shouldFade = shouldFade;
    }

    public PlayerAttackingState(PlayerStateMachine stateMachine, string attackName) : base(stateMachine)
    {
        attack = stateMachine.Attacks[0];

        PlayerAttackingState.attackName = attackName;

        //stateMachine.Animator.runtimeAnimatorController = attack.AnimatorOverrideController;

    }

    public override void Enter()
    {
        Debug.Log(attackName);
        stateMachine.WeaponDamage.SetAttack(attack.Damage, attack.Knockback);

        //stateMachine.Animator.CrossFadeInFixedTime(attackName, attack.TransitionDuration);

        //if (shouldFade)
        //{
        //    stateMachine.Animator.CrossFadeInFixedTime(attackName, attack.TransitionDuration);
        //}
        //else
        //{
        //    stateMachine.Animator.Play(attackName);
        //}

        attackAnimationState = stateMachine.AnimancerAnimator.Play(attack.AnimancerClip);

        //stateMachine.WaitForCrossFadeToGetAnimationStateLength(attackName);

        if (stateMachine.Targeter.CurrentTarget)
        {
            FaceTarget();
        }
        else
        {
            FaceCamera();
        }
    }

    public override void Tick(float deltaTime)
    {
        Move(deltaTime);

        float normalizedTime = attackAnimationState.NormalizedTime;

        if (normalizedTime < 1f)
        {
            if (normalizedTime >= attack.ForceTime)
            {
                TryApplyForce();
            }

        }
        else
        {
            ReturnToLocomotion();
        }
        //normalizedTime = GetNormalizedTime(stateMachine.Animator, "AttackTransition");
    }

    public override void Exit()
    {
        stateMachine.CurrentAnimatorStateLength = 0;
        stateMachine.WeaponHandler.DisableWeapon();
    }

    public void TryComboAttack(float normalizedTime)
    {
        if (attack.NextComboStateIndex == -1) { return; }

        if (normalizedTime < attack.ComboAttackTime) { return; }

        Debug.Log("next attack");

        stateMachine.SwitchState(new PlayerAttackingState(stateMachine, attack.NextComboStateIndex));
    }

    private void TryApplyForce()
    {
        if (alreadyAppliedForce) { return; }

        stateMachine.ForceReceiver.AddForce(stateMachine.transform.forward * attack.Force);

        alreadyAppliedForce = true;
    }

}

This state handles attacking. This state is different from all my other states because I call animancerComponent.Play the animation directly for the attack animations.
attack.AnimancerClip stores the ClipTransition for the attack animations.
Initially, I would use the mecanim animator by storing it and calling stateMachine.Animator to play the animation required by the state at the beginning of that state.
After adding the hybridAnimancerComponent, I stored that in the stateMachine and now call stateMachine.AnimancerAnimator.Animator to use mecanim for the states that I still want to animate with mecanim except for my attacking state that I am trying to make combos customizable with animancer.

I got the hybridAnimancerComponent working. I went through the animator controllers page and found out how to ensure there are seemless transitions between the animator controller and the animancer states.

If you aren’t assigning the Animator Controller to the HybridAnimancerComponent then you should just use a base AnimancerComponent because you aren’t using the Hybrid system at all. It will work fine as is, but it’s wasting a bit of extra performance for no reason. I’ve added an Inspector warning for the next version of Animancer:
image
I’ve also added more detail to the HybridCharacterAnimations sample script:


Animancer’s FSM doesn’t require states to be MonoBehaviours, only that they implement IState. The samples just use MonoBehaviours because it’s convenient. But there’s certainly nothing wrong with using your own system, that’s why Animancer’s FSM is completely separate from the animation system after all.

Those fields you’re storing the StringToHash in should be static since there’s no point in recalculating the same value for every new instance. You might also be interested in Weaver’s system for procedurally generating a script containing constants for all Animator Controller values which is included in Weaver Lite for free.