Cocos 屏幕适配 Widget

2021-10-26  本文已影响0人  合肥黑
一、适配方式

参考
多分辨率适配方案

1.设计宽高比更大
image.png

如上图,设计分辨率800/480=1.667,屏幕分辨率1024/768=1.333。此时建议适配高度,宽度两侧会裁掉一部分,这样不会出现黑边。也就是将场景图像放大到 768/480 = 1.6 倍。

2.设计宽高比更小
image.png

和上面类似,如果屏幕分辨率1920/960=2。使用适配宽度,将设计分辨率的宽度自动撑满屏幕宽度,也就是将场景放大 1920/800 = 2.4 倍。

3.不管屏幕宽高比如何,完整显示设计分辨率中的所有内容,允许出现黑边
image.png

同时开启 Canvas 组件中的 适配高度 和 适配宽度,这时场景图像的缩放比例是按照屏幕分辨率中较小的一维来计算的,在下图的例子中,由于屏幕宽高比小于 1,就会以宽度为准计算缩放倍率,即 640/800 = 0.8 倍。

在这种显示模式下,屏幕上可能会出现黑边,或超出设计分辨率的场景图像(穿帮)。尽管一般情况下开发者会尽量避免黑边,但如果需要确保设计分辨率范围的所有内容都显示在屏幕上,也可以采用这种模式。

4.自动选择适配宽度或适配高度

如果对于屏幕周围可能被剪裁的内容没有严格要求,可以同时不勾选 Canvas 组件的 Fit Width 和 Fit Height,这样子相当于开启了 NO_BORDER 模式,此时无论屏幕宽高比多少都不会产生黑边。也就是说,当设计分辨率宽高比大于屏幕分辨率时,会适配高度(上面第一张图);设计分辨率宽高比小于屏幕分辨率时,会适配宽度(上面第二张图)。

5.ExactFit

在 Cocos 引擎中,也存在称为 ExactFit 的适配模式,这种模式没有黑边,也不会裁剪设计分辨率范围内的图像。但是代价是场景图像的 x 和 y 方向的缩放倍率不同,图像会产生形变拉伸。

Canvas 组件不提供分别缩放 x 和 y 轴缩放率,会使图像变形拉伸的适配模式。

转自creator支持微信小游戏分包加载功能的疑问

就象很多人都反应需要EXACTFIT 功能,而你们就非说这个功能没有必要一样,就楞给去掉了一样,然后还大言不惭的说不建议这种用法

二、Canvas上设置适配

新建场景时,会自动在场景根节点上添加一个包含 Canvas 组件的节点。在 Canvas 组件上就可以设置上文中提到的选项:

建议将 Canvas 节点作为所有需要适配 设计分辨率 的渲染节点的根节点。因为虽然 场景中的所有节点都能享受到基于设计分辨率的智能缩放,但是 Canvas 节点本身还具备以下特性:

1.尺寸(Size)

在编辑场景时,Canvas 节点的尺寸会保持和设计分辨率一致,不能手动更改。

在游戏运行时,在无黑边的模式中,节点的尺寸会和屏幕分辨率保持一致。在有黑边的模式中,节点的尺寸会保持设计分辨率不变。

也就是说,Canvas 的尺寸就等于屏幕可见区域,我们可以设置子 UI 元素自动对齐到 Canvas 的边框,保证 UI 元素都能在屏幕可见区域正确分布。

2.位置(Position)

位置会保持在 (Width / 2, Height / 2),也就是和设计分辨率相同大小的屏幕中心。

3.锚点(Anchor)

锚点默认为 (0.5, 0.5),由于 Canvas 会保持在屏幕中心位置,因此 Canvas 的子节点会以屏幕中心作为坐标系原点。这一点和 Cocos2d-x 引擎中的习惯不同,请格外注意。

参考使用cocos creator开发微信小游戏(二)

横屏下,建议使用 Fit Width ,竖屏下,建议使用 Fit Height, 有些UI需要添加 Widget对齐挂件,以显示在相对于屏幕左,顶,右,底的位置

三、Widget

参考官方文档
对齐策略
Widget 组件参考

1.同时勾选反向的对齐

当同时勾选相反的两个方向的对齐开关时,Widget 就获得了根据对齐需要修改节点尺寸(Size)的能力,上图中我们勾选了左右两个方向并设置了边距,Widget 就会根据父节点的宽度来动态设置节点的 Width 属性,表现出来就是不管在多宽的屏幕上,我们的面板距离屏幕左右两边的距离永远保持 100px。


image.png

下面演示一下,在场景中放置两个矩形 Sprite,大的作为对话框背景,小的作为对话框上的按钮。按钮节点作为对话框的子节点,并且按钮设置成 Sliced 模式以便展示拉伸效果。

注:九宫格sclice模式,可以参考制作可任意拉伸的 UI 图像

image.png
2.制作和屏幕大小保持一致的节点
image.png

可以在运行时时刻保持该节点和 Canvas 节点的尺寸完全一致,也就是和屏幕大小一致。经过这样设置的节点,其子节点也可以使用同样的设置来传递屏幕实际尺寸。

注意,由于 Canvas 节点本身就有保持和屏幕大小一致的功能,因此不需要在 Canvas 上添加 Widget 组件。

3.设置百分比对齐距离

Widget 组件上开启某个方向的对齐之后,除了指定以像素为单位的边距以外,我们还可以输入百分比数值,这样 Widget 会以父节点相应轴向的宽度或高度乘以输入的百分比,计算出实际的边距值。

4.优化策略

Widget 组件一般用于场景在目标设备上初始化时定位每个元素的位置,但一旦场景初始化完毕,很多时候我们就不需要 Widget 组件再进行对齐了。

如果在 属性检查器 中将 Widget 组件的 Align Mode 设置为 ON_WINDOW_RESIZE 或者 ONCE,且在组件初始化时执行过一次对齐定位,引擎就会自动将 Widget 组件的 enabled 属性设为 false 来关闭之后每帧自动更新,避免重复定位。如果需要在运行时实时定位,你需要将 Align Mode 设置为 ALWAYS,或者在运行时需要进行每帧更新对齐时手动遍历需要对齐的 Widget,并将它们的 enabled 属性设为 true。

对于有很多 UI 元素的场景,建议保持 Align Mode 默认设置为 ON_WINDOW_RESIZE,可以大幅提高场景运行性能。

5.对齐组件对节点位置、尺寸的限制

如果 Align Mode 属性设为 ALWAYS 时,会在运行时每帧都按照设置的对齐策略进行对齐,组件所在节点的位置(position)和尺寸(width,height)属性可能会被限制,不能通过 API 或动画系统自由修改。这是因为通过 Widget 对齐是在每帧的最后阶段进行处理的,因此对 Widget 组件中已经设置了对齐的相关属性进行设置,最后都会被 Widget 组件本身的更新所重置。

如果需要同时满足对齐策略和可以在运行时改变位置和尺寸的需要,可以通过以下两种方式实现:

6.updateAlignment

Widget 组件会自动调整当前节点的坐标和宽高,不过目前调整后的结果要到下一帧才能在脚本里获取到,除非你先手动调用updateAlignment

https://docs.cocos.com/creator/api/zh/classes/Widget.html#updatealignment
立刻执行 widget 对齐操作。这个接口一般不需要手工调用。 只有当你需要在当前帧结束前获得 widget 对齐后的最新结果时才需要手动调用这个方法。

widget.top = 10;       // change top margin
cc.log(widget.node.y); // not yet changed
widget.updateAlignment();
cc.log(widget.node.y); // changed
7.手动刷新所有子节点

参考加了物理组建的node不受widget控制

        let widgets = this.node.getComponentsInChildren(cc.Widget);
        widgets.forEach(widget => {
            widget.updateAlignment();
        })
8.widget的prefab做缩放动画

参考
关于widget的bug
有widget的节点无法执行缩放动画
cc.Widget的对齐策略不合理

带有widget组件的B节点,在初始化时,其父节点A不能做有关scale的动作。导致widget失效,并且B节点位置发生错乱!
只要父节点和子节点都有widget,那么父节点就不能执行scale动画,否则子节点位置就错乱了。

如下代码,动画完成时刷新widget。运行时,能明显看到UI错位,在动画结束后才正确归位。没办法了,将所有widget的Align Mode 设置 always才得到解决

        this.fadeInTween = cc.tween(this.node)
            .parallel(
                cc.tween().to(this.inDuration, { opacity: 255 }, { easing: "backInOut" }),
                cc.tween().to(this.inDuration, { scale: 1 }, { easing: "backInOut" })
            )
            .call(() => {
                cc.director.getScheduler().resumeTarget(this.node);
                let widgets = this.node.getComponentsInChildren(cc.Widget);
                widgets.forEach(widget => {
                    widget.updateAlignment();
                });
            });
四、麻烦官方人员详细解释一下N个Size

cc.director.getVisibleSize();//获取运行场景的可见大小
cc.director.getWinSize();//获取视图的大小,以点为单位
cc.director.getWinSizeInPixels();//获取视图大小,以像素为单位
cc.view.getFrameSize();//Returns the frame size of the view
cc.view.getViewPortRect();//Returns the view port rectangle.
cc.view.getVisibleSize();//Returns the visible area size of the view port.
cc.view.getVisibleSizeInPixel();//Returns the visible area size of the view port.
cc.view.getDesignResolutionSize();//Returns the designed size for the view. Default resolution size is the same

虽然这篇贴子已完结,但对于这么多size的使用还是比较懵懂,后来研究了一番后,把自己的理解补充一下,以后还可以查阅。

先定义一下项目场景:

游戏的设计分辨率:1334 x 750 iPhone 6的屏幕大小 横屏 适配方案:FIT_HEIGHT

再看看几个函数定义:

//视图中canvas尺寸
console.log(“canvas size:”, cc.view.getCanvasSize());
//视图中窗口可见区域尺寸
console.log(“visible Size:”, cc.view.getVisibleSize());
//设计分辨率
console.log(“DesignResolutionSize Size:”, cc.view.getDesignResolutionSize());
console.log("frame size", cc.view.getFrameSize());
在小米8 上的值:
canvas size : 2249 x 1078
visible size : 1564.703 x 750
DesignResolutionSize size: 1334 x 750
frame size: 818 x 392

这些值差异都很大,它们分别都代表什么大小呢?

1.canvas size : 2249 x 1078

手机厂商用来营销吹牛啤的大小,设备物理像素


image.png
2.visible size : 1564.703 x 750

visible size: 官方意思是可以在游戏中使用的值,实际上就是游戏中的屏幕大小(这里先忽略安全区域的计算)。那么这个值是代表有多大呢?又是怎么出来的?这个是最关键的尺寸概念!

首先,我们在游戏中经常用到的Vec2 Size Rect各种与距离、长度相关的值其实不是像素值,是一个逻辑单位。但逻辑单位也应该有对应实际长度呀,说到这里就要把设计分辨率和适配方案结合一起说了。

一开始接触设计分辨率时也很懵逼,为啥叫分辨率?1个分辨率是几个像素,还是几厘米?都没找到直接的答案。但做项目都知道,游戏中显示一张图不可能为各种屏幕做多个不同尺寸版本。只能统一一个尺寸加上适配方案,让一张图能在多种不同大小的屏幕上看起来都合适,实际做不到100%精确显示的,合适就可以了。

上面提到适配方案:FIT_HEIGHT,也就是说游戏是在任何屏幕大小下均以设计分辨率的高(750)作屏幕的高度,那么屏幕宽度就是1334了吗?不是,上面已经写了1564.703,既然高已定为750,宽就要按比例缩放,这个数字不难推导:屏幕像素 2249 / 1078 x 750 = 1564.703。

假设在游戏中做一个物件从屏幕坐标最高位(0)走到最低位一个点一个点地走就要走750次,同样从最左(0)走到最右就要走1564.703次。(这里不讨论世界坐标和本地坐标,只谈屏幕坐标),所以,cc.view.getVisibleSize是 cocos的屏幕尺寸, 这个尺寸是由canvas size(设备物理像素)通过设计分辨率结合适配方案所得出来的。注意了,同一个设计分辨率,不同的适配方案就会有不同的cc.view.getVisibleSize大小!

3.frame size: 818 x 392

上面强调了cc.view.getVisibleSize是屏幕尺寸, frame size又是什么鬼?很多资料都说是屏幕尺寸。是的,确实是屏幕尺寸,但不是游戏里的屏幕尺寸,而是浏览器里或者JS容器的屏幕尺寸。这个数字也很奇葩818 x 392,怎么来的?

这个问题是由于做H5网页也遇到了和我们做游戏的相同问题,做网页的人在CSS只会指定一个版本的样式大小,不可能为各种不同规格的屏幕指定不同版本的样式。那么就有了逻辑像素的概念,818 x 392就是逻辑像素的大小,网页使用逻辑像素定义CSS就只要做一套样式就行了。

浏览器有一个参数cc.view.getDevicePixelRatio(设备或浏览器像素比例),小米8的值:2.75, 算算818 x 2.75和392 x 2.75就一切真相了。

注:818 x 2.75=2249.5 , 392 x 2.75=1078

所以,frame size 其实和游戏里的坐标没关系,因为我们游戏里的坐标运算都是直接gl,都用不上浏览器的换算的。

4.cc.view.getScaleX();cc.view.getScaleY()

另外 cc.view.getScaleX();cc.view.getScaleY();这两个是适配的比例值要注意一下。比如FIT_HEIGHT方案就是 1078/750 = 1.4373333
这个API的应用,可以参考【福利】实现一个汉字临摹

5.framesize后续

事情还真没那么简单,实际项目中真和frame size扯上关系了,字节平台bannerAd广告接入的参数style.left和top就是使用浏览器逻辑像素单位!

比如在游戏屏幕里指定一个NODE的位置用来显示bannerAd,结果要
node.getPosition -> node.parent.converttoWorldSpaceAR -> cc.Camera.getWorldToScreenPoint -> 乘以 cc.view.getScaleX()得到设备物理像素 -> 除以cc.view.getDevicePixelRatio -> 浏览器逻辑像素值 最终结果

6.结合另一个回复

谢邀!(好吧我是自己跑过来的)
刚好今天也折腾了下这个问题,正好整理分享一下,如有错误欢迎指出。。。

cocos 里边是有好多个 size、rect,搞得我也一脸懵逼,大概梳理一下就是这几个:
winSize (inPixels), visibleSize (inPixels), designResolusionSize 和 frameSize

下面说明下这几个东西的关系:
winSize = winSizeInPixels = visibleSIze, 实际开发的计算中,大多数情况下就用这一个 visibleSize !!!
visibleSizeInPixels 是 visibleSize 的基础上乘以各种适配策略下的缩放比例,是的就是你的实际截图测量的大小
designResolusionSize 是 canvas 组件下设置的设计分辨率
frameSize 是 cc.game.container 的 width height

除了各种 size 还有各种 rect,好像也不多,就两个:
viewportRect, visibleRect

visibleRect 是实际开发的计算中用到的
viewportRect 是 gl 下的 viewport 概念, 只不过他的 x y 值是相对于 游戏坐标系下进行计算的

======| 分割线 |===========================

总结一下,其实大多数情况下,只要关心 visibleSize 就好了~

7.部分源码

cocos creator屏幕适配的一些知识点(2.0之后的版本)
作者列出很多源码,并且能看到cc.winSize和cc.view.getVisibleSize()的返回值是一样的,二者可以通用

8.更改scale

比如当前是适配高度,宽度cc.winSize.width会随着不同的机型变化。如果想整体改变一个node的scale,可以这么做:

this.scrollList.node.scale = cc.winSize.width / cc.view.getDesignResolutionSize().width;

参考求教一个分辨率适配的问题

去掉widget,保留原状就好了。如果你一定要子节点跟父节点一起拉伸,那还是建议去掉widget,改成动态修改父节点的scaleX,scaleY,这样子节点也会受父节点的scale一起变化的。

五、其它

CocosCreator 跨平台全屏适配方案整理

麒麟子Cocos Creator 3D研究笔记二:麒麟子惯用框架分享https://blog.csdn.net/qq_36720848/article/details/107751671

麒麟子Cocos Creator实用技巧九:棋牌UI全分辨率适配方案https://blog.csdn.net/qq_36720848/article/details/89742451

多分辨適配正確的設置方式 交流一下

上一篇 下一篇

猜你喜欢

热点阅读