Ragdoll: физические кости и частичная симуляция
Законченная цепочка из трёх костей: привязка по имени, суставы, запуск, остановка и выбор ветви.
Запустить физический скелет
Добавьте RagdollLab3D.cs к корню Entity3D отдельного C#-уровня, соберите и запустите F6. Физика — 60 шагов/с, гравитация 9.8 вниз, единичный масштаб ветви. Внешняя модель не нужна: видимые капсулы показывают физические тела трёх костей.
Space включает всю цепочку, P оставляет root неподвижной и симулирует middle с потомком tip, R останавливает симуляцию и возвращает базовую позу. Кости падают на пол; соседние тела не сталкиваются друг с другом по настройкам Layer/Mask. Это учебная цепочка, а не импортированный человек с мышцами или системой вставания.
RagdollLab3D.cs
using Reckfell;
public partial class RagdollLab3D : Entity3D
{
public Skeleton3D Skeleton { get; private set; } = null!;
public PhysicalBoneSimulator3D Simulator { get; private set; } = null!;
public PhysicalBone3D[] Bodies { get; private set; } = new PhysicalBone3D[3];
public override void _Ready()
{
Skeleton = new Skeleton3D { Name = "Skeleton", Position = new Vector3(0,5,0) }; AddChild(Skeleton);
string[] names = { "root", "middle", "tip" };
for (int i = 0; i < names.Length; i++)
{
int bone = Skeleton.AddBone(names[i]);
Skeleton.SetBoneParent(bone, i - 1);
Skeleton.SetBoneRest(bone, new Transform3D(Basis.Identity, i == 0 ? Vector3.Zero : Vector3.Down));
}
Skeleton.ResetBonePoses(resetBoneSkinScale: false);
Simulator = new PhysicalBoneSimulator3D { Name = "Simulator" }; Skeleton.AddChild(Simulator);
for (int i = 0; i < Bodies.Length; i++)
{
var body = new PhysicalBone3D { Name = "Physical_" + names[i], Mass = 1, CollisionLayer = 4, CollisionMask = 1,
BodyOffset = new Transform3D(Basis.Identity, Vector3.Down * .5f), JointType = i == 0 ? PhysicalBone3D.JointTypeEnum.None : PhysicalBone3D.JointTypeEnum.Pin };
body.Set("bone_name", names[i]);
body.AddChild(new CollisionShape3D { Shape = new CapsuleShape3D { Radius = .14f, Height = 1 } });
body.AddChild(new MeshInstance3D { Mesh = new CapsuleMesh { Radius = .14f, Height = 1 }, MaterialOverride =
new StandardMaterial3D { AlbedoColor = new Color(.25f,.45f,1), ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded } });
Simulator.AddChild(body); Bodies[i] = body;
}
var floor = new StaticBody3D { Position = new Vector3(0,-.25f,0), CollisionLayer = 1, CollisionMask = 0 };
floor.AddChild(new CollisionShape3D { Shape = new BoxShape3D { Size = new Vector3(12,.5f,10) } });
floor.AddChild(new MeshInstance3D { Mesh = new BoxMesh { Size = new Vector3(12,.5f,10) }, MaterialOverride =
new StandardMaterial3D { AlbedoColor = new Color(.55f,.55f,.6f), ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded } }); AddChild(floor);
var camera = new Camera3D { Position = new Vector3(4,3.5f,5), Current = true }; AddChild(camera); camera.LookAt(ToGlobal(new Vector3(0,2.3f,0)));
}
public void StartAll() { Simulator.PhysicalBonesStartSimulation(); }
public void StartLower() { Simulator.PhysicalBonesStartSimulation(new Reckfell.Collections.Array<StringName> { "middle" }); }
public void StopAndReset()
{
Simulator.PhysicalBonesStopSimulation(); Skeleton.ResetBonePoses(resetBoneSkinScale: false); Skeleton.ResetPhysicsInterpolation();
}
public override void _UnhandledKeyInput(InputEvent @event)
{
if (@event is not InputEventKey { Pressed: true, Echo: false } key) return;
if (key.PhysicalKeycode == Key.Space) StartAll();
if (key.PhysicalKeycode == Key.P) { StopAndReset(); StartLower(); }
if (key.PhysicalKeycode == Key.R) StopAndReset();
}
}Иерархия и привязка
Skeleton3D хранит кости/rest-позы, PhysicalBoneSimulator3D управляет физической модификацией, PhysicalBone3D создаёт тела и соединения. В примере simulator находится под Skeleton3D, физические кости — под simulator. Имена костей root/middle/tip должны совпадать с реальным Skeleton.
В C# этой версии имя физической кости задаётся динамическим свойством Set("bone_name", name). Публичного свойства PhysicalBone3D.BoneName в SDK нет. После добавления в правильную иерархию GetBoneId должен вернуть ожидаемый индекс; -1 означает, что привязка не установилась. Не путайте имя сущности в дереве и имя кости скелета.
BodyOffset описывает смещение тела относительно кости, JointOffset — систему соединения. Не компенсируйте ошибочный rest-transform случайным переносом видимого меша: физический сустав останется в другом месте. В примере тело капсулы смещено вниз на половину длины сегмента; Pin связывает положения суставов, но не задаёт анатомический предел вращения.
Подготовить импортированную модель
В редакторе для выбранного Skeleton3D доступно Create Physical Skeleton. Это исходная генерация: затем удалите ненужные мелкие физические кости, упростите формы и задайте правдоподобные массы. Проверьте родство костей, rest-позу, единицы, наследуемый масштаб и положение центров тел.
Настройте суставы под нужные степени свободы: Pin, Hinge, ConeTwist или подходящий 6DOF. Ограничения и числовые параметры зависят от backend; предупреждение о неподдерживаемом поле нельзя считать действующим ограничением. Избегайте начального проникновения, крайних соотношений масс и чрезмерной детализации коллайдеров.
Layer/Mask задают столкновения со средой и другими персонажами. Для исключения конкретного владельца/тела simulator имеет PhysicalBonesAddCollisionException/RemoveCollisionException с RID. Исключение одного объекта и запрет самостолкновений — разные политики. В примере CollisionLayer = 4 означает слой 3, CollisionMask = 1 включает слой 1. Маска костей включает мир на слое 1 и исключает другие кости на слое 3. Числовая битовая маска и номер слоя — разные величины.
Запуск, анимация и остановка
PhysicalBonesStartSimulation без списка запускает всю физическую ветвь. Список имён выбирает соответствующие кости и потомков; это не список сущностей по путям. IsSimulatingPhysics у simulator и у отдельных тел отвечает на разные вопросы, проверяйте оба уровня при частичном запуске.
Анимация, ввод персонажа и физика не должны одновременно независимо назначать окончательную позу одному телу. При смерти отключите обычное управление, включите нужную физическую ветвь и согласуйте коллизии. При возвращении выберите базовую анимацию, место основного CharacterBody и способ перехода. StopAndReset здесь останавливает физику и сбрасывает позы; плавное вставание или смешивание не реализовано.
На обоих backend проверены имена/ID, падение, ограниченная Pin-цепочка, полный/частичный старт, остановка и повторный запуск. Изображение использует ReckfellPhysics3D. Не проверялись все суставы, импортированные rig/skin, высокоскоростные удары, устройства или готовое восстановление персонажа. Начинайте с малого числа костей и проверяйте на выбранном backend.