上一篇我们已经制作好tg1.tmx文件了,现在就要使用它了。
笨木头花心贡献,啥?花心?不呢,是用心~
转载请注明,原文地址: http://www.benmutou.com/blog/archives/944
文章来源:笨木头与游戏开发
很抱歉,我们又要新建2个类了,我已经尽力少新建类了,毕竟是教程,类越多越容易混乱。
我们要新建一个Monster类,以及一个MonsterLayer类,专门添加Monster对象。
来看看Monster.h文件:
- #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文件:
- #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函数:
- 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添加到物理世界
为了不要让TollgateScene的代码太过臃肿(虽然它已经很臃肿了),我不得不再创建一个MonsterLayer类。
看看MonsterLayer.h文件:
- #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文件:
- #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函数是怎么实现的:
- 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函数,重点中的重点(小若:算了吧你,重点这个词被你用烂了好吧):
- 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添加一个成员变量(我就不贴详细的代码了):
- MonsterLayer* m_monsterLayer;
然后修改TollgateScene的scene函数,把MonsterLayer添加到scene里:
- 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函数:
- void TollgateScene::logic(float dt)
- {
- m_backgroundLayer->logic(dt);
- m_monsterLayer->logic(dt);
- }
OK,运行游戏,我们看看怪物出现没?
怪物会根据我们配置的时间出现。
OK,到此,《别救我》的核心功能算是完成了,但是它完全还不能算是一个游戏。
下一篇,我们再介绍一个核心功能——读取Json配置文件