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

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


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

 

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

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

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

 

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

 

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

 

Monster类

来看看Monster.h文件:

  1. #ifndef Monster_H
  2. #define Monster_H
  3.  
  4. #include "cocos2d.h"
  5. USING_NS_CC;
  6.  
  7. class Monster : public Node 
  8. {
  9. public:
  10. Monster();
  11. ~Monster();
  12.     static Monster* create(int ID);
  13.     bool init(int ID);
  14.  
  15.     /* 加入到当前场景的物理世界 */
  16.     void joinToWorld(Node* parent);
  17.  
  18.     CC_SYNTHESIZE(int, m_ID, ID);
  19.     CC_SYNTHESIZE(float, m_fShowTime, fShowTime);
  20.     CC_SYNTHESIZE(int, m_iPosX, iPosX);
  21. CC_SYNTHESIZE(int, m_iPosY, iPosY);
  22. CC_SYNTHESIZE(int, m_iAtk, iAtk);
  23. };
  24.  
  25. #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. #include "Monster.h"
  2. #include "scene\ObjectTag.h"
  3. Monster::Monster()
  4. {
  5. }
  6.  
  7. Monster::~Monster()
  8. {
  9. }
  10.  
  11. Monster* Monster::create(int ID)
  12. {
  13.     auto monster = new Monster();
  14.     
  15.     if(monster && monster->init(ID)) 
  16.     {
  17.     monster->autorelease();
  18.     }
  19.     else
  20.     {
  21.     CC_SAFE_DELETE(monster);
  22.     }
  23.     
  24.     return monster;
  25. }
  26.  
  27. bool Monster::init(int ID)
  28. {
  29.     this->m_ID = ID;
  30.  
  31.     /* 这里以后要改成读取Json配置文件,暂时写死 */
  32.     if (ID == 1)
  33.     {
  34.         m_sModelPath = Value("item2.png");
  35. m_iAtk = 1;
  36.     }
  37.     else if (ID == 2)
  38.     {
  39.         m_sModelPath = Value("heart_red.png");
  40. m_iAtk = -1;
  41.     }
  42.     return true;
  43. }

 

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

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

 

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

 

好,再来看看joinToWorld函数:

  1. void Monster::joinToWorld(Node* parent)
  2. {
  3.     Sprite* sp = Sprite::createWithSpriteFrameName(m_sModelPath.asString().c_str());
  4.  
  5.     /* 创建刚体 */
  6.     PhysicsBody* body = PhysicsBody::createBox(sp->getContentSize());
  7.     body->setCategoryBitmask(1);    // 0001
  8.     body->setCollisionBitmask(1);   // 0001
  9.     body->setContactTestBitmask(1); // 0001
  10.  
  11.     /* 精灵居中 */
  12.     sp->setPosition(Point(sp->getContentSize().width * 0.5f, sp->getContentSize().height * 0.5f));
  13.  
  14.     /* 精灵作为Monster的表现,添加到Monster身上 */
  15.     this->addChild(sp);
  16.  
  17.     /* 设置怪物Tag类型 */
  18.     this->setTag(ObjectTag_Monster);
  19.  
  20.     /* 精灵作为Monster的表现,Monster本身没有大小,所以要设置一下大小 */
  21.     this->setContentSize(sp->getContentSize());
  22.  
  23.     /* 刚体添加到Monster本身,而不是精灵身上 */
  24.     this->setPhysicsBody(body);
  25.  
  26.     /* 设置坐标 */
  27.     this->setPosition(Point(getiPosX(), getiPosY()));
  28.  
  29.     /* Monster加入到物理世界 */
  30.     parent->addChild(this);
  31.  
  32. }

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

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

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

3. 设置怪物Tag类型

4. 将刚体添加到Monster上

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

6. 将Monster添加到物理世界

 

MonsterLayer 怪物层

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

 

看看MonsterLayer.h文件:

  1. #ifndef MonsterLayer_H
  2. #define MonsterLayer_H
  3.  
  4. #include "cocos2d.h"
  5. USING_NS_CC;
  6.  
  7. class Monster;
  8. class MonsterLayer : public Layer 
  9. {
  10. public:
  11. MonsterLayer();
  12. ~MonsterLayer();
  13. CREATE_FUNC(MonsterLayer);
  14. virtual bool init();
  15.  
  16.     void logic(float dt);
  17. private:
  18.     /* 从配置文件中加载怪物数据 */
  19.     void loadMonstersFromTmxConf();
  20.  
  21.     /* 存放所有即将要出场的怪物对象 */
  22.     Vector<Monster*> m_monsterVec;
  23.  
  24.     /* 计时器 */
  25.     float m_fTimeCounter;
  26. };
  27.  
  28. #endif

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

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

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

 

好吧,大概的意思的是:

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

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

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

 

然后再来看看MonsterLayer.cpp文件:

  1. #include "MonsterLayer.h"
  2. #include "entity\Monster.h"
  3.  
  4. MonsterLayer::MonsterLayer()
  5. {
  6.     m_fTimeCounter = 0;
  7. }
  8.  
  9. MonsterLayer::~MonsterLayer()
  10. {
  11. }
  12.  
  13. bool MonsterLayer::init()
  14. {
  15.     if (!Layer::init())
  16.     {
  17.         return false;
  18.     }
  19.  
  20.     /* 加载关卡的怪物配置 */
  21.     loadMonstersFromTmxConf();
  22.     return true;
  23. }

好,好简单~

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

 

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

  1. void MonsterLayer::loadMonstersFromTmxConf()
  2. {
  3.     /* 加载地图 */
  4.     TMXTiledMap* map = TMXTiledMap::create("tg1.tmx");
  5.     this->addChild(map);
  6.  
  7.     /* 加载怪物对象层的所有对象 */
  8.     TMXObjectGroup* objGroup = map->getObjectGroup("monster");
  9.     ValueVector objects = objGroup->getObjects();
  10.  
  11.     /* 遍历所有对象 */
  12.     for (const auto v : objects)
  13.     {
  14.         const ValueMap dict = v.asValueMap();
  15.  
  16.         int id = dict.at("id").asInt();
  17.         float fShowTime = dict.at("showTime").asFloat();
  18.         int iPosX = dict.at("x").asInt();
  19.         int iPosY = dict.at("y").asInt();
  20.  
  21.         /* 创建怪物对象,并保存起来 */
  22.         Monster* monster = Monster::create(id);
  23.         monster->setID(id);
  24.         monster->setfShowTime(fShowTime);
  25.         monster->setiPosX(iPosX);
  26.         monster->setiPosY(iPosY);
  27.  
  28.         /* 保存怪物对象 */
  29.         m_monsterVec.pushBack(monster);
  30.     }
  31. }

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

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. void MonsterLayer::logic(float dt)
  2. {
  3.     /* 计时 */
  4.     m_fTimeCounter += dt;
  5.  
  6.     /* 记录本次出场的怪物 */
  7.     Vector<Monster*> deleteVec;
  8.  
  9.     /* 让达到出场时间的怪物添加到物理世界 */
  10.     for (auto monster : m_monsterVec)
  11.     {
  12.         /* 达到时间,可以出场了 */
  13.         if (m_fTimeCounter >= monster->getfShowTime())
  14.         {
  15.             monster->joinToWorld(this);
  16.  
  17.             /* 记录本次出场的怪物,然后删除掉 */
  18.             deleteVec.pushBack(monster);
  19.         }
  20.     }
  21.  
  22.     /* 删除已经添加到物理世界的怪物,避免重复出场 */
  23.     for (auto monster : deleteVec)
  24.     {
  25.         m_monsterVec.eraseObject(monster, false);
  26.     }
  27. }

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

1. m_fTimeCounter计时器累计时间

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

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

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

 

完成喇~

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

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

 

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

 

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

  1. MonsterLayer* m_monsterLayer;

 

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

  1. Scene* TollgateScene::scene()
  2. {
  3.     auto scene = Scene::createWithPhysics();
  4.  
  5.     /* 这里省略了很多代码 */
  6.  
  7.     /* 背景层 */
  8.     auto backgroundLayer = BackgroundLayer::create();
  9.     scene->addChild(backgroundLayer, 0);
  10.  
  11.     /* 怪物层 */
  12.     auto monsterLayer = MonsterLayer::create();
  13.     scene->addChild(monsterLayer, 1);
  14.  
  15.     auto layer = TollgateScene::create();
  16.     scene->addChild(layer, 10);
  17.  
  18.     layer->m_backgroundLayer = backgroundLayer;
  19.     layer->m_monsterLayer = monsterLayer;
  20.     return scene;
  21. }

 

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

  1. void TollgateScene::logic(float dt)
  2. {
  3.     m_backgroundLayer->logic(dt);
  4.     m_monsterLayer->logic(dt);
  5. }

 

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

 

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

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

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

 

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

 

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