主题
服务端预烘焙模型碰撞
服务端预烘焙模型碰撞(Server Prebaked Model Collision)用于让服务端为复杂模型创建与美术几何匹配的 3D 碰撞体,同时不向服务端分发 .mdl、.prefab、材质或纹理等完整美术资源。
该机制用于运行时生成且具有复杂几何碰撞需求的对象,例如动态建造房屋、运行时楼梯、可受力装饰物和由多个独立模型部件组成的动态物体。它不是静态地图碰撞、角色碰撞或普通刚体碰撞的默认方案。
先判断是否需要
| 场景 | 应使用的碰撞来源 |
|---|---|
| 地形编辑器中的静态地形、静态楼梯、静态建筑和场景装饰 | 由地形 / 场景生成流程自动烘焙进场景物理数据,不配置 GameDataModel.CollisionBake |
| 角色或主控单位 | 胶囊体;官方物理角色控制器已经包含胶囊 motor 和角色碰撞 |
| 可用简单形状表达的运行时对象 | 用盒、球、胶囊、圆柱等基本几何体或少量基本形状组合 |
| 客户端本地物理或表现 | 客户端直接加载模型 / Prefab,并据此创建本地碰撞,不使用服务端 cooked 产物 |
| 运行时动态生成,且几何复杂到基本形状难以合理表达 | 使用服务端预烘焙模型碰撞 |
典型需求是动态建造系统:房屋在地图加载时并不存在,服务端运行时才决定它的位置、旋转和生命周期,但又需要与楼梯、门洞或不规则外形匹配的权威碰撞。静态建筑使用三角网格;需要重力、速度或受力响应的物体使用单凸包或复合凸包。
总体链路
text
GameDataModel.CollisionBake
|
v
Client-Resource 静态分析
|
v
ref/model_collision.json
|
v
编辑器“统计资源并生成”
|
v
block/model_collision/{HashCodeLocalHex}.bin
block/model_collision/{HashCodeLocalHex}.roles.dat
|
v
服务端 ApplyPrebakedCollision(...)
|
v
local RigidBody + CollisionShape运行时只读取 block/model_collision 中的 cooked 数据。ref/model_collision.json 是构建期输入,不参与运行时寻址;服务端也不需要原始模型资源。
运行端边界
| 能力 | 服务端 | 客户端 / 编辑器 |
|---|---|---|
GameDataModel.CollisionBake 配置 | 可读取 | 可读取并参与资源分析 |
生成 ref/model_collision.json | 不执行 | Client-Resource 执行 |
烘焙 .bin / .roles.dat | 不执行 | 编辑器资源生成流程执行 |
PrebakedModelCollision.ApplyPrebakedCollision(...) | 可用 | API 不存在 |
| 加载原始模型 / Prefab 创建表现 | 不需要 | 客户端直接使用原始资源 |
运行时 API 被限定为 server-only。客户端已经具备模型和 Prefab 资源,可以直接加载素材并据此生成本地碰撞,不应读取 block/ 下的服务端 cooked 产物,也不应通过该机制创建客户端表现。
声明烘焙配置
csharp
using GameCore.ResourceType.Data;
using GameCore.ResourceType.Data.Struct;
using GameData;
private static readonly GameLink<GameDataModel, GameDataModel> StairModel =
new("$GameEntry.Models.DynamicStair");
private static void RegisterGameData()
{
_ = new GameDataModel(StairModel)
{
Asset = "deco/buildsystem/sm_buildsystem_stairs_f/model.prefab",
CollisionBake = new ModelCollisionBake
{
Enabled = true,
Mode = ModelCollisionBakeMode.StaticTriangleMesh,
},
};
}可用模式如下:
| 模式 | 适用场景 | 关键语义 |
|---|---|---|
StaticTriangleMesh | 静态建筑、楼梯、门洞和不规则阻挡 | 只允许 Mass = 0,可精确保留凹几何 |
ConvexHull | 形状相对简单、需要运动或受力的动态物体 | 合并资产内所有 StaticModel 几何与部件变换,生成一个凸包 |
CompoundConvex | 由多个独立模型部件组成的动态物体 | 每个独立 StaticModel 生成一个凸包,保留部件本地 position / rotation,并在一个 RigidBody 下创建多个 CollisionShape |
BoundsBox / BoundsCapsule | 保留模式 | 当前不支持;简单对象直接使用 GameGraph 基础碰撞形状 |
CompoundConvex 不会把一个凹网格自动分解成多个凸包。如果素材只有一个凹 StaticModel,应调整素材部件、改用静态三角网格,或用基础凸形状手工组合。
动态凸包配置示例:
csharp
private static readonly GameLink<GameDataModel, GameDataModel> DynamicPropModel =
new("$GameEntry.Models.DynamicProp");
_ = new GameDataModel(DynamicPropModel)
{
Asset = "deco/prop/sm_rhzs_dc/model.prefab",
CollisionBake = new ModelCollisionBake
{
Enabled = true,
Mode = ModelCollisionBakeMode.ConvexHull,
MaxHullVertices = 32,
},
};MaxHullVertices 只影响凸包模式,合法范围为 4 至 4096。较低上限可减少物理复杂度,但也会降低轮廓精度。
分析器要求 Asset 和 CollisionBake 能被静态解析。运行时拼接模型路径或动态构造烘焙配置,可能导致模型未进入 ref/model_collision.json。
生成与检查产物
通过编辑器的资源统计与派生产物生成入口执行完整流程,或通过 Editor MCP 调用:
json
{ "tool": "resource_statistics_generate_animations", "arguments": {} }该工具名保留了历史动画命名,但当前会同时生成动画和模型碰撞派生产物。生成后检查:
text
<ProjectRoot>/ref/model_collision.json
<ProjectRoot>/block/model_collision/<8位小写HashCodeLocal十六进制>.bin
<ProjectRoot>/block/model_collision/<8位小写HashCodeLocal十六进制>.roles.dat每个 GameDataModel 只有一份当前烘焙配置。完整生成会先清理 block/model_collision,不会为同一模型保留多套 settings 变体,也不会生成 runtime registry 或 descriptor JSON。
服务端创建静态碰撞
csharp
#if SERVER
using GameGraph.NodeSystem;
using GameGraph.NodeSystem.Component.Physics;
var root = scene.GetSyncRoot()
?? throw new InvalidOperationException("SceneGraph is not ready.");
var node = root.CreateChild("DynamicStair", NodeReplication.Replicated)
?? throw new InvalidOperationException("Failed to create stair node.");
node.Position = new Vector3(3220f, 2050f, 0f);
var result = PrebakedModelCollision.ApplyPrebakedCollision(
node,
StairModel,
new PrebakedCollisionOptions
{
CollisionLayer = 4,
CollisionMask = uint.MaxValue,
Mass = 0f,
});
if (result != PrebakedModelCollisionApplyResult.Success)
{
node.Destroy();
Game.Logger.LogWarning(
"Dynamic stair collision creation failed: {Result}",
result);
}
#endifStaticTriangleMesh 必须保持 Mass = 0。运行时会在目标节点下创建 local 静态碰撞子节点,不会把刚体和碰撞网格复制给客户端。
服务端创建动态凸包
csharp
#if SERVER
var prop = root.CreateChild("DynamicProp", NodeReplication.Replicated)
?? throw new InvalidOperationException("Failed to create prop node.");
prop.Position = new Vector3(3000f, 2050f, 500f);
var result = PrebakedModelCollision.ApplyPrebakedCollision(
prop,
DynamicPropModel,
new PrebakedCollisionOptions
{
Mass = 20f,
CollisionLayer = 4,
CollisionMask = uint.MaxValue,
Friction = 0.8f,
});
if (result != PrebakedModelCollisionApplyResult.Success)
{
prop.Destroy();
Game.Logger.LogWarning(
"Dynamic prop collision creation failed: {Result}",
result);
}
#endifConvexHull / CompoundConvex 使用正 Mass 创建动态刚体。运行时把一个 local RigidBody 和一个或多个 local CollisionShape 直接附着到调用方传入的目标 Node,因此服务端物理移动的正是客户端接收 transform 的 replicated Node。目标 Node 不能预先存在 RigidBody 或 CollisionShape;实例化中途失败时,运行时会回滚本次已创建的物理组件。
创建结果
| 结果 | 含义 | 优先检查 |
|---|---|---|
Success | 加载并实例化成功 | 无 |
ModelDataUnavailable | GameLink 无法解析模型数据 | 注册时机、模型 id |
BakeConfigurationMissing | 未配置 CollisionBake | GameDataModel 初始化器 |
BakeDisabled | 配置显式关闭 | Enabled |
UnsupportedBakeMode | 使用了保留模式或运行时不认识该模式 | Mode、SDK 与运行时版本 |
InvalidParentNode | 目标 Node 已失效 | Node 生命周期 |
CollisionArtifactOpenFailed | .bin 缺失或不可打开 | 是否执行资源生成、服务端发布过滤 |
InvalidCollisionArtifact | .bin 格式、版本或内容无效 | 重新生成、SDK/运行时版本一致性 |
PhysicsInstanceCreationFailed | 数据已加载但物理实例创建失败 | PhysicsWorld、节点上下文、参数 |
UnknownNativeResult | SDK 尚未识别 native 新结果 | SDK 与运行时版本是否匹配 |
InvalidBakeConfiguration | 烘焙参数非法 | MaxHullVertices 等配置 |
DynamicTriangleMeshNotSupported | StaticTriangleMesh 使用了正质量 | 保持 Mass = 0,动态对象改用凸包模式 |
不要在失败时静默降级为“无碰撞对象”。权威服务端若缺少碰撞数据,应阻止对象创建或进入明确的业务失败路径。
缩放与参数
LocalScale是碰撞实例的额外本地缩放,默认Vector3.One。ApplyAssetLayerScale = true时,会把GameDataModel.AssetLayerScale乘入碰撞缩放。- 目标 Node 自身的 transform 会继续作用于所有创建出的碰撞形状。
CollisionLayer/CollisionMask应与角色 motor、射线和其他权威碰撞查询使用同一分层约定。IsTrigger、Kinematic、Friction、Restitution会应用到创建出的服务端刚体。StaticTriangleMesh只允许Mass = 0;动态刚体应使用ConvexHull或CompoundConvex并设置正质量。
客户端表现
该系统不会自动在客户端创建模型。推荐模式是:
- 服务端创建 replicated 语义 Node,并写入模型 id、类型或其他轻量 SyncVar。
- 服务端在该 Node 上应用预烘焙碰撞;静态三角网格使用 local 子节点,动态凸包组件直接附着到该 Node。
- 客户端从
Player.LocalPlayer.Scene.GetSceneGraph()取得当前 SCE 场景图。 - 客户端订阅该图的
OnNodeReplicated并回灌GetReplicatedNodes()。 - 客户端识别 SyncVar 后,直接加载原始模型 / Prefab,在 replicated Node 下创建 local 表现。
客户端不要用 GetRegisteredSceneGraphs() 查找当前 SCE 场景;该列表只包含服务端额外发布的独立脚本图。
发布要求
block/model_collision是服务端资源目录,正式发布过滤必须保留给 server。.bin与同 stem 的.roles.dat必须成对发布。ref/model_collision.json是构建期清单,不是服务端运行时依赖。- 编辑器本地调试直接读取项目目录;正式服务端从发布后的地图根目录读取固定哈希路径。
常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
没有 model_collision.json | 分析器无法静态解析配置,或未跑 Client-Resource | 使用直接对象初始化器并重新统计资源 |
有清单但没有 .bin | 未执行编辑器派生产物生成,或模型无法加载 | 运行资源生成并查看编辑器错误 |
返回 CollisionArtifactOpenFailed | 服务端找不到固定哈希文件 | 核对文件名、发布过滤和地图根目录 |
| 服务端有碰撞但客户端看不见模型 | 该系统只创建服务端碰撞 | 通过 replicated Node 在客户端创建 local Prefab 表现 |
| 动态刚体在服务端移动,但客户端模型不跟随 | 表现没有挂在应用碰撞的 replicated Node,或 Node 预先已有物理组件导致创建失败 | 让服务端碰撞和客户端 local Prefab 共用同一 replicated Node,并检查结果枚举 |
CompoundConvex 没有拆开单个凹网格 | 复合模式按独立 StaticModel 部件生成,不做自动 convex decomposition | 拆分素材部件或改用其他碰撞表达 |
| 客户端编译找不到 API | 符合设计,运行时 API 仅服务端 | 把调用放入 #if SERVER |
相关开发 Skills
| Skill | 何时使用 |
|---|---|
server-prebaked-model-collision | 配置复杂静态或动态模型碰撞、生成 cooked 产物、处理加载结果和排查产物问题 |
server-authoritative-3d-physics | 设计 replicated Node + local RigidBody、服务端权威运动、发布图和客户端表现同步 |
3d-unit-game | 开发玩家 / Unit、物理角色控制器、移动、跳跃和角色表现;角色主碰撞不使用本系统 |
sce-resource-picker | 在官方资源索引中确认可用 Prefab / 模型路径,避免凭空编写素材路径 |
sce-preview-verify | 完成接入后执行资源生成、编译、运行时日志、画面和实际碰撞行为验收 |