原文来自:http://www.raywenderlich.com/40544/cocos2d-x-tile-map-tutorial-part-2
欢迎来到Cocos2d-x tile map 教程系列的第二部分!在这里你将学会怎样用cocos2d-x制作一个基于tile的游戏,cocos2d-x是cocos2d-iphone的C++跨平台移植版。
在第一部分的教程里,你学会了怎样创建一个地图用Tiled工具,怎样添加地图到游戏中,怎样让地图跟随玩家滚动还有怎样使用对象层。
这一部分,包含了怎样在地图中检测碰撞区域,怎样使用块属性,怎样收集物品、动态改变地图,还有怎样确保你的忍者不能吃太多等等。
Tiled Maps and Collisions
你也许注意到了当前的忍者可以轻松的穿过城墙和障碍物,它是一个忍者,但即使是忍者这样也不见得好。
所以,你需要找出一种方法来标记一些图块作为“可碰撞‘,这样就可以阻止玩家移动到这些位置。这里有很多可行的办法来解决(包括使用对象层),但是,我给你的是我认为很有效的一种新技术,也是一个很好的练习——使用meta图层和图层属性。
让我们开始吧!再次打开Tiled工具,点击图层\添加图层,然后命名图层为Meta,你将要放一些假的图块来表示它是”特殊的图块“。
所以现在你需要添加一些特殊图块,点击地图\新图块...,浏览到MyTileMap\Resources\TileGameResources文件夹中的meta_tiles.png,然后点击打开,设置边距和间距都为1,再点OK。
确保图层窗口选中,点击meta_tiles,你将看到两个图块:红色和绿色的。
它们没什么特殊的——我只是做了两个部分透明的红色和绿色的图块。此后,红色代表"可碰撞",它会用来适当的绘制场景。
接着确保Meta图层选中,选择图章刷工具,点击红色图块,绘制你想要这个忍者碰撞的任何物体。完成后,可能看起来像下面这样会:
接下来,你需要在这个图块上设置一个属性来标记它,以至于你可以在代码中识别它是"可碰撞的"。在图块集区右键点击红色图块,然后点击图块属性...添加一个新的属性Collidable,设置为True,像下面这样:
点击OK,保存地图返回到Xcode,添加一个新的private属性到HelloWorldScene.h:
CCTMXLayer *_meta;
这是你meta图层的引用,添加一个新的public方法声明:CCPoint tileCoordForPosition(CCPoint position);
接着,打开HelloWorldScene.cpp,添加下面行到init方法中,在加载背景的后面:_meta = _tileMap->layerNamed("Meta");
_meta->setVisible(false);
这里得到了meta图层的引用,并把它设为不可见,你不希望问玩家可以看到这些红色图块吧!下面,添加这个新方法:
CCPoint HelloWorld::tileCoordForPosition(cocos2d::CCPoint position)
{
int x = position.x / _tileMap->getTileSize().width;
int y = ((_tileMap->getMapSize().height * _tileMap->getTileSize().height) - position.y)/_tileMap->getTileSize().height;
return ccp(x,y);
}
在这里停一下,像平常一样你为meta图层声明了一个成员变量,然后从地图中加载一个引用。注意,你标记了这个图层为不可见的,因为你不想看它们,它们只是表示可碰撞。然后添加了一个新的辅助方法来帮助你把x,y坐标转换为"tile 坐标"。每一个图块都有自己的坐标,左上角(0,0),右下角(49,49).(在这个程序中是50x50)
顺便说下,上面的截图是以前java版本的Tiled,它们已经移植到新版的(Qt)中,有一个显示图块坐标的功能。
不管怎样,你要用到的功能是需要tile坐标,而不是x,y坐标,所以,你需要将x,y坐标转化为tile坐标,这正是tileCoordForPosition的功能。
获取x坐标很容易——只需要用它除以图块的宽。为了得到y坐标,你必须反过来,因为Cocos2d-x的(0,0)是左下角,而不是左上角。
用下面的代码替换掉setPlayerPosition中的内容:
void HelloWorld::setPlayerPosition(cocos2d::CCPoint position)
{
CCPoint tileCoord = this->tileCoordForPosition(position);
int tileGid = _meta->tileGIDAt(tileCoord); //得到全局唯一标识
if (tileGid) {
CCDictionary *properties = _tileMap->propertiesForGID(tileGid);
if (properties) {
CCString *collision = new CCString();
*collision = *properties->valueForKey("Collidable");
if (collision && (collision->compare("True")==0)) {
return;
}
}
}
_player->setPosition(position);
}
在这里,你将玩家x,y坐标转化为tile坐标,然后你在meta图层用tileGID方法,通过指定的tile坐标来获取GID。噢,什么是GID? GID代表”globally unique identifier“(全局唯一标识)。但是在这种情况下我认为它就是被使用的图块的id,也就是你将要移动到得红色的图块。
然后,使用GID来查找图块的属性,它返回一个字典,所以,你可以通过看Collidable是否为true,来立即返回,这样就不设置玩家的位置,使这次移动无效。;
编译-》运行,你应该会看到玩家将不能够穿过你涂过红色的城墙了。
Modifying the Tiled Map Dynamically
到目前为止,你的忍者能够很好的探索,但是这个世界有点枯燥,原因很简单,没什么事可做。
再加上你的忍者看起来有点饿,所以让我们来弄点好吃的东西给你的忍者。
为了做到这点,我们要为你想要玩家收集的东西创建一个前景图层,这样当忍者捡起东西的时候,你可以轻松的在前景图层上删除这个图块,然后背景图层就会显示出来。
打开Tiled工具,选择图层\添加图层,并命名为Foreground。为了不影响我们后面的游戏,把以前添加的西瓜图块都要删除掉,先将背景层前置到最上层(右键->前置图层)
使用橡皮工具将以前绘制的西瓜图块都擦除掉。将Foreground层设为最上层,然后确保点中Foreground层,添加一些可收集的物品到地图中,我们这里就使用西瓜图块。
现在,你需要标记这些图块为可收集的,就像标记可碰撞的那些图块一样。先将Meta图层设为最上层,选中Meta图层,在点击meta_tiles视图然后给这些
西瓜图块涂上绿色的图块。
下一步,你需要添加图块属性来标记其可收集,在图块集窗口右键点击绿色的图块,点击图块属性...然后添加一个新属性名为Collectable,值为True,再点击OK。
保存地图(cmd+s),回到xcode,添加一个新的private属性到HelloWorldScene.h:
CCTMXLayer *_foreground;
然后打开HelloWorldScene.cpp,并添加下面行到init方法中(在加载完背景以后):_foreground = _tileMap->layerNamed("Foreground");
这里得到一个前景图层的引用,呆会会用到。接着,添加下面代码到setPlayerPosition,在造成它return之后。
CCString *collectible = new CCString();
*collectible = *properties->valueForKey("Collectable");
if (collectible && (collectible->compare("True")==0)) {
_meta->removeTileAt(tileCoord);
_foreground->removeTileAt(tileCoord);
}
这里是标准的,对于跟踪Foreground图层的引用。新的东西是你需要检查玩家是否移动到有可收集的属性。如果是,你用removeTileAt方法删除了meta图层和Foreground图层。
编译-》运行工程,现在你的忍者就可以吃到美味的西瓜了。
creating a Score Counter
现在你的忍者又饱又开心,但是作为玩家你想要知道到底吃了多少西瓜,你知道的,不希望它吃得太肥。
通常你只需要添加一个标签到你的图层上就可以解决。但是等一下,你一直在移动整个图层,这会搞砸的,oh noes!
这是一个在场景中展示怎样使用多图层的好机会。之前你一直都是在HelloWorld层做,但是你将要使用另外一个叫HudLayer图层来显示你的标签。(Hud 全称是heads up display).
当然,你的这些图层需要一些方法来交互——HudLayer图层需要知道忍者什么时候吃了西瓜。有很多方法都可以让两个图层间交互,但是你也许将用最简单的方法——你将拿到HelloWorld图层的
指针到HudLayer图层,然后当忍者吃东西的时候就调用一个方法通知它。
所以,在xcode中点击File\New\File...在IOS\C and C++选择C++ Class 模板,点击Next,命名为HudLayer然后点击Create。
打开HudLayer.h,用下面内容代替:
#ifndef __MyTileMap__HudLayer__
#define __MyTileMap__HudLayer__
#include "cocos2d.h"
USING_NS_CC;
class HudLayer:public CCLayer
{
private:
CCLabelTTF *_label;
public:
virtual bool init();
CREATE_FUNC(HudLayer);
void numCollectedChanged(int numCollected);
};
#endif /* defined(__MyTileMap__HudLayer__) */
这是一个继承至CCLayer的类,代表一个图层。它创建了一个private的成员变量来跟踪标签的显示,有一个辅助的方法来更新数值的显示。用下面代码代替HudLayer.cpp:
#include "HudLayer.h"
bool HudLayer::init()
{
if (!CCLayer::init()) {
return false;
}
CCSize winSize = CCDirector::sharedDirector()->getWinSize();
_label = new CCLabelTTF();
_label->initWithString("0", "Arial", 35.0);
_label->setColor(ccc3(255, 0, 0));
int margin = 20;
_label->setPosition(ccp(winSize.width - (_label->getContentSize().width/2) - margin, _label->getContentSize().height/2 + margin));
this->addChild(_label);
return true;
}
void HudLayer::numCollectedChanged(int numCollected)
{
CCString *labelCollected = new CCString();
labelCollected->initWithFormat("%d",numCollected);
_label->setString(labelCollected->getCString());
}
在init方法中,你创建一个标签,并作为子图层添加到这个图层中,在numCollectedChanged,你更新的标签的文本。
现在,让我们来使用这个图层,在HelloWorldScene.h中添加头文件:
#include "HudLayer.h"
然后声明两个private属性,一个是新建的图层,一个是收集的西瓜数目。HudLayer *_hud;
int _numCollected;
在HelloWorldScene.cpp中,添加下面代码到CCScene*scene()方法中,在return之前。HudLayer *hud = new HudLayer();
hud->init();
scene->addChild(hud);
layer->_hud = hud;
这创建了你的图层并添加到这个场景中。还把主图层中得成员变量hud设置到新创建的图层,以至于你有办法与它交互。最后,添加下面代码到setPlayerPosition,在收集图块的时候。
_numCollected++;
_hud->numCollectedChanged(_numCollected);
为了能够调用HudLayer中的方法你已经修改了HelloWorldScene当计数改变的时候,因此你可以相应的更新标签。编译-》运行工程,如果没错的话在右下角你就能看到西瓜的计数了。
Gratuituous Sound Effects and Music
简单的改变一下HelloWorldScene.cpp:
//在文件头部:
#include "SimpleAudioEngine.h"
//在init方法顶部
CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect("pickup.caf");
CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect("hit.caf");
CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect("move.caf");
CocosDenshion::SimpleAudioEngine::sharedEngine()->playBackgroundMusic("TileMap.caf");
//在setPlayerPosition里面,发生碰撞的时候
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("hit.caf");
//在setPlayerPosition里面,收集到西瓜的时候
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("pickup.caf");
//在setPlayerPosition里面,在设置player位置之前:
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("move.caf");
通过这个系列教程,至少你已经掌握了在cocos2d-x中使用tile map的相关重要概念。
这里是完整的游戏代码。
过完年,上班第一天,工作任务不是很多,大部分时间都用来自己学习。我喜欢!