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. // ~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 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) 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 // Jump (right A, Boneworks-style hold to charge, release to jump): holding bends the legs — the
// full extension. Takeoff speed is physics — v² = 2·(F/m − g)·stroke — so a crouch before // support goal sinks at ChargeSpeed down to JumpMaxStroke below full extension — and releasing
// the press (real or stick) jumps higher: ~0.45 m standing, ~0.7 m from a deep crouch. // pushes JumpForce over that stroke up to extension. Takeoff speed is physics,
public const float JumpForce = 2.4f; // × body weight // v² = 2·(F/m − g)·stroke: a tap hops, a full charge (or a real/stick crouch) jumps ~0.7 m.
public const float JumpDip = 0.2f; // m the hips drop first when pressed from standing 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 JumpExtension = 0.06f; // m above the standing hips at toe-off
public const float JumpMaxStroke = 0.5f; public const float JumpMaxStroke = 0.5f;
public const float JumpBuffer = 0.15f; // s a press is remembered before touchdown public const float JumpMinStroke = 0.08f; // a tap still hops
public const float CoyoteTime = 0.12f; // s after leaving the ground a jump still works 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 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 BracedAirControl = 150f; // N of horizontal hip carry while airborne but braced on the hands (mantling)
public const float CrawlCarry = 300f; public const float CrawlCarry = 300f;
@@ -197,12 +198,13 @@ public partial class PlayerRagdoll : Node3D
return GetWorld3D().DirectSpaceState.IntersectShape(_legsQuery, 1).Count == 0; return GetWorld3D().DirectSpaceState.IntersectShape(_legsQuery, 1).Count == 0;
} }
private Vector3 _goalVelocity; private Vector3 _goalVelocity;
private enum JumpPhase { None, Dip, Push, Flight } private enum JumpPhase { None, Charge, Push, Flight }
private JumpPhase _jump; 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. // Right A, every tick. Holding while landing starts the charge on touchdown.
public void Jump() => _jumpRequest = JumpBuffer; public bool JumpHeld { set => _jumpHeld = value; }
private float _standingHip; // hips of the standing pose above the feet = the legs' full extension 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(); _arms[i] = new Arm();
Built = false; Built = false;
_jump = JumpPhase.None; _jump = JumpPhase.None;
_jumpRequest = 0f; _jumpHeld = false;
_sinceLanding = float.MaxValue; _sinceLanding = float.MaxValue;
} }
@@ -931,7 +933,7 @@ public partial class PlayerRagdoll : Node3D
// tiptoes, a reach up — would otherwise float the body off the ground). // tiptoes, a reach up — would otherwise float the body off the ground).
goal.Y = Mathf.Min(goal.Y, _footY + _standingHip + JumpExtension); goal.Y = Mathf.Min(goal.Y, _footY + _standingHip + JumpExtension);
_supportGoalY = goal.Y; _supportGoalY = goal.Y;
if (_jump == JumpPhase.Dip) if (_jump == JumpPhase.Charge)
goal.Y = Mathf.Min(goal.Y, _jumpFromY); goal.Y = Mathf.Min(goal.Y, _jumpFromY);
float w = Mathf.Tau * GroundFrequency; float w = Mathf.Tau * GroundFrequency;
Vector3 accel = (goal - pos) * (w * w) + (targetVelocity - Pelvis.LinearVelocity) * (2f * GroundDamping * w); 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 // Jump state: held → Charge (the support goal sinks: the legs bend) → released → Push (full leg
// extension) → Flight (no ground until falling again) → None on touchdown. // force up to extension) → Flight (no ground until falling again) → None on touchdown.
private void UpdateJump(float dt, bool wasGrounded) private void UpdateJump(float dt, bool wasGrounded)
{ {
_airT = Grounded ? 0f : _airT + dt; _airT = Grounded ? 0f : _airT + dt;
_jumpRequest -= dt;
_jumpT += dt; _jumpT += dt;
float pos = Pelvis.GlobalPosition.Y; float pos = Pelvis.GlobalPosition.Y;
float top = _footY + _standingHip + JumpExtension; float top = _footY + _standingHip + JumpExtension;
switch (_jump) switch (_jump)
{ {
case JumpPhase.None: case JumpPhase.None:
if (_jumpRequest > 0f && (Grounded || _airT < CoyoteTime) && !Climbing) if (_jumpHeld && Grounded && !Climbing)
{ {
_jumpRequest = 0f; _jump = JumpPhase.Charge;
_jumpT = 0f; _jumpT = 0f;
// Stroke at least JumpDip + JumpExtension: from standing, dip first. _jumpFromY = Mathf.Min(pos, top);
_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);
} }
break; break;
case JumpPhase.Dip: case JumpPhase.Charge:
if (pos <= _jumpFromY + 0.02f || _jumpT > 0.2f || !Grounded) if (!Grounded && _airT > CoyoteTime || Climbing)
{ {
_jump = JumpPhase.Push; _jump = JumpPhase.None; // walked off or grabbed something while charging
_jumpFromY = pos; break;
_jumpT = 0f;
} }
if (_jumpHeld)
{
_jumpFromY = Mathf.Max(top - JumpMaxStroke, _jumpFromY - ChargeSpeed * dt);
break;
}
_jump = JumpPhase.Push;
_jumpFromY = Mathf.Min(pos, top - JumpMinStroke);
_jumpT = 0f;
break; break;
case JumpPhase.Push: case JumpPhase.Push:
if (pos >= top || pos - _jumpFromY >= JumpMaxStroke || _jumpT > 0.35f) if (pos >= top || pos - _jumpFromY >= JumpMaxStroke || _jumpT > 0.35f)
+3 -7
View File
@@ -5,7 +5,7 @@ using Godot;
// physics tick, in this order: // physics tick, in this order:
// 1. discrete origin jumps made by other code (spawn, teleport markers) carry the body along; // 1. discrete origin jumps made by other code (spawn, teleport markers) carry the body along;
// 2. stick: left = move (head-relative), right X = turn (snap by default, SmoothTurn for smooth), // 2. stick: left = move (head-relative), right X = turn (snap by default, SmoothTurn for smooth),
// right Y = crouch (down lowers, up raises — RagdollDriver.StickCrouch); right A = jump; // right Y = crouch (down lowers, up raises — RagdollDriver.StickCrouch); right A = hold to charge, release to jump;
// 3. the ragdoll's OriginCorrection (push-back at walls, floor follows the ground, falls); // 3. the ragdoll's OriginCorrection (push-back at walls, floor follows the ground, falls);
// 4. True Body solve from the corrected camera/controllers, then the ragdoll's muscles. // 4. True Body solve from the corrected camera/controllers, then the ragdoll's muscles.
// Waits for the calibrated BodyProfile like everything else; rebuilds on recalibration. // Waits for the calibrated BodyProfile like everything else; rebuilds on recalibration.
@@ -36,7 +36,6 @@ public partial class PlayerRig : Node
private bool _haveLastOrigin; private bool _haveLastOrigin;
private Vector3 _moveVelocity; private Vector3 _moveVelocity;
private bool _snapArmed = true; private bool _snapArmed = true;
private bool _jumpPrev;
private readonly MeshInstance3D[] _ghosts = new MeshInstance3D[2]; private readonly MeshInstance3D[] _ghosts = new MeshInstance3D[2];
private bool _rebuild = true; private bool _rebuild = true;
private RagdollDriver _driver; private RagdollDriver _driver;
@@ -231,11 +230,8 @@ public partial class PlayerRig : Node
if (_moveVelocity != Vector3.Zero) if (_moveVelocity != Vector3.Zero)
_origin.GlobalPosition += _moveVelocity * dt; _origin.GlobalPosition += _moveVelocity * dt;
// Jump: right A, on the press. // Jump: hold right A to charge (the legs bend), release to jump.
bool jump = _right.IsButtonPressed("ax_button"); _ragdoll.JumpHeld = _right.IsButtonPressed("ax_button");
if (jump && !_jumpPrev)
_ragdoll.Jump();
_jumpPrev = jump;
// Crouch: right stick held down lowers the body, up raises it back (the stick's dominant // Crouch: right stick held down lowers the body, up raises it back (the stick's dominant
// axis decides between crouch and turn). // axis decides between crouch and turn).
+17 -15
View File
@@ -114,8 +114,9 @@ public partial class RagdollSpike : Node3D
_phases.Add(("climbs a wall hand over hand (grab, pull, regrab higher)", SetupClimbWall, TickClimbWall, 8f, FinishClimbWall)); _phases.Add(("climbs a wall hand over hand (grab, pull, regrab higher)", SetupClimbWall, TickClimbWall, 8f, FinishClimbWall));
_phases.Add(("walks off a 1 m ledge and lands: a dip the view follows, no bounce", () => SetupLedgeTop(1f), TickDrop, 4f, () => FinishDrop(1f))); _phases.Add(("walks off a 1 m ledge and lands: a dip the view follows, no bounce", () => SetupLedgeTop(1f), TickDrop, 4f, () => FinishDrop(1f)));
_phases.Add(("big fall (5 m): legs fold fully, then it recovers", () => SetupLedgeTop(5f), TickDrop, 5f, () => FinishDrop(5f))); _phases.Add(("big fall (5 m): legs fold fully, then it recovers", () => SetupLedgeTop(5f), TickDrop, 5f, () => FinishDrop(5f)));
_phases.Add(("standing jump", Rest, t => TickJump(0f), 4f, () => FinishJump(crouched: false))); _phases.Add(("jump: a tap of A hops", Rest, t => TickJump(0f, 0.05f), 4f, () => FinishJump(0)));
_phases.Add(("jump from a crouch goes higher", Rest, t => TickJump(0.45f), 4.5f, () => FinishJump(crouched: true))); _phases.Add(("jump: holding A to charge jumps much higher", Rest, t => TickJump(0f, 0.6f), 4.5f, () => FinishJump(1)));
_phases.Add(("jump: a tap from a real crouch beats a standing tap", Rest, t => TickJump(0.45f, 0.05f), 4.5f, () => FinishJump(2)));
_phases.Add(("looks down past vertical while crouched (body doesn't flip around)", Rest, TickLookDown, 5f, FinishLookDown)); _phases.Add(("looks down past vertical while crouched (body doesn't flip around)", Rest, TickLookDown, 5f, FinishLookDown));
_phases.Add(("stick crouch lowers the body and the view, and stands back up", Rest, TickStickCrouch, 6f, FinishStickCrouch)); _phases.Add(("stick crouch lowers the body and the view, and stands back up", Rest, TickStickCrouch, 6f, FinishStickCrouch));
_phases.Add(("standing on a seesaw tips it (body weight reaches movable ground)", SetupSeesaw, _ => { }, 3f, FinishSeesaw)); _phases.Add(("standing on a seesaw tips it (body weight reaches movable ground)", SetupSeesaw, _ => { }, 3f, FinishSeesaw));
@@ -783,11 +784,11 @@ public partial class RagdollSpike : Node3D
// ---------------------------------------------------------------- jump // ---------------------------------------------------------------- jump
private float _jumpStand, _jumpApex, _jumpEyeErr, _standJumpApex; private float _jumpStand, _jumpApex, _jumpEyeErr, _tapApex;
private bool _jumpPressed; private bool _jumpPressed;
// Optionally squat (eyes `crouch` lower) first, then press jump at 2 s; the player keeps the pose. // Optionally squat (eyes `crouch` lower) first, then hold A from 2 s for `hold` s; the player keeps the pose.
private void TickJump(float crouch) private void TickJump(float crouch, float hold)
{ {
float k = Mathf.SmoothStep(0f, 1f, Mathf.Clamp((_t - 0.8f) / 0.8f, 0f, 1f)) * (crouch > 0f ? 1f : 0f); float k = Mathf.SmoothStep(0f, 1f, Mathf.Clamp((_t - 0.8f) / 0.8f, 0f, 1f)) * (crouch > 0f ? 1f : 0f);
_headLocal = new Transform3D(new Basis(Vector3.Right, -0.2f * k), new Vector3(0f, EyeHeight - crouch * k, -0.05f * k)); _headLocal = new Transform3D(new Basis(Vector3.Right, -0.2f * k), new Vector3(0f, EyeHeight - crouch * k, -0.05f * k));
@@ -804,11 +805,9 @@ public partial class RagdollSpike : Node3D
_jumpEyeErr = 0f; _jumpEyeErr = 0f;
_jumpPressed = false; _jumpPressed = false;
} }
if (_t >= 2f && !_jumpPressed) _ragdoll.JumpHeld = _t >= 2f && _t < 2f + hold;
{ if (_t >= 2f)
_jumpPressed = true; _jumpPressed = true;
_ragdoll.Jump();
}
if (_jumpPressed) if (_jumpPressed)
{ {
_jumpApex = Mathf.Max(_jumpApex, pelvis - _jumpStand); _jumpApex = Mathf.Max(_jumpApex, pelvis - _jumpStand);
@@ -816,16 +815,19 @@ public partial class RagdollSpike : Node3D
} }
} }
private bool FinishJump(bool crouched) private bool FinishJump(int kind)
{ {
GD.Print($"RAGDOLL: {(crouched ? "crouched" : "standing")} jump: hips rose {_jumpApex:F3} m above the standing stance, eye vs camera max {_jumpEyeErr:F3}, grounded {_ragdoll.Grounded}, jumping {_ragdoll.Jumping}, snaps {NewSnaps}"); string what = kind == 0 ? "standing tap" : kind == 1 ? "full charge" : "tap from a crouch";
GD.Print($"RAGDOLL: {what}: hips rose {_jumpApex:F3} m above the standing stance, eye vs camera max {_jumpEyeErr:F3}, grounded {_ragdoll.Grounded}, jumping {_ragdoll.Jumping}, snaps {NewSnaps}");
bool ok = _finite && NewSnaps == 0 && _ragdoll.Grounded && !_ragdoll.Jumping && _jumpEyeErr < 0.12f && _origin.Origin.Length() < 0.15f; bool ok = _finite && NewSnaps == 0 && _ragdoll.Grounded && !_ragdoll.Jumping && _jumpEyeErr < 0.12f && _origin.Origin.Length() < 0.15f;
if (!crouched) if (kind == 0)
{ {
_standJumpApex = _jumpApex; _tapApex = _jumpApex;
return ok && _jumpApex > 0.3f && _jumpApex < 0.65f; return ok && _jumpApex > 0.03f && _jumpApex < 0.35f;
} }
return ok && _jumpApex > _standJumpApex + 0.1f; if (kind == 1)
return ok && _jumpApex > _tapApex + 0.25f && _jumpApex < 0.9f;
return ok && _jumpApex > _tapApex + 0.1f;
} }
// ---------------------------------------------------------------- looking down past vertical // ---------------------------------------------------------------- looking down past vertical