physics: character controller (Jolt CharacterVirtual)

- CharacterControllerComponent: capsule params (radius/halfHeight, mass,
  max slope, step height); entity origin = feet via shape offset
- CharacterController per-entity wrapper: gravity integration + grounded
  clamp, ExtendedUpdate (sweep/slide + stick-to-floor + walk-stairs),
  SetLinearVelocity/Jump/IsGrounded facade
- PhysicsWorld: SyncCharacters reconcile (create/rebuild on param
  change, editor-drag teleport), UpdateCharacters after physics Update
  (Jolt recommended order), writes back to Transform
- Inspector: params + grounded/velocity telemetry + jump debug button
- Serialization: params only, state rebuilt by reconcile
This commit is contained in:
2026-10-03 16:34:20 +08:00
parent 82910c41e8
commit e1cef53158
7 changed files with 364 additions and 0 deletions
@@ -328,6 +328,21 @@ namespace Engine::Serialization {
out << YAML::EndMap;
}
// 角色只存参数(速度/着地等状态不序列化)
if (const auto* cc = reg.try_get<ECS::CharacterControllerComponent>(e)) {
out << YAML::Key << "CharacterController" << YAML::Value
<< YAML::BeginMap;
out << YAML::Key << "Radius" << YAML::Value << cc->radius;
out << YAML::Key << "CapsuleHalfHeight" << YAML::Value
<< cc->capsuleHalfHeight;
out << YAML::Key << "Mass" << YAML::Value << cc->mass;
out << YAML::Key << "MaxSlopeAngle" << YAML::Value
<< cc->maxSlopeAngle;
out << YAML::Key << "StepHeight" << YAML::Value
<< cc->stepHeight;
out << YAML::EndMap;
}
out << YAML::EndMap; // Components
out << YAML::EndMap; // Entity
}
@@ -601,6 +616,18 @@ namespace Engine::Serialization {
jc.limitMin = n["LimitMin"].as<f32>(jc.limitMin);
jc.limitMax = n["LimitMax"].as<f32>(jc.limitMax);
}
if (const YAML::Node n = comps["CharacterController"]) {
auto& cc =
handle.AddComponent<ECS::CharacterControllerComponent>();
cc.radius = n["Radius"].as<f32>(cc.radius);
cc.capsuleHalfHeight =
n["CapsuleHalfHeight"].as<f32>(cc.capsuleHalfHeight);
cc.mass = n["Mass"].as<f32>(cc.mass);
cc.maxSlopeAngle = n["MaxSlopeAngle"].as<f32>(cc.maxSlopeAngle);
cc.stepHeight = n["StepHeight"].as<f32>(cc.stepHeight);
// 状态(速度/着地)由 PhysicsWorld 对账重建,不存
}
}
// 加载在 OnImGuiRender 段,错过本帧 UpdateTransforms: