
做Cocos开发这么多年我见过太多项目上线后还在被适配问题追着跑新手机一出就乱套横屏改竖屏界面直接飞出屏幕。说实话CocosCreator3.8.2这套引擎在适配能力上已经比早期版本成熟太多但大多数人还是只在编辑器里拖一拖Canvas根本不知道背后的机制。这次我正好在3.8.2上整理了一套多分辨率适配方案顺手把横竖屏自动翻转也一起做了整个Demo跑下来不到5分钟就能复现于是把它完整拆给你看。这篇不是讲玄学适配而是直接可抄作业的实操总结。你会搞清楚Canvas到底在干嘛、监听屏幕方向变化的正确姿势、以及怎么用一套代码同时应对手机和平板的横竖屏切换。文章里的Demo工程结构很轻适合刚接触CocosCreator 3.x的人也适合老手拿去核对一下自己的配置习惯。所有代码我都放在正文里你照着建节点、粘贴脚本、选参数就能跑出和截图一样的效果。1. 多分辨率适配的核心思路先搞懂Canvas在替你做什么很多人在项目里放一个Canvas节点就完事遇到问题第一反应是换分辨率策略换过来换过去还是黑边最后干脆把UI全用绝对坐标写死。这种思路从一开始就走偏了。适配的前提是理解引擎替你做了什么以及哪些事引擎确实帮不了你。1.1 设计分辨率、屏幕尺寸和分辨率策略的三角关系先说三个最容易混淆的概念设计分辨率、屏幕尺寸、分辨率策略。设计分辨率是你“画稿子”时用的坐标基准Cocos Creator 3.8.2里默认是960x640左右但绝大多数游戏项目会改成1280x720或者1620x768。屏幕尺寸就是设备真实显示的像素或逻辑分辨率比如iPhone 15 Pro Max的430x932点安卓平板可能是一块2560x1600。分辨率策略则决定了脚本口、设计分辨率和屏幕尺寸之间的换算关系。假设你的设计分辨率是1280x720屏幕是430x932这个比例差了两倍多。引擎需要决定是让画面上下砍掉一部分还是整体缩放显示全貌留黑边还是直接拉伸变形对应在CocosCreator里就是几种策略策略行为典型副作用适用场景SHOW_ALL完整显示整个设计分辨率等比缩放保内容完整屏幕比例不一致时出现黑边竖屏休闲小游戏不介意留边NO_BORDER等比缩放并填满屏幕保证不露边画面上下方或左右两侧被裁切对沉浸感要求高的横屏动作游戏FIXED_WIDTH以宽度为基准等比缩放高度自适应高度方向内容会变多或变少左右视野固定、竖向可扩展的关卡FIXED_HEIGHT以高度为基准等比缩放宽度自适应宽度方向内容会变多或变少横屏游戏最常见高度统一为基准EXACT_FIT宽高各自拉伸到屏幕尺寸所有元素比例变形基本不推荐除非是纯全屏背景引擎内部真正做的事就是通过Canvas组件把设计分辨率映射到屏幕上再把这一帧画面交给Camera渲染。这个映射关系一旦确定你放置的UI只要基于设计分辨率坐标就能等比呈现。可问题也出在这里设计分辨率只有一个屏幕却千奇百怪。你的脑子必须随时转换这个元素在某个更宽的屏幕上应该靠左还是居中在更高的屏幕上应该贴底还是留出安全区1.2 3.8.2项目里的推荐配置我在Demo里怎么设的新建CocosCreator3.8.2项目后我建议你先改两个地方项目设置和场景里的Canvas组件。我个人的习惯是统一以FIXED_HEIGHT固定高度作为适配基准。为什么因为绝大多数横屏游戏的角色、按钮、操作区都习惯布置在屏幕中部上下两个方向留出变化空间固定高度能让UI整体缩放稳定宽度方向的空白用背景延伸或UI控件去自适应。具体操作顺序是这样的打开构建发布面板在“项目设置-项目数据”里把设计分辨率设置成1280x720。然后在场景层级管理器中选中Canvas节点检查它的“适配高度”和“适配宽度”两个勾选状态。在3.8.2中Canvas组件上有一个“Resolution Policy”属性默认是SHOW_ALL我们要把它改成FIXED_HEIGHT或者直接勾选“Fit Height”。你说依靠哪个我习惯直接改Canvas组件因为这样在编辑器预览时马上能看到效果而项目设置里的是给构建用的默认值两边要保持一致。这里有个关键点Canvas节点上还有一个Camera子节点它的orthoHeight正交高度会随着分辨率策略变化。如果之后你把分辨率策略改成FIXED_WIDTHCamera的orthoHeight会自动调整但很多人的2D坐标错乱问题其实就出在没搞清楚Camera和UI层的从属关系。简单说Camera的视野必须和Canvas的设计分辨率匹配否则你看到的就是“节点和镜头错位”。保持默认关联即可别手欠去拆开它们。2. 横竖屏自动翻转别让游戏卡在“方向”上多分辨率适配只是解决了“同一方向下不同尺寸的兼容”横竖屏切换则是另一个维度的问题。手机从横屏转竖屏屏幕的宽高比直接颠倒原来的适配策略可能瞬间从FIXED_HEIGHT的理想模式变成灾难现场。你必须主动监听变化并且在布局层面做出响应。2.1 监听到方向变化的正确姿势CocosCreator 3.8.2并没有一个专门的“orientation change事件”挂在节点身上但引擎的view对象会派发canvas-resize事件。不管是浏览器窗口被拖拽、手机旋转还是安卓平板折叠屏幕最终都会反映为画布尺寸变化所以监听这个事件是最通用的做法。如果你只在网页平台上跑监听window.resize技术上也行但为了后续打包原生和桌面端统一用view.on(canvas-resize)更省心。事件挂载的时机最好在onLoad里挂在根节点上。注意别挂在会被销毁的UI节点上否则切场景时监听就丢了方向一变又不响应了。我一般放在常驻的“根脚本”里比如一个挂在Canvas节点上的AutoFit组件只要游戏进程活着它就一直在。import { _decorator, Component, view, sys } from cc; const { ccclass } _decorator; ccclass(AutoFit) export class AutoFit extends Component { private isLandscape: boolean true; onLoad() { this.isLandscape this.checkLandscape(); this.applyLayout(this.isLandscape); view.on(canvas-resize, this.onCanvasResize, this); } onDestroy() { view.off(canvas-resize, this.onCanvasResize, this); } private onCanvasResize() { const nowLandscape this.checkLandscape(); if (nowLandscape ! this.isLandscape) { this.isLandscape nowLandscape; this.applyLayout(this.isLandscape); } this.applySafeArea(); } private checkLandscape(): boolean { const frameSize view.getFrameSize(); return frameSize.width frameSize.height; } private applyLayout(isLandscape: boolean) { // 切横屏或竖屏时的UI调整下面会写 } private applySafeArea() { // 刘海屏安全区适配下面会写 } }这段代码里checkLandscape用宽高比判断方向比用角度判断更可靠因为在PC浏览器里旋转手机模拟器时系统角度事件可能不触发但尺寸一定会变。判断完方向之后再按照当前方向去调整UI布局这是下一步的工作。2.2 切换布局的两种落地方式方向翻转让你的UI需要“换一套排布逻辑”实现方式有两条路一种是改Widget组件的对齐属性另一种是切换整套布局节点。第一种适合结构简单的页面比如顶部标题、底部按钮反向以后把顶部变成左侧、底部变成右侧通过改变Widget的isAlignTop、isAlignBottom、isAlignLeft、isAlignRight就能实现。第二种适合复杂界面横屏一套完整节点树竖屏一套完整节点树切换时控制active。我平时做项目会优先用“Widget动态改对齐”的方案因为代码可控性强不会出现两套节点状态不同步的问题。拿常见的顶部返回按钮举例横屏时它在屏幕左上角竖屏时它应该回到左上角但往下挪一点这时用Widget对齐左上角再添加一个top偏移值方向变化时改偏移就行。private topButtonWidget: Widget null; private applyLayout(isLandscape: boolean) { if (!this.topButtonWidget) { const topButton this.node.getChildByName(TopButton); this.topButtonWidget topButton.getComponent(Widget); } this.topButtonWidget.isAlignTop true; this.topButtonWidget.isAlignLeft true; this.topButtonWidget.top isLandscape ? 20 : 80; this.topButtonWidget.updateAlignment(); }记得每次修改Widget参数后调用updateAlignment()否则引擎不会立刻重算位置。这一步很多人漏掉导致改了属性但UI纹丝不动排查半天发现是没刷新对齐。如果你用UIOpacity或自定义脚本直接改position就不用调这个方法但Widget仍然是更省心的选择因为它的百分比对齐能自动适应宽高变化。3. 完整Demo的落地过程从项目配置到运行验证理论讲再多不如直接看一个能跑的Demo。这里我整理的是一个极简但五脏俱全的示例有一个顶部标题栏、一个中央内容背景、一个底部按钮组横竖屏切换时它们会自动重新排布同时自动处理刘海安全区域。整个Demo画布设计分辨率是1280x720策略是FIXED_HEIGHT。3.1 Demo的层级结构和组件设计场景层级很清晰CanvasRoot空节点挂AutoFit脚本TopBarContentPanelButtonGroupCamera为什么要把自适应逻辑挂在一个空节点而不是直接挂Canvas因为Canvas节点本身带有专门的适配逻辑我们不要在它上面堆业务代码Root作为根容器往后还能继续挂各种UI管理脚本层次更干净。TopBar、ContentPanel、ButtonGroup这三个节点都用Widget做了基础对齐TopBar对齐顶部并且左右撑满ContentPanel居中并且宽高百分比ButtonGroup对齐底部。这套结构的好处是横竖屏切换时只要在AutoFit里调整它们的Widget偏移整棵节点树就跟着动了。如果某个界面实在复杂你也可以把三个子节点当作“横屏容器”和“竖屏容器”的替换目标但Demo先保持最简结构让逻辑好对照。3.2 关键实现AutoFit根节点脚本逐段讲解完整脚本就是我在2.1节给的骨架再补上两个具体方法。这里我说一下核心逻辑的推演过程。先看设计分辨率1280x720FIXED_HEIGHT策略下屏幕高度始终对应720个设计单位宽度会变。比如一块430x932的手机竖屏屏幕宽度430高度932因为高度固定对应720宽度按比例算出来是430 / 932 * 720 ≈ 332。这个数字远小于1280意味着原本横屏布局里的左右区域都会跑到屏幕外。所以方向变化时除了调整对齐方式还要把能够拉伸的UI组件改成“从屏幕边缘对齐”而不是“从中心向两边延伸”。我把ContentPanel做成一个背景块横屏时向左向右各留20%余量竖屏时改成铺满屏幕且上下各留10%。实现如下private applyUIState(isLandscape: boolean) { const contentPanel this.node.getChildByName(ContentPanel); const contentWidget contentPanel.getComponent(Widget); if (isLandscape) { contentWidget.isAlignLeft true; contentWidget.isAlignRight true; contentWidget.isAlignTop true; contentWidget.isAlignBottom true; contentWidget.left 120; contentWidget.right 120; contentWidget.top 80; contentWidget.bottom 80; } else { contentWidget.isAlignLeft true; contentWidget.isAlignRight true; contentWidget.isAlignTop true; contentWidget.isAlignBottom true; contentWidget.left 40; contentWidget.right 40; contentWidget.top 120; contentWidget.bottom 120; } contentWidget.updateAlignment(); }按钮组和标题栏同理。标题栏本来就靠上横屏时控件在左右两侧竖屏时控件收到中间区域并缩小间距按钮组横屏时对齐底部两侧竖屏时变成纵向排列。为了Demo可读性我直接在脚本里用active切了两套按钮子节点一套是横向排列一套是纵向排列这只是演示生产环境你按自己的UI结构决定用哪种方案。安全区域的处理也很重要。iPhone的刘海屏和安卓挖孔屏会让部分屏幕区域不适合显示交互元素。CocosCreator 3.8.2里可以直接用sys.getSafeAreaRect()拿到安全区域然后把它换算到设计分辨率坐标系下再给Root容器设置一个安全边距。需要注意getSafeAreaRect()返回的是屏幕像素区域要除以屏幕和设计分辨率之间的缩放比例才能用。最省事的方法是把安全区矩形的宽高直接用来约束Root节点的尺寸核心代码如下private applySafeArea() { const safeRect sys.getSafeAreaRect(); const frameSize view.getFrameSize(); const scaleX view.getVisibleSize().width / frameSize.width; const scaleY view.getVisibleSize().height / frameSize.height; const designSafeWidth safeRect.width * scaleX; const designSafeHeight safeRect.height * scaleY; const root this.node.getChildByName(Root); const rootTransform root.getComponent(UITransform); rootTransform.setContentSize(designSafeWidth, designSafeHeight); }这段逻辑拿到真实屏幕安全区后把它映射到设计分辨率下的宽高然后把Root的大小限制在安全区内。如果你的UI全都挂在Root下面并且按钮都基于Root边缘做百分比或Widget对齐那么任何刘海屏都不会挡到关键操作。3.3 编辑器预览与真机打包的验证要点在编辑器和浏览器里模拟方向切换很多项目就是一跑起来能看到效果一打包就翻车问题多半出在预览方式和浏览器壳的差异上。编辑器右上角有分辨率模拟器可以直接切到竖屏尺寸这会触发canvas-resizeAutoFit脚本能实时响应。这一步一定要做而且要用几组不同比例测比如1280x720、720x1280、1170x2532、820x1180。构建预览时我一般选择“浏览器预览”因为能看到控制台日志适配逻辑出问题方便调。调好后打包Android或iOS再用真机验证。真机验证最容易忽略的点是“折叠屏”和“横屏锁定”有些安卓手机会自动锁定方向你怎么转它都不翻转别急着改代码先在系统设置里确认自动旋转打开了。还有一点原生平台的画布尺寸更新时机和网页不一样个别机型会等动画结束后才触发resize。防御办法是在onLoad里先执行一次applyLayout和applySafeArea再延迟200毫秒执行第二次确保初始数据正确。4. 常见问题与排查技巧实录这节我把实际跑Demo时容易踩的坑都列出来并且给出排查方向。适配代码本身不长但环境差异带来的表现很多能快速定位问题比背代码重要得多。4.1 不触发resize、黑边、模糊怎么办不触发canvas-resize通常是监听挂错地方或者被释放了。如果你把监听挂在某个普通节点上节点在场景切换时被销毁监听随着丢失。解决方法是把监听放到常驻节点上或者用game.on等全局机制。另外一种可能是你用了iframe嵌入Web页面外层iframe尺寸变了但内层画布没有跟随这时需要手动调用view.resizeWithBrowserSize(true)并同时勾选项目设置里的“适配浏览器窗口”。黑边问题我反复强调过根源是适配策略用了SHOW_ALL屏幕比例不匹配时为了保证完整显示必然留边。想彻底消除黑边要么用FIXED_HEIGHT/FIXED_WIDTH把基准固定在某一维要么用NO_BORDER接受裁切。但要警惕FIXED_HEIGHT不是万能的如果你的UI左右元素真的不可裁剪屏幕太宽时还是会出界。这时候配合Widget的百分比对齐把关键内容都钉在安全范围内背景用一块可拉伸的大图。模糊问题一半原因出在图片资源本身不是2的幂尺寸另一半出在“设计分辨率和你屏幕分辨率之间不是整数倍缩放”。比如设计分辨率1280x720屏幕实际像素1920x1080缩放比例1.5引擎插值后文字会轻微发虚。解决方法是尽量让美术资源按设计分辨率整数倍输出或者使用位图字体和Label的EnableBold合理搭配。测试时也别总用相同比例屏幕换几台不同机型的真机感受最直观。4.2 刘海屏和Android显示屏凹口的适配sys.getSafeAreaRect()拿到的安全区在绝大多数iOS和安卓设备上都能正确返回但它有个边界情况安卓系统如果开启了导航栏手势安全区底部会变化而且这个变化是动态的。你在竖屏状态下得到的底部高度是32横屏时可能变成48所以必须在每次canvas-resize时都重新调用applySafeArea()不能只在方向切换时算一次。还有一类安卓机型是“超窄边框”或者“瀑布屏”系统安全区可能返回0这时候就要做兜底判断安全区宽高和屏幕尺寸一致时手动按照固定比例设置UI边距不要在UI上堆太多关键元素。我的经验是底部导航高度至少预留设备屏幕高度的3%顶部预留5%左右这样即使安全区数据缺失界面也不至于失控。4.3 我的避坑清单现象原因处理方式方向切换后UI没变化没有监听canvas-resize或监听被销毁把监听挂在常驻根节点onDestroy里解绑切横屏后按钮跑到屏幕外只做了居中没有用Widget钉边当方向变化时把按钮改为靠左/靠右对齐Widget改了但位置不动忘记updateAlignment每次设置完立即调用顶部被刘海遮挡没处理安全区用sys.getSafeAreaRect转设计分辨率后约束Root浏览器正常打包后方向不翻转原生平台方向锁定检查系统自动旋转和构建面板的屏幕方向设置用FIXED_HEIGHT后左右内容缺失策略本身就裁剪宽度方向改用百分比Widget把关键UI收进安全范围背景图做自适应拉伸预览一切正常真机图片模糊缩放比例非整数倍资源尺寸贴近设计分辨率整数倍或换用UI纹理压缩这张表是我在多个项目里沉淀出来的不是从一个Demo里现总结的。你可以把它当作一份“适配自检清单”贴在墙上。写在后面的一点个人体会适配这件事代码本身不复杂复杂的是你愿不愿意站在不同屏幕的角度去重新审视自己的UI结构。我在刚接触多分辨率时也试图用一个分辨率策略套所有游戏结果被各种黑边和裁切问题按在地上反复摩擦。后来才明白好的适配是“从设计阶段就开始布局”而不是等发布前再来打补丁。这套Demo最大的价值不是给你一段能用的脚本而是让你看清设计分辨率、Widget、安全区、方向事件这四件事是怎么串起来的。最后再分享一个小技巧在编辑器里预览时改用“自定义分辨率模式”把尺寸填成720x1280再切回1280x720来回多切几次就能在几秒钟内模拟出绝大多数玩家的真实体验。很多看着唬人的适配问题其实根本不用上真机在这一个步骤里就能暴露干净。希望这篇内容能让你少走点弯路如果Demo跑通了记得把它扩展成你自己项目的基础模板以后做新游戏会省下大把时间。