Body step 2.6: landing dip, jump, stick crouch, no flip looking down, smooth view
- Look-down flip: body facing from BodyMath.HeadFacing (visor + crown), robust past vertical; stick direction uses it too; crouch lean pitch measured against the facing. - Landing: falling legs touch down only within their reach, so leg strength stops the body below its stance (dip scales with impact, big falls fold the legs). Rise eased so gravity can stop it at the stance; legs untuck speed-limited. Fixed the vertical play-space correction leaking into ground control's feed-forward (a bounce). - Jump on right A: countermovement dip + leg push over the stroke (crouch = higher). - Stick crouch on right stick Y (RagdollDriver.StickCrouch: virtual floor offset). - Menu summon on left X only. - XR origin drawn with the bodies' physics interpolation (camera/body desync). - RagdollSpike: landing/fall/jump/look-down/stick-crouch phases, `-- only=` filter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
84df772f38
commit
e821728fba
7 files changed
+1379
-1043
No files matched your search
+50
-30
@@ -3,36 +3,56 @@ using Godot;
|
|||||||
// Shared math for the chase-driven player bodies (hands, torso, arm segments).
|
// Shared math for the chase-driven player bodies (hands, torso, arm segments).
|
||||||
public static class BodyMath
|
public static class BodyMath
|
||||||
{
|
{
|
||||||
// Shortest-arc rotation taking `current` to `target`, as axis * angle (radians), world space.
|
// Shortest-arc rotation taking `current` to `target`, as axis * angle (radians), world space.
|
||||||
// Never use Quaternion.GetAngle()/GetAxis() for servo errors: GetAngle() is 2*acos(W) and
|
// Never use Quaternion.GetAngle()/GetAxis() for servo errors: GetAngle() is 2*acos(W) and
|
||||||
// returns NaN when float rounding puts W at 1.0000001 (the Stage-1 chest collapse), and
|
// returns NaN when float rounding puts W at 1.0000001 (the Stage-1 chest collapse), and
|
||||||
// GetAxis() returns the raw un-normalized xyz once W > 1 - 1e-6 (servo gain collapses
|
// GetAxis() returns the raw un-normalized xyz once W > 1 - 1e-6 (servo gain collapses
|
||||||
// ~1000x inside 0.16 deg). atan2 is exact near zero and can't leave its domain.
|
// ~1000x inside 0.16 deg). atan2 is exact near zero and can't leave its domain.
|
||||||
public static Vector3 RotationError(Quaternion target, Quaternion current)
|
public static Vector3 RotationError(Quaternion target, Quaternion current)
|
||||||
{
|
{
|
||||||
Quaternion q = target * current.Inverse();
|
Quaternion q = target * current.Inverse();
|
||||||
if (q.W < 0f)
|
if (q.W < 0f)
|
||||||
q = new Quaternion(-q.X, -q.Y, -q.Z, -q.W);
|
q = new Quaternion(-q.X, -q.Y, -q.Z, -q.W);
|
||||||
Vector3 v = new(q.X, q.Y, q.Z);
|
Vector3 v = new(q.X, q.Y, q.Z);
|
||||||
float s = v.Length();
|
float s = v.Length();
|
||||||
if (s < 1e-9f)
|
if (s < 1e-9f)
|
||||||
return Vector3.Zero;
|
return Vector3.Zero;
|
||||||
return v * (2f * Mathf.Atan2(s, q.W) / s);
|
return v * (2f * Mathf.Atan2(s, q.W) / s);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsFinite(RigidBody3D body) =>
|
// The horizontal direction a head faces, robust past vertical. The visor (-Z) projected flat
|
||||||
body.GlobalTransform.IsFinite() && body.LinearVelocity.IsFinite() && body.AngularVelocity.IsFinite();
|
// flips 180° once you look down (or up) beyond vertical — chin to chest while crouching, or
|
||||||
|
// looking at your own feet — and everything built on it (torso yaw, stick direction) turned
|
||||||
|
// around. Looking down, the top of the head (+Y) points the way you face; looking up it points
|
||||||
|
// back. So: when the flat visor disagrees with the flat crown, the head is past vertical and the
|
||||||
|
// visor is reversed; near vertical the crown alone carries the direction. Zero if undefined.
|
||||||
|
public static Vector3 HeadFacing(Basis head)
|
||||||
|
{
|
||||||
|
Vector3 forward = -head.Z, up = head.Y;
|
||||||
|
Vector3 visor = new(forward.X, 0f, forward.Z);
|
||||||
|
Vector3 crown = new Vector3(up.X, 0f, up.Z) * -forward.Y;
|
||||||
|
if (visor.Dot(crown) < 0f)
|
||||||
|
visor = -visor;
|
||||||
|
Vector3 f = visor + crown * 0.5f;
|
||||||
|
return f.LengthSquared() > 1e-6f ? f.Normalized() : Vector3.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
// Escape hatch: a non-finite body is unrecoverable for the solver and floods the renderer
|
// Yaw (about +Y, Godot convention: 0 = facing -Z) of a horizontal direction.
|
||||||
// with !v.is_finite() errors — put it back on its target, motionless. Returns true if reset.
|
public static float YawOf(Vector3 flat) => Mathf.Atan2(-flat.X, -flat.Z);
|
||||||
public static bool ResetIfNonFinite(RigidBody3D body, Transform3D target, string who)
|
|
||||||
{
|
public static bool IsFinite(RigidBody3D body) =>
|
||||||
if (IsFinite(body))
|
body.GlobalTransform.IsFinite() && body.LinearVelocity.IsFinite() && body.AngularVelocity.IsFinite();
|
||||||
return false;
|
|
||||||
body.GlobalTransform = target;
|
// Escape hatch: a non-finite body is unrecoverable for the solver and floods the renderer
|
||||||
body.LinearVelocity = Vector3.Zero;
|
// with !v.is_finite() errors — put it back on its target, motionless. Returns true if reset.
|
||||||
body.AngularVelocity = Vector3.Zero;
|
public static bool ResetIfNonFinite(RigidBody3D body, Transform3D target, string who)
|
||||||
GD.PushWarning($"{who}: non-finite body state, reset to target");
|
{
|
||||||
return true;
|
if (IsFinite(body))
|
||||||
}
|
return false;
|
||||||
|
body.GlobalTransform = target;
|
||||||
|
body.LinearVelocity = Vector3.Zero;
|
||||||
|
body.AngularVelocity = Vector3.Zero;
|
||||||
|
GD.PushWarning($"{who}: non-finite body state, reset to target");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+1083
-983
File diff suppressed because it is too large.
Load diff
+60
-4
@@ -4,7 +4,8 @@ using Godot;
|
|||||||
// Boneworks-style stick locomotion and the play-space reconciliation. Runs everything in one
|
// Boneworks-style stick locomotion and the play-space reconciliation. Runs everything in one
|
||||||
// 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;
|
||||||
// 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.
|
||||||
@@ -18,6 +19,8 @@ public partial class PlayerRig : Node
|
|||||||
public const float SmoothTurnSpeed = 2.5f; // rad/s
|
public const float SmoothTurnSpeed = 2.5f; // rad/s
|
||||||
public const float SnapTurnThreshold = 0.7f;
|
public const float SnapTurnThreshold = 0.7f;
|
||||||
public const float SnapTurnRearm = 0.4f;
|
public const float SnapTurnRearm = 0.4f;
|
||||||
|
public const float StickCrouchSpeed = 1.2f; // m/s at full stick
|
||||||
|
public const float StickCrouchHeadroom = 0.5f; // the stick can lower the eyes to this height (a crawl)
|
||||||
public const float JumpDistance = 0.5f; // an origin move this big in one tick = teleport, not motion
|
public const float JumpDistance = 0.5f; // an origin move this big in one tick = teleport, not motion
|
||||||
public const float JumpAngle = 0.1f; // rad
|
public const float JumpAngle = 0.1f; // rad
|
||||||
private const float GhostLagDistance = 0.1f;
|
private const float GhostLagDistance = 0.1f;
|
||||||
@@ -33,9 +36,17 @@ 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;
|
||||||
|
// Render interpolation of the play space (smooth-camera bible, VR edition): physics moves the
|
||||||
|
// origin every tick, but the body is DRAWN interpolated between the last two ticks. A camera
|
||||||
|
// stepping with the raw origin while the body glides a tick behind looks like the body is in
|
||||||
|
// two places at once when you move and look at it. So physics owns _physOrigin and every
|
||||||
|
// frame the visible origin is the same interpolation of it the bodies get.
|
||||||
|
private Transform3D _physOrigin, _prevPhysOrigin, _shownOrigin;
|
||||||
|
private bool _interpolateOrigin;
|
||||||
|
|
||||||
public PlayerRagdoll Ragdoll => _ragdoll;
|
public PlayerRagdoll Ragdoll => _ragdoll;
|
||||||
public static bool Spectator { get; private set; }
|
public static bool Spectator { get; private set; }
|
||||||
@@ -70,6 +81,8 @@ public partial class PlayerRig : Node
|
|||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
_instance = this;
|
_instance = this;
|
||||||
|
ProcessPriority = -100; // place the interpolated origin before anything reads it this frame
|
||||||
|
ProcessPhysicsPriority = -100; // restore the physics origin before anything uses it this tick
|
||||||
_origin = GetNode<XROrigin3D>("../XROrigin3D");
|
_origin = GetNode<XROrigin3D>("../XROrigin3D");
|
||||||
_camera = GetNode<XRCamera3D>("../XROrigin3D/XRCamera3D");
|
_camera = GetNode<XRCamera3D>("../XROrigin3D/XRCamera3D");
|
||||||
_left = GetNode<XRController3D>("../XROrigin3D/LeftHand");
|
_left = GetNode<XRController3D>("../XROrigin3D/LeftHand");
|
||||||
@@ -114,8 +127,10 @@ public partial class PlayerRig : Node
|
|||||||
if (profile == null || Spectator)
|
if (profile == null || Spectator)
|
||||||
{
|
{
|
||||||
_haveLastOrigin = false;
|
_haveLastOrigin = false;
|
||||||
|
_interpolateOrigin = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
RestorePhysicsOrigin();
|
||||||
|
|
||||||
// 1. Someone else moved the play space discretely: bring the body along.
|
// 1. Someone else moved the play space discretely: bring the body along.
|
||||||
Transform3D origin = _origin.GlobalTransform;
|
Transform3D origin = _origin.GlobalTransform;
|
||||||
@@ -134,10 +149,34 @@ public partial class PlayerRig : Node
|
|||||||
_origin.GlobalTransform = o;
|
_origin.GlobalTransform = o;
|
||||||
_lastOrigin = _origin.GlobalTransform;
|
_lastOrigin = _origin.GlobalTransform;
|
||||||
_haveLastOrigin = true;
|
_haveLastOrigin = true;
|
||||||
|
_prevPhysOrigin = _interpolateOrigin && !IsJump(_physOrigin, _lastOrigin) ? _physOrigin : _lastOrigin;
|
||||||
|
_physOrigin = _shownOrigin = _lastOrigin;
|
||||||
|
_interpolateOrigin = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The origin on screen is interpolated; physics must run on the real one. Anyone else who moved
|
||||||
|
// the origin since (teleport marker, menu) is respected as the new real one.
|
||||||
|
private void RestorePhysicsOrigin()
|
||||||
|
{
|
||||||
|
if (!_interpolateOrigin)
|
||||||
|
return;
|
||||||
|
Transform3D current = _origin.GlobalTransform;
|
||||||
|
if (current.IsEqualApprox(_shownOrigin))
|
||||||
|
_origin.GlobalTransform = _physOrigin;
|
||||||
|
else
|
||||||
|
_physOrigin = _prevPhysOrigin = current;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _Process(double delta)
|
public override void _Process(double delta)
|
||||||
{
|
{
|
||||||
|
if (_interpolateOrigin && !Spectator)
|
||||||
|
{
|
||||||
|
Transform3D current = _origin.GlobalTransform;
|
||||||
|
if (!current.IsEqualApprox(_shownOrigin))
|
||||||
|
_physOrigin = _prevPhysOrigin = current; // moved by someone else: show it as is
|
||||||
|
_shownOrigin = _prevPhysOrigin.InterpolateWith(_physOrigin, (float)Engine.GetPhysicsInterpolationFraction());
|
||||||
|
_origin.GlobalTransform = _shownOrigin;
|
||||||
|
}
|
||||||
for (int i = 0; i < 2; i++)
|
for (int i = 0; i < 2; i++)
|
||||||
{
|
{
|
||||||
XRController3D c = i == 0 ? _left : _right;
|
XRController3D c = i == 0 ? _left : _right;
|
||||||
@@ -178,9 +217,9 @@ public partial class PlayerRig : Node
|
|||||||
Vector3 desired = Vector3.Zero;
|
Vector3 desired = Vector3.Zero;
|
||||||
if (move.Length() > StickDeadzone)
|
if (move.Length() > StickDeadzone)
|
||||||
{
|
{
|
||||||
Vector3 fwd = -_camera.GlobalTransform.Basis.Z;
|
Vector3 fwd = BodyMath.HeadFacing(_camera.GlobalTransform.Basis.Orthonormalized());
|
||||||
fwd.Y = 0f;
|
if (fwd == Vector3.Zero)
|
||||||
fwd = fwd.LengthSquared() > 1e-6f ? fwd.Normalized() : Vector3.Forward;
|
fwd = -Solver.YawBasis.Z;
|
||||||
Vector3 rightDir = fwd.Cross(Vector3.Up);
|
Vector3 rightDir = fwd.Cross(Vector3.Up);
|
||||||
float mag = Mathf.Clamp((move.Length() - StickDeadzone) / (1f - StickDeadzone), 0f, 1f);
|
float mag = Mathf.Clamp((move.Length() - StickDeadzone) / (1f - StickDeadzone), 0f, 1f);
|
||||||
desired = (fwd * move.Y + rightDir * move.X).Normalized() * (mag * MoveSpeed);
|
desired = (fwd * move.Y + rightDir * move.X).Normalized() * (mag * MoveSpeed);
|
||||||
@@ -192,6 +231,23 @@ 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.
|
||||||
|
bool jump = _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
|
||||||
|
// axis decides between crouch and turn).
|
||||||
|
bool vertical = Mathf.Abs(turn.Y) > Mathf.Abs(turn.X);
|
||||||
|
if (vertical && Mathf.Abs(turn.Y) > StickDeadzone)
|
||||||
|
{
|
||||||
|
float max = Mathf.Max(0f, BodyProfile.Current.EyeHeight - StickCrouchHeadroom);
|
||||||
|
_driver.StickCrouch = Mathf.Clamp(_driver.StickCrouch - turn.Y * StickCrouchSpeed * dt, 0f, max);
|
||||||
|
}
|
||||||
|
if (vertical)
|
||||||
|
turn.X = 0f;
|
||||||
|
|
||||||
// Turn about the head.
|
// Turn about the head.
|
||||||
float angle = 0f;
|
float angle = 0f;
|
||||||
if (SmoothTurn)
|
if (SmoothTurn)
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ public sealed class RagdollDriver
|
|||||||
public TrueBodySolver Solver;
|
public TrueBodySolver Solver;
|
||||||
public PlayerRagdoll Ragdoll;
|
public PlayerRagdoll Ragdoll;
|
||||||
public bool RebuildRequested = true;
|
public bool RebuildRequested = true;
|
||||||
|
// Stick crouch (right stick down), metres: the play-space floor is treated as this far ABOVE where
|
||||||
|
// it is. The True Body then reads the head as that much lower (crouch pose), ground control and
|
||||||
|
// the view's re-centering settle the play space that far below the ground — so the camera rides
|
||||||
|
// down with the crouching body, and nothing feeds back (same play-space-relative rule as 2.5).
|
||||||
|
public float StickCrouch;
|
||||||
private double _now;
|
private double _now;
|
||||||
|
|
||||||
// origin = play space (modified by the correction); raw* = origin-local tracked poses as last
|
// origin = play space (modified by the correction); raw* = origin-local tracked poses as last
|
||||||
@@ -79,15 +84,16 @@ public sealed class RagdollDriver
|
|||||||
_now += dt;
|
_now += dt;
|
||||||
Transform3D head = Head.Sample(rawHead, _now), left = Left.Sample(rawLeft, _now), right = Right.Sample(rawRight, _now);
|
Transform3D head = Head.Sample(rawHead, _now), left = Left.Sample(rawLeft, _now), right = Right.Sample(rawRight, _now);
|
||||||
if (Ragdoll.Built && !RebuildRequested)
|
if (Ragdoll.Built && !RebuildRequested)
|
||||||
origin.Origin += Ragdoll.OriginCorrection((origin * head).Origin, origin.Origin.Y, dt);
|
origin.Origin += Ragdoll.OriginCorrection((origin * head).Origin, origin.Origin.Y + StickCrouch, dt);
|
||||||
|
float floorY = origin.Origin.Y + StickCrouch;
|
||||||
Transform3D h = origin * head, l = origin * left, r = origin * right;
|
Transform3D h = origin * head, l = origin * left, r = origin * right;
|
||||||
Solver.Solve(profile, h, l, r, origin.Origin.Y, dt);
|
Solver.Solve(profile, h, l, r, floorY, dt);
|
||||||
if (RebuildRequested || !Ragdoll.Built)
|
if (RebuildRequested || !Ragdoll.Built)
|
||||||
{
|
{
|
||||||
Ragdoll.Build(profile, Solver, l, r);
|
Ragdoll.Build(profile, Solver, l, r);
|
||||||
RebuildRequested = false;
|
RebuildRequested = false;
|
||||||
}
|
}
|
||||||
Vector3 lv = origin.Basis * Left.Velocity + moveVelocity, rv = origin.Basis * Right.Velocity + moveVelocity;
|
Vector3 lv = origin.Basis * Left.Velocity + moveVelocity, rv = origin.Basis * Right.Velocity + moveVelocity;
|
||||||
Ragdoll.Step(Solver, l, r, lv, rv, origin.Origin.Y, dt);
|
Ragdoll.Step(Solver, l, r, lv, rv, floorY, dt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+164
-14
@@ -38,6 +38,7 @@ public partial class RagdollSpike : Node3D
|
|||||||
private Transform3D Right => _origin * Tracked(2, _rightLocal);
|
private Transform3D Right => _origin * Tracked(2, _rightLocal);
|
||||||
|
|
||||||
private bool _realInput = true, _noiseOn = true, _holdOn = true;
|
private bool _realInput = true, _noiseOn = true, _holdOn = true;
|
||||||
|
private string[] _only; // `-- only=jump,fall`: run just the phases whose names contain one of these
|
||||||
private const float TrackingHz = 90f;
|
private const float TrackingHz = 90f;
|
||||||
private readonly Transform3D[] _held = new Transform3D[3];
|
private readonly Transform3D[] _held = new Transform3D[3];
|
||||||
private readonly long[] _heldFrame = { -1, -1, -1 };
|
private readonly long[] _heldFrame = { -1, -1, -1 };
|
||||||
@@ -71,6 +72,8 @@ public partial class RagdollSpike : Node3D
|
|||||||
_noiseOn = false;
|
_noiseOn = false;
|
||||||
if (arg == "nohold")
|
if (arg == "nohold")
|
||||||
_holdOn = false;
|
_holdOn = false;
|
||||||
|
if (arg.StartsWith("only="))
|
||||||
|
_only = arg[5..].Split(',');
|
||||||
}
|
}
|
||||||
GD.Print($"RAGDOLL: input mode {(_realInput ? "REAL (90 Hz tracking, jitter)" : "ideal")} at {Engine.PhysicsTicksPerSecond} Hz physics");
|
GD.Print($"RAGDOLL: input mode {(_realInput ? "REAL (90 Hz tracking, jitter)" : "ideal")} at {Engine.PhysicsTicksPerSecond} Hz physics");
|
||||||
StaticBody3D floor = MakeStatic(new Vector3(40f, 1f, 40f), new Vector3(0f, -0.5f, 0f), 29);
|
StaticBody3D floor = MakeStatic(new Vector3(40f, 1f, 40f), new Vector3(0f, -0.5f, 0f), 29);
|
||||||
@@ -106,11 +109,18 @@ public partial class RagdollSpike : Node3D
|
|||||||
_phases.Add(("holds poses (stand, crouch, deep crouch, lean, reach, kneel) without view/body drift", Rest, TickPoseHold, 18f, FinishPoseHold));
|
_phases.Add(("holds poses (stand, crouch, deep crouch, lean, reach, kneel) without view/body drift", Rest, TickPoseHold, 18f, FinishPoseHold));
|
||||||
_phases.Add(("hanging from a bar keeps the view on the body", () => SetupBarHang(), TickHang, 4f, FinishHang));
|
_phases.Add(("hanging from a bar keeps the view on the body", () => SetupBarHang(), TickHang, 4f, FinishHang));
|
||||||
_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 without sinking away from the view", SetupLedgeTop, TickDrop, 4f, FinishDrop));
|
_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(("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));
|
_phases.Add(("standing on a seesaw tips it (body weight reaches movable ground)", SetupSeesaw, _ => { }, 3f, FinishSeesaw));
|
||||||
_phases.Add(("playground: swing board hangs, seesaw on its pivot, big plank grabbable", SetupPlayground, _ => { }, 2f, FinishPlayground));
|
_phases.Add(("playground: swing board hangs, seesaw on its pivot, big plank grabbable", SetupPlayground, _ => { }, 2f, FinishPlayground));
|
||||||
_phases.Add(("walks up an 18° ramp", () => SetupRamp(18f), _ => _stickVelocity = new Vector3(0f, 0f, -1.2f), 4f, () => FinishRamp(true)));
|
_phases.Add(("walks up an 18° ramp", () => SetupRamp(18f), _ => _stickVelocity = new Vector3(0f, 0f, -1.2f), 4f, () => FinishRamp(true)));
|
||||||
_phases.Add(("can't walk up a 40° ramp", () => SetupRamp(40f), _ => _stickVelocity = new Vector3(0f, 0f, -1.2f), 4f, () => FinishRamp(false)));
|
_phases.Add(("can't walk up a 40° ramp", () => SetupRamp(40f), _ => _stickVelocity = new Vector3(0f, 0f, -1.2f), 4f, () => FinishRamp(false)));
|
||||||
|
if (_only != null)
|
||||||
|
_phases.RemoveAll(ph => ph.Name != "hinge sign" && !Array.Exists(_only, o => ph.Name.Contains(o)));
|
||||||
Next();
|
Next();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +163,8 @@ public partial class RagdollSpike : Node3D
|
|||||||
_finite = true;
|
_finite = true;
|
||||||
_measureFrom = 1f;
|
_measureFrom = 1f;
|
||||||
_stickVelocity = Vector3.Zero;
|
_stickVelocity = Vector3.Zero;
|
||||||
|
if (_driver != null)
|
||||||
|
_driver.StickCrouch = 0f;
|
||||||
_phases[_phase].Setup();
|
_phases[_phase].Setup();
|
||||||
_snapsAtStart = _ragdoll.Snaps;
|
_snapsAtStart = _ragdoll.Snaps;
|
||||||
}
|
}
|
||||||
@@ -662,38 +674,176 @@ public partial class RagdollSpike : Node3D
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Start standing on a 1 m platform whose edge is 0.5 m ahead.
|
// Start standing on a platform whose edge is 0.5 m ahead.
|
||||||
private void SetupLedgeTop()
|
private void SetupLedgeTop(float height)
|
||||||
{
|
{
|
||||||
Rest();
|
Rest();
|
||||||
_origin = new Transform3D(Basis.Identity, new Vector3(0f, 1f, 0f));
|
_origin = new Transform3D(Basis.Identity, new Vector3(0f, height, 0f));
|
||||||
_stage.AddChild(MakeStatic(new Vector3(3f, 1f, 3f), new Vector3(0f, 0.5f, 1f), 29));
|
_stage.AddChild(MakeStatic(new Vector3(3f, height, 3f), new Vector3(0f, height * 0.5f, 1f), 29));
|
||||||
|
_dropEyeErr = _dropEyeErrLate = _dropRebound = 0f;
|
||||||
|
_dropMinPelvis = float.MaxValue;
|
||||||
|
_dropStand = 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
private float _dropEyeErr, _dropMinPelvis = float.MaxValue;
|
private float _dropEyeErr, _dropEyeErrLate, _dropMinPelvis = float.MaxValue, _dropStand, _dropRebound;
|
||||||
|
|
||||||
private void TickDrop(float dt)
|
private void TickDrop(float dt)
|
||||||
{
|
{
|
||||||
_stickVelocity = _t > 0.5f && _t < 2f ? new Vector3(0f, 0f, -1.2f) : Vector3.Zero;
|
_stickVelocity = _t > 0.5f && _t < 2f ? new Vector3(0f, 0f, -1.2f) : Vector3.Zero;
|
||||||
if (_ragdoll.Built && !_needBuild && _t > 0.5f)
|
if (!_ragdoll.Built || _needBuild)
|
||||||
|
return;
|
||||||
|
float pelvis = _ragdoll.Pelvis.GlobalPosition.Y;
|
||||||
|
if (_t < 0.5f)
|
||||||
|
_dropStand = pelvis - _ragdoll.GroundY; // standing hips above the feet
|
||||||
|
else
|
||||||
{
|
{
|
||||||
_dropEyeErr = Mathf.Max(_dropEyeErr, _ragdoll.Eye.DistanceTo(Camera.Origin));
|
_dropEyeErr = Mathf.Max(_dropEyeErr, _ragdoll.Eye.DistanceTo(Camera.Origin));
|
||||||
if (_ragdoll.Grounded && _ragdoll.GroundY < 0.5f)
|
if (_ragdoll.Grounded && _ragdoll.GroundY < 0.5f)
|
||||||
_dropMinPelvis = Mathf.Min(_dropMinPelvis, _ragdoll.Pelvis.GlobalPosition.Y);
|
{
|
||||||
|
if (pelvis < _dropMinPelvis)
|
||||||
|
{
|
||||||
|
_dropMinPelvis = pelvis;
|
||||||
|
_dropRebound = pelvis;
|
||||||
|
}
|
||||||
|
_dropRebound = Mathf.Max(_dropRebound, pelvis);
|
||||||
|
}
|
||||||
|
if (_dropMinPelvis < float.MaxValue && _ragdoll.Pelvis.LinearVelocity.Y > -0.5f)
|
||||||
|
_dropEyeErrLate = Mathf.Max(_dropEyeErrLate, _ragdoll.Eye.DistanceTo(Camera.Origin));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool FinishDrop()
|
private bool FinishDrop(float height)
|
||||||
{
|
{
|
||||||
GD.Print($"RAGDOLL: landing: eye vs camera max {_dropEyeErr:F3} m (limit 0.15), lowest pelvis after touchdown {_dropMinPelvis:F3} m (standing 0.99, limit > 0.7)");
|
float dip = _dropStand - _dropMinPelvis, overshoot = _dropRebound - _dropStand;
|
||||||
bool landed = _dropEyeErr < 0.15f && _dropMinPelvis > 0.7f;
|
// The dip grows with the impact; a big fall folds the legs (hips near the ground). Rebound
|
||||||
_dropEyeErr = 0f;
|
// past the stance would be a bounce. The view rides the body throughout.
|
||||||
_dropMinPelvis = float.MaxValue;
|
bool dipOk = height < 2f ? dip > 0.12f && dip < 0.5f : dip > 0.45f;
|
||||||
|
GD.Print($"RAGDOLL: landing from {height} m: dip {dip:F3} m (stance {_dropStand:F3}), rebound above the stance {overshoot:F3} (limit 0.05), eye vs camera max {_dropEyeErr:F3} / after the impact {_dropEyeErrLate:F3} (limit 0.1)");
|
||||||
|
bool landed = dipOk && overshoot < 0.05f && _dropEyeErrLate < 0.1f;
|
||||||
if (!landed)
|
if (!landed)
|
||||||
return false;
|
return false;
|
||||||
GD.Print($"RAGDOLL: drop: feet y {_ragdoll.GroundY:F3}, origin y {_origin.Origin.Y:F3}, pelvis {_ragdoll.Pelvis.GlobalPosition}, eye error {_ragdoll.Eye.DistanceTo(Camera.Origin):F3}, snaps {NewSnaps}");
|
GD.Print($"RAGDOLL: drop: feet y {_ragdoll.GroundY:F3}, origin y {_origin.Origin.Y:F3}, pelvis {_ragdoll.Pelvis.GlobalPosition}, eye error {_ragdoll.Eye.DistanceTo(Camera.Origin):F3}, snaps {NewSnaps}");
|
||||||
return _finite && NewSnaps == 0 && Mathf.Abs(_ragdoll.GroundY) < 0.05f && Mathf.Abs(_origin.Origin.Y) < 0.1f
|
return _finite && NewSnaps == 0 && Mathf.Abs(_ragdoll.GroundY) < 0.05f && Mathf.Abs(_origin.Origin.Y) < 0.1f
|
||||||
&& _ragdoll.Eye.DistanceTo(Camera.Origin) < 0.1f && _ragdoll.Pelvis.GlobalPosition.Z > -3f;
|
&& _ragdoll.Eye.DistanceTo(Camera.Origin) < 0.1f && _ragdoll.Pelvis.GlobalPosition.Z > -3f
|
||||||
|
&& Mathf.Abs(_ragdoll.Pelvis.GlobalPosition.Y - _ragdoll.GroundY - _dropStand) < 0.05f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- jump
|
||||||
|
|
||||||
|
private float _jumpStand, _jumpApex, _jumpEyeErr, _standJumpApex;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
_leftLocal = new Transform3D(Basis.Identity, new Vector3(-0.2f, 1.0f - crouch * k, -0.3f));
|
||||||
|
_rightLocal = new Transform3D(Basis.Identity, new Vector3(0.2f, 1.0f - crouch * k, -0.3f));
|
||||||
|
_measureFrom = float.MaxValue;
|
||||||
|
if (!_ragdoll.Built || _needBuild)
|
||||||
|
return;
|
||||||
|
float pelvis = _ragdoll.Pelvis.GlobalPosition.Y;
|
||||||
|
if (_t < 0.7f)
|
||||||
|
{
|
||||||
|
_jumpStand = pelvis;
|
||||||
|
_jumpApex = 0f;
|
||||||
|
_jumpEyeErr = 0f;
|
||||||
|
_jumpPressed = false;
|
||||||
|
}
|
||||||
|
if (_t >= 2f && !_jumpPressed)
|
||||||
|
{
|
||||||
|
_jumpPressed = true;
|
||||||
|
_ragdoll.Jump();
|
||||||
|
}
|
||||||
|
if (_jumpPressed)
|
||||||
|
{
|
||||||
|
_jumpApex = Mathf.Max(_jumpApex, pelvis - _jumpStand);
|
||||||
|
_jumpEyeErr = Mathf.Max(_jumpEyeErr, _ragdoll.Eye.DistanceTo(Camera.Origin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool FinishJump(bool crouched)
|
||||||
|
{
|
||||||
|
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}");
|
||||||
|
bool ok = _finite && NewSnaps == 0 && _ragdoll.Grounded && !_ragdoll.Jumping && _jumpEyeErr < 0.12f && _origin.Origin.Length() < 0.15f;
|
||||||
|
if (!crouched)
|
||||||
|
{
|
||||||
|
_standJumpApex = _jumpApex;
|
||||||
|
return ok && _jumpApex > 0.3f && _jumpApex < 0.65f;
|
||||||
|
}
|
||||||
|
return ok && _jumpApex > _standJumpApex + 0.1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- looking down past vertical
|
||||||
|
|
||||||
|
private float _lookYaw0, _lookYawMax, _lookChestMax, _lookPitchMax;
|
||||||
|
|
||||||
|
private static float YawOfBody(RigidBody3D b)
|
||||||
|
{
|
||||||
|
Vector3 f = BodyMath.HeadFacing(b.GlobalTransform.Basis.Orthonormalized());
|
||||||
|
return f == Vector3.Zero ? 0f : BodyMath.YawOf(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crouch (eyes 1.0 m), then look down to 125° below the horizon (chin on the chest, looking
|
||||||
|
// behind the feet) and back, hands down near the floor — interacting with stuff on the ground.
|
||||||
|
private void TickLookDown(float dt)
|
||||||
|
{
|
||||||
|
float k = Mathf.SmoothStep(0f, 1f, Mathf.Min(_t / 1f, 1f));
|
||||||
|
float pitch = Mathf.DegToRad(125f) * Mathf.Sin(Mathf.Pi * Mathf.Clamp((_t - 1.5f) / 3f, 0f, 1f));
|
||||||
|
_lookPitchMax = Mathf.Max(_lookPitchMax, pitch);
|
||||||
|
Basis head = new(Vector3.Right, -pitch);
|
||||||
|
Vector3 pivot = new(0f, Mathf.Lerp(EyeHeight, 1.0f, k) + TrueBodySolver.EyeToNeckPivot.Y, -0.1f * k + TrueBodySolver.EyeToNeckPivot.Z);
|
||||||
|
_headLocal = new Transform3D(head, pivot - head * TrueBodySolver.EyeToNeckPivot);
|
||||||
|
_leftLocal = new Transform3D(Aim(Vector3.Down + Vector3.Forward), new Vector3(-0.2f, Mathf.Lerp(1.0f, 0.3f, k), -0.4f));
|
||||||
|
_rightLocal = new Transform3D(Aim(Vector3.Down + Vector3.Forward), new Vector3(0.2f, Mathf.Lerp(1.0f, 0.3f, k), -0.4f));
|
||||||
|
_measureFrom = float.MaxValue;
|
||||||
|
if (!_ragdoll.Built || _needBuild)
|
||||||
|
return;
|
||||||
|
if (_t < 1.4f)
|
||||||
|
{
|
||||||
|
_lookYaw0 = YawOfBody(_ragdoll.Pelvis);
|
||||||
|
_lookYawMax = _lookChestMax = _lookPitchMax = 0f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_lookYawMax = Mathf.Max(_lookYawMax, Mathf.Abs(Mathf.AngleDifference(_lookYaw0, YawOfBody(_ragdoll.Pelvis))));
|
||||||
|
_lookChestMax = Mathf.Max(_lookChestMax, Mathf.Abs(Mathf.AngleDifference(_lookYaw0, YawOfBody(_ragdoll.Chest))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool FinishLookDown()
|
||||||
|
{
|
||||||
|
GD.Print($"RAGDOLL: look down to {Mathf.RadToDeg(_lookPitchMax):F0}°: pelvis yaw change max {Mathf.RadToDeg(_lookYawMax):F1}°, chest {Mathf.RadToDeg(_lookChestMax):F1}° (limit 15), eye error {_ragdoll.Eye.DistanceTo(Camera.Origin):F3}, snaps {NewSnaps}");
|
||||||
|
return _finite && NewSnaps == 0 && _lookYawMax < Mathf.DegToRad(15f) && _lookChestMax < Mathf.DegToRad(15f)
|
||||||
|
&& _ragdoll.Eye.DistanceTo(Camera.Origin) < 0.08f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- stick crouch
|
||||||
|
|
||||||
|
private float _stickEyeLow, _stickOriginLow, _stickEyeErr;
|
||||||
|
|
||||||
|
private void TickStickCrouch(float dt)
|
||||||
|
{
|
||||||
|
_measureFrom = float.MaxValue;
|
||||||
|
if (_driver == null)
|
||||||
|
return;
|
||||||
|
// Hold the stick down for 1 s (1.2 m/s → capped at 0.6 m here), hold, then back up.
|
||||||
|
float c = _t < 0.5f ? 0f : _t < 3.5f ? Mathf.Min(0.6f, (_t - 0.5f) * 1.2f) : Mathf.Max(0f, 0.6f - (_t - 3.5f) * 1.2f);
|
||||||
|
_driver.StickCrouch = c;
|
||||||
|
if (_t > 2.5f && _t < 3.4f && _ragdoll.Built)
|
||||||
|
{
|
||||||
|
_stickEyeLow = _ragdoll.Eye.Y;
|
||||||
|
_stickOriginLow = _origin.Origin.Y;
|
||||||
|
_stickEyeErr = Mathf.Max(_stickEyeErr, _ragdoll.Eye.DistanceTo(Camera.Origin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool FinishStickCrouch()
|
||||||
|
{
|
||||||
|
GD.Print($"RAGDOLL: stick crouch 0.6 m: eye y {_stickEyeLow:F3} (standing {EyeHeight}), origin y {_stickOriginLow:F3}, eye vs camera {_stickEyeErr:F3}; back up: eye y {_ragdoll.Eye.Y:F3}, origin y {_origin.Origin.Y:F3}, snaps {NewSnaps}");
|
||||||
|
bool ok = _finite && NewSnaps == 0 && Mathf.Abs(_stickEyeLow - (EyeHeight - 0.6f)) < 0.1f && Mathf.Abs(_stickOriginLow + 0.6f) < 0.08f
|
||||||
|
&& _stickEyeErr < 0.06f && Mathf.Abs(_ragdoll.Eye.Y - EyeHeight) < 0.06f && Mathf.Abs(_origin.Origin.Y) < 0.06f;
|
||||||
|
_stickEyeErr = 0f;
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector3 _crouchPelvis;
|
private Vector3 _crouchPelvis;
|
||||||
|
|||||||
+11
-6
@@ -14,6 +14,7 @@ public sealed class TrueBodySolver
|
|||||||
public const float ChestLeanMax = 0.75f; // rad (~43°) at a deep crouch
|
public const float ChestLeanMax = 0.75f; // rad (~43°) at a deep crouch
|
||||||
public const float CrouchRange = 0.5f; // crouch factor 1 when eyes are this fraction of standing eye height lower
|
public const float CrouchRange = 0.5f; // crouch factor 1 when eyes are this fraction of standing eye height lower
|
||||||
public const float HeadPitchToChest = 0.25f;
|
public const float HeadPitchToChest = 0.25f;
|
||||||
|
public const float MaxHeadPitch = 2.4f; // rad; chin-to-chest looking at your feet goes past 90°
|
||||||
// Crawl: when the head gets so low that the hips would go through the floor, the trunk tips
|
// Crawl: when the head gets so low that the hips would go through the floor, the trunk tips
|
||||||
// further (pivoting at the hips, hips kicking back) up to nearly horizontal (Nimso: lower = crawl).
|
// further (pivoting at the hips, hips kicking back) up to nearly horizontal (Nimso: lower = crawl).
|
||||||
public const float CrawlLeanMax = 1.45f; // rad (~83°)
|
public const float CrawlLeanMax = 1.45f; // rad (~83°)
|
||||||
@@ -79,16 +80,19 @@ public sealed class TrueBodySolver
|
|||||||
WristTarget[1] = right * new Vector3(0f, 0f, WristOffset);
|
WristTarget[1] = right * new Vector3(0f, 0f, WristOffset);
|
||||||
|
|
||||||
// Torso yaw: HMD yaw blended toward the hands when they're out in front (decoupled from the head).
|
// Torso yaw: HMD yaw blended toward the hands when they're out in front (decoupled from the head).
|
||||||
|
// Facing from BodyMath.HeadFacing: looking down past vertical must not turn the body around.
|
||||||
Vector3 headFwd = -headBasis.Z;
|
Vector3 headFwd = -headBasis.Z;
|
||||||
float hmdYaw = Mathf.Atan2(-headFwd.X, -headFwd.Z);
|
Vector3 flatFwd = BodyMath.HeadFacing(headBasis);
|
||||||
|
if (flatFwd == Vector3.Zero)
|
||||||
|
flatFwd = new Basis(Vector3.Up, _yaw) * Vector3.Forward;
|
||||||
|
float hmdYaw = BodyMath.YawOf(flatFwd);
|
||||||
float targetYaw = hmdYaw;
|
float targetYaw = hmdYaw;
|
||||||
Vector3 toHands = (WristTarget[0] + WristTarget[1]) * 0.5f - Eye;
|
Vector3 toHands = (WristTarget[0] + WristTarget[1]) * 0.5f - Eye;
|
||||||
toHands.Y = 0f;
|
toHands.Y = 0f;
|
||||||
Vector3 flatFwd = new Vector3(headFwd.X, 0f, headFwd.Z);
|
if (toHands.LengthSquared() > 1e-4f)
|
||||||
if (toHands.LengthSquared() > 1e-4f && flatFwd.LengthSquared() > 1e-6f)
|
|
||||||
{
|
{
|
||||||
float handsYaw = Mathf.Atan2(-toHands.X, -toHands.Z);
|
float handsYaw = BodyMath.YawOf(toHands);
|
||||||
float forwardness = Mathf.Clamp(toHands.Dot(flatFwd.Normalized()) / YawForwardness, 0f, 1f);
|
float forwardness = Mathf.Clamp(toHands.Dot(flatFwd) / YawForwardness, 0f, 1f);
|
||||||
targetYaw = Mathf.LerpAngle(hmdYaw, handsYaw, forwardness * YawHandsWeight);
|
targetYaw = Mathf.LerpAngle(hmdYaw, handsYaw, forwardness * YawHandsWeight);
|
||||||
}
|
}
|
||||||
if (!Initialized)
|
if (!Initialized)
|
||||||
@@ -102,7 +106,8 @@ public sealed class TrueBodySolver
|
|||||||
float pivotAboveFloor = NeckPivot.Y - floorY;
|
float pivotAboveFloor = NeckPivot.Y - floorY;
|
||||||
float standingPivot = p.EyeHeight + EyeToNeckPivot.Y;
|
float standingPivot = p.EyeHeight + EyeToNeckPivot.Y;
|
||||||
Crouch = Mathf.Clamp((standingPivot - pivotAboveFloor) / (CrouchRange * p.EyeHeight), 0f, 1f);
|
Crouch = Mathf.Clamp((standingPivot - pivotAboveFloor) / (CrouchRange * p.EyeHeight), 0f, 1f);
|
||||||
float headPitchDown = Mathf.Max(0f, -Mathf.Asin(Mathf.Clamp(headFwd.Y, -1f, 1f)));
|
// Pitch against the facing, so it keeps growing past vertical (asin peaks at 90°).
|
||||||
|
float headPitchDown = Mathf.Clamp(Mathf.Atan2(-headFwd.Y, headFwd.Dot(flatFwd)), 0f, MaxHeadPitch);
|
||||||
float lean = Crouch * ChestLeanMax + headPitchDown * HeadPitchToChest;
|
float lean = Crouch * ChestLeanMax + headPitchDown * HeadPitchToChest;
|
||||||
float trunk = TrunkRatio * p.Stature;
|
float trunk = TrunkRatio * p.Stature;
|
||||||
float neckLen = Mathf.Max(0.05f, p.ShoulderDrop + EyeToNeckPivot.Y - NeckBaseAboveShoulders);
|
float neckLen = Mathf.Max(0.05f, p.ShoulderDrop + EyeToNeckPivot.Y - NeckBaseAboveShoulders);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
// The in-VR menu: a MenuScreen (Godot Control UI in a SubViewport on a thin 0.36x0.24 m
|
// The in-VR menu: a MenuScreen (Godot Control UI in a SubViewport on a thin 0.36x0.24 m
|
||||||
// panel) showing MainMenu. This node is the XR glue: summon/dismiss with ax_button (A/X),
|
// panel) showing MainMenu. This node is the XR glue: summon/dismiss with the LEFT ax_button (X; right A is jump),
|
||||||
// grip-drag (direct or along the laser), re-orient toward the head, feed both controllers'
|
// grip-drag (direct or along the laser), re-orient toward the head, feed both controllers'
|
||||||
// pointer rays into the screen, click sound + haptics. PARENTED UNDER XROrigin3D (fixed in the
|
// pointer rays into the screen, click sound + haptics. PARENTED UNDER XROrigin3D (fixed in the
|
||||||
// player's room; game locomotion carries it).
|
// player's room; game locomotion carries it).
|
||||||
@@ -114,8 +114,7 @@ public partial class WorldPanel : Node3D
|
|||||||
|
|
||||||
public override void _Process(double delta)
|
public override void _Process(double delta)
|
||||||
{
|
{
|
||||||
PollSummon(_left, 0);
|
PollSummon(_left, 0); // left X only: right A is jump
|
||||||
PollSummon(_right, 1);
|
|
||||||
if (Visible)
|
if (Visible)
|
||||||
{
|
{
|
||||||
UpdateDrag(_left, 0);
|
UpdateDrag(_left, 0);
|
||||||
|
|||||||
Reference in new issue
Block a user