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
@@ -0,0 +1,227 @@
|
||||
using Godot;
|
||||
|
||||
// Visual procedural legs for the ragdoll player (Nimso: "a very basic procedural leg system", feet
|
||||
// "don't actually hold the player up" — ground control does). Feet stay planted on the ground and
|
||||
// step, alternating, when the hips move or turn away from them; the stride leads with the body's
|
||||
// velocity. Airborne the feet hang under the hips; low (crawling) they trail behind. Knees bend
|
||||
// forward via a 2-bone solve. Physics-tick state (PlayerRagdoll.Step calls Tick), drawn in
|
||||
// _Process from the interpolated pelvis. No collision — the lower-body collider is separate.
|
||||
public partial class ProceduralLegs : Node3D
|
||||
{
|
||||
public const float ThighRatio = 0.245f, ShinRatio = 0.246f, AnkleRatio = 0.039f; // Winter, × stature
|
||||
public const float HipHalfWidth = 0.09f;
|
||||
public const float StanceHalfWidth = 0.11f;
|
||||
public const float StepTrigger = 0.2f; // m from the ideal spot
|
||||
public const float StepTurnTrigger = 0.6f; // rad
|
||||
public const float StepLead = 0.25f; // s of velocity the new foothold leads by
|
||||
public const float StepTimeSlow = 0.32f, StepTimeFast = 0.2f;
|
||||
public const float StepHeight = 0.09f;
|
||||
public const float SettleTime = 0.4f; // standing still this long → the trailing foot squares up
|
||||
|
||||
private struct Foot
|
||||
{
|
||||
public Vector3 Planted, From, To;
|
||||
public float Yaw, FromYaw, ToYaw;
|
||||
public float T; // step progress 0..1 (1 = planted)
|
||||
public Vector3 Current;
|
||||
public float CurrentYaw;
|
||||
}
|
||||
|
||||
private readonly Foot[] _feet = new Foot[2];
|
||||
private readonly MeshInstance3D[] _thigh = new MeshInstance3D[2], _shin = new MeshInstance3D[2], _foot = new MeshInstance3D[2];
|
||||
private RigidBody3D _pelvis;
|
||||
private Vector3 _hipLocal; // pelvis-body-local hip centre
|
||||
private float _thighLen, _shinLen, _ankle, _scale;
|
||||
private bool _initialized;
|
||||
private int _lastStepped;
|
||||
private float _stillTime;
|
||||
|
||||
public void Setup(RigidBody3D pelvis, Vector3 hipLocal, float stature, Color pants, Color shoes)
|
||||
{
|
||||
_pelvis = pelvis;
|
||||
_hipLocal = hipLocal;
|
||||
_thighLen = ThighRatio * stature;
|
||||
_shinLen = ShinRatio * stature;
|
||||
_ankle = AnkleRatio * stature;
|
||||
_scale = stature / 1.75f;
|
||||
PhysicsInterpolationMode = PhysicsInterpolationModeEnum.Off; // meshes are placed every frame
|
||||
TopLevel = true;
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
_thigh[i] = MakeMesh(new Vector3(0.13f * _scale, 1f, 0.14f * _scale), pants);
|
||||
_shin[i] = MakeMesh(new Vector3(0.1f * _scale, 1f, 0.11f * _scale), pants);
|
||||
_foot[i] = MakeMesh(new Vector3(0.1f * _scale, 0.07f * _scale, 0.25f * _scale), shoes);
|
||||
}
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
private MeshInstance3D MakeMesh(Vector3 size, Color color)
|
||||
{
|
||||
MeshInstance3D m = new() { Mesh = new BoxMesh { Size = size } };
|
||||
m.SetSurfaceOverrideMaterial(0, new StandardMaterial3D { AlbedoColor = color, Roughness = 0.7f });
|
||||
AddChild(m);
|
||||
return m;
|
||||
}
|
||||
|
||||
private Vector3 Hip(Transform3D pelvis, int side) =>
|
||||
pelvis * (_hipLocal + new Vector3(side == 0 ? -HipHalfWidth * _scale : HipHalfWidth * _scale, 0f, 0f));
|
||||
|
||||
private static float YawOf(Basis b)
|
||||
{
|
||||
Vector3 f = -b.Z;
|
||||
return Mathf.Atan2(-f.X, -f.Z);
|
||||
}
|
||||
|
||||
// groundY = where the feet stand (ground control's foot level); crawl = legs collider off.
|
||||
public void Tick(float groundY, bool grounded, bool crawl, Basis yawBasis, float dt)
|
||||
{
|
||||
if (_pelvis == null)
|
||||
return;
|
||||
Transform3D pelvis = _pelvis.GlobalTransform;
|
||||
Vector3 velocity = _pelvis.LinearVelocity;
|
||||
velocity.Y = 0f;
|
||||
float yaw = YawOf(yawBasis);
|
||||
Vector3 back = yawBasis.Z;
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
Vector3 hip = Hip(pelvis, i);
|
||||
Vector3 side = yawBasis.X * (i == 0 ? -1f : 1f);
|
||||
Vector3 ideal = new Vector3(hip.X, groundY, hip.Z) + side * (StanceHalfWidth - HipHalfWidth) * _scale;
|
||||
if (crawl)
|
||||
ideal += back * (_thighLen + _shinLen) * 0.8f;
|
||||
if (!_initialized)
|
||||
{
|
||||
_feet[i] = new Foot { Planted = ideal, Current = ideal, To = ideal, Yaw = yaw, CurrentYaw = yaw, ToYaw = yaw, T = 1f };
|
||||
continue;
|
||||
}
|
||||
|
||||
ref Foot f = ref _feet[i];
|
||||
if (!grounded)
|
||||
{
|
||||
// Hanging: feet dangle under the hips, a little forward, and replant on landing.
|
||||
Vector3 hang = hip + Vector3.Down * (_thighLen + _shinLen) * 0.92f - back * 0.08f;
|
||||
f.Current = f.Current.Lerp(hang, 1f - Mathf.Exp(-dt * 10f));
|
||||
f.CurrentYaw = Mathf.LerpAngle(f.CurrentYaw, yaw, 1f - Mathf.Exp(-dt * 10f));
|
||||
f.Planted = f.Current;
|
||||
f.T = 1f;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (f.T < 1f)
|
||||
{
|
||||
float speed = velocity.Length();
|
||||
float duration = Mathf.Lerp(StepTimeSlow, StepTimeFast, Mathf.Clamp(speed / 2.5f, 0f, 1f));
|
||||
f.T = Mathf.Min(1f, f.T + dt / duration);
|
||||
f.To = new Vector3(f.To.X, groundY, f.To.Z);
|
||||
float k = Mathf.SmoothStep(0f, 1f, f.T);
|
||||
f.Current = f.From.Lerp(f.To, k) + Vector3.Up * (Mathf.Sin(Mathf.Pi * f.T) * (StepHeight + 0.03f * speed));
|
||||
f.CurrentYaw = Mathf.LerpAngle(f.FromYaw, f.ToYaw, k);
|
||||
if (f.T >= 1f)
|
||||
{
|
||||
f.Planted = f.To;
|
||||
f.Yaw = f.ToYaw;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
f.Planted = new Vector3(f.Planted.X, Mathf.MoveToward(f.Planted.Y, groundY, dt * 2f), f.Planted.Z);
|
||||
f.Current = f.Planted;
|
||||
f.CurrentYaw = f.Yaw;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_initialized)
|
||||
{
|
||||
_initialized = true;
|
||||
return;
|
||||
}
|
||||
if (!grounded)
|
||||
return;
|
||||
|
||||
// One foot at a time: step the foot that is farthest from where it should be.
|
||||
if (_feet[0].T >= 1f && _feet[1].T >= 1f)
|
||||
{
|
||||
float speed = velocity.Length();
|
||||
_stillTime = speed < 0.05f ? _stillTime + dt : 0f;
|
||||
int pick = -1;
|
||||
float worst = 0f;
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
Vector3 hip = Hip(pelvis, i);
|
||||
Vector3 side = yawBasis.X * (i == 0 ? -1f : 1f);
|
||||
Vector3 ideal = new Vector3(hip.X, groundY, hip.Z) + side * (StanceHalfWidth - HipHalfWidth) * _scale;
|
||||
if (crawl)
|
||||
ideal += back * (_thighLen + _shinLen) * 0.8f;
|
||||
float off = new Vector2(_feet[i].Planted.X - ideal.X, _feet[i].Planted.Z - ideal.Z).Length();
|
||||
float turn = Mathf.Abs(Mathf.AngleDifference(_feet[i].Yaw, yaw));
|
||||
bool need = off > StepTrigger * _scale || turn > StepTurnTrigger || (_stillTime > SettleTime && off > 0.06f);
|
||||
float score = off + turn * 0.2f + (i == _lastStepped ? -0.01f : 0f);
|
||||
if (need && score > worst)
|
||||
{
|
||||
worst = score;
|
||||
pick = i;
|
||||
}
|
||||
}
|
||||
if (pick >= 0)
|
||||
{
|
||||
Vector3 hip = Hip(pelvis, pick);
|
||||
Vector3 side = yawBasis.X * (pick == 0 ? -1f : 1f);
|
||||
Vector3 to = new Vector3(hip.X, groundY, hip.Z) + side * (StanceHalfWidth - HipHalfWidth) * _scale + velocity * StepLead;
|
||||
if (crawl)
|
||||
to += back * (_thighLen + _shinLen) * 0.8f;
|
||||
ref Foot f = ref _feet[pick];
|
||||
f.From = f.Planted;
|
||||
f.FromYaw = f.Yaw;
|
||||
f.To = to;
|
||||
f.ToYaw = yaw;
|
||||
f.T = 0f;
|
||||
_lastStepped = pick;
|
||||
_stillTime = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (_pelvis == null || !_initialized || !IsInstanceValid(_pelvis))
|
||||
return;
|
||||
Transform3D pelvis = _pelvis.GetGlobalTransformInterpolated();
|
||||
Vector3 forward = -pelvis.Basis.Z;
|
||||
forward.Y = 0f;
|
||||
forward = forward.LengthSquared() > 1e-6f ? forward.Normalized() : Vector3.Forward;
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
Vector3 hip = Hip(pelvis, i);
|
||||
Vector3 ankle = _feet[i].Current + Vector3.Up * _ankle;
|
||||
// 2-bone solve, knee bending forward.
|
||||
Vector3 d = ankle - hip;
|
||||
float reach = _thighLen + _shinLen;
|
||||
float len = Mathf.Clamp(d.Length(), 0.05f, reach * 0.999f);
|
||||
Vector3 dir = d.LengthSquared() > 1e-8f ? d.Normalized() : Vector3.Down;
|
||||
ankle = hip + dir * len;
|
||||
float along = (_thighLen * _thighLen - _shinLen * _shinLen + len * len) / (2f * len);
|
||||
float radius = Mathf.Sqrt(Mathf.Max(_thighLen * _thighLen - along * along, 0f));
|
||||
Vector3 pole = forward - dir * forward.Dot(dir);
|
||||
pole = pole.LengthSquared() > 1e-6f ? pole.Normalized() : forward;
|
||||
Vector3 knee = hip + dir * along + pole * radius;
|
||||
Place(_thigh[i], hip, knee);
|
||||
Place(_shin[i], knee, ankle);
|
||||
Basis footBasis = new Basis(Vector3.Up, _feet[i].CurrentYaw);
|
||||
_foot[i].GlobalTransform = new Transform3D(footBasis, _feet[i].Current + Vector3.Up * (0.035f * _scale) + footBasis * new Vector3(0f, 0f, -0.06f * _scale));
|
||||
}
|
||||
}
|
||||
|
||||
private static void Place(MeshInstance3D mesh, Vector3 from, Vector3 to)
|
||||
{
|
||||
Vector3 seg = to - from;
|
||||
float len = seg.Length();
|
||||
if (len < 1e-4f)
|
||||
return;
|
||||
Vector3 y = seg / len;
|
||||
Vector3 x = Mathf.Abs(y.Y) < 0.99f ? Vector3.Up.Cross(y).Normalized() : Vector3.Right;
|
||||
Vector3 z = x.Cross(y);
|
||||
// Box mesh is 1 m tall along Y: stretch it to the segment.
|
||||
mesh.GlobalTransform = new Transform3D(new Basis(x, y * len, z), (from + to) * 0.5f);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user