【木头Cocos2d-x 014】游戏实例-《跑跑跑》制作教程(第六篇)——添加障碍物

笨木头  2012-11-18 21:31   2.0游戏实例《跑跑跑》,Cocos2d-x2.0   阅读(8,797)   24条评论

Cocos2d-x游戏实例《跑跑跑》制作教程(第六篇)——添加障碍物

 

笨木头花心贡献,啥?花心?不呢,是用心~

转载请注明,原文地址
http://www.benmutou.com/archives/32

正文:

 

注:本文使用到的资源请到这里下载:http://download.csdn.net/detail/musicvs/4769412

哎呀,这游戏实在是太好玩太有意思了(啊,个屁啊~),不过,我们还是要继续优化的喇~

这次我们就来为地图添加障碍物吧~很好玩的,一起来~

 

1.      
再次打开我们的Tiled Map Editor

我们还要打开level01.tmx文件,首先新建一个图层,命名为barrier,也就是我们的障碍层。

游戏实例-《跑跑跑》制作教程(第六篇)——添加障碍物

然后,我们要介绍一个新的素材,meta_tiles.png

游戏实例-《跑跑跑》制作教程(第六篇)——添加障碍物

objects.pngmeta_tiles.png都加进图块里,宽高都是32像素。

游戏实例-《跑跑跑》制作教程(第六篇)——添加障碍物

2.      
添加障碍物

好,现在,我们利用下面的素材在地图的barrier层上画几笔,增加一些障碍物。

游戏实例-《跑跑跑》制作教程(第六篇)——添加障碍物

 

下面是我画的,太好看了~(啊才怪!):

游戏实例-《跑跑跑》制作教程(第六篇)——添加障碍物

 

3.      
让障碍物真正成为障碍物!

别以为它叫做障碍层那它就真的可以阻止主角的前进,主角现在一样能顺利的穿过这些障碍物,我们还得新建一个层,叫做meta

游戏实例-《跑跑跑》制作教程(第六篇)——添加障碍物

然后选择图块里的红色方块:

游戏实例-《跑跑跑》制作教程(第六篇)——添加障碍物

然后,右键,图块属性,给图块新建一个属性,叫做Collidable,值为true

游戏实例-《跑跑跑》制作教程(第六篇)——添加障碍物

是的,我们将会利用这个属性判断是否为障碍物。

现在,我们要用红色方块在meta层上画东西,怎么画?把所有障碍物填满,记住,是在meta层上描。

看看效果,好多红色,哈哈:

游戏实例-《跑跑跑》制作教程(第六篇)——添加障碍物

OK,保存地图,然后把焦点回到代码里。

 

4.      
通过图块属性判断是否可以通行

大家,我又要改了,我要把PlayerCCTMXTiledMap* map成员变量放到Entity里,同时添加一个函数tileCoordForPosition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#ifndef __ENTITY_H__
#define __ENTITY_H__

#include "cocos2d.h"
#include "Controller.h"
#include "ControllerListener.h"

using namespace cocos2d;

class Entity : public CCNode, public ControllerListener {
public:
void setSprite(CCSprite* mSprite);
void setController(Controller* controller);

/* 实现SimpleMoveListener接口的方法 */
virtual void setSimplePosition(int x, int y);
virtual CCPoint getCurPosition();
protected:
CCSprite* mSprite;
Controller* mController;

/* map本来是在Player类的,现在我们放在这里 */
CCTMXTiledMap* map;

/* 检测碰撞的地图层 */
CCTMXLayer* meta;

/* 将像素坐标转换为地图格子坐标*/
CCPoint tileCoordForPosition(CCPoint pos);
};

#endif

tileCoordForPosition是做什么的呢?它是用来把像素坐标转换为地图里相应的格子的。

1
2
3
4
5
6
7
8
9
10
11
12
13
CCPoint Entity::tileCoordForPosition( CCPoint pos )
{
CCSize mapTiledNum = map->getMapSize();
CCSize tiledSize = map->getTileSize();

float x = pos.x / tiledSize.width;
float y = pos.y / tiledSize.height;

/* Cocos2d-x的默认Y坐标是由下至上的,所以要做一个相减操作 */
y = mapTiledNum.height - y;

return ccp(x, y);
}

刚刚大家一定发现了,我们还多了一个CCTMXLayer* meta,这是啥?meta不就是我们刚刚给地图加的一个图层么?在这个图层上面我们画了很多红色格子的,而且我们还给这些红色格子加了一个Collidable属性。

我想大家应该要猜到了,我们只要判断主角即将要去的地方是不是在红色格子的位置上就可以了,如果要去的地方是红色格子的位置,那就不让主角移动~哇,听起来好简单~

 

5.      
开始限制主角的行动

现在,请打开Player.cpp类的initWithTiledMap函数,在最开始加上这句代码(什么,你确定是一句?看起来好像是两句吧):

1
2
3
/* 加载meta层 */
meta = map->layerNamed("meta");
meta->setVisible(false);

OK,最后一步,我们修改Player.cppsetSimplePosition函数,在最前面加上以下几句代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* -----------------判断是否不可通行---------------- */
/* 获得当前主角在地图中的格子位置 */
CCPoint tiledPos = tileCoordForPosition(ccp(x, y));

/* 获取地图格子的唯一标识 */
int tiledGid = meta->tileGIDAt(tiledPos);

/* 不为0,代表存在这个格子 */
if(tiledGid != 0) {
/*
获取该地图格子的所有属性,目前我们只有一个Collidable属性,
格子是属于meta层的,但同时也是属于整个地图的,所以在获取格子的所有属性时,
通过格子唯一标识在地图中取得。
*/

CCDictionary* propertiesDict = map->propertiesForGID(tiledGid);

/* 取得格子的Collidable属性值 */
const CCString* prop = propertiesDict->valueForKey("Collidable");

/* 判断Collidable属性是否为true,是的话,不让玩家移动 */
if(prop->m_sString.compare("true") == 0) {
return;
}
}

好吧,我感觉代码的注释已经很详细了,首先从meta层里获得某个地图格子(某个?咳咳,就是我们主角当前所在的地图格子位置)在地图中的唯一标识。然后根据这个唯一标识在地图中取得格子的所有属性。

有个疑问,为什么要从meta中取得唯一标识而不是直接格局格子位置从地图中获取格子的属性?因为,因为地图有很多个图层啊,我怎么知道你的这个格子位置是那一层的位置啊?你说你要第一行第三列的那个格子,天呐,我有ground图层,有meta图层,你是要哪个?糟糕,我太入戏的,一不小心用了第一人称。所以呢,不要让程序混乱,否则它会让你更混乱~

最后当然是取得格子的Collidable属性,看看这个属性的值是否为true,是的话,就不让主角前进了。看,主角被那颗高大威猛的树挡着了,不能前进了~

游戏实例-《跑跑跑》制作教程(第六篇)——添加障碍物

 

6.      
解决卡死问题

我们确实是卡住主角,不让他前进了,但是我们会发现,这一卡就把主角永远卡住了,他再也无法移动了~!这很糟糕。没关系,我们加点东西就好。当遇到障碍物时,不是让主角停止前进,而是让主角后退一个像素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 判断Collidable属性是否为true,是的话,不让玩家移动 */ if(prop->m_sString.compare("true") == 0) {
if(x > 0) {
x -= 1;
}
else {
x += 1;
}

if(y > 0) {
y -= 1;
}
else {
y += 1;
}
}

我们修改了setSimplePosition里的一个小地方,当Collidable属性的值为true时,我们不要直接return,而是让主角稍微往相反方向移动一个像素,这样主角就不会永远动不了了。

 

OK,本篇结束了,下一篇我们将加入胜利判断条件,以及加入可以让主角吃掉的东西,太帅了~

24 评论

  1. 楼主,在runAction(actions)之后需不需要return;就是说if判断成功后,要不要执行Entity::setTagPosition(p);setViewPointByPlayer();

  2. /* 获取地图格子的唯一标识 */
    int tiledGid = meta->tileGIDAt(tiledPos);
    中tileGIDAt和书中的getTileGIDAt有啥区别啊?

      1. 哦,谢谢,我正在学习木头大神的著作,哈哈。同时我也在一篇篇看你的博客。虽然起步有点晚,但是也一步步往前走,嘿嘿。

  3. 怎么感觉mete层没啥用尼?meta层图块添加的属性如果直接添加在barrier层上的图块上,同样可以实现碰撞检测的效果。meta层岂不多此一举?还请木头解答 另外,Player里有必要存储controller的引用么?感觉没什么用啊。

    1. meta层单独出来的意义其实就是分清职责,否则,以后要添加的东西越来越多,我们就会越来越难弄明白之前的思路了~至于Controller的引用,我已经忘了(这么久之前的代码,不会要我重新去看一遍吧O_O),你看看它在哪个地方被使用了,那就是有用了不过,这代码也蛮久了,不一定全都具有参考价值~

      1. 我的想法比较单纯,将属性直接加到barrier层里的图块上。mete层与barrier层图块位置完全相同,将属性添加到自身不是更加明朗么

        1. 这样还是不够灵活,万一同一个图块(比如石头),它可能是障碍物,也可能是让主角掉血的特殊石头,这时候,你把属性设置到石头图块上,就无法实现了。另外,石头包含了好几个图块,你得同时给这些图块设置属性。而使用meta层只需每个图块设置一种属性,不管怎么说,还是得用meta会更灵活,扩展起来也方便~

            1. 这就不是三两句话可以说清楚喇,这应该可以成为争论的话题了~反正我是倾向于组合,继承只是用于有明显父子类关系的对象,比如角色作为父类,英雄和怪物作为其子类。但英雄本身我就不会去继承Sprite,而是将Sprite作为其组件~但使用组合确实也有一些麻烦的地方~不过..也只能这样了~

        1. 这个确实是为了扩展,有了基类,我们只需要知道这是一个控制器,但至于是什么控制器,由其具体的子类来决定。我们只需要给主角添加一个控制器,不需要知道这个控制器具体是哪个子类。所以,当需求改变时,比如主角现在要八个方向都能移动,那,我们只需要新增一个控制器之类,然后给主角的控制器赋值就可以了。

  4. 我的更奇怪,int tiledGid = meta->tileGIDAt(tilepos);这里返回的始终都是118,CCDictionary* propertiesdict = this->map->propertiesForGID(tiledGid);这个地方获取出来的时候始终都为空,不知道为什么?

  5. 一直会提示CCTMXLayer.cpp中的unsigned int CCTMXLayer::tileGIDAt(const CCPoint& pos, ccTMXTileFlags* flags)的CCAssert(pos.x < m_tLayerSize.width && pos.y < m_tLayerSize.height && pos.x >=0 && pos.y >=0, “TMXLayer: invalid position”);处发生中断,我们不会调用到这个函数啊,只传一个参数,那就是unsigned int CCTMXLayer::tileGIDAt(const CCPoint& pos)这个函数啊,我纠结死啊。昨晚上到现在都没弄好。

  6. 这个程序中照着敲有个问题,是关于CCTMXLayer *meta;
    运行程序会出现程序崩了的情况,这个原因是Player里面加载meta时出现的错误。
    bool Player::initWithTiledMap(CCTMXTiledMap *map)
    {

    。。。。。
    //这里加载的是meta不是mate,改过来就好了
    meta=map->layerNamed("meta");

    meta->setVisible(false);

    }

    楼主我已经加群了,知道我是谁不。楼主fighting

发表评论

电子邮件地址不会被公开。 必填项已用*标注