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:
@@ -242,6 +242,7 @@ namespace Engine {
|
||||
|
||||
m_System->Update(kFixedDt, steps, m_TempAllocator.get(), m_JobSystem.get());
|
||||
Writeback();
|
||||
UpdateCharacters(kFixedDt); // 角色扫掠(Jolt 官方建议:物理更新后)
|
||||
UpdateAccels(kFixedDt); // 速度差分 → 加速度(step 率采样一次)
|
||||
}
|
||||
|
||||
@@ -406,6 +407,9 @@ namespace Engine {
|
||||
|
||||
// 3. 约束对账(body 循环之后:依赖新建 body 的最终 ID)
|
||||
SyncConstraints(invalidated);
|
||||
|
||||
// 4. 角色对账(非刚体,不依赖 body 生命周期)
|
||||
SyncCharacters();
|
||||
}
|
||||
|
||||
// ---- 约束对账 ----
|
||||
@@ -559,6 +563,75 @@ namespace Engine {
|
||||
m_Constraints.erase(found); // Ref 析构 → 引用计数释放
|
||||
}
|
||||
|
||||
// ---- 角色控制器 ----
|
||||
|
||||
void PhysicsWorld::SyncCharacters() {
|
||||
auto ® = m_Scene.GetRegistry();
|
||||
|
||||
// 1. 孤儿清理:实体销毁 / 组件被移除(Ref 析构即清理,无 body 负担)
|
||||
for (auto it = m_Characters.begin(); it != m_Characters.end();) {
|
||||
const auto e = static_cast<entt::entity>(it->first);
|
||||
if (!reg.valid(e) ||
|
||||
!reg.all_of<ECS::CharacterControllerComponent,
|
||||
ECS::TransformComponent>(e)) {
|
||||
it = m_Characters.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 遍历:缺失 → 创建;参数变更 → 重建;编辑器拖动 → 传送
|
||||
auto view = reg.view<ECS::CharacterControllerComponent,
|
||||
ECS::TransformComponent>();
|
||||
for (auto [e, cc, tr]: view.each()) {
|
||||
const u32 bits = static_cast<u32>(entt::to_integral(e));
|
||||
CharacterController::Params p;
|
||||
p.radius = cc.radius;
|
||||
p.halfHeight = cc.capsuleHalfHeight;
|
||||
p.mass = cc.mass;
|
||||
p.maxSlopeAngle = cc.maxSlopeAngle;
|
||||
p.stepHeight = cc.stepHeight;
|
||||
|
||||
auto found = m_Characters.find(bits);
|
||||
if (found == m_Characters.end()) {
|
||||
CharacterEntry entry;
|
||||
entry.cc = std::make_unique<CharacterController>(
|
||||
*m_System, p, tr.GetPosition());
|
||||
entry.lastPos = tr.GetPosition();
|
||||
m_Characters.emplace(bits, std::move(entry));
|
||||
continue;
|
||||
}
|
||||
if (found->second.cc->GetParams() != p) {
|
||||
// 参数变更 → 重建(位置沿用当前 Transform)
|
||||
found->second.cc = std::make_unique<CharacterController>(
|
||||
*m_System, p, tr.GetPosition());
|
||||
found->second.lastPos = tr.GetPosition();
|
||||
continue;
|
||||
}
|
||||
if (found->second.lastPos != tr.GetPosition()) {
|
||||
// 编辑器拖动实体 → 传送角色(角色是唯一位置源,反向覆盖)
|
||||
found->second.cc->Teleport(tr.GetPosition());
|
||||
found->second.lastPos = tr.GetPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsWorld::UpdateCharacters(const f32 dt) {
|
||||
auto ® = m_Scene.GetRegistry();
|
||||
const JPH::Vec3 gravity = m_System->GetGravity();
|
||||
for (auto &[bits, entry]: m_Characters) {
|
||||
auto *tr = reg.try_get<ECS::TransformComponent>(
|
||||
static_cast<entt::entity>(bits));
|
||||
if (tr == nullptr) {
|
||||
continue;
|
||||
}
|
||||
entry.cc->Update(dt, gravity, *m_TempAllocator);
|
||||
const glm::vec3 p = entry.cc->GetPosition(); // 原点 = 脚底
|
||||
tr->SetPosition(p); // 标脏 → UpdateTransforms 重算 world
|
||||
entry.lastPos = p;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Mesh 碰撞体:视觉网格 → Jolt 形状 ----
|
||||
|
||||
JPH::RefConst<JPH::Shape> PhysicsWorld::BuildMeshShape(
|
||||
@@ -888,6 +961,37 @@ namespace Engine {
|
||||
return {v.GetX(), v.GetY(), v.GetZ()};
|
||||
}
|
||||
|
||||
// ---- 角色控制器门面 ----
|
||||
|
||||
CharacterController *PhysicsWorld::FindCharacter(const u32 entityBits) {
|
||||
const auto found = m_Characters.find(entityBits);
|
||||
return found != m_Characters.end() ? found->second.cc.get() : nullptr;
|
||||
}
|
||||
|
||||
void PhysicsWorld::SetCharacterVelocity(const u32 entityBits,
|
||||
const glm::vec3 &velocity) {
|
||||
if (CharacterController *cc = FindCharacter(entityBits)) {
|
||||
cc->SetLinearVelocity(velocity);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsWorld::Jump(const u32 entityBits, const f32 speed) {
|
||||
if (CharacterController *cc = FindCharacter(entityBits)) {
|
||||
cc->Jump(speed);
|
||||
}
|
||||
}
|
||||
|
||||
bool PhysicsWorld::IsCharacterGrounded(const u32 entityBits) const {
|
||||
const auto found = m_Characters.find(entityBits);
|
||||
return found != m_Characters.end() && found->second.cc->IsGrounded();
|
||||
}
|
||||
|
||||
glm::vec3 PhysicsWorld::GetCharacterVelocity(const u32 entityBits) const {
|
||||
const auto found = m_Characters.find(entityBits);
|
||||
return found != m_Characters.end() ? found->second.cc->GetLinearVelocity()
|
||||
: glm::vec3{0.0f};
|
||||
}
|
||||
|
||||
// ---- 写回:动态体姿态 → Transform(标脏,帧循环统一 Compose)----
|
||||
|
||||
void PhysicsWorld::Writeback() const {
|
||||
|
||||
Reference in New Issue
Block a user