本文出自[無間落葉](轉載請保留出處):http://blog.leafsoar.com/archives/2013/05-10-19.htmlhtml
爲了適應移動終端的各類分辨率大小,各類屏幕寬高比,在 cocos2d-x(當前穩定版:2.0.4) 中,提供了相應的解決方案,以方便咱們在設計遊戲時,可以更好的適應不一樣的環境。linux
而在設計遊戲之初,決定着咱們屏幕適配的因素有哪些,簡而言之只有兩點:屏幕大小 和 寬高比。這兩個因素是如何影響遊戲的:android
- 屏幕大小: 從小分辨率 480x320 到 1280x800 分辨率,再到全高清 1080p,從手機到平板,還有蘋果設備的 Retina屏,這麼多不一樣的分辨率,並且大小差距甚大,不可能作到一套資源走天下,資源往小了設計,在大屏幕會顯示模糊,圖片往大了設計,在小屏幕設備又太浪費,並且小屏幕的手機硬件資源也會相對的緊缺,因此 根據屏幕大小使用不一樣的資源 是有必要的,而 cocos2d-x 也幫咱們解決了這一點。
- 寬高比: 什麼是寬高比,就是你的屏幕是方的仍是長的,靠近方形的分辨率如 480x320,比例爲 3:2,還有 960x540 的16:9 標準寬屏,這也算是兩種總極端狀況了,若是能在這兩種比例狀況作好適配基本就能夠了,若是比 3:2 「更方」如 4:3,比 16:9 「更長」,那麼不論如何佈局,顯示效果差距甚大,最好對固定比例優化吧。當在寬高比在必定範圍內,能夠經過靈活編寫程序去適應,而在顯示效果上,cocos2d-x 爲咱們提供了三種模式,這些 模式更多的是幫咱們解決比例不一的狀況而存在 的,若是隻是屏幕大小(比例同樣),那經過簡單的放大縮小便可完成。
三種模式
說是三種模式,其實還有一種 無模式,也就是 cocos2d-x 默認的適配方案,如今咱們就來認識一下這些模式,而且經過這些模式去認識其中一些概念 FrameSize、WinSize、VisibleSize、VisibleOrigin,以及它們存在的意義,而且最後靈活運行這些概念 建立出一個不屬於這些模式而超越這些模式的新適配解決方案,這是最終目的。git
kResolutionUnKnown 認識 FrameSize
這是 cocos2d-x 編寫的默認模式,沒有作任何處理,在這種狀況下,遊戲畫面的大小與比例都是不可控的,在程序運行之初,由各個平臺入口函數定義畫面大小:github
// proj.linux/main.cpp linux 平臺手動指定畫面大小 CCEGLView* eglView = CCEGLView::sharedOpenGLView(); eglView->setFrameSize(720, 480); // proj.android/jni/hellocpp/main.cpp android 平臺由 jni 調用傳入設備分辨率參數 void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv* env, jobject thiz, jint w, jint h) { if (!CCDirector::sharedDirector()->getOpenGLView()) { CCEGLView *view = CCEGLView::sharedOpenGLView(); view->setFrameSize(w, h); AppDelegate *pAppDelegate = new AppDelegate(); CCApplication::sharedApplication()->run(); } else { // other ... } }
在此咱們首先認識了 FrameSize 參數,在遊戲運行時,咱們能夠經過 CCEGLView::sharedOpenGLView()->getFrameSize();
得到此值。若是在手機上運行,那麼不一樣分辨率將會獲得不一樣的值,既然這個值不可控,那麼在寫遊戲中也就沒有參考價值了,好比咱們寫一個精靈的位置距離底部 320 高度,在 480x320 分辨率,能看到其在屏幕上方,若是換一臺手機分辨率 960x540 那麼只能顯示在中間靠上的位置,若是設置精靈位置爲距離屏幕上方(高度)320,反之依然,顯示效果不一。函數
此時可行的方案是使用百分比,如精靈位置在屏幕橫向距離左邊 1/3 寬度,在 1/2 正中間處,而相似這樣的設置也不用依賴 FrameSize 的具體數值。而這樣的作法,使得內部元素像彈簧同樣,隨着 FrameSize 的大小改變而改變,伸縮或者擠壓,對於圖片資源大小也是徹底不可控,若是根據屏幕大小放大縮小,那咱們能夠考慮用下面要說的模式,在此不推薦使用 cocos2d-x 的無模式方案。佈局
kResolutionExactFit and kResolutionShowAll 認識 WinSize
在 AppDelegate.cpp 處能夠經過設置:優化
CCEGLView::sharedOpenGLView()->setDesignResolutionSize(720, 480, kResolutionShowAll); // 或者 CCEGLView::sharedOpenGLView()->setDesignResolutionSize(720, 480, kResolutionExactFit);
DesignResolutionSize!顧名思義,也就是邏輯上的遊戲屏幕大小,在這裏咱們設置了其分辨率爲 720x480 爲例,那麼在遊戲中,我麼設置精靈的位置即可以參照此值,如 左下角 ccp(0,0),右上角 ccp(720, 480),而不論 FrameSize 的大小爲多少,是 720x480 也好,是 480x320 也罷,總能正確顯示其位置,左下角和右上角。可以實現這一點的緣由是,固定了設計分辨率大小,從而肯定了其固定的寬高比,它的 優點 是可使用具體的數值擺放精靈位置,不會由於實際屏幕大小寬高比而是內部元素相對位置關係出現混亂。this
而爲了保持畫面的寬高比,cocos2d-x 作了些犧牲,犧牲了什麼呢?kResolutionExactFit 犧牲了畫質而保持了全屏顯示,對畫面進行了拉伸,這意味着什麼?意味着相對極端狀況下,原本精靈是方形的,顯示出來變成長方形,原本圓形的變成了橢圓,固此模式不推薦使用。kResolutionShowAll 爲了保持設計畫面比例對四周進行留黑邊處理,使得不一樣比例下畫面不能全屏。魚和熊掌不能兼得也 ~spa
咱們能夠經過以下方法獲取到 setDesignResolutionSize 所設置的值:
CCSize winSize = CCDirector::sharedDirector()->getWinSize();
咱們能夠用 Cocos2d-x 程序是如何開始運行與結束的 一文的方法,跟蹤 WinSize 的初始化,獲取過程,在這裏簡單提一下,以下步驟:
// 得到 winSize CCSize winSize = CCDirector::sharedDirector()->getWinSize(); // 查看其 getWinSize(); 方法實現 [cocos2dx-path]/cocos2dx/CCDirector.cpp CCSize CCDirector::getWinSize(void) { return m_obWinSizeInPoints; } // 而 m_obWinSizeInPoints 是什麼時候被賦值的 [cocos2dx-path]/cocos2dx/platform/CCEGLViewProtocol.cpp void CCEGLViewProtocol::setDesignResolutionSize(float width, float height, ResolutionPolicy resolutionPolicy) { ... ... m_obDesignResolutionSize.setSize(width, height); ... ... CCDirector::sharedDirector()->m_obWinSizeInPoints = getDesignResolutionSize(); } const CCSize& CCEGLViewProtocol::getDesignResolutionSize() const { return m_obDesignResolutionSize; }
具體的優點:經過設置邏輯分辨率大小,相比無模式,能夠幫咱們解決了屏幕自動放大縮小問題,而且保持屏幕寬高比,使得遊戲更好設計,能夠將設計畫面大小做爲默認背景圖片大小等,惟一點遺憾就是那點前面所提到的一點點犧牲。
kResolutionShowAll 方案能夠做爲咱們的默認解決方案,使得遊戲的設計更爲簡化,但爲了補填拉伸或留黑邊這點缺憾,進入下一個模式!
kResolutionNoBorder 瞭解 VisibleSize 與 VisibleOrigin
此模式能夠解決兩個問題,其一:遊戲畫面全屏;其二:保持設置遊戲時的寬高比例,相比 kResolutionShowAll 有所區別的是,爲了填補留下的黑邊,將畫面稍微放大,以致於可以正好補齊黑邊,而這樣作的後果可想而知,補齊黑邊的同時,另外一個方向上將會有一部分畫面露出屏幕以外,以下示意圖:
黑色邊框標示實際的屏幕分辨率,紫色區域標示遊戲設計大小,而經過放大縮小,保持寬高比固定, 能夠看到 Show All 之中的黑色陰影部分爲留邊,而 No Border 的紫色陰影部分則不能顯示,而這紫色區域的大小是遊戲設計之時是不可控的。那麼原設計的畫面大小就失去了 必定的 參考價值了,由於這可能讓你的畫面顯示殘缺。這時僅僅經過 WinSize 知足不了咱們的設計需求,因此引入了 VisibleSize 與 VisibleOrigin 概念。
如上所示,紫色區域是被屏幕截去的部分,不可顯示的,根據實際狀況,可能出現橫向截取和豎向截取,這取決於實際分辨率的寬高比。而 A、B、C、D所標示的是設計分辨率,固定大小。若是咱們想讓一個精靈元素顯示在屏幕上方靠邊,那麼若是使用 WinSize 的高度設置其位置,可能出現的狀況就是顯示到屏幕以外了。FrameSize 和 WinSize 咱們已經知道其概念,而 VisibleSize 和 VisibleOrigin 所表明的是什麼呢,又時如何爲咱們解決靠邊的問題!注意上圖下方的定義, VisibleSize = H I J K 是用紫色標註的。 而在上圖是 黑色 標註,標示屏幕實際分辨率,雖然 FrameSize 和 VisibleSize 都是 H I J K,但其意義不一樣,紫色代表它是與設計分辨率相關的。
FrameSize 是實際的屏幕分辨率,而 VisibleSize 是在 WinSize 以內,保持 FrameSize 的寬高比所能佔用的最大區域,實際屏幕分辨率 H I J K (黑色) 能夠大於 WinSize ,但VisibleSize 必定會小於或者等於 WinSize,這二者相同的是寬高比。VisibleSize 有着 WinSize 大小(隨WinSize 的大小改變而改變),還有着 FrameSize 的寬高比,它標示 在設計分辨率(WinSize)下,在屏幕中的可見區域大小。 而 VisibleOrigin 則標示在設計分辨率下被截取的區域大小,用點 K 標示,有了這些數據,咱們想讓遊戲元素始終在屏幕顯示的區域以內不成難事。下面經過幾個數值帶入,加深這些概念的印象。
// 組[1] : FrameSize: width = 720, height = 420 WinSize: width = 720, height = 480 VisibleSize: width = 720, height = 420 VisibleOrigin: x = 0, y = 30 // 組[2] :相比 組 [1] FrameSize 不變 VisibleSize 和 VisibleOrigin 隨着 WinSize 的變小而變小 FrameSize: width = 720, height = 420 WinSize: width = 480, height = 320 VisibleSize: width = 480, height = 280 VisibleOrigin: x = 0, y = 20 // 組[3] : 相比組 [1] WinSize 不變,VisibleSize 隨着 FrameSize 的比例改變而改變 FrameSize: width = 720, height = 540 WinSize: width = 720, height = 480 VisibleSize: width = 640, height = 480 VisibleOrigin: x = 40, y = 0 // WinSize VisibleSize VisibleOrigin 與都設計的分辨率相關,知足以下關係 WinSize.width = (VisibleOrigin.x * 2) + VisibleSize.width WinSize.height = (VisibleOrigin.y * 2) + VisibleSize.height
NoBorder 具體的使用方法能夠參考 cocos2d-x 自帶例程 TestCpp ,有詳細的使用方法,而且封裝了 VisibleRect 類,能夠獲取設計分辨率,不一樣比例屏幕之時的主要參考點,屏幕四個拐角,和邊的中點等,讓咱們設置元素位置時,使其總能顯示在屏幕以內,這裏就不詳細介紹了。
基於這幾種模式的程序使用方法,cocos2d-x 自帶例程或者網上有不少教程,這裏只詳細解釋了其中各類概念,而知道了這些概念,固然用起來就沒有多大問題了。
kResolutionLeafsoar
!!!這是什麼模式!好吧,Leafsoar 是 一葉 的 ID ,或者是本博客的一級域名而已 :P 在 cocos2d-x 中並無這種模式。除卻 UnKnown 與 ExactFit 不說,ShowAll 的優點是,只須要一個設計分辨率,而後經過 WinSize 設置相對對位便可,並且位置的最大長寬都是肯定,方便了開發,但屏幕不能填滿, NoBorder 模式的優點是在畫面不變形的狀況下,實現全屏,顯示效果更好,但 WinSize 必定程度失效,須要經過運行時計算 VisibleSize 和 VisibleOrigin 來設置位置,因爲是運行時計算,因此也就會出現,各類屏幕顯示效果不同的狀況。
ShowAll 和 NoBorder 各有所長,各有所短,而這裏提出的新適配解決方案正是取二者之長,舍二者之短的 組合模式。簡單說來就是用 NoBorder 去實現 ShowAll 的思想。NoBorder 能夠保證全屏利用,ShowAll 能夠更好的使用實際設計座標固定位置,並且相對位置不會隨寬高比的改變而改變,這在編寫遊戲的時候能方便很多。先上一個示意圖,一目瞭然 (兩個圖,兩個方向):
在原來 NoBorder 模式示意圖上添加了新的概念,LsSize = X Y M N (leafsoar 簡寫了,爲了避免跟 cocos2d-x 的一些概念混淆,什麼名字不重要,只要瞭解其含義便可),在 NoBorder 模式下的 LsSize 相對於 FrameSize 而言,正如 在 ShowAll 模式下的 WinSize 相對於 FrameSize,因此說這是 ShowAll NoBorder 的組合概念,而這裏的 LsSize 與 WinSize 的寬高比是一致的。
猛地一看,彷佛把問題複雜化了,仔細一看,還不如猛地一看 ~~
在 ShowAll 中,WinSize 做爲最高的寬高,以此參照設置位置,由於在此範圍內都能在屏幕上顯示,用了 NoBorder 使得四周可能被截去一塊區域,而這個區域大小不可控制,因此不能再使用 WinSize 做爲參考點來設置位置,而這裏的 LsSize 一樣,由於 LsSzie 不論在什麼狀況下,總能顯示在屏幕以內,咱們能夠方便的使用 LsSize 做爲座標系參考,而且能夠全屏顯示,在配合 VisibleSize ,相比純的 NoBorder 增強了很多。它能夠怎麼用?
能夠把 LsSize 看成 ShowAll 中的 WinSize 來用,而黑邊可使用稍大的圖片填充,或者使用其它圖片修飾邊框,修飾的邊框圖案可大可小,可長可短,填充屏幕,保持全屏。
開始基於 LsSize 的遊戲設計實現
爲了可以準確實現基於 LsSize 的設計,初步計劃將 LsSize 設定在 480x320 的分辨率方案,爲此作了些準備,首先不使用任何模式狀況下,在場景內調用以下:
CCSize size = CCDirector::sharedDirector()->getWinSize(); CCPoint center = ccp(size.width/2, size.height/2); // 大小 600x500 爲了 NoBorder 看到效果,使用稍大的背景圖 CCSprite* pb = CCSprite::create("Back.jpg"); pb->setPosition(center); this->addChild(pb, 0); // 480x320 此圖爲使用於設計分辨率 LsSize 的圖片 CCSprite* pSprite = CCSprite::create("HelloWorld.png"); pSprite->setPosition(center); this->addChild(pSprite, 0); // 37x37 在 480x320 畫面的四個拐角處,添加參照 CCSprite* p1 = CCSprite::create("Peas.png"); p1->setPosition(ccpAdd(center, ccp(-240, -160))); this->addChild(p1); CCSprite* p2 = CCSprite::create("Peas.png"); p2->setPosition(ccpAdd(center, ccp(240, 160))); this->addChild(p2); CCSprite* p3 = CCSprite::create("Peas.png"); p3->setPosition(ccpAdd(center, ccp(-240, 160))); this->addChild(p3); CCSprite* p4 = CCSprite::create("Peas.png"); p4->setPosition(ccpAdd(center, ccp(240, -160))); this->addChild(p4);
顯示效果:(FrameSize = 640x540)
顯示效果:(ShowAll; FrameSize = 520x320; WinSize = 480x320)
顯示效果:(NoBorder; FrameSize = 520x320; WinSize = 480x320)
經過效果咱們能夠看到,在相同 FrameSize 下 NoBorder 時,畫面因爲填充了黑邊,將畫面放大,以致於上下有部分顯示不全,經過拐角四個精靈能夠看出。
好!既然咱們知道是因爲放大所致,那麼咱們將畫面縮小呢?cocos2d-x 提供了一個方法,咱們調用以下代碼:
CCDirector *pDirector = CCDirector::sharedDirector();
pDirector->setContentScaleFactor(
CCEGLView::sharedOpenGLView()->getScaleY() );
爲了彌補畫面因須要不填空白出現的方法,咱們將畫面縮小,放大係數能夠經過 CCEGLView::sharedOpenGLView()->getScaleY() 取得。其實 setContentScaleFactor 方法是爲了適配不一樣資源而設計的,能夠用此方法對不一樣資源適配,縮放等。效果以下:
咱們看到 480x320 的圖片顯示徹底正確了,也正是咱們想要的效果,但惟一的缺點是 ~~ 拐角處四個精靈的位置依然不是咱們想要的,咱們設計的位置是以 480x320 設置位置的,而 WinSize 也是 480x320 ,而此時基於 480x320 的設計必然會顯示到屏幕以外,而要想不修改精靈位置,而讓其顯示正確的位置,那麼爲了保證 LsSize 的固定,咱們須要一個方法,那就是動態設置 WinSize。
什麼意思?咱們知道通常這些模式設計遊戲時,是經過 setDesignResolutionSize 設置 WinSize 的,這個值在遊戲運行其間是定植,動態改變的是 VisibleSize 等,而這裏提出了 LsSize 的概念,可想而知,若是 WinSize 固定,那麼 LsSize 會隨着屏幕寬高比的改變而改變,那麼咱們反其道而行,固定 LsSize 值,那麼在運行時能夠經過實際的寬高比來算得 WinSize 的值,這樣動態算得的 WinSize 值就可以保證咱們的 LsSize 是一個定值了。
相對論,WinSize 與 LsSize 的值是相對的,與其經過固定 WinSize 在運行時動態得到 LsSize (這也是 NoBorder 的默認方式,而致使的結果是 WinSize 沒有參考價值),不如咱們固定 LsSize 而在運行時算得 WinSize 設置來的要更妙一些。
如今不使用 setContentScaleFactor 方法,而修改 setDesignResolutionSize 這裏的值,咱們知道 WinSize 是 480x320 時,LsSize 必然會小於此值,而 NoBorder 的放大係數咱們能夠經過以下方式算得(能夠參考setDesignResolutionSize方法內部實現),並在 AppDelegate 裏執行:
CCSize frameSize = CCEGLView::sharedOpenGLView()->getFrameSize(); // 設置 LsSize 固定值 CCSize lsSize = CCSizeMake(480, 320); float scaleX = (float) frameSize.width / lsSize.width; float scaleY = (float) frameSize.height / lsSize.height; // 定義 scale 變量 float scale = 0.0f; // MAX(scaleX, scaleY); if (scaleX > scaleY) { // 若是是 X 方向偏大,那麼 scaleX 須要除以一個放大係數,放大係數能夠由樅方向獲取, // 由於此時 FrameSize 和 LsSize 的上下邊是重疊的 scale = scaleX / (frameSize.height / (float) lsSize.height); } else { scale = scaleY / (frameSize.width / (float) lsSize.width); } CCLog("x: %f; y: %f; scale: %f", scaleX, scaleY, scale); // 根據 LsSize 和屏幕寬高比動態設定 WinSize CCEGLView::sharedOpenGLView()->setDesignResolutionSize(lsSize.width * scale, lsSize.height * scale, kResolutionNoBorder);
顯示效果:(NoBorder 模式 ;FrameSize = 520x320; LsSize = 480x320; WinSize = 動態獲取)
咱們看到在沒有修改源代碼,而且在設計中使用 480x320 的參考系,也既是基於 LsSize 的設計顯示效果如咱們預期,那麼咱們換一個 FrameSize 來看看是否可以自動適應呢?以下:
顯示效果:(NoBorder 模式 ;FrameSize = 600x480; LsSize = 480x320; WinSize = 動態獲取)
到此,基於 LsSize 參考系的遊戲設計已經完成了,這樣作的好處是很明顯的,集 ShowAll 和 NoBorder 的優勢於一處,這裏的圖片元素是爲了好定位,實現的須要而寫的,具體場景可使用背景地圖,或一張大的圖片顯示,而沒有任何影響,也能夠繼續使用 VisibleSize 獲得 LsSize 以外的部分區域大小,在 LsSize 以外可使用背景圖片做爲裝飾,即保證了遊戲的全屏,又保證了遊戲設計時的方便,若是使用徹底基於 LsSize 的設計實現,除了顯示背景裝飾以外,咱們不想讓 LsSize 的內部元素顯示到 LsSize 以外如何作呢?咱們只須要設定 LsSize 層的的顯示區域便可,咱們能夠修改場景的實現:
// 這裏先簡單實現思路 CCScene* HelloWorld::scene() { CCScene *scene = CCScene::create(); // 建立背景層 CCLayer* b = CCLayer::create(); scene->addChild(b); // 添加背景圖片和設置位置,可使用其它裝飾,或者小圖片屏幕都行 CCSize size = CCDirector::sharedDirector()->getWinSize(); CCPoint center = ccp(size.width/2, size.height/2); CCSprite* pb = CCSprite::create("Back.jpg"); pb->setPosition(center); b->addChild(pb, 0); // 建立 LsLayer 層 HelloWorld *lsLayer = HelloWorld::create(); scene->addChild(lsLayer); return scene; } // 在 HelloWorld 中重寫 visit() 函數 設定顯示區域 void HelloWorld::visit() { glEnable(GL_SCISSOR_TEST); // 開啓顯示指定區域 // 在這裏只寫上固定值,在特性環境下,以便快速看效果,實際的值,須要根據實際狀況算得 glScissor(20, 0, 480, 320); // 只顯示當前窗口的區域 CCLayer::visit(); // 調用下面的方法 glDisable(GL_SCISSOR_TEST); // 禁用 }
顯示效果:(NoBorder 模式 ;FrameSize = 520x320; LsSize = 480x320; WinSize = 動態獲取)
屏幕適配新解
看完這篇文章想必對 cocos2d-x 的屏幕適配方案及其原理有了至關的認識,從內部提供的三種模式,再到咱們自定義基於 LsSize 的 Leafsoar 模式 (好把,因該叫作 ShowAllNoBorder)。這裏已經給出了徹底的實現原理以及實現方法,並配有效果圖,固然這其中還有些細節須要注意,好比咱們基於 LsSize 的大小設計,那麼實際的圖片確定須要比 LsSize 的要大,大多少,過小了不夠適應,太大了又浪費,如何取捨等問題,這一點取決的因素是什麼,留給讀者思考 ~~
一葉將在 GitHub 處創建一個ScreenSolutions 項目,讀者能夠從這裏參考實現的方案。(也許此時在 GitHub 所看到的實現並不徹底,但已經有了簡單的實現方法,而且可以運行,若有必要,將會新寫一篇博客,去實現 ScreenSolutions 而且解說)