531 lines
27 KiB
C++
531 lines
27 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 <Engine/Scripting/ScriptParam.h>
|
||
|
||
#include <entt/entt.hpp>
|
||
|
||
#include <glm/glm.hpp>
|
||
#include <glm/gtc/matrix_transform.hpp>
|
||
#include <glm/gtc/quaternion.hpp>
|
||
#include <cmath>
|
||
#include <algorithm>
|
||
#include <string>
|
||
#include <unordered_map>
|
||
#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 FTransform 语义):TRS 分量 = 相对父的 **local** 值
|
||
//(无父实体 local 即 world,旧场景零迁移)。分量私有,所有写入口
|
||
// 标脏;world 缓存(SRT 三分量 + 矩阵)由帧循环的
|
||
// Scene::UpdateTransforms() 从根 DFS 只对脏子树重算(transform-
|
||
// update 阶段)。子 world = 父 world SRT 组合而非矩阵乘法——
|
||
// 非均匀父缩放下组合式仍是干净 SRT(矩阵分解会引入剪切)。
|
||
// 渲染热路径只读 World();灯光/相机/物理/Gizmo 读 world 三分量。
|
||
//
|
||
// 旋转内部存储 = 四元数(UE FTransform 语义,不存欧拉角):
|
||
// 欧拉往返是漂移/万向锁之源(Gizmo 拖拽乱跳的根因)。欧拉只在
|
||
// 表皮出现——UI 显示(GetRotation 派生)与 UI 输入(SetRotation
|
||
// 转存);Gizmo/物理热路径走 quat 直写(SetRotationQuat)零往返。
|
||
struct TransformComponent {
|
||
// ---- local 读取(相对父;UI/游戏逻辑用)----
|
||
const glm::vec3& GetPosition() const { return m_Position; }
|
||
const glm::quat& GetRotationQuat() const { return m_Rotation; }
|
||
const glm::vec3& GetScale() const { return m_Scale; }
|
||
|
||
// ---- world 缓存读取(要求已由 UpdateTransforms 计算)----
|
||
const glm::mat4& World() const { return m_World; }
|
||
const glm::vec3& GetWorldPosition() const { return m_WorldPosition; }
|
||
const glm::quat& GetWorldRotationQuat() const { return m_WorldRotation; }
|
||
const glm::vec3& GetWorldScale() const { return m_WorldScale; }
|
||
|
||
// 欧拉角(度)视图:从 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)
|
||
// 万向锁分支阈值 1e-6:asin 在 |sin|→1 处导数发散(sin90 的
|
||
// float 表示差 6e-8 → asin 放大成 0.02° 显示噪声,用户实测
|
||
// 90 → 89.98)。贴锁区改走组合角 atan2(分子分母同源缩放,
|
||
// 比值稳定)→ 手输 90.0 显示 90.0。落入该窄区(±0.00057°)
|
||
// 的真实 yaw/roll 被归并为组合角,UI 输入不可达,可接受
|
||
glm::vec3 euler = std::abs(sp) < 1.0f - 1e-6f
|
||
? glm::degrees(glm::vec3(
|
||
std::asin(sp),
|
||
std::atan2(R[2][0], R[2][2]),
|
||
std::atan2(R[0][1], R[1][1])))
|
||
: (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));
|
||
// 显示约定 = UE Details 的 0-360(-90 → 270)。逐轴 fmod wrap
|
||
// 与 [-180,180] 数学严格等价(每轴旋转周期 360°),矩阵重建类
|
||
// 消费端(Camera::EulerRotMat 等)不受影响;需要连续域做
|
||
// clamp/lerp 的调用方自行 wrap 回 [-180,180](如自由视角)
|
||
for (int i = 0; i < 3; ++i) {
|
||
euler[i] = std::fmod(euler[i], 360.0f);
|
||
if (euler[i] < 0.0f) euler[i] += 360.0f;
|
||
if (euler[i] >= 360.0f) euler[i] = 0.0f; // 浮点边界
|
||
}
|
||
return euler;
|
||
}
|
||
|
||
// ---- 写入(标脏,脏实体由 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 航向序,语义不同)。输入域无限制:三角函数周期性
|
||
// 自动归一(720 ≡ -45 ≡ 315),编辑器拖动累计值直接喂
|
||
TransformComponent& SetRotation(const glm::vec3& r)
|
||
{
|
||
return SetRotationQuat(QuatFromEuler(r));
|
||
}
|
||
// 欧拉(度)→ quat 的唯一转换点(qy·qx·qz)。static 供编辑器
|
||
// mirror 的旋转等价检测复用(同一公式构造 → dot 判定严格)
|
||
static glm::quat QuatFromEuler(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 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; }
|
||
|
||
// 显式标脏:层级系统改父/挂载后触发 world 重算用
|
||
//(local 值未变但 world 缓存已失效)
|
||
TransformComponent& MarkDirty() { m_Dirty = true; return *this; }
|
||
|
||
// SRT 分量 → world 矩阵(T * R * S),清除脏标记。
|
||
// 根实体版(world = local);由 Scene::UpdateTransforms 调用,
|
||
// 勿手动
|
||
TransformComponent& Compose()
|
||
{
|
||
m_WorldPosition = m_Position;
|
||
m_WorldRotation = m_Rotation;
|
||
m_WorldScale = m_Scale;
|
||
RebuildWorldMatrix();
|
||
m_Dirty = false;
|
||
return *this;
|
||
}
|
||
|
||
// 子实体版:world = 父 world SRT 组合(UE FTransform 语义):
|
||
// wPos = pPos + pRot·(pScale⊙lPos) wRot = pRot·lRot wScl = pScale⊙lScl
|
||
// 组合式(非矩阵分解)保证非均匀父缩放下结果仍是干净 SRT。
|
||
// 要求父已 Compose(DFS 父先于子保证)。由 Scene 调用,勿手动
|
||
TransformComponent& ComposeFromParent(const TransformComponent& parent)
|
||
{
|
||
m_WorldPosition = parent.m_WorldPosition +
|
||
parent.m_WorldRotation * (parent.m_WorldScale * m_Position);
|
||
m_WorldRotation = parent.m_WorldRotation * m_Rotation;
|
||
m_WorldScale = parent.m_WorldScale * m_Scale;
|
||
RebuildWorldMatrix();
|
||
m_Dirty = false;
|
||
return *this;
|
||
}
|
||
|
||
private:
|
||
void RebuildWorldMatrix()
|
||
{
|
||
const glm::mat4 t = glm::translate(glm::mat4(1.0f), m_WorldPosition);
|
||
const glm::mat4 s = glm::scale(glm::mat4(1.0f), m_WorldScale);
|
||
m_World = t * glm::mat4_cast(m_WorldRotation) * s;
|
||
}
|
||
|
||
// ---- local TRS(相对父)----
|
||
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};
|
||
// ---- world 缓存(UpdateTransforms 重算)----
|
||
glm::vec3 m_WorldPosition{0.0f};
|
||
glm::quat m_WorldRotation{1.0f, 0.0f, 0.0f, 0.0f};
|
||
glm::vec3 m_WorldScale{1.0f};
|
||
glm::mat4 m_World{1.0f};
|
||
bool m_Dirty = true; // 出生即脏:首次 UpdateTransforms 计算 world
|
||
};
|
||
|
||
// 层级链接(UE Actor attach):父句柄 + 子表。惰性创建——
|
||
// 只有参与父子关系的实体才有本组件,独立实体零存储成本。
|
||
// entt 句柄仅在 registry 存续期有效;序列化换 UUID,加载后重链
|
||
struct HierarchyComponent {
|
||
entt::entity parent = entt::null; // entt::null = 根实体
|
||
std::vector<entt::entity> children; // 挂载顺序
|
||
};
|
||
|
||
// 网格渲染体:引用共享的 Model 资产。
|
||
// 存中性 AssetRef(Core 层纯值类型,Unity InstanceID 思路)——
|
||
// ECS 不感知资产系统内部;解析归 AssetManager,悬空由 generation 拦截。
|
||
//
|
||
// 材质覆盖(UE Material Slot 语义):key = mesh 下标(Model::GetMeshes
|
||
// 序),value = .material 资产路径;无该项 = 用模型内建材质(只读)。
|
||
// 存路径不存句柄:覆盖材质由渲染收集期按路径惰性 resolve(资产
|
||
// 已被场景预载/编辑器指派,收集只查缓存),组件保持纯数据。
|
||
// 资产被多实体共享时编辑同时生效;实体独立 = 每实体一份覆盖文件
|
||
struct MeshRendererComponent {
|
||
ModelRef model;
|
||
bool active = true; // 禁用 = 渲染/拾取跳过(Unity enabled / UE SetActive)
|
||
// 单 mesh 子引用:-1 = 整模型全部;N = 只画第 N 个
|
||
//(GetMeshes 下标)。物理 Mesh 碰撞体自动跟随(BuildMeshShape
|
||
// 读本组件)。序列化缺省键 = -1 向前兼容
|
||
int meshIndex = -1;
|
||
std::unordered_map<u32, std::string> materialOverrides;
|
||
};
|
||
|
||
// 骨骼动画播放状态(纯数据 + 便捷控制)——Unity Animator 组件 /
|
||
// UE SkeletalMeshComponent 动画侧的对应物。
|
||
//
|
||
// 职责分层(2026-10-06 重构,对齐 UE/Unity):
|
||
// Model(资产:node/bone/clip)→ AnimationComponent(状态,本结构)
|
||
// → Animation::AnimationSystem(调度:推进播放头 + 姿态求值 +
|
||
// palette 产出)→ SceneRenderer(渲染只读查表消费)。
|
||
// 本组件不含任何求值逻辑;ImGui 面板经 Play()/字段直接驱动。
|
||
// 无此组件 = 按绑定姿势渲染。
|
||
struct AnimationComponent {
|
||
// ---- 播放设置(序列化落盘)----
|
||
bool active = true; // 禁用 = 暂停采样(按当前帧姿态定格)
|
||
int clipIndex = 0; // 当前 clip(Model::GetClips 下标)
|
||
bool playing = true;
|
||
bool loop = true;
|
||
f32 speed = 1.0f;
|
||
// 根运动开关(Unity Animator "Apply Root Motion" 同语义):
|
||
// true = 保留根节点位移轨道(跑动 clip 自带位移会漂出锚点);
|
||
// false = 原地播放(丢弃动画子树根节点位移,旋转保留)——
|
||
// 位移归脚本/物理驱动的角色(CharacterController 等)用 false
|
||
bool applyRootMotion = true;
|
||
|
||
// ---- 播放头 / crossfade(瞬态,不落盘——重启从 0 重播)----
|
||
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; // 过渡总时长(秒)
|
||
|
||
// 按名播放的运行时反指(瞬态,不落盘不序列化):AnimationSystem
|
||
// 每帧回填(同 RigidBody::bodyValid 模式)。空 = 模型未就绪
|
||
const Model* runtimeModel = nullptr;
|
||
|
||
// ---- 资产引用(序列化落盘)----
|
||
// 外部动画资产(编辑器 attach 的动画容器文件,随场景持久化)。
|
||
// clips 本体运行时并入 Model 资产(共享);重启后由 Inspector
|
||
// 按此列表幂等重放
|
||
std::vector<std::string> attachedAnimPaths;
|
||
|
||
// ---- Controller 模式(Unity Animator 语义)----
|
||
// Direct = 现状单 clip 直控(clipIndex/playing 等);Controller =
|
||
// .animctrl 状态机驱动(参数 + 过渡 + BlendTree)。两模式字段
|
||
// 独立,切换互不清除(来回切配置不丢)
|
||
enum class AnimMode : u8 { Direct = 0, Controller = 1 };
|
||
AnimMode mode = AnimMode::Direct; // 落盘;缺键回退 Direct
|
||
|
||
std::string controllerPath; // .animctrl 资产路径(落盘)
|
||
// 参数实例值(落盘 = 设计意图:调好的值重启还在;缺键 = 用
|
||
// Controller 声明的默认值)。Trigger 是一次性信号不落盘
|
||
std::unordered_map<std::string, float> floatParams;
|
||
std::unordered_map<std::string, bool> boolParams;
|
||
std::vector<std::string> pendingTriggers; // 消费即清(瞬态)
|
||
|
||
// 阻尼参数推进状态(瞬态,不落盘——重启从 floatParams 当前值
|
||
// 重新起步):current 逐帧指数逼近 target,写回 floatParams 供
|
||
// 状态机条件统一读取
|
||
struct DampedParam {
|
||
float current = 0.0f;
|
||
float target = 0.0f;
|
||
float response = 8.0f; // 逼近速率(1/s,越大越快)
|
||
};
|
||
std::unordered_map<std::string, DampedParam> dampedParams;
|
||
|
||
// 状态机瞬态(不落盘,重启从 entry state 开始)
|
||
std::string currentState; // 空 = 未初始化(System 首帧置 entry)
|
||
f32 stateTime = 0.0f; // 当前状态播放头(秒)
|
||
std::string transFrom; // 过渡来源状态(空 = 无过渡)
|
||
f32 transFromTime = 0.0f; // 来源状态播放头(过渡期间继续推进)
|
||
f32 transElapsed = 0.0f; // 已过渡时长
|
||
f32 transFade = 0.25f; // 过渡总时长(秒)
|
||
|
||
// ---- 脚本/编辑器共用 API(Unity Animator.SetXxx 语义)----
|
||
void SetFloat(const std::string& name, float value)
|
||
{
|
||
floatParams[name] = value;
|
||
}
|
||
// 阻尼变体(Unity SetFloat(name, target, dampTime) 同构):登记
|
||
// 目标值,AnimationSystem Controller 驱动期逐帧指数逼近(帧率
|
||
// 无关)。首次登记从 floatParams 当前值起步(无则取目标),
|
||
// 避免跳变。与 SetFloat 同名混用 = 直接 set 会被下一帧阻尼值
|
||
// 覆盖,别混
|
||
void SetFloatDamped(const std::string& name, float target, float response)
|
||
{
|
||
auto [it, inserted] = dampedParams.try_emplace(name);
|
||
it->second.target = target;
|
||
it->second.response = glm::max(response, 0.01f);
|
||
if (inserted) {
|
||
const auto fp = floatParams.find(name);
|
||
it->second.current =
|
||
fp != floatParams.end() ? fp->second : target;
|
||
}
|
||
}
|
||
void SetBool(const std::string& name, bool value)
|
||
{
|
||
boolParams[name] = value;
|
||
}
|
||
// 一次性信号:被状态机条件评估消费后清除(同名去重——误配资产
|
||
// 无消费条件时防每帧 SetTrigger 无限堆积)
|
||
void SetTrigger(const std::string& name)
|
||
{
|
||
if (std::find(pendingTriggers.begin(), pendingTriggers.end(),
|
||
name) == pendingTriggers.end()) {
|
||
pendingTriggers.push_back(name);
|
||
}
|
||
}
|
||
// 强制切状态(CrossFade 语义:来源状态淡出、目标状态淡入,
|
||
// fade<=0 = 硬切)。编辑器下拉与脚本共用
|
||
void CrossFade(const std::string& stateName, f32 fadeSeconds)
|
||
{
|
||
if (stateName == currentState) return;
|
||
transFrom = currentState;
|
||
transFromTime = stateTime;
|
||
transElapsed = 0.0f;
|
||
transFade = glm::max(fadeSeconds, 1e-3f);
|
||
currentState = stateName;
|
||
stateTime = 0.0f;
|
||
}
|
||
|
||
// 切换 clip 并开始播放(Unity Animator.Play 语义:指派即播,
|
||
// 编辑态选中 clip 直接预览;crossfade 越界/空 clip 保护在消费端)。
|
||
// 脚本 API 与编辑器 Inspector 共用同一入口
|
||
void Play(int index, f32 fadeSeconds = 0.3f)
|
||
{
|
||
if (index == clipIndex) {
|
||
playing = true; // 重复指派 = 确保在播
|
||
return;
|
||
}
|
||
fadeFrom = clipIndex;
|
||
fadeFromTime = time;
|
||
fadeElapsed = 0.0f;
|
||
fadeDuration = fadeSeconds;
|
||
clipIndex = index;
|
||
time = 0.0f;
|
||
playing = true;
|
||
}
|
||
|
||
// 按名播放(Unity Animator.Play("Walk") 同形,推荐):clip 名经
|
||
// runtimeModel 的 clip 表解析,消掉下标硬耦合(attach 顺序变了
|
||
// 脚本不改)。未命中/模型未就绪返回 false。实现住
|
||
// AnimationSystem.cpp——需要 Model 完整类型,组件头保持零资产
|
||
// 依赖(Model.h 拖 vulkan/assimp,进本头会污染全部包含者)
|
||
bool Play(const std::string& clipName, f32 fadeSeconds = 0.3f);
|
||
};
|
||
|
||
// 相机。一个场景中 primary=true 的实体是当前渲染视角。
|
||
struct CameraComponent {
|
||
bool active = true; // 禁用 = 退出 primary 候选
|
||
Camera camera;
|
||
bool primary = false;
|
||
};
|
||
|
||
// 灯光。类型:
|
||
// Directional——方向即照射方向(无位置,阴影光源用第 0 盏方向光)
|
||
// Point——位置取实体 Transform(固有的),向四周发光 + 距离衰减
|
||
struct LightComponent {
|
||
bool active = true; // 禁用 = 不进光照
|
||
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 {
|
||
bool active = true; // 禁用 = body 销毁/不建(对账自然消化)
|
||
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 / UE Overlap Events 语义):只产生
|
||
// Added/Removed 事件,无碰撞解算;底层 Jolt sensor
|
||
bool isTrigger = 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 {
|
||
bool active = true; // 禁用 = 约束销毁/不建(对账自然消化)
|
||
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 {
|
||
bool active = true; // 禁用 = 角色移除(重启用按当前 Transform 重建)
|
||
f32 radius = 0.3f; // 胶囊半径
|
||
f32 capsuleHalfHeight = 0.4f; // 圆柱段半高(总高 = 2*(hh+r))
|
||
f32 mass = 70.0f;
|
||
f32 maxSlopeAngle = 45.0f; // 度:可站立的最大坡角
|
||
f32 stepHeight = 0.4f; // 台阶自动攀爬高(WalkStairs)
|
||
// 重力倍率(UE CharacterMovement.GravityScale 同位):真实 9.81
|
||
// 对人形角色偏"棉花糖"(滞空 1.2s+),游戏手感普遍 2~3 倍
|
||
f32 gravityScale = 2.0f;
|
||
|
||
bool operator==(const CharacterControllerComponent&) const = default;
|
||
// 实体原点 = 脚底(胶囊底端),符合角色 pivot 习惯
|
||
};
|
||
|
||
// 脚本组件:多脚本挂载(Unity 语义——一个 GameObject 挂 N 个
|
||
// MonoBehaviour,各自有 enabled)。每项 = 脚本类名(注册表键,
|
||
// 将来 Lua/C# 同键解析)+ 独立启停位;组件级 active 是总开关。
|
||
// 实例生命周期由 ScriptRuntime 对账管理(不进组件,保持可拷贝)
|
||
struct ScriptComponent {
|
||
// 单个脚本项
|
||
struct Item {
|
||
std::string name;
|
||
bool active = true; // 禁用 = 实例保留但停 OnUpdate
|
||
// 参数(权威数据,随场景序列化)。脚本成员 = 运行时真值,
|
||
// 实例化期由 ScriptRuntime 按 schema 灌入:键缺失 → 声明
|
||
// 默认,多余键忽略;运行时 SetParam 写穿回这里(PIE 实时
|
||
// 反映,Stop 由快照天然回滚)
|
||
std::vector<Engine::Scripting::ScriptParam> params;
|
||
|
||
bool operator==(const Item&) const = default;
|
||
};
|
||
|
||
bool active = true; // 组件总开关(全部停 tick,实例保留)
|
||
std::vector<Item> items;
|
||
|
||
bool operator==(const ScriptComponent&) const = default;
|
||
};
|
||
|
||
// 天空盒/环境背景(UE Skybox Actor 语义:环境是场景里的 Actor +
|
||
// 组件,不是常驻全局设置)。envPath = 环境贴图资产(.hdr,经
|
||
// AssetManager 加载、Renderer 预积分后进 IBL/Skybox pass);空 =
|
||
// 只清屏(ClearColor 露出)。多实例取第一个 active(entt 视图序 =
|
||
// 组件创建序,首个命中即稳定)
|
||
struct SkyboxComponent {
|
||
bool active = true; // 禁用 = 不画天空(清屏色露出)
|
||
std::string envPath;
|
||
};
|
||
|
||
// 环境光(UE SkyLight 语义:全局漫射环境照明)。多实例取第一个 active
|
||
struct SkyLightComponent {
|
||
bool active = true;
|
||
glm::vec4 ambientColor{1.0f}; // 旧 Settings 默认
|
||
};
|
||
|
||
} // namespace Engine::ECS
|
||
|
||
#endif //HELLOVULKAN_COMPONENTS_H
|