Add pose-switching system with continuous hold and sprint reset
New PlayerPose ScriptableObject (AnimationClip + ceiling Y-offset) and PlayerPoseController that drives poses via a PlayableGraph, overriding the Animator controller. Poses are held continuously (idle, moving, on wall/ ceiling) so cycle and sprint changes show immediately. Sprinting in any state resets to pose 0. Non-keyed bones fall back to a captured rest pose so empty/partial clips apply fully. Ceiling Y-offset is folded into the controller's single mesh-offset writer. PlayerCharacterController gains IsIdle / IsSprinting / IsOnCeiling / AnimatorOverridden accessors and SetPoseLocalYOffset; SetAnimatorRunning is guarded while a pose owns the Animator. Removed obsolete Paintman.controller. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
d867c617df
commit
c41fb398e4
18 files changed
+649
-465
No files matched your search
@@ -147,6 +147,26 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
/// <summary>True while in wall-climb mode.</summary>
|
||||
public bool IsWallClimbing => _isWallClimbing;
|
||||
|
||||
/// <summary>
|
||||
/// True while the player is idle (under control and essentially still): standing on the floor
|
||||
/// below the run threshold, or attached to a wall/ceiling with no climb input. Snapshotted at the
|
||||
/// end of Update so a sibling reads a stable, order-independent value.
|
||||
/// </summary>
|
||||
public bool IsIdle => _isIdle;
|
||||
|
||||
/// <summary>True while the sprint key is held, in any state (ground, air, wall, or ceiling).</summary>
|
||||
public bool IsSprinting => _sprinting;
|
||||
|
||||
/// <summary>True while stuck to a ceiling (wall-climb mode with a downward-facing surface normal).</summary>
|
||||
public bool IsOnCeiling => _isWallClimbing && _wallNormal.y < CeilingNormalThreshold;
|
||||
|
||||
/// <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
|
||||
/// log "Animator is not playing an AnimatorController" every frame.
|
||||
/// </summary>
|
||||
public bool AnimatorOverridden { get; 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
|
||||
|
||||
@@ -154,6 +174,9 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
private int _runningHash;
|
||||
private float _verticalVelocity;
|
||||
private Vector3 _horizontalVelocity; // smoothed horizontal move velocity (accel/decel ramp)
|
||||
private bool _sprinting; // sprint key state this frame (exposed via IsSprinting)
|
||||
private bool _isIdle; // snapshot: player essentially still this frame (exposed via IsIdle)
|
||||
private float _poseYOffset; // local-up model offset from the active pose (ceiling only); folded into SetMeshLean
|
||||
|
||||
// Ground contact normal from the latest Move (for slope sliding).
|
||||
private Vector3 _groundNormal = Vector3.up;
|
||||
@@ -241,6 +264,8 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
#endif
|
||||
}
|
||||
|
||||
_sprinting = sprinting;
|
||||
|
||||
Vector3 inputDir = new Vector3(h, 0f, v);
|
||||
if (inputDir.sqrMagnitude > 1f)
|
||||
inputDir.Normalize();
|
||||
@@ -282,6 +307,7 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
if (cameraOrbit)
|
||||
cameraOrbit.LookSuppressed = _offsetDragging;
|
||||
_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
|
||||
SetAnimatorRunning(false); // climbing is its own pose, not the ground run
|
||||
return;
|
||||
}
|
||||
@@ -350,7 +376,9 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
// Real horizontal speed, so pushing into a wall (blocked) reads as idle, not foot-sliding.
|
||||
Vector3 horizontalVel = _controller.velocity;
|
||||
horizontalVel.y = 0f;
|
||||
SetAnimatorRunning(_controller.isGrounded && horizontalVel.sqrMagnitude > RunningSpeedThresholdSqr);
|
||||
bool movingOnGround = _controller.isGrounded && horizontalVel.sqrMagnitude > RunningSpeedThresholdSqr;
|
||||
SetAnimatorRunning(movingOnGround);
|
||||
_isIdle = ControlEnabled && _controller.isGrounded && !movingOnGround; // standing still on the floor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -642,7 +670,9 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
{
|
||||
if (!meshOffsetTarget) return;
|
||||
Vector3 worldOffset = dir * offset;
|
||||
meshOffsetTarget.localPosition = _meshBaseLocalPos + transform.InverseTransformVector(worldOffset);
|
||||
meshOffsetTarget.localPosition = _meshBaseLocalPos
|
||||
+ transform.InverseTransformVector(worldOffset)
|
||||
+ Vector3.up * _poseYOffset; // active pose's ceiling y-offset (0 except while hanging on a ceiling)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -657,6 +687,24 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
SetMeshLean(_meshOffsetExitNormal, _meshOffsetResidual);
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
public void SetPoseLocalYOffset(float y)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <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
|
||||
@@ -897,10 +945,10 @@ public partial class PlayerCharacterController : MonoBehaviour
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Drives the animator's ground-run bool (null-safe if no animator is assigned).</summary>
|
||||
/// <summary>Drives the animator's ground-run bool (null-safe; skipped while a pose graph overrides the Animator).</summary>
|
||||
private void SetAnimatorRunning(bool running)
|
||||
{
|
||||
if (animator)
|
||||
if (animator && !AnimatorOverridden)
|
||||
animator.SetBool(_runningHash, running);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user