Cocos2d-x3.0游戏实例之《别救我》第九篇——从tmx文件中加载关卡怪物

笨木头  2014-05-2 21:05   3.0游戏实例《别救我》,Cocos2d-x,Cocos2d-x3.0   阅读(20,749)   72条评论

 

 

上一篇我们已经制作好tg1.tmx文件了,现在就要使用它了。

 

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

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

文章来源:笨木头与游戏开发

 

很抱歉,我们又要新建2个类了,我已经尽力少新建类了,毕竟是教程,类越多越容易混乱。

 

我们要新建一个Monster类,以及一个MonsterLayer类,专门添加Monster对象。

 

Monster类

来看看Monster.h文件:


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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#ifndef Monster_H

#define Monster_H

 

#include "cocos2d.h"

USING_NS_CC;

 

class Monster : public Node 

{

public:

Monster();

~Monster();

    static Monster* create(int ID);

    bool init(int ID);

 

    /* 加入到当前场景的物理世界 */

    void joinToWorld(Node* parent);

 

    CC_SYNTHESIZE(int, m_ID, ID);

    CC_SYNTHESIZE(float, m_fShowTime, fShowTime);

    CC_SYNTHESIZE(int, m_iPosX, iPosX);

    CC_SYNTHESIZE(int, m_iPosY, iPosY);

    CC_SYNTHESIZE(int, m_iAtk, iAtk);

    CC_SYNTHESIZE(Value, m_sModelPath, sModelPath);

};

 

#endif

很简单,有几个成员变量:

1. m_ID

2. m_fShowTime就是记录什么时候出场

3. m_sModelPath就是怪物的图片资源名称,Value是3.0的新特性,可以理解为CCString、CCInteger等的合体

4. m_iPosX和m_iPosY是坐标不解释(小若:已经解释了~)~

5. m_iAtk就是怪物的攻击力

 

主要关注一下joinToWorld函数,这个函数是让Monster加入到物理世界的,为什么不创建Monster对象的时候就让它加入到物理世界呢?

原因很简单,因为我们的怪物不是一开始就出现的,是随着游戏时间慢慢出现的,所以不能一开始就加入到世界中。

 

然后,来看看Monster.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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include "Monster.h"

#include "scene\ObjectTag.h"

Monster::Monster()

{

}

 

Monster::~Monster()

{

}

 

Monster* Monster::create(int ID)

{

    auto monster = new Monster();

    

    if(monster && monster->init(ID)) 

    {

    monster->autorelease();

    }

    else

    {

    CC_SAFE_DELETE(monster);

    }

    

    return monster;

}

 

bool Monster::init(int ID)

{

    this->m_ID = ID;

 

    /* 这里以后要改成读取Json配置文件,暂时写死 */

    if (ID == 1)

    {

        m_sModelPath = Value("item2.png");

m_iAtk = 1;

    }

    else if (ID == 2)

    {

        m_sModelPath = Value("heart_red.png");

m_iAtk = -1;

    }

    return true;

}

 

为了别一下子太复杂,joinToWorld函数我暂时没有贴出来。

这里主要看看init函数,Monster对象是根据ID来创建的,不同的ID代表不同的怪物类型,目前我们只有两种类型。

 

因为还没讲解到Json的部分,所以init函数暂时写死,但是大概意思是一样的:根据怪物ID加载不同的配置。

 

好,再来看看joinToWorld函数:


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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
void Monster::joinToWorld(Node* parent)

{

    Sprite* sp = Sprite::createWithSpriteFrameName(m_sModelPath.asString().c_str());

 

    /* 创建刚体 */

    PhysicsBody* body = PhysicsBody::createBox(sp->getContentSize());

    body->setCategoryBitmask(1);    // 0001

    body->setCollisionBitmask(1);   // 0001

    body->setContactTestBitmask(1); // 0001

 

    /* 精灵居中 */

    sp->setPosition(Point(sp->getContentSize().width * 0.5f, sp->getContentSize().height * 0.5f));

 

    /* 精灵作为Monster的表现,添加到Monster身上 */

    this->addChild(sp);

 

    /* 设置怪物Tag类型 */

    this->setTag(ObjectTag_Monster);

 

    /* 精灵作为Monster的表现,Monster本身没有大小,所以要设置一下大小 */

    this->setContentSize(sp->getContentSize());

 

    /* 刚体添加到Monster本身,而不是精灵身上 */

    this->setPhysicsBody(body);

 

    /* 设置坐标 */

    this->setPosition(Point(getiPosX(), getiPosY()));

 

    /* Monster加入到物理世界 */

    parent->addChild(this);

 

}

代码注释已经够详细的了,大概的意思就是:

1. 创建一个Sprite对象,用于表现Monster的样子

2. 创建一个刚体,用于做物理碰撞

3. 设置怪物Tag类型

4. 将刚体添加到Monster上

5. Monster根据自身属性进行设置(比如设置坐标,完整版还有出场音效、颜色之类的)

6. 将Monster添加到物理世界

 

MonsterLayer 怪物层

为了不要让TollgateScene的代码太过臃肿(虽然它已经很臃肿了),我不得不再创建一个MonsterLayer类。

 

看看MonsterLayer.h文件:


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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#ifndef MonsterLayer_H

#define MonsterLayer_H

 

#include "cocos2d.h"

USING_NS_CC;

 

class Monster;

class MonsterLayer : public Layer 

{

public:

MonsterLayer();

~MonsterLayer();

CREATE_FUNC(MonsterLayer);

virtual bool init();

 

    void logic(float dt);

private:

    /* 从配置文件中加载怪物数据 */

    void loadMonstersFromTmxConf();

 

    /* 存放所有即将要出场的怪物对象 */

    Vector<Monster*> m_monsterVec;

 

    /* 计时器 */

    float m_fTimeCounter;

};

 

#endif

噗,好简单,你不用解释了~

哦,好吧,既然如此,我就不解释了。

(小若:喂,别在那里自己表演对白好吗?!谁说不用解释的?)

 

好吧,大概的意思的是:

1. loadMonstersFromTmxConf函数,很明显就是从我们生成的TiledMap关卡文件里读取数据,然后创建Monster对象了

2. m_monsterVec,这就是一个列表,用来保存读取到的所有怪物对象。而Vector也是3.0的新特性,封装了C++的Vector容器

3. m_fTimeCounter是计时器,因为怪物是达到某个时间才会出现的~

 

然后再来看看MonsterLayer.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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include "MonsterLayer.h"

#include "entity\Monster.h"

 

MonsterLayer::MonsterLayer()

{

    m_fTimeCounter = 0;

}

 

MonsterLayer::~MonsterLayer()

{
    SpriteFrameCache::getInstance()->removeSpriteFramesFromFile("monster.plist");
}

 

bool MonsterLayer::init()

{

    if (!Layer::init())

    {

        return false;

    }
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("monster.plist");
 

    /* 加载关卡的怪物配置 */

    loadMonstersFromTmxConf();

    return true;

}

好,好简单~

(小若:啊才怪啊,少了两个函数别以为我不知道)

 

OK,刚刚贴出来的一看就知道什么意思了,我就不说了,我们来看看loadMonstersFromTmxConf函数是怎么实现的:


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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void MonsterLayer::loadMonstersFromTmxConf()

{

    /* 加载地图 */

    TMXTiledMap* map = TMXTiledMap::create("tg1.tmx");

    this->addChild(map);

 

    /* 加载怪物对象层的所有对象 */

    TMXObjectGroup* objGroup = map->getObjectGroup("monster");

    ValueVector objects = objGroup->getObjects();

 

    /* 遍历所有对象 */

    for (const auto v : objects)

    {

        const ValueMap dict = v.asValueMap();

 

        int id = dict.at("id").asInt();

        float fShowTime = dict.at("showTime").asFloat();

        int iPosX = dict.at("x").asInt();

        int iPosY = dict.at("y").asInt();

 

        /* 创建怪物对象,并保存起来 */

        Monster* monster = Monster::create(id);

        monster->setID(id);

        monster->setfShowTime(fShowTime);

        monster->setiPosX(iPosX);

        monster->setiPosY(iPosY);

 

        /* 保存怪物对象 */

        m_monsterVec.pushBack(monster);

    }

}

我对自己的注释还是比较有信心的,所以,我再讲解一下(小若:好有自信= =)

1. 首先,加载TiledMap地图对象(TMXTiledMap)

2. 然后获取对象层(TMXObjectGroup)也就是我们之前一直大做文章的monster对象层了

3. 从monster对象层中获取所有的对象列表,也就是我们之前画的那一堆矩形

4. 每一个矩形对象取出来之后就是一个ValueMap,这也是3.0封装好的,功能和map一样(当然,用法有点点区别)

5. 从ValueMap中取出所有的属性,然后创建一个Monter对象,给Monster对象赋值

6. 把Monster对象添加到m_monsterVec列表,保存好

7. 大功告成

 

这个函数完成之后,我们就得到了所有的怪物对象,这些怪物对象都已经跟进关卡配置文件赋值好了。

怎么样,很简单吧~

 

最后,我们看看MonsterLayer的logic函数,重点中的重点(小若:算了吧你,重点这个词被你用烂了好吧)


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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void MonsterLayer::logic(float dt)

{

    /* 计时 */

    m_fTimeCounter += dt;

 

    /* 记录本次出场的怪物 */

    Vector<Monster*> deleteVec;

 

    /* 让达到出场时间的怪物添加到物理世界 */

    for (auto monster : m_monsterVec)

    {

        /* 达到时间,可以出场了 */

        if (m_fTimeCounter >= monster->getfShowTime())

        {

            monster->joinToWorld(this);

 

            /* 记录本次出场的怪物,然后删除掉 */

            deleteVec.pushBack(monster);

        }

    }

 

    /* 删除已经添加到物理世界的怪物,避免重复出场 */

    for (auto monster : deleteVec)

    {

        m_monsterVec.eraseObject(monster, false);

    }

}

虽然我对自己的注释很有自信,但是,我还是解释一下:

1. m_fTimeCounter计时器累计时间

2. 遍历怪物列表,判断有没有哪只怪物已经达到出场时间,可以出场的

3. 调用可出场怪物的joinToWorld函数,这样怪物就会出现在游戏中了

4. 把已经出场过的怪物对象从m_monsterVec中删除,避免下次又重复出场

 

完成喇~

木了个头的,真是没想到,就这么简单的几个类,竟然要花这么长时间讲解。

而且,本篇竟然还不是最后一篇~!(小若:那你在这么发表什么获奖感言啊)

 

我们来试试加载怪物有没有成功吧~

 

同样的,给TollgateScene添加一个成员变量(我就不贴详细的代码了):


1
MonsterLayer* m_monsterLayer;

 

然后修改TollgateScene的scene函数,把MonsterLayer添加到scene里:


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
33
34
35
36
37
38
39
40
41
Scene* TollgateScene::scene()

{

    auto scene = Scene::createWithPhysics();

 

    /* 这里省略了很多代码 */

 

    /* 背景层 */

    auto backgroundLayer = BackgroundLayer::create();

    scene->addChild(backgroundLayer, 0);

 

    /* 怪物层 */

    auto monsterLayer = MonsterLayer::create();

    scene->addChild(monsterLayer, 1);

 

    auto layer = TollgateScene::create();

    scene->addChild(layer, 10);

 

    layer->m_backgroundLayer = backgroundLayer;

    layer->m_monsterLayer = monsterLayer;

    return scene;

}

 

最后,别忘了在TollgateScene的logic里调用MonsterLayer的logic函数:


1
2
3
4
5
6
7
8
9
void TollgateScene::logic(float dt)

{

    m_backgroundLayer->logic(dt);

    m_monsterLayer->logic(dt);

}

 

OK,运行游戏,我们看看怪物出现没?

 

Cocos2d-x3.0游戏实例之《别救我》第九篇

Cocos2d-x3.0游戏实例之《别救我》第九篇

怪物会根据我们配置的时间出现。

 

OK,到此,《别救我》的核心功能算是完成了,但是它完全还不能算是一个游戏。

 

下一篇,我们再介绍一个核心功能——读取Json配置文件

 

 

72 评论

  1. 木头还在吗 运行时中断了 提示
    0x0016C425 处的第一机会异常(在 Myproj.exe 中): 0xC0000005: 读取位置 0xCDCDCFD9 时发生访问冲突。
    0x0016C425 处有未经处理的异常(在 Myproj.exe 中): 0xC0000005: 读取位置 0xCDCDCFD9 时发生访问冲突。
    也就是
    /* 计时 */
    m_fTimeCounter += dt;
    这里有问题指针

  2. suffix = csbtextureSize = 0classname = Nodesize = 3classname = Buttoncocos2d: fullPathForFilename: No file found at Default/Button_Press.png. Possible missing file.cocos2d: fullPathForFilename: No file found at Default/Button_Disable.png. Possible missing file.callBackName cannot be foundsize = 0child = 050CDA48classname = Buttoncocos2d: fullPathForFilename: No file found at Default/Button_Press.png. Possible missing file.cocos2d: fullPathForFilename: No file found at Default/Button_Disable.png. Possible missing file.callBackName cannot be foundsize = 0child = 050F8E40classname = Buttoncocos2d: fullPathForFilename: No file found at Default/Button_Press.png. Possible missing file.cocos2d: fullPathForFilename: No file found at Default/Button_Disable.png. Possible missing file.callBackName cannot be foundsize = 0child = 050E7498“SaveME.exe”(Win32): 已卸载“C:WindowsSysWOW64igdusc32.dll”“SaveME.exe”(Win32): 已加载“C:WindowsSysWOW64igdusc32.dll”。无法查找或打开 PDB 文件。0x00FEDC15 处的第一机会异常(在 SaveME.exe 中): 0xC0000005: 读取位置 0xCDCDD085 时发生访问冲突。0x00FEDC15 处有未经处理的异常(在 SaveME.exe 中): 0xC0000005: 读取位置 0xCDCDD085 时发生访问冲突。

  3. Pingback: free skype download
  4. Pingback: java version
  5. Pingback: firefox free download
  6. Pingback: Vanessa Smith
  7. 大神,请问在这class MonsterLayer :public Layerda上面哪句class Monster; 和 #include “Monster.h” 有什么区别? 为什么这样引用呢

        1. 原来是你吖~完整版当然要多很多,哈哈,不然怎么能叫做完整版呢,我可不会坑人 其实不用慌的,核心就是这10篇教程,只是在此基础上加了很多界面而已,其他的那些逻辑都不会复杂的~至于广告部分,就可以忽略了

  8. MonsterLayer.obj : error LNK2019: 无法解析的外部符号 “public: static class Monster * __cdecl Monster::create(int)” (?create@Monster@@SAPAV1@H@Z),该符号在函数 “private: void __thiscall MonsterLayer::loadMonstersFromTmxConf(void)” (?loadMonstersFromTmxConf@MonsterLayer@@AAEXXZ) 中被引用为什么无法解析呢? 找不出哪里错了 求解

  9. 你好 我使用xcode 学习您的教程 float fShowTime = dict.at(“showTime”).asFloat(); //libc++abi.dylib: terminating with uncaught exception of type std::out_of_range: unordered_map::at: key not found每次老卡在 这里 百度了一下 说是c++异常 但是也没有个解决办法 libc++abi.dylib:自己也尝试了添加了这个libc++abi.dylib:文件 但还是没有解决问题。博主见多识广 能否在百忙之中指点一二 不胜感激

  10. 报错!!Assertion failed!Program:../DontSaveMe.exeFile:CCSprite.cppLine:132Expression:frame != nullptr这是F5调试时出现的 同时出现了调试影像 就是游戏的画面 主角往下掉 掉到差不多中心卡住 弹出此提示(这些好像不要紧) 和下面的童鞋问题一样 于是我检查代码和图片 发现代码中有这两句m_sModelPath = Value(“item2.png”);m_sModelPath = Value(“heart_red.png”);(还有代码省略了)然后资源中好像并没有这两张图 只有一张合起来的怪物图问下木头大大~是不是这里出了问题 然后怎么解决呐

          1. 对,就是在MonsterLayer里加载的这句报错就是因为找不到图片资源,你知道怎么看堆栈信息吗?在报错之后,看看最后执行的是哪一句代码(引擎的不算)?另外,也看看CCSprite里是在加载哪个图片(看看spriteFrameName的值是什么)~

            1. 有一个箭头指向一句代码 这就是发生错误后下一步要执行的代码对吧 .嗯…..它上面的那句就是最后执行的吧 那句正好是Sprite* sp = Sprite::createWithSpriteFrameName(m_sModelPath.asString().c_str());Monster.cpp里的 实在新手看不出有什么问题啊

              1. 那,你知道怎么断点调试吧?~在这句代码断点,然后调试运行,执行到这句代码的时候就会停止,你看看m_sModelPath的值是什么,如果是不正常的值,那就不是图片问题~如果能看到正常的字符串,那就100%是图片问题了。。。

                1. Ready for GLSLReady for OpenGL 2.0{ gl.version: 4.2.12002 Compatibility Profile Context 9.12.0.0 gl.supports_NPOT: true cocos2d.x.version: cocos2d-x 3.0 cocos2d.x.compiled_with_profiler: false cocos2d.x.build_type: DEBUG cocos2d.x.compiled_with_gl_state_cache: true gl.max_texture_size: 16384 gl.vendor: ATI Technologies Inc. gl.renderer: AMD Radeon HD 6400M Series gl.max_texture_units: 32 gl.supports_ETC1: false gl.supports_S3TC: true gl.supports_ATITC: false gl.supports_PVRTC: false gl.supports_BGRA8888: false gl.supports_discard_framebuffer: false gl.supports_vertex_array_object: true}Node warning: setPhysicsBody sets anchor point to Point::ANCHOR_MIDDLE.Node warning: setPhysicsBody sets anchor point to Point::ANCHOR_MIDDLE.iversion 1301Read design size error!cocos2d: fullPathForFilename: No file found at /cc_2x2_white_image. Possible missing file.file name == [OprUI_1.ExportJson]filename == OprUI_1.ExportJson完全木有发现m_sModelPath的值 这是下方的调试输出也许有但我看不出来 又或者我弄错了额麻烦你了我第一次写完整的游戏

                  1. 额,这些值没什么帮助。。。你得熟悉一下调试查看变量值的方法了~你可以这样,先直接下载我的源码,编译运行,确定没问题了~再对照你自己的,看看哪里有问题~万事开头难~不要灰心对了,你知道怎么打印日志吧?如果调试实在不会,可以把想看的值打印出来

  11. 可以麻烦你帮忙看看是什么问题吗?真的看不出什么问题,之前TILED中做地图时,我是把怪物也分到另外一个层上画,我以为这样有影响,就像你这样,把怪物也画到背景层上,认认真真地对比那些该删的内容,再去试,依旧不行,我调用时, 每次都在monster->joinToworld(this);这句报错,是生成前面3个后报错 for (auto monster : m_monsterVec) { /* 达到时间,可以出场了 */ if (m_fTimeCounter >= monster->getfShowTime()) { monster->joinToworld(this); /* 记录本次出场的怪物,然后删除掉 */ deleteVec.pushBack(monster); } }但我调试时,看相关信息,又没看到什么有问题的地方。链接: pan.baidu.com/s/1dDiWakx 密码: 9v7r如果方便,麻烦能帮帮忙,真有点没办法了。

    1. 按照你的描述,我猜测要么是怪物资源找不到,要么是你的怪物配置文件有地方写错了。你报错的信息是什么?~不要直接丢代码给我,我会sha人的啊

      1. Assertion failed!Program:../DontSaveMe.exeFile:CCSprite.cppLine:132Expression:frame != nullptr报了这样一个错误

            1. 不要心急,检查一下,看看是不是哪里写错了,或者图片不存在,既然前面3条没问题,那就看看第4条有什么特别的~或者你把前面3条删掉2条,看看是不是仍然到了第2条(也就是原先的第4条)就报错,如果是,那就更加肯定是第4条的问题了。然后也可以在第4条的后面增加1条,看看是不是仍然是第4条报错~总之,就是各种排除法喇

              1. 回头再看一遍第8篇,再回来看看Monster类,终于搞明白了,之前一直无脑地给TILED上的怪物的ID,乱添加ID, 以为这个ID 是不能重复的东西,然后像我之前提问的,把怪物属性设置成第一个: id:1 showtime:0第二个:id:2 showtime:2第三个:id:3 showtime:3第四个:id:4 showtime:4第五个:id:5 showtime:5…..回头看代码 if (ID == 1) { m_sModelpath = Value(“item2.png”); m_iAtk = 1; } else if (ID == 2) { m_sModelpath = Value(“heart_red.png”); m_iAtk = -1; }才发现这ID,只能是1或者2,我把它设成3,4,5肯定出错了。唉,没头没脑地绕了一个大圈,(PS:这问题木头君也没发现<( ̄︶ ̄)> )

  12. 我在生成怪物出来的过程中,生成几个就出错了,这会是什么原因呢?我这样分配的第一个: id:1 showtime:0第二个:id:2 showtime:2第三个:id:3 showtime:3第四个:id:4 showtime:4第五个:id:5 showtime:5…..如此类推,这样会有问题?

      1. 本来也不想发这个,前面解决了一堆问题,后来这个不够时间解决了,就匆匆发问,看不会是些什么小问题(-__-)b

  13. SpriteFrameCache::getInstance()->addSpriteFramesWithFile(“monster.plist”);这句是不是要放在if外面?放在里面错误提示item2.png找不到,拿出来就好了

      1. 是我自己忘调用一个函数,我在void Monster::joinToWorld(Node* parent)函数中的Sprite* sp = Sprite::createWithSpriteFrameName(m_sModelPath.asString().c_str());这段代码报一个断点:CCSprite line 34 frame != nullptr

      2. OK了,是我自己设置id的时候出问题了。谢谢您的课程,让我学到很多,真的很感谢您!

    1. 不用重复评论~评论要审核,可能会延迟显示(因为最近老有个变态来我这发广告,所以评论必须审核了 )关于继承和组合,大家一般都推荐用组合,这样比较符合面向对象的思维(怪物是一种复杂的物体,不是单纯的精灵,精灵只是用于图像方面的表现,所以,怪物继承精灵并不是太合适。)这里选择用组合,精灵只是怪物的一个组件,用于表现图像的组件。不过,也不是绝对吧,看实际情况,我个人用组合比较多~

发表评论

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