Cocos2d-x 之屏幕适配

六种适配方案

  • UNKNOWN :不作任何适配

  • FIXED_WIDTH :以宽度为基准,使宽度刚好充满屏幕,高度以下边为起点随宽度等比例缩放,如果高度不足,则上面会出现黑边,如果高度过大,则上边的内容可能会被裁剪掉

  • FIXED_HEIGHT :以高度为基准,高度刚好充满屏幕,宽度以左边为起点随高度等比例缩放,如果宽度不足,则右边会出现黑边,如果宽度过大,则右边的内容可能会被裁剪掉

  • SHOW_ALL :以最小边为基准,最小边刚好充满屏幕,另一边以中心为起点随最小边等比例缩放,因此另一边可能由于无法充满屏幕而有黑边(最小边是指比较接近屏幕的边,比如一个分辨率为 800x600,而屏幕分辨率为 1920x1080,这时高度 600 只需要乘以 1.8 倍即可适应到屏幕,而宽度 800 必须乘以 2.4 倍才能适应到屏幕,因此高度看作最小边)

  • NO_BORDER :这种方式和 SHOW_ALL 刚好相反,它以最大边为基准,使最大边刚好充满屏幕,而另一边则可能会超出屏幕,超出的部分会被裁剪掉,裁剪的方式是以中心为起点,两边都裁剪。

这六种方案大概可以划分为两类

  • 第一类是 UNKNOWNEXACT_FIT,这两种是最简单暴力的方式。一种是不作任何适配,什么事都不干,这种方案可能会在右边和上边出现黑边,也可能在右边和上边裁剪内容。另一种是强行充满整个屏幕,这种方案不会出现任何黑边或裁剪,但会对图像进行拉伸。因此这两种方案都是不可取的。

  • 第二类是剩下的四种方案,这四种方案都是以一条边为基准,使这条边刚好适配屏幕,而另一条边则等比例进行缩放,缩放的结果是可能出现黑边或裁剪。FIXED_WIDTHFIXED_HEIGHT 分别指定了以宽度为基准和以高度为基准;剩下的两种则是由引擎自动判断以哪条条为基准。SHOW_ALL 的目标是不要发生裁剪,因此以最小边为基准,而另一边则可能出现黑边;NO_BORDER 的目标是不要出现黑边,因此以最大边为基准,而另一边则可能发生裁剪。

//ResolutionPolicy::UNKOWN 默认值,即不任任何适配
glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::UNKNOWN);
//ResolutionPolicy::EXACT_FAT 对图像进行拉伸以填满屏幕
glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::EXACT_FIT);
//ResolutionPolicy::SHOW_ALL 以最小边为缩放因子,可能会有黑边
glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::SHOW_ALL);
//ResolutionPolicy::NO_BORDER 以最大边为缩放因子,可能会裁剪
glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);
//ResolutionPolicy::FXIED_HEIGHT 保持高度不变
glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::FIXED_HEIGHT);
//ResolutionPolicy::FIXED_WIDTH 保持宽度不变
glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::FIXED_WIDTH);

该选择哪种方案?
很显然第一类那两种方案是不可取的,太过简单暴力。剩下的四种中,FIXED_WIDTHFIXED_HEIGHT 也不可取,因为这两种定死了以哪条边为基准,但不同的分辨率很可能会出问题;比如,我们以宽度为基准,然后在测试的分辨率上高度会发生裁剪,我们解决了裁剪的问题,结果发现到了另一个分辨率上出现黑边了。
因此,最可取的方案应该是 SHOW_ALLNO_BORDER,这两种是刚好相反的,一种避免裁剪,一种消除黑边;无论选择了哪一种,都无法避开另一个问题,选择了 SHOW_ALL 就必须面对黑边的问题,选择了 NO_BORDER 就必须面对裁剪的问题。至于这两种方案哪种好,我们得先看看 cocos2d-x 中的三个大小。

三个分辨率

WinSize 设计分辨率

WinSize 也叫 DesignSize,是我们在设计 UI 时参考的大小,无论实际屏幕分辨率是多少,也无论是使用哪种适配方案,UI 在代码中设置的位置都是参考 WinSize。在 cocostudio 中设计的分辨率就是 WinSize,这个分辨率一般是事先写好的,因为各个 UI 的位置就是以它为参考。在 C++ 代码中设置其大小的代码

director->getOpenGLView()->setDesignResolutionSize(960, 640, ResolutionPolicy::SHOW_ALL);

在 lua 中使用配置文件 config.lua 定义其大小

CC_DESIGN_RESOLUTION = {
    width = 800,
    height = 600,
    autoscale = "NO_BORDER",
    callback = function(framesize)
        local ratio = framesize.width / framesize.height
        if ratio <= 1.34 then
            -- iPad 768*1024(1536*2048) is 4:3 screen
            return {autoscale = "SHOW_ALL"}
        end
    end
}

获取设计分辨率,C++ 代码

auto winSize = Director::getInstance()->getWinSize();

lua 代码

local winSize = cc.Director:getInstance():getWinSize()
FrameSize 设备分辨率

FrameSize 是指游戏运行时实际设备的分辨率,不同的手机,平板有不同的分辨率,因此这个分辨率是运行时才知道的。在 C++ 中获取设备分辨率

auto glview = director->getOpenGLView();
auto frameSize = glview->getFrameSize();

在 lua 中获取

local glView = cc.Director:getInstance():getOpenGLView()
local frameSize = glView:getFrameSize()
VisibleSize 可见分辨率

VisibleSize 就是实际显示的分辨率,它表示在设计分辨率下,在屏幕中的可见区域大小。这个区域只会小于或等于设计分辨率,它的值是根据设计分辨率的大小和设备分辨率的比例得到的。另外,还有一个 VisibleOrigin,表示可见分辨率与设计分辨率的起点偏移。

我们以一个例子来探讨下几种适配方案的处理方法及可见分辨率是多少,假如一个手机的分辨率是 1920x1080,而设计分辨率只有 800x600。

  • 第一种方案,UNKNOWN,将 800x600 的游戏画面不作处理地渲染到 1920x1080 的屏幕上,画面就会蜷缩在左下角,右边和上边会有一大片黑边。因为不作任何适配,所以可见分辨率为实际的设备分辨率。

UNKNOWN

  • 第二种方案,EXACT_FIT,强行把 800x600 的画面映射到 1920x1080 的屏幕上,出面出现了拉伸但刚好完全适配上屏幕上。因为设计分辨率渲染到屏幕时不作处理,而是对画面进行拉伸,因此可见分辨率和设计分辨率是一样的。

EXACT_FIT

  • 第三种方案,SHOW_ALL,把 800x600 的画面渲染到 1920x1080 的画面时,以最小边 600 为基准,根据设计分辨率的比例得到宽度为 1440,因此设计分辨率投映到屏幕上之后的分辨率是 1440x1080,这个大小在屏幕分辨率 1920x1080 范围之内,因此渲染时只取了 1440x1080 的画面,可以认为是对屏幕分辨率进行了裁剪。因为设计分辨率不作处理,而是将画面渲染到屏幕的一部分,因此可见分辨率还是和设计分辨率一样。

SHOW_ALL

  • 第四种方案,NO_BORDER,把 800x600 的画面渲染到 1920x1080 的画面时,以比最大边 800 为基准,根据设计分辨率的比例得到高度 1440,因此设计分辨率投映到屏幕之后的分辨率是 1920x1440,这个大小超出了屏幕分辨率 1920x1080,超出的部分将被裁剪;设计分辨率裁剪之后的大小为 800x450,这个分辨率就是可见分辨率。因为设计分辨率渲染到屏幕之后超出屏幕大小,因此可见分辨率比设计分辨率小,也意味着游戏画面要进行裁剪。

NO_BORDER

使用 NO_BORDER

现在回到前面的问题,该使用哪种方案呢?很明显应该选择 NO_BORDER,因为无论是拉伸还是有黑边都是无法接受的结果;而 NO_BORDER 的问题是会发现画面裁剪,这个问题我们可以想办法解决。
解决裁剪问题的方法就是使用比设计尺寸大的背景图,预留一些大小给游戏裁剪。而对于控件出界的问题,我们可以使用动态的分辨率大小来计算,而不是基于静态的分辨率大小。WinSize 是静态的分辨率,它的大小不会随着不同屏幕分辨率而改变,因此 UI 的位置如果基于 WinSize,那位置就可能超出屏幕边界了。而 VisibleSize 是游戏运行时根据设计分辨率和设备分辨率得到的,它的值是动态的,它表示适配屏幕之后的设计分辨率。因此,UI 的位置如果基于 VisibleSize 和 VisibleOrigin 来设计,则不会出现超出屏幕而被裁剪的问题。
虽然可以使用 VisibleSize 和 VisibleOrigin 来计算每个 UI 的位置或者大小,但这种方式并不是一种高效的方式,每个 UI 的位置都要动态来计算,既麻烦又耗效率。我们逆向地想一下这问题,WinSize 是静态的,而 VisibleSize 是动态的,所以 UI 基于 WinSize 来确定位置就会出问题;那如果 WinSize 是动态的呢,如果保证在不同的设备分辨率上 WinSize 都不会超出屏幕,这样不就可以解决问题了吗。
如果把 WinSize 设计成动态的,那它的值怎么确定?我们要引入一个新的分辨率 LittleWinSize,然后 WinSize 由 FrameSize 和 LittleWinSize 计算得到。像上面的例子,我们固定 LittleWinSize 为 800x600,然后动态计算 WinsSize,这样在不同的设备上最终得到的 WinSize 和 VisibleSize 虽然不一样(但都大于 800x600),但 LittleWinSize 都是 800x600,而我们把所有 UI 控制在 800x600 的范围之内,这样就不会有 UI 出现在屏幕之外,因为 800x600 这个区域一定在屏幕内。但前提是得把所有 UI 放在中心 800x600 这个区域内,所以 UI 的位置设置必须以中心为参考点,左右距离不能超过 400,上下距离不能超过 300。
接下来就看看怎么计算 WinSize 的大小。先看看 NO_BORDER 方案是怎么计算 VisibleSize 的。首先,确定以哪条边为基准,NO_BORDER 是以最大边为基准;然后基准边的大小不用计算,另一个边根据根据基准边的缩放比例和设备分辨率得到。

local scale_x = frame_size.width / win_size.width
local scale_y = frame_size.height / win_size.height
-- 以最大边为基准
if scale_x > scale_y then
    -- 以宽度为基准,计算高度
    visible_size.width = win_size.width
    visible_size.height = visible_size.width *(frame_size.height / frame_size.width)
else
    -- 以高度为基准,计算宽度
    visible_size.height = win_size.height
    visible_size.width = visible_size.height *(frame_size.width / frame_size.height)
end

之前例子的计算过程如下

scale_x = 1920/800 = 2.4
scale_y = 1080/600 = 1.8
visible_sie.width = 800
visible_size.height = 800 * (1080/1920) = 450

计算 WinSize 的方法和 NO_BORDER 计算 VisibleSize 的方法类似,首先确定以哪条边为基准,同样是以最大边为基准;然后以基准边的缩放比例计算 WinSize。

local scale_x = frame_size.width / new_win_size.width
local scale_y = frame_size.height / new_win_size.height
-- 以最大边为基准
if scale_x > scale_y then
    win_size.width = new_win_size.width * scale_x
    win_size.height = new_win_size.height * scale_x
else
    win_size.width = new_win_size.width * scale_y
    win_size.height = new_win_size.height * scale_y
end

之前例子的计算过程如下

scale_x = 1920/800 = 2.4
scale_y = 1080/600 = 1.8
win_size.width = 800 * 2.4 = 1920
win_size.height = 600 * 2.4 = 1660

这样计算之后 WinSize 的值是在 NewWinSize 的基础上乘以一个比较大的倍数,其实并不需要扩大那么多倍,只需要使得 WinSize 刚好能覆盖屏幕即可,所以不要直接乘以 scale_x,而是乘以 scale_x/scale_y。

local scale_x = frame_size.width / new_win_size.width
local scale_y = frame_size.height / new_win_size.height
local scale
-- 以最大边为基准
if scale_x > scale_y then
    scale = scale_x / scale_y
else
    scale = scale_y / scale_x
end
win_size.width = new_win_size.width * scale
win_size.height = new_win_size.height * scale

整个例子的计算过程

scale_x = 1920/800 = 2.4
scale_y = 1080/600 = 1.8
scale = 2.4/1.8 = 1.3
win_size.width = 800 * 1.3 = 1064
win_size.height = 600 * 1.3 = 798

visible_sie.width = 1064
visible_size.height = 1064 * (1080/1920) = 599

解决方案

一个实例,植物大战僵尸游戏的游戏场景
植物大战僵尸

红色框为 LittleWinSize,在这个框内的植物、僵尸、子弹等实体都会正常显示;小车在边界处,也能显示一部分或者全部。而蓝色框为背景图大小,WinSize 的大小为红色框与蓝色框之间,保证既不会出现黑边也不会发生错误的裁剪。事实上裁剪是避免不了的,我们只是保证一些不能裁剪的不会被裁剪,但像背景图则必须预留裁剪空间;像图中僵尸产生和僵尸消失,以及僵尸越过失败界线,在不同分辨率下显示的结果可能不同,有可能我们看不到僵尸在何处产生,也看不到僵尸在何处消亡,以及僵尸到达哪里时游戏失败。但这些都不重要,因为游戏的需求并不关心这些,我们只要保证草地能够正确地适配到屏幕上即可。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值