主题
服务端预烘焙模型碰撞
系统文档:docs/systems/ServerPrebakedModelCollision.md。物理分层参考 docs/systems/PhysicsSystem.md。
先判断是否该用
只在对象运行时才生成,并且其复杂几何无法用少量基本碰撞形状合理表达时使用。典型需求是动态建造房屋、运行时楼梯、可受力装饰物或多部件动态物体。
不适合:
- 地形编辑器中的静态地形、楼梯、建筑和装饰:场景生成会自动烘焙到地形 / 场景物理数据。
- 角色主碰撞:使用胶囊体;官方物理角色控制器已经包含。
- 普通运行时刚体:优先盒、球、胶囊、圆柱等基本几何体或少量组合。
- 客户端本地物理:客户端可直接加载模型 / Prefab 并据此生成碰撞。
模式选型:
| 模式 | 使用条件 |
|---|---|
StaticTriangleMesh | 静态复杂几何,Mass = 0 |
ConvexHull | 合并整个资产为一个凸包,适合简单动态刚体 |
CompoundConvex | 每个独立 StaticModel 部件一个凸包,适合多部件动态刚体 |
BoundsBox / BoundsCapsule | 当前不支持;简单对象直接使用基础碰撞形状 |
CompoundConvex 不自动分解单个凹网格。凸包模式的 MaxHullVertices 合法范围为 4 至 4096。
固定架构
text
客户端/编辑器资源链 服务端运行时
GameDataModel.CollisionBake GameLink<GameDataModel>
-> Client-Resource -> HashCodeLocal
-> ref/model_collision.json -> block/model_collision/<hash>.bin
-> 编辑器烘焙 .bin + .roles.dat -> local 物理实例原始 .mdl / .prefab 不上传服务端。运行时不读 registry 或 descriptor JSON。客户端不需要这条管线,因为它可以直接加载模型 / Prefab 并生成本地碰撞。
接入配方
1. 配置 GameDataModel
csharp
private static readonly GameLink<GameDataModel, GameDataModel> Model =
new("$GameEntry.Models.DynamicStair");
_ = new GameDataModel(Model)
{
Asset = "deco/buildsystem/sm_buildsystem_stairs_f/model.prefab",
CollisionBake = new()
{
Enabled = true,
Mode = ModelCollisionBakeMode.StaticTriangleMesh,
},
};配置必须能被静态分析。不要运行时拼模型路径或动态构造 CollisionBake。
2. 生成产物
运行编辑器“统计资源并生成”,或 MCP:
json
{ "tool": "resource_statistics_generate_animations", "arguments": {} }必须检查:
text
ref/model_collision.json
block/model_collision/<hash>.bin
block/model_collision/<hash>.roles.dat3. 服务端创建
csharp
#if SERVER
var result = PrebakedModelCollision.ApplyPrebakedCollision(
node,
Model,
new PrebakedCollisionOptions
{
Mass = 0f,
CollisionLayer = 4,
CollisionMask = uint.MaxValue,
});
if (result != PrebakedModelCollisionApplyResult.Success)
{
node.Destroy();
Game.Logger.LogWarning(
"Prebaked collision creation failed: {Result}",
result);
}
#endifAPI 仅存在于服务端。不要写 bool helper,也不要忽略枚举结果。
动态对象把模式改为 ConvexHull 或 CompoundConvex,并给 Mass 设置正值。凸包运行时会把 local RigidBody 和 CollisionShape 直接附着到传入的 Node;该 Node 应同时作为 replicated transform 和客户端 local Prefab 的父节点,且调用前不能已有物理组件。
4. 客户端表现分层
碰撞创建不会自动显示模型。需要可见对象时:
- 服务端复制语义 Node 或玩法状态。
- 碰撞保持服务端 local。
- 客户端从
Player.LocalPlayer.Scene.GetSceneGraph()观察当前 SCE 场景节点。 - 客户端识别 SyncVar 后,直接加载 Prefab 并挂 local 表现。
不要扫描 GetRegisteredSceneGraphs() 查找当前 SCE 场景。
结果处理
| 结果 | 下一步 |
|---|---|
Success | 继续创建业务对象 |
ModelDataUnavailable | 检查模型注册和 id |
BakeConfigurationMissing / BakeDisabled | 检查 CollisionBake |
UnsupportedBakeMode | 检查是否使用保留模式,以及 SDK / 运行时是否匹配 |
InvalidParentNode | 检查 Node 生命周期 |
CollisionArtifactOpenFailed | 检查生成、文件名和 server 发布过滤 |
InvalidCollisionArtifact | 重新烘焙并确保 SDK/运行时版本一致 |
PhysicsInstanceCreationFailed | 检查场景物理上下文和参数 |
UnknownNativeResult | 检查 SDK 与运行时是否错版 |
InvalidBakeConfiguration | 检查 MaxHullVertices 等烘焙参数 |
DynamicTriangleMeshNotSupported | 静态网格保持 Mass = 0;动态对象改用凸包模式 |
排错顺序
GameDataModel.CollisionBake是否存在且Enabled = true。ref/model_collision.json是否包含该模型及正确modelLinkHashLocal。block/model_collision/<hash>.bin与.roles.dat是否成对存在。- 正式发布是否把
block/model_collision分发到 server。 - 调用是否位于
#if SERVER,结果枚举具体是什么。 - Node 是否仍有效,所在 SCE 场景是否已有物理世界,且目标 Node 是否已存在物理组件。
硬性约束
- 不在服务端加载原始模型或 Prefab。
- 不在客户端调用预烘焙碰撞运行时 API。
- 不为静态三角网格设置正质量。
- 不把碰撞加载失败静默降级为无碰撞对象。
- 不把
ref/model_collision.json或额外 descriptor 当运行时索引。 - 不期待
CompoundConvex自动分解单个凹网格。
相关 Skill 路由
- 复杂模型烘焙配置、产物和结果枚举:继续使用本 Skill。
- replicated Node、local RigidBody、发布图和客户端 transform 表现:使用
server-authoritative-3d-physics。 - 玩家 / Unit 的 KCC、移动与跳跃:使用
3d-unit-game;不要把角色主碰撞改成预烘焙模型碰撞。 - 查找有效 Prefab / 模型路径:先使用
sce-resource-picker。 - 资源生成、编译、运行日志、截图和实际碰撞回归:最后使用
sce-preview-verify完成验收。