主题
ScreenViewport 分辨率变化最佳实践
ScreenViewport 描述客户端当前屏幕的物理尺寸、UI 逻辑尺寸、方向、设备像素比和安全区。分辨率适配代码应把它当作一次完整的 viewport 状态更新,而不是只监听 drawable 宽高。
选择事件
优先订阅 ScreenViewport.Primary.OnViewportChanged:
csharp
#if CLIENT
using GameUI.Device;
private static void OnRegisterGameClass()
{
var viewport = ScreenViewport.Primary;
viewport.OnViewportChanged += RefreshLayout;
Game.OnGameEnd += () => viewport.OnViewportChanged -= RefreshLayout;
}
private static void RefreshLayout()
{
var viewport = ScreenViewport.Primary;
var visibleSize = viewport.Size;
var safeZone = viewport.SafeZonePadding;
var orientation = viewport.DisplayOrientations;
var devicePixelRatio = viewport.DevicePixelRatio;
RebuildResponsiveLayout(visibleSize, safeZone, orientation, devicePixelRatio);
}
#endifOnViewportChanged 在尺寸、方向、设备像素比、安全区和裁切偏移更新完成后触发。移动端 Surface 重建即使没有改变 drawable 宽高,也会通过该事件通知项目。OnSizeChanged 和 OnOrientationChanged 仍适合只关心单一维度的轻量逻辑,但不应单独作为完整响应式布局的唯一入口。
使用正确的坐标
- 普通 GameUI 布局使用
Size、DesignResolution和控件的逻辑坐标,不要把WidthPx/HeightPx直接当作 UI 坐标。 WidthPx/HeightPx是设备像素,适合截图、纹理、像素预算和底层诊断。SafeZonePadding是设备无关像素;可交互按钮、文字和重要 HUD 应避开安全区,背景和全屏装饰可以延伸到屏幕边缘。GlobalOffsetPx反映CoverCentered的裁切偏移。需要和物理像素坐标互转时,必须同时考虑DevicePixelRatio。- 设计分辨率应从
ScreenViewport或项目GameDataGameUI读取,不要硬编码某个手机或桌面分辨率。
避免重复和抖动
窗口拖动、旋转和系统 UI 变化可能在短时间内产生多次通知。回调中只更新状态并标记布局脏,昂贵的图片重排、Canvas 重建或场景相机调整应合并到下一帧执行。不要在事件中递归修改窗口尺寸,也不要假定一次操作只产生一次回调。
如果界面会多次进入和退出,保存事件处理器并在对应生命周期结束时取消订阅,避免同一个布局被重复刷新。临时 UI 关闭后按控件生命周期调用 Destroy()。
旧客户端兼容
同尺寸 Surface 重建通知属于客户端能力版本 4。项目通常可以直接订阅 OnViewportChanged;框架会对旧客户端保留实际尺寸变化的兼容路径。只有在项目必须区分两种实现时,才使用:
csharp
#if CLIENT
using GameCore.ClientEnvironment;
bool supportsSurfaceResize = ClientEnvironment.Runtime.Supports(
ClientRuntimeFeature.ScreenViewportSurfaceResizeEvents);
#endif不要用 BinaryVersion 或自行比较版本数字判断功能。未知能力版本按不支持处理。
Canvas 特别说明
Canvas 开启 AutoUpdateResolutionOnResize 后,绘制分辨率会随控件尺寸自动调整;通常不需要在每次 viewport 通知中手动设置 Canvas.Resolution。如果项目确实要按像素预算调节 Canvas 质量,应在 OnViewportChanged 中读取最新的 WidthPx / HeightPx,计算目标分辨率,并合并连续通知。