Skip to content
HY Devlog
Go back

CrossFade's 0.3f: On One Side It's 0.3 Seconds, on the Other It Isn't

Looking for a way to smooth animation transitions from script, I landed on CrossFade and clipped an explanation that was short and clear. Reopening it, it’s from June 2014 — twelve years ago.

The CrossFade function takes two arguments. The first is the name of the animation clip to change to. The second is the time it takes to fade out to the other animation clip.

Something caught me immediately. The example starts with Animation anim;. That’s Animation, not Animator. And in Unity today, character animation almost always goes through Animator.

So I checked. Both components have a method by that name, and the second argument is in a different unit.

Table of contents

Table of contents

The API that 2014 post describes is Animation

First, is the original correct? It is.

public void CrossFade(string animation,
                      float fadeLength = 0.3F,
                      PlayMode mode = PlayMode.StopSameLayer);

The docs describe fadeLength like this:

The duration of the crossfade in seconds. Negative values are clamped to 0 seconds.

That’s exactly what the original’s “time it takes to fade out” means. The 0.3f in its example is even the method’s own default value. A 2014 post that is still not wrong.

The problem is elsewhere. This is a method on the Animation component, and Animation predates Mecanim. Pulling clips out as properties, as in the original’s anim.runForward.name, is from that era too.

Three docs pages say three different things

Looking up where Animation stands today, I found three official pages with three different postures.

PageWhat it says
Manual — Legacy Animation component“This component is retained in Unity for backwards compatibility.” / “For new projects, use the Animator component.
Manual — Legacy Animation system“Legacy is still available because it is easier to use and provides better performance for simpler animations.”
Scripting Reference — AnimationNo legacy marking at all

Same company, same moment in time: one says don’t use it for new projects, one lists the reasons it was kept, and one says nothing.

Reading only the Scripting Reference, there’s no way to tell this is the older system. The Animation.CrossFade page carries no pointer to Animator either. Since search usually drops you straight onto an API page, that’s a genuinely confusing arrangement.

The reading that holds up: new character animation goes on Animator, and Animation is for maintaining what was already built with it. That’s what the manual’s component page says most plainly.

0.3f changes meaning

Here’s the crux. Animator has a CrossFade too.

public void CrossFade(string stateName,
                      float normalizedTransitionDuration,
                      int layer = -1,
                      float normalizedTimeOffset = float.NegativeInfinity,
                      float normalizedTransitionTime = 0.0f);

The second parameter has a different name. Not fadeLength but normalizedTransitionDuration. The description differs by one word:

The duration of the transition (normalized).

The class description says the same: it “creates a crossfade from the current state to any other state using normalized times.”

So porting the 2014 post straight onto Animator gives you this:

// The 2014 post — Animation component. 0.3f is 0.3 seconds.
_animation.CrossFade("Run", 0.3f);

// The same number on Animator — not 0.3 seconds.
_animator.CrossFade("Run", 0.3f);

It compiles, it runs, and a fade happens on screen. Its length just isn’t 0.3 seconds — it’s a fraction of a state’s duration. A two-second clip gives 0.6 seconds; a half-second clip gives 0.15. Transition speed varies per clip while the number stays 0.3f, which makes it hard to trace.

One more thing worth flagging. The API docs say “normalized” and never say normalized to what. The animator transition inspector docs do state the basis:

If the Fixed Duration box is not checked, the transition time is interpreted as a fraction of the normalized time of the source state.

That’s a description of inspector transitions, not of the CrossFade API. So reading the API alone, you can’t learn the exact basis.

You don’t need to. Unity ships a seconds-based version separately.

public void CrossFadeInFixedTime(string stateName,
                                 float fixedTransitionDuration,
                                 int layer = -1,
                                 float fixedTimeOffset = 0.0f,
                                 float normalizedTransitionTime = 0.0f);

The duration of the transition (in seconds).

Its class description is “creates a crossfade … using times in seconds.” The three methods side by side:

MethodSecond argumentUnit
Animation.CrossFadefadeLengthSeconds
Animator.CrossFadenormalizedTransitionDurationNormalized
Animator.CrossFadeInFixedTimefixedTransitionDurationSeconds

When you carry a number over from old code or an old post, the match is CrossFadeInFixedTime. That’s the one whose meaning lines up with Animation.CrossFade(name, 0.3f).

Where and why you’d use this

Transitions on an Animator usually run off parameters in the controller graph. So when is CrossFade the right call? When the transition is awkward to draw in the graph. A hit reaction that must interrupt from any state, or a state count high enough that drawing every edge makes the graph unreadable.

Driving a state change from code

using UnityEngine;

/// <summary>
/// Cuts to the hit reaction immediately, from whatever state is playing.
/// </summary>
[RequireComponent(typeof(Animator))]
public class HitReaction : MonoBehaviour
{
    private const float FADE_SECONDS = 0.15f;
    private const int BASE_LAYER = 0;

    // Cache the state hash so the string isn't hashed on every call.
    private static readonly int HIT_STATE = Animator.StringToHash("Base Layer.Hit");

    [Header("Reaction")]
    [SerializeField, Range(0.02f, 1f), Tooltip("Transition time in seconds")]
    private float _fadeSeconds = FADE_SECONDS;

    private Animator _animator;

    private void Awake()
    {
        _animator = GetComponent<Animator>();
    }

    public void Play()
    {
        // The seconds version, not the normalized one.
        // Transition time stays put even when clip lengths change.
        _animator.CrossFadeInFixedTime(HIT_STATE, _fadeSeconds, BASE_LAYER);
    }
}

Three decisions here come from the docs:

If input drives the state change, the lifetime side of that is covered in Subscribing to InputAction Directly.

What the string overload hides

Hash caching is known as a performance matter, but on CrossFade there’s a second place where behavior changes. The two overloads have different defaults.

OverloadnormalizedTimeOffset default
CrossFade(string, ...)float.NegativeInfinity
CrossFade(int, ...)0.0f

Same parameter, different default. Switch from the string version to the hash version for performance, and if you were omitting the third and fourth arguments, the default time-offset behavior changes along with it.

On CrossFadeInFixedTime both overloads use fixedTimeOffset = 0.0f. The seconds version is less hazardous on this count too.

If your code cares about the offset, pass it explicitly rather than omitting it. Then changing overloads changes nothing.

Where not to use it

Summary

What makes an old post dangerous isn’t being wrong — it’s being right about something else. The 0.3f here is still a valid number, and it means something different the moment you copy it across.


References

The source this post started from is 주누다 — [Unity] CrossFade (2014-06-02). Its explanation matched the current docs for Animation.CrossFade; the Animator differences were checked separately against the Scripting Reference and the manual.


Share this post:

Previous Post
There Are Two Clamps: Swap the Arguments and Only One Throws
Next Post
OverlapSphere Doesn't Ignore the Ignore Raycast Layer