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:
1 parent
fc73e26da1
commit
3b26f2e622
10 files changed
+11515
-101
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 460c2fe161ff3a6449648312c4e14eff
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large.
Load diff
@@ -18,4 +18,10 @@ MonoBehaviour:
|
||||
walkClipSpeed: 1.4
|
||||
runClip: {fileID: -203655887218126122, guid: a4d4588e12cac6f48a17583c08a100d2, type: 3}
|
||||
runClipSpeed: 1.5
|
||||
ceilingYOffset: 0
|
||||
fallClip: {fileID: 7400000, guid: 460c2fe161ff3a6449648312c4e14eff, type: 2}
|
||||
fallClipSpeed: 1
|
||||
controllerCenterY: 0.66
|
||||
controllerRadius: 0.1
|
||||
controllerHeight: 1.3
|
||||
cameraTargetYOffset: 0
|
||||
firstPersonCameraTargetYOffset: 0
|
||||
@@ -120,6 +120,7 @@ MonoBehaviour:
|
||||
jumpHeight: 1.2
|
||||
gravity: -14.715
|
||||
groundedStickVelocity: -2
|
||||
fallAnimationSpeed: 0.1
|
||||
slideDownSteepSlopes: 1
|
||||
slideAcceleration: 20
|
||||
maxSlideSpeed: 8
|
||||
|
||||
@@ -7834,14 +7834,6 @@ PrefabInstance:
|
||||
propertyPath: m_Name
|
||||
value: Player
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3694788198465753627, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
|
||||
propertyPath: m_IsActive
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8001664246499114791, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
|
||||
propertyPath: m_Enabled
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -38,6 +38,14 @@ public class PlayerPose : ScriptableObject
|
||||
[Range(0.1f, 3f)]
|
||||
[SerializeField] private float runClipSpeed = 1f;
|
||||
|
||||
[Tooltip("Optional. Played instead of the idle clip while falling (airborne and descending past the " +
|
||||
"controller's fall threshold). Leave empty to keep holding the idle pose while falling. Should loop.")]
|
||||
[SerializeField] private AnimationClip fallClip;
|
||||
|
||||
[Tooltip("Playback speed multiplier for the fall clip. 1 = authored speed.")]
|
||||
[Range(0.1f, 3f)]
|
||||
[SerializeField] private float fallClipSpeed = 1f;
|
||||
|
||||
[Header("Collision")]
|
||||
[Tooltip("CharacterController capsule center Y while this pose is active (meters, local space). Lower " +
|
||||
"this together with Height so a short pose still rests its feet on the floor.")]
|
||||
@@ -82,6 +90,12 @@ public class PlayerPose : ScriptableObject
|
||||
/// <summary>Playback speed multiplier for the run clip (1 = authored speed).</summary>
|
||||
public float RunClipSpeed => runClipSpeed;
|
||||
|
||||
/// <summary>Optional clip played while falling; null = hold the idle pose.</summary>
|
||||
public AnimationClip FallClip => fallClip;
|
||||
|
||||
/// <summary>Playback speed multiplier for the fall clip (1 = authored speed).</summary>
|
||||
public float FallClipSpeed => fallClipSpeed;
|
||||
|
||||
/// <summary>CharacterController capsule center Y applied while this pose is active.</summary>
|
||||
public float ControllerCenterY => controllerCenterY;
|
||||
|
||||
|
||||
@@ -147,11 +147,12 @@ public partial class PlayerPoseController : MonoBehaviour
|
||||
|
||||
PlayerPose pose = ActivePose();
|
||||
|
||||
// Pick the clip for the current locomotion state. Standing still, airborne, on a wall or on the
|
||||
// ceiling holds the idle pose; moving on the ground plays the run clip while sprinting or the
|
||||
// walk clip otherwise. Both locomotion clips are optional — a null one falls back to the idle
|
||||
// pose, so an unset walk clip keeps the held-pose behaviour. PlayPose crossfades on any change
|
||||
// and hands the rig back only when there is no clip at all (empty list / null idle clip).
|
||||
// Pick the clip for the current locomotion state. Standing still, on a wall or on the ceiling
|
||||
// holds the idle pose; moving on the ground plays the run clip while sprinting or the walk clip
|
||||
// otherwise; falling (airborne and descending past the controller's threshold) plays the fall
|
||||
// clip. All three extra clips are optional — a null one falls back to the idle pose, so an unset
|
||||
// clip keeps the held-pose behaviour. PlayPose crossfades on any change and hands the rig back
|
||||
// only when there is no clip at all (empty list / null idle clip).
|
||||
AnimationClip clip = pose ? pose.Clip : null;
|
||||
float speed = pose ? pose.ClipSpeed : 1f;
|
||||
if (pose && _player.IsMovingOnGround)
|
||||
@@ -163,6 +164,11 @@ public partial class PlayerPoseController : MonoBehaviour
|
||||
speed = _player.IsSprinting ? pose.RunClipSpeed : pose.WalkClipSpeed;
|
||||
}
|
||||
}
|
||||
else if (pose && _player.IsFalling && pose.FallClip)
|
||||
{
|
||||
clip = pose.FallClip;
|
||||
speed = pose.FallClipSpeed;
|
||||
}
|
||||
PlayPose(clip, speed);
|
||||
|
||||
// CharacterController capsule: each pose carries its own center Y / radius / height so a low pose
|
||||
|
||||
Reference in new issue
Block a user