基于Cocos2D-X的磚塊地圖教程
這是Cocos2D-X磚塊地圖教程系列,你將在此創(chuàng)造一款有關(guān)沙漠中的忍者尋找美味的西瓜的簡單游戲。
需要注意的是該教程是關(guān)于Cocos2D-X,即Cocos2D-iPhone的跨平臺C++移植。所以你在此編寫的代碼將適用于iPhone,Android和更多平臺上!
在本系列文章的第一部分中,你將學(xué)習(xí)如何添加磚塊地圖到游戲中,跟著玩家滾動(dòng)地圖,并使用對象圖層。你將學(xué)到如何使用地圖編輯器去創(chuàng)造磚塊地圖本身。
而第二部分是關(guān)于如何在地圖上創(chuàng)造碰撞領(lǐng)域,如何使用磚塊屬性,如何創(chuàng)造可收集的道具并動(dòng)態(tài)地修改地圖,以及如何確保你的忍者不會(huì)吃太多東西。
注:本篇教程類似于Cocos2D-iPhone教程。
讓我們開始創(chuàng)造磚塊地圖吧!
開始
對于這一教程,你需要安裝最新的Cocos2D-X版本(游戲邦注:在寫本篇教程的時(shí)候更新到2.1.4)。如果你還未擁有最新版本的Cocos2D-X,先下載它并在終端運(yùn)行如下命令去安裝模版:
- cd ~/Downloads/cocos2d-x-2.1.4
- ./install-templates-xcode.sh -f -u
然后使用iOS\cocos2d-x\cocos2dx模版在Xcode創(chuàng)造一個(gè)新項(xiàng)目。點(diǎn)擊Next,將項(xiàng)目命名為TileGame,將項(xiàng)目設(shè)置為Universal,點(diǎn)擊Next然后點(diǎn)擊Create。
你將在這一項(xiàng)目中使用ARC,所以如果這是你第一次聽到ARC,我會(huì)鼓勵(lì)你先了解下它。模版并不是默認(rèn)使用ARC,但幸運(yùn)的是,我們能夠輕松地進(jìn)行 修改。前往Edit\Refactor\Convert to Objective-C ARC。往下拉并只選擇文件main.m, AppDelegate.cpp, HelloWorldScene.cpp,然后點(diǎn)擊Check并完成向?qū)У牟襟E。
創(chuàng)建并運(yùn)行,然后確保一切都還正常運(yùn)行—-你應(yīng)該能夠看到標(biāo)準(zhǔn)的“你好世界”屏幕。
接下來下載游戲資源的壓縮文件。壓縮文件包含如下內(nèi)容:
你將面向玩家對象使用的精靈。
一些伴隨著cfxr效用所創(chuàng)造的音效(你將會(huì)在教程中用到)。
一些伴隨著Garage Band所創(chuàng)造的背景音樂。
你將用到的一些磚塊設(shè)置—-這將伴隨著你將使用的地圖編輯器,但我認(rèn)為我們能夠更輕松地將其與其它內(nèi)容包含在一起。
一些額外的“特別”磚塊,將在之后進(jìn)行詳細(xì)解釋。
當(dāng)你下載了資源后,打開它并將TileGameResources文件夾拖到項(xiàng)目的Resources群組中。在項(xiàng)目菜單里,右擊 Resources群組,并選擇Add Files to “TileGame”…選擇Resources/TileGameResources文件夾,核實(shí)選中了Copy items into destination group’s folder (if needed)以及Create groups for any added folders,然后點(diǎn)擊完成。
如果一切順利的畫,所有的文件都將出現(xiàn)在你的項(xiàng)目中。
你的項(xiàng)目應(yīng)該如下:
現(xiàn)在我們將開始創(chuàng)造地圖!
創(chuàng)造地圖
Cocos2D-X支持基于開放源Tiled Map Editor去創(chuàng)造地圖并將其以TMX格式進(jìn)行保存。
下載Tiled Map Editor。在編寫本篇教程的時(shí)候,其最新版本是0.9.0。
然后運(yùn)行Tiled,前往File\New,并如下填寫對話內(nèi)容:
在定向區(qū)域中,你可以在Orthogonal或Isometric間做出選擇。在此你將選擇Orthogonal。
接下來你將設(shè)置地圖的大小。記住這是在磚塊中,而不是像素中。你將創(chuàng)造一個(gè)較小的地圖,所以在此你應(yīng)該選擇50×50。Tiled將基于像素呈現(xiàn)給 你總體地圖的大小,即在New Map對話的最底部。這是在長度和寬度的基礎(chǔ)上將地圖大小(50個(gè)磚塊)乘以磚塊的大?。?2像素)所計(jì)算出來的。
最后,你將明確寬度和高度。你在此所選擇的是取決于美術(shù)人員所設(shè)置的磚塊。對于本篇教程,你將使用一些伴隨著Tiled編輯器的樣本磚塊,即32×32規(guī)格,選擇它便點(diǎn)擊OK。
接下來你必須添加磚塊設(shè)置去繪制你的地圖。在菜單欄上點(diǎn)擊Map,然后關(guān)掉New Tileset…,并如下填寫對話框內(nèi)容:
為了獲得圖像,點(diǎn)擊Browse并導(dǎo)航至你自己的TileGame/Resources/TileGameResources文件夾,然后選擇你之前從資源壓縮中下載的tmw_desert_spacing.png文件,并將其添加到項(xiàng)目中。它將自動(dòng)根據(jù)文件名填寫名字。
你可以將寬度和高度設(shè)置為32×32,因?yàn)檫@也是磚塊的大小。對于邊緣和間隔:
邊緣是關(guān)于在Tiled開始尋找真正的磚塊像素前應(yīng)該為當(dāng)前的磚塊略過多少多少像素(包括寬度和高度)。
間隔是關(guān)于Tiled在明確了實(shí)際磚塊像素并轉(zhuǎn)向下一個(gè)磚塊數(shù)據(jù)之后應(yīng)該前進(jìn)多少像素(包括寬度和高度)。
如果你著眼于tmw_desert_spacing.png,你將發(fā)現(xiàn)每個(gè)磚塊都圍繞著一個(gè)1像素的黑色邊緣,這也解釋了邊緣和間隔為1的設(shè)置。
當(dāng)你點(diǎn)擊OK時(shí),你將看到磚塊呈現(xiàn)在Tilesets窗口中?,F(xiàn)在你可以開始繪制了。點(diǎn)擊工具欄的Stamp Brush圖標(biāo),然后點(diǎn)擊地圖上的任何一個(gè)位置去放置一個(gè)磚塊。
所以繼續(xù)繪制地圖—-盡可能發(fā)揮創(chuàng)造性!確保添加至少一些建筑到地圖上,因?yàn)槟阍谥髮⑿枰恍┡鲎矁?nèi)容。
為了更輕松地繪制內(nèi)容,你可以著眼于一些快捷方法。以下是最常用到的一些方法:
你可以在Tileset選擇器中圍繞著一系列磚塊拖曳一個(gè)盒子,并同時(shí)放下多個(gè)相鄰的磚塊。
你可以通過View\Zoom In和View\Zoom Out進(jìn)行放大和縮小。
z鍵將在基于Stamp Brush工具編輯地圖時(shí)進(jìn)行旋轉(zhuǎn)。
在一些新功能中你可能會(huì)注意到Mini-map。這是一個(gè)很棒的功能,它讓你能夠看到一個(gè)迷你地圖!著眼于我在Mini-map最下方的迷宮中的糟糕嘗試。紅色盒子代表你在主要編輯窗口中看到的區(qū)域。
當(dāng)你在閱讀下一個(gè)區(qū)域中的滾動(dòng)時(shí)牢牢記住這一Mini-map視圖。
需要注意的是這一教程的資源是出現(xiàn)在地圖前的——所以如果你很懶的話便可以直接利用它。如果你這么做,你應(yīng)該在Tiled打開地圖并明確它是如何設(shè)置的。
當(dāng)你完成地圖的繪制時(shí),在Layers視圖中雙擊Tile Layer,將名字改為Background。然后點(diǎn)擊File\Save,并將文件保存到TileGame項(xiàng)目中的TileGame \Resources\TileGameResources,將文件命名為TileMap.tmx,并覆蓋現(xiàn)有的文件。
你將在之后使用Tiled做其它事,但是現(xiàn)在讓我們將這一地圖帶進(jìn)游戲中!
添加Tiled地圖到Cocos2D-X場景中
打開HelloWorldScene.h,在#include “cocos2d.h”之后添加如下內(nèi)容:
- using namespace cocos2d;
這能指導(dǎo)編輯器去使用cocos2d命名空間,所以你不需要為所有內(nèi)容加上cocos2d的前綴。
然后添加以下內(nèi)容到類定義中,即在花括號之后:
- private:
- CCTMXTiledMap *_tileMap;
- CCTMXLayer *_background;
這創(chuàng)造了一個(gè)實(shí)例變量去追蹤磚塊地圖本身,并創(chuàng)造了另一個(gè)實(shí)例變量去追蹤地圖的背景層。你將在之后學(xué)到更多有關(guān)磚塊地圖層面的內(nèi)容。
接下來,用如下內(nèi)容換掉HelloWorldScene.cpp:
- CCTMXObjectGroup *objectGroup = _tileMap->objectGroupNamed(“Objects”);
- if(objectGroup == NULL){
- CCLog(“tile map has no objects object layer”);
- return false;
- }
- CCDictionary *spawnPoint = objectGroup->objectNamed(“SpawnPoint”);
- int x = ((CCString)*spawnPoint->valueForKey(“x”)).intValue();
- int y = ((CCString)*spawnPoint->valueForKey(“y”)).intValue();
- _player = new CCSprite();
- _player->initWithFile(“Player.png”);
- _player->setPosition(ccp(x,y));
- this->addChild(_player);
- this->setViewPointCenter(_player->getPosition());
最后一行有個(gè)預(yù)兆——但不要擔(dān)心,你很快就能到達(dá)那里。
讓我們暫停一會(huì)并解釋對象層面和對象群組。首先注意你是通過在CCTMXTiledMap對象中(而不是layerNamed)通過objectGroupNamed方法檢索對象層面。它返回了一個(gè)特殊的CCTMXObjectGroup對象。
然后objectGroup調(diào)用了objectNamed方法去獲得一個(gè)CCDictionary,并包含了一些有關(guān)對象的有用信息,如x和y軸,寬度和高度。在教程的這一部分,你需要關(guān)心的便是x和y軸,將其設(shè)置為玩家精靈的位置。
在代碼塊的最后你設(shè)置了視圖去明確玩家的位置。所以現(xiàn)在添加如下內(nèi)容到HelloWorldScene.h中:
- // In the public section
- void setViewPointCenter(CCPoint position);
并添加一個(gè)新方法到HelloWorldScene.cpp(在文件的最下方最好):
- void HelloWorld::setViewPointCenter(CCPoint position) {
- CCSize winSize = CCDirector::sharedDirector()->getWinSize();
- int x = MAX(position.x, winSize.width/2);
- int y = MAX(position.y, winSize.height/2);
- x = MIN(x, (_tileMap->getMapSize().width * this->_tileMap->getTileSize().width) – winSize.width / 2);
- y = MIN(y, (_tileMap->getMapSize().height * _tileMap->getTileSize().height) – winSize.height/2);
- CCPoint actualPosition = ccp(x, y);
- CCPoint centerOfView = ccp(winSize.width/2, winSize.height/2);
- CCPoint viewPoint = ccpSub(centerOfView, actualPosition);
- this->setPosition(viewPoint);
- }
這是關(guān)于磚塊的解釋。想象這一函數(shù)設(shè)置了攝像機(jī)的中心位置。它讓用戶能夠進(jìn)入地圖中x,y軸的任何位置—-但是你有可能不想呈現(xiàn)出某些點(diǎn),如你可能不想要屏幕超過地圖的邊緣(那么它便只會(huì)呈現(xiàn)出黑邊?。?/p>
如下圖:
如果攝像機(jī)的中心小于winSize.width/2或winSize.height/2,那么部分視角是否會(huì)脫離屏幕?同樣的,檢查最上方的界限也很重要,這也是setViewPointCenter所做的。
到目前為止這一函數(shù)被當(dāng)成設(shè)置了攝像機(jī)所面對的中心位置。然而,這并不是它真正做的。這是在Cocos2D-X中操控CCNode的攝像機(jī)的一種方法,但使用它會(huì)比你將使用的解決方法(移動(dòng)整個(gè)層面)更復(fù)雜。
著眼于這一圖解:
diagram(from raywenderlich)想象一個(gè)大世界,你將著眼于坐標(biāo)軸,即從0到winSize.height/width。你的視圖的中心是 centerOfView,你便能清楚自己想要以哪里為中心(actualPosition)。所以為了用實(shí)際位置去匹配視圖中心位置,你需要做的便是向 下傾斜地圖!
通過從視圖中心減去實(shí)際位置你便能夠做到這點(diǎn),然后將HelloWorld層面設(shè)為該位置。
說了這么多理論,是時(shí)候執(zhí)行它們了!創(chuàng)建并運(yùn)行項(xiàng)目,如果一切運(yùn)行正常,你將在屏幕上看到忍者,并且視圖會(huì)不斷移動(dòng)去呈現(xiàn)他的行動(dòng)。
讓忍者移動(dòng)
這是個(gè)好的開始,但是你的忍者還只是站在那里!這并不像真正的忍者。你將朝著用戶敲打的方向移動(dòng)忍者而讓他動(dòng)起來。添加如下代碼到HelloWorldScene.h的公共部分:
- void registerWithTouchDispatcher();
- void setPlayerPosition(CCPoint position);
- bool ccTouchBegan(CCTouch *touch, CCEvent *event);
- void ccTouchEnded(CCTouch *touch, CCEvent *event);
然后打開HelloWorldScene.cpp并將如下代碼添加到init:
- this->setTouchEnabled(true);
這將層面設(shè)置為可碰觸的,所以它將關(guān)注于碰觸事件。接下來添加如下方法到文件最底端:
- #pragma mark – handle touches
- void HelloWorld::registerWithTouchDispatcher() {
- CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 0, true);
- }
- bool HelloWorld::ccTouchBegan(CCTouch *touch, CCEvent *event)
- {
- return true;
- }
- void HelloWorld::setPlayerPosition(CCPoint position) {
- _player->setPosition(position);
- }
- void HelloWorld::ccTouchEnded(CCTouch *touch, CCEvent *event)
- {
- CCPoint touchLocation = touch->getLocationInView();
- touchLocation = CCDirector::sharedDirector()->convertToGL(touchLocation);
- touchLocation = this->convertToNodeSpace(touchLocation);
- CCPoint playerPos = _player->getPosition();
- CCPoint diff = ccpSub(touchLocation, playerPos);
- if ( abs(diff.x) > abs(diff.y) ) {
- if (diff.x > 0) {
- playerPos.x += _tileMap->getTileSize().width;
- } else {
- playerPos.x -= _tileMap->getTileSize().width;
- }
- } else {
- if (diff.y > 0) {
- playerPos.y += _tileMap->getTileSize().height;
- } else {
- playerPos.y -= _tileMap->getTileSize().height;
- }
- }
- // safety check on the bounds of the map
- if (playerPos.x <= (_tileMap->getMapSize().width * _tileMap->getTileSize().width) &&
- playerPos.y <= (_tileMap->getMapSize().height * _tileMap->getTileSize().height) &&
- playerPos.y >= 0 &&
- playerPos.x >= 0 )
- {
- this->setPlayerPosition(playerPos);
- }
- this->setViewPointCenter(_player->getPosition());
- }
在此你覆蓋了registerWithTouchDispatcher方法去處理目標(biāo)碰觸事件。這將導(dǎo)致ccTouchBegan/ccTouchEnded方法(單數(shù)情況)被調(diào)用,而不是ccTouchesBegan/ccTouchesEnded方法(復(fù)數(shù)情況)。
你可能會(huì)好奇單數(shù)情況和復(fù)數(shù)情況有什么區(qū)別。不過在這種情況下我們沒有必要去弄清楚這些問題。但是我還是想向所有人介紹這一方法,因?yàn)樗鼛в?個(gè)主要優(yōu)勢:
“你不需要處理NSSets,調(diào)度程序能夠區(qū)分它們。每次調(diào)用你將獲得一個(gè)UITouch。”
“你可以通過在ccTouchBegan返回YES而要求一個(gè)UITouch。要求碰觸的更新只會(huì)被發(fā)送到要求它們的委托中。所以如果你刪除/結(jié)束/取消更新,你就需要確保它是你的碰觸。這將讓你無需在執(zhí)行多點(diǎn)碰觸時(shí)做各種檢查。”
不管怎樣,在你的ccTouchEnded位置上,你像往常那樣將位置轉(zhuǎn)換成視圖坐標(biāo)軸,然后再轉(zhuǎn)換成GL坐標(biāo)軸。而新任務(wù)便是你調(diào)用了this->convertToNodeSpace(touchLocation)。
這是因?yàn)榕鲇|位置將提供給你用戶在視口中輕敲的坐標(biāo)軸(例如100,100)。但是你可能已經(jīng)滾動(dòng)了地圖,所以它將匹配(800,800)的位置。所以調(diào)用這一方法將基于你如何移動(dòng)層面而抵消碰觸。
接下來你將明確碰觸點(diǎn)和玩家位置的區(qū)別。你將基于碰觸選擇一個(gè)方向,所以首先你應(yīng)該決定是上下移動(dòng)還是左右移動(dòng)。然后你將判斷是正數(shù)還是復(fù)數(shù)而進(jìn)行上下移動(dòng)。
你將相對地調(diào)整玩家位置,然后將視圖中心設(shè)置為玩家的位置,這是你在上部分便寫下的內(nèi)容!
注意你必須添加一個(gè)安全檢查以確保不會(huì)將玩家?guī)щx地圖外部!
所以創(chuàng)建并運(yùn)行項(xiàng)目,然后嘗試它!現(xiàn)在你應(yīng)該能夠輕敲屏幕去移動(dòng)忍者了!
最后
這時(shí)候你已經(jīng)知道如何創(chuàng)造地圖并將其整合到游戲中了。而在第二部分教程中你將進(jìn)一步學(xué)習(xí)如何添加碰撞檢測到地圖中以避免忍者能夠輕松地穿越墻壁。