cocos 屏幕适配源码分析及VisibleSize,VisibleOrigin

其实这个话题应该是从第一天接触cocos就会碰到的问题,我始终没能完全理解那些文章的意思,只是大概知道是怎么回事,仅此而已,智商捉急呀!!今天也是花了很长的时间去理解,现在总算有点眉目了,就把它记录下来,以后可以常回顾一下。

不废话了,进入正题。所谓的屏幕适配到底需要我们完成什么样的功能呢?这才是我们研究这个问题需要解决的东西,看了很多文章写屏幕适配,大神们总是在侃侃而谈,殊不知我们这些学渣理解能力确实有问题,所以经常一篇文章读下来,确实能理解里面讲的是什么,但是为什么要解决这个问题,为什么要这样解决,就呵呵啦。。我的理解是:不管在什么手机上都可以保证UI完全显示出来。接下来就来探讨一下这个问题。

建议提前参考Cocos2d-x屏幕适配之Sprite绘制原理 这篇文章。

前言:设计分辨率是截面区域,视口是图像映射到的矩形区域,屏幕分辨率是真正显示的区域大小。

case Projection::_3D:
        {
            float zeye = this->getZEye();

            Mat4 matrixPerspective, matrixLookup;

            loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);

#if CC_TARGET_PLATFORM == CC_PLATFORM_WP8
            //if needed, we need to add a rotation for Landscape orientations on Windows Phone 8 since it is always in Portrait Mode
            GLView* view = getOpenGLView();
            if(getOpenGLView() != nullptr)
            {
                multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, getOpenGLView()->getOrientationMatrix());
            }
#endif
            // issue #1334
            Mat4::createPerspective(60, (GLfloat)size.width/size.height, 10, zeye+size.height/2, &matrixPerspective);

            multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, matrixPerspective);

            Vec3 eye(size.width/2, size.height/2, zeye), center(size.width/2, size.height/2, 0.0f), up(0.0f, 1.0f, 0.0f);
            Mat4::createLookAt(eye, center, up, &matrixLookup);
            multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, matrixLookup);

            loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
            break;
        }

可以看出投影之后的截面区域就是:设计分辨率(size)。这个区域经过视口变换映射到视口区域:

void GLViewImpl::setViewPortInPoints(float x , float y , float w , float h)
{
    glViewport((GLint)(x * _scaleX * _retinaFactor * _frameZoomFactor + _viewPortRect.origin.x * _retinaFactor * _frameZoomFactor),
               (GLint)(y * _scaleY * _retinaFactor  * _frameZoomFactor + _viewPortRect.origin.y * _retinaFactor * _frameZoomFactor),
               (GLsizei)(w * _scaleX * _retinaFactor * _frameZoomFactor),
               (GLsizei)(h * _scaleY * _retinaFactor * _frameZoomFactor));
}

如果视口区域比屏幕小,那么整个设计分辨率下的东西便能完全显示到屏幕上;如果比屏幕大,那么设计分辨率下的一部分才能完全显示在屏幕上,计算出这个区域就可以了。

这就是cocos保证全屏显示的两种方法。下面的几种策略模式就采用了这两种方式。

cocos2d 引入了 屏幕适配策略 一共一有6种策略:EXACT_FIT,NO_BORDER,SHOW_ALL,FIXED_HEIGHT,FIXED_WIDTH,UNKNOWN。其实最后一种并不算什么策略,而是我们未指定其它策略时默认的情况。下面我们就分别来讨论这些策略:

cocos采用的

1:UNKNOWN模式

拿这个来引出屏幕适配的问题吧,假如我们游戏运行在屏幕分辨率为480X320的手机上,我们要设置一个按钮的位置在手机的右上方,这个好办,button.y=320-button.width/2,button.x = 480-button.height/2;就是这么简单,那么问题来了,现在假如我又有一部480X280的手机,那么这个按钮就跑到外面去了,所以屏幕适配是个迫切需要解决的问题。

2:EXACT_FIT模式 采用的是第一种方式

从这里开始我们引入designSize,设计分辨率,这是个非常重要的概念。这个是和手机分辨率没有任何关系的,纯粹的是程序员定义的,可以理解为在此分辨率下设计的游戏。在该分辨率下的坐标经过各种投影变换映射到视口中。

EXACT_FIT是如何去根据设计分辨率来使得全屏显示呢?

 _scaleX = (float)_screenSize.width / _designResolutionSize.width;
 _scaleY = (float)_screenSize.height / _designResolutionSize.height; 
if (_resolutionPolicy == ResolutionPolicy::NO_BORDER)
        {
            _scaleX = _scaleY = MAX(_scaleX, _scaleY);
        }
        
        else if (_resolutionPolicy == ResolutionPolicy::SHOW_ALL)
        {
            _scaleX = _scaleY = MIN(_scaleX, _scaleY);
        }
        
        else if ( _resolutionPolicy == ResolutionPolicy::FIXED_HEIGHT) {
            _scaleX = _scaleY;
            _designResolutionSize.width = ceilf(_screenSize.width/_scaleX);
        }
        
        else if ( _resolutionPolicy == ResolutionPolicy::FIXED_WIDTH) {
            _scaleY = _scaleX;
            _designResolutionSize.height = ceilf(_screenSize.height/_scaleY);
        }
void GLViewImpl::setViewPortInPoints(float x , float y , float w , float h)
{
    glViewport((GLint)(x * _scaleX * _retinaFactor * _frameZoomFactor + _viewPortRect.origin.x * _retinaFactor * _frameZoomFactor),
               (GLint)(y * _scaleY * _retinaFactor  * _frameZoomFactor + _viewPortRect.origin.y * _retinaFactor * _frameZoomFactor),
               (GLsizei)(w * _scaleX * _retinaFactor * _frameZoomFactor),
               (GLsizei)(h * _scaleY * _retinaFactor * _frameZoomFactor));
}

保持设计分辨率不变,视口就是屏幕分辨率,所以显示效果就是将设计分辨率拉伸到屏幕分辨率,整个投影过去,很明显资源的宽高比不能得到保持。所以此种模式理论上是OK的,但是并不建议采用这种方式,没有用户会接受这样的体验感。显示区域为:(0,0)--->(designSize.width,designSize.height)。

3:FIXED_HEIGHT,FIXED_WIDTH模式 采用的是第一种方式

这两种放在一起讲,原因是它们很类似,会改变程序员最初设置的设计分辨率。还是分析下它的实现原理吧。

新的设计分辨率A和屏幕分辨率B的宽高比是一样的,具体A和B之间的缩放比例取决于是FIXED_HEIGHT还是FIXED_WIDTH。

此时的视口和B是一样的,经过视口变换后,显示出来的效果就相当于缩放了整个显示区域。所以在A分辨率下能正常显示的肯定能在B分辨率下显示。显示区域是:(0,0)--->(designSize.width,designSize.height)。

4:SHOW_ALL模式 采用的是第一种方式

保持设计分辨率不变,只是改变视口的大小。

 _scaleX = _scaleY = MIN(_scaleX, _scaleY);
void GLViewImpl::setViewPortInPoints(float x , float y , float w , float h)
{
    glViewport((GLint)(x * _scaleX * _retinaFactor * _frameZoomFactor + _viewPortRect.origin.x * _retinaFactor * _frameZoomFactor),
               (GLint)(y * _scaleY * _retinaFactor  * _frameZoomFactor + _viewPortRect.origin.y * _retinaFactor * _frameZoomFactor),
               (GLsizei)(w * _scaleX * _retinaFactor * _frameZoomFactor),
               (GLsizei)(h * _scaleY * _retinaFactor * _frameZoomFactor));
}

所以视口和设计分别率的宽高比是一样的。但是肯定比屏幕分辨率小,因为_scale取得是较小的那个比值,所以此模式下会有黑边存在,除非你有个很大的背景在,拿也无可厚非。此时在设计分辨率下能正常显示的肯定能在屏幕上显示出来。显示区域是:(0,0)--->(desingSize,width,desingSize.height)。

5:NO_BORDER模式 采用的是第二种方式

保持设计分辨率不变,改变视口的大小。

 _scaleX = _scaleY = MAX(_scaleX, _scaleY);
void GLViewImpl::setViewPortInPoints(float x , float y , float w , float h)
{
    glViewport((GLint)(x * _scaleX * _retinaFactor * _frameZoomFactor + _viewPortRect.origin.x * _retinaFactor * _frameZoomFactor),
               (GLint)(y * _scaleY * _retinaFactor  * _frameZoomFactor + _viewPortRect.origin.y * _retinaFactor * _frameZoomFactor),
               (GLsizei)(w * _scaleX * _retinaFactor * _frameZoomFactor),
               (GLsizei)(h * _scaleY * _retinaFactor * _frameZoomFactor));
}

视口和设计分辨率的宽高比一致,但是会比屏幕分辨率大,此模式下有些东西会看不到,在此设计分辨率下的能正常显示的UI并不能保证在屏幕上完全显示出来,会裁剪掉一部分,显示区域不再是:(0,0)--->(designsize.width,desingSize.height),而是:(origin.x,origin,y)----->(origin.x+visibleSize.width,origin.y+visibleSize.heigth),很容易发现这就是我们经常用来设置UI坐标的东西。下面看看它们是怎么来的。

Size GLViewProtocol::getVisibleSize() const
{
    if (_resolutionPolicy == ResolutionPolicy::NO_BORDER)
    {
        return Size(_screenSize.width/_scaleX, _screenSize.height/_scaleY);
    }
    else
    {
        return _designResolutionSize;
    }
}

Vec2 GLViewProtocol::getVisibleOrigin() const
{
    if (_resolutionPolicy == ResolutionPolicy::NO_BORDER)
    {
        return Vec2((_designResolutionSize.width - _screenSize.width/_scaleX)/2,
                           (_designResolutionSize.height - _screenSize.height/_scaleY)/2);
    }
    else
    {
        return Vec2::ZERO;
    }
}

仔细想想就会看出来的。这个区域和屏幕分辨率的宽高比是一样的,但是比设计分辨率小,更通俗点讲就是:在设计分辨率下的最大区域,该区域有的特征是:和屏幕分辨率的宽高比一致。(有点啰嗦)

不仅仅是这个模式,其它模式也适合,其它模式的visibleOrigin=(0,0),visibleSize=_desiginResolutionSize,其它模式在这个区域中的UI肯定能在屏幕分辨率下显示。

这就是为什么不管我哦们的策略模式是如何的,都可以用这个两个参数去设置UI的坐标来保证UI可以在显示区域中。

表达能力有限,只能讲成这样了。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-26 15:30:24

cocos 屏幕适配源码分析及VisibleSize,VisibleOrigin的相关文章

源码分析 Sentinel 之 Dubbo 适配原理

在Alibaba Sentinel 限流与熔断初探(技巧篇) 的示例中我选择了 sentinel-demo-apache-dubbo 作为突破点,故本文就从该项目入手,看看 Sentinel 是如何对 Dubbo 做的适配,让项目使用方无感知,只需要引入对应的依即可. sentinel-apache-dubbo-adapter 比较简单,展开如下: 上面的代码应该比较简单,在正式进入源码研究之前,我先抛出如下二个问题: 1.限流.熔断相关的功能是在 Dubbo 的客户端实现还是服务端实现?为什么

Cocos2d-X3.0 刨根问底(九)----- 场景切换(TransitionScene)源码分析

上一章我们分析了Scene与Layer相关类的源码,对Cocos2d-x的场景有了初步了解,这章我们来分析一下场景变换TransitionScene源码. 直接看TransitionScene的定义 class CC_DLL TransitionScene : public Scene { public: /** Orientation Type used by some transitions */ enum class Orientation { /// An horizontal orie

Android 上千实例源码分析以及开源分析

Android 上千实例源码分析以及开源分析(百度云分享) 要下载的直接翻到最后吧,项目实例有点多. 首先 介绍几本书籍(下载包中)吧. 01_Android系统概述 02_Android系统的开发综述 03_Android的Linux内核与驱动程序 04_Android的底层库和程序 05_Android的JAVA虚拟机和JAVA环境 06_Android的GUI系统 07_Android的Audio系统 08_Android的Video 输入输出系统 09_Android的多媒体系统 10_

5 cocos2dx 3.0源码分析 渲染 render

渲染,感觉这个挺重要了,这里代入一个简单的例子 Sprite 建立及到最后的画在屏幕上, 我们描述一下这个渲染的流程: 1 sprite 初始化(纹理, 坐标,及当前元素的坐标大小信息) 2 主循环调用sprite的draw(), 把绘制命令发送到系统的render的渲染队列中. 3 Render拿到渲染队列中的渲染命令, 分别对每个命令进行处理, 我们这里的QUAD_COMMAND, 把这个命令中的坐标信息复制到自己的渲染缓冲中, 之后通过调用OpenGL命令对当前的矩形进行绘制. 1 Spr

DialogFragment源码分析

目录介绍 1.最简单的使用方法 1.1 官方建议 1.2 最简单的使用方法 1.3 DialogFragment做屏幕适配 2.源码分析 2.1 DialogFragment继承Fragment 2.2 onCreate(@Nullable Bundle savedInstanceState)源码分析 2.3 setStyle(@DialogStyle int style, @StyleRes int theme) 2.4 onActivityCreated(Bundle savedInstan

S5PV210-uboot源码分析-uboot环境变量

9.1.uboot的环境变量 1.环境变量的作用 (1)在我们不改变uboot源代码的情况下,只需要改变环境变量的值就可以改变uboot运行时的数据和一些特性.比如说,通过修改bootdelay环境变量,就可以更改开机倒数的秒数. 2.环境变量的优先级 (1)uboot代码当中有一个值,环境变量(DDR 环境变量的分区中)中也有一个值,uboot程序实际运行时,规则是,如果环境变量(DDR中环境变量的分区)为空,则使用代码中的环境变量的值,如果环境变量不为空,优先使用环境变对应的值. (2)比如

YII 的源码分析(二)

上一篇简单分析了一下yii的流程,从创建一个应用,到屏幕上输出结果.这一次我来一个稍复杂一点的,重点在输出上,不再是简单的一行"hello world",而是要经过view(视图)层的处理. 依然是demos目录,这次我们选择hangman,一个简单的猜字游戏.老规则,还是从入口处开始看. index.php: <?php // change the following paths if necessary $yii=dirname(__FILE__).'/../../frame

[Android]Fragment源码分析(一) 构造

Fragment是Android3.0之后提供的api,被大家广泛所熟知的主要原因还是因为随即附带的ViewPager控件.虽然我并不喜欢用它,但是它确实是一个相对不错的控件.还是我的一贯作风,我将从源码上向大家展示什么是Fragment.我们先写一个简单的代码对Fragment有个直观的认识:(为了保证我们方便调试,我们可以直接使用V4提供的源码包) FragmentTransaction t = getSupportFragmentManager().beginTransaction();

cocos2d-x 源码分析 : control 源码分析 ( 控制类组件 controlButton)

源码版本来自3.1rc 转载请注明 cocos2d-x源码分析总目录 http://blog.csdn.net/u011225840/article/details/31743129 1.继承结构 control的设计整体感觉挺美的,在父类control定义了整个控制事件的基础以及管理,虽然其继承了Layer,但其本身和UI组件的实现并没有关联.在子类(controlButton,controlSwitch,controlStepper等中实现不同的UI组件).下面通过源码来分析control与