- 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
280 lines
13 KiB
C++
280 lines
13 KiB
C++
//
|
||
// Created by atdunbg on 2026/9/22.
|
||
//
|
||
// Components:内置组件集合。
|
||
// 组件是纯数据(POD 倾向),逻辑在系统/应用层。
|
||
//
|
||
|
||
#ifndef HELLOVULKAN_COMPONENTS_H
|
||
#define HELLOVULKAN_COMPONENTS_H
|
||
|
||
#include <Engine/Core/Types.h>
|
||
#include <Engine/Core/UUID.h>
|
||
#include <Engine/Core/AssetRef.h>
|
||
#include <Engine/Renderer/Camera.h>
|
||
|
||
#include <glm/glm.hpp>
|
||
#include <glm/gtc/matrix_transform.hpp>
|
||
#include <glm/gtc/quaternion.hpp>
|
||
#include <cmath>
|
||
#include <algorithm>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
namespace Engine {
|
||
class Model;
|
||
}
|
||
|
||
namespace Engine::ECS {
|
||
|
||
// 实体稳定身份:CreateEntity 时自动分配,用于序列化/跨引用。
|
||
// ECS 内部遍历不用它(用 index+generation 句柄,性能)。
|
||
struct IdComponent {
|
||
UUID uuid;
|
||
|
||
operator UUID() const { return uuid; }
|
||
};
|
||
|
||
// 实体名(调试/查询用;大规模场景建议演进为哈希 id)
|
||
struct NameComponent {
|
||
std::string name;
|
||
};
|
||
|
||
// 世界变换:UE USceneComponent 的 encapsulation 模式——
|
||
// SRT 分量私有,所有写入口标脏;world 矩阵由帧循环的
|
||
// Scene::UpdateTransforms() 只对脏的实体重算(transform-update
|
||
// 阶段),并自动向未来的子层级传播。渲染热路径只读 World()。
|
||
// 层级(父子变换)留给后续的层级同步系统。
|
||
//
|
||
// 旋转内部存储 = 四元数(UE FTransform 语义,不存欧拉角):
|
||
// 欧拉往返是漂移/万向锁之源(Gizmo 拖拽乱跳的根因)。欧拉只在
|
||
// 表皮出现——UI 显示(GetRotation 派生)与 UI 输入(SetRotation
|
||
// 转存);Gizmo/物理热路径走 quat 直写(SetRotationQuat)零往返。
|
||
struct TransformComponent {
|
||
// ---- 读取 ----
|
||
const glm::vec3& GetPosition() const { return m_Position; }
|
||
const glm::quat& GetRotationQuat() const { return m_Rotation; }
|
||
const glm::vec3& GetScale() const { return m_Scale; }
|
||
const glm::mat4& World() const { return m_World; } // 要求已由 UpdateTransforms 计算
|
||
|
||
// 欧拉角(度)视图:从 quat 派生(仅 UI 显示/编辑用,勿做存储)。
|
||
// 按引擎 YXZ 序(R = Ry·Rx·Rz)从旋转矩阵反解;glm 列主序索引:
|
||
// x = asin(-R[2][1])、y = atan2(R[2][0], R[2][2])、z = atan2(R[0][1], R[1][1])。
|
||
// cx 在 atan2 分子分母中约掉,pitch 贴 ±90° 依然精确
|
||
glm::vec3 GetRotation() const
|
||
{
|
||
const glm::mat3 R = glm::mat3_cast(m_Rotation);
|
||
const f32 sp = std::clamp(-R[2][1], -1.0f, 1.0f); // = sin(pitch)
|
||
if (std::abs(sp) < 1.0f) {
|
||
return glm::degrees(glm::vec3(
|
||
std::asin(sp),
|
||
std::atan2(R[2][0], R[2][2]),
|
||
std::atan2(R[0][1], R[1][1])));
|
||
}
|
||
// ±90° 万向锁:yaw/roll 只剩组合角,约定 roll=0(UE 同策略)
|
||
// 归入 yaw;两支组合角符号不同
|
||
return sp > 0.0f
|
||
? glm::vec3(90.0f,
|
||
glm::degrees(std::atan2(-R[0][2], R[1][2])), 0.0f)
|
||
: glm::vec3(-90.0f,
|
||
glm::degrees(std::atan2(-R[0][2], -R[1][2])), 0.0f);
|
||
}
|
||
|
||
// ---- 写入(标脏,脏实体由 UpdateTransforms 重算)----
|
||
TransformComponent& SetPosition(const glm::vec3& p) { if (m_Position != p) { m_Position = p; m_Dirty = true; } return *this; }
|
||
// 欧拉角(度)入口:UI/游戏逻辑用。转 quat 存(q = qy·qx·qz,
|
||
// 与原 Ry·Rx·Rz 合成严格同构;不能用 glm::quat(euler) ctor——
|
||
// 那是 ZYX 航向序,语义不同)
|
||
TransformComponent& SetRotation(const glm::vec3& r)
|
||
{
|
||
const glm::quat qx = glm::angleAxis(glm::radians(r.x), glm::vec3(1, 0, 0));
|
||
const glm::quat qy = glm::angleAxis(glm::radians(r.y), glm::vec3(0, 1, 0));
|
||
const glm::quat qz = glm::angleAxis(glm::radians(r.z), glm::vec3(0, 0, 1));
|
||
return SetRotationQuat(qy * qx * qz);
|
||
}
|
||
// 四元数直写(UE FTransform 语义):Gizmo 拖拽/物理写回用,
|
||
// 不经欧拉往返 → 零漂移、无万向锁
|
||
TransformComponent& SetRotationQuat(const glm::quat& q) { if (m_Rotation != q) { m_Rotation = q; m_Dirty = true; } return *this; }
|
||
TransformComponent& SetScale(const glm::vec3& s) { if (m_Scale != s) { m_Scale = s; m_Dirty = true; } return *this; }
|
||
|
||
bool IsDirty() const { return m_Dirty; }
|
||
|
||
// SRT 分量 → world 矩阵(T * R * S),清除脏标记。
|
||
// 由 Scene::UpdateTransforms 调用(或层级系统),勿手动
|
||
TransformComponent& Compose()
|
||
{
|
||
const glm::mat4 t = glm::translate(glm::mat4(1.0f), m_Position);
|
||
const glm::mat4 s = glm::scale(glm::mat4(1.0f), m_Scale);
|
||
m_World = t * glm::mat4_cast(m_Rotation) * s;
|
||
m_Dirty = false;
|
||
return *this;
|
||
}
|
||
|
||
private:
|
||
glm::vec3 m_Position{0.0f};
|
||
glm::quat m_Rotation{1.0f, 0.0f, 0.0f, 0.0f}; // 单位四元数(w,x,y,z)
|
||
glm::vec3 m_Scale{1.0f};
|
||
glm::mat4 m_World{1.0f};
|
||
bool m_Dirty = true; // 出生即脏:首次 UpdateTransforms 计算 world
|
||
};
|
||
|
||
// 网格渲染体:引用共享的 Model 资产。
|
||
// 存中性 AssetRef(Core 层纯值类型,Unity InstanceID 思路)——
|
||
// ECS 不感知资产系统内部;解析归 AssetManager,悬空由 generation 拦截。
|
||
struct MeshRendererComponent {
|
||
ModelRef model;
|
||
};
|
||
|
||
// 骨骼动画播放状态(纯数据 + 便捷控制)。
|
||
// 采样/局部层混合逻辑在 SceneRenderer::BuildFrame(渲染侧唯一消费者);
|
||
// ImGui 面板经 Play()/字段直接驱动。无此组件 = 按绑定姿势渲染。
|
||
struct AnimationComponent {
|
||
int clipIndex = 0; // 当前 clip(Model::GetClips 下标)
|
||
bool playing = true;
|
||
bool loop = true;
|
||
f32 speed = 1.0f;
|
||
f32 time = 0.0f; // 当前 clip 播放头(秒,wrap 由消费端处理)
|
||
|
||
// ---- crossfade(clip 切换平滑过渡)----
|
||
// 来源 clip 淡出期间与目标 clip 并行采样,局部 TRS 层混合
|
||
//(palette 数量不变,只是求值端多采一组姿态)
|
||
int fadeFrom = -1; // 来源 clip 下标(-1 = 无过渡)
|
||
f32 fadeFromTime = 0.0f; // 来源 clip 播放头
|
||
f32 fadeElapsed = 0.0f; // 已过渡时长
|
||
f32 fadeDuration = 0.3f; // 过渡总时长(秒)
|
||
|
||
// 切换 clip(带 crossfade)。越界/空 clip 保护在消费端。
|
||
void Play(int index, f32 fadeSeconds = 0.3f)
|
||
{
|
||
if (index == clipIndex) return;
|
||
fadeFrom = clipIndex;
|
||
fadeFromTime = time;
|
||
fadeElapsed = 0.0f;
|
||
fadeDuration = fadeSeconds;
|
||
clipIndex = index;
|
||
time = 0.0f;
|
||
}
|
||
};
|
||
|
||
// 相机。一个场景中 primary=true 的实体是当前渲染视角。
|
||
struct CameraComponent {
|
||
Camera camera;
|
||
bool primary = false;
|
||
};
|
||
|
||
// 灯光。类型:
|
||
// Directional——方向即照射方向(无位置,阴影光源用第 0 盏方向光)
|
||
// Point——位置取实体 Transform(固有的),向四周发光 + 距离衰减
|
||
struct LightComponent {
|
||
enum class Type : u8 { Directional = 0, Point = 1 };
|
||
Type type = Type::Directional;
|
||
glm::vec3 direction{0.0f, -1.0f, 0.0f}; // 仅方向光
|
||
glm::vec4 color{1.0f, 1.0f, 1.0f, 1.0f}; // rgb = 颜色,a = 强度
|
||
f32 radius = 10.0f; // 仅点光:衰减半径
|
||
bool castShadow = false; // 方向光可投影(引擎取第一个 castShadow 方向光为 CSM 光源)
|
||
};
|
||
|
||
// 刚体(Jolt 后端)。纯数据 POD——头文件零 Jolt 依赖,
|
||
// bodyId 存 JPH::BodyID 的裸位模式,翻译在 PhysicsWorld。
|
||
// 生命周期:PhysicsWorld::SyncScene 每帧对账(创建/销毁/参数同步),
|
||
// 动态体的 Transform 由物理写回(Writeback 标脏),静态/运动学的
|
||
// Transform 由编辑器驱动同步进 body。
|
||
// 扩展路线:软体/布料 = SoftBodyComponent(Jolt SoftBodySharedSettings),
|
||
// 角色 = CharacterControllerComponent(CharacterVirtual),载具 = VehicleComponent
|
||
// (VehicleConstraint)——都走 PhysicsWorld 门面,不复用本组件。
|
||
struct RigidBodyComponent {
|
||
enum class BodyType : u8 { Static = 0, Dynamic = 1, Kinematic = 2 };
|
||
// Mesh:用实体 MeshRenderer 的视觉网格建碰撞(Unity MeshCollider
|
||
// 语义)——Static → Jolt MeshShape(精确三角网格,不可移动);
|
||
// Dynamic/Kinematic → ConvexHullShape(凸包近似,MeshShape 太贵
|
||
// 不能动)。数据源 Mesh 的 CPU 位置副本,无 MeshRenderer 回退 Box
|
||
enum class ColliderType : u8 {
|
||
Box = 0, Sphere = 1, Mesh = 2,
|
||
Capsule = 3, Cylinder = 4, Compound = 5
|
||
};
|
||
|
||
// Compound 子形状:局部 pos/rot 相对刚体原点,不递归嵌套
|
||
//(child 不能再是 Compound)。尺寸语义同顶层(× Transform 缩放烘焙)
|
||
struct CompoundChild {
|
||
enum class Shape : u8 { Box = 0, Sphere = 1, Capsule = 2 };
|
||
Shape shape = Shape::Box;
|
||
glm::vec3 boxHalfExtents{0.25f, 0.25f, 0.25f};
|
||
f32 radius = 0.25f; // Sphere 半径 / Capsule 半径
|
||
f32 capsuleHalfHeight = 0.25f; // 仅 Capsule:圆柱段半高
|
||
glm::vec3 position{0.0f};
|
||
glm::quat rotation{1.0f, 0.0f, 0.0f, 0.0f};
|
||
bool operator==(const CompoundChild&) const = default;
|
||
};
|
||
|
||
BodyType type = BodyType::Dynamic; // Unity 语义:加 Rigidbody 即受物理驱动
|
||
ColliderType collider = ColliderType::Box;
|
||
|
||
// 碰撞体尺寸(× Transform 缩放,Unity BoxCollider.size 语义)——
|
||
// 程序化模型顶点烘焙尺寸,不能从 scale 反推。
|
||
// Capsule/Cylinder 的"半高"指圆柱段半高(不含半球端盖),总高 =
|
||
// 2*(halfHeight + radius);Jolt 两 ctor 参数序相反见 PhysicsWorld
|
||
glm::vec3 boxHalfExtents{0.5f, 0.5f, 0.5f};
|
||
f32 sphereRadius = 0.5f;
|
||
f32 capsuleRadius = 0.5f; // Unity CapsuleCollider 默认值同构
|
||
f32 capsuleHalfHeight = 0.5f;
|
||
f32 cylinderRadius = 0.5f;
|
||
f32 cylinderHalfHeight = 0.5f;
|
||
std::vector<CompoundChild> compound; // 仅 ColliderType::Compound
|
||
|
||
// 动态体参数
|
||
f32 mass = 1.0f;
|
||
f32 gravityScale = 1.0f;
|
||
f32 friction = 0.5f;
|
||
f32 restitution = 0.2f;
|
||
|
||
// 传感器(Unity isTrigger 语义):只产生 Added/Removed 事件,无碰撞解算
|
||
bool isSensor = false;
|
||
|
||
// 运行时(PhysicsWorld 维护,勿手改)
|
||
u32 bodyId = 0; // JPH::BodyID 裸位
|
||
bool bodyValid = false;
|
||
glm::vec3 velocity{0.0f}; // 只读调试视图:线速度(m/s)
|
||
};
|
||
|
||
// 约束/关节(Jolt TwoBodyConstraint 家族)。纯数据 POD 零 Jolt 依赖;
|
||
// 创建/对账在 PhysicsWorld::SyncConstraints(kPhyDbgConstraints 出图元)。
|
||
// target 存 UUID(跨存档稳定),惰性解析——SyncScene 每帧按 UUID 反查
|
||
// 实体:序列化无需两遍解析,UUID 再生后自然失联,加载顺序无关
|
||
struct JointComponent {
|
||
enum class Type : u8 { Fixed = 0, Hinge = 1, Ball = 2 };
|
||
Type type = Type::Fixed;
|
||
UUID target{}; // 目标实体(0 = 未设置,不建约束)
|
||
|
||
// 锚点:各体 Transform 局部系(Unity connectedAnchor 语义,
|
||
// × 本体缩放烘焙)。Fixed/Hinge/Ball 都连这两点
|
||
glm::vec3 anchorA{0.0f}; // 本体(挂组件的实体)
|
||
glm::vec3 anchorB{0.0f}; // 目标体
|
||
|
||
// Hinge 专用:转轴(本体局部系,方向量不吃缩放)+ 角度限位(度)
|
||
glm::vec3 hingeAxis{1.0f, 0.0f, 0.0f};
|
||
bool enableLimit = false;
|
||
f32 limitMin = -90.0f; // Jolt 语义 min∈[-180,0] max∈[0,180]
|
||
f32 limitMax = 90.0f;
|
||
|
||
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
|