Cocos2d-x雖然很火,但是相關的學習資料還是很少的。在看完《Cocos2d-x權威指南》的基礎內容之後,我跟着學習過“老G的博客”,也看過一些“地球人也阻止不了程序猿們學習Cocos2d-x了”之類的帖子。總感覺介紹和講解的內容是零零散散的。這非常不利於對於Cocos2d-x引擎的整體理解和掌握。(求大爺們不要滅我,只是個人觀點額)
我相信,那些寫帖子的同學們、老師們...大神們,都有自己的學習方法,必定不是通過看帖子來學習新東西的。
好了,不廢話了。希望和大家一起學習,一起進步。爲了給自己鼓勁,在此也爲自己附上一句話。
有夢想,並堅持,人生就有希望。
------ 北大校長
-------------------------------------------------------------------美麗的分割線-------------------------------------------------------------------
任何一個程序都有一個開始執行的入口,這個入口通常叫做“main”。那麼Cocos2d-x在Win32平臺下的入口是int APIENTRY _tWinMain:
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// create the application instance
AppDelegate app;
CCEGLView* eglView = CCEGLView::sharedOpenGLView();
eglView->setViewName("TestCpp");
eglView->setFrameSize(480, 320);
return CCApplication::sharedApplication()->run();
}
AppDelegate:
程序源碼中的註釋:The reason for implement as private inheritance is to hide some interface call by CCDirector。(這句話如何理解,看各位自己的功力了,我就不做坑爹的翻譯了^ ^)
包含三個方法:
1、virtual bool applicationDidFinishLaunching();
應用程序相關資源加載完成後執行此方法。可以簡單的認爲,這個就是程序的初始化函數。
功能:
(1)初始化應用程序中唯一的CCDirector導演。
(2)設置OpenGL的相關參數等。
(3)創建場景,創建佈景層,並將佈景層放入場景中,作爲子節點。
(4)pDirector->runWithScene(pScene); 設置運行的第一個場景。
2、void AppDelegate::applicationDidEnterBackground()
原文中的註釋:This function will be called when the app is inactive。表示應用程序當前狀態是“不活躍”(來電話)時,這個方法將被執行。
功能:
(1)停止遊戲當中的動畫。
(2)暫停遊戲的背景音樂。
(3)暫停遊戲的所有音效。
3、void AppDelegate::applicationWillEnterForeground()
應用程序從後臺再次被喚醒時執行此方法。
功能:恢復動畫、背景音樂、所有音效。
CCEGLView:
由於Cocos2d-x是基於OpenGL ES圖形引擎的,所有在圖形繪製方面所遵守的規則都是OpenGL的規則,比如座標系統。
CCEGLView* eglView = CCEGLView::sharedOpenGLView();
上面的代碼用來獲得OpenGL的視圖。關於圖形學的視圖這個概念可以在網上搜搜。當然,暫時還沒太多必要。就把這理解爲“獲得應用程序的畫面”吧。
那麼有了視圖這個對象指針後,我們就可以設置這個視圖的名稱和大小。
CCApplication::sharedApplication()->run();
應用程序相關的參數設置完成後,就可以調用run()方法,進入應用程序的消息循環。
-------------------------------------------------------------------美麗的分割線-------------------------------------------------------------------
馬上進入正題了,這裏非常關鍵,因此我也會非常小心的分析,免得誤人子弟,當然,還有自己~
學習Cocos2d-x的各種特性,莫過於學習TestCpp項目了,這是公開的“祕密”。開始我學習這個TestCpp的時候頭昏腦脹的,一點頭緒沒有,因爲程序的結構對於我這樣的新人來說太複雜,更別說是用C++編寫的程序....可惡的宏啊、可惡的高級特性啊。完全沒心思找那些示例代碼了,因爲和這個程序結構是緊緊相連的啊。
但如論怎麼說,這個是學習Cocos2d-x最好的資料了,目前還沒發現比這個更好的。
既然這樣,那就下定決心搞定它吧~!
OK,先來看看項目的整體文件。
文件列表:main.cpp、main.h、AppDelegate.h/AppDelegate.cpp、tests.h、testResource.h、VisibleRect.h/VisbleRect.cpp
testBasic.h/testBasic.cpp、controller.h/controller.cpp
除了上面的文件,Classes包(文件夾)中還有許多繼承於CCLayer、CCScene的類,那些都是示例子場景和示例子佈景層。
一一擊破:
main.h/main.cpp和AppDelegate.h/AppDelegate.cpp 在前面我們已經搞定了。
tests.h:#include所有Classes包中的頭文件、一些字符串、枚舉的標記變量等。(C/C++頭文件,通過包含的方法,就可以使用頭文件內的資源)
testResource.h:圖片路徑的字符串等。
VisibleRect.h/VisbleRect.cpp:作者封裝的一個管理座標的類,可以直接讓顯示對象居中、靠左、靠右對齊等。(可以Copy下來,以後自己用)
testBasic.h/testBasic.cpp:TestScene,繼承於CCScene場景類,是所有示例子場景的爹。
controller.h/controller.cpp:TestController,繼承於CCLayer佈景層類,是被應用程序第一個創建的佈景層,裏面有一個CCMenu菜單(主菜單)。
到此,一切文件我們都熟悉了。現在我們深入的調查一下,這個示例程序到底是如何運行這麼多東東的。
CCApplication::sharedApplication()->run()
main.cpp中,上面的代碼執行完之後就進入了消息循環,程序就正常運行了。
當程序相關資源加載和設置完成之後就開始執行AppDelegate類中的applicationDidFinishLaunching()方法。
bool AppDelegate::applicationDidFinishLaunching()
{
// As an example, load config file
// XXX: This should be loaded before the Director is initialized,
// XXX: but at this point, the director is already initialized
CCConfiguration::sharedConfiguration()->loadConfigFile("configs/config-example.plist");
// 獲得導演實例
CCDirector *pDirector = CCDirector::sharedDirector();
// 設置OpenGL視圖
pDirector->setOpenGLView(CCEGLView::sharedOpenGLView());
// 獲取屏幕的分辨率(在Win32下,屏幕的大小是自己設置的,在main.cpp中看到eglView->setFrameSize(480, 320);)
CCSize screenSize = CCEGLView::sharedOpenGLView()->getFrameSize();
// 理想的分辨率
CCSize designSize = CCSizeMake(480, 320);
// CCFileUtils文件操作類
CCFileUtils* pFileUtils = CCFileUtils::sharedFileUtils();
// 如果屏幕的高度大於理想的高度,執行if內的代碼
if (screenSize.height > 320)
{
CCSize resourceSize = CCSizeMake(960, 640);
std::vector<std::string> searchPaths;
searchPaths.push_back("hd");
pFileUtils->setSearchPaths(searchPaths);
pDirector->setContentScaleFactor(resourceSize.height/designSize.height);
}
// 將視圖設置爲理想的分辨率。
CCEGLView::sharedOpenGLView()->setDesignResolutionSize(designSize.width, designSize.height, kResolutionNoBorder);
// 創建一個場景和一個佈景層
CCScene * pScene = CCScene::create();
CCLayer * pLayer = new TestController();
// 設置後,自動管理對象的釋放,不用手動釋放。
pLayer->autorelease();
// 將佈景層添加到場景中
pScene->addChild(pLayer);
// 運行場景
pDirector->runWithScene(pScene);
return true;
}
分辨率的概念:移動平臺設備的分辨率總是不統一的,什麼大小的分辨率都有,因此Coco有一個處理辦法,那就是使用縮放方法。
第一種解決思路,當設備的分辨率改變了,那麼讀取不同分辨率的資源。(需要準備各種分辨率的資源)
第二種解決思路,直接縮放圖片資源的大小,計算公式:X = 設備分辨率/設計分辨率,然後將圖片縮放X倍。(這個比較坑爹)
第三種,思考ing。
在上面代碼的if()結構中,是讀取大分辨率資源(圖片等)。TestCpp項目有兩個資源文件夾,一個是images,一個是hd。我們可以找到工程的所在的文件夾,看到只有一部分的圖片有兩個版本的,而且hd文件夾中的資源,很多都在images裏面沒有。這樣做,可能爲了一些示例表現得更好纔沒有小分辨率的吧。暫時不管它。
pFileUtils->setSearchPaths(searchPaths);
本來程序裏精靈加載的圖片都是搜索默認Resources文件夾下的images文件夾。當if()結構觸發之後,就會執行上面的這行代碼,改變搜索路徑,轉到hd文件夾下搜索資源。我們也可以看到一個技巧,它這裏設置的搜索資源的路徑可以是多個,searchPaths是一個vector容器。
CCEGLView::sharedOpenGLView()->setDesignResolutionSize(designSize.width, designSize.height, kResolutionNoBorder);
上面代碼將設置應用程序分辨率,第三個參數決定了不同尺寸時畫面顯示的效果。
kResolutionExactFit:不同尺寸時,畫面被拉伸。
kResolutionNoBorder:不同尺寸時,沒有黑邊,畫面被裁減。
kResolutionShowAll:不同尺寸時,有黑邊,如果設計的分辨率和屏幕分辨率不同,將顯示黑邊。
(三種效果的說明是從coco源碼定義中的註釋找到的,我只是把英文通俗的翻譯過來了,大致也能想象到這三種都是什麼效果,等實際開發的時候再確定)
CCScene * pScene = CCScene::create();
CCLayer * pLayer = new TestController();
細心的童鞋會發現在TestController佈景層中有加入了主菜單,和右下角的關閉按鈕。
pLayer->autorelease();
我們知道,C++中都是成套使用new/delete的,但是在coco中,有內存自動管理的機制,非常方便,不易出現內存泄露。
在網上一些關於coco的內存管理分析,大神們好像都建議使用這個,除非有特殊情況。
不用時,使用release或autorelease,將來要用時,使用retain。
pDirector->runWithScene(pScene);
Coco 應用程序運行成功!
那麼童鞋們,最後我們震一下~看看大致的結構圖。
-------------------------------------------------------------------美麗的分割線-------------------------------------------------------------------
圖x.x TestCpp中ActionTest示例結構圖
如果童鞋比較細心的話,可以發現我這個圖其實是UML圖。
爲什麼是UML圖?UML能很好的將程序結構表現出來。遊戲編程除了要學習語言基礎、遊戲引擎,最重要的還是編碼能力和遊戲邏輯的思維。
如有哪裏繪製錯誤了,希望童鞋們能馬上幫我指出來。好了,廢話不講了,簡單分析下這個結構。
TestController被創建時,構造函數中會創建一個CCMenu菜單,用來選擇示例場景。在菜單的回調函數(菜單可以指定回調函數,通俗點講,就是響應函數)中會確定調用哪一個示例場景並顯示出來。代碼如下:
TestController::TestController()
: m_tBeginPos(CCPointZero)
{
// 右下角的關閉按鈕
CCMenuItemImage *pCloseItem = CCMenuItemImage::create(s_pPathClose, s_pPathClose, this, menu_selector(TestController::closeCallback) );
CCMenu* pMenu =CCMenu::create(pCloseItem, NULL);
// 設置關閉按鈕的位置
pMenu->setPosition( CCPointZero );
pCloseItem->setPosition(ccp( VisibleRect::right().x-30, VisibleRect::center().y));
// 創建主菜單,添加菜單項
m_pItemMenu = CCMenu::create();
for (int i = 0; i < TESTS_COUNT; ++i)
{
CCLabelTTF* label = CCLabelTTF::create(g_aTestNames[i].c_str(), "Arial", 24);
CCMenuItemLabel* pMenuItem = CCMenuItemLabel::create(label, this, menu_selector(TestController::menuCallback));
m_pItemMenu->addChild(pMenuItem, i + 10000);
pMenuItem->setPosition( ccp( VisibleRect::center().x, (VisibleRect::top().y - (i + 1) * LINE_SPACE) ));
}
// 設置菜單的固定大小,無論視圖如何縮放。
m_pItemMenu->setContentSize(CCSizeMake(VisibleRect::getVisibleRect().size.width, (TESTS_COUNT + 1) * (LINE_SPACE)));
m_pItemMenu->setPosition(s_tCurPos);
addChild(m_pItemMenu);
// 允許觸摸事件
setTouchEnabled(true);
addChild(pMenu, 1);
}
void TestController::menuCallback(CCObject * pSender)
{
// get the userdata, it's the index of the menu item clicked
CCMenuItem* pMenuItem = (CCMenuItem *)(pSender);
int nIdx = pMenuItem->getZOrder() - 10000;
// create the test scene and run it
TestScene* pScene = CreateTestScene(nIdx);
if (pScene)
{
pScene->runThisTest();
pScene->release();
}
}
當在主場景(TestController)中選擇一個菜單項時,就用上面的調用回調函數。pSender指向的就是被選擇的菜單項。
接着獲取菜單項的z值,通過z值確定創建哪一個示例場景。(z值就是對象在容器對象裏的前後的關係)可爲什麼是獲取z值後減去了10000?
我們找到創建菜單的地方(TestController的構造函數),有一個 m_pItemMenu->addChild(pMenuItem, i + 10000); 原來並不是從0開始的,而是從10000開始的。
接着調用TestScene類中的靜態方法 CreateTestScene(int nIdx),創建新場景。代碼如下:
static TestScene* CreateTestScene(int nIdx)
{
CCDirector::sharedDirector()->purgeCachedData();
TestScene* pScene = NULL;
switch (nIdx)
{
case TEST_ACTIONS:
pScene = new ActionsTestScene(); break;
case TEST_TRANSITIONS:
pScene = new TransitionsTestScene(); break;
case TEST_PROGRESS_ACTIONS:
pScene = new ProgressActionsTestScene(); break;
case TEST_EFFECTS:
pScene = new EffectTestScene(); break;
case TEST_CLICK_AND_MOVE:
pScene = new ClickAndMoveTestScene(); break;
......
return pScene;
}
接着調用場景的runThisTest()方法替換原有的場景,並運行。在Action示例中的代碼如下:(其他示例場景也類似)
void ActionsTestScene::runThisTest()
{
sceneIdx = -1;
addChild(nextAction());
// 通過導演的replaceScene方法改變場景
CCDirector::sharedDirector()->replaceScene(this);
}
回到UML圖。每個示例子場景都繼承於一個基類,都有共同的runThisTest()方法,但內部實現不同。(TestScene其實是一個抽象類,它包含了一個純虛函數:virtual void runThisTest() = 0;。
從主菜單的場景到示例的場景,切換就完成了。
最後總結一下程序的執行過程。
執行過程已經被我們搞定了,終於可以好好的去學習TestCpp中的示例了~!