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
+15
View File
@@ -259,6 +259,21 @@ namespace Engine::ECS {
bool operator==(const JointComponent&) const = default;
};
// 角色控制器参数(Jolt CharacterVirtual 扫掠运动学,非刚体——不受
// 物理模拟驱动,游戏逻辑经 PhysicsWorld 门面喂速度/跳跃)。本组件
// 只存参数(序列化:存参数不存状态),速度/着地等运行时状态在
// PhysicsWorld 的 CharacterController 里(对账重建)
struct CharacterControllerComponent {
f32 radius = 0.3f; // 胶囊半径
f32 capsuleHalfHeight = 0.4f; // 圆柱段半高(总高 = 2*(hh+r))
f32 mass = 70.0f;
f32 maxSlopeAngle = 45.0f; // 度:可站立的最大坡角
f32 stepHeight = 0.4f; // 台阶自动攀爬高(WalkStairs)
bool operator==(const CharacterControllerComponent&) const = default;
// 实体原点 = 脚底(胶囊底端),符合角色 pivot 习惯
};
} // namespace Engine::ECS
#endif //HELLOVULKAN_COMPONENTS_H
@@ -0,0 +1,70 @@
//
// CharacterController:单实体角色控制器(Jolt CharacterVirtual 后端)。
//
// 非刚体、扫掠运动学:CharacterVirtual 不注册进 PhysicsSystem,位置由
// 本类每步扫掠推进(撞墙滑动/斜坡限制/贴地/上台阶),其他物理对象
// "看不到"它(无碰撞解算,仅查询)。由 PhysicsWorld 对账创建/销毁并
// 在 PhysicsSystem::Update 之后驱动(Jolt 官方建议序)。
//
// 速度语义(游戏逻辑职责分工):
// - 水平移动:游戏逻辑每帧 SetCharacterVelocity 喂意图速度
// - 垂直分量:本类自动积分重力(着地清零向下分量);Jump 置垂直速度
// (Unity CharacterController.Move + gravity 手动积分同构)
//
// 实体原点 = 脚底(胶囊底端经 mShapeOffset 抬升到原点上方)。
//
#ifndef HELLOVULKAN_CHARACTERCONTROLLER_H
#define HELLOVULKAN_CHARACTERCONTROLLER_H
#include <Engine/Core/Types.h>
#include <Jolt/Jolt.h>
#include <Jolt/Physics/PhysicsSystem.h>
#include <Jolt/Physics/Character/CharacterVirtual.h>
#include <glm/glm.hpp>
namespace Engine {
class CharacterController {
public:
// 创建参数快照(变更检测 → 重建 CharacterVirtual)
struct Params {
f32 radius = 0.3f; // 胶囊半径
f32 halfHeight = 0.4f; // 圆柱段半高
f32 mass = 70.0f;
f32 maxSlopeAngle = 45.0f; // 度
f32 stepHeight = 0.4f; // 台阶攀爬高
bool operator==(const Params&) const = default;
};
CharacterController(JPH::PhysicsSystem& system, const Params& params,
const glm::vec3& position);
~CharacterController() = default; // Ref 引用计数自动释放
// 每物理步调用:重力积分 + ExtendedUpdate(扫掠滑动 + 贴地 +
// 上台阶)。allocator 与 PhysicsSystem::Update 串行复用同一池
void Update(f32 dt, const JPH::Vec3& gravity,
JPH::TempAllocator& allocator);
// ---- 游戏逻辑门面(PhysicsWorld 转发)----
void SetLinearVelocity(const glm::vec3& velocity);
glm::vec3 GetLinearVelocity() const;
void Jump(f32 speed); // 垂直速度置 speed(着地方向才有效)
bool IsGrounded() const;
glm::vec3 GetPosition() const;
void Teleport(const glm::vec3& position); // 编辑器拖动
const Params& GetParams() const { return m_Params; }
private:
Params m_Params;
JPH::Ref<JPH::CharacterVirtual> m_Character;
JPH::CharacterVirtual::ExtendedUpdateSettings m_Ext;
};
} // namespace Engine
#endif //HELLOVULKAN_CHARACTERCONTROLLER_H
@@ -25,6 +25,7 @@
#include <Engine/Physics/PhysicsDebugDraw.h>
#include <Engine/Physics/PhysicsEvents.h>
#include <Engine/Physics/PhysicsRay.h>
#include <Engine/Physics/CharacterController.h>
#include <Engine/ECS/Components.h> // BodyRecord 快照存 CompoundChild(完整类型)
#include <Jolt/Jolt.h>
@@ -90,6 +91,14 @@ namespace Engine {
// 无 body / 静态体返回零向量
glm::vec3 GetLinearVelocity(u32 entityBits) const;
// ---- 角色控制器(游戏逻辑门面;实体无 CharacterControllerComponent
// 或未就绪时静默忽略)----
// 速度直写喂意图(水平移动);垂直分量由 Update 自动积分重力
void SetCharacterVelocity(u32 entityBits, const glm::vec3& velocity);
void Jump(u32 entityBits, f32 speed); // 垂直速度置 speed
bool IsCharacterGrounded(u32 entityBits) const;
glm::vec3 GetCharacterVelocity(u32 entityBits) const;
private:
// 创建时缓存的参数(变更检测 → 重建 body)
struct BodyRecord {
@@ -121,6 +130,12 @@ namespace Engine {
void CreateConstraint(u32 ownerBits, u32 targetBits,
const ECS::JointComponent& jc);
void RemoveConstraint(u32 ownerBits);
// 角色对账:孤儿清理 / 参数变更重建 / 编辑器拖动传送。
// 角色非刚体(不进 PhysicsSystem),无 body 清理负担
void SyncCharacters();
// 每物理步(PhysicsSystem::Update 之后,Jolt 官方建议序):
// 重力积分 + 扫掠 → 写回 TransformComponent(标脏)
void UpdateCharacters(f32 dt);
// Mesh 碰撞体:实体视觉网格 → Jolt 形状(Static=MeshShape /
// Dynamic=ConvexHull)。无 MeshRenderer 或构建失败返回 null(回退 Box)
JPH::RefConst<JPH::Shape> BuildMeshShape(u32 entityBits,
@@ -132,6 +147,7 @@ namespace Engine {
ECS::TransformComponent& tr);
void RemoveBody(u32 entityBits);
const BodyRecord* FindRecord(u32 entityBits) const; // 刚体驱动寻址
CharacterController* FindCharacter(u32 entityBits); // 角色门面寻址
void QueueEvent(CollisionEvent&& ev); // 回调线程 append(多线程并发,加锁)
bool TryBitsByBodyId(const JPH::BodyID& id, u32& outBits) const;
@@ -144,6 +160,13 @@ namespace Engine {
};
std::unordered_map<u32, ConstraintRecord> m_Constraints;
// ---- 角色(entityBits → CharacterVirtual 封装)----
struct CharacterEntry {
std::unique_ptr<CharacterController> cc;
glm::vec3 lastPos{0.0f}; // 编辑器拖动检测
};
std::unordered_map<u32, CharacterEntry> m_Characters;
ECS::Scene& m_Scene;
Assets::AssetManager* m_Assets = nullptr; // Mesh 碰撞体解析 modelRef
bool m_Enabled = true;