知易遊戲開發教程cocos2d-x移植版002(下)

原文:http://www.cnblogs.com/cocos2d-x/archive/2012/03/01/2376143.html


示例說明

上一節我們對cocos2d-x的基本概念有了初步瞭解,下面我們將通過一個實例對前面的概念進行實戰演練。
假設我們要開發一款飛行射擊類遊戲,其中有3個主要畫面。
1)主菜單畫面
2)遊戲畫面
3)設置畫面

 

在遊戲場景中,我們使用一副太空的圖片作爲背景,遊戲的主角是一架噴着火焰的飛行器,狀態欄用來顯示生命條數和遊戲時間,當然少不了一個"Main Menu"以便返回主菜單畫面。
爲了讓遊戲看起來更加精緻,我們在場景切換時添加一些效果。

幻燈片切換效果:

縮放顯示切換效果:

預備知識

場景切換

實例中包含3個典型場景,分別由3個不同的層來實現。場景的切換主要使用CCDirector的replaceScene函數實現,具體代碼如下:

複製代碼
1 void SysMenu::onNewGame(CCObject* pSender)
2 {
3 CCScene *scene = CCScene::node();
4 scene->addChild(GameLayer::node());
5 CCDirector::sharedDirector()->replaceScene(scene);
6 }
複製代碼

首先創建一個新的CCScene實例,接着創建一個目標層的新實例(我們這裏就是GameLayer),並附加給場景實例。然後調用CCDirector的replaceScene函數,用新場景取代當前場景。
需要爲畫面切換增加效果時,可以使用畫面切換效果類,以CCTransitionSlideInR類爲例,上面的代碼要修改成:

1     CCDirector::sharedDirector()->replaceScene(CCTransitionSlideInR::transitionWithDuration(1.2f, scene));

從本章開始,我們將逐步深入講解基於cocos2d-x的代碼編寫。網上很多示例代碼的作者沒有深入研究cocos2d-x自帶的例子和分析該引擎的源代碼,因此寫出來的代碼比較隨意,不規範。筆者建議大家仔細分析官方的源代碼,並儘量使用標準的編程方法。

座標系

再進一步細講每個層的實現之前,先明確一下座標系的概念。
一般意義上的座標系爲笛卡爾座標系(應該是初中平面幾何開始講的吧,高中立體幾何擴展到三維空間。):

不同的圖形庫採用不同的座標系。iPhone平臺提供了兩種繪圖庫:Quartz 2D和OpenGL ES。其中Quartz 2D是Core Graphics繪圖庫的子集,OpenGL ES是跨平臺圖形庫OpenGL的嵌入設備版。這兩者的座標系原點不一樣。

Quartz 2D的原點在左上角:

大多數圖形窗口應用程序都採用類似的座標系。這是一種基於虛擬“畫布”繪圖模型的圖形庫,繪圖指令按次序向“畫布”上畫下不同的內容,後畫的內容會覆蓋先畫的內容(透明的除外)。這比較容易理解。

OpenGL ES的原點在左下角:

OpenGL ES相對比較複雜,這實際上是一個3D的繪圖庫,按照“狀態機”模型設計的繪圖庫。他不是簡單地讓後者疊加在前者上面,而是記錄各個繪製內容的三維位置關係,再按照系統設定的投影關係,將繪製的所有內容投影在某個特定的虛擬窗口上。
cocos2d-x是基於OpenGL ES的,所以請大家牢記我們使用笛卡爾座標系,座標系的原點在左下角。

cocos2d-x引擎的大多數可顯示對象都是從CCNode類派生而來的,理解該類對於使用該圖形引擎至關重要。今天我們先來說明與位置相關的屬性:
1)anchorPoint(錨點)
爲了將一個矩形圖像精準地放置在屏幕某一位置上,需要設置該矩形的位置參考點。通常人們習慣於將該參考點設置在矩形的左上角上,而在cocos2d-x中我們稱其爲錨點,默認位置在矩形的中心。
2)Position(位置)
Position就是CCNode對象實際的OpenGL ES座標。

下圖說明了如何利用這兩個屬性來設置CCNode對象的顯示位置:

圖中紅色矩形框的Position爲(5, 5),anchorPoint爲(0.3, 0.5)。若要選擇紫色大圓點A爲錨點,則設置anchorPoint爲(0, 0),如要選擇粉紅色大圓點B爲錨點,則設置anchorPoint爲(1, 1)。顯然設置爲(0.5, 0.5)時,錨點位於矩形對象的幾何中心點C,這是CCSprite類對象的默認anchorPoint值。

代碼分析

上文說明了幾個場景之間的切換。下面,我們重點介紹每個場景內部的實現。

SysMenu

作爲遊戲的主菜單,SysMenu的實現並不複雜。

1)主菜單的構建

複製代碼
 1 bool SysMenu::init(void)
2 {
3 bool bRet = false;
4 do
5 {
6 CC_BREAK_IF(! CCLayer::init());
7
8 // 用一個圖片做畫面的背景
9 CCSprite *sp = CCSprite::spriteWithFile("bg.png");
10 CC_BREAK_IF(! sp);
11 sp->setAnchorPoint(CCPointZero);
12 // 既然是背景,zOrder值儘量小。
13 this->addChild(sp, 0, 1);
14
15 // 設置菜單項字體
16 CCMenuItemFont::setFontName("Marker Felt");
17 CCMenuItemFont::setFontSize(25);
18
19 // 逐一創建5個菜單項
20 CCMenuItemFont *newGame = CCMenuItemFont::itemFromString("New Game", this, menu_selector(SysMenu::onNewGame));
21 CC_BREAK_IF(! newGame);
22 CCMenuItemFont *loadGame = CCMenuItemFont::itemFromString("Load");
23 CC_BREAK_IF(! loadGame);
24 CCMenuItemFont *gameSettings = CCMenuItemFont::itemFromString("Option", this, menu_selector(SysMenu::onSettings));
25 CC_BREAK_IF(! gameSettings);
26 CCMenuItemFont *help = CCMenuItemFont::itemFromString("Help");
27 CC_BREAK_IF(! help);
28 CCMenuItemFont *quit = CCMenuItemFont::itemFromString("Quit", this, menu_selector(SysMenu::onQuit));
29 CC_BREAK_IF(! quit);
30
31 // 將這些菜單項都加入到菜單對象中
32 CCMenu *menu = CCMenu::menuWithItems(newGame, loadGame, gameSettings, help, quit, NULL);
33 CC_BREAK_IF(! menu);
34 // 設置菜單以縱向佈局顯示
35 menu->alignItemsVertically();
36 // 最重要的是不要忘記把菜單加入主層對象,否則不會顯示
37 this->addChild(menu, 1, 2);
38
39 bRet = true;
40 } while (0);
41
42 return bRet;
43 }
複製代碼

需要講解的核心函數是itemFromString,他是CCMenuItemFont的一個靜態成員函數,能夠幫助我們創建CCMenuItemFont菜單項。
第一個參數比較直接,就是菜單項顯示的文字。
第二個參數是響應菜單事件的對象,通常是菜單所屬的層對象。
第三個參數是菜單事件對應的回調函數。

2)切換到其他場景

以下兩個函數onNewGame和onSettings被作爲參數傳遞給菜單構建函數,他們的作用都是進行場景切換。

複製代碼
 1 void SysMenu::onNewGame(CCObject* pSender)
2 {
3 CCScene *scene = CCScene::node();
4 // 特別說明,我們將遊戲層和控制層在此分開,由scene作爲他們統一的父節點。
5 scene->addChild(GameLayer::node());
6 scene->addChild(GameControlMenu::node());
7 // 從右至左滾動畫面切換到遊戲場景。
8 CCDirector::sharedDirector()->replaceScene(CCTransitionSlideInR::transitionWithDuration(1.2f, scene));
9 }
10
11 void SysMenu::onSettings(CCObject* pSender)
12 {
13 CCScene *scene = CCScene::node();
14 scene->addChild(SettingsLayer::node());
15 // 縮放變換切換到遊戲設置畫面。
16 CCDirector::sharedDirector()->replaceScene(CCTransitionShrinkGrow::transitionWithDuration(1.2f, scene));
17 }
複製代碼

GameLayer

作爲遊戲的主畫面,主要包含以下幾個關鍵要素:
1)狀態欄:顯示主角的生命數,得分,遊戲時間。
2)控制欄:遊戲需要的控制菜單,動作按鈕等。
3)場景畫面:遊戲場景、NPC、助教角色動作(是主要角色動作嗎?)。

下面我們詳細介紹GameLayer的三個重點:

主畫面的創建

複製代碼
 1 bool GameLayer::init(void)
2 {
3 bool bRet = false;
4 do
5 {
6 CC_BREAK_IF(! CCLayer::init());
7
8 // 遊戲場景背景圖
9 CCSprite *bg = CCSprite::spriteWithFile("Space.png");
10 CC_BREAK_IF(! bg);
11 bg->setAnchorPoint(CCPointZero);
12 // 爲了突出遊戲場景中的精靈,將背景色彩變淡
13 bg->setOpacity(100);
14 this->addChild(bg, 0, 1);
15
16 // 使用位圖字體顯示遊戲時間
17 CCLabelBMFont *lbScore = CCLabelBMFont::labelWithString("Time: 0", "font09.fnt");
18 CC_BREAK_IF(! lbScore);
19 lbScore->setAnchorPoint(ccp(1, 1));
20 lbScore->setScale(0.6f);
21 this->addChild(lbScore, 1, 3);
22 lbScore->setPosition(ccp(310, 450));
23
24 // 載入飛船圖像集。整個圖像集僅載入一次,可以被使用多次。
25 CCSpriteBatchNode *mgr = CCSpriteBatchNode::batchNodeWithFile("flight.png", 5);
26 CC_BREAK_IF(! mgr);
27 this->addChild(mgr, 0, 4);
28
29 // 在狀態欄顯示一個飛船的圖標
30 CCSprite *sprite = CCSprite::spriteWithTexture(mgr->getTexture(), CCRectMake(0, 0, 31, 30));
31 CC_BREAK_IF(! sprite);
32 mgr->addChild(sprite, 1, 5);
33 sprite->setScale(1.1f);
34 sprite->setAnchorPoint(ccp(0, 1));
35 sprite->setPosition(ccp(10, 460));
36
37 // 顯示當前飛船生命條數
38 CCLabelBMFont *lbLife = CCLabelBMFont::labelWithString("3", "font09.fnt");
39 CC_BREAK_IF(! lbLife);
40 lbLife->setAnchorPoint(ccp(0, 1));
41 lbLife->setScale(0.6f);
42 this->addChild(lbLife, 1, 6);
43 lbLife->setPosition(ccp(50, 450));
44
45 // 設定時間回調函數,修改遊戲用時顯示
46 this->schedule(schedule_selector(GameLayer::step), 1.0f);
47
48 // 顯示飛船。爲了讓飛船有不斷閃爍的火焰噴射效果,這是一個簡單的重複性動作。
49 // 在後面的章節中我們會專門講解動作。
50 flight = CCSprite::spriteWithTexture(mgr->getTexture(), CCRectMake(0, 0, 31, 30));
51 CC_BREAK_IF(! flight);
52 flight->setPosition(ccp(160, 30));
53 flight->setScale(1.6f);
54 mgr->addChild(flight, 1, 99);
55
56 // 設定動畫每一幀的內容
57 CCAnimation *animation = CCAnimation::animation();
58 CC_BREAK_IF(! animation);
59 animation->setName("flight");
60 animation->setDelay(0.2f);
61 for (int i = 0; i < 3; ++i)
62 {
63 int x = i % 3;
64 animation->addFrameWithTexture(mgr->getTexture(), CCRectMake(x * 32, 0, 31, 30));
65 }
66
67 // 基於動畫創建動作
68 CCAnimate *action = CCAnimate::actionWithAnimation(animation);
69 CC_BREAK_IF(! action);
70 // 主角精靈不斷重複動作,實現動態飛行效果
71 flight->runAction(CCRepeatForever::actionWithAction(action));
72
73 // accept touch now!
74 this->setIsTouchEnabled(true);
75
76 bRet = true;
77 } while (0);
78
79 return bRet;
80 }
複製代碼

計時器

在遊戲設計時,我們需要不斷的改變屏幕顯示來反映遊戲操作的效果,最簡單的就是提示用戶已經進行的遊戲時間。爲此,我們需要使用cocos2d-x內置的任務調度機制,即CCNode的schedule成員函數。

1     // 設定時間回調函數,修改遊戲用時顯示
2 this->schedule(schedule_selector(GameLayer::step), 1.0f);

schedule的作用類似計時器,按照指定的時間間隔不斷調用某個指定的回調函數。在我們的演示程序中,這個回調函數是這樣的:

複製代碼
1 void GameLayer::step(ccTime dt)
2 {
3 time += dt;
4 char time_str[20];
5 sprintf(time_str, "Time: %d", (int)time);
6 CCLabelBMFont *label1 = (CCLabelBMFont *)this->getChildByTag(3);
7 label1->setString(time_str);
8 }
複製代碼

這段代碼的作用十分簡單,每隔1秒記錄一下新的時間值,並修改遊戲用時的畫面顯示。需要特別說明的是本函數的參數dt,這是系統決定的,是上一次調用到本次調用的時間間隔。因此爲了計算遊戲的用時,我們只需將它與累積時間相加即可。
按照時間間隔檢查內部對象之前的相互作用,及時更新畫面。這是遊戲編程中經常要用到的邏輯。
對於這樣的處理,我們是統一設定一個處理所有檢查反饋邏輯的計時器呢,還是使用多個專用的計時器,這兩種方法哪一個效率更高?cocos2d-iphone的作者指出:小於20個的專用計時器和一個複雜時間計時器的效率基本相同。

可以通過CCNode的unschedule成員函數來取消計時器。

簡單的動作

讓遊戲中的精靈執行一次或者重複執行某個動畫動作是遊戲中經常遇到的,因此cocos2d-x引擎支持一個重要的概念Action(動作)。本例中我們只是給出了一個簡單的“噴火”動畫效果來模擬飛船的飛行。後面我們將有專門的章節來詳細說明動作。

爲了允許飛船在用戶的操作下移動,我們增加了Touch事件的響應:

複製代碼
1 void GameLayer::ccTouchesEnded(CCSet *pTouches, CCEvent *pEvent)
2 {
3 CCTouch *touch = (CCTouch *)pTouches->anyObject();
4 // 獲得觸摸點座標
5 CCPoint location = touch->locationInView(touch->view());
6 CCPoint convertedLocation = CCDirector::sharedDirector()->convertToGL(location);
7 // 讓飛船在1秒鐘內移動過去
8 flight->runAction(CCMoveTo::actionWithDuration(1.0f, ccp(convertedLocation.x, convertedLocation.y)));
9 }
複製代碼

這裏又用到了一個簡單的動作:CCMoveTo。他的作用像他的名字一樣直接,就是移動到某個點。第一個參數是動作執行的時間,第二參數是目標點的座標。

GameControlMenu

完全可以將菜單按鈕放置在GameLayer層實現,但考慮到將來畫面滾動等問題,我們把遊戲界面上的控制畫面分離出來,由一個獨立的層實現。

目前來說,這上面只有唯一的一個菜單項。

複製代碼
 1 bool GameControlMenu::init(void)
2 {
3 bool bRet = false;
4 do
5 {
6 CC_BREAK_IF(! CCLayer::init());
7
8 // 控制欄部分出於簡單考慮,只有一個切換到主菜單的菜單項
9 CCMenuItemFont::setFontSize(22);
10 CCMenuItemFont *systemMenu = CCMenuItemFont::itemFromString("Main Menu",
11 this, menu_selector(GameControlMenu::sysMenu));
12 CC_BREAK_IF(! systemMenu);
13 CCMenu *menu = CCMenu::menuWithItems(systemMenu, NULL);
14 CC_BREAK_IF(! menu);
15 menu->setPosition(ccp(0, 0));
16 // 手工設置菜單位置在左下角
17 systemMenu->setAnchorPoint(ccp(0, 0));
18 systemMenu->setPosition(ccp(0, 0));
19 this->addChild(menu, 1, 2);
20
21 bRet = true;
22 } while (0);
23
24 return bRet;
25 }
複製代碼

下面的函數實現切換到主菜單畫面的功能:

複製代碼
1 void GameControlMenu::sysMenu(CCObject* pSender)
2 {
3 CCScene *scene = CCScene::node();
4 scene->addChild(SysMenu::node());
5 CCDirector::sharedDirector()->replaceScene(CCTransitionSlideInL::transitionWithDuration(1.2f, scene));
6 }
複製代碼

SettingsLayer

這個畫面的實現我們主要參考了cocos2d-x自帶的MenuTest例子。

複製代碼
 1 bool SettingsLayer::init(void)
2 {
3 bool bRet = false;
4 do
5 {
6 CC_BREAK_IF(! CCLayer::init());
7
8 // 設置標題字體
9 CCMenuItemFont::setFontName("American Typewriter");
10 CCMenuItemFont::setFontSize(18);
11 // 用一個禁用狀態的菜單項作爲標題
12 CCMenuItemFont *title1 = CCMenuItemFont::itemFromString("Sound");
13 CC_BREAK_IF(! title1);
14 title1->setIsEnabled(false);
15
16 // 設置選項字體(設置不同的字體以示與標題的區別)
17 CCMenuItemFont::setFontName("Marker Felt");
18 CCMenuItemFont::setFontSize(26);
19 // 設置可切換的菜單項,菜單狀態:開、關。
20 CCMenuItemToggle *item1 = CCMenuItemToggle::itemWithTarget(NULL, NULL,
21 CCMenuItemFont::itemFromString("On"), CCMenuItemFont::itemFromString("Off"), NULL);
22 CC_BREAK_IF(! item1);
23
24 // 設置標題字體
25 CCMenuItemFont::setFontName("American Typewriter");
26 CCMenuItemFont::setFontSize(18);
27 CCMenuItemFont *title2 = CCMenuItemFont::itemFromString("Music");
28 CC_BREAK_IF(! title2);
29 title2->setIsEnabled(false);
30
31 // 設置選項字體
32 CCMenuItemFont::setFontName("Marker Felt");
33 CCMenuItemFont::setFontSize(26);
34 CCMenuItemToggle *item2 = CCMenuItemToggle::itemWithTarget(NULL, NULL,
35 CCMenuItemFont::itemFromString("On"), CCMenuItemFont::itemFromString("Off"), NULL);
36 CC_BREAK_IF(! item2);
37
38 CCMenuItemFont::setFontName("American Typewriter");
39 CCMenuItemFont::setFontSize(18);
40 CCMenuItemFont *title3 = CCMenuItemFont::itemFromString("AI");
41 CC_BREAK_IF(! title3);
42 title3->setIsEnabled(false);
43
44 CCMenuItemFont::setFontName("Marker Felt");
45 CCMenuItemFont::setFontSize(26);
46 CCMenuItemToggle *item3 = CCMenuItemToggle::itemWithTarget(NULL, NULL,
47 CCMenuItemFont::itemFromString("Attack"), CCMenuItemFont::itemFromString("Defense"), NULL);
48 CC_BREAK_IF(! item3);
49
50 CCMenuItemFont::setFontName("American Typewriter");
51 CCMenuItemFont::setFontSize(18);
52 CCMenuItemFont *title4 = CCMenuItemFont::itemFromString("Mode");
53 CC_BREAK_IF(! title4);
54 title4->setIsEnabled(false);
55
56 // 設置多選項效果。首先加入一個子選項(subItems),再加入一個包含了多個子菜單的數組。
57 CCMenuItemFont::setFontName("Marker Felt");
58 CCMenuItemFont::setFontSize(26);
59 CCMenuItemToggle *item4 = CCMenuItemToggle::itemWithTarget(NULL, NULL,
60 CCMenuItemFont::itemFromString("Easy"), NULL);
61 CC_BREAK_IF(! item4);
62 CCMutableArray<CCMenuItem *> *more_items =
63 CCMutableArray<CCMenuItem *>::arrayWithObjects(CCMenuItemFont::itemFromString("Normal"),
64 CCMenuItemFont::itemFromString("Hard"), CCMenuItemFont::itemFromString("Nightmare"), NULL);
65 CC_BREAK_IF(! more_items);
66 // TIP: you can manipulate the items like any other CCMutableArray
67 item4->getSubItems()->addObjectsFromArray(more_items);
68 // you can change the one of the items by doing this
69 item4->setSelectedIndex(0);
70
71 CCMenuItemFont::setFontName("Marker Felt");
72 CCMenuItemFont::setFontSize(26);
73 CCLabelBMFont *label = CCLabelBMFont::labelWithString("Go back", "font01.fnt");
74 CC_BREAK_IF(! label);
75 CCMenuItemLabel *back = CCMenuItemLabel::itemWithLabel(label, this, menu_selector(SettingsLayer::backCallback));
76 CC_BREAK_IF(! back);
77 back->setScale(0.8f);
78
79 // 組合創建菜單層
80 CCMenu *menu = CCMenu::menuWithItems(title1, title2, item1, item2,
81 title3, title4, item3, item4, back, NULL);
82 CC_BREAK_IF(! menu);
83 // 設置多列的菜單項佈局
84 menu->alignItemsInColumns(2, 2, 2, 2, 1, NULL);
85 this->addChild(menu);
86
87 // 手工微調一下最後一個菜單項的位置
88 cocos2d::CCPoint cp_back = back->getPosition();
89 cp_back.y -= 50.0f;
90 back->setPosition(cp_back);
91
92 bRet = true;
93 } while (0);
94
95 return bRet;
96 }
複製代碼

演示實例源代碼可以從下面的鏈接獲取
http://files.cnblogs.com/cocos2d-x/ZYG002.rar


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章