Skip to content

ScreenViewport 分辨率适配 ​

何时使用 ​

当用户要实现或排查以下问题时使用本 Skill:

  • 窗口调整、横竖屏切换或移动端 Surface 重建后的 UI 适配
  • 全屏 HUD、全面屏安全区、不同宽高比和设计分辨率
  • Canvas / CanvasAnimated 的绘制分辨率和像素预算
  • 用户反馈分辨率变化后布局没有刷新
  • 同一版本只在部分 Android / iOS 设备出现 UI 缩放、错位、裁切或字号异常

推荐入口 ​

优先订阅客户端 ScreenViewport.Primary.OnViewportChanged。回调触发时,尺寸、方向、设备像素比、安全区和裁切偏移都已经更新完成:

csharp
#if CLIENT
var viewport = GameUI.Device.ScreenViewport.Primary;
viewport.OnViewportChanged += RefreshLayout;

void RefreshLayout()
{
    var size = viewport.Size;
    var safe = viewport.SafeZonePadding;
    var dpr = viewport.DevicePixelRatio;
    var orientation = viewport.DisplayOrientations;
    RebuildResponsiveLayout(size, safe, dpr, orientation);
}
#endif

OnSizeChanged 只适合只关心实际宽高变化的逻辑;完整布局不要只依赖它,因为 Surface 重建可能保持相同 drawable 宽高。OnOrientationChanged 和 OnDevicePixelRatioChanged 可用于专门的局部优化。

坐标和安全区 ​

  • Size、DesignResolution 和普通控件布局使用逻辑坐标。
  • FontSize 同样使用设计坐标,随画布缩放;这里的 DevicePixelRatio 是设计画布到屏幕像素的比例,不等于操作系统密度。排查小字或制定字号时先读 UI 字号与可读性,不要照搬 App 的 12–16 DIP/sp 或重复缩放字体。
  • WidthPx / HeightPx 是设备像素,只用于像素级资源或诊断。
  • 交互控件和重要文字使用 SafeZonePadding 内的区域;背景可以铺满屏幕。
  • 使用 CoverCentered 时,像素坐标换算还要考虑 GlobalOffsetPx。
  • 不要硬编码 1920x1080、1080x1920 或某个设备分辨率作为运行时尺寸。

Size 已经由当前 DesignResolution 和 ScaleMode 换算完成;例如 Contain 会让非标准宽高比设备的 Size 扩展到完整可见逻辑区域。普通 GameUI 不要再用 WidthPx / HeightPx 重建另一套逻辑视口。

设计分辨率所有权 ​

优先在当前 GameDataGameUI 中配置 DesignResolutionWidth、DesignResolutionHeight 和 ScaleMode,把它作为游戏 UI 的单一配置来源。代码式 UI 若确实需要调用 SetDesignResolution(...),应使用稳定的项目设计值,而不是按设备物理分辨率、当前方向或启动阶段采样动态改写。

SetDesignResolution(...) 修改的是全局 GameUI 坐标系,会同时影响页面、文字、系统 UI 和 Canvas。不要在项目代码中通过以下方式“纠正”框架状态:

  • 比较 DisplayOrientations 与 WidthPx / HeightPx 后切换成正方形或临时设计分辨率。
  • 根据 WidthPx / HeightPx 反推 Size,再因为反推值不同而重写设计分辨率。
  • 在 viewport 回调、轮询和延迟任务中反复调用 SetDesignResolution(...),直到观察值看似稳定。

如果完整 viewport 快照内部自相矛盾,先记录证据并按客户端 / 框架问题隔离复现;不要把设备时序假设固化成项目级全局缩放状态机。

布局变换边界 ​

选择一个布局坐标来源并保持到底:优先让 Page / FillParent()、对齐、流式布局和 ScreenViewport.Size 完成响应式排版。需要在宽屏中居中展示固定宽高比内容时,可以在逻辑坐标内计算一次 Contain 内容矩形,但不要让同一层同时接受引擎全局缩放和另一套不完整的手工缩放。

若项目手工缩放固定设计坐标,必须让尺寸、位置、间距、圆角、字体和命中区域使用同一个缩放因子。只缩放控件矩形、仍把 FontSize 恢复为固定设计值,会在安全区或窄屏使文字与框体比例失配。安全区也只应用一次:背景可延伸到全屏,内容容器再扣除 SafeZonePadding。

事件处理约束 ​

把回调视为“状态失效通知”,在回调中读取最新属性并标记布局脏;昂贵重排、Canvas 重建和相机调整应合并到下一帧。窗口拖动和旋转可能连续产生多次通知,不要递归修改窗口尺寸,也不要假定回调只触发一次。

完整布局通常只订阅 OnViewportChanged。不要同时把 OnViewportChanged、OnSizeChanged、OnOrientationChanged、OnDevicePixelRatioChanged 和定时轮询都接到同一套重排 / 设计分辨率修改逻辑;这些兼容事件可能在统一快照前分别到达,重复入口会放大时序差异。布局缓存必须包含或失效其全部输入,例如 viewport、safe area、父容器设计尺寸和内容模式,不能只按上次写入的子控件矩形跳过刷新。

如果对象会重复创建和销毁,保存处理器并在结束时取消订阅,避免重复刷新。临时 UI 不再复用时调用 Destroy()。

设备特异错位审计 ​

不要只比较两张截图后猜设备 DPI。请让正常设备与异常设备在同一页面、同一数据和同一客户端版本下同时记录:

  • WidthPx、HeightPx、Size、DevicePixelRatio、DesignResolution、ScaleMode、DisplayOrientations 和 SafeZonePadding。
  • 页面根、主要内容容器、一个文字控件的逻辑尺寸、像素矩形和字号。
  • 是否有项目代码调用 SetDesignResolution(...),以及调用前后的完整快照。

先检查 Size.Width * DevicePixelRatio / Size.Height * DevicePixelRatio 是否近似物理像素,再区分根坐标系异常、布局缓存未刷新、只缩放几何未缩放文字,或单个页面使用固定绝对坐标。验证至少覆盖 16:9、超宽屏、较窄横屏、安全区非零和旋转 / Surface 重建;只改变物理像素但保持宽高比时,逻辑布局应保持一致。

客户端能力版本 ​

同尺寸 Surface 重建通知需要客户端能力版本 4。项目一般直接订阅事件即可;框架会在旧客户端上保留普通实际尺寸变化行为。只有需要选择不同实现时才检查:

csharp
#if CLIENT
using GameCore.ClientEnvironment;

bool supportsSurfaceResize = ClientEnvironment.Runtime.Supports(
    ClientRuntimeFeature.ScreenViewportSurfaceResizeEvents);
#endif

不要通过 BinaryVersion 判断能力,也不要在每帧重复查询 Supports(...)。

Canvas ​

Canvas 默认自动根据控件尺寸更新绘制分辨率。需要按移动端像素预算降级时,在 viewport 通知中读取像素尺寸并合并更新 Canvas.Resolution;不要把逻辑坐标整体改成设备像素坐标。

相关文档 ​