Smooth free-camera flight and look
FreeCamController: ease movement via dt-correct Vector3.SmoothDamp on a world-space velocity (accel/decel ramp) and lag the look with Mathf.SmoothDampAngle. Both are framerate-independent and collapse to instant at smoothTime 0; smoothed state is snapped on activation (SyncToCurrentView) so entering freecam never jumps to catch up. Bundles accumulated deferred work: PlayerScaler uniform-scale helper, scale-aware wall/ceiling climb probes and the double-scale skinned-collider bake fix, plus user content authoring (Sit pose + animation, font SDF, scene/prefab edits). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
f66ebbf10a
commit
8c89e95014
14 files changed
+12083
-360
No files matched your search
@@ -636,7 +636,7 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
if (!found)
|
||||
{
|
||||
// Wall only: losing the center ray while climbing up the face = top edge → mantle.
|
||||
if (!onCeiling && alongSurface.y > 0f && WallInFront(_controller.height * 0.5f))
|
||||
if (!onCeiling && alongSurface.y > 0f && WallInFront(WorldHalfHeight))
|
||||
{
|
||||
_mantling = true;
|
||||
_mantleClearedFeet = false;
|
||||
@@ -695,8 +695,8 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
// longer moves the capsule (only the visual mesh), so it always rests against the wall.
|
||||
// The controller's depenetration never fights back, so there's no buzz and no quantizing
|
||||
// deadzone is needed. Exponential follow keeps it framerate-stable; clamp guards spikes.
|
||||
const float maxPerpSpeed = 8f;
|
||||
float gapError = _controller.skinWidth - gap;
|
||||
float maxPerpSpeed = 8f * CapsuleScale;
|
||||
float gapError = _controller.skinWidth * CapsuleScale - gap;
|
||||
float follow = 1f - Mathf.Exp(-wallOffsetFollow * dt);
|
||||
velocity += _wallNormal * Mathf.Clamp(gapError * follow / dt, -maxPerpSpeed, maxPerpSpeed);
|
||||
}
|
||||
@@ -877,6 +877,19 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
cameraTarget.localPosition = _cameraTargetBaseLocalPos + Vector3.up * y;
|
||||
}
|
||||
|
||||
// The CharacterController's serialized center/radius/height are authored at the player's base
|
||||
// (scale-1) size; transform.localScale resizes the ACTUAL collision capsule via lossyScale but
|
||||
// leaves those serialized numbers untouched. Every climb probe below raycasts in WORLD space, so
|
||||
// it must use the scaled (world) capsule dimensions and scale its world-distance paddings too --
|
||||
// otherwise the wall grab range, head-to-ceiling gap and mantle distance are all measured at the
|
||||
// authored size on a shrunk/grown player (PlayerScaler scales the tunables but not these probes).
|
||||
// Uniform player scale is enforced by PlayerScaler, so a single lossyScale component suffices.
|
||||
private float CapsuleScale => transform.lossyScale.y;
|
||||
private Vector3 WorldCapsuleCenter => transform.TransformPoint(_controller.center);
|
||||
private float WorldRadius => _controller.radius * CapsuleScale;
|
||||
private float WorldHalfHeight => _controller.height * 0.5f * CapsuleScale;
|
||||
private const float ProbeReachSlack = 0.5f; // extra ray length beyond the capsule surface (scale-1 units)
|
||||
|
||||
/// <summary>
|
||||
/// Raycasts from the character center toward the surface (along -_wallNormal) to find the
|
||||
/// current normal and the gap (surface clearance) to it. The capsule's support distance
|
||||
@@ -888,10 +901,10 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
normal = _wallNormal;
|
||||
gap = 0f;
|
||||
|
||||
Vector3 origin = transform.position + _controller.center;
|
||||
Vector3 origin = WorldCapsuleCenter;
|
||||
Vector3 dir = -_wallNormal;
|
||||
float extent = CapsuleExtent(dir);
|
||||
float maxDist = extent + wallMaxOffset + 0.5f;
|
||||
float maxDist = extent + wallMaxOffset + ProbeReachSlack * CapsuleScale;
|
||||
|
||||
int count = Physics.RaycastNonAlloc(
|
||||
origin, dir, _wallProbe, maxDist, ~0, QueryTriggerInteraction.Ignore);
|
||||
@@ -921,8 +934,8 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
/// </summary>
|
||||
private float CapsuleExtent(Vector3 dir)
|
||||
{
|
||||
float radius = _controller.radius;
|
||||
return radius + Mathf.Abs(dir.y) * (_controller.height * 0.5f - radius);
|
||||
float radius = WorldRadius;
|
||||
return radius + Mathf.Abs(dir.y) * (WorldHalfHeight - radius);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -934,9 +947,9 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
normal = Vector3.down;
|
||||
gap = 0f;
|
||||
|
||||
Vector3 origin = transform.position + _controller.center;
|
||||
float half = _controller.height * 0.5f;
|
||||
float maxDist = half + wallMaxOffset + 0.5f;
|
||||
Vector3 origin = WorldCapsuleCenter;
|
||||
float half = WorldHalfHeight;
|
||||
float maxDist = half + wallMaxOffset + ProbeReachSlack * CapsuleScale;
|
||||
|
||||
int count = Physics.RaycastNonAlloc(
|
||||
origin, Vector3.up, _wallProbe, maxDist, ~0, QueryTriggerInteraction.Ignore);
|
||||
@@ -980,7 +993,7 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
{
|
||||
// Phase 1: climb straight up until no wall is left in front of the feet.
|
||||
_controller.Move(Vector3.up * (speed * dt));
|
||||
if (!WallInFront(_controller.height * 0.5f))
|
||||
if (!WallInFront(WorldHalfHeight))
|
||||
_mantleClearedFeet = true;
|
||||
}
|
||||
else
|
||||
@@ -988,7 +1001,7 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
// Phase 2: move forward onto the top surface, then finish.
|
||||
_controller.Move(forward * (speed * dt));
|
||||
_mantleOverDist += speed * dt;
|
||||
if (_mantleOverDist >= _controller.radius * 2f + 0.15f || _controller.isGrounded)
|
||||
if (_mantleOverDist >= WorldRadius * 2f + 0.15f * CapsuleScale || _controller.isGrounded)
|
||||
{
|
||||
_reentryCooldown = TopOutRegrabCooldown;
|
||||
ExitWallClimb();
|
||||
@@ -1010,8 +1023,8 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
/// </summary>
|
||||
private bool WallInFront(float downOffset)
|
||||
{
|
||||
Vector3 origin = transform.position + _controller.center - Vector3.up * downOffset;
|
||||
float maxDist = _controller.radius + wallMaxOffset + 0.5f;
|
||||
Vector3 origin = WorldCapsuleCenter - Vector3.up * downOffset;
|
||||
float maxDist = WorldRadius + wallMaxOffset + ProbeReachSlack * CapsuleScale;
|
||||
|
||||
int count = Physics.RaycastNonAlloc(
|
||||
origin, -_wallNormal, _wallProbe, maxDist, ~0, QueryTriggerInteraction.Ignore);
|
||||
@@ -1032,8 +1045,8 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
/// </summary>
|
||||
private bool WalkableGroundBelow()
|
||||
{
|
||||
Vector3 origin = transform.position + _controller.center;
|
||||
float maxDist = _controller.center.y + _controller.stepOffset;
|
||||
Vector3 origin = WorldCapsuleCenter;
|
||||
float maxDist = (_controller.center.y + _controller.stepOffset) * CapsuleScale;
|
||||
|
||||
int count = Physics.RaycastNonAlloc(
|
||||
origin, Vector3.down, _wallProbe, maxDist, ~0, QueryTriggerInteraction.Ignore);
|
||||
|
||||
Reference in new issue
Block a user