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
+36
-13
@@ -11,6 +11,8 @@ public partial class PropGrabber : Node
|
||||
private const float BreakTime = 0.25f;
|
||||
private const float BreakDistanceBoth = 0.4f;
|
||||
private const float GripRipDistance = 0.8f;
|
||||
private const float WorldGrabRadius = 0.05f; // palm sphere for grabbing static world (ledges, bars, holds)
|
||||
public const string NoGrabGroup = "no_grab"; // static bodies that can't be grabbed (the floor)
|
||||
|
||||
private static PropGrabber _instance;
|
||||
|
||||
@@ -18,7 +20,7 @@ public partial class PropGrabber : Node
|
||||
private XRController3D _right;
|
||||
private PhysicsArms _arms;
|
||||
|
||||
private readonly RigidBody3D[] _held = new RigidBody3D[2];
|
||||
private readonly PhysicsBody3D[] _held = new PhysicsBody3D[2]; // a prop, or static world (climbing)
|
||||
private readonly ConeTwistJoint3D[] _joints = new ConeTwistJoint3D[2];
|
||||
private readonly Vector3[] _localAnchor = new Vector3[2];
|
||||
private readonly uint[] _grabSeq = new uint[2];
|
||||
@@ -59,7 +61,7 @@ public partial class PropGrabber : Node
|
||||
|
||||
if (grip && !_gripPrev[hand] && _held[hand] == null)
|
||||
{
|
||||
RigidBody3D prop = FindProp(controller, hand);
|
||||
PhysicsBody3D prop = FindProp(controller, hand);
|
||||
RigidBody3D handBody = PhysicsHands.GetBody(controller);
|
||||
if (prop != null && handBody != null)
|
||||
{
|
||||
@@ -68,26 +70,27 @@ public partial class PropGrabber : Node
|
||||
Vector3 pivot = PalmPoint(handBody, hand);
|
||||
_joints[hand] = MakeWeld(handBody, prop, pivot, RigidWeldBias);
|
||||
_localAnchor[hand] = prop.GlobalTransform.AffineInverse() * pivot;
|
||||
_arms?.SetHeld(hand, prop);
|
||||
_arms?.SetHeld(hand, prop as RigidBody3D);
|
||||
PlayerRig.SetHeld(controller, prop);
|
||||
GD.Print($"PropGrabber: grabbed '{prop.Name}' ({prop.Mass:0.###} kg, {(hand == 0 ? "left" : "right")}{(_held[other] == prop ? ", two-handed" : "")})");
|
||||
string what = prop is RigidBody3D rb ? $"{rb.Mass:0.###} kg" : "static world";
|
||||
GD.Print($"PropGrabber: grabbed '{prop.Name}' ({what}, {(hand == 0 ? "left" : "right")}{(_held[other] == prop ? ", two-handed" : "")})");
|
||||
}
|
||||
}
|
||||
|
||||
if (!grip && _held[hand] != null)
|
||||
{
|
||||
RigidBody3D prop = _held[hand];
|
||||
PhysicsBody3D prop = _held[hand];
|
||||
_held[hand] = null;
|
||||
DestroyJoint(hand);
|
||||
if (IsInstanceValid(prop))
|
||||
GD.Print($"PropGrabber: released '{prop.Name}' at {prop.LinearVelocity.Length():F2} m/s");
|
||||
if (prop is RigidBody3D released && IsInstanceValid(released))
|
||||
GD.Print($"PropGrabber: released '{released.Name}' at {released.LinearVelocity.Length():F2} m/s");
|
||||
}
|
||||
_gripPrev[hand] = grip;
|
||||
}
|
||||
|
||||
private void HoldUpdate(XRController3D controller, int hand, float delta)
|
||||
{
|
||||
RigidBody3D body = _held[hand];
|
||||
PhysicsBody3D body = _held[hand];
|
||||
if (body == null)
|
||||
return;
|
||||
if (!IsInstanceValid(body))
|
||||
@@ -148,7 +151,7 @@ public partial class PropGrabber : Node
|
||||
}
|
||||
}
|
||||
|
||||
private int PrimaryHand(RigidBody3D body)
|
||||
private int PrimaryHand(PhysicsBody3D body)
|
||||
{
|
||||
int primary = -1;
|
||||
for (int hand = 0; hand < 2; hand++)
|
||||
@@ -159,7 +162,7 @@ public partial class PropGrabber : Node
|
||||
return primary;
|
||||
}
|
||||
|
||||
private ConeTwistJoint3D MakeWeld(RigidBody3D handBody, RigidBody3D prop, Vector3 pivot, float bias)
|
||||
private ConeTwistJoint3D MakeWeld(RigidBody3D handBody, PhysicsBody3D prop, Vector3 pivot, float bias)
|
||||
{
|
||||
ConeTwistJoint3D weld = new()
|
||||
{
|
||||
@@ -175,7 +178,7 @@ public partial class PropGrabber : Node
|
||||
return weld;
|
||||
}
|
||||
|
||||
private void BreakGrip(int hand, RigidBody3D body, string reason)
|
||||
private void BreakGrip(int hand, PhysicsBody3D body, string reason)
|
||||
{
|
||||
if (_held[hand] != body)
|
||||
return;
|
||||
@@ -199,7 +202,7 @@ public partial class PropGrabber : Node
|
||||
|
||||
private static float PalmOffset(int hand) => hand == 0 ? PalmGrabOffset : -PalmGrabOffset;
|
||||
|
||||
private RigidBody3D FindProp(XRController3D controller, int hand)
|
||||
private PhysicsBody3D FindProp(XRController3D controller, int hand)
|
||||
{
|
||||
RigidBody3D handBody = PhysicsHands.GetBody(controller);
|
||||
if (handBody == null)
|
||||
@@ -208,7 +211,27 @@ public partial class PropGrabber : Node
|
||||
RigidBody3D nearest = NearestProp(palm, GrabRadius);
|
||||
if (nearest != null)
|
||||
return nearest;
|
||||
return NearestProp(handBody.GlobalPosition, CenterGrabRadius);
|
||||
nearest = NearestProp(handBody.GlobalPosition, CenterGrabRadius);
|
||||
if (nearest != null)
|
||||
return nearest;
|
||||
return WorldUnderPalm(palm, handBody.GetWorld3D());
|
||||
}
|
||||
|
||||
// Climbing: any static body touching the palm (except the "no_grab" group) can be held.
|
||||
private static StaticBody3D WorldUnderPalm(Vector3 palm, World3D world)
|
||||
{
|
||||
PhysicsShapeQueryParameters3D query = new()
|
||||
{
|
||||
Shape = new SphereShape3D { Radius = WorldGrabRadius },
|
||||
Transform = new Transform3D(Basis.Identity, palm),
|
||||
CollisionMask = 1,
|
||||
};
|
||||
foreach (Godot.Collections.Dictionary hit in world.DirectSpaceState.IntersectShape(query, 8))
|
||||
{
|
||||
if (hit["collider"].AsGodotObject() is StaticBody3D body && !body.IsInGroup(NoGrabGroup))
|
||||
return body;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private RigidBody3D NearestProp(Vector3 point, float radius)
|
||||
|
||||
Reference in new issue
Block a user