Body step 2.4: landing, movable ground, playground fixes
- legs brake landings at 4.5 g; vertical support is a rate-limited servo (no launch when stepping onto a mantled ledge) - ground control pushes back on movable ground (seesaw tips under the player) - playground hinges use absolute paths (joint paths resolve from the joint) - big props grabbable via surface query; Reset props keeps playground pieces - spike: landing, seesaw, playground joints/grab, person-like wall climb Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b5f598aae9
commit
0b8f2131c1
6 files changed
+208
-42
No files matched your search
@@ -52,10 +52,11 @@ public partial class PlayerRagdoll : Node3D
|
||||
// Ground control (world-space support on the pelvis, only while grounded).
|
||||
public const float GroundFrequency = 4f; // Hz
|
||||
public const float GroundDamping = 1f;
|
||||
public const float LegStrength = 2f; // max support, × body weight
|
||||
public const float LegStrength = 4.5f; // max support, × body weight (real legs brake a landing at 3–5 g; 2× sank ~0.4 m)
|
||||
public const float GroundFriction = 1f; // horizontal force ≤ this × support
|
||||
public const float GroundProbeExtra = 0.3f; // ground counts while within hip height + this
|
||||
public const float MaxStepHeight = 0.35f;
|
||||
public const float MaxRiseSpeed = 1.2f; // m/s the legs lift the body toward its stance
|
||||
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;
|
||||
@@ -166,6 +167,8 @@ public partial class PlayerRagdoll : Node3D
|
||||
private readonly Vector3[] _girdle = new Vector3[2]; // True Body shoulder offset, chest frame
|
||||
private float _footY;
|
||||
private float _supportGoalY;
|
||||
private RigidBody3D _groundBody;
|
||||
private Vector3 _groundPoint;
|
||||
private float _tuckFree, _dt;
|
||||
private PhysicsShapeQueryParameters3D _legsQuery;
|
||||
|
||||
@@ -752,10 +755,13 @@ public partial class PlayerRagdoll : Node3D
|
||||
Godot.Collections.Dictionary hit = GetWorld3D().DirectSpaceState.IntersectRay(query);
|
||||
bool wasGrounded = Grounded;
|
||||
Grounded = false;
|
||||
_groundBody = null;
|
||||
if (hit.Count > 0)
|
||||
{
|
||||
Vector3 point = (Vector3)hit["position"];
|
||||
Vector3 normal = (Vector3)hit["normal"];
|
||||
_groundBody = GodotObject.InstanceFromId((ulong)hit["collider_id"]) as RigidBody3D; // Box3D: "collider" is null
|
||||
_groundPoint = point;
|
||||
bool inReach = pos.Y - point.Y < hipAboveFloor + GroundProbeExtra;
|
||||
bool steep = normal.Y <= Mathf.Cos(Mathf.DegToRad(MaxSlopeDegrees));
|
||||
if (inReach && steep && wasGrounded && point.Y <= _footY + MaxStepHeight)
|
||||
@@ -806,7 +812,12 @@ public partial class PlayerRagdoll : Node3D
|
||||
float w = Mathf.Tau * GroundFrequency;
|
||||
Vector3 accel = (goal - pos) * (w * w) + (targetVelocity - Pelvis.LinearVelocity) * (2f * GroundDamping * w);
|
||||
Vector3 force = accel * BodyMass;
|
||||
float support = Mathf.Clamp(force.Y + weight, 0f, LegStrength * weight);
|
||||
// Vertical as a rate-limited velocity servo: full leg strength brakes a landing, but
|
||||
// the legs raise the body at most MaxRiseSpeed — they can push, not pull, so a hard push
|
||||
// toward a higher stance (onto a ledge) would otherwise overshoot into a jump.
|
||||
float riseVelocity = Mathf.Clamp((goal.Y - pos.Y) * w, -3f, MaxRiseSpeed) + targetVelocity.Y;
|
||||
float verticalAccel = (riseVelocity - Pelvis.LinearVelocity.Y) * (2f * GroundDamping * w);
|
||||
float support = Mathf.Clamp(verticalAccel * BodyMass + weight, 0f, LegStrength * weight);
|
||||
float carry = Mathf.Max(GroundFriction * support, _legsSolid ? 0f : CrawlCarry);
|
||||
// Hips held back by an obstacle: lean on it, don't bulldoze — full leg force tipped the
|
||||
// body over knee-high boxes and steep slopes (and the riding view tipped with it).
|
||||
@@ -814,6 +825,10 @@ public partial class PlayerRagdoll : Node3D
|
||||
carry = Mathf.Min(carry, BlockedPush);
|
||||
Vector3 horizontal = new Vector3(force.X, 0f, force.Z).LimitLength(carry);
|
||||
Pelvis.ApplyCentralForce(horizontal + Vector3.Up * support);
|
||||
// Newton's third law: standing on something movable (seesaw, plank, crate) pushes it
|
||||
// down — the feet aren't physical, so the reaction is applied where the ground ray hit.
|
||||
if (_groundBody != null && IsInstanceValid(_groundBody))
|
||||
_groundBody.ApplyForce(-(horizontal + Vector3.Up * support), _groundPoint - _groundBody.GlobalPosition);
|
||||
}
|
||||
else if (Braced)
|
||||
{
|
||||
|
||||
@@ -186,8 +186,8 @@ public partial class Playground : Node3D
|
||||
HingeJoint3D pivot = new()
|
||||
{
|
||||
Transform = new Transform3D(new Basis(Vector3.Right, Vector3.Up, Vector3.Back), new Vector3(-8.5f, 0.48f, -1.5f)),
|
||||
NodeA = GetPathTo(GetNode("SeesawPivot")),
|
||||
NodeB = GetPathTo(seesaw),
|
||||
NodeA = GetNode<Node>("SeesawPivot").GetPath(), // absolute: joint paths resolve relative to the joint
|
||||
NodeB = seesaw.GetPath(),
|
||||
};
|
||||
AddChild(pivot);
|
||||
Sign("Seesaw", new Vector3(-8.5f, 1.3f, -1.5f));
|
||||
@@ -200,8 +200,8 @@ public partial class Playground : Node3D
|
||||
HingeJoint3D swing = new()
|
||||
{
|
||||
Transform = new Transform3D(new Basis(Vector3.Back, Vector3.Up, Vector3.Left), new Vector3(-6f, 2.35f, 1.5f)),
|
||||
NodeA = GetPathTo(beam),
|
||||
NodeB = GetPathTo(board),
|
||||
NodeA = beam.GetPath(),
|
||||
NodeB = board.GetPath(),
|
||||
};
|
||||
AddChild(swing);
|
||||
Sign("Hanging board", new Vector3(-6f, 2.9f, 1.5f));
|
||||
|
||||
+24
-1
@@ -214,7 +214,30 @@ public partial class PropGrabber : Node
|
||||
nearest = NearestProp(handBody.GlobalPosition, CenterGrabRadius);
|
||||
if (nearest != null)
|
||||
return nearest;
|
||||
return WorldUnderPalm(palm, handBody.GetWorld3D()) ?? WorldUnderPalm(handBody.GlobalPosition, handBody.GetWorld3D());
|
||||
// Big props (planks, seesaw, boards): their origin is far from the hand — use the surface.
|
||||
World3D world = handBody.GetWorld3D();
|
||||
return BodyUnderPalm(palm, world) ?? BodyUnderPalm(handBody.GlobalPosition, world);
|
||||
}
|
||||
|
||||
// The body whose surface is near the point: a "prop" first, else static world (climbing).
|
||||
public static PhysicsBody3D BodyUnderPalm(Vector3 point, World3D world)
|
||||
{
|
||||
PhysicsShapeQueryParameters3D query = new()
|
||||
{
|
||||
Shape = new SphereShape3D { Radius = WorldGrabRadius },
|
||||
Transform = new Transform3D(Basis.Identity, point),
|
||||
CollisionMask = 1,
|
||||
};
|
||||
StaticBody3D found = null;
|
||||
foreach (Godot.Collections.Dictionary hit in world.DirectSpaceState.IntersectShape(query, 8))
|
||||
{
|
||||
GodotObject o = GodotObject.InstanceFromId((ulong)hit["collider_id"]);
|
||||
if (o is RigidBody3D prop && prop.IsInGroup("prop"))
|
||||
return prop;
|
||||
if (o is StaticBody3D body && !body.IsInGroup(NoGrabGroup))
|
||||
found ??= body;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
// Climbing: any static body near the palm (except the "no_grab" group) can be held.
|
||||
|
||||
+5
-2
@@ -34,8 +34,11 @@ public partial class PropShop : Node3D
|
||||
|
||||
public void Respawn()
|
||||
{
|
||||
foreach (Node node in GetTree().GetNodesInGroup("prop"))
|
||||
node.QueueFree();
|
||||
// Only the table's own props (the playground's dynamic pieces are props too — they reset
|
||||
// in place via Playground.Reset).
|
||||
foreach (Node node in GetChildren())
|
||||
if (node is RigidBody3D && node.IsInGroup("prop"))
|
||||
node.QueueFree();
|
||||
foreach (PropSpec spec in Specs)
|
||||
SpawnProp(spec);
|
||||
GD.Print("PropShop: table reset");
|
||||
|
||||
+135
-33
@@ -103,8 +103,10 @@ public partial class RagdollSpike : Node3D
|
||||
_phases.Add(("holds a deep crouch (eyes 0.7 m) without sliding or spinning", Rest, t => TickCrouchHold(0.7f), 5f, FinishCrouchHold));
|
||||
_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(("climbs a wall hand over hand (grab, pull, regrab higher)", SetupClimbWall, TickClimbWall, 6.5f, FinishClimbWall));
|
||||
_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(("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(("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(("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();
|
||||
@@ -666,8 +668,27 @@ public partial class RagdollSpike : Node3D
|
||||
_stage.AddChild(MakeStatic(new Vector3(3f, 1f, 3f), new Vector3(0f, 0.5f, 1f), 29));
|
||||
}
|
||||
|
||||
private float _dropEyeErr, _dropMinPelvis = float.MaxValue;
|
||||
|
||||
private void TickDrop(float dt)
|
||||
{
|
||||
_stickVelocity = _t > 0.5f && _t < 2f ? new Vector3(0f, 0f, -1.2f) : Vector3.Zero;
|
||||
if (_ragdoll.Built && !_needBuild && _t > 0.5f)
|
||||
{
|
||||
_dropEyeErr = Mathf.Max(_dropEyeErr, _ragdoll.Eye.DistanceTo(Camera.Origin));
|
||||
if (_ragdoll.Grounded && _ragdoll.GroundY < 0.5f)
|
||||
_dropMinPelvis = Mathf.Min(_dropMinPelvis, _ragdoll.Pelvis.GlobalPosition.Y);
|
||||
}
|
||||
}
|
||||
|
||||
private bool FinishDrop()
|
||||
{
|
||||
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)");
|
||||
bool landed = _dropEyeErr < 0.15f && _dropMinPelvis > 0.7f;
|
||||
_dropEyeErr = 0f;
|
||||
_dropMinPelvis = float.MaxValue;
|
||||
if (!landed)
|
||||
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}");
|
||||
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;
|
||||
@@ -776,7 +797,7 @@ public partial class RagdollSpike : Node3D
|
||||
private void TickHumanMantle(bool both)
|
||||
{
|
||||
float t = _t;
|
||||
_stickVelocity = t < 1f || t > 4.5f ? new Vector3(0f, 0f, -0.8f) : Vector3.Zero;
|
||||
_stickVelocity = t < 1f || (t > 4.5f && t < 5.5f) ? new Vector3(0f, 0f, -0.8f) : Vector3.Zero; // stop before the ledge's far end
|
||||
float crouch = Mathf.SmoothStep(0f, 1f, Mathf.Clamp((t - 1f) / 0.8f, 0f, 1f));
|
||||
float rise = Mathf.SmoothStep(0f, 1f, Mathf.Clamp((t - 2.2f) / 1.6f, 0f, 1f));
|
||||
float eye = EyeHeight - 0.25f * crouch + 0.35f * rise;
|
||||
@@ -833,58 +854,139 @@ public partial class RagdollSpike : Node3D
|
||||
|
||||
// Hold targets live in the world (the holds); while gripping, the player pulls the controllers
|
||||
// down in the room. Free hands reach for the next hold 45 cm higher.
|
||||
private readonly Vector3[] _gripLocal = new Vector3[2];
|
||||
private readonly float[] _pullAtGrip = new float[2];
|
||||
private int _climbStep; // 0 reach+grip both, 1 pull, 2 right moves up, 3 left moves up, 4 pull, 5 done
|
||||
private float _climbStepT, _pulled;
|
||||
|
||||
// Like a climber: grip two holds, pull (hands go down ~45 cm relative to the BODY/room — the
|
||||
// play space rides up with the body), move one hand to a higher hold and only then the other,
|
||||
// pull again.
|
||||
private void TickClimbWall(float dt)
|
||||
{
|
||||
float t = _t;
|
||||
_measureFrom = float.MaxValue;
|
||||
if (!_ragdoll.Built || _needBuild)
|
||||
return;
|
||||
float pull1 = Mathf.Clamp((t - 1.0f) / 1.2f, 0f, 1f), pull2 = Mathf.Clamp((t - 4.0f) / 1.2f, 0f, 1f);
|
||||
if (t > 0.95f && t < 1.0f)
|
||||
if (t < dt * 1.5f)
|
||||
{
|
||||
Grip(0, true);
|
||||
Grip(1, true);
|
||||
_climbStartPelvis = _ragdoll.Pelvis.GlobalPosition.Y;
|
||||
_climbStep = 0;
|
||||
_climbStepT = 0f;
|
||||
_pulled = 0f;
|
||||
}
|
||||
_climbStepT += dt;
|
||||
bool pulling = _climbStep == 1 || _climbStep == 4;
|
||||
if (pulling)
|
||||
_pulled = Mathf.MoveToward(_pulled, _climbStep == 1 ? 0.45f : 0.9f, 0.4f * dt);
|
||||
Basis aim = Aim(Vector3.Up + Vector3.Forward * 0.15f);
|
||||
for (int side = 0; side < 2; side++)
|
||||
{
|
||||
float releaseAt = side == 1 ? 2.3f : 3.1f, regripAt = releaseAt + 0.6f;
|
||||
if (t > releaseAt && t < regripAt && _climbWeld[side] != null)
|
||||
{
|
||||
Grip(side, false);
|
||||
// Next hold: 30 cm above the hand that is still holding (as a climber reaches).
|
||||
_gripWorld[side] = _ragdoll.Hand(1 - side).GlobalPosition + Vector3.Up * 0.3f + Vector3.Right * (side == 0 ? -0.4f : 0.4f);
|
||||
_gripWorld[side].Z = ClimbFace + 0.05f;
|
||||
}
|
||||
if (t >= regripAt && _climbWeld[side] == null && _ragdoll.Hand(side).GlobalPosition.DistanceTo(_gripWorld[side]) < 0.08f)
|
||||
Grip(side, true);
|
||||
// Gripping: the controller is pulled down in the room by the pull amount; reaching: it
|
||||
// goes to the next hold.
|
||||
Vector3 target = _climbWeld[side] != null || t < 1.0f
|
||||
? _gripWorld[side] - _origin.Origin - Vector3.Up * (0.5f * (pull1 + pull2))
|
||||
: _gripWorld[side] - _origin.Origin;
|
||||
// Reach like a person: ease from where the hand is to the hold over ~0.5 s.
|
||||
Vector3 current = side == 0 ? _leftLocal.Origin : _rightLocal.Origin;
|
||||
if (_climbWeld[side] == null)
|
||||
target = current.MoveToward(target, 1.2f * dt);
|
||||
Basis aim = Aim(Vector3.Up + Vector3.Forward * 0.15f);
|
||||
if (side == 0)
|
||||
_leftLocal = new Transform3D(aim, target);
|
||||
Vector3 local;
|
||||
if (_climbWeld[side] != null)
|
||||
local = _gripLocal[side] - Vector3.Up * (_pulled - _pullAtGrip[side]);
|
||||
else
|
||||
_rightLocal = new Transform3D(aim, target);
|
||||
{
|
||||
Vector3 current = side == 0 ? _leftLocal.Origin : _rightLocal.Origin;
|
||||
local = current.MoveToward(_gripWorld[side] - _origin.Origin, 1.2f * dt);
|
||||
if (_ragdoll.Hand(side).GlobalPosition.DistanceTo(_gripWorld[side]) < 0.08f && (_climbStep == 0 || _climbStep == 2 + (1 - side)))
|
||||
{
|
||||
Grip(side, true);
|
||||
_gripLocal[side] = local;
|
||||
_pullAtGrip[side] = _pulled;
|
||||
}
|
||||
}
|
||||
if (side == 0)
|
||||
_leftLocal = new Transform3D(aim, local);
|
||||
else
|
||||
_rightLocal = new Transform3D(aim, local);
|
||||
}
|
||||
switch (_climbStep)
|
||||
{
|
||||
case 0 when _climbWeld[0] != null && _climbWeld[1] != null:
|
||||
_climbStartPelvis = _ragdoll.Pelvis.GlobalPosition.Y;
|
||||
Next(1);
|
||||
break;
|
||||
case 1 when _pulled >= 0.45f:
|
||||
MoveUp(1);
|
||||
Next(2);
|
||||
break;
|
||||
case 2 when _climbWeld[1] != null && _climbStepT > 0.2f:
|
||||
MoveUp(0);
|
||||
Next(3);
|
||||
break;
|
||||
case 3 when _climbWeld[0] != null && _climbStepT > 0.2f:
|
||||
Next(4);
|
||||
break;
|
||||
case 4 when _pulled >= 0.9f:
|
||||
Next(5);
|
||||
break;
|
||||
}
|
||||
if (t > 1f)
|
||||
_climbEyeErr = Mathf.Max(_climbEyeErr, _ragdoll.Eye.DistanceTo(Camera.Origin));
|
||||
|
||||
void Next(int step)
|
||||
{
|
||||
_climbStep = step;
|
||||
_climbStepT = 0f;
|
||||
}
|
||||
|
||||
void MoveUp(int side)
|
||||
{
|
||||
Grip(side, false);
|
||||
// Next hold: 30 cm above the hand that keeps holding.
|
||||
_gripWorld[side] = _ragdoll.Hand(1 - side).GlobalPosition + Vector3.Up * 0.3f + Vector3.Right * (side == 0 ? -0.4f : 0.4f);
|
||||
_gripWorld[side].Z = ClimbFace + 0.05f;
|
||||
}
|
||||
}
|
||||
|
||||
private bool FinishClimbWall()
|
||||
{
|
||||
float rise = _ragdoll.Pelvis.GlobalPosition.Y - _climbStartPelvis;
|
||||
GD.Print($"RAGDOLL: wall climb: pelvis rose {rise:F3} m (> 0.6), holding {_climbWeld[0] != null}/{_climbWeld[1] != null}, eye vs camera max {_climbEyeErr:F3} (< {PlayerRagdoll.MaxViewLead + 0.03f:F2}), snaps {NewSnaps}");
|
||||
GD.Print($"RAGDOLL: wall climb: reached step {_climbStep}/5, pelvis rose {rise:F3} m (> 0.35), holding {_climbWeld[0] != null}/{_climbWeld[1] != null}, eye vs camera max {_climbEyeErr:F3} (< {PlayerRagdoll.MaxViewLead + 0.03f:F2}), snaps {NewSnaps}");
|
||||
Grip(0, false);
|
||||
Grip(1, false);
|
||||
// MaxViewLead 0.08 is enforced before each step; this is measured after it (+ one tick of motion).
|
||||
return _finite && NewSnaps == 0 && rise > 0.6f && _climbEyeErr < PlayerRagdoll.MaxViewLead + 0.03f;
|
||||
// Two 45 cm pulls minus the sag while hanging one-handed during the two hand moves.
|
||||
return _finite && NewSnaps == 0 && _climbStep == 5 && rise > 0.35f && _climbEyeErr < PlayerRagdoll.MaxViewLead + 0.03f;
|
||||
}
|
||||
|
||||
private RigidBody3D _seesaw;
|
||||
|
||||
// A 3 m plank along Z hinged 0.45 m up at z = −1; the player stands on its near half.
|
||||
private void SetupSeesaw()
|
||||
{
|
||||
Rest();
|
||||
StaticBody3D pivot = MakeStatic(new Vector3(0.6f, 0.45f, 0.3f), new Vector3(0f, 0.225f, -1f), 29);
|
||||
_stage.AddChild(pivot);
|
||||
_seesaw = MakeBox(new Vector3(0.6f, 0.06f, 3f), 12f, new Vector3(0f, 0.48f, -1f));
|
||||
HingeJoint3D hinge = new() { Transform = new Transform3D(new Basis(Vector3.Back, Vector3.Up, Vector3.Left), new Vector3(0f, 0.48f, -1f)), NodeA = pivot.GetPath(), NodeB = _seesaw.GetPath() };
|
||||
_stage.AddChild(hinge);
|
||||
_origin = new Transform3D(Basis.Identity, new Vector3(0f, 0.51f, -0.2f)); // standing on the plank, 0.8 m from the pivot
|
||||
}
|
||||
|
||||
private bool FinishSeesaw()
|
||||
{
|
||||
float tilt = Mathf.RadToDeg(_seesaw.GlobalTransform.Basis.Z.AngleTo(Vector3.Back));
|
||||
GD.Print($"RAGDOLL: seesaw tilted {tilt:F1}° under the player (> 8), player feet y {_ragdoll.GroundY:F2}, snaps {NewSnaps}");
|
||||
return _finite && NewSnaps == 0 && tilt > 8f;
|
||||
}
|
||||
|
||||
private Playground _playground;
|
||||
|
||||
private void SetupPlayground()
|
||||
{
|
||||
Rest();
|
||||
_playground = new Playground { Position = new Vector3(0f, 0f, 40f) }; // far from the player
|
||||
_stage.AddChild(_playground);
|
||||
}
|
||||
|
||||
private bool FinishPlayground()
|
||||
{
|
||||
RigidBody3D board = _playground.GetNode<RigidBody3D>("SwingBoard"), seesaw = _playground.GetNode<RigidBody3D>("Seesaw"), plank = _playground.GetNode<RigidBody3D>("Plank0");
|
||||
Vector3 plankEnd = plank.GlobalTransform * new Vector3(0.9f, 0.05f, 0f);
|
||||
PhysicsBody3D grabbed = PropGrabber.BodyUnderPalm(plankEnd, plank.GetWorld3D());
|
||||
GD.Print($"RAGDOLL: playground: swing board y {board.GlobalPosition.Y:F2} (hangs ~1.75), seesaw y {seesaw.GlobalPosition.Y:F2} (on pivot ~0.48), plank end grab finds {(grabbed == null ? "nothing" : grabbed.Name)}");
|
||||
return board.GlobalPosition.Y > 1.5f && seesaw.GlobalPosition.Y > 0.35f && grabbed is RigidBody3D && grabbed.Name.ToString().StartsWith("Plank"); // planks are stacked
|
||||
}
|
||||
|
||||
// A slope rising away (−Z) from ObstacleFront up to 1 m, then a platform.
|
||||
|
||||
Reference in new issue
Block a user