因为各类智能手机的屏幕大小都不一致,会出现同一张图片资源在不一样的设备分辨率下显示不同的问题。为避免这样的状况,须要Cocos引擎能提供多分辨率的支持,也就是说要求实现这样的效果 — 开发者不须要考虑程序实际运行在什么分辨率下而只须要制定设置好设计分辨率就行,接着引擎便会自动实现设计分辨率到屏幕分辨率的转化,以及不一样资源分辨率到设计分辨率的转化。下面逐一分析理解涉及到的概念:编辑器
一 设计分辨率ide
顾名思义,指由开发者自定义的分辨率,最终引擎会拿实际的屏幕分辨率和这个自定义的设计分辨率获得缩放因子。但实际的项目开发中还有细节要兼顾,如是否须要宽高都等比缩放,故有一知识点—缩放策略。在cocos2dx中经过setDesignResolutionSize来设置,方法使用以下:学习
setDesignResolutionSize(float width, float height, ResolutionPolicy resolutionPolicy);ui
参数width和height指定设计分辨率的尺寸,resolutionPolicy指定缩放策略,由ResolutionPolicy枚举定义,以下图所示:lua
//Cocos2dConstants.lua cc.ResolutionPolicy = { EXACT_FIT = 0, NO_BORDER = 1, SHOW_ALL = 2, FIXED_HEIGHT = 3, FIXED_WIDTH = 4, UNKNOWN = 5, }
EXACT_FIT :充满整个屏幕,惟一一个不按等比缩放的策略,宽高比不相等可能致使拉伸或压缩发生形变,开发中不建议用;spa
NO_BORDER :充满屏幕,等比缩放,实质上是屏幕宽、高分别和设计分辨率宽、高计算缩放因子,取较(大)者做为宽、高的缩放因子。保证了设计区域总能一个方向上铺满屏幕,而另外一个方向通常会超出屏幕区域。 而区域是被居中对齐到屏幕(如上图);设计
SHOW_ALL :保持所有元素可见,等比缩放。实质是屏幕宽、高分别和设计分辨率宽、高计算缩放因子,取较(小)者做为宽、高的缩放因子。保证了设计区域所有显示到屏幕上,但可能会有黑边(如上图)。code
以上三者都不须要开发者对元素的位置进行调整,都是优先字面上的意思,NO_BORDER是“没黑边,但可能有裁剪”,SHOW_ALL是“彻底没裁剪,但可能有黑边”,能够说二者恰好是相反的状况。blog
FIXED_XXXX :也是等比缩放,按开发者指定的某一边来创建缩放比例。与上述不一样的是,它使用屏幕左下角做为原点,且充满屏幕。游戏
二 调整元素位置
除了FIXED_XXXX,其余的缩放策略的绘制区域都是设计分辨率表示的有效区域,都是设计分辨率对应区域的原点(如上述的中点)。可是FIXED_XXXX对应的区域改变了,则程序中设定的绝对坐标每每失效,以下图的在一个分辨率中位于中点的点(100,50),在另外一分辨率下就再也不是中点了:
故针对使用FIXED_XXXX缩放策略的项目,开发过程当中尽可能不要使用绝对坐标,除此之外别的策略都没有问题。解决这个问题的方法是不固定边方向的坐标不要用绝对坐标,能够经过引入visibleSize辅助调整,如auto p2 = Vec2(visibleSize.width/2 - 10,visibleSize.height/2 + 3),这样即可实现逻辑对齐的自适应了。
三 视口设置
ViewPort的设置在OpenGL的GPU渲染管线中的屏幕映射占重要做用,在Cocos2dx中经过setViewPortInPoints方法来设置视口的大小:
void Director::setViewport() { if (_openGLView) { _openGLView->setViewPortInPoints(0, 0, _winSizeInPoints.width, _winSizeInPoints.height); } } //CCGLView.cpp void GLView::setViewPortInPoints(float x , float y , float w , float h) { glViewport((GLint)(x * _scaleX + _viewPortRect.origin.x), (GLint)(y * _scaleY + _viewPortRect.origin.y), (GLsizei)(w * _scaleX), (GLsizei)(h * _scaleY)); }
setViewPortInPoints方法将基于设计分辨率的坐标信息转换为基于屏幕实际像素大小的坐标信息,而后使用GL指令glViewPort进行设置。
四 资源分辨率
上面已经讲述了设计分辨率到屏幕分辨率的转化流程,下面简述设计分辨率到资源分辨率的转化。虽然说场景元素的位置不该和屏幕的实际分辨率有什么关系的,可是在实际的项目应用开发中,咱们最好使设计分辨率和资源分辨率保持一致,这样开发者只须要设置好设计分辨率以后对应的资源UI元素就能被放置在正确的位置上了。
在Cocos2dx中使用setContentScaleFactor(float scaleFactor)方法来对资源进行相应缩放,参数scaleFactor表示设计分辨率与资源分辨率的缩放因子。
void Director::setContentScaleFactor(float scaleFactor) { if (scaleFactor != _contentScaleFactor) { _contentScaleFactor = scaleFactor; _isStatusLabelUpdated = true; } }
补充总结一点,因为纹理坐标使用归一化的坐标值,所以对图元的贴图是与分辨率无关的。可是对于2D绘图,渲染系统要依赖于纹理的实际大小来计算顶点坐标,这就须要对不一样分辨率的资源进行肯定的缩放因子值缩放。
Size Texture2D::getContentSize() const { Size ret; ret.width = _contentSize.width / CC_CONTENT_SCALE_FACTOR(); ret.height = _contentSize.height / CC_CONTENT_SCALE_FACTOR(); return ret; }
到此为止,相关的概念已经比较笼统的理解了一遍,下面举以前个人项目中屏幕适配方案来加深巩固下对这些内容的理解:
在lua入口加载文件开头设置好设计分辨率长和宽,以及缩放策略:
CONFIG_SCREEN_WIDTH = 960 CONFIG_SCREEN_HEIGHT = 540 CONFIG_SCREEN_AUTOSCALE = "SHOW_ALL"
这里设置宽和高分别为960和540,秉着“设计分辨率和资源分辨率保持一致”的作法,项目中用Cocos Studio拼的界面中panel的大小和设计分辨率保持一致,只要在编辑器中调整好UI控件的位置就好,以后程序中不再用理会。同时,策略使用“SHOW_ALL”保证所有元素都能看到,而出现的黑边会额外用花纹图片挡住,后面会继续讲解。
屏幕分辨率是960x640,在SHOW_ALL策略下960x540的设计分辨率,便以小的缩放因子为主,那明显高height方向上会出现黑边,须要添加花纹图片掩盖黑边
ClsStarttScene.onEnter = function(self) local utils = require("update/utils") utils.makeOutSideEdge("update/screen_edge.jpg") self:showLogo() end utils.makeOutSideEdge = function(file_path) local glview = CCDirector:sharedDirector():getOpenGLView() local framesize = glview:getFrameSize() local scaleX = framesize.width / CONFIG_SCREEN_WIDTH local scaleY = framesize.height / CONFIG_SCREEN_HEIGHT local parent = getNotification() if scaleY > scaleX then --上下出现黑边 local viewportsprite_down = ViewPortSprite:create(file_path, CONFIG_SCREEN_WIDTH, CONFIG_SCREEN_HEIGHT); parent:addChild(viewportsprite_down) --按照游戏主窗口的x轴大小缩放花纹图片的大小 local need_width = framesize.width local contentsize = viewportsprite_down:getContentSize() local sp_width = contentsize.width*scaleX viewportsprite_down:setScaleX(need_width/sp_width) local offsetX = ((sp_width - need_width)/2)/scaleX ... ... ... .... else --Iphonex等机型是左右出现黑边 local viewportsprite_left = ViewPortSprite:create(file_path, CONFIG_SCREEN_WIDTH, CONFIG_SCREEN_HEIGHT); parent:addChild(viewportsprite_left) viewportsprite_left:setRotation(-90) viewportsprite_left:setAnchorPoint(CCPoint(0,0)) local contentsize = viewportsprite_left:getContentSize() --横向资源,旋转90度放直 local sp_ct = {} sp_ct.height = contentsize.width sp_ct.width = contentsize.height
... ... ... ... end
效果以下,完美:
适配效果实现了,但还有两个细节须要特别学习记录一下。一个是只要设置了设计分辨率,游戏程序都会从新更新重设视口,投影变换矩阵等等,后续添加的UI元素都会在这个设计分辨率基础上进行渲染,以下:
void GLView::updateDesignResolutionSize() { if (_screenSize.width > 0 && _screenSize.height > 0 && _designResolutionSize.width > 0 && _designResolutionSize.height > 0) { _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); } // calculate the rect of viewport float viewPortW = _designResolutionSize.width * _scaleX; float viewPortH = _designResolutionSize.height * _scaleY; _viewPortRect.setRect((_screenSize.width - viewPortW) / 2, (_screenSize.height - viewPortH) / 2, viewPortW, viewPortH); // reset director's member variables to fit visible rect auto director = Director::getInstance(); director->_winSizeInPoints = getDesignResolutionSize(); director->_isStatusLabelUpdated = true; director->setGLDefaultValues(); //重设 } } void GLView::setDesignResolutionSize(float width, float height, ResolutionPolicy resolutionPolicy) { CCASSERT(resolutionPolicy != ResolutionPolicy::UNKNOWN, "should set resolutionPolicy"); if (width == 0.0f || height == 0.0f) { return; } _designResolutionSize.setSize(width, height); _resolutionPolicy = resolutionPolicy; updateDesignResolutionSize(); }
另一个细节就是添加挡住黑边的花纹图片的时候,不该该在游戏逻辑设计分辨率下而是在实际屏幕分辨率下添加,这样计算主窗口大小比较方便调整坐标,但要记住恢复游戏的设计分辨率。以下:
ViewPortSprite* ViewPortSprite::create(const char *pszFileName, int nViewPortW, int nViewPortH) { ViewPortSprite *pobSprite = new ViewPortSprite(); if (pobSprite && pobSprite->initWithFile(pszFileName)) { pobSprite->autorelease(); pobSprite->setViewPort(nViewPortW, nViewPortH); pobSprite->ignoreAnchorPointForPosition(true); return pobSprite; } CC_SAFE_DELETE(pobSprite); return NULL; } void ViewPortSprite::updateViewPort() { CCSize szframsize = CCDirector::sharedDirector()->getOpenGLView()->getFrameSize(); glViewport(0, 0, szframsize.width, szframsize.height); } void ViewPortSprite::draw(void) { updateViewPort(); CCSprite::draw(); //绘制完毕后要记得恢复 CCDirector::sharedDirector()->getOpenGLView()->setDesignResolutionSize(m_nViewPortW, m_nViewPortH, kResolutionShowAll); }
默认状况下,当不调用方法显示去修改,游戏初始化以后设计分辨率和屏幕分辨率是保持一致的。