Cocos Creator屏幕适配全解:从fitHeight/fitWidth到血条跟随
2026/9/18 5:39:49 网站建设 项目流程

做 Cocos Creator 开发,屏幕适配是绕不过去的坎。很多新手一开始只会在 Canvas 组件上勾选 Fit Height 或者 Fit Width,结果换一台真机就露馅,更不要提后面遇到的 PC 浏览器适配、全屏切换、血条跟随这些需求。这个标题里每一个词我基本都踩过坑:竖屏手机、桌面浏览器、PC 分辨率全屏、血条跟随、fitHeight/fitWidth 混着装在一起的时候,如果没把底层机制理清楚,很容易改一处崩一处。

这篇文章我按实际项目调整的顺序来讲,先搞懂 fitHeight/fitWidth 到底做了什么,再分别处理手机竖屏、PC 浏览器、全屏适配,最后单独讲血条跟随 UI 的坐标转换。适合正在被适配问题折磨的 Cocos Creator 开发者,也适合刚接触跨平台发布、想少走弯路的新手。

1. 适配机制与设计分辨率

1.1 设计分辨率到底是谁的“设计”

很多人的误区是把编辑器的 720x1280 当成手机物理分辨率,其实不是。这个尺寸是你在编辑器里排 UI 用的“坐标系”,你可以把它理解成一张画布:美术在这张画布上画好图,程序在这张画布上摆好控件,引擎再做一件事——把这张画布缩放、位移,让它尽量贴合真实屏幕。

真实屏幕的比例一旦和设计分辨率不一样,就一定会出现两种情况:要么某些内容被挤出画面,要么画面里出现多余的区域。fitHeight 和 fitWidth 这两个选项,就是用来告诉引擎:你优先保证哪一边完整显示,另一边不保证。

我见过很多项目在策划阶段就把 720x1280 定死了,结果 UI 全部用绝对坐标摆在角落,换到一台 19.5:9 的手机上,左右直接被切掉一截。这里要记住一个核心结论:设计分辨率只是一个参考坐标系,它不等于可见区域,真正决定可见区域大小的是“屏幕宽高比 + 适配策略”。

1.2 fitHeight、fitWidth 与 ResolutionPolicy 的对应关系

Cocos Creator 的 Canvas 组件上有两个勾选框:Fit Height、Fit Width。勾选组合不同,引擎内部对应不同的 ResolutionPolicy 策略,行为差异很大。我用一张表总结:

Canvas 勾选状态内部策略实际行为
只勾 Fit HeightFIXED_HEIGHT高度固定,宽度随屏幕比例变化
只勾 Fit WidthFIXED_WIDTH宽度固定,高度随屏幕比例变化
两个都勾SHOW_ALL完整显示设计分辨率,可能上下或左右出现黑边
两个都不勾NO_BORDER铺满屏幕,可能裁剪设计区域的一部分

以竖屏项目为例,设计分辨率是 720x1280,如果只勾 Fit Height,那么游戏高度永远保持 1280 这个设计高度,宽度会根据真实屏幕比例换算。比如屏幕比例是 9:16,那宽度正好是 720;如果是窄长屏 19.5:9,宽度可能变成 600 左右,左右位置原来的按钮就可能被切掉。

如果只勾 Fit Width,那就是宽度固定为 720,高度跟着比例走。高屏手机上高度会超过 1280,你可以往下多放内容,但竖屏游戏上下的底部按钮位置就要小心,因为多出来的高度会撑开。

这里要特别提醒一句:在真实项目里,不要看到 Fit Height 就叫竖屏适配,看到 Fit Width 就叫横屏适配。它们只是“固定哪条边”的选择,具体用哪个完全取决于你的 UI 重点在哪条边上。

1.3 为什么单一策略解决不了所有场景

如果只做手机竖屏,很多项目一个 Fit Height 能从开发到上线都不出问题。可一旦打开 PC 浏览器,问题就来了。

PC 窗口通常是横着的,屏幕宽高比可能是 16:10、16:9,甚至 21:9。如果设计分辨率是 720x1280,还只勾 Fit Height,引擎会保持高度 1280,宽度按比例变成 2400 甚至更多。于是你的场景左右两边会出现一大片“多余区域”,背景如果没做拉伸,就是大片空白或黑边。

反过来,如果只勾 Fit Width,PC 上的设计高度会缩到只有 400 多,整个游戏被压成一条细带,UI 全挤在一起。这个现象不是 Cocos 的 bug,而是适配策略没有跟着运行环境切换。

所以真正靠谱的做法是:先理解 fitHeight/fitWidth 的本质,再根据实际运行窗口的宽高比,动态决定采用哪个策略。这也是下面几章要展开的内容。

2. 手机端竖屏适配从项目配置到打包

2.1 竖屏项目的设计分辨率怎么选

竖屏项目我用的最多的设计分辨率是 720x1280,美术资产和 UI 切图在这个尺寸下比例清楚,真机上缩放倍数计算也方便。如果目标机型都是中高端设备,也可以直接上 1080x1920,清晰度更高,但包体和纹理内存会变大,要自己权衡。

选定设计分辨率后,在 Canvas 组件的属性面板上把 Fit Height 勾上,Fit Width 取消,这样上下内容永远完整,左右宽度根据不同手机比例自适应。这是竖屏游戏最常见的配置,因为上下往往有排行榜、倒计时、角色头像这类不能丢的信息。

如果你有从左到右完全不能裁切的内容,比如塔防场景里的整条防守路线,那就得反过来勾 Fit Width,保证宽度完整,但上下区域可能会比设计高度多一些,背景需要做延伸。不要死记硬背,关键是看核心玩法要保住哪一边。

2.2 如何使用 fitHeight / fitWidth 保护核心 UI

实际操作时,我会在项目启动后主动设置一次适配策略,而不是完全依赖编辑器勾选。这样后续切 PC、切全屏时能手动控制。代码一般放在 GameRoot 或者 Main 入口脚本里,Cocos Creator 3.x 写法如下:

import { view, ResolutionPolicy } from 'cc'; export function initScreenAdapter() { const designWidth = 720; const designHeight = 1280; // 竖屏手机:固定高度,宽度自适应 view.setDesignResolutionSize(designWidth, designHeight, ResolutionPolicy.FIXED_HEIGHT); }

如果需要在窄长屏上保证完整宽度,就把策略改成ResolutionPolicy.FIXED_WIDTH,引擎会保持设计宽度不变,让高度跟着屏幕比例去变。

但这里有个很关键的点:不管用哪个策略,UI 都不能全用固定坐标。左右两侧如果放重要按钮,在窄长屏上很容易被切掉,所以要么把按钮往中间收,要么用 Widget 组件做成“离屏幕边缘固定距离”的布局。

我的习惯是:核心按钮放在水平方向从 -260 到 260 的范围内,左右各留出至少 50 的安全边距。这样哪怕真机宽度只有 600,也只是裁掉边缘装饰,不会影响操作。

2.3 刘海屏、挖孔屏与安全区

竖屏适配到真机上,还有一个绕不开的问题:刘海屏和挖孔屏。

Cocos Creator 3.x 提供了 SafeArea 组件,可以直接加到处于 Canvas 根节点下的 UI 容器上。它会自动读取屏幕的安全区数据,把容器限制在安全区域范围内。我一般会做一个专门的 “SafeAreaRoot” 节点,挂上 SafeArea 组件,所有带文字、按钮的 UI 都放它下面,背景和全屏特效放外面。

如果项目需要兼容没有 SafeArea 组件的版本,或者自定义要求比较强,可以用系统 API 读取安全区数据自己算。原生平台上可以通过原生桥接拿到刘海高度;浏览器端可以看 CSS 环境变量env(safe-area-inset-top)。不过我建议优先用引擎自带组件,少造轮子。

注意 SafeArea 只处理屏幕凹槽和圆角,不处理 Android 虚拟导航栏。部分安卓机型底部导航栏会占用一块空间,需要额外留底部边距,否则你以为是贴底的按钮实际被导航栏挡住了一半。

2.4 APK 打包方向设置

竖屏适配还有最后一步:打包 APK 时把方向锁死。否则你在编辑器里怎么调都没用,到了手机上转个横屏,所有适配全部失效。

Cocos Creator 构建发布面板里,Android 平台的构建选项一般有屏幕方向设置,正常选择 Portrait 或者竖屏。如果项目用的是自定义原生工程,还要检查 AndroidManifest.xml 里 Activity 的android:screenOrientation字段,把它设成portrait。两者都确认一遍,才不会出现打包出来自动横屏的情况。

这里有个小经验:如果是从别的项目复制过来的 Android 工程,最容易忘了改 manifest。我已经不止一次遇到“引擎设置竖屏,但 APK 还是横屏”的奇怪问题,最后都是因为 manifest 里残留了 landscape 配置。

3. PC 浏览器与全屏适配方案

3.1 桌面端遇到的宽高比冲击

手机竖屏的宽高比通常在 0.46 到 0.56 之间,也就是竖着的长方形。PC 浏览器窗口则是典型的宽扁形态,宽高比一般在 1.3 到 1.8 之间,压缩到极端时可能是 1.0 甚至更低。

这个差异带来的冲击,比不同手机之间的差异大得多。如果还用 Fit Height,PC 上设计宽度会从 720 变成 2000 多,很多 UI 会像被拉到了屏幕两头;如果改用 Fit Width,设计高度又会被压到 400 多,整个游戏看起来像一条横带,日常玩法都没法看。

所以当项目需要在 PC 浏览器上运行时,适配逻辑就不能只在启动时执行一次,而是要监听窗口尺寸变化,动态切换。

3.2 动态切换适配策略的完整实现

我先给一套兼容手机和 PC 的思路:以设计分辨率 720x1280 为基准,如果窗口比基准更“窄高”,用 FIXED_WIDTH,保证宽度完整;如果窗口比基准更“宽扁”,用 FIXED_HEIGHT,保证高度完整。

代码可以这样写:

import { view, screen, ResolutionPolicy } from 'cc'; const DESIGN_WIDTH = 720; const DESIGN_HEIGHT = 1280; const DESIGN_ASPECT = DESIGN_WIDTH / DESIGN_HEIGHT; export function applyScreenAdapter() { const windowSize = screen.windowSize; const aspect = windowSize.width / windowSize.height; if (aspect < DESIGN_ASPECT) { // 窗口更窄/更高:固定宽度 view.setDesignResolutionSize(DESIGN_WIDTH, DESIGN_HEIGHT, ResolutionPolicy.FIXED_WIDTH); } else { // 窗口更宽:固定高度 view.setDesignResolutionSize(DESIGN_WIDTH, DESIGN_HEIGHT, ResolutionPolicy.FIXED_HEIGHT); } }

这套逻辑可以解决大部分手机和普通 PC 窗口的问题,因为它保证设计分辨率里至少不会出现“内容被强制裁剪”的情况,多出来的方向通过背景和 UI 锚点去延伸。

不过如果你想在 PC 超宽屏上更精细,可以在这个基础上做动态设计宽度。比如 PC 窗口很宽时,固定高度为 1280,但把设计宽度从 720 动态扩展到比例 * 1280,这样 UI 往左右边缘靠时不会出现夸张的大空白。扩展代码:

if (aspect >= 1.2) { const dynamicWidth = Math.round(aspect * DESIGN_HEIGHT); view.setDesignResolutionSize(dynamicWidth, DESIGN_HEIGHT, ResolutionPolicy.FIXED_HEIGHT); }

这段的意义是:PC 上不是把原有的 720 宽“硬拉”,而是把设计坐标系本身变成更宽的版本。代价是代码里的 UI 绝对坐标需要更依赖 Widget,不能让 UI 写在固定的 720 世界观里。

3.3 窗口 resize 与全屏 API 的正确接入

动态策略写好后,最重要的事情是确保它在窗口变化时被调用。

Cocos Creator 3.x 里可以监听screenwindow-resize事件,也可以用浏览器的原生 resize 事件做兜底。组件生命周期里类似这样处理:

protected onEnable() { screen.on('window-resize', this.onWindowResize, this); window.addEventListener('resize', this.onWindowResize); } protected onDisable() { screen.off('window-resize', this.onWindowResize, this); window.removeEventListener('resize', this.onWindowResize); } private onWindowResize() { this.scheduleOnce(() => applyScreenAdapter(), 0.1); }

这里我加了一个scheduleOnce,相当于 100ms 防抖。拖动浏览器窗口时 resize 事件会疯狂触发,每次都去改 designResolutionSize 会浪费性能,也没必要。延迟 100ms 后,窗口尺寸已经稳定,再刷新一次适配结果。

PC 全屏是另一个常见需求。最简单的实现是使用浏览器 Fullscreen API,比如点击按钮后:

document.documentElement.requestFullscreen();

退出全屏用:

document.exitFullscreen();

注意全屏切换完成后,窗口尺寸不会立刻更新,需要在fullscreenchange事件里等一帧再调用适配函数:

document.addEventListener('fullscreenchange', () => { requestAnimationFrame(() => applyScreenAdapter()); });

不要在全屏事件触发的瞬间立刻读取 window size,很可能还是旧值。

3.4 PC 全屏下的背景与 Widget 布局

PC 全屏后,窗口比例可能是 16:10,也可能是 21:9,背景如果只有 720x1280 一张图,必然露馅。最稳的方案是做一个“全屏背景容器”,给背景节点加上 Widget 组件,并把 Top、Bottom、Left、Right 全部设为 0,让它自动铺满整个 Canvas 可见区域。

如果背景是带有边框、光影的 UI 底图,建议用Sprite的九宫格拉伸,或者用TILED平铺模式,这样在任意宽高比下都不会出现拉伸变形和明显模糊。

UI 根节点下重要的控件也要配置 Widget,比如顶部返回按钮:顶部边距 20,左边距 20;底部操作按钮:底部边距 60,水平居中。Widget 的百分比和边距会随着 Canvas 尺寸变化自动重新计算,这样 720 的窄屏和 2000 的宽屏都能保持相对位置,而不会固定在以中心为原点的某个绝对坐标上。

我自己的经验是:PC 端的 UI 绝对坐标能不用就不用,所有“靠边”需求都交给 Widget,所有“居中”需求可以交给 Widget 的水平居中,或者自己写一个setPercent逻辑。虽然前期配置会繁琐一点,但换分辨率时真的能保住头绪。

4. 血条跟随的适配细节

4.1 血条放在 Canvas 下而不是怪物体下

屏幕适配做到一半,最常被问到的就是“血条怎么跟住角色”。很多人的第一反应是把血条做成怪物的子节点,直接挂在头顶。这在简单 2D 项目里能跑,但在 3D 项目或者有相机缩放、旋转的场景里,血条会跟着模型一起变形,甚至被模型挡住。

更规范的做法是:血条永远放在 UI Canvas 下,每一帧把“怪物的脚底位置”转换成 UI 坐标系的位置,然后让血条节点移动到那里。这样血条始终以 UI 方式渲染,不受 3D 相机透视影响,也不受屏幕适配策略变化影响。

4.2 世界坐标到 UI 坐标的标准转换

在 Cocos Creator 3.x 中,Camera 组件提供了convertToUINode方法,可以把世界坐标直接转换到某个 UI 节点的本地坐标系。这是做血条跟随最省事的方式。

我在组件里会这样写:

import { _decorator, Component, Node, Camera, Vec3 } from 'cc'; const tmpUIPos = new Vec3(); @ccclass('UIFollowTarget') export class UIFollowTarget extends Component { @property({ type: Camera }) camera: Camera | null = null; @property({ type: Node }) footNode: Node | null = null; lateUpdate() { if (!this.camera || !this.footNode) return; this.camera.convertToUINode( this.footNode.worldPosition, this.node.parent!, tmpUIPos ); this.node.setPosition(tmpUIPos); } }

这里的核心是convertToUINode的三个参数:第一个是世界坐标点,第二个是血条所在 UI 层的根节点,第三个是输出变量。方法执行后,tmpUIPos就是血条在 UI 父节点下的本地坐标,直接setPosition就行。

不要把它和worldToScreen搞混。worldToScreen得到的是屏幕像素坐标,还要再做一次 Canvas 坐标转换;convertToUINode会直接把世界坐标转到 UI 节点坐标,省掉很多手工换算,血条也能自动兼容适配策略变化。

4.3 锚点选择、父节点层级与性能节奏

血条跟随位置很讲究,直接取怪物的中心节点,会让血条“浮”在怪物腰上。我在每个带血条的角色根节点下放一个名为FootAnchor的空节点,放在脚底,然后脚本里引用这个子节点。这样血条出现在头顶的高度就是稳定可控的。

血条自身的锚点建议设置成 (0.5, 0),这样血条的底部中心点会紧贴合脚底转换出来的坐标,再往上偏移一点点就是头顶。如果还想加一个浮动的伤害数字,可以做成血条节点的子节点,位置继续上移。

层级方面要注意:多个血条同时出现时,如果它们都挂在同一个 Canvas 下,后创建的子节点会盖住先创建的,最好在创建后根据 Y 坐标或者角色优先级排一次setSiblingIndex,保证视觉顺序正确。性能方面,血量变化和角色移动都需要每一帧刷新位置,不需要做缓存;但如果同一屏有上百个角色,可以考虑每 0.1 秒批量刷新一次,减少 UI 节点 setPosition 的调用次数。

4.4 血条在适配策略切换后如何不漂移

PC 窗口切到全屏,或者手机转屏时,适配策略一变,Canvas 的坐标系也会变。如果血条位置是上一帧缓存下来的,下一帧就可能会漂。

最稳妥的方案是:不在 resize 事件里维护血条缓存,而是让UIFollowTarget组件的lateUpdate一直执行。只要每一帧都重新做一次convertToUINode,分辨率怎么变都不会影响最终位置。如果你有特殊的暂停逻辑导致lateUpdate不跑了,记得在applyScreenAdapter()之后手动遍历所有血条,调用一次刷新方法。

我在实际项目中还遇到过一个问题:换适配策略以后,血条位置没错,但血条底图大小变得很怪。这是因为血条里用了固定 PNG 宽度,而 Canvas 的缩放比例变了。解决方法是血条底图不要用绝对宽度写死,要么用定宽节点 + 内部条状 Sprite 等比缩放,要么把血条放到独立分辨率层,不受主 Canvas 缩放影响。

5. 常见问题速查与避坑建议

5.1 高频问题速查表

现象可能原因解决方案
手机竖屏正常,PC 浏览器左右大片空白一直用 FIXED_WIDTH 或固定设计分辨率根据宽高比动态切换 FIXED_HEIGHT / FIXED_WIDTH
PC 全屏后游戏被压成一条带子设计分辨率用竖屏但没动态调整设计宽度桌面端按比例动态扩大设计宽度
血条跟随位置在空中,不在头顶取了角色模型中心而非脚底锚点在怪物脚底挂 FootAnchor 节点
血条跟着角色,但切换分辨率后漂移位置被缓存,没有每帧重新转换用 lateUpdate 每帧调用 convertToUINode
打包 APK 后仍是横屏构建面板和 AndroidManifest 不一致两边都设成 portrait
刘海屏按钮被遮挡没有处理 SafeArea用 SafeArea 组件或安全区边距
背景在宽屏下露边背景只做了 720x1280 固定尺寸背景用 Widget 全屏拉伸或 TILED 平铺

5.2 适配卡壳时的排查顺序

我自己的排查顺序基本是固定的:先看setDesignResolutionSize传进去的设计宽高和策略是否正确;再看 Canvas 组件上的 Fit Height / Fit Width 是否和代码策略冲突;然后检查 UI 控件是不是真的挂在 Canvas 节点下;最后才是遍历血条、进度条这类跟随节点,看坐标转换有没有用对 API。

如果发现血条漂移,优先检查的不应该是分辨率,而是取的是不是脚底节点。很多漂移问题其实就是锚点取错,跟屏幕适配一点关系都没有。等把坐标系转换这一类基本功练熟,再看 fitHeight / fitWidth 就会觉得它们只是控制“哪条边固定”的开关,真正的适配重点还是在 UI 布局的灵活度上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询