Cocos2dx開發之屏幕適配


  由於各種智能手機的屏幕大小都不一致,會出現同一張圖片資源在不同的設備分辨率下顯示不一樣的問題。為避免這樣的情況,需要Cocos引擎能提供多分辨率的支持,也就是說要求實現這樣的效果 — 開發者不需要考慮程序實際運行在什么分辨率下而只需要制定設置好設計分辨率就行,接着引擎便會自動實現設計分辨率到屏幕分辨率的轉化,以及不同資源分辨率到設計分辨率的轉化。下面逐一分析理解涉及到的概念:

  一 設計分辨率

  顧名思義,指由開發者自定義的分辨率,最終引擎會拿實際的屏幕分辨率和這個自定義的設計分辨率得到縮放因子。但實際的項目開發中還有細節要兼顧,如是否需要寬高都等比縮放,故有一知識點—縮放策略。在cocos2dx中通過setDesignResolutionSize來設置,方法使用如下:

  setDesignResolutionSize(float width, float height, ResolutionPolicy resolutionPolicy);

  參數width和height指定設計分辨率的尺寸,resolutionPolicy指定縮放策略,由ResolutionPolicy枚舉定義,如下圖所示:

//Cocos2dConstants.lua
cc.ResolutionPolicy =
{
    EXACT_FIT = 0,
    NO_BORDER = 1,
    SHOW_ALL  = 2,
    FIXED_HEIGHT  = 3,
    FIXED_WIDTH  = 4,
    UNKNOWN  = 5,
}

  

  EXACT_FIT 充滿整個屏幕,唯一一個不按等比縮放的策略,寬高比不相等可能導致拉伸或壓縮發生形變,開發中不建議用;

  NO_BORDER :充滿屏幕,等比縮放,實質上是屏幕寬、高分別和設計分辨率寬、高計算縮放因子,取較()者作為寬、高的縮放因子。保證了設計區域總能一個方向上鋪滿屏幕,而另一個方向一般會超出屏幕區域。 而區域是被居中對齊到屏幕(如上圖);

  SHOW_ALL  :保持全部元素可見,等比縮放。實質是屏幕寬、高分別和設計分辨率寬、高計算縮放因子,取較()者作為寬、高的縮放因子。保證了設計區域全部顯示到屏幕上,但可能會有黑邊(如上圖)。

  以上三者都不需要開發者對元素的位置進行調整,都是優先字面上的意思,NO_BORDER是“沒黑邊,但可能有裁剪”,SHOW_ALL是“完全沒裁剪,但可能有黑邊”,可以說兩者剛好是相反的情況。

  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);
}

  默認情況下,當不調用方法顯示去修改,游戲初始化之后設計分辨率和屏幕分辨率是保持一致的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM