Godot Android 分屏模式适配:窗口突然变窄时别只重排按钮

分析 Godot Android 多窗口和分屏模式下的视口尺寸突变、UI 安全区、输入坐标、相机裁剪和资源预算调整。

分屏不是一次普通 resize

Android 分屏和多窗口模式看起来像普通窗口尺寸变化,实际对游戏客户端更像一次小型环境切换。屏幕比例会突然变窄或变矮,系统栏占用区域变化,触摸坐标重新映射,软键盘可能挤压可用区域,游戏相机的视野和 UI 安全区都要重新计算。只把按钮锚点改成自适应,通常不够。

Godot 项目在桌面调试时很容易忽略这个问题。开发者拖动窗口,看 UI 能跟着缩放,就以为移动端也安全。但 Android 分屏有更多约束:窗口可能很小,设备方向可能被系统保持,输入法弹出后高度再次变化,返回全屏时又触发一轮尺寸恢复。玩家可能一边看攻略一边玩游戏,也可能在折叠屏上改变窗口区域。客户端如果没有把窗口变化作为运行时事件处理,就会出现按钮跑出安全区、触摸落点偏移、镜头裁剪错误、战斗 UI 挡住角色等问题。

这篇不是讲普通 UI 响应式,而是讲 Godot 在 Android 多窗口场景下如何把 Viewport、Control、Camera、Input 和资源预算一起重新协调。

先识别窗口模式

第一步不是重排 UI,而是知道当前处于什么窗口环境。建议通过 Android 插件或平台桥接提供归一化信息:是否多窗口、窗口尺寸、屏幕尺寸、方向、系统栏区域、输入法区域、像素密度、是否折叠屏姿态变化。Godot 层收到后生成 WindowProfile,而不是让每个页面自己读 viewport size。

WindowProfile 可以包含:modesafe_rectviewport_sizeaspect_classinput_rectdensitygeneration。其中 generation 很重要。窗口连续变化时,旧的布局计算可能晚到。只要 generation 不匹配,就不能应用旧布局。

模式可以粗分为 fullscreensplit_widesplit_tallfloating_smallkeyboard_compressed。不同模式下策略不同。split_tall 可能需要把横向技能栏改成竖向折叠,floating_small 可能禁止复杂战斗入口,keyboard_compressed 要优先保护输入框和确认按钮。

事件管线

窗口变化要走统一管线。不要让 UI、相机、输入和资源系统分别监听 resize 后各自处理。它们需要同一个 WindowProfile,同一个 generation,并且按顺序更新。

flowchart TD
    A["Android Window Changed"] --> B["Build WindowProfile"]
    B --> C["Freeze Layout Mutations"]
    C --> D["Recalculate Safe Areas"]
    D --> E["Update Viewport And Camera"]
    E --> F["Remap Input Regions"]
    F --> G["Apply UI Layout Variant"]
    G --> H["Adjust Render Budget"]
    H --> I["Unfreeze Interaction"]

冻结交互是为了避免玩家在布局半更新时点击。窗口变化通常很快,冻结时间可以只有一两帧,但这能防止输入落到旧坐标。尤其软键盘弹出、分屏拖动边界时,坐标变化可能连续发生。

Viewport 和 Camera 的区别

很多适配问题来自混淆 Viewport 尺寸和相机视野。Viewport 变窄后,如果只拉伸画面,角色和 UI 会变形;如果只保持相机 FOV,边缘信息可能丢失;如果只缩放 UI,触摸区域可能和视觉不一致。Godot 里需要明确:Viewport 负责渲染目标尺寸,Camera 负责游戏世界可见范围,Control 负责 UI 布局,它们不能互相替代。

2D 游戏常见策略是保持逻辑高度,横向增减可见区域。但在竖向分屏里,横向太窄可能影响战斗预判。此时可以启用窄屏相机 profile:略微拉远镜头、减少边缘装饰、把重要提示移到角色附近。3D 游戏则要考虑 FOV 和裁剪。窗口很窄时,继续使用全屏 FOV 可能让玩家失去侧向信息,改 FOV 又可能造成眩晕。更稳的是为分屏模式定义专门的相机 framing,保护角色、目标和交互提示。

相机变化要慢一点。窗口 resize 可能瞬间发生,但镜头最好用短过渡,避免画面跳。UI 可以快速重排,Camera 可以在 0.1 到 0.2 秒内平滑调整。战斗中如果窗口变化太剧烈,可以先暂停高风险输入一小段时间,给玩家重新感知空间。

UI 安全区和密度

Android 分屏下,系统栏、安全区和手势区域会变化。不要假设屏幕底部永远可点。Godot 的 Control 锚点只能解决一部分问题,真正需要的是全局安全区服务。所有关键按钮、拖拽区域、摇杆、确认按钮都应该布局在 safe_rect 内,或者明确声明可以贴边。

触摸控件要看物理尺寸,而不是只看像素。窗口变小后,如果按比例缩小虚拟摇杆,可能变得难以操作。建议为触摸控件设置最小物理尺寸,低于阈值时切换布局:摇杆改为浮动,技能按钮折叠,次要入口收进菜单。不要为了显示完整 UI 把所有按钮缩到玩家点不准。

文字同样要处理。系统字体缩放、窗口变窄和本地化长文本叠加时,按钮最容易撑爆。分屏模式下可以减少文案,保留图标和短标签,但不能牺牲可理解性。关键状态如网络恢复、下载暂停、支付确认,仍然要显示清楚。

输入坐标要重新绑定

窗口变化后,输入坐标可能发生偏移。尤其 Godot 项目如果同时使用 SubViewport、CanvasLayer 和自定义触控区域,旧的坐标转换很容易残留。建议所有触摸判定都通过 InputRegionService 获取当前 profile 下的区域,不要在控件里缓存屏幕坐标。

虚拟摇杆、拖拽物品、技能瞄准、地图缩放都要在 resize 时取消或重绑定。玩家正在拖动技能方向时窗口突然变窄,继续使用旧起点会导致方向错乱。更稳的做法是:窗口 generation 变化时,取消当前连续手势,给出轻量反馈,要求玩家重新触摸。对普通按钮点击,可以在新布局稳定后继续。

软键盘是另一个重点。输入框获得焦点后,窗口可用高度可能变化。聊天输入、兑换码输入、角色命名等页面要把确认按钮和输入内容保持在键盘上方,不要只依赖系统自动 resize。输入法候选栏也会占空间,中文输入尤其明显。

渲染预算也要变

分屏窗口变小不一定意味着渲染更便宜。像素少了,但 UI 重排、相机变化、后处理、SubViewport 和动态分辨率都可能产生新成本。折叠屏和高刷新设备上,多窗口模式还可能和系统动画、其他应用同时竞争资源。

建议在 WindowProfile 里输出 render_budget_class。小窗口可以降低后处理、减少阴影距离、暂停高成本背景动画、降低粒子上限。注意这不是低端机策略,而是窗口模式策略。高端设备也可能在分屏时需要降低预算,因为玩家同时运行另一个应用。

资源加载也要配合。多窗口模式下,后台预加载和截图缓存可以降优先级。玩家使用分屏通常意味着当前游戏不是唯一关注对象,客户端更应该保证响应和稳定,而不是偷偷做重资源任务。

Android 与 Godot 的桥接

Godot 层可以通过原生插件获得更精确的 Android 信息。桥接层不要把 Android 原始事件一股脑传上来,而是整理成稳定字段。比如 onMultiWindowModeChanged、配置变化、窗口 insets、键盘区域,都可以汇总成一次 window_profile_changed

桥接要处理事件风暴。拖动分屏边界时,系统可能连续发 resize。Godot 层可以做 debounce,但不能丢最后一次。一个常见做法是记录最新 profile,在下一帧或短延迟后应用;如果期间又有新 profile,就覆盖旧计划。布局应用过程要带 generation,防止旧计划晚到。

如果项目没有原生桥接,也可以先用 Viewport resize 和 DisplayServer 信息做基础适配。但要承认这只能覆盖一部分。真正严肃的移动端项目,最好把系统 insets 和多窗口状态纳入平台层。

QA 场景

测试要覆盖普通手机、平板、折叠屏和不同 Android 版本。场景包括:全屏进入游戏后切分屏,分屏拖动边界,分屏中弹软键盘,分屏中进入战斗,分屏中打开背包,分屏中切网络恢复,分屏返回全屏。每个场景都要检查 UI、安全区、输入、相机和性能。

特别要测连续手势。玩家按住虚拟摇杆时拖动分屏边界,技能瞄准时弹出系统栏,聊天输入时切回全屏,这些场景容易暴露坐标缓存问题。QA 不能只看页面是否重排,还要看交互是否仍然可信。

日志字段建议包括:window_generationmodeviewport_sizesafe_rectkeyboard_visiblelayout_variantcamera_profileinput_cancel_countrender_budget_class。有这些字段,线上反馈“分屏后点不到按钮”才有排查基础。

落地顺序

第一阶段建立 WindowProfile 和调试面板。让开发包能看到当前窗口模式、安全区、输入区域和布局 variant。先不急着适配所有页面。

第二阶段接入核心页面:大厅、战斗 HUD、聊天输入、资源下载确认。它们覆盖 UI、安全区、输入和网络提示。只要这几个页面稳定,大部分高风险路径就能控制。

第三阶段处理相机和渲染预算。为窄屏、矮屏、小窗口定义 profile,不要让全屏相机参数硬套所有模式。性能采样要分窗口模式记录。

第四阶段补自动化和真机矩阵。自动化可以模拟 resize,真机负责多窗口、软键盘和系统栏。两者缺一不可。

页面分级策略

不是所有页面都要在小窗口里提供完整能力。强行完整适配,反而会让操作变得危险。可以把页面分成三类。第一类是必须完整可用的页面,例如登录恢复、基础设置、下载确认、错误提示。它们负责让玩家能安全退出、恢复或处理阻塞问题。第二类是降级可用页面,例如大厅、背包、任务列表,可以隐藏次要入口、压缩信息密度。第三类是建议暂停或限制的页面,例如高强度战斗、复杂编辑器、拍照模式和大型商城预览。

分级不是偷懒,而是承认窗口太小时无法保证同等体验。客户端要把限制说清楚。比如小窗口下进入高难战斗前提示“当前窗口较小,部分战斗提示会收起”,或者直接要求恢复全屏。比起让玩家进场后发现技能按钮挤成一团,提前限制更负责任。

这个策略也适合 QA。每个页面在不同 WindowProfile 下应该有预期:完整、降级、只读、阻止。没有预期时,测试只能凭感觉判断“看起来还行”。有预期后,任何超出策略的表现都能明确报 bug。

配置化布局变体

分屏适配不建议完全写死在脚本里。战斗 HUD、地图、背包和聊天页面都可以声明自己的 layout variant。例如 normalcompact_widthcompact_heightkeyboard。WindowProfile 只负责选择 variant,页面自己负责把控件放到对应位置。

配置里不要只写坐标,还要写交互策略。比如 compact_width 下技能栏折叠成两层,长按显示说明;keyboard 模式下聊天列表高度减少,但发送按钮保持最小触控尺寸;floating_small 下地图默认隐藏筛选面板。布局和交互分开写,很容易出现视觉适配了但操作不可用。

Godot 中可以用 Resource 保存每个页面的布局策略。页面加载时读取当前 profile,应用对应 variant。窗口 generation 变化时,页面先保存临时状态,例如当前滚动位置、选中格子、输入框内容,再切换布局。这样玩家不会因为分屏拖动丢失上下文。

相机安全边界

战斗或探索场景里,窗口变窄后最重要的是保住玩法信息。角色脚下的危险区、敌人攻击预警、交互目标、任务方向,这些比远景和装饰更重要。相机 profile 可以定义安全边界:关键目标必须在可见区域内,UI 遮挡区域不能覆盖角色核心区域,边缘提示要在安全区内重算。

如果场景依赖屏幕边缘刷怪、弹幕或提示,分屏模式下要重新评估。全屏下从右侧飞来的投射物,窄屏下可能几乎没有预警时间。解决方法不一定是拉远镜头,也可以减少分屏模式下的边缘刷怪、提前显示方向提示,或在高风险玩法中禁止小窗口。

这部分需要策划、程序和 QA 一起定标准。技术上能显示不代表体验公平。移动端窗口变化会改变玩家可见信息,客户端要把它当作玩法条件变化。

小结

Android 分屏模式适配不是把按钮重新排一下。它会同时影响 Godot 的 Viewport、Camera、Control、Input 和资源预算。真正稳的做法是把窗口变化建模成 WindowProfile,再让各系统按同一个 generation 更新。

如果项目暂时只能做一件事,就先做安全区和输入坐标重绑定。按钮看起来偏一点只是视觉问题,点不到或点错才是玩家最不能接受的移动端事故。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页