Skip to content

服务端预烘焙模型碰撞 ​

服务端预烘焙模型碰撞(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);
}
#endif

StaticTriangleMesh 必须保持 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);
}
#endif

ConvexHull / CompoundConvex 使用正 Mass 创建动态刚体。运行时把一个 local RigidBody 和一个或多个 local CollisionShape 直接附着到调用方传入的目标 Node,因此服务端物理移动的正是客户端接收 transform 的 replicated Node。目标 Node 不能预先存在 RigidBody 或 CollisionShape;实例化中途失败时,运行时会回滚本次已创建的物理组件。

创建结果 ​

结果含义优先检查
Success加载并实例化成功无
ModelDataUnavailableGameLink 无法解析模型数据注册时机、模型 id
BakeConfigurationMissing未配置 CollisionBakeGameDataModel 初始化器
BakeDisabled配置显式关闭Enabled
UnsupportedBakeMode使用了保留模式或运行时不认识该模式Mode、SDK 与运行时版本
InvalidParentNode目标 Node 已失效Node 生命周期
CollisionArtifactOpenFailed.bin 缺失或不可打开是否执行资源生成、服务端发布过滤
InvalidCollisionArtifact.bin 格式、版本或内容无效重新生成、SDK/运行时版本一致性
PhysicsInstanceCreationFailed数据已加载但物理实例创建失败PhysicsWorld、节点上下文、参数
UnknownNativeResultSDK 尚未识别 native 新结果SDK 与运行时版本是否匹配
InvalidBakeConfiguration烘焙参数非法MaxHullVertices 等配置
DynamicTriangleMeshNotSupportedStaticTriangleMesh 使用了正质量保持 Mass = 0,动态对象改用凸包模式

不要在失败时静默降级为“无碰撞对象”。权威服务端若缺少碰撞数据,应阻止对象创建或进入明确的业务失败路径。

缩放与参数 ​

  • LocalScale 是碰撞实例的额外本地缩放,默认 Vector3.One。
  • ApplyAssetLayerScale = true 时,会把 GameDataModel.AssetLayerScale 乘入碰撞缩放。
  • 目标 Node 自身的 transform 会继续作用于所有创建出的碰撞形状。
  • CollisionLayer / CollisionMask 应与角色 motor、射线和其他权威碰撞查询使用同一分层约定。
  • IsTrigger、Kinematic、Friction、Restitution 会应用到创建出的服务端刚体。
  • StaticTriangleMesh 只允许 Mass = 0;动态刚体应使用 ConvexHull 或 CompoundConvex 并设置正质量。

客户端表现 ​

该系统不会自动在客户端创建模型。推荐模式是:

  1. 服务端创建 replicated 语义 Node,并写入模型 id、类型或其他轻量 SyncVar。
  2. 服务端在该 Node 上应用预烘焙碰撞;静态三角网格使用 local 子节点,动态凸包组件直接附着到该 Node。
  3. 客户端从 Player.LocalPlayer.Scene.GetSceneGraph() 取得当前 SCE 场景图。
  4. 客户端订阅该图的 OnNodeReplicated 并回灌 GetReplicatedNodes()。
  5. 客户端识别 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完成接入后执行资源生成、编译、运行时日志、画面和实际碰撞行为验收

相关文档 ​