根据一个TMX文件来创建一个背景地图
根据一个TMX文件以及资源路径,来创建一个背景地图
本文实践自 Ray Wenderlich 的文章《How To Make a Tile-Based Game with Cocos2D》,文中使用Cocos2D,我在这里使用Cocos2D-x 2.0.4进行学习和移植。这个游戏是关于一个忍者在沙漠中寻找西瓜的故事。
在这部分内容,将会学习到如何用Tile创建一个地图,如何加载地图到游戏,如何让地图跟随玩家滚动,以及如何使用对象层。下一部分内容,将介绍如何在地图中创建可碰撞的区域,如何使用Tile属性,如何创建可拾取的物品和动态修改地图,以及如何确保忍者不会吃撑掉。
步骤如下:
1.新建Cocos2d-win32工程,工程名为"TileGame",去除"Box2D"选项,勾选"Simple Audio Engine in Cocos Denshion"选项;
2.下载本游戏所需的资源,将资源放置"Resources"目录下;
3.使用Tiled工具制作地图。首先,下载开源的Tiled Map Editor工具,当前版本为0.9。在Tiled工具,点击菜单栏→"文件"→"新文件",在弹出的对话框中,填入如下内容:
地图方向分为:正常、45度。地图大小填入的是tile单位。块大小是资源每个tile的实际像素大小,在本篇中,使用32x32大小。点击"确定"。
4.接着,把所需要的tile集合加入到工具中。菜单栏→"地图"→"新图块",填入如下内容:
点击"浏览",选择"Resources"目录下的tmw_desert_spacing.png文件,会自动填充"名称"内容。块的宽高都为32像素。边距就是当前tile块开始计算实际像素时,应该跳过多少像素,宽高一样。间距就是两个tile块之间的像素距离,宽高一样。看看tmw_desert_spacing.png文件,可以看到每个tile块都1像素的黑色边框,这就是为什么要设置边距和间距为1像素。
点击"确定"。
5.可以看到tile块出现在"图块"窗口中。现在可以开始画地图。点击菜单栏→"视图"→"显示网格",可以开启网格参照线。点击工具栏"图章刷",然后在"图块"窗口点选一个tile块,接着在地图中,点击放入你所想要的位置。
用工具栏"填充",把地图背景填充成同一个tile块,这里为沙漠背景。可以从"图块"窗口点选多个tile块,可按Ctrl键多选,这样可以一次性将多个tile块放入地图中。进行制作地图,确保至少有一对建筑在地图上,因为后面需要一些东西来做碰撞。一旦完成了地图的制作,双击"图层"窗口中的当前层"块层 1",重命名为"Background"。然后点击工具栏"保存",命名为"TileMap.tmx",保存在"Resources"目录下。
6.将Tile地图加入到场景中。在HelloWorldScene.h文件中,添加如下代码:
CC_SYNTHESIZE_RETAIN(cocos2d::CCTMXTiledMap*, _tilemap, Tilemap);
CC_SYNTHESIZE_RETAIN(cocos2d::CCTMXLayer*, _background, Background);
在HelloWorldScene.cpp文件中,构造函数添加如下:
HelloWorld::HelloWorld()
{
_tilemap = NULL;
_background = NULL;
}
初始化init函数,修改如下:
bool HelloWorld::init() { bool bRet = false; do { CC_BREAK_IF(! CCLayer::init()); this->setTileMap(CCTMXTiledMap::create("TileMap.tmx")); this->setBackground(_tileMap->layerNamed("Background")); this->addChild(_tileMap, -1); bRet = true; } while (0); return bRet; }
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
bool HelloWorld::init()
{ bool bRet = false; do { CC_BREAK_IF(! CCLayer::init()); this->setTileMap(CCTMXTiledMap::create( "TileMap.tmx")); this->setBackground(_tileMap->layerNamed( "Background")); this->addChild(_tileMap, - 1); bRet = true; } while ( 0); return bRet; } |
7.编译运行,可以看到整个地图的左下角,如下图所示:
要让它成为一个游戏,还需要三件事:玩家、玩家初始点、移动视图以便能够看到玩家。
8.Tiled支持两种类型层:tile层和对象层。对象层允许你在地图上可能发生事件的区域绘制矩形。比如:你可能需要一个怪物出生区域,或者一个一进入就会挂掉的区域。在这里,我们给玩家创建一个出生地。菜单栏→"图层"→"添加对象层",命名为"Objects",点击工具栏"插入对象",在地图上选择一个位置并点击它,这时会出现一个灰色的矩形框,右键选择"对象属性",名称填入"SpawnPoint",如下图所示:
按"确定"按钮。保存地图。
9.在HelloWorldScene.h文件中,添加如下声明:
1
|
CC_SYNTHESIZE_RETAIN(cocos2d::CCSprite*, _player, Player);
|
在HelloWorldScene.cpp构造函数中,添加如下:
1
|
_player =
NULL;
|
在init初始化函数,添加如下:
1
2 3 4 5 6 7 8 9 10 11 12 |
CCTMXObjectGroup *objects = _tileMap->objectGroupNamed(
"Objects");
CCAssert(objects != NULL, "Objects' object group not found"); CCDictionary *spawnPoint = objects->objectNamed( "SpawnPoint"); CCAssert(spawnPoint != NULL, "SpawnPoint object not found"); int x = spawnPoint->valueForKey( "x")->intValue(); int y = spawnPoint->valueForKey( "y")->intValue(); this->setPlayer(CCSprite::create( "Player.png")); _player->setPosition(ccp(x, y)); this->addChild(_player); this->setViewpointCenter(_player->getPosition()); |
添加一个方法setViewpointCenter,代码如下:
用户传任何的坐标过来,但是有一些点不希望显示出来,比如,我们不想屏幕移出地图边界,那里只是一片空白。如图:
摄像机的中心若是小于winSize.width/2或winSize.height/2,部分的视图将会在屏幕之外。同样的,我们需要检查边界条件。到目前为止,我们一直把这个函数看作是设置摄像机中心的位置。但是,这并不是我们实际做的。实际做的是移动整个层。看下图:
想象一下,身处在一个巨大的世界中,我们能看到的区域是从0到winSize.height/width的这部分。我们视野的中心是centerOfView,并且我们知道要让中心在哪儿(actualPosition)。所以要让我们视野的中心向上向右移动到actualPosition,我们只需要让地图相对的向下向左移动即可。这一步是通过actualPosition与centerOfView坐标相减得到的,然后把HelloWorld层的坐标设为这个结果。
10.编译运行,可以看到忍者在屏幕中,如下图所示:
11.接下来,让忍者可以移动。在HelloWorldScene.cpp文件init函数中,添加如下代码:
1
|
this->setTouchEnabled(
true);
|
开启触摸,然后重载registerWithTouchDispatcher函数,代码如下:
1
2 3 4 |
void HelloWorld::registerWithTouchDispatcher(
void)
{ CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate( this, 0, true); } |
注册触摸事件,这样单点触摸ccTouchBegan和ccTouchEnded会被调用。重载ccTouchBegan函数,代码如下:
1
2 3 4 |
bool HelloWorld::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent)
{ return true; } |
返回true表明接受这个触摸。添加setPlayerPosition方法,来设置玩家坐标,代码如下:
1
2 3 4 |
void HelloWorld::setPlayerPosition(CCPoint position)
{ _player->setPosition(position); } |
重载ccTouchEnded方法,代码如下:
计算触摸点与玩家坐标之间的差值,来决定玩家的移动方向。
convertToNodeSpace函数就是把OpenGL的坐标转换成CCLayer的坐标。
12.编译运行,点击屏幕,让忍者动起来,如下图所示:
参考资料:
1.How To Make a Tile-Based Game with Cocos2D http://www.raywenderlich.com/1163/how-to-make-a-tile-based-game-with-cocos2d
2.如何使用Cocos2D制作一款基于tile的游戏 http://www.raywenderlich.com/zh-hans/16771/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8cocos2d%E5%88%B6%E4%BD%9C%E4%B8%80%E6%AC%BE%E5%9F%BA%E4%BA%8Etile%E7%9A%84%E6%B8%B8%E6%88%8F
3.(译)如何使用cocos2d制作基于tile地图的游戏教程:第一部分http://www.cnblogs.com/zilongshanren/archive/2011/04/11/2012852.html
非常感谢以上资料,本例子源代码附加资源下载地址:http://download.csdn.net/detail/akof1314/4966622
iOS版下载地址 : http://vdisk.weibo.com/s/G8HUO
地图编辑工具: http://pan.baidu.com/share/link?shareid=1613009067&uk=3189484501
在第一篇《如何制作一个基于Tile的游戏》基础上,增加碰撞和拾取功能,原文《Collisions and Collectables: How To Make a Tile-Based Game with Cocos2D Part 2》,在这里继续以Cocos2d-x进行实现。有关源码、资源等在文章下面给出了地址。
步骤如下:
1.使用上一篇的工程;
2.打开Tiled Map Editor工具,菜单栏→"图层"→"添加图层",命名为"Meta"。这个层,我们将放入一些假的tile来代表"特殊tile"。菜单栏→"地图"→"新图块",点击"浏览",选择"Resources"目录下的meta_tiles.png文件,边距和间距设置成1像素点,点击"确定"。可以看到在"图块"窗口新增了一页,里面有红色和绿色两种tile,如下图所示:
3.确认"Meta"层被选中,选择工具栏上"图章刷",选择红色tile,绘制可碰撞区域,完成之后,大概如下图所示:
需要给这个tile设置属性来标识它,这样才能知道该tile具有碰撞属性。在"图块"窗口,右键红色tile,选择"图块属性",新建一个属性,名称为"Collidable",其值为"true",如下图所示:
点击"确定"。保存地图。
4.打开HelloWorldScene.h文件,添加如下声明:
1
|
CC_SYNTHESIZE_RETAIN(cocos2d::CCTMXLayer*, _meta, Meta);
|
1
|
_meta =
NULL;
|
1
2 |
this->setMeta(_tileMap->layerNamed(
"Meta"));
_meta->setVisible( false); |
1
2 3 4 5 6 |
CCPoint HelloWorld::tileCoordForPosition(CCPoint position)
{ //将人物的目的的坐标的x轴坐标转换成瓦片地图中的x轴的坐标 int x = position.x / _tileMap->getTileSize().width;//将人物的目的的坐标的y轴坐标转换成瓦片地图中的y轴的坐标 int y = ((_tileMap->getMapSize().height * _tileMap->getTileSize().height) - position.y) / _tileMap->getTileSize().height;return ccp(x, y); } |
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); } |
1
|
CC_SYNTHESIZE_RETAIN(cocos2d::CCTMXLayer*, _foreground, Foreground);
|
1
|
_foreground =
NULL;
|
1
|
this->setForeground(_tileMap->layerNamed(
"Foreground"));
|
1
2 3 4 5 6 |
const CCString *collectable = properties->valueForKey(
"Collectable");
if (collectable && collectable->compare( "true") == 0) { _meta->removeTileAt(tileCoord); _foreground->removeTileAt(tileCoord); } |
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; }; |
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); |
1
2 |
_numCollected =
0;
_hud = NULL; |
1
2 3 4 |
HelloWorldHud *hud = HelloWorldHud::create();
scene->addChild(hud); layer->setHud(hud); |
在setPlayerPosition函数,检测到"Collectable"属性为"true"时,添加如下代码:
1
2 |
_numCollected++;
_hud->numCollectedChanged(_numCollected); |
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"); |
1
|
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect(
"hit.wav");
|
1
|
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect(
"pickup.wav");
|
1
|
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect(
"move.wav");
|
非常感谢以上资料,本例子源代码附加资源下载地址:http://download.csdn.net/detail/akof1314/4983778
iOS版下载地址(1): http://pan.baidu.com/share/link?shareid=2062648195&uk=3189484501
iOS版下载地址(2):http://vdisk.weibo.com/s/HWUeY
学习的路上,与君共勉。