Quick answer: Animator.SetFloat modifies runtime playable state, not the controller asset. It is never serialized. To change prefab defaults, edit the AnimatorController asset (set parameters[i].defaultFloat) in an editor script or in the Parameters panel directly.
Here is how to fix Unity Animator parameter values that will not stick. You call animator.SetFloat("Speed", 2.5f), play the scene, see it working, save, exit Play Mode — and the parameter resets to 1.0 every time you replay. You check the prefab, the AnimatorController, the scene file. Nothing is dirty. The change is gone because SetFloat never writes to disk.
The Symptom
- SetFloat/SetBool/SetInt changes revert on scene reload
- Parameters in the Animator Controller window always show defaults
- Prefab spawns with same initial parameter values no matter what
- Editor script that modifies parameters at runtime does not persist changes
- Recording in Animation window captures property animation but not parameter values
What Causes This
Runtime state lives in a playable graph. When an Animator initializes, it builds an AnimatorControllerPlayable from the bound controller asset. Parameters are stored in the playable’s memory. SetFloat modifies that memory. The controller asset file on disk is never written to at runtime.
Unity design choice. This is intentional — controllers are shared assets and a game instance should not mutate them. If SetFloat wrote to the asset, every Animator instance would mutate the shared asset and cause chaos. Runtime-only parameters are the correct design.
Parameter defaults are in the asset. The AnimatorController asset has a parameters array with defaultFloat, defaultBool, defaultInt fields per entry. These are the values used to initialize each new Animator. Changing these in an editor script dirties the asset and saves.
AnimatorOverrideController exists for variations. If you need different defaults per prefab, an AnimatorOverrideController wraps a base controller and overrides specific clips. Parameters are inherited from the base, so overrides do not change defaults — but you can build variants via this mechanism plus per-prefab initialization scripts.
The Fix
Step 1: Know what you are trying to change. If you want the runtime value of a parameter, SetFloat is correct — just set it every time the Animator starts:
using UnityEngine;
public class AnimatorInit : MonoBehaviour
{
[SerializeField] private float initialSpeed = 2.5f;
void Start()
{
var anim = GetComponent<Animator>();
anim.SetFloat("Speed", initialSpeed);
}
}
This is the standard pattern: store per-prefab initial values in a serialized field on a component, apply them to the Animator on Start. The component is serialized per-instance and per-prefab normally.
Step 2: Change controller defaults via editor script. If you really need the default value on the AnimatorController asset to change:
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
public static class AnimParamEditor
{
[MenuItem("Tools/Set Speed Default")]
public static void SetDefault()
{
var ctrl = Selection.activeObject as AnimatorController;
if (ctrl == null) return;
var parms = ctrl.parameters;
for (int i = 0; i < parms.Length; i++)
{
if (parms[i].name == "Speed")
parms[i].defaultFloat = 2.5f;
}
ctrl.parameters = parms; // assignment triggers dirty
EditorUtility.SetDirty(ctrl);
AssetDatabase.SaveAssets();
}
}
You must reassign ctrl.parameters = parms after modification — writing to individual array elements does not notify Unity. Then SetDirty and SaveAssets flush to disk.
Step 3: Use AnimatorOverrideController for clip variants. If different prefabs use the same state machine but different clips, do not duplicate the controller. Create an AnimatorOverrideController (Assets > Create > Animator Override Controller), drag the base controller in, then override specific clips.
[SerializeField] private AnimatorOverrideController overrideController;
void Start()
{
GetComponent<Animator>().runtimeAnimatorController = overrideController;
}
Step 4: Disable Write Defaults on state machines. A separate issue often confused with this one: if WriteDefaults is enabled on a state, that state writes default values for properties it does not animate when transitioning in. Parameter-driven logic can see values flicker back to defaults unexpectedly.
Open the AnimatorController, select each state, uncheck Write Defaults in the Inspector. Do this for every state in the controller (Unity warns if some are on and others off). All animations then need to explicitly animate any property they want to control — safer behavior.
Detecting Parameter Changes
During play mode, the Animator window shows live parameter values. You can watch them change as SetFloat calls land. If you see the value change then immediately reset, a state transition with WriteDefaults or another SetFloat elsewhere is clobbering it.
void Update()
{
float cur = anim.GetFloat("Speed");
if (cur != lastSpeed)
{
Debug.Log($"Speed changed: {lastSpeed} -> {cur}");
lastSpeed = cur;
}
}
Prefab Initial Values Pattern
The clean pattern for per-prefab parameter defaults:
using UnityEngine;
[RequireComponent(typeof(Animator))]
public class AnimatorParameterDefaults : MonoBehaviour
{
[System.Serializable]
public struct FloatParam { public string name; public float value; }
[SerializeField] private FloatParam[] floats;
void Start()
{
var anim = GetComponent<Animator>();
foreach (var p in floats)
anim.SetFloat(p.name, p.value);
}
}
The array is serialized per-instance and per-prefab, so different prefabs or scene instances carry different initial values. SetFloat applies them at runtime.
“SetFloat is runtime. defaultFloat is asset. Know which one you actually want — usually runtime is correct.”
See Unity Animation Not Playing for related animator debugging.
SetFloat = runtime only. Initialize on Start. Edit defaults via AnimatorController.parameters in editor scripts.