Animancer - Less Animator Controller, More Animator Control

My TransitionAsset Transition type is ClipTransition

Idle_Normal2.MaximumDuration

Itā€™s not called Length because other state types like Mixers and Controller States can have a variable length depending on their parameters, but for a ClipState / ClipTransition itā€™s just the Length.

Whats that warning means? i just want to repeat a TransitionAsset clipTransition,is like a LOL hero atk can be interrupted and repeated,now it work right,but i wonder that warning,it warning just at the first time when Atk_Normal1 gona to be end


62d78223-d1ca-4a03-97fd-06d5e206859a

It means that whatever youā€™re doing in SetCurrentStates isnā€™t telling Animancer to stop the current animation or play something, which often indicates an issue with your logic. If you have some other system that will play something later on in response to something that was set during the event, Animancer canā€™t detect that so you can just disable the warning.

problem solved, thanks a lot.

how to add a callback without play Skill_R in Transition Asset
87d33e38-eaf7-41ab-82c8-55f4b79924c7


This plugin seems incredibly promising! Iā€™m surprised it hasnā€™t gained more recognition on this forum. Iā€™ve reached a point where Iā€™m fed up with dealing with Animator FSM graphs and want to switch everything back to code. Iā€™m definitely purchasing this plugin to escape the frustration of Animator graphs. Well done!"
ć‚«ć‚¤ćƒˆ

animancer.States.GetOrCreate(transition) will create the state without playing it so you can add the events.

i want to use Dynamic Layers
i know fade out layer,but how to fade in layer and set layer weight
i set StartFade parameter but it is not work,its weight is still 1 when PlayQ

like this Action Layer , i want to set layer weight is 0.5 before play Skill_Q1 Clipļ¼Œbut it still 1 weight

Playing something on a layer will automatically fade it in to Weight 1 so you should get what you want if you start your fade after calling Play.

So we have our own kinematic character movement controller that uses FixedUpdate(). The problem we are running into is that because we accumulate root motion in OnAnimatorMove() (which happens on Update()) and apply it in our character movement controllerā€™s FixedUpdate(), there are root motion translation discrepancies when we have frame rate variance. This is resolved when we set the Animatorā€™s UpdateMode to AnimatePhysics, however that causes our animations to be jittery.

Is there anyway to get root motion information from an animation in FixedUpdate(), but have that animation play smoothly? My current plan is to attempt to apply this technique: Motion Interpolation Solution (to eliminate FixedUpdate() stutter). However, I wasnā€™t sure if doing something like this is even possible in Animancer, or if thereā€™s another simpler way Iā€™m not thinking about.

Animancerā€™s updating is all handled by Unity in the same systems it uses for Animator Controllers so you should be able to do the same interpolation. Iā€™ve never tried anything like that so Iā€™d be interested to hear if you can get it working.

Darnā€¦I was afraid youā€™d say that lol. So what I think would be helpful to know is what methods allow me to move an animation while ensuring animation events get invoked, as well as ones that donā€™t?

Setting an AnimancerState.Time skips events and root motion.

Calling AnimancerState.MoveTime doesnā€™t, as long as you make sure not to call it more than once per frame due to a stupid design decision in the Playables API.

1 Like

What does Animancer.Playable.Evaluate() do? Is that something I need to call in addition to MoveTime(), or instead of?

(post deleted by author)

So Iā€™ve got something thatā€™s working pretty well, however it breaks down whenever I set a LinearMixerTransitionā€™s float Parameter. It appears to be somehow conflicting with the Time Iā€™m setting in this component. Maybe you could help me figure out what I might be overlooking here?

using System.Collections.Generic;
using Animancer;
using UnityEngine;

public class AnimancerInterpolatorComponent : MonoBehaviour
{
    // Struct containing cached animancer state data.
    private readonly struct AnimancerStateData
    {
        public float Time { get; }

        private AnimancerStateData(float time)
        {
            Time = time;
        }

        public static AnimancerStateData CreateFromState(AnimancerState state)
        {
            return new AnimancerStateData(state.Time);
        }

        public void ApplyToState(AnimancerState state)
        {
            state.Time = Time;
        }
    }

    [SerializeField]
    [HideInInspector]
    private AnimancerComponent _animancer;

    public bool RootMotionDeltasValid { get; private set; }

    private AnimancerComponent Animancer
    {
        get => _animancer;
        set => _animancer = value;
    }

    private Dictionary<AnimancerState, AnimancerStateData> CurrentAnimancerStateData { get; } = new();
    private Dictionary<AnimancerState, AnimancerStateData> PreviousAnimancerStateData { get; } = new();

    private void Awake()
    {
        // Prevent graph from updating on its own.
        Animancer.Playable.PauseGraph();
    }

    private void FixedUpdate()
    {
        foreach (var state in Animancer.States)
        {
            if (state.Weight <= 0f)
            {
                continue;
            }

            if (!CurrentAnimancerStateData.TryGetValue(state, out var stateData))
            {
                stateData = AnimancerStateData.CreateFromState(state);

                CurrentAnimancerStateData[state] = stateData;
                PreviousAnimancerStateData[state] = stateData;
            }

            // When a state's speed is 0, we assume its time is being managed externally.
            if (state.Speed != 0f)
            {
                stateData.ApplyToState(state);
            }
        }

        Animancer.Playable.Evaluate();

        // Tells the root motion redirector to only respect deltaPosition/deltaRotation values when this flag is set.
        RootMotionDeltasValid = true;
        Animancer.Playable.Evaluate(Time.fixedDeltaTime);
        RootMotionDeltasValid = false;

        foreach (var (state, stateData) in CurrentAnimancerStateData)
        {
            PreviousAnimancerStateData[state] = stateData;
        }

        foreach (var state in Animancer.States)
        {
            CurrentAnimancerStateData[state] = AnimancerStateData.CreateFromState(state);
        }
    }

    private void LateUpdate()
    {
        // (Time.time - Time.fixedTime) is the "unprocessed" time according to documentation.
        var interpolationAlpha = (Time.time - Time.fixedTime) / Time.fixedDeltaTime;

        foreach (var (state, stateData) in CurrentAnimancerStateData)
        {
            if (state.Weight <= 0f
                || state.Speed == 0f
                || !PreviousAnimancerStateData.TryGetValue(state, out var previousStateData))
            {
                continue;
            }

            state.Time = Mathf.Lerp(previousStateData.Time, stateData.Time, interpolationAlpha);
        }

        Animancer.Playable.Evaluate();
    }

#if UNITY_EDITOR
    private void OnValidate()
    {
        if (TryGetComponent<AnimancerComponent>(out var animancer))
        {
            Animancer = animancer;
        }
    }
#endif
}

Your logic looks reasonable and time shouldnā€™t have any connection with a mixer parameter so Iā€™m not sure what could be wrong.

If you want to send a minimal reproduction project to animancer@kybernetik.com.au Iā€™d be happy to take a look at it.

So I solved that issue, now the last thing to resolve is that because Iā€™m using Evaluate() 3 times, itā€™s triggering AnimancerEvents multiple times. Do you have a recommended method for temporarily disabling AnimancerEvent invocation? I was thinking about nulling out the AnimancerStateā€™s Events before calling Evaluate() then reassigning after, but wasnā€™t sure if you had a safer, less disruptive approach.

EDIT: Upon testing, nulling out the AnimancerStateā€™s Events before calling Evaluate() did not seem to work