Cocos2dx開發之屏幕適配

  因爲各類智能手機的屏幕大小都不一致,會出現同一張圖片資源在不一樣的設備分辨率下顯示不同的問題。爲避免這樣的狀況,須要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);
}

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

相關文章
相關標籤/搜索