主题
GameGraph 库总览
> 项目视频的 UI 控件、异步加载和视频纹理用法见视频播放。普通界面优先使用 GameUI 视频控件,自定义 Canvas 或材质使用底层 VideoPlayer。
> 角色外观组合见 GameGraph 附加模型挂载:这是“自动挂载模型表现”的客户端下钻接口,通过 Prefab 主骨骼模型的 AttachAdditionModel(...) 挂载,由调用方管理挂载句柄。
GameGraph 是 WasiCore 游戏框架的 场景图与组件化 3D 引擎封装层,底层基于 Urho3D 引擎。它既可以为上层 GameCore 提供节点管理、3D 物理模拟、资源加载、材质控制等能力,作为 Entity/Unit 与 3D 引擎之间的桥梁;也可以被项目代码直接使用,通过 SceneGraph / Node 构造不依赖 SCE Unit/技能系统的 3D 游戏、工具预览或非 Unit 物理对象。
> 选择 SCE Unit/技能系统、直接 GameGraph SceneGraph 或 Canvas/GameUI 绘制路线时,先看 游戏架构路线、SCE 场景空间模型与 Canvas/GameGraph 边界。
> 重要提示:GameGraph 已双端化,但不是所有子系统都双端可用。 SceneGraph / Node / 3D PhysicsWorld / RigidBody / CollisionShape / 2D PhysicsWorld2D / RigidBody2D / CollisionShape2D 可在服务端和客户端使用;图形渲染、材质、音频资源与实际输出、Spine 仍主要是客户端表现层 API。音源与监听器的部分组件状态和控制可双端使用。服务端可用 SceneGraphReplication.ReplicatedUnpublished + Publish(player) 发布脚本创建的 SceneGraph,详见 服务端 GameGraph SceneGraph 发布与同步。
> 坐标系约定:Z 轴向上(左手坐标系),XY 平面为地面。与 Unreal Engine 一致。
与 Urho3D 的兼容心智模型
GameGraph 主要将星火所用 Urho 引擎的常用游戏开发 API 导出并封装为 C# 接口,同时补充星火特有能力,以及与 GameCore 单位、表现和场景系统的桥接。 例如,GameCore 下钻、面向玩家的 SceneGraph 发布,以及星火粒子与动画资源管线,都在熟悉的引擎能力上提供了进一步集成。
GameGraph 已覆盖大部分常用的、面向游戏开发的 Urho3D 场景图、组件、数学、物理,以及客户端渲染与资源操作心智模型。熟悉 Urho3D 的开发者和 AI 助手通常可以沿用 Scene → Node → Component、局部/世界 Transform、Serializable、ResourceCache、Drawable、Octree 和 PhysicsWorld 等既有经验来设计 GameGraph 代码。
这是一套广泛的等价 API 和兼容心智模型,不是 Urho3D 全部 API、全部重载或内部子系统的逐项绑定。生成代码前仍应以当前 SDK 的 api/client 与 api/server 声明确认准确名称、参数和可用端;不要仅凭 Urho3D 记忆假设某个方法必然存在。
对 AI 助手,使用方式是:用 Urho 知识提出方案,用当前 SDK 声明确认可调用接口,用星火专题文档处理扩展与差异。 这适用于直接 Node 开发,也适用于现有 Unit 项目的局部下钻;客户端和服务端分别以各自的 API 声明为准。
优先记住以下星火差异:
- 星火使用 Z-up 左手坐标系;不要直接套用其他 Urho 项目的坐标约定。
SceneGraph、Node、Serializable、数学和主要物理能力优先双端;资源内容、GPU 对象、材质、音频资源与实际输出、渲染流程和表现动画大多仅客户端可用。音源与监听器的部分组件状态和控制可双端使用。- 多数完整游戏仍应以 GameCore 的
Entity/Unit/Actor/Scene/PropertyObject为高层入口,需要精细控制时再通过类型明确的扩展方法下钻到 GameGraph。 - 星火粒子与骨骼动画保留了熟悉的 Urho 使用方式,但不是原生实现的逐项复制;它们提供额外的资源管线、发射器类型、动画烘焙和角色表现能力,具体行为以星火公共 API 和专题文档为准。
- 普通界面使用 GameUI。
Text3DComponent是场景中的可渲染文字组件,不代表 Urho UIElement 系统已经完整导出。 - Navigation 使用
SceneNavigation/ Nav3D 抽象,不应假设原生 NavigationMesh、CrowdManager 等低层类型已经直接导出。 - C++ 中会原地修改对象的方法,在 WASM 友好的 GameGraph API 中可能包装为返回新值;以具体 C# 签名为准。
能力覆盖速查
| 类别 | Urho 心智模型迁移程度 | 星火侧重点或差异 |
|---|---|---|
| SceneGraph / Node / Component | 高 | 加入双端复制、SCE 场景接入、Publish(player) 和 GameCore 下钻 |
| Serializable / Variant | 高 | 用于引擎对象属性、场景保存/Clone 和原生网络属性;不替代 PropertyObject |
| 数学与几何 | 高 | BoundingBox、Sphere、Plane、Frustum、Ray、Polyhedron、Spline、矩阵和空间转换等 |
| 3D / 2D Physics | 高 | 主要能力双端可用,并加入服务端权威 SceneGraph 同步路线 |
| Camera / Light / Zone / Drawable | 中到高 | 部分状态双端可表达;真正渲染和大多数派生表现组件仍在客户端 |
| Octree / Billboard / Decal / Text3D / Terrain / 2D Drawable | 高 | 客户端表现能力;具体材质、Technique、shader 与资源可用性仍需验证 |
| Mesh / Texture / Technique / RenderPath / GPU buffer | 中到高 | 类型或基础句柄可能双端存在,内容读取、GPU 和渲染操作主要仅客户端 |
| 粒子 | 概念兼容 | 继续使用“粒子资源 → 挂载 Node → 控制发射器”,但资源格式、Builder 和发射器模块采用星火能力 |
| 骨骼模型与动画 | 高度兼容 | 保留 Bone、Socket、AnimationState、Layer/Weight/Fade 等模型,并增加星火角色与动画管线能力 |
| UI / Navigation / 引擎内部系统 | 有限 | 使用 GameUI、SceneNavigation/Nav3D 等星火公开抽象,不假设原生低层系统完整存在 |
目录
架构总览
+--------------------------------------------------------------+
| GameCore (游戏逻辑) |
| Entity / Unit / Actor / Player / Ability / Buff ... |
+------------------+-------------------------------------------+
| 扩展方法: GetNode(Unit), GetRigidBody() 等
v
+--------------------------------------------------------------+
| GameGraph (引擎封装层) |
| |
| +------------------+ +---------------+ +---------------+ |
| | NodeSystem | | ResourceSystem| | MaterialSystem| |
| | (场景图/组件) | | (资源加载) | | (材质/Shader) | |
| +--------+---------+ +---------------+ +---------------+ |
| | |
| +--------+---------+ |
| | Component 子系统 | |
| | - Graphics | |
| | - Physics (3D) | |
| | - Physics2D (*) | |
| | - Scripts | |
| +------------------+ |
| (*) 双端可用 |
+-------------------------------+------------------------------+
|
v
+--------------------------------------------------------------+
| Urho3D (底层 3D 引擎) |
+--------------------------------------------------------------+命名空间与职责
| 命名空间 | 职责 | 主要类 |
|---|---|---|
GameGraph.NodeSystem | 场景图核心:节点树、复制模式、发布同步 | Node, SceneGraph, SceneGraphReplication, NodeReplication, ComponentReplication |
GameGraph.NodeSystem.Component | 组件基类与接口 | INodeComponent, EngineNodeComponent |
GameGraph.NodeSystem.Component.Graphics | 图形渲染组件 | LightComponent, Zone, SkyboxComponent, StaticMeshComponent, AnimatedModelComponent, AnimationController, AnimationState, AnimationHandle, Socket, Bone, PrimitiveShapeComponent, DebugRenderer, Frustum, Ray |
GameGraph.NodeSystem.Component.Physics | 3D 物理(双端可用) | PhysicsWorld, RigidBody, CollisionShape |
GameGraph.NodeSystem.Component.Physics.Physics2D | 2D 物理(双端可用) | PhysicsWorld2D, RigidBody2D, 碰撞形状, 约束 |
GameGraph.NodeSystem.Component.Scripts | 脚本生命周期组件 | ScriptComponent |
GameGraph.NodeSystem.Component.Audio | 音频组件 | SoundSourceComponent, SoundSource3DComponent, SoundListenerComponent, AudioManager |
GameGraph.ResourceSystem | 资源加载与缓存 | Resource, ResourceCache, Mesh, Prefab, Technique, Texture2D, ParticleSystem, SoundResource, AnimationResource |
GameGraph.MaterialSystem | 材质、地形渲染 | Terrain, KnownTechnique, KnownColorProperty, KnownFloatProperty |
客户端与服务端差异
GameGraph 的底层场景图、Serializable、数学、3D 物理和 2D 物理已经双端化。资源内容访问、GPU 对象、渲染、材质、音频资源与实际输出、Spine、粒子和表现动画仍主要是客户端能力;音源与监听器的部分组件状态和控制已双端开放。
各模块的客户端/服务端可用性
| 模块 | 双端公共能力 | 主要仅客户端能力 |
|---|---|---|
| NodeSystem / Serializable | SceneGraph, Node, 标签/查询/Transform、Variant 属性、temporary、网络属性、发布/复制模式 | Viewport、RenderToTexture 和客户端场景注册/观察辅助 |
| Graphics 基础 | CameraComponent, LightComponent, Zone, Drawable 的部分场景状态与数学查询 | Static/Animated Model、AnimationController、Octree、BillboardSet、DecalSet、Text3D、Terrain、CustomGeometry、RibbonTrail、Sprite/TileMap 等实际表现组件 |
| Physics 3D | PhysicsWorld, RigidBody, CollisionShape, Constraint、KinematicCharacterMotor、车辆、查询与碰撞事件 | 仅用于可视化的 debug draw 等辅助能力 |
| Physics2D | PhysicsWorld2D, RigidBody2D, 主要碰撞形状、约束、查询与事件 | 与 Sprite、TileMap 等渲染资源直接关联的表现能力 |
| ResourceSystem | Resource, ResourceCache 以及少量基础资源句柄/加载入口 | Image、Prefab、AnimationResource、ParticleSystem、SoundResource、资源内容读取、MeshBuilder、纹理/GPU/RenderSurface/RenderPath/Technique Pass 等详细操作 |
| Audio | SoundSourceComponent, SoundSource3DComponent, SoundListenerComponent 的组件状态,以及 Stop/Seek、音量、频率、声像与衰减等部分控制 | SoundResource、按资源 Play、AudioManager、实际音频加载与输出 |
| Material / Particle | — | 材质与粒子均为客户端表现能力 |
| Scripts | ScriptComponent | — |
> “服务端声明中存在某个资源类型”不等于该类型的内容、GPU 或渲染 API 已双端开放。需要读取纹理像素、访问 Mesh 数据、创建 RenderTarget、修改 Technique Pass 或加载表现资源时,先检查 api/server 是否真的包含所需成员。
> 架构含义:如果只在客户端创建本地 SceneGraph,它仍然只是客户端本地表现层,不会自动获得联机同步、服务器权威逻辑或数据持久化。需要同步脚本创建的 GameGraph 场景时,应在服务端创建 replicated SceneGraph 并按玩家 Publish(player)。多数完整联机游戏仍建议以 GameCore 的 Entity/Unit/GameData 为主体,再按需组合 GameGraph。
其他差异
| 维度 | 服务端 (Server) | 客户端 (Client) |
|---|---|---|
| 实体创建方式 | Entity(通过 GameCore 创建) | Actor(客户端本地对象) |
| 场景图发布 | SceneGraph.Publish(player) / Unpublish(player) | 接收 OnRegistered / OnUnregistered |
| 节点复制模式 | NodeReplication.Local / Replicated / Inherit | 接收 replicated 节点并可检查 IsReplicated |
| 组件复制模式 | ComponentReplication.Local / Replicated / Inherit | 接收 replicated 组件并可检查 IsReplicated |
条件编译标识
csharp
#if SERVER
// 服务端专用逻辑
#endif
#if CLIENT
// 客户端专用逻辑(渲染、音频、材质表现等)
#endif与 GameCore 的集成
首次迁移地图编辑器或 Unity / Urho 经验时,阅读理解星火。需要实际下钻时,阅读GameCore 与 GameGraph 桥接,其中集中说明节点用途、组件查找、控制归属与生命周期。
GameGraph 通过 扩展方法(Extension Methods) 将 GameCore 高层对象映射到对应的场景图或表现组件。这是两个系统之间的核心桥接机制。优先使用类型明确的下钻入口,而不是把任意 Actor 都当成稳定、服务端权威的 Node。
核心扩展方法
| 方法签名 | 作用 |
|---|---|
GetNode(this Unit unit) | 客户端优先取表现节点,缺失时查询默认绑定;服务端取默认绑定,缺失时尝试创建;可能返回 null |
TryGetClientVisualNode(this IActor actor, out Node? node) | 探测 Actor 当前的客户端表现 Node;不代表服务端权威 Node,也不回退 UnitNodeBinding |
GetSceneGraph(this Scene scene) | 获取当前 SCE Scene 绑定的 GameGraph SceneGraph |
GetDynamicRootNode(this Scene scene) | 获取编辑器场景的动态根 Node(仅客户端) |
GetOwnerPhysicsWorld(this Unit unit) | 获取 Unit 所在的 PhysicsWorld |
GetRigidBody(this PhysicsRaycastResult result) | 从射线检测结果获取 RigidBody |
GetParticleEmitters(this ActorParticle actor) | 获取 ActorParticle 实际使用的所有粒子发射器(仅客户端) |
GetParticleEmitter(this ActorParticle actor, int index) | 获取第 index 个粒子发射器(仅客户端) |
GetParticleEmitterCount(this ActorParticle actor) | 获取粒子发射器数量(仅客户端) |
GetSoundSource(this ActorSound actor) | 获取 ActorSound 实际使用的 SoundSourceComponent(仅客户端) |
GetText3D(this ActorText actor) | 获取 ActorText 实际使用的 Text3DComponent(仅客户端) |
GetAnimatedModel(this IActorAnimationPlayable actor) | 获取 Actor 实际使用的 AnimatedModelComponent(仅客户端) |
GetAnimationController(this IActorAnimationPlayable actor) | 获取 Actor 实际使用的 AnimationController(仅客户端) |
典型使用流程
取得已有 Unit / Actor / Scene
|
v
按用途和运行端选择表现节点、绑定节点或场景图入口
|
v
查询目标节点上的组件;需要新能力时按控制归属添加
|
v
保留高层玩法机制,按需补充底层行为在 SCE 场景中,通过 UnitNodeBinding 绑定到 Unit 的 replicated Node 会随该 Unit 的视野可见性对客户端创建或移除;未绑定 Unit 的 replicated Node 仍按所属 SCE Scene 同步。需要让 hitbox、hurtbox、fire point 或自定义 proxy 不越过单位视野泄露时,应通过 BindNode(...) / EnsureNodeBinding(...) 建立绑定,而不是只把 Node 挂到 scene sync root。
Serializable 与 GameCore 属性系统
Node、SceneGraph 和 EngineNodeComponent 暴露的 Serializable/Variant API 用于读取和修改引擎对象自身注册的属性,包括默认值、temporary 状态、场景保存/Clone 语义以及引擎网络属性。它适合通用组件工具、调试器、序列化编辑和确实需要按属性名访问引擎组件的代码。
它不替代 GameCore 的 Unit 属性、PropertyObject 或 PropertyObjectHostComponent:后者负责游戏业务状态、类型安全访问、变更监听以及星火框架的同步与生命周期。普通玩法状态优先使用 GameCore 属性系统;只有目标确实是底层 Node/Component 的引擎属性时才使用 Serializable。
代码示例
csharp
#if CLIENT
using GameGraph.NodeSystem;
// Unit 自身支持动画接口;查询已有模型,不改变单位的移动控制。
var model = unit.GetAnimatedModel();
var socket = model?.GetSocket("socket_weapon_r"); // 挂接时同时应用 LocalPosition / LocalRotation / LocalScale
#endif需要为单位增加物理行为时,先确定位置的权威来源与绑定模式,参考 UnitMovementController;不要直接在未知用途的表现节点上添加刚体并假定它会接管单位移动。
核心类速查
NodeSystem -- 场景图
Node(场景节点)
场景树的基本单元,可挂载组件、包含子节点。
| 属性 | 类型 | 说明 |
|---|---|---|
IsEnabled | bool | 是否激活 |
Position | Vector3 | 世界坐标位置 |
Rotation | Quaternion | 世界旋转 |
LocalPosition | Vector3 | 局部坐标位置 |
LocalRotation | Quaternion | 局部旋转 |
LocalScale | Vector3 | 局部缩放 |
SceneGraph | SceneGraph | 所属场景图 |
| 方法 | 说明 |
|---|---|
CreateComponent<T>() | 创建并挂载组件 |
AddComponent<T>(T) | 挂载已有组件 |
RemoveComponent<T>() | 移除组件 |
GetComponent<T>() | 获取组件 |
AddChild(Node) | 添加子节点 |
CreateChild() | 创建空子节点 |
SetSyncVar(...) | 服务端写入少量节点级同步变量 |
TryGetSyncVar(...) | 双端读取节点级同步变量 |
HasSyncVar(...) / GetSyncVarType(...) | 查询节点同步变量是否存在及其类型 |
碰撞事件(双端)
| 事件 | 回调参数 | 说明 |
|---|---|---|
OnCollisionBegin | CollisionContact | 3D 碰撞开始,含接触点信息 |
OnCollisionStay | CollisionContact | 3D 碰撞持续(每物理帧) |
OnCollisionEnd | CollisionContact | 3D 碰撞结束 |
OnCollisionBegin2D | CollisionContact2D | 2D 碰撞开始 |
OnCollisionEnd2D | CollisionContact2D | 2D 碰撞结束 |
OnCollisionPreSolve2D | CollisionContact2D, ref bool | 2D 预求解,可禁用碰撞响应 |
| 属性 | 说明 |
|---|---|
AllowDuplicateContactEvents | 设为 true 使碰撞双方都收到事件 |
SceneGraph(场景根)
csharp
var sceneGraph = new SceneGraph(SceneGraphReplication.ReplicatedUnpublished);
var node = sceneGraph.CreateChild("marker", NodeReplication.Replicated);
var shape = node?.CreateComponent<CollisionShape>(ComponentReplication.Replicated);
#if SERVER
sceneGraph.Publish(player);
#endifSceneGraph 是独立 Urho Scene 的脚本包装。服务端可创建 replicated SceneGraph 并按玩家发布;客户端通过 SceneGraph.OnRegistered / OnUnregistered 接收这些发布图。SCE 默认数编场景仍由框架场景系统维护,默认场景同步和脚本创建 SceneGraph 发布是两条不同路径。
如果需要访问 SCE Scene 自带的内置 SceneGraph,不要创建新图,也不要调用 Publish。使用 scene.GetSceneGraph() 获取包装,使用 scene.GetSyncRoot() / scene.AddSyncNode(...) 把节点挂入当前 SCE 场景同步路径。普通联机流程下每个客户端同一时刻只有一个当前 SCE 场景,但可以同时订阅多个额外发布的脚本 SceneGraph。
发布图注册不等于自动渲染。独立 SceneGraph 可以只作为同步数据源使用,例如客户端读取节点状态后绘制到 GameUI Canvas,而不创建 Camera 或接管任何 Viewport。若该图确实要直接显示为 3D 画面,需要客户端配置 Octree、local 表现组件、CameraComponent 和 Viewport;底层入口是 Renderer.SetupMainViewport(sceneGraph, camera)。SceneGraphMainViewportController 只是可选 helper,只用于项目需要反复临时接管主视口时复用保存/恢复和注销切换逻辑,不参与发布图同步或默认消费策略。
客户端 Viewport 与 RenderPath
纯 GameGraph 装配的 3D 场景不要依赖不同调试配置下的默认 RenderPath。主视口、附加 Viewport 和 RenderToTexture Viewport 都应在初始化阶段分别显式设置一次 RenderPath;不要假设新建 Viewport 会自动沿用主视口的渲染管线:
csharp
Renderer.SetupMainViewport(sceneGraph, camera);
var viewport = Renderer.GetMainViewport();
if (viewport is not null)
{
_ = viewport.SetRenderPath("EngineRes/RenderPaths/CEMapSSAO.xml"u8);
}> 雾效差异是多 Viewport 最容易漏掉的问题之一。 当前默认资源配置下,编辑器内嵌调试通常使用 EditorMapSSAOBloomHDR.xml,该路径默认不呈现场景雾效;独立客户端和对战平台客户端通常使用 CEMapSSAO.xml,会呈现场景雾效。附加 Viewport 或 RTT Viewport 如果没有单独调用 SetRenderPath(...),就可能回退到当前宿主的默认路径,造成“编辑器里没有雾,独立客户端里却有雾”的差异。小地图、监控相机等远距离镜头尤其危险,客户端中可能整幅画面都被雾遮住,看不到地面。编辑器内看得见不能作为最终客户端可见性的证据。
附加 Viewport 或 RenderToTexture Viewport 必须按用途独立选择 RenderPath。做画中画或多摄像机画面时,不要硬编码 viewport 编号,优先从 Renderer.GetNextFreeViewportIndex() 取空位;需要诊断当前占用情况时可用 Renderer.GetOccupiedViewportIndices():
csharp
var pipViewport = Renderer.CreateViewport(sceneGraph, pipCamera);
pipViewport.SetRect(16, 16, 336, 256);
_ = pipViewport.SetRenderPath("EngineRes/RenderPaths/GameOverlay.xml"u8);
Renderer.SetViewport(Renderer.GetNextFreeViewportIndex(), pipViewport);GameOverlay.xml 是直接附加到主画面的叠加路径:它只清理深度,不清理颜色,也不执行 Bloom 等全屏后处理。因此主视口应先渲染,附加 viewport 只会覆盖角色实际绘制到的像素。若附加 viewport 需要独立的完整背景,请改用普通 RenderPath;若需要把画面作为 UI 图片或材质使用,请使用下面的 RenderToTexture 流程。
该路径依赖根节点的 useLastRenderTarget="true" 继续使用前一个 Viewport 已写入的颜色目标。若 Viewport、Scene 和 Camera 均设置成功但透明叠加完全不显示,先确认客户端实际加载的是包含该属性的新版本资源。推荐同时放置一个使用 CEMapSSAO.xml 的普通 PIP 作为对照;完整流程、纯客户端验收要求和可读 XML 参考见 GameGraph 透明背景画中画。
Viewport.SetRect(left, top, right, bottom) 使用当前 render target 的像素坐标。普通 backbuffer 下通常等同窗口客户区坐标;编辑器内嵌调试模式下,附加 viewport 会写入编辑器管理的游戏画布 RenderTarget,因此坐标以该游戏画布为基准,不是整个编辑器窗口。
RenderToTexture 的基本流程是:创建 RenderTarget 纹理,取它的 RenderSurface,把独立 viewport 绑定到该 surface,再把纹理作为材质贴图使用。
csharp
var rttScene = new SceneGraph(createOctree: true);
var rttCamera = rttScene.CreateChild("RttCamera")!
.CreateComponent<CameraComponent>()!;
var texture = Texture2D.CreateRenderTarget(
width: 256,
height: 192,
format: RenderTargetFormat.Rgba8);
var surface = texture.RenderSurface!;
var rttViewport = Renderer.CreateViewport(rttScene, rttCamera);
rttViewport.SetRect(0, 0, 0, 0);
_ = rttViewport.SetRenderPath("EngineRes/RenderPaths/CEMapSSAO.xml"u8);
surface.NumViewports = 1;
surface.SetViewport(0, rttViewport);
surface.UpdateMode = RenderSurfaceUpdateMode.UpdateAlways;
surface.QueueUpdate();
var material = new MaterialPBR(useAlbedoTexture: true, useAlphaChannel: false);
material.Textures[TextureUnit.PBRAlbedo] = texture;UpdateMode 决定 surface 何时渲染,QueueUpdate() 只排队下一次渲染;GetImage 的读回时机、8 位转换和颜色空间语义见透明背景画中画。
RenderTarget 纹理也可以直接进入 GameUI。若要给普通 UI 控件、按钮背景、面板图片等使用,可以用控件扩展方法把纹理注册为运行时图片名;建议使用 RT: 前缀,相关 API 会在传入普通名称时自动补齐该前缀:
csharp
var panel = new Panel
{
Width = 256,
Height = 192,
}.Image(texture, "PreviewCamera");也可以显式注册后把 RT: 图片名赋给控件:
csharp
RuntimeTexture.Register("PreviewCamera", texture);
panel.Image = RuntimeTexture.NormalizeName("PreviewCamera");若只想在 Canvas 上绘制,不需要注册图片名,可直接调用 Canvas.DrawTexture(...):
csharp
var canvas = new Canvas
{
Width = 256,
Height = 192,
};
canvas.DrawTexture(texture, 0, 0, 256, 192);运行时图片名会写入当前 GameUI 的图片缓存;不再使用时调用 RuntimeTexture.Unregister("PreviewCamera") 清理。RTT 用于透明 UI 合成时使用 RenderTargetFormat.Rgba8(或需要 HDR 时使用 Rgba16F / Rgba32F),以保留 alpha 通道。旧的 highDynamicRange 重载仍保持兼容,但 false 使用 RGB 格式,不提供可依赖的 alpha;不要把它用于透明画中画。
编辑器内调试有两种常见模式:编辑器内嵌调试和纯客户端调试。主 GameGraph 视口应优先使用 Renderer.SetupMainViewport(...),它会适配编辑器内嵌调试的主视口管理。若用户直接调用 Renderer.SetViewport(...),SDK 会在编辑器内嵌调试模式下发出一次性 warning:该模式的 viewport / render-target 配置、默认 RenderPath 和独立客户端不同,手动 viewport 编号、SetRect 坐标以及雾效表现都不一定代表真实运行结果。需要验证画中画、多 viewport、远距离小地图或 RenderToTexture 的最终效果时,建议在编辑器启动参数中加入 -no_debug_game_in_editor,或使用纯客户端调试模式。这样做不只是为了校验布局,也是为了让画面经过独立客户端实际使用的渲染链路,及时发现 CEMap 雾效等只在客户端默认 RenderPath 下暴露的问题。
引擎内置 RenderPath 都在 EngineRes/RenderPaths/ 下。当前常用项如下:
| 路径 | 典型用途 |
|---|---|
EngineRes/RenderPaths/EditorMapSSAOBloomHDR.xml | 编辑器主视口同款;当前默认不呈现场景雾效,适合明确需要忽略雾的编辑器式预览或技术视图 |
EngineRes/RenderPaths/CEMapSSAO.xml | 游戏运行时常用 Forward 光照 + SSAO;会呈现场景雾效,适合对齐独立客户端和对战平台表现 |
EngineRes/RenderPaths/CEMapDeferred.xml | 游戏运行时 Deferred 光照 + SSAO |
EngineRes/RenderPaths/EditorMapDeferred.xml | 编辑器 Deferred 变体 + SSAO |
EngineRes/RenderPaths/CEMapUnderUISSAO.xml | UI 之下的游戏渲染变体,Forward 光照 + SSAO |
EngineRes/RenderPaths/GameOverlay.xml | 直接附加 viewport 的透明叠加,不清颜色、不执行全屏后处理 |
这些 RenderPath 名称反映的是历史用途,不一定完整描述所有 pass。实际差异来自 Forward / Deferred 光照模型、渲染顺序、雾效和后处理组合。普通游戏镜头需要对齐客户端环境时显式使用 CEMapSSAO.xml;小地图等技术视图若设计上必须忽略雾,可显式使用 EditorMapSSAOBloomHDR.xml;透明叠加则使用 GameOverlay.xml。关键不是统一使用某一个路径,而是每个 Viewport 都明确选择,不能依赖宿主默认值。推荐在游戏初始化时设置一次;运行中频繁切换 RenderPath 可能在 D3D11 debug layer 下产生 framebuffer validation 警告。
SDK 的 resources/rendering-reference/ 保存了部分 RenderPath、KnownTechniquePath Technique、默认 Material 和光照预设的可读 XML 副本。这些文件只用于理解原始结构,不会自动参与游戏运行;项目内实验应复制到项目 res/ 并使用唯一资源路径。
Replicated Node 的 transform 在客户端会通过 Urho 的 SmoothedTransform 向最新网络目标收敛。SceneGraph.TransformSmoothingConstant 控制收敛速度,SceneGraph.TransformSnapThreshold 控制距离目标超过多少时直接贴合目标,ConfigureTransformSmoothing(...) 可一次设置两者;Urho 默认值分别为 50 和 5。这是客户端视觉平滑参数,不是带历史时间戳的插值缓冲,也不会作为网络状态从服务端同步到客户端。需要调整服务端发布图或 SCE 默认场景的视觉平滑时,应在客户端拿到对应 SceneGraph 后设置,例如 SceneGraph.OnRegistered 或 scene.GetSceneGraph() 之后。
Replicated Node 还支持轻量 SyncVar。服务端可用 SetSyncVar(...) 写入 int、bool、float、string、Vector2、Vector3、Quaternion,客户端和服务端都可用 TryGetSyncVar(...) 读取。SyncVar 只适合节点级小型元数据或状态,例如对象类型、阵营、机关阶段、调试标记;不要用它替代 Entity / Unit / Player 属性系统,也不要存放复杂对象或大 payload。SetSyncVar(...) 仅存在于服务端 SDK,客户端需要改变同步状态时应请求服务端处理。完整语义见 服务端 GameGraph SceneGraph 发布与同步 - Node SyncVar。
复制枚举
| 枚举 | 值 | 说明 |
|---|---|---|
SceneGraphReplication | Local | 本地图,不参与发布同步 |
ReplicatedUnpublished | 可发布图,等待服务端 Publish(player) | |
NodeReplication | Inherit | 继承父节点或所属 SceneGraph 的复制语义 |
Local | 本地节点,不同步 | |
Replicated | replicated 节点,参与网络复制 | |
ComponentReplication | Inherit | 继承节点复制语义 |
Local | 本地组件,不同步 | |
Replicated | replicated 组件,参与网络复制 |
发布与客户端事件
| API | 运行端 | 说明 |
|---|---|---|
SceneGraph.Publish(Player) | 服务端 | 将脚本创建的 replicated SceneGraph 发布给指定玩家 |
SceneGraph.Unpublish(Player) | 服务端 | 取消指定玩家订阅;不影响其他玩家 |
SceneGraph.Unpublish() | 服务端 | 取消全部玩家订阅 |
SceneGraph.GetRegisteredSceneGraphs() | 客户端 | 获取当前客户端已注册的服务端发布图 |
SceneGraph.OnRegistered / OnUnregistered | 客户端 | 发布图注册/注销事件 |
SceneGraph.OnNodeReplicated / OnNodeUnreplicated | 客户端 | 按 SceneGraph 订阅节点复制/移除事件 |
IsReplicated | 双端 | SceneGraph / Node / INodeComponent 均可检查 |
Node.SetSyncVar(...) | 服务端 | 写入节点级轻量同步变量 |
Node.TryGetSyncVar(...) | 双端 | 读取节点级轻量同步变量 |
TransformSmoothingConstant / TransformSnapThreshold | 双端 API,通常客户端设置 | replicated transform 的视觉平滑参数 |
完整语义见 服务端 GameGraph SceneGraph 发布与同步。
PlaneClippingBody / PlaneClippingShape
平面裁剪体,用于定义裁剪区域。
| 方法 | 说明 |
|---|---|
SetCircle(...) | 设置圆形裁剪区域 |
SetRect(...) | 设置矩形裁剪区域 |
Component -- 组件基类
| 类型 | 说明 |
|---|---|
INodeComponent | 组件接口。属性: Node, IsEnabled。方法: Setup() |
EngineNodeComponent | 引擎组件抽象基类,继承自 INodeComponent |
组件生命周期:创建 -> Setup() -> 运行 -> 销毁。
Graphics -- 图形组件
LightComponent(光源)
场景光照组件。
| 属性 | 类型 | 说明 |
|---|---|---|
Type | LightType | 光源类型(Directional / Spot / Point) |
Color | Color | 光源颜色 |
Brightness | float | 亮度 |
Range | float | 照射范围(Point / Spot) |
SpecularIntensity | float | 高光强度 |
Fov | float | 聚光灯张角(Spot) |
PerVertex | bool | 逐顶点光照(性能优化) |
Zone(环境区域)
控制环境光、雾效等场景环境参数。
| 属性 | 类型 | 说明 |
|---|---|---|
AmbientColor | Color | 环境光颜色 |
FogColor | Color | 雾颜色 |
DepthFogStart / DepthFogEnd | float | 深度雾起止距离 |
DepthFogEnable | bool | 深度雾开关 |
HeightFogDensity / HeightFogHeight / HeightFogFalloff | float | 高度雾参数 |
HeightFogEnable | bool | 高度雾开关 |
Priority | int | Zone 优先级(重叠区域取高优) |
Override | bool | 强制覆盖子 Zone |
AmbientGradient | bool | 环境光渐变 |
| 方法 | 说明 |
|---|---|
SetBoundingBox(BoundingBox) | 设置 Zone 作用范围 |
SkyboxComponent(天空盒)
天空盒渲染组件,继承自 StaticMeshComponent。使用 CubeMap 纹理实现天空背景。
典型场景搭建示例
csharp
#if CLIENT
// 设置方向光
var lightNode = sceneGraph.CreateChild();
lightNode.Rotation = Quaternion.CreateFromYawPitchRoll(0, -45f * MathF.PI / 180f, 0);
var light = lightNode.CreateComponent<LightComponent>();
light.Type = LightType.Directional;
light.Color = System.Drawing.Color.FromArgb(255, 255, 250, 240);
light.Brightness = 1.2f;
// 配置环境区域
var zone = sceneGraph.GetComponent<Zone>();
zone.AmbientColor = System.Drawing.Color.FromArgb(255, 60, 80, 100);
zone.FogColor = System.Drawing.Color.FromArgb(255, 180, 200, 220);
zone.DepthFogStart = 300f;
zone.DepthFogEnd = 1000f;
zone.DepthFogEnable = true;
zone.SetBoundingBox(new BoundingBox(
new Vector3(-2000, -2000, -2000),
new Vector3(2000, 2000, 2000)));
// 添加天空盒
var skyNode = sceneGraph.CreateChild();
var skybox = skyNode.CreateComponent<SkyboxComponent>();
// 天空盒需要设置 CubeMap 材质(通过 Material API)
#endifAnimatedModelComponent / AnimationController(骨骼动画)
挂带骨架的 .mdl 模型 + 用 AnimationController 控制播放。继承结构上 AnimatedModelComponent 是 StaticMeshComponent 的子类,所以材质、阴影等属性都从父类继承。
csharp
#if CLIENT
var character = sceneGraph.CreateChild("Hero");
var model = character.CreateComponent<AnimatedModelComponent>();
model.Mesh = Mesh.Load("Models/Hero/hero.mdl");
var ctrl = character.CreateComponent<AnimationController>();
ctrl.Play("Animations/Idle.ani", layer: 0, looped: true);
ctrl.PlayExclusive("Animations/Run.ani", layer: 0, looped: true, fadeInTime: 0.2f);
// 取挂点挂武器
var socket = model.GetSocket("socket_weapon_r");
if (socket.HasValue)
{
var sword = socket.Value.BoneNode.CreateChild("Sword");
sword.LocalPosition = socket.Value.LocalPosition;
// ...
}
#endif> 与 Actor 体系并存:Actor 层的 ActorModel.PlayAnimation(...) 和 GameGraph 层的 AnimationController 操作的是同一个底层动画对象。可通过扩展方法 actor.GetAnimationController() / actor.GetAnimatedModel() 从 Actor 下钻;详见 GameGraphSkeletalAnimation.md。 > > 完整 API、AnimationHandle 句柄、层级混合、Bone/Socket 快照、事件轮询、AnimationResource 等详见 GameGraphSkeletalAnimation.md。
PrimitiveShapeComponent(基础形状)
用于渲染简单几何体。
| 属性 | 类型 | 说明 |
|---|---|---|
ShapeType | enum | 形状类型(Cube, Sphere 等) |
ShapeScale | Vector3 | 形状缩放 |
ShapeOffset | Vector3 | 形状偏移 |
ShapeRotation | Quaternion | 形状旋转 |
DebugRenderer(调试绘制)
客户端调试绘制组件。通常挂在 SceneGraph 根节点,并需要对应视口开启 Viewport.DrawDebug = true。普通线段、体积和网格调试几何会在帧末清空;需要持续显示时应每帧重新提交。AddStaticModelNormals(...) 注册的模型法线调试会持续绘制,直到调用 RemoveStaticModelNormals(...) 或 ClearStaticModelNormals()。
| 方法 | 说明 |
|---|---|
AddLine(...), AddTriangle(...), AddPolygon(...) | 绘制基础线段和面 |
AddBoundingBox(...), AddSphere(...), AddCapsule(...) | 绘制常见碰撞/范围体 |
AddCircle(...), AddArc(...), AddEllipse(...) | 绘制圆、弧线、椭圆 |
AddCylinder(...), AddVerticalCylinder(...), AddCone(...), AddConeAndHemisphere(...) | 绘制柱体、圆锥、视野/扇形辅助体 |
AddFrustum(...) | 绘制相机视锥或裁剪体 |
AddTriangleMesh(...) | 以三角边线绘制任意网格顶点/索引 |
AddPolyhedron(...) | 绘制多面体 faces,用于凸包、裁剪体、遮挡区域调试 |
AddSkeleton(AnimatedModelComponent, ...) | 绘制骨骼模型当前骨架 |
AddStaticModelNormals(...), RemoveStaticModelNormals(...), ClearStaticModelNormals() | 持续绘制/清理静态模型法线 |
csharp
#if CLIENT
var debug = sceneGraph.GetComponent<DebugRenderer>()
?? sceneGraph.CreateComponent<DebugRenderer>();
Renderer.GetMainViewport()!.DrawDebug = true;
debug.AddLine(Vector3.Zero, new Vector3(300, 0, 0), System.Drawing.Color.Red);
debug.AddSphere(new Vector3(0, 0, 100), 100f, System.Drawing.Color.Yellow);
debug.AddSkeleton(animatedModel, System.Drawing.Color.Cyan);
#endif几何类型
| 类 | 说明 |
|---|---|
Frustum | 视锥体 |
Ray | 射线 |
Physics -- 3D 物理
PhysicsWorld(物理世界)
| 方法 | 说明 |
|---|---|
Raycast(...) | 射线检测(首个命中) |
RaycastAll(...) | 射线检测(全部命中) |
SphereCast(...) | 球体投射检测 |
OverlapSphere(...) | 球体重叠检测 |
OverlapBox(...) | 盒体重叠检测 |
RigidBody(刚体)
| 属性 | 类型 | 说明 |
|---|---|---|
Mass | float | 质量(0 = 静态) |
LinearVelocity | Vector3 | 线速度 |
AngularVelocity | Vector3 | 角速度 |
Friction | float | 摩擦系数 |
Restitution | float | 弹性系数 |
UseGravity | bool | 是否受重力 |
Kinematic | bool | 是否为运动学刚体 |
IsTrigger | bool | 是否为触发器 |
CollisionLayer | uint | 碰撞层 |
CollisionMask | uint | 碰撞掩码 |
| 方法 | 说明 |
|---|---|
ApplyForce(Vector3) | 施加持续力 |
ApplyImpulse(Vector3) | 施加瞬间冲量 |
ApplyTorque(Vector3) | 施加扭矩 |
CollisionShape(碰撞形状)
ShapeType 枚举值:
| 值 | 说明 |
|---|---|
Box | 盒体 |
Sphere | 球体 |
StaticPlane | 静态平面 |
Cylinder | 圆柱体 |
Capsule | 胶囊体 |
Cone | 圆锥体 |
TriangleMesh | 三角面网格(仅静态) |
ConvexHull | 凸包 |
Terrain | 地形碰撞 |
其他枚举
| 枚举 | 值 |
|---|---|
CollisionEventMode | Never, Active, Always |
ConstraintType | Point, Hinge, Slider, ConeTwist |
Physics2D -- 2D 物理(双端)
> 运行端:Physics2D 命名空间在客户端和服务端均可用。服务端可用于 Box2D 权威模拟、碰撞判定和查询;需要让客户端看到结果时,仍要通过 replicated Node/SceneGraph 或业务消息同步,并在客户端做 Canvas/GameUI/渲染表现。
PhysicsWorld2D(2D 物理世界)
| 属性/方法 | 说明 |
|---|---|
Gravity | 2D 重力设置 |
RaycastAll(...) | 2D 射线检测(全部) |
RaycastSingle(...) | 2D 射线检测(首个) |
GetRigidBody(...) | 获取指定位置的 RigidBody2D |
QueryAABB(...) | AABB 区域查询 |
RigidBody2D(2D 刚体)
| 属性 | 说明 |
|---|---|
BodyType2D | Static / Kinematic / Dynamic |
Mass | 质量 |
LinearVelocity | 线速度 |
AngularVelocity | 角速度 |
2D 碰撞形状
| 类 | 说明 |
|---|---|
CollisionBox2D | 矩形碰撞 |
CollisionCircle2D | 圆形碰撞 |
CollisionEdge2D | 边线碰撞 |
CollisionChain2D | 链线碰撞 |
CollisionPolygon2D | 多边形碰撞 |
2D 约束(关节)
| 类 | 说明 |
|---|---|
ConstraintDistance2D | 距离约束 |
ConstraintFriction2D | 摩擦约束 |
ConstraintGear2D | 齿轮约束 |
ConstraintMotor2D | 马达约束 |
ConstraintMouse2D | 鼠标约束 |
ConstraintPrismatic2D | 棱柱(滑动)约束 |
ConstraintPulley2D | 滑轮约束 |
ConstraintRevolute2D | 旋转约束 |
ConstraintRope2D | 绳索约束 |
ConstraintWeld2D | 焊接约束 |
ConstraintWheel2D | 车轮约束 |
Scripts -- 脚本组件
ScriptComponent(脚本组件)
挂载到 Node 上的自定义逻辑组件,提供完整的生命周期回调。
| 回调方法 | 调用时机 |
|---|---|
OnStart() | 组件首次启用时 |
OnDelayedStart() | 延迟启动(所有组件 Start 完成后) |
OnStop() | 组件停用时 |
OnUpdate(float dt) | 每帧更新 |
OnPostUpdate(float dt) | 每帧后更新 |
OnFixedUpdate(float dt) | 固定时间步更新(物理同步) |
OnFixedPostUpdate(float dt) | 固定时间步后更新 |
OnTriggerEnter(Node other) | 触发器进入(仅传对方 Node) |
OnTriggerStay(Node other) | 触发器持续接触(仅传对方 Node) |
OnTriggerExit(Node other) | 触发器离开(仅传对方 Node) |
> 提示:需要完整的碰撞接触信息(接触点、法线、冲量等)时,优先使用 Node 级别的碰撞事件(OnCollisionBegin / OnCollisionBegin2D 等),参见 物理系统 文档。
csharp
public class MyScript : ScriptComponent
{
public override void OnStart()
{
var body = Node.CreateComponent<RigidBody>();
body.Mass = 5f;
}
public override void OnFixedUpdate(float dt)
{
// 物理相关逻辑在 FixedUpdate 中处理
}
public override void OnTriggerEnter(Node other)
{
// 简单碰撞触发(仅知道对方 Node)
}
}ResourceSystem -- 资源系统
资源 API 的端侧边界比类型名称更重要。服务端可见的 Resource、Mesh、Texture2D 等基础类型主要用于共享句柄、路径或少量场景语义;像素/网格内容读取、GPU buffer、RenderTarget、Technique Pass、Prefab 实例化、粒子、音频和动画资源等详细操作主要仅客户端开放。
| 类 | 说明 | 关键 API |
|---|---|---|
Resource | 资源基类 | -- |
ResourceCache | 资源缓存与加载 | GetResource<T>(string path) |
Mesh | 网格资源 | Load(Model) |
MeshBuilder | 流式构建自定义 Mesh(仅客户端) | SetPositions(), SetIndices(), SetNormals(), SetTexCoords(), SetColors(), SetTangents(), Build() |
Prefab | 预制体 | CreateInstance(), CreatePhysicsInstance(), Load(Prefab) |
Technique | Technique XML 渲染方案 | Find(path), PropertyToID(name), Path |
Shader | 旧兼容名称,实际表示 Technique XML;新代码不推荐使用 | Find(path), PropertyToID(name) |
Texture2D | 2D 纹理(加载发布资源或创建客户端运行时纹理) | Load(texturePath), CreateFromImage(image), CreateRenderTarget(...) |
ResourceCache 常用资源类型名(客户端)
通过资源缓存加载的 Load(...) 入口(例如 Mesh.Load、Texture2D.Load、ParticleSystem.Load)按“资源类型 + 逻辑路径”复用同一份资源对象;同一路径重复加载不会创建独立副本。资源定义共享,运行时组件或实例状态仍由各自入口创建;例如同一 ParticleSystem 可以挂到多个节点,每个节点上的发射器独立。需要独立可变对象时,如果该类型提供运行时构造入口(如 Texture2D.CreateFromImage、ParticleSystem.CreateRuntime),使用该入口,不要修改共享资源来区分实例。
ResourceCache.ReleaseResource(type, name)、GetExistingResource(type, name) 等接口中的 type 是引擎资源类型名,name 是加载资源时使用的逻辑路径,不是磁盘绝对路径;类型名也不一定等于 GameGraph 的 C# 类名。下面列的是已确认的常用类型,不是所有资源类型的白名单。新增资源类型或其他引擎资源应以对应加载 API 实际使用的类型名为准。
GameUI 控件图片不使用这张表的类型名刷新。 控件图片和 Canvas.DrawImage 的普通散图走当前 GameUI 视图的图片缓存;开发中同路径替换后,使用 GameUI.ImageCache.Invalidate("image/abc.png"),而不是 ResourceCache.ReleaseResource("Image", ...) 或 ReleaseResource("Texture2D", ...)。这里的 Image 指 GameGraph 的 CPU 图片资源,Texture2D 指引擎纹理(例如 Spine atlas 页),均不等同于 GameUI 控件图片缓存。详见项目资源路径指南。
type 参数 | 对应的常用资源 / 加载入口 | 注意 |
|---|---|---|
AnimationSet2D | AnimationSet2D.Load(...),Spriter .scml / Spine .skel、.json | Spine 的 .atlas 由动画集读取,不是一个单独的缓存类型名 |
Texture2D | Texture2D.Load(...),Spine atlas 引用的页贴图 | 缓存接口会同时处理普通 Texture2D 和 TextureX 容器纹理 |
Model | Mesh.Load(...)、ResourceCache.GetResource<Mesh>(...) | C# 类型叫 Mesh,缓存类型名是 Model |
Material | Material.Load(...) 等材质资源加载 | 指资源文件,不是运行时临时构造的材质 |
Animation | AnimationResource.Load(...),3D 骨骼动画资源 | 不要与 AnimationSet2D 混用 |
Sound | SoundResource.Load(...),音频资源 | C# 类型叫 SoundResource,缓存类型名是 Sound |
Image | Image.Load(...),CPU 图片资源 | 不等同于 GPU Texture2D |
Sprite2D / SpriteSheet2D | Sprite2D.Load(...) / SpriteSheet2D.Load(...) | 与 AnimationSet2D 分别缓存 |
例如开发中覆盖了同路径的 Spine 文件,可在新一局客户端调试创建 AnimatedSprite2D 之前,按实际资源路径定点释放旧动画集及 atlas 中引用的每张页贴图,然后重新加载:
csharp
ResourceCache.ReleaseResource("AnimationSet2D", "Animated/hero.skel");
ResourceCache.ReleaseResource("Texture2D", "Animated/hero.png"); // 多页 atlas 逐页处理
var animationSet = AnimationSet2D.Load("Animated/hero.skel");示例使用旧项目 res/Animated/... 资源:代码中的 name 从 res/ 下一级开始写。新项目放在 ui/spine/... 时,使用 spine/... 逻辑路径,详见项目资源路径指南。默认 force: false 不会移除仍被组件或其他对象引用的资源;不要在 Spine 正在播放时强制释放或原地重载动画集。此操作只刷新当前客户端的缓存,不负责将导出文件同步到项目资源目录。
csharp
// 加载纹理
Texture2D tex = Texture2D.Load("Textures/MyTexture.png");
// 加载预制体并实例化
Prefab prefab = Prefab.Load(somePrefabData);
Node instance = prefab.CreateInstance();
// 带物理的实例化(自动创建 RigidBody + CollisionShape)
Node physInstance = prefab.CreatePhysicsInstance();
// 查找 Technique XML
Technique pbrTechnique = Technique.Find("PBR_PackedNormal/DefaultMetallicRoughness");
uint propId = Technique.PropertyToID("TintColor");运行时生成的 CPU Image 可通过 Texture2D.CreateFromImage(...) 上传;发布资源仍使用 Load(...),由 TextureX 容器链路选择当前客户端的平台纹理产物;RenderToTexture 使用 CreateRenderTarget(...)。三条路径不互相替代,色彩空间、mip、采样状态和释放顺序见运行时程序化纹理指南。
GameGraph、Actor、粒子、音频、模型等非 UI 引擎资源通常使用资源库或官方资源路径。自定义资源如果已经通过资源库上传到云端,直接使用资源库路径;只有明确需要随项目离线发布、暂时不走云端下载的资源,才放到项目 res/ 目录,并在代码中从 res/ 下一级开始写路径。例如 res/effect/custom/fire/particle.effect 对应 ParticleSystem.Load("effect/custom/fire/particle.effect")。更多路径规则见 项目资源路径指南。
MaterialSystem -- 材质系统
材质是仅客户端表现能力。常规模型继续使用 MaterialPBR;无贴图纯色程序几何使用 MaterialFlatLambert;需要贴图与顶点 RGB/Alpha 相乘时使用 MaterialVertexColor。后者提供四个固定模式:LitOpaque、UnlitOpaque、LitAlphaCutout、UnlitAlphaBlend,分别表达受光/无光与不透明/裁剪/混合语义,不需要项目在运行时修改 pass defines。
csharp
var texture = Texture2D.Load("texture/voxel/stone.tga")
?? throw new InvalidOperationException("Voxel texture not found.");
var material = new MaterialVertexColor(
texture,
VertexColorMaterialMode.UnlitOpaque);MeshBuilder.SetColors(...) 和 CustomGeometryComponent.DefineColor(...) 都能提供 Color 顶点流。VoxelMaterialSample 用上下两排对照同一组材质模式,检查 StaticMeshComponent 与 CustomGeometryComponent 的输出是否一致;四平台 shadercache 与真实 Editor 画质矩阵已覆盖这些固定模式。
Terrain
地形渲染组件。
已知枚举常量
| 枚举 | 示例值 | 说明 |
|---|---|---|
KnownColorProperty | PBRColorFactor, PBREmissiveColor, FlatLambertColor, VertexColorColorFactor | 预定义颜色属性名 |
KnownFloatProperty | PBRMetallicFactor, PBRRoughnessFactor | 预定义浮点属性名 |
KnownTechnique | PBR、Lambert、Water 与四种 TexturedVertexColor* 变体 | 预定义 Technique 标识 |
KnownTechniquePath | -- | 预定义 Technique XML 路径 |
> 材质系统的详细用法参见 MaterialSystem.md。
组件继承层次
GameGraph 组件遵循以下继承结构,理解它有助于判断 API 可用范围:
EngineNodeComponent (组件基类)
├── Drawable (可渲染组件基类)
│ ├── LightComponent — 光源
│ ├── Zone — 环境区域(环境光、雾效)
│ ├── StaticMeshComponent — 静态模型
│ │ ├── SkyboxComponent — 天空盒
│ │ └── AnimatedModelComponent — 骨骼动画模型
│ └── PrimitiveShapeComponent — 基础几何体
├── DebugRenderer — 调试绘制组件(仅客户端)
├── ParticleEmitter — 粒子发射器(仅客户端)
├── SoundSourceComponent — 2D 音源(仅客户端)
│ └── SoundSource3DComponent — 3D 空间音源
├── SoundListenerComponent — 声音监听器(仅客户端)
├── AnimationController — 动画控制器(仅客户端)
├── RigidBody — 3D 刚体
├── CollisionShape — 3D 碰撞形状
├── PhysicsWorld — 3D 物理世界
├── ScriptComponent — 脚本生命周期组件
└── (Physics2D 组件,双端)
├── RigidBody2D
├── PhysicsWorld2D
├── CollisionBox2D / CollisionCircle2D / ...
└── ConstraintDistance2D / ConstraintRevolute2D / ...
EngineObject (引擎对象基类,非 Component)
└── AnimationState — 单条动画运行时状态(仅客户端)
Resource (资源基类)
├── Mesh — 网格模型
├── Texture2D — 2D 纹理
├── Shader — 着色器
├── Prefab — 预制体
├── ParticleSystem — 粒子系统(.effect 文件)
├── SoundResource — 音频资源(.ogg/.wav)
└── AnimationResource — 骨骼动画资源(.ani)
readonly record struct(按值传递的 POD 数据)
├── AnimationHandle — 动画句柄(uint nameHash 包装)
├── Socket — 挂点信息(BoneNode + 局部 transform)
└── Bone — 骨骼只读快照(initial pose + parent + node + animated)
AudioManager (静态类) — 全局音频控制(主音量、监听器)> Drawable 的子类共享 GetZone() 等方法。StaticMeshComponent 提供 protected 构造函数供 SkyboxComponent 等子类指定不同的引擎对象类型。
游戏结束与资源清理
游戏实例结束时(编辑器停止调试、客户端切换游戏等),不同层级的资源清理规则不同:
自动清理(无需手动处理)
| 资源类型 | 清理方 | 说明 |
|---|---|---|
框架场景系统(Scene) | 框架场景生命周期 | 编辑器场景、单位、Actor 及其引擎资源随游戏实例销毁 |
| UI 控件 | 引擎 UI 层 | AddToVisualTree 的元素随游戏实例清除 |
| C# 事件、Trigger | WASM 沙箱 | 纯托管对象,沙箱销毁时一并回收 |
| IThinker 注册 | WASM 沙箱 | 框架侧随沙箱清除 |
需要手动清理:通过 GameGraph API 直接创建的引擎对象
通过 new SceneGraph() 或 new Node() 等 GameGraph API 直接创建的对象持有底层引擎资源。这些对象不受框架场景系统管理,WASM 沙箱销毁前不会被自动清理。
如果不手动释放,会导致原生内存泄漏——在编辑器反复调试或客户端切换游戏时尤为严重。
服务端发布的 replicated SceneGraph 也遵循同样规则。流程结束时应调用 Unpublish(...) 或直接 Dispose();Dispose() 会销毁图内节点/组件并清理发布关系。
标准做法:继承 DisposableObject,在 Game.OnGameEnd 时调用 Destroy()。
完整游戏类模板(3D SceneGraph 游戏)
csharp
#if CLIENT
using GameCore;
using GameCore.BaseType;
using GameCore.Interface;
using GameGraph.NodeSystem;
using GameGraph.NodeSystem.Component.Graphics;
using EngineInterface.Urho3DInterface.Graphics;
public class MyGame : DisposableObject, IGameClass, IThinker
{
private SceneGraph? sceneGraph;
private Node? cameraNode;
private CameraComponent? cameraComponent;
// DisposableObject 提供 IsDisposed,游戏结束后自动停止 Think
public bool DoesThink => !IsDisposed;
public static void OnRegisterGameClass()
{
Game.OnGameTriggerInitialization += RegisterAll;
}
private static void RegisterAll()
{
if (Game.GameModeLink != ScopeData.GameMode.MyMode) return;
MyGame? game = null;
Game.OnGameStart += () =>
{
game = new MyGame();
game.Initialize();
};
Game.OnGameEnd += () =>
{
game?.Destroy(); // 调用 DisposableObject.Destroy() → DisposeManaged()
game = null;
};
}
private void Initialize()
{
// 1. 创建 SceneGraph(createOctree: true 启用渲染索引)
sceneGraph = new SceneGraph(createOctree: true);
// 2. 创建 Camera + Viewport
cameraNode = sceneGraph.CreateChild("Camera");
cameraComponent = cameraNode?.CreateComponent<CameraComponent>();
if (cameraComponent != null)
{
cameraComponent.Fov = 60f;
cameraComponent.NearClip = 1f;
cameraComponent.FarClip = 100000f;
cameraComponent.AutoAspectRatio = true;
Renderer.SetupMainViewport(sceneGraph, cameraComponent);
}
// 3. 加载光照预设(Zone + 方向光 + IBL + 阴影,一行搞定)
var lightNode = sceneGraph.CreateChild("LightGroup");
lightNode?.LoadXML("LightGroup/Daytime.xml");
// 4. 创建游戏内容 ...
Game.RegisterThinker(this);
}
public void Think(int deltaMs) { /* 游戏逻辑 */ }
protected override void DisposeManaged()
{
// Dispose SceneGraph 级联销毁所有子 Node 及其 Component,无需逐个销毁
sceneGraph?.Dispose();
}
}
#endif清理规则
- SceneGraph:
Dispose()后原生 Scene 引用计数归零,级联销毁所有子节点和组件 - 子节点:挂在 SceneGraph 下的 Node 无需单独销毁,Scene 销毁时自动级联
- 无主 Node:未挂到任何 SceneGraph 的独立 Node 需要单独
Dispose() - DisposableObject:框架提供的标准 Dispose 模式基类,自带防重入和 finalizer 兜底
- 不需要清理的:UI 控件、C# 事件订阅、Trigger 注册、IThinker 注册(随 WASM 沙箱自动回收)
2D 物理游戏(Canvas + SceneGraph)
用 Canvas 绘制但背后使用 SceneGraph + PhysicsWorld2D 的游戏,同样需要在 DisposeManaged() 中释放 SceneGraph,即使它不直接渲染。
纯 Canvas 游戏(不使用 SceneGraph/Node)无需手动清理。
框架约束备忘
| 约束 | 说明 |
|---|---|
| 坐标系 | 左手坐标系,Z 轴向上,XY 为地面 |
| Entity | 服务端创建的游戏实体 |
| Actor | 客户端创建的本地对象 |
| Physics2D | 双端可用 |
| Node-Unit 映射 | 通过 unit.GetNode() 扩展方法获取 |
| 物理逻辑位置 | 应在 OnFixedUpdate 中处理 |