Replace ceiling Y offset with per-pose CharacterController capsule

Each PlayerPose now carries its own collider center Y, radius and height,
eased and routed to the CharacterController via a SetCapsule single-writer.
This lets crawl poses (Ball/Star) shrink the capsule to fit under furniture.
The ceiling Y offset is removed; poses without authored sizes fall back to
the base capsule, so existing poses keep their collision.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 4.8 committed 2026-06-27 09:55:10 +02:00
1 parent 13de058819
commit 52f9ace9e0
6 files changed
+144 -59

No files matched your search

+26 -16
View File
@@ -127,7 +127,7 @@ public partial class PlayerCharacterController : MonoBehaviour
public float wallExitGrace = 0.15f;
[Tooltip("Head-to-ceiling distance (units) at which climbing a wall up into an overhang hands off to the ceiling.")]
public float ceilingGrabDistance = 0.3f;
public float ceilingGrabDistance = 0.02f;
[Tooltip("Upward hop when topping out over a wall/slope edge, so holding W carries you onto the ledge instead of dropping back onto the face and re-grabbing. In meters of jump height; 0 = no hop.")]
public float wallTopOutBoost = 0.3f;
@@ -185,6 +185,16 @@ public partial class PlayerCharacterController : MonoBehaviour
/// </summary>
public Transform MeshOffsetTarget => meshOffsetTarget;
/// <summary>Authored CharacterController capsule center Y, captured in Awake. The per-pose capsule
/// (driven by <see cref="PlayerPoseController"/>) eases back to this when no pose overrides it.</summary>
public float BaseCapsuleCenterY { get; private set; }
/// <summary>Authored CharacterController capsule radius, captured in Awake (see <see cref="BaseCapsuleCenterY"/>).</summary>
public float BaseCapsuleRadius { get; private set; }
/// <summary>Authored CharacterController capsule height, captured in Awake (see <see cref="BaseCapsuleCenterY"/>).</summary>
public float BaseCapsuleHeight { get; private set; }
// Below this horizontal speed (m/s, squared) the character counts as idle for the run animation.
private const float RunningSpeedThresholdSqr = 0.04f; // (0.2 m/s)^2
@@ -194,7 +204,6 @@ 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 float _poseYOffset; // local-up model offset from the active pose (ceiling only); folded into SetMeshLean
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).
@@ -234,6 +243,9 @@ public partial class PlayerCharacterController : MonoBehaviour
void Awake()
{
_controller = GetComponent<CharacterController>();
BaseCapsuleCenterY = _controller.center.y;
BaseCapsuleRadius = _controller.radius;
BaseCapsuleHeight = _controller.height;
if (!cameraTransform && Camera.main)
cameraTransform = Camera.main.transform;
if (!cameraOrbit)
@@ -720,7 +732,7 @@ public partial class PlayerCharacterController : MonoBehaviour
Vector3 worldOffset = dir * offset;
meshOffsetTarget.localPosition = _meshBaseLocalPos
+ transform.InverseTransformVector(worldOffset)
+ Vector3.up * (_poseYOffset + _groundOffset); // ceiling pose y-offset + ground mouse raise (both 0 unless in use)
+ Vector3.up * _groundOffset; // ground mouse raise (0 unless in use)
}
/// <summary>
@@ -773,21 +785,19 @@ public partial class PlayerCharacterController : MonoBehaviour
}
/// <summary>
/// Sets a local-up offset applied to the visual model only, composed into the wall-lean's single
/// write of <see cref="meshOffsetTarget"/>. Used by <see cref="PlayerPoseController"/> to hang a
/// ceiling pose at the right height; 0 = none. Re-applies immediately so a change shows this frame.
/// Resizes the CharacterController capsule (center Y, radius, height) to the active pose's dimensions,
/// driven by <see cref="PlayerPoseController"/> so a low pose can fit where the default standing capsule
/// is too tall. Only the collision capsule changes — the visual model and camera target are untouched.
/// Cached so an unchanged value skips the write (a resize re-bakes the capsule internally).
/// </summary>
public void SetPoseLocalYOffset(float y)
public void SetCapsule(float centerY, float radius, float height)
{
if (_poseYOffset == y) return;
_poseYOffset = y;
if (!meshOffsetTarget) return;
// While climbing the lean is the live _wallOffset along _wallNormal; otherwise re-apply along
// whatever residual lean is easing home (both 0 when there's none, restoring the authored pose).
if (_isWallClimbing)
SetMeshLean(_wallNormal, _wallOffset);
else
SetMeshLean(_meshOffsetExitNormal, _meshOffsetResidual);
Vector3 c = _controller.center;
if (c.y == centerY && _controller.radius == radius && _controller.height == height) return;
c.y = centerY;
_controller.center = c;
_controller.radius = radius;
_controller.height = height;
}
/// <summary>