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:
@@ -121,6 +121,11 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
|
||||
{
|
||||
reg.emplace<Engine::ECS::JointComponent>(selected);
|
||||
}
|
||||
if (!reg.all_of<Engine::ECS::CharacterControllerComponent>(selected) &&
|
||||
ImGui::MenuItem("Character Controller"))
|
||||
{
|
||||
reg.emplace<Engine::ECS::CharacterControllerComponent>(selected);
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::Separator();
|
||||
@@ -391,6 +396,38 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- CharacterController(参数编辑;状态在 PhysicsWorld 运行时)----
|
||||
if (auto* cc = reg.try_get<Engine::ECS::CharacterControllerComponent>(selected))
|
||||
{
|
||||
if (ImGui::CollapsingHeader("Character Controller",
|
||||
ImGuiTreeNodeFlags_DefaultOpen))
|
||||
{
|
||||
// 实体原点 = 脚底(胶囊底端),总高 = 2*(halfHeight + radius)
|
||||
ImGui::DragFloat("radius", &cc->radius, 0.02f, 0.05f, 5.0f);
|
||||
ImGui::DragFloat("halfHeight", &cc->capsuleHalfHeight, 0.02f, 0.0f, 5.0f);
|
||||
ImGui::DragFloat("mass", &cc->mass, 1.0f, 1.0f, 500.0f, "%.0f");
|
||||
ImGui::DragFloat("max slope deg", &cc->maxSlopeAngle, 1.0f, 1.0f, 89.0f);
|
||||
ImGui::DragFloat("step height", &cc->stepHeight, 0.02f, 0.0f, 2.0f);
|
||||
|
||||
// 遥测(只读):运行时状态在 PhysicsWorld 的 CharacterController
|
||||
if (auto& phy = ctx.app->GetPhysics(); phy.IsEnabled())
|
||||
{
|
||||
const uint32_t bits =
|
||||
static_cast<uint32_t>(entt::to_integral(selected));
|
||||
ImGui::Text("grounded: %s",
|
||||
phy.IsCharacterGrounded(bits) ? "yes" : "no");
|
||||
const glm::vec3 v = phy.GetCharacterVelocity(bits);
|
||||
ImGui::Text("vel: %.2f, %.2f, %.2f m/s", v.x, v.y, v.z);
|
||||
|
||||
// 跳跃调试按钮
|
||||
if (ImGui::Button("Jump"))
|
||||
{
|
||||
phy.Jump(bits, 5.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Animation ----
|
||||
if (auto* anim = reg.try_get<Engine::ECS::AnimationComponent>(selected))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// CharacterController.cpp:Jolt CharacterVirtual 封装实现。
|
||||
//
|
||||
|
||||
#include <Engine/Physics/CharacterController.h>
|
||||
|
||||
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
|
||||
|
||||
namespace Engine {
|
||||
|
||||
CharacterController::CharacterController(JPH::PhysicsSystem &system,
|
||||
const Params ¶ms,
|
||||
const glm::vec3 &position)
|
||||
: m_Params(params) {
|
||||
// 胶囊体以脚底为原点:CapsuleShape 原点在其中心,经 mShapeOffset
|
||||
// 上抬 (halfHeight + radius) 使底端落在原点(Jolt 角色约定)
|
||||
JPH::CharacterVirtualSettings s;
|
||||
s.mUp = JPH::Vec3::sAxisY();
|
||||
s.mMass = std::max(params.mass, 1e-3f);
|
||||
s.mMaxSlopeAngle = glm::radians(params.maxSlopeAngle);
|
||||
s.mShape = new JPH::CapsuleShape(
|
||||
std::max(params.radius, 0.01f),
|
||||
std::max(params.halfHeight, 0.0f));
|
||||
s.mShapeOffset = JPH::Vec3(0.0f,
|
||||
params.halfHeight + params.radius, 0.0f);
|
||||
|
||||
m_Character = new JPH::CharacterVirtual(
|
||||
&s, JPH::RVec3(position.x, position.y, position.z),
|
||||
JPH::Quat::sIdentity(), &system);
|
||||
|
||||
// 台阶攀爬:竖直抬升 = stepHeight(ExtendedUpdate 内部走
|
||||
// WalkStairs 前向距离按速度取)
|
||||
m_Ext.mWalkStairsStepUp = JPH::Vec3(0.0f, std::max(params.stepHeight, 0.0f), 0.0f);
|
||||
}
|
||||
|
||||
void CharacterController::Update(const f32 dt, const JPH::Vec3 &gravity,
|
||||
JPH::TempAllocator &allocator) {
|
||||
// 重力积分:着地时清掉向下分量(防贴地累积穿透速度),悬空时累加
|
||||
const JPH::Vec3 v = m_Character->GetLinearVelocity();
|
||||
if (m_Character->IsSupported()) {
|
||||
if (v.GetY() < 0.0f) {
|
||||
m_Character->SetLinearVelocity(
|
||||
JPH::Vec3(v.GetX(), 0.0f, v.GetZ()));
|
||||
}
|
||||
} else {
|
||||
m_Character->SetLinearVelocity(v + gravity * dt);
|
||||
}
|
||||
|
||||
// 扫掠推进(撞墙滑动 + 斜坡限制 + StickToFloor 贴地 + WalkStairs
|
||||
// 上台阶)。过滤器默认全放行:角色与静/动层全碰
|
||||
m_Character->ExtendedUpdate(dt, gravity, m_Ext,
|
||||
JPH::BroadPhaseLayerFilter{},
|
||||
JPH::ObjectLayerFilter{},
|
||||
JPH::BodyFilter{}, JPH::ShapeFilter{},
|
||||
allocator);
|
||||
}
|
||||
|
||||
void CharacterController::SetLinearVelocity(const glm::vec3 &velocity) {
|
||||
m_Character->SetLinearVelocity(
|
||||
JPH::Vec3(velocity.x, velocity.y, velocity.z));
|
||||
}
|
||||
|
||||
glm::vec3 CharacterController::GetLinearVelocity() const {
|
||||
const JPH::Vec3 v = m_Character->GetLinearVelocity();
|
||||
return {v.GetX(), v.GetY(), v.GetZ()};
|
||||
}
|
||||
|
||||
void CharacterController::Jump(const f32 speed) {
|
||||
const JPH::Vec3 v = m_Character->GetLinearVelocity();
|
||||
m_Character->SetLinearVelocity(JPH::Vec3(v.GetX(), speed, v.GetZ()));
|
||||
}
|
||||
|
||||
bool CharacterController::IsGrounded() const {
|
||||
return m_Character->IsSupported();
|
||||
}
|
||||
|
||||
glm::vec3 CharacterController::GetPosition() const {
|
||||
const JPH::RVec3 p = m_Character->GetPosition();
|
||||
return {static_cast<f32>(p.GetX()), static_cast<f32>(p.GetY()),
|
||||
static_cast<f32>(p.GetZ())};
|
||||
}
|
||||
|
||||
void CharacterController::Teleport(const glm::vec3 &position) {
|
||||
m_Character->SetPosition(
|
||||
JPH::RVec3(position.x, position.y, position.z));
|
||||
}
|
||||
|
||||
} // namespace Engine
|
||||
@@ -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 {
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user