From 0bddcf4517cea98388fe89b30a31b7d6c51ddb66 Mon Sep 17 00:00:00 2001 From: Foxyz Date: Fri, 2 Oct 2026 20:15:32 +0300 Subject: [PATCH] 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 --- scripts/PlayerRagdoll.cs | 59 ++++++++++++++++++++++------------------ scripts/PlayerRig.cs | 10 ++----- scripts/RagdollSpike.cs | 32 ++++++++++++---------- 3 files changed, 52 insertions(+), 49 deletions(-) diff --git a/scripts/PlayerRagdoll.cs b/scripts/PlayerRagdoll.cs index 8925ed0..142a236 100644 --- a/scripts/PlayerRagdoll.cs +++ b/scripts/PlayerRagdoll.cs @@ -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) diff --git a/scripts/PlayerRig.cs b/scripts/PlayerRig.cs index e26b0c6..d6292e2 100644 --- a/scripts/PlayerRig.cs +++ b/scripts/PlayerRig.cs @@ -5,7 +5,7 @@ using Godot; // physics tick, in this order: // 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), -// 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); // 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. @@ -36,7 +36,6 @@ public partial class PlayerRig : Node private bool _haveLastOrigin; private Vector3 _moveVelocity; private bool _snapArmed = true; - private bool _jumpPrev; private readonly MeshInstance3D[] _ghosts = new MeshInstance3D[2]; private bool _rebuild = true; private RagdollDriver _driver; @@ -231,11 +230,8 @@ public partial class PlayerRig : Node if (_moveVelocity != Vector3.Zero) _origin.GlobalPosition += _moveVelocity * dt; - // Jump: right A, on the press. - bool jump = _right.IsButtonPressed("ax_button"); - if (jump && !_jumpPrev) - _ragdoll.Jump(); - _jumpPrev = jump; + // Jump: hold right A to charge (the legs bend), release to jump. + _ragdoll.JumpHeld = _right.IsButtonPressed("ax_button"); // Crouch: right stick held down lowers the body, up raises it back (the stick's dominant // axis decides between crouch and turn). diff --git a/scripts/RagdollSpike.cs b/scripts/RagdollSpike.cs index 3f20372..7ff9be7 100644 --- a/scripts/RagdollSpike.cs +++ b/scripts/RagdollSpike.cs @@ -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(("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(("standing jump", Rest, t => TickJump(0f), 4f, () => FinishJump(crouched: false))); - _phases.Add(("jump from a crouch goes higher", Rest, t => TickJump(0.45f), 4.5f, () => FinishJump(crouched: true))); + _phases.Add(("jump: a tap of A hops", Rest, t => TickJump(0f, 0.05f), 4f, () => FinishJump(0))); + _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(("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)); @@ -783,11 +784,11 @@ public partial class RagdollSpike : Node3D // ---------------------------------------------------------------- jump - private float _jumpStand, _jumpApex, _jumpEyeErr, _standJumpApex; + private float _jumpStand, _jumpApex, _jumpEyeErr, _tapApex; private bool _jumpPressed; - // Optionally squat (eyes `crouch` lower) first, then press jump at 2 s; the player keeps the pose. - private void TickJump(float crouch) + // 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, float hold) { 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)); @@ -804,11 +805,9 @@ public partial class RagdollSpike : Node3D _jumpEyeErr = 0f; _jumpPressed = false; } - if (_t >= 2f && !_jumpPressed) - { + _ragdoll.JumpHeld = _t >= 2f && _t < 2f + hold; + if (_t >= 2f) _jumpPressed = true; - _ragdoll.Jump(); - } if (_jumpPressed) { _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; - if (!crouched) + if (kind == 0) { - _standJumpApex = _jumpApex; - return ok && _jumpApex > 0.3f && _jumpApex < 0.65f; + _tapApex = _jumpApex; + 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