feat(movement): spacebar charged jump with skill-based height
Hold spacebar to charge (0→1 over 1s), release to jump. Height from GetJumpHeight formula using Jump skill via PlayerWeenie. Jump physics use MotionInterpreter.jump() → LeaveGround() → get_leave_ground_velocity(). JumpExtent is returned in MovementResult (non-null when jump fires this frame) so GameWindow can log and eventually send the server jump packet. Double-jump is prevented by jump_is_allowed() checking Contact+OnWalkable flags before allowing another jump. Tests updated to use charge-then-release pattern matching the new input model. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5cb14da714
commit
0bec5d5296
3 changed files with 46 additions and 10 deletions
|
|
@ -31,7 +31,8 @@ public readonly record struct MovementResult(
|
|||
uint? TurnCommand,
|
||||
float? ForwardSpeed,
|
||||
float? SidestepSpeed,
|
||||
float? TurnSpeed);
|
||||
float? TurnSpeed,
|
||||
float? JumpExtent = null); // non-null when a jump was triggered this frame
|
||||
|
||||
/// <summary>
|
||||
/// Portal-space state for the player movement controller.
|
||||
|
|
@ -94,6 +95,11 @@ public sealed class PlayerMovementController
|
|||
/// </summary>
|
||||
public float VerticalVelocity => _body.Velocity.Z;
|
||||
|
||||
// Jump charge state.
|
||||
private bool _jumpCharging;
|
||||
private float _jumpExtent;
|
||||
private const float JumpChargeRate = 1.0f; // 0→1 over 1 second
|
||||
|
||||
// Previous frame's motion commands for change detection.
|
||||
private uint? _prevForwardCmd;
|
||||
private uint? _prevSidestepCmd;
|
||||
|
|
@ -246,15 +252,33 @@ public sealed class PlayerMovementController
|
|||
// Rotation about Z does not affect the Z component, so world Vz == local Vz.
|
||||
_body.set_local_velocity(new Vector3(localX, localY, savedWorldVz));
|
||||
|
||||
// ── 3. Jump ───────────────────────────────────────────────────────────
|
||||
if (input.Jump)
|
||||
// ── 3. Jump (charged) ─────────────────────────────────────────────────
|
||||
// Hold spacebar to charge (0→1 over JumpChargeRate seconds).
|
||||
// Release to execute: jump(extent) validates + sets JumpExtent,
|
||||
// then LeaveGround() applies the scaled velocity via get_leave_ground_velocity.
|
||||
float? outJumpExtent = null;
|
||||
|
||||
if (input.Jump && _body.OnWalkable)
|
||||
{
|
||||
var jumpResult = _motion.jump(1.0f);
|
||||
// Spacebar held and on the ground — accumulate charge.
|
||||
if (!_jumpCharging)
|
||||
{
|
||||
_jumpCharging = true;
|
||||
_jumpExtent = 0f;
|
||||
}
|
||||
_jumpExtent = MathF.Min(_jumpExtent + dt * JumpChargeRate, 1.0f);
|
||||
}
|
||||
else if (_jumpCharging)
|
||||
{
|
||||
// Spacebar released (or left ground during charge) — fire jump.
|
||||
var jumpResult = _motion.jump(_jumpExtent);
|
||||
if (jumpResult == WeenieError.None)
|
||||
{
|
||||
// jump() set_on_walkable(false); now apply the launch velocity.
|
||||
_motion.LeaveGround();
|
||||
outJumpExtent = _jumpExtent;
|
||||
}
|
||||
_jumpCharging = false;
|
||||
_jumpExtent = 0f;
|
||||
}
|
||||
|
||||
// ── 4. Integrate physics (gravity, friction, sub-stepping) ────────────
|
||||
|
|
@ -384,6 +408,7 @@ public sealed class PlayerMovementController
|
|||
TurnCommand: outTurnCmd,
|
||||
ForwardSpeed: outForwardSpeed,
|
||||
SidestepSpeed: outSidestepSpeed,
|
||||
TurnSpeed: outTurnSpeed);
|
||||
TurnSpeed: outTurnSpeed,
|
||||
JumpExtent: outJumpExtent);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1912,6 +1912,13 @@ public sealed class GameWindow : IDisposable
|
|||
forcePositionSequence: _liveSession.ForcePositionSequence);
|
||||
_liveSession.SendGameAction(body);
|
||||
}
|
||||
|
||||
if (result.JumpExtent.HasValue)
|
||||
{
|
||||
// TODO: send jump packet to server (format needs research from holtburger).
|
||||
// Local jump physics work without server acknowledgment for now.
|
||||
Console.WriteLine($"jump: extent={result.JumpExtent.Value:F2}");
|
||||
}
|
||||
}
|
||||
|
||||
// Update the player entity's animation cycle to match current motion.
|
||||
|
|
|
|||
|
|
@ -108,8 +108,11 @@ public class PlayerMovementControllerTests
|
|||
var controller = new PlayerMovementController(engine);
|
||||
controller.SetPosition(new Vector3(96f, 96f, 50f), 0x0001);
|
||||
|
||||
var input = new MovementInput(Jump: true);
|
||||
controller.Update(0.016f, input);
|
||||
// Charged jump: hold for a full charge (1s dt), then release to fire.
|
||||
// A full charge gives enough Vz that the player clears the 0.05-unit
|
||||
// ground-snap threshold within the same integration frame.
|
||||
controller.Update(1.0f, new MovementInput(Jump: true)); // full charge
|
||||
controller.Update(0.016f, new MovementInput(Jump: false)); // release → jump fires
|
||||
|
||||
Assert.True(controller.IsAirborne);
|
||||
Assert.True(controller.VerticalVelocity > 0f);
|
||||
|
|
@ -122,8 +125,9 @@ public class PlayerMovementControllerTests
|
|||
var controller = new PlayerMovementController(engine);
|
||||
controller.SetPosition(new Vector3(96f, 96f, 50f), 0x0001);
|
||||
|
||||
// Jump
|
||||
controller.Update(0.016f, new MovementInput(Jump: true));
|
||||
// Charged jump: hold for a full charge, then release.
|
||||
controller.Update(1.0f, new MovementInput(Jump: true)); // full charge
|
||||
controller.Update(0.016f, new MovementInput(Jump: false)); // release → jump fires
|
||||
float z1 = controller.Position.Z;
|
||||
|
||||
// A few frames of rising
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue