Skip to content

客户端二进制兼容与能力版本 ​

WasiCore 依赖库可以独立于客户端发布,但客户端原生运行时以及随客户端二进制发布的接口程序集会共同组成一条客户端契约。依赖库新增客户端事件或 API 时,需要先确认当前客户端是否具备对应契约能力,才能在旧客户端上保持可加载、可启动。

这篇指南说明如何查询客户端能力版本、如何判断某项能力是否可用,以及每次客户端能力扩展后“旧客户端”的边界如何变化。

为什么会出现新版依赖库配旧客户端 ​

客户端通常会随着平台版本持续更新,但多数客户端更新不是强制更新。玩家可以暂时不主动更新客户端,而项目使用的 WasiCore / WasiSparkCore 依赖库可能已经通过后续发布更新,因此运行时可能出现“依赖库版本高于客户端二进制版本”的组合。

如果新版依赖库直接调用旧客户端二进制中不存在的事件、方法或底层能力,旧客户端可能无法注册事件,甚至在启动阶段加载失败。客户端能力版本就是用来描述这条兼容边界的:依赖库先确认客户端具备某项能力,再进入引用新客户端成员的代码路径;不具备时保持旧行为或使用降级方案。

因此,能力版本不是要求所有玩家立刻升级的产品版本号,而是允许依赖库在客户端可选更新期间同时服务新旧客户端的兼容契约。

三种版本信息 ​

在客户端代码中使用 ClientEnvironment.Runtime:

csharp
#if CLIENT
using GameCore.ClientEnvironment;

var runtime = ClientEnvironment.Runtime;
long binaryVersion = runtime.BinaryVersion;
int capabilityVersion = runtime.CapabilityVersion;
bool supportsHostDisconnected = runtime.Supports(
    ClientRuntimeFeature.HostDisconnectedEvent);
#endif
信息用途不能做什么
BinaryVersion精确客户端构建号,用于日志、问题上报和诊断展示不要用构建号大小直接推断某项 API 是否存在;不同平台或特殊构建可能使用不同编号规则
CapabilityVersion单调递增的客户端二进制契约版本,用于展示当前客户端处在哪条能力线不要把它当成服务端玩法数据,也不要在业务代码中维护一套替代 Supports(...) 的数字比较
Runtime.Supports(feature)按功能判断当前客户端是否支持某项可选能力不要在每帧或每条消息中重复查询;框架会缓存版本值

这些信息只描述当前玩家的本地客户端,不同步到服务端,也不应作为玩法权威输入。未知或旧客户端的 CapabilityVersion 为 0,遇到不认识的能力时应按不支持处理。

最新能力版本与强更保证基线 ​

截至 2026-11-02,当前开发版本的客户端能力版本为 10;本期从 9 提升至 10,目标平台是否已发布该能力仍需按实际客户端确认:

最低能力版本能力代码入口范围
1HostDisconnectedEventGame.OnServerDisconnected、EventServerDisconnected仅客户端
2UIAuthoringRuntimeInitializeUI 编辑器创作模式运行时初始化仅客户端
3PaymentBalanceInfoPaymentClient.GetBalanceInfo()仅客户端
3SimpleSkinningAnimatedModelComponent.SimpleSkinning仅客户端
4ScreenViewportSurfaceResizeEventsScreenViewport.OnViewportChanged 的同尺寸 Surface 重建通知仅客户端
5RuntimeTerrainTextureEditingGameDataScene.EnableRuntimeTerrainTextureEditing 与离散瓦片地表运行时权重编辑仅客户端
5RelativeMouseCaptureGamePointerCapture:等待点击捕获、相对移动和状态变化事件仅客户端,桌面鼠标;请求仍需检查宿主接受结果
6ClientLogUploadClientEnvironment.Diagnostics.TryUploadLogs():游戏内请求上传本地客户端日志仅客户端;返回值只表示请求已被接收
7TmxFile2DModernLoadingTmxFile2D.Load(...):读取 TMX 1.x 普通有限地图,包括 1.11,以及 Base64 zlib/gzip/zstd 图层数据和 TSX 相对图片路径仅客户端;格式读取能力发生变化,API 签名不变;边界见 TMX 地图指南
8ApplicationLifecycleEventsGame.OnApplicationWillEnterBackground、OnApplicationDidEnterBackground、OnApplicationWillEnterForeground、OnApplicationDidEnterForeground、OnApplicationLowMemory,以及对应的 EventApplication...仅客户端;移动端应用生命周期和低内存通知
9NativeClientServicesClientPlatform:显示模式、窗口、Surface、帧调度、设备诊断、手柄、扫码及选项事件仅客户端;请求结果仍受平台和宿主限制
9GameRenderSettingsGameGraph.Rendering.GameRenderSettings:当前游戏实时渲染细项与快照恢复仅客户端;需存在当前游戏私有设置
10ParticleDeterminismParticleSystem.AddToNode(node, randomSeed)、ParticleSystem.SetRandomSeed/SetDeterministicMode/Advance/Replay、ParticleEmitter.RandomSeed/DeterministicMode/Advance/Replay仅客户端;时间参数为播放秒并乘当前 TickSpeed,单次推进最多 100000 步

因此,针对这项能力可以这样理解:

  • CapabilityVersion == 0:旧客户端或无法报告能力的客户端,不会注册依赖可选客户端能力的增强路径。
  • CapabilityVersion >= 1:客户端具备 HostDisconnectedEvent,框架会在初始化时完成事件订阅。
  • CapabilityVersion >= 4:ScreenViewport.OnViewportChanged 对同尺寸 Surface 重建提供通知保证;实际尺寸变化在更早的兼容路径中仍可处理。
  • 更高版本仍应继续支持版本 1 的能力;新增能力会在能力表中声明自己的最低版本。

这里的“旧客户端”是相对于具体能力而言的,而不是一个永久固定的产品版本名称。未来能力版本提升后,旧客户端边界也会随最低能力版本表一起变化。

粒子确定性回放的旧客户端边界 ​

ParticleDeterminism 的最低能力版本为 10。项目只应在 ClientEnvironment.Runtime.Supports(ClientRuntimeFeature.ParticleDeterminism) 为真时调用新增的种子、Advance 和 Replay API;ParticleSystem.Replay 是 static 节点级入口,不能通过 ParticleSystem 实例调用。

能力版本由随客户端发布的 Common/接口程序集报告,不能单独证明原生 import 注册和 dotnet.wasm 的 PInvoke table 一致。旧客户端同时使用旧 Common、旧 dotnet.wasm 和旧 native 时,新增 import 不会注册;按当前解释型运行时,既有粒子 API 仍可加载,新增 API 调用才会缺成员。若把新 Common/dotnet.wasm 与旧 native 混装,Windows 客户端仅对未知 import 走 Windows 条件下的 trap 路径,调用新 API 时失败;Android/iOS 没有同样的 unknown-import 兜底,存在模块实例化失败风险。发布时应让 native、dotnet.wasm、Common/接口程序集和 GameGraph 依赖保持同一客户端契约,并用真实旧客户端矩阵验证,不能只凭能力版本 10 判断。

复现时固定 TickSpeed、fixedStepSeconds 和步数序列;Advance/Replay 最多 100000 步,默认 0.05 秒。Seek 仍是增量推进,Replay 才从初始状态重放。增删或重排发射器会改变节点级种子派生顺序,需要重新设置 seed。

上述旧客户端加载行为来自源码静态核对,尚未完成真实旧客户端 WASI 兼容矩阵验证;不能将其视为已通过目标平台验收。

完整 API 示例和限制见GameGraph 粒子与音频。

还需要区分两个发布概念:

概念含义
最新客户端能力版本最新客户端二进制能够提供的最高能力契约;处于可选更新阶段时,并不代表所有玩家都已经具备这些能力
强更保证基线平台强制用户更新并完成生效后,目标平台和发布环境中可以保证存在的最低能力版本

平台会不定期强制更新客户端。假设一次强更完成后,保证所有可进入目标环境的客户端都满足 CapabilityVersion >= N,则最低能力版本不高于 N 的功能可以视为必定存在,项目代码不必再为这些功能增加能力判断;最低能力版本高于 N 的新功能仍需使用 Runtime.Supports(...) 或由框架完成兼容门禁。

强更保证只在平台明确完成强制更新后成立,而且应按平台、渠道和环境分别确认。强更仍在灰度过程中,或本地调试、离线安装包、特殊测试客户端不受该强更约束时,不能直接套用线上保证基线。

使用场景是否需要项目侧能力判断
客户端更新仍为可选更新,功能最低版本高于已确认的强更基线需要,优先使用 Runtime.Supports(feature)
平台已完成强更,功能最低版本不高于已确认的强更基线不需要,可以把该功能视为目标环境必定存在
框架公开 API 已自行完成门禁,例如 Game.OnServerDisconnected项目直接使用,由框架兼容旧客户端
本地、特殊测试或无法确认是否受强更约束的客户端继续判断,按不保证处理

断线事件的使用方式 ​

普通新增 API 由项目在调用前检查所需能力,例如:

csharp
#if CLIENT
using GameCore.ClientEnvironment;
using GameCore.ClientPlatform;

if (ClientEnvironment.Runtime.Supports(ClientRuntimeFeature.NativeClientServices))
{
    var surface = ClientPlatform.GetSurface();
}
#endif

初始化时主动注册内部事件由框架完成兼容判断。新的普通调用仍可能引用旧客户端不存在的接口,必须在能力分支内进入新调用路径。新增平台服务使用独立入口,原有环境 API 不会在初始化时解析其新 Provider。

项目通常不需要自己读取版本后再订阅 Game.OnServerDisconnected,框架已经完成兼容门禁:

csharp
#if CLIENT
using GameCore.GameSystem;

Game.OnServerDisconnected += HandleServerDisconnected;

static void HandleServerDisconnected()
{
    // 清理客户端临时状态,或显示重新连接提示。
}
#endif

事件只表示客户端已经建立过 Host 连接,之后进入断开状态;它不表示尚未建立成功的连接尝试。旧客户端不会触发该事件,也不会因为加载新版 WasiCore 依赖库而在启动阶段失败。

需要使用触发器事件体系时,可以订阅 EventServerDisconnected。它与 Game.OnServerDisconnected 表示同一次客户端断线,不应重复执行同一份清理逻辑。

发布与维护规则 ​

客户端能力版本只在客户端二进制契约新增或改变依赖库可见能力时递增,例如新增可选事件、原生注册能力、WASM export、随客户端发布程序集中的成员,或现有 API 新增可读取的资源格式。即使 API 签名不变,读取新版 TMX 与压缩图层也属于新能力。纯 WasiCore 逻辑或文档变化不提升该版本。

每个发布周期的 changelog 顶部应明确写出:

  • 当前最新客户端能力版本。
  • 本周期是否提升能力版本;如果提升,写明从哪个版本提升到哪个版本。
  • 新增能力及其最低能力版本。
  • 当前已确认的强更保证基线,以及本周期是否通过强更提高该基线。
  • 强更后新成为“必定存在”的能力;没有明确强更信息时,不应自行假设所有玩家已经更新。

这样项目开发者可以在更新日志和运行时诊断中直接判断“旧客户端”的实际边界,而不必猜测某个产品构建号是否包含某项 API。

服务端不使用这套客户端能力门禁。服务端依赖库的发布规则保证其版本不会高于服务端二进制;本指南的兼容矩阵只针对客户端依赖库与客户端二进制之间的错峰更新。

相关文档 ​