cocos2d-x Tile Map教程(二)

原文来自: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的相关重要概念。

这里是完整的游戏代码


过完年,上班第一天,工作任务不是很多,大部分时间都用来自己学习。我喜欢!



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值