Add per-pose fall animation state and clip

Play a dedicated falling animation when the player is airborne and
descending past a scale-aware velocity threshold.

- PlayerCharacterController: new IsFalling state + Fall Animation Speed
  threshold (true only when !grounded && !wallClimbing && descending,
  scaled via CapsuleScale); explicitly false during wall/ceiling climb.
- PlayerPose: per-pose Fall Clip + Fall Clip Speed (mirrors walk/run).
- PlayerPoseController: select the fall clip while falling, fall back to
  the idle clip when a pose has none (no regression).
- Author content: Paintman-Fall.anim assigned to Pose_Idle; UMotion
  project, Player prefab and scene authoring.
- Docs: check off the fall animation in GAME_DESIGN.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 4.8 committed 2026-06-27 13:49:16 +02:00
1 parent fc73e26da1
commit 3b26f2e622
10 files changed
+11515 -101

No files matched your search

@@ -71,6 +71,10 @@ public partial class PlayerCharacterController : MonoBehaviour
[Tooltip("Small downward velocity kept while grounded for reliable ground contact.")]
public float groundedStickVelocity = -2f;
[Tooltip("Downward speed (units/second) past which the player counts as falling, for the fall " +
"animation. Only while airborne (not grounded, not wall-climbing). Scales with player size.")]
public float fallAnimationSpeed = 4f;
[Header("Slope Sliding")]
[Tooltip("Slide down surfaces steeper than the CharacterController's Slope Limit instead of balancing on them.")]
public bool slideDownSteepSlopes = true;
@@ -177,6 +181,13 @@ public partial class PlayerCharacterController : MonoBehaviour
/// </summary>
public bool IsMovingOnGround => _movingOnGround;
/// <summary>
/// True while airborne and descending faster than <see cref="fallAnimationSpeed"/> (scaled by player
/// size): not grounded and not wall-climbing. Rising after a jump, standing, moving on the ground, and
/// wall/ceiling states all read false. Exposed so the pose system can pick its fall clip.
/// </summary>
public bool IsFalling => _isFalling;
/// <summary>
/// Set by <see cref="PlayerPoseController"/> while a pose Playable graph owns the Animator output.
/// While set, the run-bool is not poked: it would be moot (the graph overrides it) and Unity would
@@ -211,6 +222,7 @@ public partial class PlayerCharacterController : MonoBehaviour
private bool _sprinting; // sprint key state this frame (exposed via IsSprinting)
private bool _isIdle; // snapshot: player essentially still this frame (exposed via IsIdle)
private bool _movingOnGround; // snapshot: grounded & moving above the run threshold (exposed via IsMovingOnGround)
private bool _isFalling; // snapshot: airborne & descending past the fall threshold (exposed via IsFalling)
private float _groundOffset; // signed vertical mesh raise from the ground mouse pose-adjust (+ up / - down); folded into SetMeshLean
// Ground contact normal from the latest Move (for slope sliding).
@@ -367,6 +379,7 @@ public partial class PlayerCharacterController : MonoBehaviour
_horizontalVelocity = Vector3.zero; // drop ground momentum so resuming starts from rest
_isIdle = ControlEnabled && (h * h + v * v) <= 1e-4f; // attached & no climb input = holding a pose
_movingOnGround = false; // on a wall/ceiling, never ground locomotion
_isFalling = false; // attached to a surface, not falling
return;
}
@@ -453,6 +466,9 @@ public partial class PlayerCharacterController : MonoBehaviour
bool movingOnGround = _controller.isGrounded && horizontalVel.sqrMagnitude > RunningSpeedThresholdSqr;
_movingOnGround = movingOnGround;
_isIdle = ControlEnabled && _controller.isGrounded && !movingOnGround; // standing still on the floor
// Falling: airborne and descending past the threshold (wall-climb already returned above, so
// not-grounded here means free in the air). Scaled by player size like gravity/jump.
_isFalling = !_controller.isGrounded && _verticalVelocity < -fallAnimationSpeed * CapsuleScale;
}
/// <summary>