在第一篇《》基础上,增加碰撞和拾取功能,原文《》,在这里继续以Cocos2d-x进行实现。有关源码、资源等在文章下面给出了地址。

步骤如下:

1.使用上一篇的工程;
2.打开Tiled Map Editor工具,菜单栏→"图层"→"添加图层",命名为"Meta"。这个层,我们将放入一些假的tile来代表"特殊tile"。菜单栏→"地图"→"新图块",点击"浏览",选择"Resources"目录下的meta_tiles.png文件,边距和间距设置成1像素点,点击"确定"。可以看到在"图块"窗口新增了一页,里面有红色和绿色两种tile,如下图所示:
1357867488_5438.png
3.确认"Meta"层被选中,选择工具栏上"图章刷",选择红色tile,绘制可碰撞区域,完成之后,大概如下图所示:
1357867505_7194.png
需要给这个tile设置属性来标识它,这样才能知道该tile具有碰撞属性。在"图块"窗口,右键红色tile,选择"图块属性",新建一个属性,名称为"Collidable",其值为"true",如下图所示:
1357867530_1799.png
点击"确定"。保存地图。
4.打开HelloWorldScene.h文件,添加如下声明:

1
CC_SYNTHESIZE_RETAIN(cocos2d::CCTMXLayer*, _meta, Meta);
HelloWorldScene.cpp
文件构造函数里,添加代码:

1
_meta =
NULL;
init
函数里,添加背景之后,添加如下代码:

1
2
this->setMeta(_tileMap->layerNamed(
"Meta"));
_meta->setVisible(
false);
这里把Meta层隐藏起来了,因为这只是作为阻挡的,不是真实可见的。添加一个新的方法,代码如下:

1
2
3
4
5
6
CCPoint HelloWorld::tileCoordForPosition(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);
}
这个方法将坐标转换成tile坐标,tile坐标系如下图所示:

1357867615_7209.png

修改
setPlayerPosition
函数,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void HelloWorld::setPlayerPosition(CCPoint position)
{
   CCPoint tileCoord =
this->tileCoordForPosition(position);
int tileGid = _meta->tileGIDAt(tileCoord);
if (tileGid)
   {
       CCDictionary *properties = _tileMap->propertiesForGID(tileGid);
if (properties)
       {
const CCString *collision = properties->valueForKey(
"Collidable");
if (collision && collision->compare(
"true") ==
0)
           {
return;
           }          
       }      
   }
   _player->setPosition(position);
}
在这里,我们将坐标转成tile坐标,获得这个tile坐标上的GID,再根据GID得到的属性字典,查找是否"
Collidable
"属性为"
true
",如果是则直接返回。

5.编译运行,可以看到忍者不能穿过红色区域了。如下图所示:

1357867688_9410.gif

6.动态修改Tiled地图。我们为忍者增加可以吃的东西,比如这里的西瓜。创建一个可拾取的前景层,当忍者从tile拾取东西时,就把这个tile从前景层中移除。菜单栏→"
图层
"→"
添加图层
",命名为"
Foreground
"。
注意
,若是之前有在"
Background
"层绘制过西瓜的,需要用底图块,比如这里的沙漠块填充覆盖,以免达不到吃西瓜的效果。然后,选中"
Foreground
"层,选择西瓜tile块,在地图上进行绘制。如下图所示:

1357867817_7940.png

为西瓜标识可拾取。选择"
Meta
"层,图块切换到"
meta
_tiles
"页,选择绿色tile,绘制到地图上西瓜tile区域。需要先把"
Meta
"层前置,点击菜单栏→"
图层
"→"
前置图层
",确保"
Meta
"层在最上层。如下图所示:

1357867831_4211.png

在"
图块
"窗口,右键绿色tile块,选择"图块属性",新建一个属性,名称为"
Collectable
",其值为"
true
"。点击"
确定
"。保存地图。

7.打开
HelloWorldScene.h
文件,添加如下声明:

1
CC_SYNTHESIZE_RETAIN(cocos2d::CCTMXLayer*, _foreground, Foreground);
HelloWorldScene.cpp
文件构造函数里,添加代码:

1
_foreground =
NULL;
init
函数里,添加背景之后,添加如下代码:

1
this->setForeground(_tileMap->layerNamed(
"Foreground"));
setPlayerPosition
函数检测"
Collidable
"属性之后,添加检测"
Collectable
"属性,代码如下:

1
2
3
4
5
6
const CCString *collectable = properties->valueForKey(
"Collectable");
if (collectable && collectable->compare(
"true") ==
0)
{
   _meta->removeTileAt(tileCoord);
   _foreground->removeTileAt(tileCoord);
}
8.编译运行,可以看到忍者把西瓜吃掉了,如下图所示:

1357868053_7929.gif

9.创建计分器。为忍者记录所吃西瓜的数量。我们创建一个新层
HelloWorldHud
来显示分数。在
HelloWorldScene.h
文件中,添加如下代码:

1
2
3
4
5
6
7
8
class HelloWorldHud :
public cocos2d::CCLayer
{
public:
virtual
bool init();
   CREATE_FUNC(HelloWorldHud);
void numCollectedChanged(
int numCollected);
   cocos2d::CCLabelTTF *lable;
};
HelloWorldScene.cpp
文件中,进行实现,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bool HelloWorldHud::init()
{
bool bRet =
false;
do
   {    
       CC_BREAK_IF(! CCLayer::init());
       CCSize winSize = CCDirector::sharedDirector()->getWinSize();
       lable = CCLabelTTF::create(
"0",
"Verdana-Bold",
18.
0, CCSizeMake(
50,
20), kCCTextAlignmentRight);
       lable->setColor(ccc3(
0,
0,
0));
int margin =
10;
       lable->setPosition(ccp(winSize.width - (lable->getContentSize().width /
2) - margin,
           lable->getContentSize().height /
2 + margin));
this->addChild(lable);
       bRet =
true;
   }
while (
0);
return bRet;
}
void HelloWorldHud::numCollectedChanged(
int numCollected)
{
   lable->setString(CCString::createWithFormat(
"%d", numCollected)->getCString());
}

接下去在HelloWorld类,添加HelloWorldHud层指针,在HelloWorldScene.h文件中HelloWorld类里,添加如下代码:

1
2
CC_SYNTHESIZE(
int, _numCollected, NumCollected);
CC_SYNTHESIZE_RETAIN(HelloWorldHud*, _hud, Hud);
HelloWorldScene.cpp
文件
HelloWorld
类构造函数里,添加代码:

1
2
_numCollected =
0;
_hud =
NULL;
scene()
函数里,添加如下代码:

1
2
3
4
HelloWorldHud *hud = HelloWorldHud::create();
scene->addChild(hud);
layer->setHud(hud);

setPlayerPosition函数,检测到"Collectable"属性为"true"时,添加如下代码:

1
2
_numCollected++;
_hud->numCollectedChanged(_numCollected);
10.编译运行,现在可以看到右下角有一个西瓜计分器,如下图所示:

1357867919_4191.png

11.增加音效和音乐。在
HelloWorldScene.cpp
文件
HelloWorld
init
函数里,添加如下代码:

1
2
3
4
CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect(
"pickup.wav");
CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect(
"hit.wav");
CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect(
"move.wav");
CocosDenshion::SimpleAudioEngine::sharedEngine()->playBackgroundMusic(
"TileMap.wav");
setPlayerPosition
函数,检测到"
Collidable
"属性为"
true
",添加如下代码:

1
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect(
"hit.wav");
检测到"
Collectable
"属性为"
true
",添加如下代码:

1
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect(
"pickup.wav");
在设置玩家坐标前,添加如下代码:

1
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect(
"move.wav");
12.编译运行,现在忍者的行动将有配乐,效果图:

1357868429_4995.gif

参考资料:

1.Collisions and Collectables: How To Make a Tile-Based Game with Cocos2D Part 2

2.碰撞与拾取:如何使用Cocos2D制作一款基于tile的游戏第2部分

3.(译)碰撞检测和收集物品:如何使用cocos2d制作基于tiled地图的游戏:第二部分

非常感谢以上资料,本例子源代码附加资源下载地址:

如文章存在错误之处,欢迎指出,以便改正。