Body step 2.1: climbing, mantling, crawling, legs, playground, 144 Hz
- Step rule + lower-body collider: no walking onto tables, steps <= 0.35 m - Structural hand strength: two-hand mantle/pull-up works, one hand can't - Arms aim from the physical shoulder horizontally, keep True Body pose vertically - Crawl pose, crawl carry, 35 deg slope limit, ground feed-forward ignores play-space corrections (fixes a runaway feedback loop) - Grab static world (climbing), procedural visual legs - Playground sandbox; menu strength slider, spectator (old drag) and turn toggles - Physics 144 Hz with interpolation; legacy chase-body spikes pin 90 Hz - RagdollSpike: 27 phases incl. mantle, pull-up, crawl, ledge drop, ramps Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d83273dd2f
commit
2aff2b61da
26 files changed
+1243
-70
No files matched your search
@@ -57,6 +57,17 @@ public partial class RagdollSpike : Node3D
|
||||
_phases.Add(("12 kg held at arm's length sags more", () => SetupWeight(close: false), _ => { }, 3f, () => FinishWeight(close: false)));
|
||||
_phases.Add(("teleport + snap turn carry the body", Rest, TickTeleport, 2f, FinishTeleport));
|
||||
_phases.Add(("random motion soak", Rest, TickRandom, 8f, FinishSoak));
|
||||
_phases.Add(("a knee-high box blocks walking (no stepping onto it)", () => SetupObstacle(0.6f), _ => _stickVelocity = new Vector3(0f, 0f, -1.5f), 3f, FinishBlockedLow));
|
||||
_phases.Add(("a table can't be walked over", SetupTableObstacle, _ => _stickVelocity = new Vector3(0f, 0f, -1.5f), 3f, FinishBlockedLow));
|
||||
_phases.Add(("walks up a 0.25 m step", () => SetupObstacle(0.25f, 8f), _ => _stickVelocity = new Vector3(0f, 0f, -1.2f), 3f, FinishStep));
|
||||
_phases.Add(("two-handed mantle onto a 0.9 m ledge", () => SetupObstacle(0.9f), t => TickMantle(both: true), 6f, () => FinishMantle(true)));
|
||||
_phases.Add(("one hand can't mantle", () => SetupObstacle(0.9f), t => TickMantle(both: false), 6f, () => FinishMantle(false)));
|
||||
_phases.Add(("two-handed pull-up on a grabbed bar", () => SetupBarHang(), t => TickPullUp(both: true), 3.5f, () => FinishPullUp(true)));
|
||||
_phases.Add(("one-handed pull-up is weaker", () => SetupBarHang(), t => TickPullUp(both: false), 3.5f, () => FinishPullUp(false)));
|
||||
_phases.Add(("crawls through a 0.6 m tunnel", SetupTunnel, TickCrawl, 7f, FinishCrawl));
|
||||
_phases.Add(("walks off a 1 m ledge and lands", SetupLedgeTop, _ => _stickVelocity = _t > 0.5f && _t < 2f ? new Vector3(0f, 0f, -1.2f) : Vector3.Zero, 4f, FinishDrop));
|
||||
_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)));
|
||||
Next();
|
||||
}
|
||||
|
||||
@@ -448,6 +459,198 @@ public partial class RagdollSpike : Node3D
|
||||
return _finite && NewSnaps == 0 && _maxEyeErr < 0.05f && _maxHandErr < 0.06f;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- obstacles, mantling, climbing, crawling
|
||||
|
||||
private const float ObstacleFront = -0.55f;
|
||||
|
||||
// A box from ObstacleFront 2 m deep (wide), `height` tall.
|
||||
private void SetupObstacle(float height, float depth = 2f)
|
||||
{
|
||||
Rest();
|
||||
_stage.AddChild(MakeStatic(new Vector3(3f, height, depth), new Vector3(0f, height * 0.5f, ObstacleFront - depth * 0.5f), 29));
|
||||
}
|
||||
|
||||
private void SetupTableObstacle()
|
||||
{
|
||||
Rest();
|
||||
_stage.AddChild(MakeStatic(new Vector3(1.4f, 0.05f, 0.8f), new Vector3(0f, 0.725f, ObstacleFront - 0.4f), 29));
|
||||
foreach (float x in new[] { -0.65f, 0.65f })
|
||||
foreach (float z in new[] { ObstacleFront - 0.05f, ObstacleFront - 0.75f })
|
||||
_stage.AddChild(MakeStatic(new Vector3(0.06f, 0.7f, 0.06f), new Vector3(x, 0.35f, z), 29));
|
||||
}
|
||||
|
||||
private bool FinishBlockedLow()
|
||||
{
|
||||
GD.Print($"RAGDOLL: obstacle front z {ObstacleFront}: pelvis z {_ragdoll.Pelvis.GlobalPosition.Z:F3}, feet y {_ragdoll.GroundY:F3}, origin y {_origin.Origin.Y:F3}, camera min z {_minCamZ:F3}, snaps {NewSnaps}");
|
||||
return _finite && NewSnaps == 0 && _ragdoll.GroundY < 0.1f && _origin.Origin.Y < 0.1f && _ragdoll.Pelvis.GlobalPosition.Z > ObstacleFront - 0.3f;
|
||||
}
|
||||
|
||||
private bool FinishStep()
|
||||
{
|
||||
GD.Print($"RAGDOLL: step: feet y {_ragdoll.GroundY:F3}, origin y {_origin.Origin.Y:F3}, pelvis z {_ragdoll.Pelvis.GlobalPosition.Z:F3}, snaps {NewSnaps}");
|
||||
return _finite && NewSnaps == 0 && Mathf.Abs(_ragdoll.GroundY - 0.25f) < 0.03f && _origin.Origin.Y > 0.2f && _ragdoll.Pelvis.GlobalPosition.Z < ObstacleFront - 0.3f;
|
||||
}
|
||||
|
||||
private float _maxPelvisRise, _lastRise;
|
||||
|
||||
// Put both hands on the ledge, press down (controllers sink as the real arms straighten), then
|
||||
// walk forward onto it.
|
||||
// Walk up to the ledge (the legs collider stops you), hands on top just ahead, press down.
|
||||
private void TickMantle(bool both)
|
||||
{
|
||||
float t = _t;
|
||||
_stickVelocity = t < 1f ? new Vector3(0f, 0f, -0.8f) : t > 3.5f ? new Vector3(0f, 0f, -0.8f) : Vector3.Zero;
|
||||
float down = Mathf.Clamp((t - 1.6f) / 1.5f, 0f, 1f);
|
||||
float y = t < 1.6f ? Mathf.Lerp(1.0f, 0.95f, Mathf.Clamp(t - 1f, 0f, 0.6f) / 0.6f) : Mathf.Lerp(0.95f, 0.35f, down);
|
||||
// Hands just past the ledge edge (aimed in the room, like a real player looking at it).
|
||||
float z = t < 1f ? -0.4f : ObstacleFront - 0.1f - _origin.Origin.Z;
|
||||
Vector3 l = new(-0.22f, y, z), r = new(0.22f, y, z);
|
||||
_rightLocal = new Transform3D(Aim(Vector3.Down + Vector3.Forward * 0.3f), r);
|
||||
_leftLocal = both ? new Transform3D(Aim(Vector3.Down + Vector3.Forward * 0.3f), l) : new Transform3D(Basis.Identity, new Vector3(-0.2f, 1.0f, -0.3f));
|
||||
if (t > 3.6f)
|
||||
{
|
||||
// Up and walking on: hands come back to the body like a real player's.
|
||||
_leftLocal = new Transform3D(Basis.Identity, new Vector3(-0.2f, 1.0f, -0.3f));
|
||||
_rightLocal = new Transform3D(Basis.Identity, new Vector3(0.2f, 1.0f, -0.3f));
|
||||
}
|
||||
if (_ragdoll.Built && !_needBuild)
|
||||
_maxPelvisRise = Mathf.Max(_maxPelvisRise, _ragdoll.Pelvis.GlobalPosition.Y - 0.99f);
|
||||
}
|
||||
|
||||
private bool FinishMantle(bool both)
|
||||
{
|
||||
GD.Print($"RAGDOLL: mantle ({(both ? "two hands" : "one hand")}): pelvis rose up to {_maxPelvisRise:F3} m, feet y {_ragdoll.GroundY:F3}, origin y {_origin.Origin.Y:F3}, snaps {NewSnaps}");
|
||||
bool onTop = _ragdoll.GroundY > 0.85f && _origin.Origin.Y > 0.8f;
|
||||
_lastRise = _maxPelvisRise;
|
||||
_maxPelvisRise = 0f;
|
||||
return _finite && NewSnaps == 0 && (both ? onTop : !onTop && _lastRise < 0.25f);
|
||||
}
|
||||
|
||||
private StaticBody3D _bar;
|
||||
private const float BarY = 1.95f, BarHangZ = -0.15f;
|
||||
private float _barPelvisStart;
|
||||
private bool _barGrabbed;
|
||||
|
||||
private void SetupBarHang()
|
||||
{
|
||||
Rest();
|
||||
_bar = new StaticBody3D { Position = new Vector3(0f, BarY, BarHangZ), CollisionMask = 29 };
|
||||
_bar.AddChild(new CollisionShape3D { Shape = new CylinderShape3D { Radius = 0.025f, Height = 2f }, RotationDegrees = new Vector3(0f, 0f, 90f) });
|
||||
_stage.AddChild(_bar);
|
||||
_barGrabbed = false;
|
||||
_maxPelvisRise = 0f;
|
||||
}
|
||||
|
||||
// Reach up to the bar, grip it (the PropGrabber weld), then pull the controllers down.
|
||||
private void TickPullUp(bool both)
|
||||
{
|
||||
float t = _t;
|
||||
float pull = Mathf.Clamp((t - 0.8f) / 1.5f, 0f, 1f);
|
||||
float y = Mathf.Lerp(BarY - 0.03f, BarY - 0.75f, pull);
|
||||
Basis grip = Aim(Vector3.Up + Vector3.Forward * 0.2f);
|
||||
_rightLocal = new Transform3D(grip, new Vector3(0.22f, y, BarHangZ + 0.03f));
|
||||
_leftLocal = both ? new Transform3D(grip, new Vector3(-0.22f, y, BarHangZ + 0.03f)) : new Transform3D(Basis.Identity, new Vector3(-0.2f, 1.0f, -0.3f));
|
||||
if (!_barGrabbed && t > 0.6f && _ragdoll.Built)
|
||||
{
|
||||
_barGrabbed = true;
|
||||
_barPelvisStart = _ragdoll.Pelvis.GlobalPosition.Y;
|
||||
for (int side = both ? 0 : 1; side < 2; side++)
|
||||
{
|
||||
RigidBody3D hand = _ragdoll.Hand(side);
|
||||
ConeTwistJoint3D weld = new() { Position = hand.GlobalPosition, NodeA = hand.GetPath(), NodeB = _bar.GetPath(), SwingSpan = 0f, TwistSpan = 0f, Bias = 0f };
|
||||
_stage.AddChild(weld);
|
||||
_ragdoll.SetHeld(side, _bar);
|
||||
}
|
||||
}
|
||||
if (_barGrabbed)
|
||||
_maxPelvisRise = Mathf.Max(_maxPelvisRise, _ragdoll.Pelvis.GlobalPosition.Y - _barPelvisStart);
|
||||
}
|
||||
|
||||
private float _twoHandRise;
|
||||
|
||||
private bool FinishPullUp(bool both)
|
||||
{
|
||||
GD.Print($"RAGDOLL: pull-up ({(both ? "two hands" : "one hand")}): pelvis rose {_maxPelvisRise:F3} m, braced {_ragdoll.Braced}, snaps {NewSnaps}");
|
||||
_ragdoll.SetHeld(0, null);
|
||||
_ragdoll.SetHeld(1, null);
|
||||
if (both)
|
||||
{
|
||||
_twoHandRise = _maxPelvisRise;
|
||||
return _finite && NewSnaps == 0 && _maxPelvisRise > 0.25f;
|
||||
}
|
||||
return _finite && NewSnaps == 0 && _maxPelvisRise < _twoHandRise * 0.6f;
|
||||
}
|
||||
|
||||
private const float TunnelStart = -1.0f, TunnelEnd = -3.5f;
|
||||
private float _minCamY;
|
||||
|
||||
private void SetupTunnel()
|
||||
{
|
||||
Rest();
|
||||
float len = TunnelStart - TunnelEnd;
|
||||
float mid = (TunnelStart + TunnelEnd) * 0.5f;
|
||||
_stage.AddChild(MakeStatic(new Vector3(1.4f, 0.1f, len), new Vector3(0f, 0.65f, mid), 29));
|
||||
_stage.AddChild(MakeStatic(new Vector3(0.1f, 0.6f, len), new Vector3(-0.65f, 0.3f, mid), 29));
|
||||
_stage.AddChild(MakeStatic(new Vector3(0.1f, 0.6f, len), new Vector3(0.65f, 0.3f, mid), 29));
|
||||
_minCamY = float.MaxValue;
|
||||
}
|
||||
|
||||
// Get down on hands and knees (eyes ~0.4 m, looking ahead), then stick-crawl forward.
|
||||
private void TickCrawl(float dt)
|
||||
{
|
||||
float k = Mathf.SmoothStep(0f, 1f, Mathf.Min(_t / 2f, 1f));
|
||||
_headLocal = new Transform3D(new Basis(Vector3.Right, -0.35f * k), new Vector3(0f, Mathf.Lerp(EyeHeight, 0.42f, k), Mathf.Lerp(0f, -0.25f, k)));
|
||||
_leftLocal = new Transform3D(Aim(Vector3.Forward + Vector3.Down), new Vector3(-0.2f, Mathf.Lerp(1.0f, 0.12f, k), Mathf.Lerp(-0.3f, -0.45f, k)));
|
||||
_rightLocal = new Transform3D(Aim(Vector3.Forward + Vector3.Down), new Vector3(0.2f, Mathf.Lerp(1.0f, 0.12f, k), Mathf.Lerp(-0.3f, -0.45f, k)));
|
||||
_stickVelocity = _t > 2.3f ? new Vector3(0f, 0f, -0.8f) : Vector3.Zero;
|
||||
_measureFrom = float.MaxValue;
|
||||
if (_t > 2.3f)
|
||||
_minCamY = Mathf.Min(_minCamY, Camera.Origin.Y);
|
||||
}
|
||||
|
||||
private bool FinishCrawl()
|
||||
{
|
||||
GD.Print($"RAGDOLL: crawl: camera min z {_minCamZ:F3} (tunnel {TunnelStart}..{TunnelEnd}), eye y {_ragdoll.Eye.Y:F2}, pelvis y {_ragdoll.Pelvis.GlobalPosition.Y:F2}, origin y {_origin.Origin.Y:F3}, snaps {NewSnaps}");
|
||||
return _finite && NewSnaps == 0 && _minCamZ < TunnelStart - 1.5f && Mathf.Abs(_origin.Origin.Y) < 0.1f;
|
||||
}
|
||||
|
||||
|
||||
// Start standing on a 1 m platform whose edge is 0.5 m ahead.
|
||||
private void SetupLedgeTop()
|
||||
{
|
||||
Rest();
|
||||
_origin = new Transform3D(Basis.Identity, new Vector3(0f, 1f, 0f));
|
||||
_stage.AddChild(MakeStatic(new Vector3(3f, 1f, 3f), new Vector3(0f, 0.5f, 1f), 29));
|
||||
}
|
||||
|
||||
private bool FinishDrop()
|
||||
{
|
||||
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
|
||||
&& _ragdoll.Eye.DistanceTo(Camera.Origin) < 0.1f && _ragdoll.Pelvis.GlobalPosition.Z > -3f;
|
||||
}
|
||||
|
||||
// A slope rising away (−Z) from ObstacleFront up to 1 m, then a platform.
|
||||
private void SetupRamp(float degrees)
|
||||
{
|
||||
Rest();
|
||||
float a = Mathf.DegToRad(degrees);
|
||||
float len = 1f / Mathf.Sin(a);
|
||||
float run = Mathf.Cos(a) * len;
|
||||
_stage.AddChild(new StaticBody3D { CollisionMask = 29, Transform = new Transform3D(new Basis(Vector3.Right, a), new Vector3(0f, 0.5f - 0.05f / Mathf.Cos(a), ObstacleFront - run * 0.5f)) });
|
||||
_stage.GetChild<StaticBody3D>(_stage.GetChildCount() - 1).AddChild(new CollisionShape3D { Shape = new BoxShape3D { Size = new Vector3(3f, 0.1f, len) } });
|
||||
_stage.AddChild(MakeStatic(new Vector3(3f, 1f, 3f), new Vector3(0f, 0.5f, ObstacleFront - run - 1.5f), 29));
|
||||
}
|
||||
|
||||
private bool FinishRamp(bool walkable)
|
||||
{
|
||||
GD.Print($"RAGDOLL: ramp: feet y {_ragdoll.GroundY:F3}, origin y {_origin.Origin.Y:F3}, pelvis {_ragdoll.Pelvis.GlobalPosition}, snaps {NewSnaps}");
|
||||
bool up = _ragdoll.GroundY > 0.6f;
|
||||
// Stopped at the slope's foot: on the floor, view level, not flung away.
|
||||
bool stopped = Mathf.Abs(_origin.Origin.Y) < 0.1f && _ragdoll.Pelvis.GlobalPosition.Length() < 2.5f;
|
||||
return _finite && NewSnaps == 0 && (walkable ? up : !up && stopped);
|
||||
}
|
||||
|
||||
private readonly RandomNumberGenerator _rng = new() { Seed = 11 };
|
||||
private Vector3 _ra, _rb, _rc;
|
||||
|
||||
|
||||
Reference in new issue
Block a user