《Cocos2d-x3.x游戏开发之旅》学习
1.addEventListenerWidthSceneGraphPriority函数,这个函数的两个参数作用如下:
- EventListener *listener:事件监听对象,当触摸事件发生时通过它来回调;
- Node *node:绑定的对象,当node对象被释放时,监听事件的注册也会被取消,同时,有多个触摸事件发生时(比如几个按钮叠加在一起),会根据node层次优先回调(越在上面的对象越先回调);
addEventListenerWithFixedPriority,也是用于注册监听事件,但这个函数需要手动指定触摸事件回调的优先级,并且需要手动取消监听事件。一帮情况下,我们使用addEventListenerWidthSceneGraphPriority就可以了。
Value CsvUtil::getValue(int iRow, int iCol, const char *csvFilePath) {auto csvData = mCsvMap.at(csvFilePath); /*取出Csv文件对象*//*如果配置文件的数据不存在,则加载配置文件*/if (csvData == nullptr) {loadFile(csvFilePath);csvData = mCsvMap.at(csvFilePath);}ValueVector rowVector = csvData->getSingleLineData(iRow); /*获取第iRow行数据*/Value colValue = rowVector.at(iCol); /*获取第iCol列数据*/return colValue;
}
修改HelloWorldScene的init函数,代码如下:
void TowerBorder::cancelClick(Ref *target, TouchEventType type) {if (type == TouchEventType::TOUCH_EVENT_ENDED) {deleteOprBtns();}
}void TowerBorder::deleteClick(Ref *target, TouchEventType type) {if (type == TouchEventType::TOUCH_EVENT_ENDED) {deleteHero();m_hero = NULL;deleteOprBtns();}
}void TowerBorder::upgradeClick(Ref *target, TouchEventType type) {if (type == TouchEventType::TOUCH_EVENT_ENDED) {m_hero->upgrade();deleteOprBtns();}
}
cancelClick函数就是关闭操作按钮,直接调用deleteOprBtns函数即可。
deleteClick函数是删除炮台上的英雄,先调用deleteHero函数解决炮台对Hero的绑定,然后关闭操作按钮。
upgradeClick函数用于升级英雄,直接调用Hero的upgrade函数,然后关闭操作按钮。
TowerBorder还有最后一个地方要修改,在构造函数里对成员变量做一些初始化,代码所示:
TowerBorder::TowerBorder() {m_iLevel = 1;m_hero = NULL;m_cancelBtn = NULL;m_deleteBtn = NULL;m_upgradeBtn = NULL;m_isOprBtnsShow = false;
}
bool HeroManager::initWithLevel(int iCurLevel) {/*加载塔坐标对象(省略)*//*创建炮台(省略)*//*添加触摸监听(省略)*/listener->onTouchEnded = [&](Touch *touch, Event *event) {/*省略了一些代码*//*当前塔坐标没有英雄对象,则添加英雄*/if (clickBorder->getHero() == NULL) {}/*绑定英雄对象到炮台(省略)*/}else {clickBorder->showTowerOprBtns(); /*显示炮台操作按钮*/}};return true;
}
给英雄增加upgrade函数:
void Hero::upgrade() {Sprite *sprite = getSprite();if (sprite == NULL || m_iLevel >= 4) {return;}m_iLevel++; /*增加等级*//*英雄等级特效*/if (m_iLevel == 2) {Sprite *heroTop1 = Sprite::create("sprite/hero/hero_top_1.png");this->addChild(heroTop1);}if (m_iLevel == 3) {Sprite *heroTop2 = Sprite::create("sprite/hero/hero_top_2.png");this->addChild(heroTop2);auto rotateBy = RotateBy::create(25.0f, 360, 360);auto repeat = RepeatForever::create(rotateBy);heroTop2->runAction(repeat);}if (m_iLevel == 4) {Sprite *heroTop3 = Sprite::create("sprite/hero/hero_top_3.png");this->addChild(heroTop3);auto rotateBy = RotateBy::create(10.0f, 360, 360);auto repeat = RepeatForever::create(rotateBy);heroTop3->runAction(repeat);}/*增加英雄攻击力*/setiBaseAtk(getiBaseAtk() * m_fUpgradeAtkBase);setiCurAtk(getiBaseAtk());
}
运行游戏,单击英雄,然后进行各种操作,如下:

15.22 怪物起点和终点魔法台
void TollgateMapLayer::createEndPoint() {MonsterPos *pos = m_monsterMgr->getMonsterEndPos();Sprite *home = Sprite::create("sprite/end.png");home->setPosition(pos->getPos());auto rotateBy = RotateBy::create(15.0f, 360, 360);auto repeat = RepeatForever::create(rotateBy);home->runAction(repeat);this->addChild(home, HOME_LAYER_LVL);
}void TollgateMapLayer::createStartPoint() {MonsterPos *pos = m_monsterMgr->getMonsterStartPos();Sprite *startSp = CCSprite::create("sprite/start.png");startSp->setPosition(pos->getPos());auto *rotateBy = RotateBy::create(15.0f, 360, 360);auto repeat = RepeatForever::create(rotateBy);startSp->runAction(repeat);this->addChild(startSp, HOME_LAYER_LVL);
}
createEndPoint函数创建的就是怪物的终点,怪物的终点也就是玩家的堡垒,createStartPoint函数负责创建怪物的起点。怪物的起点和终点其实就是获取怪物坐标列表的第一个和最后一个坐标,然后创建一个精灵,赋予一些Action动作让它看起来更华丽。
最后,在TollgateMapLayer的init函数的最后添加两行代码,代码如下:
/*创建起始点*/
createStartPoint();/*创建终点*/
createEndPoint();
效果如下:

16.1 关卡信息UI
新建一个Layer,用来处理关卡信息的逻辑,命名为TollgateDataLayer,头文件如下:
#include "editor-support/cocostudio/CCSGUIReader.h"
#include "ui/CocosGUI.h"
using namespace cocos2d::ui;
using namespace cocostudio;
class TollgateDataLayer : public Layer {
public:TollgateDataLayer();~TollgateDataLayer();CREATE_FUNC(TollgateDataLayer);virtual bool init();
};
看看TollgateDataLayer.cpp文件,代码如下:
TollgateDataLayer::TollgateDataLayer() {
}
TollgateDataLayer::~TollgateDataLayer() {
}
bool TollgateDataLayer::init() {if (!Layer::init()) {return false;}/*加载UI*/auto UI = cocostudio::GUIReader::getInstance()->widgetFromJsonFile("TollgateUI/TollgateUI_1.ExportJson");this->addChild(UI);UI->setTouchEnabled(false);return false;
}
我们把TollgateDataLayer添加到场景里,修改TollgateScene的createScene函数,代码如下:
Scene *TollgateScene::createScene() {auto scene = Scene::create();TollgateScene *tgLayer = TollgateScene::create();TollgateDataLayer *dataLayer = TollgateDataLayer::create();TollgateMapLayer *mapLayer = TollgateMapLayer::create();Scene->addChild(mapLayer, 1, TAG_MAP_LAYER);scene->addChild(tgLayer, 3);scene->addChild(dataLayer, 5, TAG_DATA_LAYER);return scene;
}
效果如下:

16.2 关卡信息数据刷新---NotificationCenter的应用
我们先为TollgateDataLayer.h添加三个函数,代码如下:
void recvRefreshTowerSoulNum(Ref *pData);
void recvRefreshMonsterNum(Ref *pData);
void recvRefreshMagicNum(Ref *pData);int m_iTowerSoulNum; /*塔魂数量*/
int m_iMonsterNum; /*怪物数量*/
int m_iMagicNum; /*魔力数量*/Text *m_towerSoulLab; /*塔魂标签*/
Text *m_monsterLab; /*怪物标签*/
Text *m_magicLab; /*魔力标签*/
我们要在特定的事件发生时修改这三种数据,所以我们要订阅三种消息,分别对应这三种数据的刷新,而刷新数据就是修改三个信息的标签值。
修改一下TollgateDataLayer的init函数,代码如下:
bool TollgateDataLayer::init() {if (!Layer::init()) { return false; }/*加载UI*/auto UI = cocostudio::GUIReader::getInstance()->widgetFromJsonFile("TollgateUI/TollgateUI_1.ExportJson");this->addChild(UI);UI->setTouchEnabled(false);/*塔魂标签*/m_towerSoulLab = (Text *)Helper::seekWidgetByName(UI, "towerSoulLab");/*怪物标签*/m_monsterLab = (Text *)Helper::seekWidgetByName(UI, "monsterNumLab");/*魔力标签*/m_magicLab = (Text *)Helper::seekWidgetByName(UI, "magicLab");/*订阅消息*/NOTIFY->addObserver(this, callfuncO_selector(TollgateDataLayer::recvRefreshTowerSoulNum),"TowerSoulChange",NULL);NOTIFY->addObserver(this, callfuncO_selector(TollgateDataLayer::recvRefreshMonsterNum),"MonsterNumChange",NULL);NOTIFY->addObserver(this,callfuncO_selector(TollgateDataLayer::recvRefreshMagicNum),"MagicChange",NULL);return true;
}
看看这三个消息发生时的函数,代码如下:
void TollgateDataLayer::recvRefreshTowerSoulNum(Ref *pData) {int iAltValue = (int)pData;m_iTowerSoulNum += iAltValue;m_towerSoulLab->setText(StringUtils::toString(m_iTowerSoulNum));
}void TollgateDataLayer::recvRefreshMonsterNum(Ref *pData) {int iAltValue = (int)pData;m_iMonsterNum += iAltValue;m_monsterLab->setText(StringUtils::toString(m_iMonsterNum));
}void TollgateDataLayer::recvRefreshMagicNum(Ref *pData) {int iAltValue = (int)pData;m_iMagicNum += iAltValue;m_magicLab->setText(StringUtils::toString(m_iMagicNum));
}
最后,我们还要做一下初始化和释放的工作,代码如下:
TollgateDataLayer::TollgateDataLayer() {m_iTowerSoulNum = 0; /*塔魂数量*/m_iMonsterNum = 0; /*怪物数量*/m_iMagicNum = 0; /*魔力数量*/
}
TollgateDataLayer::~TollgateDataLayer() {NOTIFY->removeAllObservers(this);
}
我们要修改一下TollgataMapLayer,给它添加一个initData函数,代码如下:
void TollgateMapLayer::initData() {/*初始化塔魂、怪物和魔力数量*/int iTowerSoulNum = 5;int iMonsterNum = m_monsterMgr->getNotShowMonsterCount(); /*怪物数量*/int iMagicNum = 100; /*魔力数量*/NOTIFY->postNotification("TowerSoulChange", (Ref *)iTowerSoulNum);NOTIFY->postNotification("MonsterNumChange", (Ref *)iMonsterNum);NOTIFY->postNotification("MagicChange", (Ref *)iMagicNum);
}
我们在TollgateMapLayer层里发送信息,改变关卡信息UI的值。
修改一下TollgateScene的createScene函数,代码如下:
Scene *TollgateScene::createScene() {auto scene = Scene::create();TollgateScene *tgLayer = TollgateScene::create();TollgateMapLayer *mapLayer = TollgateMapLayer::create();TollgateDataLayer *dataLayer = TollgateMapLayer::create();scene->addChild(mapLayer, 1, TAG_MAP_LAYER);scene->addChild(tgLayer, 3);scene->addChild(dataLayer, 5, TAG_DATA_LAYER);mapLayer->initData();return scene;
}
16.3 怪物数量刷新
在MonsterManager.cpp的showMonster函数的最后加上几句代码,代码如下:
/*发布怪物数量改变消息*/
int iMonsterNumChange = -monsterWantToDelete.size();
NOTIFY->postNotification("MonsterNumChange", (Ref *)iMonsterNumChange);
运行游戏,就能看到没出现一只怪物,关卡信息UI上的怪物数量就会减1,如下:

16.4 怪物安息---怪物死亡后塔魂数量刷新
我们来刷新塔魂数量,塔魂也就是我们的金钱,以后英雄升级需要花费塔魂。
怪物死亡时会调用onDead函数。代码如下:
void Manager::onDead() {this->removeFrom();/*发布塔魂增加消息*/int iTowerSoulNumChange = 3 * getiLevel();NOTIFY->postNotification("TowerSoulChange", (Ref *)iTowerSoulNumChange);
}
运行游戏,尽量打死一只怪物试试,效果如下:

16.5 堡垒安息---怪物到达堡垒后扣除魔力值
ControllerMoveBase.h文件,代码如下:
class ControllerMoveBase : public ControllerBase {
public:void bindMoveEndFunc(std::function moveEndFunc);
protected:/*用于移动结束时的回调*/std::function m_moveEndFunc;
};
void ControllerMoveBase::bindMoveEndFunc(std::function moveEndFunc) {m_moveEndFunc = moveEndFunc;
}
看看ControllerSimpleMove的moveOneStep函数,代码如下:
void ControllerSimpleMove::moveOneStep() {/*省略了很多代码*//*到达当前目的地,开始下一个目的地*/if (entityPos.x == curDestPos.x && entityPos.y == curDestPos.y) {if (m_movePosList.size() > 0) {nextMovePos();} else {/*移动结束*/if (m_moveEndFunc) {m_moveEndFunc();m_moveEndFunc = nullptr;}}}
}
最后,我们还要修改Monster的init函数,代码如下:
16.6 打怪升级---英雄升级扣除塔魂
我们在英雄升级的时候发送消息,改变塔魂数量即可,修改一下Hero的upgrade函数,代码如下:
void Hero::upgrade() {Sprite *sprite = getSprite();if (sprite == NULL || m_iLevel >= 4) {return;}/*判断塔魂是否足够*/auto dataLayer = (TollgateDataLayer *)Director::getInstance()->getRunningScene()->getChildByTag(TAG_DATA_LAYER);int iCurMagicNum = dataLayer->getiTowerSoulNum();int iCostTowerSoul = m_iUpgradeCostBase * m_iLevel;if (iCurMagicNum < iCostTowerSoul) {return;}/*发布消息,扣除塔魂*/NOTIFY->postNotification("TowerSoulChange", (Ref *)iCostTowerSoul);/*省略了很多代码*/
}
给TollgateDataLayer新增一个getiTowerSoulNum函数,以便获取塔魂数量。getiTowerSoulNum函数代码如下:
int TollgateDataLayer::getiMagicNum() {return m_iMagicNum;
}
int TollgateDataLayer::getiTowerSoulNum() {return m_iTowerSoulNum;
}
运行游戏,然后升级英雄,我们就能看到塔魂的数量减少了。
16.7 关卡选择---根据关卡树加载游戏
新建一个关卡选择场景,头文件代码如下:
class TollgateSelectScene : public CCLayer {
public:static Scene *createScene(); virtual bool init();CREATE_FUNC(TollgateSelectScene);
private:void level_1(Ref *target, TouchEventType type);void level_2(Ref *target, TouchEventType type);void level_3(Ref *target, TouchEventType type);
};
创建的界面比较简陋,只有三个按钮,来看看TollgateSelectScene.cpp文件:
bool TollgateSelectScene::init() {bool bRet = false;do {CC_BREAK_IF(! Layer::init());/*加载UI*/auto UI = cocostudio::GUIReader::getInstance()->widgetFromJsonFile("TgSelectUI/TgSelectUI_1.ExportJson");this->addChild(UI);Button *tgSelect1Btn = (Button *)Helper::seekWidgetByName(UI, "tgSelect1Btn");Button *tgSelect2Btn = (Button *)Helper::seekWidgetByName(UI, "tgSelect2Btn");Button *tgSelect3Btn = (Button *)Helper::seekWidgetByName(UI, "tgSelect3Btn");tgSelect1Btn->addTouchEventListener(this, toucheventselector(TollgateSelectScene::level_1));tgSelect2Btn->addTouchEventListener(this, toucheventselector(TollgateSelectScene::level_2));tgSelect3Btn->addTouchEventListener(this, toucheventselector(TollgateSelectScene::level_3));bRet = true;} while(0);return bRet;
}void TollgateSelectScene::level_1(Ref *target, TouchEventType type) {if (type != TouchEventType::TOUCH_EVENT_ENDED) {return; }GlobalClient::getInstance()->setiCurTollgateLevel(1);SceneManager::getInstance()->changeScene(SceneManager::en_TollgateScene);
}void TollgateSelectScene::level_2(Ref *target, TouchEventType type) {if (type != TouchEventType::TOUCH_EVENT_ENDED) {return; }GlobalClient::getInstance()->setiCurTollgateLevel(2);SceneManager::getInstance()->changeScene(SceneManager::en_TollgateScene);
}void TollgateSelectScene::level_3(Ref *target, TouchEventType type) {if (type != TouchEventType::TOUCH_EVENT_ENDED) {return;}GlobalClient::getInstance()->setiCurTollgateLevel(3);SceneManager::getInstance()->changeScene(SceneManager::en_TollgateScene);
}
关卡的加载是在TollgateMapLayer层进行的,我们在TollgateMapLayer的构造函数里初始化m_iCurLevel的值即可,代码如下:
TollgateMapLayer::TollgateMapLayer() {m_iCurLevel = GlobalClient::getInstance()->getiCurTollgateLevel();
}
然后,我们新增了TollgateSelectScene场景,修改SceneManager的changeScene函数,添加一个switch分支,代码如下:
case en_TollgateSelectScene: /*关卡选择场景*/pScene = TollgateSelectScene::scene();break;
GlobalClient是一个单例类,存放我们的一些全局数据,方便各个场景或者层之间传递数据,代码如下:
GlobalClient.h文件:
class GlobalClient : public Ref {
public:static GlobalClient *getInstance();CREATE_FUNC(GlobalClient);virtual bool init();
private:static GlobalClient *m_GlobalClient;CC_SYNTHESIZE(int, m_iCurTollgateLevel, iCurTollgateLevel);
};GlobalClient.cpp文件:
GlobalClient *GlobalClient::m_GlobalClient = NULL;
GlobalClient *GlobalClient::getInstance() {if (m_GlobalClient == NULL) {m_GlobalClient = new GlobalClient();if (m_GlobalClient && m_GlobalClient->init()) {m_GlobalClient->autorelease();m_GlobalClient->retain();}else {CC_SAFE_DELETE(m_GlobalClient);m_GlobalClient->retain();}}return m_GlobalClient;
}bool GlobalClient::init() {return true;
}
运行游戏,效果如下:

16.8 胜利条件判断
我们修改一下Monster的init函数,代码如下:
CC_SYNTHESIZE_BOOL(m_isMoveEnd, MoveEnd); //是否达到目的地
然后,我们修改一下Monster的init函数,代码如下:
bool Monster::init() {m_moveController = ControllerSimpleMove::create(this);this->addChild(m_moveController);/*绑定移动结束回调函数*/m_moveController->bindMoveEndFunc([&](){/*发布魔力值改变消息*/int iMagicChange = -getiLevel() * 10;NOTIFY->postNotification("MagicChange", (Ref *)iMagicChange);m_isMoveEnd = true;});return true;
}
当怪物移动结束时,将m_isMoveEnd标记为true,代表怪物达到目的地。
我们为MonsterManager新增一个函数,代码如下:
void MonsterManager::logic(float dt) {Vector monsterWantToDelete;for (auto monster : m_monsterList) {/*从列表中删除已经到达目的地的怪物,先记录,后删除*/if (monster->isMoveEnd() == true) {monsterWantToDelete.pushBack(monster);}/*从列表汇总删除已经死亡的怪物,先记录,后删除*/else if (monster->isDead() == true) {monsterWantToDelete.pushBack(monster);}}/*正式删除的怪物*/for (auto monster : monsterWantToDelete) {monster->removeFromParent();m_monsterList.eraseObject(monster);}if (m_monsterList.size() == 0) {/*怪物数量为0,发布怪物全灭消息*/NOTIFY->postNotification("AllMonsterDead");}
}
最后,判断怪物列表中是否还有怪物,如果怪物数量为0,则发布一条"AllMonsterDead"消息。
在MonsterManager的createMonsters函数最后添加一句代码,代码如下:
this->schedule(schedule_selector(MonsterManager::showMonster));
this->schedule(schedule_selector(MonsterManager::logic));
然后,我们要在TollgateDataLayer层处理"AllmonsterDead"消息,为TollgateDataLayer新增一个函数,代码如下:
void TollgateDataLayer::recvAllMonsterDead(Ref *pData) {if (m_iMagicNum > 0) {SceneManager::getInstance()->changeScene(SceneManager::en_WinScene);}
}
要接收"AllMonsterDead"消息,就要订阅消息,我们在TollgateDataLayer的init函数的最后再订阅一条消息,代码如下:
NOTIFY->addObserver(this, callfuncO_selector(TollgateDataLayer::recvAllMonsterDead),"AllMonsterDead", NULL);
WinScene场景只是一个很简单的场景,关键的两个函数,代码如下:
bool WinScene::init() {if (!Layer::init()) {return false;}Size visibleSize = Director::getInstance()->getVisibleSize();/*添加一个标签*/Label *winLab = Label::create("You Win!", PATH_FONT_PUBLIC, GlobalParam::PublicFontSizeLarge);winLab->setPosition(ccp(visibleSize.width / 2, visibleSize.height / 2));this->addChild(winLab);/*3秒后返回关卡选择场景*/this->schedule(schedule_selector(WinScene::backToTollgateSelectScene), 3.0f);return true;
}
void WinScene::backToTollgateSelectScene(float dt) {sceneManager::getInstance()->changeScene(SceneManager::en_TollgateSelectScene);
}
16.9 失败条件判断
直接在接收到“MagicChange”消息时做判断,修改TollgateDataLayer的recvRefreshMagicNum,在最后添加几句代码,代码如下:
void TollgateDataLayer::recvRefreshMagicNum(Ref *pData) {/*省略了很多代码*//*魔力值小于等于0,游戏失败*/if (m_iMagicNum <= 0) {SceneManager::getInstance()->changeScene(SceneManager::en_GameOverScene);}
}
资料链接:https://pan.baidu.com/s/106JS0kNH_J6pT2Ns5hW3Sg 密码:ixl3
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
