# 3. Root Motion Advanced
Part of [[Animation Advanced]]. Prev: [[2.Sync Layers Write Defaults and Interruption]]. Next: [[4.MatchTarget CrossFade Rebind OverrideController]]. Basics: [[4.Layers Masks Root Motion and Events]]. Conflicts: [[Surface-Walking with an Omnidirectional Player Movement]], [[Car Controller]].
**Docs:** [Scripting Root Motion](https://docs.unity3d.com/6000.5/Documentation/Manual/ScriptingRootMotion.html) · [OnAnimatorMove](https://docs.unity3d.com/6000.5/Documentation/ScriptReference/MonoBehaviour.OnAnimatorMove.html)
## 3.1 Authority models
| Mode | Who moves the character |
|---|---|
| Apply Root Motion **On** | Animator applies `deltaPosition` / `deltaRotation` |
| **Handled by Script** | You implement `OnAnimatorMove` (Animator detects the method) |
| Off + in-place clips | Your CharacterController / Rigidbody / custom mover only |
Pick one authority. Mixing CharacterController `.Move` *and* unhandled root motion causes slides and double motion — a common conflict with [[Surface-Walking with an Omnidirectional Player Movement]] and vehicle seats ([[Car Controller]]).
## 3.2 OnAnimatorMove — slopes and control
```csharp
using UnityEngine;
/// <summary>
/// Apply root motion with ground projection and optional external motion.
/// NOT compiled in Unity.
/// </summary>
[RequireComponent(typeof(Animator))]
public sealed class RootMotionApplicator : MonoBehaviour
{
[SerializeField] Animator animator;
[SerializeField] CharacterController controller;
[SerializeField] LayerMask groundMask = ~0;
[SerializeField] float slopeStickForce = 5f;
[SerializeField] bool projectOnGround = true;
Vector3 _external; // moving platform / knockback for this frame
void Awake()
{
if (animator == null) animator = GetComponent<Animator>();
// Presence of OnAnimatorMove => Apply Root Motion shows Handled by Script
}
public void AddExternalMotion(Vector3 worldDelta) => _external += worldDelta;
void OnAnimatorMove()
{
Vector3 delta = animator.deltaPosition + _external;
_external = Vector3.zero;
if (projectOnGround && controller != null)
{
// Simple stick-to-ground: push down a bit when grounded
if (controller.isGrounded)
delta += Vector3.down * slopeStickForce * Time.deltaTime;
}
if (controller != null)
controller.Move(delta);
else
transform.position += delta;
transform.rotation *= animator.deltaRotation;
}
}
```
## 3.3 Moving platforms
Root motion is in **world/actor space as evaluated** — it does not automatically inherit a platform's motion. Patterns:
1. **Parent** the character to the platform (simple; fights some Controllers).
2. **Add platform delta** each FixedUpdate/Update into `_external` (above).
3. Disable root motion on platforms and drive fully from physics (vehicles — [[Car Controller]]).
```csharp
using UnityEngine;
public sealed class PlatformRider : MonoBehaviour
{
[SerializeField] RootMotionApplicator root;
Transform _platform;
Vector3 _lastPlatformPos;
void OnControllerColliderHit(ControllerColliderHit hit)
{
if (!hit.collider.CompareTag("MovingPlatform")) return;
if (_platform != hit.transform)
{
_platform = hit.transform;
_lastPlatformPos = _platform.position;
}
}
void LateUpdate()
{
if (_platform == null || root == null) return;
Vector3 delta = _platform.position - _lastPlatformPos;
_lastPlatformPos = _platform.position;
root.AddExternalMotion(delta);
}
public void ClearPlatform() => _platform = null;
}
```
*(Tag/`OnControllerColliderHit` approach is illustrative — verify against your mover.)*
## 3.4 Slopes and foot slide
- Author clips with appropriate Root Transform rotation / bake options (fundamentals part 4).
- On steep slopes, pure horizontal root deltas climb poorly — project onto ground plane or blend to in-place + mover.
- `animator.gravityWeight` interacts with grounded logic; do not invent values — read current API when tuning.
## 3.5 When to turn root motion off
- Precision platforming with custom acceleration curves ([[Surface-Walking with an Omnidirectional Player Movement]]).
- Networked movement where server owns transform ([[Multiplayer with Netcode for GameObjects]] — separate topic).
- Seated / driving states ([[Car Controller]]).
Use Animator bool `UseRootMotion` driven by the brain, toggled in `OnAnimatorMove` or by setting `animator.applyRootMotion`.
```csharp
public void SetRootMotion(bool on) => animator.applyRootMotion = on;
```
Note: with `OnAnimatorMove` present, Inspector shows Handled by Script — you still decide whether to consume `deltaPosition`.
## Unverified
- Exact interaction of `gravityWeight` with your CharacterController gravity solution.
- Physics-based Rigidbody root motion (use `Rigidbody.MovePosition` carefully; not expanded here).