Setting up ragdolls programatically using .Net version

Godot Version

4.5.1

Question

Hi. I’ve been having a lot of issues trying to get active/full ragdolls to work programmatically using the .Net version of Godot. I have been going round in circles for days using ChatGPT (a last resort) and there is no real documentation to speak of for this. So, I was wandering if anyone here would be able to help me.

I have a method which loads character models (complete with skeletons) as .glb files during loading screens and then sets them up via code. Firstly, they are scaled to the correct height (only the root node is scaled) and then each of the bones are parsed and a custom BoneReference class is created for each, which contains the bone index, name etc.

ScaleCharacterToHeight(_bodyNode, Entity.Identity.AbsoluteHeight);

AssignBoneReferences(Skeleton);

These are the relevant methods which are called:

private void AssignBoneReferences(Skeleton3D skeleton) {

    Dictionary<string, BoneReference> bones = CollectBones(skeleton);

    PhysicsSimulator = new PhysicalBoneSimulator3D() {

        Active = false

    };

    skeleton.AddChild(PhysicsSimulator);

    foreach(KeyValuePair<string, BoneReference> pair in bones) {

        *// CreateBonePhysics is called for each relevant bone.*

    }
}
private void CreateBonePhysics(Skeleton3D skeleton, Enums.BodyPart bodyPart, BoneReference boneRef, float mass = 10f, float linearDamp = 0.1f, float angularDamp = 0.1f, float radius = 0.05f, float height = 1.0f) {

    if (boneRef.Index == -1) {

        GD.Print("ignoring bone: " + boneRef.Name);

        return;

    }

    if (PhysicalBones.ContainsKey(bodyPart)) {

        return;

    }



    Transform3D boneRestLocal = skeleton.GetBoneRest(boneRef.Index);

    Transform3D boneRestGlobal = skeleton.GlobalTransform \* boneRestLocal;

    *var* physicalBone = new PhysicalBone3D

    {

        Name = "Physical Bone " + boneRef.Name,

        Mass = mass,

        LinearDamp = linearDamp,

        AngularDamp = angularDamp,

        GlobalTransform = boneRestGlobal

    };

    PhysicsSimulator.AddChild(physicalBone);

    *// physics collision box sizes are directly copied from hurtboxes for now*

    CollisionShape3D hurtboxCollision = (CollisionShape3D)Hurtboxes\[bodyPart\].GetNode("col\_" + bodyPart.ToString().ToLower());

    Shape3D shape = null;

    if (hurtboxCollision.Shape is SphereShape3D) {

        SphereShape3D sphereShape = (SphereShape3D)hurtboxCollision.Shape;

        shape = new SphereShape3D() {

            Radius = sphereShape.Radius

        };

    } else {

        CapsuleShape3D capShape = (CapsuleShape3D)hurtboxCollision.Shape;

        shape = new CapsuleShape3D() {

            Radius = capShape.Radius,

            Height = capShape.Height

        };

    }

    var collisionShape = new CollisionShape3D

    {

        Shape = shape

    };

    physicalBone.AddChild(collisionShape);

    PhysicalBones.Add(bodyPart, physicalBone);
}

Lastly, here is the method which I am manually calling on keyboard input (y key press) while the game is running to test the ragdoll, which is currently only causing the animation to stop with no actual physics taking over:

public void StartRagdoll() {
    AnimTree.Active = false;

    AnimPlayer.Active = false;

    Skeleton.ModifierCallbackModeProcess = Skeleton3D.ModifierCallbackModeProcessEnum.Physics;

    Skeleton.ForceUpdateTransform();

    Entity.Animator.SetActiveState(false);

    PhysicsSimulator.Active = true;

    PhysicsSimulator.PhysicalBonesStartSimulation();

    Skeleton.SetPhysicsProcess(true);

    AnimPlayer.SetPhysicsProcess(true);

    DebugPhysicalBones();
}

And the debug method:

public void DebugPhysicalBones()

{
    foreach (Node child in PhysicsSimulator.GetChildren())

    {

        if (child is PhysicalBone3D pb)

        {

            int boneId = pb.GetBoneId();

            GD.Print(

                $"PhysicalBone '{pb.Name}': BoneId = {boneId}, Valid = {boneId >= 0}"

            );

        }

    }
}

Each bone returns with “BoneId = -1, Valid = False” and the character simply freezes, it fall or react physically.

I have not set up the skeletons using the CreatePhysicalSkeleton button in the editor since this is not possible; I am setting all of my characters up programmatically. However, I have heard that this is no longer necessary and that Godot binds Physical bones to skeletal bones automatically now. Hopefully someone will confirm if this is true or not.

Does anyone know what the issue could be, or the location of any documentation?

Edit: sorry about the formatting, the forum editor is so frustratingly “helpful” that I had to just stop fighting it and leave the text it as it was before I had an aneurysm.

After a bit more investigation and debugging, I’ve realized that after all this time, Godot was (infuriatingly) renaming my physical bones from e.g. “upper_arm.L” to “upper_arm_L”, without my consent or knowledge. According to ChatGPT (which seems to literally be the only source of documentation for physical bones at the moment), the physical bone needs to be the exact same same as the bone itself (which does allow periods), so the bones will never bind as long as Godot is stealth renaming one set of those bones. I’m unable to test this at the moment (I won’t have access to the blend files again until later), but this might be the issue.