主题
客户端窗口、Surface 与原生服务
GameCore.ClientPlatform.ClientPlatform 提供显示模式、窗口、移动端渲染 Surface、帧调度、设备诊断、手柄、扫码和本地选项。当前游戏的实时渲染细项使用 GameGraph.Rendering.GameRenderSettings。这些新增原生接口要求客户端能力版本 9。
所有操作属于本地客户端。窗口、Surface、帧调度、字体和部分渲染开关影响进程;游戏渲染设置和选项带有自己的作用域。设备和系统仍可能拒绝请求。
能力检查与请求结果
在初始化或打开设置界面时判断一次,再进入引用新接口的代码路径:
csharp
#if CLIENT
using GameCore.ClientEnvironment;
using GameCore.ClientPlatform;
if (ClientEnvironment.Runtime.Supports(ClientRuntimeFeature.NativeClientServices))
{
var surface = ClientPlatform.GetSurface();
// 在这里使用需要能力 9 的平台服务。
}
#endifClientOperationResult 的 Applied 表示已应用,Accepted 表示已受理;Unsupported、BlockedByHost、InvalidArgument、Unavailable、Failed、Busy 分别表示平台不支持、宿主控制、参数错误、服务不可用、请求失败和服务繁忙。异步尺寸变化需在视口变化通知后重新读取实际值。
旧客户端使用原有 ClientEnvironment、ClientSettings 和 ScreenViewport 入口;调用这些新增 API 前由项目判断能力。普通新 API 不会自行把缺失接口变成空操作。
分清几种尺寸
| 查询 | 含义 | 用法 |
|---|---|---|
ClientPlatform.GetDisplays() | 系统显示器、桌面模式、工作区及支持的模式列表,含刷新率 | 桌面分辨率选择 |
GetSurface().DefaultSize | 系统当前应用窗口按默认布局和 backing scale 得到的像素尺寸 | 恢复默认渲染清晰度;不承诺等于面板物理像素 |
GetSurface().RenderSize | 当前引擎渲染 Surface 像素 | 判断降分辨率设置是否生效 |
GetSurface().WindowSize | SDL 窗口坐标尺寸,可能与高 DPI 渲染像素不同 | 判断窗口与渲染比例 |
ScreenViewport 长宽 | 当前游戏视口的像素尺寸 | 当前游戏的布局和输入;PIE 中通常小于整个宿主窗口 |
| UI 设计尺寸 | 项目布局坐标与缩放基准 | 控件布局,不改变渲染 Surface |
移动端显示模式列表不代表可切换的 Surface 档位。分屏、旋转、系统显示缩放和宿主嵌入方式都会影响默认应用尺寸。
屏幕密度与物理尺寸估算
以下查询要求客户端能力 9,均为按需读取的本地快照。平台专属方法在其它平台返回 null。
| 查询 | 数据与限制 |
|---|---|
GetDisplayDpi(display) | SDL 显示器横向、纵向 DPI;不支持、索引无效或查询失败返回 null。Windows 可能返回缩放 DPI,Android 依赖厂商,iOS 来自型号表或比例估算 |
GetAndroidScreenMetrics() | 仅 Android:系统显示器像素、Xdpi/Ydpi、逻辑 Density/DensityDpi/ScaledDensity、当前窗口 WindowSizeDp 与 SmallestScreenWidthDp |
GetIOSScreenMetrics() | 仅 iOS:NativePixelSize、整屏 ScreenSizePoints、应用视图 ViewSizePoints、Scale/NativeScale 及 SDL 估算 DPI |
Android 的 ScreenPixelSize 来自 Display.getRealMetrics,iOS 的 NativePixelSize 来自 UIScreen.nativeBounds;两者都不读取主动缩小后的渲染 Surface。Android 系统分辨率覆盖仍可能影响系统像素值。iOS nativeBounds 使用固定坐标轴,旋转后长宽不随应用方向交换;point 尺寸反映当前方向。
两个移动端快照的 EstimatedPhysicalSize 用横向像素 / 横向 DPI、纵向像素 / 纵向 DPI 推算宽高,再计算对角线,单位均为英寸。像素或 DPI 缺失、非正数、非有限数时返回 null。厂商错误 DPI、新 iOS 型号和外接屏会降低准确度;这不是测量结果。
iOS 内置屏优先按硬件型号匹配面板 PPI,已补充 iPhone 12~17 系列、18 Pro/Pro Max、SE 第二/三代、iPhone Air,以及近年的 iPad、Air、Pro 和 mini;例如 iPhone 12/13 mini 为 476 PPI,iPad mini 6/A17 Pro 为 326 PPI,其它近期 iPad 为 264 PPI。未知型号仍按 nativeScale 与设备类别估算。型号表随客户端二进制更新;Android 使用系统上报的 Xdpi/Ydpi,不套用此表。
csharp
#if CLIENT
if (ClientEnvironment.Runtime.Supports(ClientRuntimeFeature.NativeClientServices))
{
var android = ClientPlatform.GetAndroidScreenMetrics();
double? androidDiagonalInches = android?.EstimatedPhysicalSize?.DiagonalInches;
var ios = ClientPlatform.GetIOSScreenMetrics();
double? iosDiagonalInches = ios?.EstimatedPhysicalSize?.DiagonalInches;
}
#endifUI 布局优先使用当前窗口的 dp/point 与视口。Density、Scale 和 DPR 都是缩放比例,不能直接当成物理 PPI;WindowSizeDp、ViewSizePoints 描述窗口,不是整块屏幕。Android ScaledDensity 也不能完整表达新版系统的非线性字体缩放。
Windows 切换支持的模式
csharp
#if CLIENT
using GameCore.ClientPlatform;
// 需先判断 NativeClientServices。模式选择由设置界面完成。
var displays = ClientPlatform.GetDisplays();
if (displays.Length > 0 && displays[0].Modes.Length > 0)
{
var display = displays[0];
var mode = display.Modes[0];
var result = ClientPlatform.SetWindowMode(mode.Width, mode.Height,
ClientWindowMode.ExclusiveFullscreen, display.Index, mode.RefreshRate);
}
#endif模式包括 Windowed、ExclusiveFullscreen、BorderlessFullscreen。GetWindow() 读取实际模式、位置、显示器、刷新率、显隐、缩放状态和尺寸约束。PIE 或明确指定启动宽高的宿主返回 BlockedByHost。
窗口控制还包括 ShowWindow、HideWindow、RaiseWindow、MinimizeWindow、MaximizeWindow、RestoreWindow、SetWindowPosition、SetWindowAspectRatio、SetWindowMinimumSize、SetWindowMaximumSize、SetWindowResizable 和 ApplyWindowMode。宽高比设为 0, 0 清除约束。
Android 与 iOS Surface
SetSurfaceSize(width, height) 请求渲染像素尺寸;RestoreDefaultSurface() 恢复系统布局或默认 backing scale。Android 恢复后继续跟随旋转与分屏,iOS 调整 backing scale 并保持窗口宽高比,因此最终尺寸可能与请求值不同。两者都以 resize 后的读取为准。
RequestOrientation(Portrait/Landscape) 请求竖屏或横屏;SetAllowedOrientations(...) 指定允许集合。Android 支持单方向、双横屏、双竖屏和全部方向,其它混合子集返回 Unsupported;iOS 还受宿主声明的方向集合限制。当前实际方向仍从 ScreenViewport 读取。
SetHomeIndicator 仅 iOS 可用:Visible、HiddenUntilTouch、Dimmed 对应系统指示条的显示行为,系统仍决定实际表现。
帧调度
GetFrameTiming() 读取原生即时值;SetFrameTiming(setting, value) 分别设置:
| 设置 | 含义 |
|---|---|
MaximumFps | 前台帧率上限 |
LockedFps | 独立锁帧设置 |
TimeStepClampFps | 限制过大的帧时间步,不是保证达到的最低帧率 |
BackgroundFps | 后台帧率上限 |
PauseWhenMinimized | 最小化暂停,传 0 或 1 |
TimeScale | 客户端引擎时间倍率,不改变服务端模拟 |
VSync | 垂直同步,传 0 或 1;实际表现受后端与系统影响 |
这些值不自动写入持久化设置。常规设置面板仍可用 ClientSettings.Graphics.MaxFrameRate,它同时控制原有的帧率上限与锁帧。
当前游戏的渲染细项
先检查 ClientRuntimeFeature.GameRenderSettings,再检查 GameRenderSettings.IsAvailable。没有当前游戏私有设置时,属性访问会抛出 InvalidOperationException。
| 属性 | 控制内容 |
|---|---|
OffscreenShadowsEnabled、BakedShadowsEnabled、PlanarShadowsEnabled | 离屏、烘焙和平面阴影 |
ClusterLightingEnabled、MergeDirectionalAndPointLights | Cluster 和灯光合并 |
RenderMaskEnabled、AmbientOcclusion | 遮罩与 AO 模式 |
ComputeSkinningEnabled、SimpleSkinningEnabled | 当前游戏的蒙皮策略 |
ParticleDynamicBatchingEnabled、ParticleLod | 粒子合批与原生 LOD |
PointLightsEnabled、PostProcessingEnabled | 独立点光和后处理 |
Quality | 实时场景质量,更新原生 LOD 距离 |
VertexShaderExcludes、PixelShaderExcludes | 引擎 Shader 宏排除字符串,保留自定义值 |
MaximumDrawDistance、MaximumShadowDistance | 绘制与阴影距离 |
Capture() 保存这些属性,快照的 Restore() 仅允许在同一个 Gameplay 恢复。快照不包括窗口、进程设置、场景分类开关和选项存储。RequestShadowRefresh() 请求后续渲染刷新烘焙阴影,不代表编辑器光照烘焙已完成。
TryGetSceneFlag / SetSceneFlag 控制 NativeSceneFlag:地形、原生 UI、粒子/背景/动画、UI 场景、原生世界降帧、世界静音及原生画布/单位调试分类。带 Process 前缀的项影响进程。原生画布不包含托管 GameUI Page,原生单位统计和调试层也不代表托管 Unit。
托管单位调试使用 GameUI.Debug.DebugOverlay.UnitLayers 与 UnitDebugLayers:攻击半径、碰撞半径、模型包围盒和足印网格。它只绘制当前本地场景已同步的单位,游戏结束时清理。实例化允许策略继续使用已有 Renderer.DynamicInstancing;GetDeviceInfo().InstancingAvailable 是可被原生调试修改的引擎标志,不是独立的硬件能力证明。
设备与诊断
GetDeviceInfo() 包括包标识、构建信息、构建信息哈希、设备描述、磁盘剩余字节、图形后端和可用的本地 IP/MAC。网络信息可能缺失,不能作为稳定设备标识。FormFactor 为 Desktop / Phone / Tablet / Unknown;Android 平板按系统 smallestScreenWidthDp >= 600 分类。
GetDiagnostics() 按需采样进程内存、原生分配器、粒子池、原生单位、图元、骨骼和已收到的服务端成本/CPU 样本。字节数为 64 位;未采集或不支持的可空字段保留 null,骨骼数目前仅编辑器编译提供。
GameCore.Diagnostics.GameDiagnostics.CaptureManaged() 提供本局当前端已注册的 Entity、Unit、Buff、托管堆字节和 GC 次数。客户端只看到本地已同步对象;服务端调用得到服务端快照,跨端展示由项目显式同步。没有准确的待 GC 单位计数。
RequestDiagnostic 支持 CPU 采集与上传、原生内存快照/分配报告、GPU 内存报告和 RenderDoc 请求,受客户端编译选项及运行环境限制。配对分段采集使用 ProfileCpuBlock(name, Action)、ProfileNativeMemoryBlock(name, Action);异常时仍结束采集,不支持时仍执行回调。回调必须同步,不能传 async void。
SendTelemetry 提交事件和字段;RecordStage 标记、清理、上传阶段或应用记录;SetLagThresholds 修改当前游戏诊断阈值。它们使用现有平台采集通道,不提供服务端权威数据。
手柄与变化事件
GetGamepads() 读取 ID、名称、控制器类型、按钮、轴和帽键状态。订阅 Changed 后才启用原生事件,事件在游戏更新阶段分发:
gamepad.connected/disconnected/button/axis/hat:按键释放值为0,手柄输入遵循游戏视口焦点。display.changed:重新读取显示器、窗口或 Surface。option.changed:通过Key、Project、Scope、WriteMode、OptionValue判断变化。state.resync:队列溢出,重新读取所需状态。
结束使用时取消订阅,避免在设置面板关闭后继续处理事件。
本地选项
GetOption / SetOption 与现有本地持久化配置互通,支持 bool、有限 float 范围数值和字符串。Game scope 读取项目值并继承设备值;Device scope 明确读取设备命名空间。
| 写入模式 | 行为 |
|---|---|
Transient | 仅当前 WASM 会话可见,不写磁盘,只通知本会话;传 null 清除覆盖 |
Persisted | 保存本地配置,清除该会话同键临时覆盖,按已有设置处理器应用 |
Default | 设置默认值,不覆盖显式配置或临时覆盖 |
临时选项保存的是数据,不调用进程级音量/帧率处理器或旧 Lua 选项回调;需要即时改变硬件或渲染时,调用相应类型化 API,并在需要时自行恢复。设备 scope 的临时覆盖仍只属于当前会话。不同游戏不会读取彼此的临时覆盖。
二维码、资源及应用操作
GenerateQrCode(text)返回不含静区的模块矩阵,Pixels行优先,1深色、0浅色;显示时自行留出至少四模块静区。过长文本或不支持时返回null。ScanQrCodeAsync(token)负责启动、等待和清理相机扫描;返回成功、权限拒绝、用户取消、繁忙、不支持、不可用或失败。调用方令牌取消或游戏结束会抛出OperationCanceledException并取消原生请求,同一进程一次仅允许一个 WASM 游戏拥有扫码请求。GetFileChecksum(path, algorithm, raw)校验真实文件;相对路径以客户端根目录为基准,raw=false走引擎文件解混淆。GetResourceChecksum(path, algorithm)经 ResourceCache 解析逻辑资源与资源包,计算读出的资源内容校验。支持 MD5、CRC32、SHA1,CRC32 返回无符号十进制文本,失败/不支持返回null。原始包字节与包内资源内容不是同一校验对象。ClientCompatibleHash.Djb2保持旧 Lua UTF-8、初值 5381、32 位溢出及 NUL 截断语义;Md5对托管字节生成小写十六进制。JSON 使用System.Text.Json。GetResourceDependencies()返回当前原生库名、资源路径和已知资源包版本;资源包版本不等同托管 DLL 版本,未知为null。ReloadFontMap()刷新宿主字体映射。资源释放/重载继续使用 ResourceCache 与 ImageCache 的明确操作。RequestExit()请求退出应用,PIE 返回BlockedByHost,iOS 普通应用退出返回Unsupported;embeddedSdk=true请求宿主 SDK 退出。退出当前对局使用现有LobbyService.QuitCurrentGame。CreateProjectShortcut()仅 Android 请求为当前项目创建快捷入口,是否展示由宿主和系统决定。
安全区调试使用 ScreenViewport.SetSafeZoneSimulation(Thickness?),单位为当前渲染像素,传 null 恢复系统值;NativeSafeZonePadding 始终读取系统值。