實例比較簡單,如圖所示,地圖上有一個忍者精靈,玩家點擊他周圍的上、下、左、右,他可以向這個方向行走。當他遇到障礙物後是沒法穿越的,障礙物是除了草地覺得部分,包括了:樹、山、河流等。
php
忍者實例地圖(TODO用這個精靈替換圖中的)html
設計地圖
咱們採用David Gervais提供開源免費瓦片集,下載的文件dg_grounds32.gif,gif文件格式會有必定的問題,咱們須要轉換爲.jpg或.png文件。本實例中我是使用PhotoShop轉換爲dg_grounds32.jpg。
David Gervais提供的瓦片集中的瓦片是32 x 32像素,咱們建立的地圖大小是32 x 32瓦片。咱們先爲地圖添加普通層和對象層,普通層按照上圖設計,對象層中添加幾個矩形區域對象,這裏再也不贅述。這個階段設計完成的結果如圖所示。保存文件名爲MiddleMap.tmx,保存目錄Resources\map。
設計地圖
程序中加載地圖
地圖設計完成咱們就能夠在程序中加載地圖了。下面咱們再看看具體的程序代碼,首先看一下HelloWorldScene.h文件,它的代碼以下:
[html] view plaincopyswift
#ifndef __HELLOWORLD_SCENE_H__ 微信
#define __HELLOWORLD_SCENE_H__ 編輯器
#include "cocos2d.h" 函數
class HelloWorld : public cocos2d::Layer 網站
{ this
cocos2d::TMXTiledMap* _tileMap; ① spa
cocos2d::Sprite *_player; ② .net
public:
static cocos2d::Scene* createScene();
virtual bool init();
CREATE_FUNC(HelloWorld);
};
#endif // __HELLOWORLD_SCENE_H__
上述代碼第①行代碼是定義成員變量地圖成員_tileMap,。第②行代碼是定義精靈成員變量_player。
HelloWorldScene的實現代碼HelloWorldScene.ccp文件,它的HelloWorld::init()代碼以下:
[html] view plaincopy
bool HelloWorld::init()
{
if ( !Layer::init() )
{
return false;
}
Size visibleSize = Director::getInstance()->getVisibleSize();
Point origin = Director::getInstance()->getVisibleOrigin();
_tileMap = TMXTiledMap::create("map/MiddleMap.tmx"); ①
addChild(tileMap,0,100); ②
TMXObjectGroup* group = _tileMap ->getObjectGroup("objects"); ③
ValueMap spawnPoint = group->getObject("ninja"); ④
float x = spawnPoint["x"].asFloat(); ⑤
float y = spawnPoint["y"].asFloat(); ⑥
_player = Sprite::create("ninja.png"); ⑦
_player ->setPosition(Point(x,y)); ⑧
addChild(_player, 2,200);
return true;
}
上述第①代碼是建立TMXTiledMap對象,地圖文件是MiddleMap.tmx,map是資源目錄Resources下的子目錄。TMXTiledMap對象也是Node對象,須要經過第②行代碼添加到當前場景中。
第③行代碼是經過對象層名objects得到層中對象組集合。第④行代碼是從對象組中,經過對象名得到ninja對象信息,它的返回值類型是ValueMap,ValueMap是一種「鍵-值」對結構。第⑤行代碼float x = spawnPoint["x"].asFloat()中的spawnPoint["x"]就是從按照x鍵取出它的值,即x軸座標。spawnPoint["x"]的返回值是Value類型,還須要使用asFloat()函數轉換爲基本的int類型。相似地,第⑥行代碼是得到y軸座標。
第⑦行代碼是建立精靈_player,第⑧行代碼是設置精靈位置,這個位置是從對象層中ninja對象信息獲取的。
加載地圖(TODO從新截取,或者換精靈)
移動精靈
移動精靈是經過觸摸事件實現移動的,須要在層中進行事件處理,咱們須要在層中重寫以下函數:
bool onTouchBegan(Touch * touch, Event* unused_event)
void onTouchEnded(Touch * touch,Event* unused_event)
void onTouchMoved(Touch * touch,Event* unused_event)
下面咱們再看看具體的程序代碼,首先看一下HelloWorldScene.h文件,它的代碼以下:
[html] view plaincopy
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
#include "cocos2d.h"
class HelloWorld : public cocos2d::Layer
{
cocos2d::TMXTiledMap* _tileMap;
cocos2d::Sprite *_player;
public:
static cocos2d::Scene* createScene();
virtual bool init();
virtual bool onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* event); ①
virtual void onTouchMoved(cocos2d::Touch *touch, cocos2d::Event *event);
virtual void onTouchEnded(cocos2d::Touch *touch, cocos2d::Event *event); ②
CREATE_FUNC(HelloWorld);
};
#endif // __HELLOWORLD_SCENE_H__
上述代碼第①~②行代碼是聲明觸摸事件函數。HelloWorldScene的實現代碼HelloWorldScene.ccp文件,它的HelloWorld::init()代碼以下:
[html] view plaincopy
bool HelloWorld::init()
{
… …
setTouchEnabled(true);
//設置爲單點觸摸
setTouchMode(Touch::DispatchMode::ONE_BY_ONE);
return true;
}
上述代碼setTouchEnabled(true)是使層開始觸摸事件支持。代碼setTouchMode(Touch::DispatchMode::ONE_BY_ONE)是設置觸摸模式爲單點觸摸。
HelloWorldScene.ccp文件的觸摸事件函數代碼以下:
[html] view plaincopy
bool HelloWorld::onTouchBegan(Touch* touch, Event* event)
{
log("onTouchBegan");
return true;
}
void HelloWorld::onTouchMoved(Touch *touch, Event *event)
{
log("onTouchMoved");
}
void HelloWorld::onTouchEnded(Touch *touch, Event *event)
{
log("onTouchEnded");
Point touchLocation = touch->getLocation(); ①
Point playerPos = _player->getPosition(); ②
Point diff = touchLocation - playerPos; ③
if (abs(diff.x) > abs(diff.y)) { ④
if (diff.x > 0) { ⑤
playerPos.x += _tileMap->getTileSize().width;
_player->runAction(FlipX::create(false)); ⑥
} else {
playerPos.x -= _tileMap->getTileSize().width;
_player->runAction(FlipX::create(true)); ⑦
}
} else {
if (diff.y > 0) { ⑧
playerPos.y += _tileMap->getTileSize().height;
} else {
playerPos.y -= _tileMap->getTileSize().height;
}
}
_player->setPosition(playerPos); ⑨
}
上述第①代碼touch->getLocation()是得到在Open GL座標,Open GL座標的座標原點是左下角,touch對象封裝了觸摸點對象。第②行代碼_player->getPosition()是得到精靈的位置。
第③行代碼是得到觸摸點與精靈位置之差。第④行代碼是比較一下觸摸點與精靈位置之差,是y軸之差大仍是x軸之差大,那個軸之差大就沿着那個軸移動,(abs(diff.x) > abs(diff.y))狀況是x軸之差大,不然是y軸之差大。第⑤行代碼,diff.x > 0狀況是沿着x軸正方向移動,不然狀況是沿着x軸負方向移動。第⑥行代碼_player->runAction(FlipX::create(false))是把精靈翻轉回原始狀態。第⑦行代碼_player->runAction(FlipX::create(true))是把精靈是沿着y軸水平翻轉。
第⑧行代碼是沿着y軸移動,diff.y > 0是沿着y軸正方向移動,不然是沿着y軸負方向移動。
第⑨行代碼是從新設置精靈座標。
檢測碰撞
到目前爲止咱們遊戲中的精靈,能夠穿越任何障礙物。爲了可以檢測到精靈是否碰撞到障礙物,咱們須要再添加一個普通層(collidable),它的目的不是現實地圖,而是檢測碰撞。咱們在檢測碰撞層中使用瓦片覆蓋background層中的障礙物之上,如圖所示。
檢測碰撞層檢測碰撞層中的瓦片集能夠是任何的知足格式要求的圖片文件。在本例中咱們使用一個32 x 32像素單色jpg圖片文件collidable_tiles. jpg,它的大小與瓦片大小同樣,也就是說這個瓦片集中只有一個瓦片。導入這個瓦片集到地圖後,咱們須要爲瓦片添加一個自定義屬性,瓦片自己也有一些屬性,例如:座標屬性x和y。
咱們要添加的屬性名爲「Collidable」,屬性值爲「true」。添加過程如圖所示,首先,選擇collidable_tiles瓦片集中的要設置屬性的瓦片。而後,點擊屬性視圖中左下角「+」按鈕,添加自定義屬性,這時候會彈出一個對話框,咱們在對話框中輸入自定義屬性名「Collidable」,點擊肯定按鈕。這時候回到屬性視圖,Collidable在屬性後面是能夠輸入內容的,這裏咱們輸入「true」。
添加檢測碰撞屬性地圖修改完成後,咱們還要修改代碼。首選在頭文件HelloWorldScene.h中添加一個成員變量和兩個函數的聲明。
[html] view plaincopy
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
#include "cocos2d.h"
#include "SimpleAudioEngine.h"
class HelloWorld : public cocos2d::Layer
{
cocos2d::TMXTiledMap* _tileMap;
cocos2d::TMXLayer* _collidable; ①
cocos2d::Sprite *_player;
public:
static cocos2d::Scene* createScene();
virtual bool init();
virtual bool onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* event);
virtual void onTouchMoved(cocos2d::Touch *touch, cocos2d::Event *event);
virtual void onTouchEnded(cocos2d::Touch *touch, cocos2d::Event *event);
void setPlayerPosition(cocos2d::Point position); ②
cocos2d::Point tileCoordFromPosition(cocos2d::Point position); ③
CREATE_FUNC(HelloWorld);
};
#endif // __HELLOWORLD_SCENE_H__
上述代碼第①行是聲明一個TMXLayer類型的成員變量,它是用來保存地圖碰撞層對象。第②行代碼setPlayerPosition函數是從新設置精靈的位置,在這個函數中能夠檢測精靈是否與障礙物碰撞。第③行代碼tileCoordFromPosition函數是把像素座標點轉換爲地圖瓦片座標點。
修改HelloWorldScene.cpp中的HelloWorld::init()代碼以下:
[html] view plaincopy
bool HelloWorld::init()
{
if ( !Layer::init() )
{
return false;
}
Size visibleSize = Director::getInstance()->getVisibleSize();
Point origin = Director::getInstance()->getVisibleOrigin();
_tileMap = TMXTiledMap::create("map/MiddleMap.tmx");
addChild(_tileMap,0,100);
TMXObjectGroup* group = _tileMap->getObjectGroup("objects");
ValueMap spawnPoint = group->getObject("ninja");
float x = spawnPoint["x"].asFloat();
float y = spawnPoint["y"].asFloat();
_player = Sprite::create("ninja.png");
_player->setPosition(Point(x,y));
addChild(_player, 2, 200);
_collidable = _tileMap->getLayer("collidable"); ①
_collidable->setVisible(false); ②
setTouchEnabled(true);
//設置爲單點觸摸
setTouchMode(Touch::DispatchMode::ONE_BY_ONE);
return true;
}
咱們須要在HelloWorld::init()函數中建立並初始化碰撞層。第①行代碼是_collidable = _tileMap->getLayer("collidable")是經過層名字collidable建立層,第②行代碼_collidable->setVisible(false)是設置層隱藏,咱們要麼在這裏隱藏的,要麼在地圖編輯的時候,將該層透明,如圖所示,在層視圖中選擇層,而後經過滑動上面的透明度滑塊來改變層的透明度,在本例中是須要將透明度設置爲0,那麼_collidable->setVisible(false)語句就再也不須要了。
注意 在地圖編輯器中,設置層的透明度爲0與設置層隱藏,在地圖上看起來同樣,可是有着本質的區別,設置層隱藏是沒法經過_collidable = _tileMap->getLayer("collidable")語句訪問的。
設置層透明度
咱們在前面也介紹過,collidable層不是用來顯示地圖內容的,而是用來檢測碰撞的。修改HelloWorldScene.cpp中的[html] view plaincopy
HelloWorld::onTouchEnded代碼以下:
void HelloWorld::onTouchEnded(Touch *touch, Event *event)
{
log("onTouchEnded");
//得到在OpenGL座標
Point touchLocation = touch->getLocation();
Point playerPos = _player->getPosition();
Point diff = touchLocation - playerPos;
if (abs(diff.x) > abs(diff.y)) {
if (diff.x > 0) {
playerPos.x += _tileMap->getTileSize().width;
_player->runAction(FlipX::create(false));
} else {
playerPos.x -= _tileMap->getTileSize().width;
_player->runAction(FlipX::create(true));
}
} else {
if (diff.y > 0) {
playerPos.y += _tileMap->getTileSize().height;
} else {
playerPos.y -= _tileMap->getTileSize().height;
}
}
this->setPlayerPosition(playerPos); ①
}
HelloWorld::onTouchEnded有一些變化,第①行代碼this->setPlayerPosition(playerPos)替換了_player->setPosition(playerPos),setPlayerPosition是咱們自定的函數,這個函數的做用是移動精靈和檢測碰撞。
setPlayerPosition代碼以下:
[html] view plaincopy
void HelloWorld::setPlayerPosition(Point position)
{
//從像素點座標轉化爲瓦片座標
Point tileCoord = this->tileCoordFromPosition(position); ①
//得到瓦片的GID
int tileGid = _collidable->getTileGIDAt(tileCoord); ②
if (tileGid > 0) { ③
Value prop = _tileMap->getPropertiesForGID(tileGid); ④
ValueMap propValueMap = prop.asValueMap(); ⑤
std::string collision = propValueMap["Collidable"].asString(); ⑥
if (collision == "true") { //碰撞檢測成功 ⑦
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("empty.wav"); ⑧
return ;
}
}
_player->setPosition(position);
}
上述代碼第①行this->tileCoordFromPosition(position)是調用函數,實現從像素點座標轉化爲瓦片座標。第②行代碼_collidable->getTileGIDAt(tileCoord)是經過瓦片座標得到GID值。
第③行代碼tileGid > 0能夠判斷瓦片是否存在,tileGid == 0是瓦片不存在狀況。第④行代碼_tileMap->getPropertiesForGID(tileGid)是經過地圖對象的getPropertiesForGID返回,它的返回值是Value類型。
因爲Value類型能夠表明不少類型。所以第⑤行代碼prop.asValueMap()是將Value類型轉換成爲ValueMap,ValueMap類是「鍵-值」對。第⑥行代碼propValueMap["Collidable"].asString()是將propValueMap變量中的Collidable屬性取出來,asString()函數能夠將Value類型轉換成爲std::string類型。第⑦行代碼collision == "true"是碰撞檢測成功狀況。第⑧行代碼是碰撞檢測成功狀況下處理,在本例中咱們是播放一下音效。
tileCoordFromPosition代碼以下:
[html] view plaincopy
Point HelloWorld::tileCoordFromPosition(Point pos)
{
int x = pos.x / _tileMap->getTileSize().width; ①
int y = ((_tileMap->getMapSize().height * _tileMap->getTileSize().height) - pos.y) /
_tileMap->getTileSize().height; ②
return Point(x,y);
}
在該函數中第①行代碼pos.x / _tileMap->getTileSize().width是得到x軸瓦片座標(單位是瓦片數),pos.x是觸摸點x軸座標(單位是像素),_tileMap->getTileSize().width是每一個瓦片的寬度,單位是像素。代碼第②行是得到y軸瓦片座標(單位是瓦片數),這個計算有點麻煩,瓦片座標的原點在左上角,而觸摸點使用的座標是Open GL座標,座標原點在左下角,表達式(_tileMap->getMapSize().height * _tileMap->getTileSize().height) - pos.y)是反轉座標軸,結果除以每一個瓦片的高度_tileMap->getTileSize().height,就獲得y軸瓦片座標了。
滾動地圖
因爲地圖比屏幕要大,當咱們移動精靈到屏幕的邊緣時候,那些處於屏幕以外的地圖部分,應該滾動到屏幕以內。這些須要咱們從新設置視點(屏幕的中心點),使得精靈一直處於屏幕的中心。可是精靈太靠近地圖的邊界時候,他有可能不在屏幕的中心。精靈與地圖的邊界距離的規定是,左右邊界距離不小於屏幕寬度的一半,不然會出現圖所示的左右黑邊問題。上下邊界距離不小於屏幕高度的一半,不然也會在上下黑邊問題。
從新設置視點實現的方式不少,本章中採用移動地圖位置實現這種效果。
咱們在HelloWorldScene.cpp中再添加一個函數setViewpointCenter,添加後代碼以下:
[html] view plaincopy
void HelloWorld::setViewpointCenter(Point position)
{
Size visibleSize = Director::getInstance()->getVisibleSize();
int x = MAX(position.x, visibleSize.width / 2); ①
int y = MAX(position.y, visibleSize.height / 2); ②
x = MIN(x, (_tileMap->getMapSize().width * _tileMap->getTileSize().width)
- visibleSize.width / 2); ③
y = MIN(y, (_tileMap->getMapSize().height * _tileMap->getTileSize().height)
- visibleSize.height/2); ④
//屏幕中心點
Point pointA = Point(visibleSize.width/2, visibleSize.height/2); ⑤
//使精靈處於屏幕中心,移動地圖目標位置
Point pointB = Point(x, y); ⑥
log("目標位置 (%f ,%f) ",pointB.x,pointB.y);
//地圖移動偏移量
Point offset =pointA - pointB; ⑦
log("offset (%f ,%f) ",offset.x, offset.y);
this->setPosition(offset); ⑧
}
在上述代碼①~④是保障精靈移動到地圖邊界時候不會再移動,防止屏幕超出地圖以外,這一點很是重要。其中第①行代碼是防止屏幕左邊超出地圖以外,MAX(position.x, visibleSize.width / 2)語句表示當position.x < visibleSize.width / 2狀況下,x軸座標始終是visibleSize.width / 2,即精靈再也不向左移動。第②行代碼與第①行代碼相似,再也不解釋。第③行代碼是防止屏幕右邊超出地圖以外,MIN(x, (_tileMap->getMapSize().width * _tileMap->getTileSize().width) - visibleSize.width / 2)語句表示當x > (_tileMap->getMapSize().width * _tileMap->getTileSize().width) - visibleSize.width / 2時候,x軸座標始終是(_tileMap->getMapSize().width * _tileMap->getTileSize().width) - visibleSize.width / 2表達式計算的結果。
提示visibleSize 是表示屏幕的寬度,_tileMap->getMapSize().width * _tileMap->getTileSize().width) - visibleSize.width / 2表達式計算的是地圖的寬度減去屏幕寬度的一半。
第④行代碼與第③行代碼相似,再也不解釋。
屏幕左邊超出地圖
屏幕右邊超出地圖代碼⑤~⑧行實現了移動地圖效果,使得精靈一直處於屏幕的中心。A點是目前屏幕的中心點,也是精靈的位置。玩家觸摸B點,精靈會向B點移動。爲了讓精靈保持在屏幕中心,地圖必定要向相反的方向移動。
第⑤行代碼Point pointA = Point(visibleSize.width/2, visibleSize.height/2)是獲取屏幕中心點(A點)。第⑥行代碼是獲取移動地圖目標位置(B點)。第⑦行代碼是計算A點與B點二者之差,這個差值就是地圖要移動的距離。因爲精靈的世界座標就是地圖層的模型座標,即精靈的座標原點是地圖的左下角,所以第⑧行代碼this->setPosition(offset)是將地圖座標原點移動offset位置。
移動地圖
更多內容請關注國內第一本Swift圖書《Swift開發指南》本書交流討論網站:http://www.51work6.com/swift.php歡迎加入Swift技術討論羣:362298485
歡迎關注智捷iOS課堂微信公共平臺