Jump: hold right A to charge (legs bend), release to jump

Tap hops ~0.16 m, full charge ~0.68 m, a real crouch charges too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
FoxyzandClaude Opus 5.5 committed 2026-10-02 20:15:32 +03:00
1 parent 537e72c207
commit 0bddcf4517
3 files changed
+52 -49

No files matched your search

+32 -27
View File
@@ -64,15 +64,16 @@ public partial class PlayerRagdoll : Node3D
// ~0.3 m from 1 m, legs fully folded from a big fall (Boneworks) — then the normal rise.
public const float LandingReach = 0.05f; // m past the pose's hip height
public const float AbsorbTime = 0.4f; // s after a hard touchdown the head guide stays off (it would hold the dip up)
// Jump (right A): a short countermovement dip, then the legs push JumpForce over the stroke up to
// full extension. Takeoff speed is physics — v² = 2·(F/m − g)·stroke — so a crouch before
// the press (real or stick) jumps higher: ~0.45 m standing, ~0.7 m from a deep crouch.
public const float JumpForce = 2.4f; // × body weight
public const float JumpDip = 0.2f; // m the hips drop first when pressed from standing
// Jump (right A, Boneworks-style hold to charge, release to jump): holding bends the legs — the
// support goal sinks at ChargeSpeed down to JumpMaxStroke below full extension — and releasing
// pushes JumpForce over that stroke up to extension. Takeoff speed is physics,
// v² = 2·(F/m − g)·stroke: a tap hops, a full charge (or a real/stick crouch) jumps ~0.7 m.
public const float JumpForce = 2.2f; // × body weight
public const float ChargeSpeed = 1.2f; // m/s the hips sink while A is held
public const float JumpExtension = 0.06f; // m above the standing hips at toe-off
public const float JumpMaxStroke = 0.5f;
public const float JumpBuffer = 0.15f; // s a press is remembered before touchdown
public const float CoyoteTime = 0.12f; // s after leaving the ground a jump still works
public const float JumpMinStroke = 0.08f; // a tap still hops
public const float CoyoteTime = 0.12f; // s after leaving the ground a release still jumps
public const float MaxSlopeDegrees = 35f; // steeper ground gives no support (Nimso: "slopes beyond an angle can't be run up") // higher ground under the hips isn't stood on unless the body got up there
public const float BracedAirControl = 150f; // N of horizontal hip carry while airborne but braced on the hands (mantling)
public const float CrawlCarry = 300f;
@@ -197,12 +198,13 @@ public partial class PlayerRagdoll : Node3D
return GetWorld3D().DirectSpaceState.IntersectShape(_legsQuery, 1).Count == 0;
}
private Vector3 _goalVelocity;
private enum JumpPhase { None, Dip, Push, Flight }
private enum JumpPhase { None, Charge, Push, Flight }
private JumpPhase _jump;
private float _jumpT, _jumpRequest, _airT, _jumpFromY, _sinceLanding = float.MaxValue;
private float _jumpT, _airT, _jumpFromY, _sinceLanding = float.MaxValue;
private bool _jumpHeld;
// Right A. Buffered, so a press just before touchdown still jumps.
public void Jump() => _jumpRequest = JumpBuffer;
// Right A, every tick. Holding while landing starts the charge on touchdown.
public bool JumpHeld { set => _jumpHeld = value; }
private float _standingHip; // hips of the standing pose above the feet = the legs' full extension
@@ -349,7 +351,7 @@ public partial class PlayerRagdoll : Node3D
_arms[i] = new Arm();
Built = false;
_jump = JumpPhase.None;
_jumpRequest = 0f;
_jumpHeld = false;
_sinceLanding = float.MaxValue;
}
@@ -931,7 +933,7 @@ public partial class PlayerRagdoll : Node3D
// tiptoes, a reach up — would otherwise float the body off the ground).
goal.Y = Mathf.Min(goal.Y, _footY + _standingHip + JumpExtension);
_supportGoalY = goal.Y;
if (_jump == JumpPhase.Dip)
if (_jump == JumpPhase.Charge)
goal.Y = Mathf.Min(goal.Y, _jumpFromY);
float w = Mathf.Tau * GroundFrequency;
Vector3 accel = (goal - pos) * (w * w) + (targetVelocity - Pelvis.LinearVelocity) * (2f * GroundDamping * w);
@@ -984,35 +986,38 @@ public partial class PlayerRagdoll : Node3D
}
}
// Jump state: request → Dip (countermovement, the support goal drops) → Push (full leg force up to
// extension) → Flight (no ground until falling again) → None on touchdown.
// Jump state: held → Charge (the support goal sinks: the legs bend) → released → Push (full leg
// force up to extension) → Flight (no ground until falling again) → None on touchdown.
private void UpdateJump(float dt, bool wasGrounded)
{
_airT = Grounded ? 0f : _airT + dt;
_jumpRequest -= dt;
_jumpT += dt;
float pos = Pelvis.GlobalPosition.Y;
float top = _footY + _standingHip + JumpExtension;
switch (_jump)
{
case JumpPhase.None:
if (_jumpRequest > 0f && (Grounded || _airT < CoyoteTime) && !Climbing)
if (_jumpHeld && Grounded && !Climbing)
{
_jumpRequest = 0f;
_jump = JumpPhase.Charge;
_jumpT = 0f;
// Stroke at least JumpDip + JumpExtension: from standing, dip first.
_jumpFromY = Mathf.Max(top - JumpMaxStroke, Mathf.Min(pos, top - JumpDip - JumpExtension));
_jump = pos > _jumpFromY + 0.02f && Grounded ? JumpPhase.Dip : JumpPhase.Push;
_jumpFromY = Mathf.Min(_jumpFromY, pos);
_jumpFromY = Mathf.Min(pos, top);
}
break;
case JumpPhase.Dip:
if (pos <= _jumpFromY + 0.02f || _jumpT > 0.2f || !Grounded)
case JumpPhase.Charge:
if (!Grounded && _airT > CoyoteTime || Climbing)
{
_jump = JumpPhase.Push;
_jumpFromY = pos;
_jumpT = 0f;
_jump = JumpPhase.None; // walked off or grabbed something while charging
break;
}
if (_jumpHeld)
{
_jumpFromY = Mathf.Max(top - JumpMaxStroke, _jumpFromY - ChargeSpeed * dt);
break;
}
_jump = JumpPhase.Push;
_jumpFromY = Mathf.Min(pos, top - JumpMinStroke);
_jumpT = 0f;
break;
case JumpPhase.Push:
if (pos >= top || pos - _jumpFromY >= JumpMaxStroke || _jumpT > 0.35f)