Ground under the body: sphere footprint, clamber by pressure (Nimso), physics objects climbable, arms keep colliding
- Ground = centre ray + stance-wide sphere cast (face normal from a ray inside the contact): planks and lips keep support. - Higher ground is stood on when the hips are over it and the body presses onto it (hands braced, a just-finished press, legs tucked over it, or airborne) - no hips-well-above rule. - Bracing counts heavy/frozen physics objects (>= 15 kg), not what the hand carries. - Knee up: braced + hips held back tucks the legs collider. - Support goal capped at straight legs (tiptoe/over-tall head floated the body). - Arms never get collision exceptions with held objects (they disabled whole crag rocks). - Spike: heavy-box mantle, 15 cm plank, chest-high ledge two hands yes / one hand no. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3e300c7f04
commit
2d986d08ac
2 files changed
+172
-52
No files matched your search
+97
-42
@@ -187,7 +187,7 @@ public partial class PlayerRagdoll : Node3D
|
|||||||
private float _supportGoalY;
|
private float _supportGoalY;
|
||||||
private RigidBody3D _groundBody;
|
private RigidBody3D _groundBody;
|
||||||
private Vector3 _groundPoint;
|
private Vector3 _groundPoint;
|
||||||
private float _tuckFree, _dt;
|
private float _tuckFree, _dt, _sinceBraced = float.MaxValue;
|
||||||
private PhysicsShapeQueryParameters3D _legsQuery;
|
private PhysicsShapeQueryParameters3D _legsQuery;
|
||||||
|
|
||||||
private bool LegsSpaceFree(Vector3 center, Basis basis)
|
private bool LegsSpaceFree(Vector3 center, Basis basis)
|
||||||
@@ -599,6 +599,7 @@ public partial class PlayerRagdoll : Node3D
|
|||||||
HandGuide(a, _targets[a.Hand], side == 0 ? leftVelocity : rightVelocity);
|
HandGuide(a, _targets[a.Hand], side == 0 ? leftVelocity : rightVelocity);
|
||||||
Braced |= IsBraced(a);
|
Braced |= IsBraced(a);
|
||||||
}
|
}
|
||||||
|
_sinceBraced = Braced ? 0f : _sinceBraced + dt;
|
||||||
GroundControl(pelvisTarget, floorY, dt);
|
GroundControl(pelvisTarget, floorY, dt);
|
||||||
HeadGuide(s, dt);
|
HeadGuide(s, dt);
|
||||||
LegsDrive(s);
|
LegsDrive(s);
|
||||||
@@ -720,15 +721,24 @@ public partial class PlayerRagdoll : Node3D
|
|||||||
j.SetParamZ(Generic6DofJoint3D.Param.LinearMotorForceLimit, cap);
|
j.SetParamZ(Generic6DofJoint3D.Param.LinearMotorForceLimit, cap);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A hand pressing on or holding static world (a ledge, a wall, a grabbed bar) — not the floor:
|
// A hand pressing on or holding something solid enough to push yourself up on (a ledge, a wall,
|
||||||
// hands on the floor while crawling don't lift the body.
|
// a grabbed bar, a heavy crate) — not the floor: hands on the floor while crawling don't lift the
|
||||||
|
// body. Nimso: physics objects are climbed "the same" as the world. A light prop just gets pushed,
|
||||||
|
// and something you're carrying isn't support.
|
||||||
|
public const float BraceMinMass = 15f;
|
||||||
|
public const float ClamberMemory = 0.6f; // s a hand press still counts after the hands leave
|
||||||
|
|
||||||
private static bool IsBraced(Arm a)
|
private static bool IsBraced(Arm a)
|
||||||
{
|
{
|
||||||
if (a.Held is StaticBody3D)
|
if (a.Held is StaticBody3D || a.Held is RigidBody3D { Freeze: true })
|
||||||
return true;
|
return true;
|
||||||
foreach (Node3D n in a.Hand.GetCollidingBodies())
|
foreach (Node3D n in a.Hand.GetCollidingBodies())
|
||||||
if (n is StaticBody3D && !n.IsInGroup(PropGrabber.NoGrabGroup))
|
{
|
||||||
|
if (n == a.Held || n.IsInGroup(PropGrabber.NoGrabGroup))
|
||||||
|
continue;
|
||||||
|
if (n is StaticBody3D || n is RigidBody3D { Freeze: true } || n is RigidBody3D { Mass: >= BraceMinMass })
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,7 +758,10 @@ public partial class PlayerRagdoll : Node3D
|
|||||||
bool climbingHold = _arms[0].Held is StaticBody3D || _arms[1].Held is StaticBody3D;
|
bool climbingHold = _arms[0].Held is StaticBody3D || _arms[1].Held is StaticBody3D;
|
||||||
_lifted = Braced && (Pelvis.GlobalPosition.Y > _supportGoalY + MantleLift || climbingHold);
|
_lifted = Braced && (Pelvis.GlobalPosition.Y > _supportGoalY + MantleLift || climbingHold);
|
||||||
bool deep = center + _legsHeight * 0.5f >= hip.Y + LegsOffAbove;
|
bool deep = center + _legsHeight * 0.5f >= hip.Y + LegsOffAbove;
|
||||||
bool wantTuck = deep || _lifted || (!_legsSolid && Braced);
|
// Hands braced while the hips are held back by an obstacle = getting a knee up onto it (a
|
||||||
|
// thigh-high crate or ledge the straight arms can't press down on while standing).
|
||||||
|
bool kneeUp = Braced && HeldBack != Vector3.Zero;
|
||||||
|
bool wantTuck = deep || _lifted || kneeUp || (!_legsSolid && Braced);
|
||||||
_tuckFree = wantTuck ? 0f : _tuckFree + _dt;
|
_tuckFree = wantTuck ? 0f : _tuckFree + _dt;
|
||||||
Vector3 untucked = new(hip.X, center, hip.Z);
|
Vector3 untucked = new(hip.X, center, hip.Z);
|
||||||
bool solid = _legsSolid ? !wantTuck : _tuckFree > UntuckDelay && LegsSpaceFree(untucked, s.YawBasis);
|
bool solid = _legsSolid ? !wantTuck : _tuckFree > UntuckDelay && LegsSpaceFree(untucked, s.YawBasis);
|
||||||
@@ -845,16 +858,12 @@ public partial class PlayerRagdoll : Node3D
|
|||||||
{
|
{
|
||||||
Vector3 pos = Pelvis.GlobalPosition;
|
Vector3 pos = Pelvis.GlobalPosition;
|
||||||
float hipAboveFloor = target.Origin.Y - floorY;
|
float hipAboveFloor = target.Origin.Y - floorY;
|
||||||
var query = PhysicsRayQueryParameters3D.Create(pos, pos + Vector3.Down * (hipAboveFloor + GroundProbeExtra + 0.5f), WorldMask);
|
|
||||||
Godot.Collections.Dictionary hit = GetWorld3D().DirectSpaceState.IntersectRay(query);
|
|
||||||
bool wasGrounded = Grounded;
|
bool wasGrounded = Grounded;
|
||||||
Grounded = false;
|
Grounded = false;
|
||||||
_groundBody = null;
|
_groundBody = null;
|
||||||
if (hit.Count > 0)
|
if (ProbeGround(pos, hipAboveFloor + GroundProbeExtra + 0.5f, wasGrounded, out Vector3 point, out Vector3 normal, out GodotObject groundObject, out bool underHips))
|
||||||
{
|
{
|
||||||
Vector3 point = (Vector3)hit["position"];
|
_groundBody = groundObject as RigidBody3D;
|
||||||
Vector3 normal = (Vector3)hit["normal"];
|
|
||||||
_groundBody = GodotObject.InstanceFromId((ulong)hit["collider_id"]) as RigidBody3D; // Box3D: "collider" is null
|
|
||||||
_groundPoint = point;
|
_groundPoint = point;
|
||||||
// Standing (or walking down a step) the legs keep contact over GroundProbeExtra; falling,
|
// Standing (or walking down a step) the legs keep contact over GroundProbeExtra; falling,
|
||||||
// they touch down only within their reach — the rest of the fall is stopped by the legs.
|
// they touch down only within their reach — the rest of the fall is stopped by the legs.
|
||||||
@@ -873,14 +882,17 @@ public partial class PlayerRagdoll : Node3D
|
|||||||
}
|
}
|
||||||
else if (inReach && !steep)
|
else if (inReach && !steep)
|
||||||
{
|
{
|
||||||
// Step rule: ground more than a step above the feet is only stood on once the
|
// Step rule (Nimso: higher ground "doesn't move my body up … until I push on it with enough
|
||||||
// hips are already well above it (climbed/mantled/landed there) — otherwise it's an
|
// pressure, at which point I actually clamber over"): ground more than a step above the
|
||||||
// obstacle the legs collider holds us back from, and the feet stay where they were.
|
// feet is stood on once the hips are over it AND the body is pressing onto it — hands
|
||||||
// Higher ground climbed onto must be under the hips' back edge too (not just a
|
// braced on anything solid (ledge, crate), or already airborne
|
||||||
// pelvis centre hanging over the lip).
|
// (landing, hanging). Otherwise it's an obstacle the legs collider holds us back from.
|
||||||
bool clear = point.Y <= _footY + MaxStepHeight || GroundBehindAt(pos, point.Y);
|
// No "hips well above it" requirement: that made a ledge need a full press-up.
|
||||||
bool reachable = point.Y <= _footY + MaxStepHeight
|
bool step = point.Y <= _footY + MaxStepHeight;
|
||||||
|| ((!wasGrounded || _lifted || pos.Y - point.Y > 0.6f * hipAboveFloor) && clear);
|
// The press lasts a moment after the hands leave (you push up, then move over it), and legs
|
||||||
|
// tucked up with the hips over it are already on top of it.
|
||||||
|
bool pressing = !wasGrounded || Braced || _lifted || _sinceBraced < ClamberMemory || !_legsSolid;
|
||||||
|
bool reachable = step || (underHips && pressing);
|
||||||
Grounded = true;
|
Grounded = true;
|
||||||
if (reachable)
|
if (reachable)
|
||||||
_footY = point.Y;
|
_footY = point.Y;
|
||||||
@@ -915,6 +927,9 @@ public partial class PlayerRagdoll : Node3D
|
|||||||
{
|
{
|
||||||
// Pose heights are relative to the tracking floor; stand on the ground actually under us.
|
// Pose heights are relative to the tracking floor; stand on the ground actually under us.
|
||||||
Vector3 goal = target.Origin + Vector3.Up * (GroundY - floorY);
|
Vector3 goal = target.Origin + Vector3.Up * (GroundY - floorY);
|
||||||
|
// Legs can't hold the hips higher than straight legs (head above the calibrated height —
|
||||||
|
// tiptoes, a reach up — would otherwise float the body off the ground).
|
||||||
|
goal.Y = Mathf.Min(goal.Y, _footY + _standingHip + JumpExtension);
|
||||||
_supportGoalY = goal.Y;
|
_supportGoalY = goal.Y;
|
||||||
if (_jump == JumpPhase.Dip)
|
if (_jump == JumpPhase.Dip)
|
||||||
goal.Y = Mathf.Min(goal.Y, _jumpFromY);
|
goal.Y = Mathf.Min(goal.Y, _jumpFromY);
|
||||||
@@ -1070,16 +1085,64 @@ public partial class PlayerRagdoll : Node3D
|
|||||||
|
|
||||||
private PhysicsShapeQueryParameters3D _cameraQuery;
|
private PhysicsShapeQueryParameters3D _cameraQuery;
|
||||||
|
|
||||||
// Second ground ray 12 cm behind the hips (toward the body's back): ground at least `groundY`.
|
// Ground under the body (Nimso: "ground detection uses the objects underneath the body"), not
|
||||||
private bool GroundBehindAt(Vector3 pelvisPos, float groundY)
|
// under one point: a sphere the width of the stance is cast down from the hips, plus the
|
||||||
|
// centre ray. One ray lost the ground as soon as the hips swayed off a plank or past a ledge lip.
|
||||||
|
// The sphere's contact can be a corner, whose normal is useless — the surface normal comes from a
|
||||||
|
// short ray just inside the contact. Same-level ground (within a step) from either probe is
|
||||||
|
// stood on; higher ground only from the centre ray (the hips must really be over it), and it's
|
||||||
|
// reported via underHips for the pressure rule.
|
||||||
|
public const float FootprintRadius = 0.12f;
|
||||||
|
|
||||||
|
private bool ProbeGround(Vector3 pos, float length, bool wasGrounded, out Vector3 point, out Vector3 normal, out GodotObject collider, out bool underHips)
|
||||||
{
|
{
|
||||||
Vector3 back = Pelvis.GlobalTransform.Basis.Z;
|
PhysicsDirectSpaceState3D space = GetWorld3D().DirectSpaceState;
|
||||||
back.Y = 0f;
|
point = normal = Vector3.Zero;
|
||||||
back = back.LengthSquared() > 1e-6f ? back.Normalized() * 0.12f : Vector3.Zero;
|
collider = null;
|
||||||
var query = PhysicsRayQueryParameters3D.Create(pelvisPos + back, pelvisPos + back + Vector3.Down * 3f, WorldMask);
|
underHips = false;
|
||||||
Godot.Collections.Dictionary hit = GetWorld3D().DirectSpaceState.IntersectRay(query);
|
Godot.Collections.Dictionary ray = space.IntersectRay(PhysicsRayQueryParameters3D.Create(pos, pos + Vector3.Down * length, WorldMask));
|
||||||
return hit.Count > 0 && ((Vector3)hit["position"]).Y > groundY - 0.05f;
|
bool haveRay = ray.Count > 0;
|
||||||
|
if (haveRay)
|
||||||
|
{
|
||||||
|
point = (Vector3)ray["position"];
|
||||||
|
normal = (Vector3)ray["normal"];
|
||||||
|
collider = GodotObject.InstanceFromId((ulong)ray["collider_id"]); // Box3D: "collider" is null
|
||||||
|
underHips = true;
|
||||||
}
|
}
|
||||||
|
_footprintQuery ??= new PhysicsShapeQueryParameters3D { Shape = new SphereShape3D { Radius = FootprintRadius }, CollisionMask = WorldMask };
|
||||||
|
_footprintQuery.Transform = new Transform3D(Basis.Identity, pos);
|
||||||
|
_footprintQuery.Motion = Vector3.Down * length;
|
||||||
|
Godot.Collections.Dictionary rest = space.GetRestInfo(_footprintQuery);
|
||||||
|
if (rest.Count == 0)
|
||||||
|
return haveRay;
|
||||||
|
Vector3 contact = (Vector3)rest["point"];
|
||||||
|
if (contact.Y >= pos.Y - 0.05f)
|
||||||
|
return haveRay; // touching a wall beside the hips, not ground
|
||||||
|
Vector3 inward = new(contact.X - pos.X, 0f, contact.Z - pos.Z);
|
||||||
|
inward = inward.LengthSquared() > 1e-6f ? inward.Normalized() * 0.02f : Vector3.Zero;
|
||||||
|
Vector3 probe = contact + inward;
|
||||||
|
Godot.Collections.Dictionary face = space.IntersectRay(PhysicsRayQueryParameters3D.Create(
|
||||||
|
new Vector3(probe.X, contact.Y + 0.05f, probe.Z), new Vector3(probe.X, contact.Y - 0.05f, probe.Z), WorldMask));
|
||||||
|
if (face.Count == 0)
|
||||||
|
return haveRay;
|
||||||
|
Vector3 sPoint = (Vector3)face["position"], sNormal = (Vector3)face["normal"];
|
||||||
|
if (sNormal.Y <= Mathf.Cos(Mathf.DegToRad(MaxSlopeDegrees)))
|
||||||
|
return haveRay;
|
||||||
|
// The sphere wins where the centre ray found nothing, nothing walkable, or only lower ground
|
||||||
|
// within the stance (a plank's edge under the hips) — but never higher ground than a step.
|
||||||
|
bool sameLevel = sPoint.Y <= _footY + MaxStepHeight || !wasGrounded;
|
||||||
|
bool better = !haveRay || normal.Y <= Mathf.Cos(Mathf.DegToRad(MaxSlopeDegrees)) || sPoint.Y > point.Y + 0.01f;
|
||||||
|
if (sameLevel && better)
|
||||||
|
{
|
||||||
|
point = sPoint;
|
||||||
|
normal = sNormal;
|
||||||
|
collider = GodotObject.InstanceFromId((ulong)face["collider_id"]);
|
||||||
|
underHips = true; // under the footprint
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PhysicsShapeQueryParameters3D _footprintQuery;
|
||||||
|
|
||||||
public void TeleportBy(Transform3D delta)
|
public void TeleportBy(Transform3D delta)
|
||||||
{
|
{
|
||||||
@@ -1118,23 +1181,15 @@ public partial class PlayerRagdoll : Node3D
|
|||||||
b.ResetPhysicsInterpolation();
|
b.ResetPhysicsInterpolation();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collision exceptions between a held prop and that arm's segments (its hand is excluded by
|
// What each hand holds (bracing, climbing). Only the hand itself is excluded from the held object
|
||||||
// the grab weld itself) — a forearm wrapped around a held object must not fight it.
|
// (by the grab weld).
|
||||||
public void SetHeld(int side, PhysicsBody3D prop)
|
public void SetHeld(int side, PhysicsBody3D prop)
|
||||||
{
|
{
|
||||||
if (!Built)
|
if (!Built)
|
||||||
return;
|
return;
|
||||||
Arm a = _arms[side];
|
// The arm keeps colliding with what the hand holds (owner: real arms don't pass through things).
|
||||||
if (a.Held != null && IsInstanceValid(a.Held) && a.Held != _arms[1 - side].Held)
|
// The old per-object exception switched the arm off against a whole crag rock while
|
||||||
{
|
// gripping one of its holds — arms "randomly" phased through the world mid-climb.
|
||||||
a.Upper.RemoveCollisionExceptionWith(a.Held);
|
_arms[side].Held = prop;
|
||||||
a.Fore.RemoveCollisionExceptionWith(a.Held);
|
|
||||||
}
|
|
||||||
a.Held = prop;
|
|
||||||
if (prop != null)
|
|
||||||
{
|
|
||||||
a.Upper.AddCollisionExceptionWith(prop);
|
|
||||||
a.Fore.AddCollisionExceptionWith(prop);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+75
-10
@@ -100,7 +100,10 @@ public partial class RagdollSpike : Node3D
|
|||||||
_phases.Add(("a table can't be walked over", SetupTableObstacle, _ => _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(("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 (crouch, hands on the edge, stand and press)", () => SetupObstacle(0.9f), _ => TickHumanMantle(both: true), 7f, () => FinishMantle(true)));
|
_phases.Add(("two-handed mantle onto a 0.9 m ledge (crouch, hands on the edge, stand and press)", () => SetupObstacle(0.9f), _ => TickHumanMantle(both: true), 7f, () => FinishMantle(true)));
|
||||||
_phases.Add(("one hand can't mantle", () => SetupObstacle(0.9f), _ => TickHumanMantle(both: false), 7f, () => FinishMantle(false)));
|
_phases.Add(("two-handed mantle onto a chest-high 1.2 m ledge", () => SetupObstacle(1.2f), _ => TickHumanMantle(both: true), 7f, () => FinishMantle(true)));
|
||||||
|
_phases.Add(("one hand can't mantle the 1.2 m ledge (the hips must be lifted)", () => SetupObstacle(1.2f), _ => TickHumanMantle(both: false), 7f, () => FinishMantle(false)));
|
||||||
|
_phases.Add(("mantles onto the playground's heavy box (25 kg physics object, 0.7 m)", SetupCrateLedge, _ => TickHumanMantle(both: true), 8.5f, FinishCrateMantle));
|
||||||
|
_phases.Add(("walks a 15 cm plank swaying the hips without falling off", SetupPlank, TickPlank, 6f, FinishPlank));
|
||||||
_phases.Add(("two-handed pull-up on a grabbed bar", () => SetupBarHang(), t => TickPullUp(both: true), 3.5f, () => FinishPullUp(true)));
|
_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(("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(("crawls through a 0.6 m tunnel", SetupTunnel, TickCrawl, 7f, FinishCrawl));
|
||||||
@@ -531,9 +534,59 @@ public partial class RagdollSpike : Node3D
|
|||||||
private void SetupObstacle(float height, float depth = 2f)
|
private void SetupObstacle(float height, float depth = 2f)
|
||||||
{
|
{
|
||||||
Rest();
|
Rest();
|
||||||
|
_ledgeBox = null;
|
||||||
|
_ledgeTop = height;
|
||||||
|
if (height >= 1f)
|
||||||
|
_origin = new Transform3D(Basis.Identity, new Vector3(0f, 0f, ObstacleFront + 0.4f));
|
||||||
_stage.AddChild(MakeStatic(new Vector3(3f, height, depth), new Vector3(0f, height * 0.5f, ObstacleFront - depth * 0.5f), 29));
|
_stage.AddChild(MakeStatic(new Vector3(3f, height, depth), new Vector3(0f, height * 0.5f, ObstacleFront - depth * 0.5f), 29));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Playground.HeavyBox (25 kg, 0.7 m cube) as the ledge, same human mantle as onto the static ledge.
|
||||||
|
private void SetupCrateLedge()
|
||||||
|
{
|
||||||
|
Rest();
|
||||||
|
_ledgeBox = MakeBox(Vector3.One * 0.7f, 25f, new Vector3(0f, 0.35f, ObstacleFront - 0.35f));
|
||||||
|
_ledgeTop = 0.7f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RigidBody3D _ledgeBox;
|
||||||
|
private float _ledgeTop = 0.9f;
|
||||||
|
|
||||||
|
private bool FinishCrateMantle()
|
||||||
|
{
|
||||||
|
GD.Print($"RAGDOLL: crate mantle: pelvis rose up to {_maxPelvisRise:F3} m, feet y {_ragdoll.GroundY:F3}, origin y {_origin.Origin.Y:F3}, snaps {NewSnaps}");
|
||||||
|
_maxPelvisRise = 0f;
|
||||||
|
return _finite && NewSnaps == 0 && _ragdoll.GroundY > 0.6f && _origin.Origin.Y > 0.55f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private const float PlankY = 0.3f;
|
||||||
|
private float _plankMinFoot;
|
||||||
|
|
||||||
|
private void SetupPlank()
|
||||||
|
{
|
||||||
|
Rest();
|
||||||
|
_origin = new Transform3D(Basis.Identity, new Vector3(0f, PlankY, 0f));
|
||||||
|
_stage.AddChild(MakeStatic(new Vector3(0.15f, PlankY, 8f), new Vector3(0f, PlankY * 0.5f, -3f), 29));
|
||||||
|
_plankMinFoot = float.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk along it with the head (and so the hips) swaying ±9 cm side to side.
|
||||||
|
private void TickPlank(float dt)
|
||||||
|
{
|
||||||
|
float sway = _t > 1f ? 0.09f * Mathf.Sin(Mathf.Tau * 0.7f * (_t - 1f)) : 0f;
|
||||||
|
_headLocal = new Transform3D(Basis.Identity, new Vector3(sway, EyeHeight, 0f));
|
||||||
|
_stickVelocity = _t > 1f && _t < 5f ? new Vector3(0f, 0f, -0.6f) : Vector3.Zero;
|
||||||
|
_measureFrom = float.MaxValue;
|
||||||
|
if (_ragdoll.Built && !_needBuild && _t > 0.5f)
|
||||||
|
_plankMinFoot = Mathf.Min(_plankMinFoot, _ragdoll.Grounded ? _ragdoll.GroundY : -1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool FinishPlank()
|
||||||
|
{
|
||||||
|
GD.Print($"RAGDOLL: plank: lowest supported foot level {_plankMinFoot:F3} (plank top {PlankY}), pelvis {_ragdoll.Pelvis.GlobalPosition}, eye error {_ragdoll.Eye.DistanceTo(Camera.Origin):F3}, snaps {NewSnaps}");
|
||||||
|
return _finite && NewSnaps == 0 && _plankMinFoot > PlankY - 0.03f && _ragdoll.Pelvis.GlobalPosition.Z < -1.8f;
|
||||||
|
}
|
||||||
|
|
||||||
private void SetupTableObstacle()
|
private void SetupTableObstacle()
|
||||||
{
|
{
|
||||||
Rest();
|
Rest();
|
||||||
@@ -562,7 +615,7 @@ public partial class RagdollSpike : Node3D
|
|||||||
private bool FinishMantle(bool both)
|
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}");
|
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;
|
bool onTop = _ragdoll.GroundY > _ledgeTop - 0.05f && _origin.Origin.Y > _ledgeTop - 0.1f;
|
||||||
_lastRise = _maxPelvisRise;
|
_lastRise = _maxPelvisRise;
|
||||||
_maxPelvisRise = 0f;
|
_maxPelvisRise = 0f;
|
||||||
return _finite && NewSnaps == 0 && (both ? onTop : !onTop); // standing tall also lifts the hips; one hand never gets you up
|
return _finite && NewSnaps == 0 && (both ? onTop : !onTop); // standing tall also lifts the hips; one hand never gets you up
|
||||||
@@ -949,20 +1002,32 @@ public partial class RagdollSpike : Node3D
|
|||||||
private void TickHumanMantle(bool both)
|
private void TickHumanMantle(bool both)
|
||||||
{
|
{
|
||||||
float t = _t;
|
float t = _t;
|
||||||
_stickVelocity = t < 1f || (t > 4.5f && t < 5.5f) ? new Vector3(0f, 0f, -0.8f) : Vector3.Zero; // stop before the ledge's far end
|
// Walk up to a waist-high ledge (stop before its far end); a chest-high one starts within reach.
|
||||||
float crouch = Mathf.SmoothStep(0f, 1f, Mathf.Clamp((t - 1f) / 0.8f, 0f, 1f));
|
_stickVelocity = (t < 1f && _ledgeTop < 1f) || (t > 4.5f && t < 5.5f) ? new Vector3(0f, 0f, -0.8f) : Vector3.Zero;
|
||||||
|
// Crouch to load the legs for a waist-high ledge; at a chest-high one you reach up instead.
|
||||||
|
float crouch = Mathf.SmoothStep(0f, 1f, Mathf.Clamp((t - 1f) / 0.8f, 0f, 1f)) * (_ledgeTop < 1f ? 1f : 0f);
|
||||||
float rise = Mathf.SmoothStep(0f, 1f, Mathf.Clamp((t - 2.2f) / 1.6f, 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;
|
float eye = EyeHeight - 0.25f * crouch + 0.35f * rise;
|
||||||
float lean = 0.25f * rise;
|
float lean = (_ledgeTop < 1f ? 0.25f : 0.55f) * rise; // over a chest-high edge you put your head well over it
|
||||||
_headLocal = new Transform3D(new Basis(Vector3.Right, -0.3f * crouch), new Vector3(0f, eye, -lean));
|
_headLocal = new Transform3D(new Basis(Vector3.Right, -0.3f * crouch), new Vector3(0f, eye, -lean));
|
||||||
float handZ = ObstacleFront - 0.08f - _origin.Origin.Z;
|
// Hands on the top just past the near edge (of the box itself when it's a movable one).
|
||||||
float handY = t < 1f ? 1.0f : Mathf.Lerp(0.95f, 0.55f, rise) - 0.05f * crouch;
|
float front = _ledgeBox != null ? _ledgeBox.GlobalPosition.Z + 0.35f : ObstacleFront;
|
||||||
|
float handZ = front - 0.08f - _origin.Origin.Z;
|
||||||
|
float handY = t < 1f ? 1.0f : Mathf.Lerp(_ledgeTop + 0.05f, _ledgeTop - 0.35f, rise) - 0.05f * crouch - _origin.Origin.Y;
|
||||||
Basis down = Aim(Vector3.Down + Vector3.Forward * 0.3f);
|
Basis down = Aim(Vector3.Down + Vector3.Forward * 0.3f);
|
||||||
_leftLocal = both ? new Transform3D(down, new Vector3(-0.2f, handY, handZ)) : new Transform3D(Basis.Identity, new Vector3(-0.2f, 1.0f, -0.3f));
|
// Walking up, the hands are down at the sides; then they go up over the edge onto the top.
|
||||||
_rightLocal = new Transform3D(down, new Vector3(0.2f, handY, handZ));
|
float reach = Mathf.Clamp((t - 0.8f) / 0.4f, 0f, 1f);
|
||||||
|
Vector3 rest = new(0.2f, 1.0f, -0.3f), onTop = new(0.2f, Mathf.Max(handY, _ledgeTop + 0.08f - _origin.Origin.Y), handZ);
|
||||||
|
Vector3 r = reach < 1f ? rest.Lerp(new Vector3(0.2f, _ledgeTop + 0.12f - _origin.Origin.Y, -0.3f), reach) : new Vector3(0.2f, handY, handZ);
|
||||||
|
if (reach >= 1f && t < 1.4f)
|
||||||
|
r = onTop;
|
||||||
|
_leftLocal = both ? new Transform3D(down, new Vector3(-r.X, r.Y, r.Z)) : new Transform3D(Basis.Identity, new Vector3(-0.2f, 1.0f, -0.3f));
|
||||||
|
_rightLocal = new Transform3D(down, r);
|
||||||
if (_ragdoll.Built && !_needBuild)
|
if (_ragdoll.Built && !_needBuild)
|
||||||
_maxPelvisRise = Mathf.Max(_maxPelvisRise, _ragdoll.Pelvis.GlobalPosition.Y - 0.99f);
|
_maxPelvisRise = Mathf.Max(_maxPelvisRise, _ragdoll.Pelvis.GlobalPosition.Y - 0.99f);
|
||||||
if (t > 4.5f)
|
if (OS.HasEnvironment("MANTLETRACE") && _ragdoll.Built && ((int)(t * 144f)) % 14 == 0)
|
||||||
|
GD.Print($"MT t {t:F2} pelvis {_ragdoll.Pelvis.GlobalPosition} v {_ragdoll.Pelvis.LinearVelocity.Y:F2} o {_origin.Origin.Y:F2} j {_ragdoll.Jumping} braced {_ragdoll.Braced} lifted {_ragdoll.Lifted} legs {_ragdoll.LegsSolid} g {_ragdoll.Grounded} foot {_ragdoll.GroundY:F2} hands {_ragdoll.Hand(0).GlobalPosition.Y:F2}/{_ragdoll.Hand(1).GlobalPosition.Y:F2} {(_stage.GetChildCount() > 0 && _stage.GetChild(0) is RigidBody3D rb ? rb.GlobalPosition.ToString() : "")}");
|
||||||
|
if (t > (_ledgeTop < 1f ? 4.5f : 5.5f))
|
||||||
{
|
{
|
||||||
_leftLocal = new Transform3D(Basis.Identity, new Vector3(-0.2f, 1.0f, -0.3f));
|
_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));
|
_rightLocal = new Transform3D(Basis.Identity, new Vector3(0.2f, 1.0f, -0.3f));
|
||||||
|
|||||||
Reference in new issue
Block a user