《Cocos2d-x3.x游戏开发之旅》学习
1.addEventListenerWidthSceneGraphPriority函數(shù),這個函數(shù)的兩個參數(shù)作用如下:
- ? ?EventListener *listener:事件監(jiān)聽對象,當(dāng)觸摸事件發(fā)生時通過它來回調(diào);
 - ? ?Node *node:綁定的對象,當(dāng)node對象被釋放時,監(jiān)聽事件的注冊也會被取消,同時,有多個觸摸事件發(fā)生時(比如幾個按鈕疊加在一起),會根據(jù)node層次優(yōu)先回調(diào)(越在上面的對象越先回調(diào));
 
? ?addEventListenerWithFixedPriority,也是用于注冊監(jiān)聽事件,但這個函數(shù)需要手動指定觸摸事件回調(diào)的優(yōu)先級,并且需要手動取消監(jiān)聽事件。一幫情況下,我們使用addEventListenerWidthSceneGraphPriority就可以了。
bool HelloWorld::init() {if (!Layer::init()) { return false; }Size visibleSize = Director::getInstance()->getVisibleSize();/* 創(chuàng)建兩個精靈,相互有重疊的部分 */Sprite *sp1 = Sprite::create("sprite1.png");sp1->setPosition(Point(visibleSize.width * 0.5f, visibleSize.height * 0.5f));this->addChild(sp1);Sprite *sp2 = Sprite::create("sprite2.png");sp2->setPosition(Point(visibleSize.width * 0.5f, visibleSize.height * 0.5f));this->addChild(sp2);auto listener = EventListenerTouchOneByOne::create();listener->onTouchBegan = [](Touch *touch, Event *event) {/* 注冊監(jiān)聽事件時綁定了一個Node對象,在這里就可以取出這個對象 */auto target = static_cast<Sprite *>(event->getCurrentTarget());Point pos = Director::getInstance()->convertToGL(touch->getLocationInView());/* 判斷單擊的坐標(biāo)是否在精靈的范圍內(nèi) */if (target->getBoundingBox().containsPoint(pos)) {/* 設(shè)置精靈的透明度為100 */target->setOpacity(100);return true;}return false;};listener->onTouchEnded = [](Touch *touch, Event *event) {/* 恢復(fù)精靈的透明度 */auto target = static_cast<Sprite *>(event->getCurrentTarget());target->setOpacity(255);};/* 注冊監(jiān)聽事件,綁定精靈1 */_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, sp1);/*注冊監(jiān)聽事件,綁定精靈2,這里要注意,listener對象復(fù)制了一份*/_eventDispatcher->addEventListenerWithSceneGraphPriority(listener->clone(), sp2);return true; }? ? ? 分步驟來講解:
? ? ? ?a) 創(chuàng)建兩個精靈,讓這兩個精靈剛好有部分位置是重疊的;
? ? ? ?b) 創(chuàng)建EventListenerTouchOneByOne監(jiān)聽事件;
? ? ? ?c) 在onTouchBegan函數(shù)里獲取事件綁定的精靈對象,判斷單擊的坐標(biāo)是否在精靈的范圍內(nèi),是的話,則修改精靈的透明度為100;
? ? ? ?d) 調(diào)用addEventListenerWithSceneGraphPriority函數(shù)分別添加兩個精靈的監(jiān)聽事件;
? ? ?通常我們要求在單擊重疊部位的時候,只有上面的按鈕能獲得響應(yīng)。要實現(xiàn)這個效果,很簡單,我們給listener調(diào)用一個函數(shù)即可:
? ? ?.listener->setSwallowTouches(true);
? ? setSwallowTouches函數(shù)用于設(shè)置是否吞沒事件。
2.多點觸控
bool HelloWorld::init() {if (!Layer::init()) { return false; }auto listener = EventListenerTouchAllAtOnce::create();listener->onTouchesBegan = [](const std::vector<Touch *>& touches, Event *event) {};listener->onTouchesMoved = [](const std::vector<Touch *>& touches, Event *event) {};listener->onTouchesEnded = [](const std::vector<Touch *>& touches, Event *event) {};_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);return true; }?
bool HelloWorld::init() {if (!Layer::init()) { return false; }Label *logText1 = Label::create("", "Arial", 24);logText1->setPosition(Point(400, 280));this->addChild(logText1, 1, 1);Label *logText2 = Label::create("", "Arial", 24);logText3->setPosition(Point(400, 100));this->addChild(logText3, 1, 3);auto listener = EventListenerTouchAllAtOnce::create();listener->onTouchesBegan = [&](const std::vector<Touch *>& touches, Event *event) {auto logText = (Label *)this->getChildByTag(1);int num = touches.size();logText->setString(Value(num).asString() + " Touches:");};listener->onTouchesMoved = [&](const std::vector<Touch *>& touches, Event *event) {auto logText = (Label *)this->getChildByTag(2);int num = touches.size();std::string text = Value(num).asString() + " Touches:";for (auto &touch : touches) {auto location = touch->getLocation();text += "[touchID" + Value(touch->getID()).asString() + "],";}logText->setString(text);};listener->onTouchesEnded = [&](const std::vector<Touch *>& touches, Event *event) {auto logText = (Label *)this->getChildByTag(3);int num = touches.size();logText->setString(Value(num).asString() + " Touches:");};_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);return true; }3.<LittleRunner>的主要功能如下:
- ? ?主角一直往前跑,實際上是地圖在滾動;
 - ? ?跑動過程中會有很多金幣飛向主角,主角被金幣打中會扣血;
 - ? ?扣血有動畫效果;
 - ? ?按下Jump按鈕,主角可以向上彈跳;
 
? ?游戲包含的開發(fā)要點:
- ? ? ?地圖無限滾動;
 - ? ? ?精靈動作的使用;
 - ? ? ?Cocostudio UI使用:按鈕、標(biāo)簽、進(jìn)度條;
 - ? ? ?簡單的碰撞檢測;
 - ? ? ?update函數(shù)的應(yīng)用.
 
? 3.1 創(chuàng)建實體基類
? ? ? Entity.h文件:
#ifndef __Entity_H__ #define __Entity_H__ #include "cocos2d.h" USING_NS_CC;class Entity : public Node {public:Entity();~Entity();Sprite *getSprite(); /* 獲取精靈對象 */void bindSprite(Sprite *sprite); /* 綁定精靈對象 */private:Sprite *m_sprite; };#endif? ? ? Entity.cpp文件
#include "Player.h" #include "Entity.h" Entity::Entity() {m_sprite = NULL; } Entity::~Entity() {} Sprite *Entity::getSprite() {return this->m_sprite; } void Entity::bindSprite(Sprite *sprite) {this->m_sprite = sprite;this->addChild(m_sprite); }? ? 3.2 創(chuàng)建主角類
? ? ? ?Player.h文件
#ifndef __Player_H__ #define __Player_H__#include "cocos2d.h" #include "Entity.h" using namespace cocos2d;#define JUMP_ACTION_TAG 1 class Player : public Entity {public:Player();~Player();CREATE_FUNC(Player);virtual bool init(); };#endif? ? ? ? Player.cpp文件
#include "Player.h"Player::Player() {} Player::~Player() {}bool Player::init() {return true; }? ? ? 3.3 創(chuàng)建游戲場景
? ? ? ? ?TollgateScene.h文件
#ifndef __TollgateScene_H__ #define __TollgateScene_H__#include "cocos2d.h" using namespace cocos2d;class Player; class TollgateScene : public Layer {public:static Scene *createScene();virtual bool init();CREATE_FUNC(TollgateScene);private:void initBG(); //初始化關(guān)卡背景private:Sprite *m_bgSprite1; //背景精靈1Sprite *m_bgSprite2; //背景精靈2Player *m_player; //主角 };#endif? ? ? ? ?TollgateScene.cpp文件:
bool TollgateScene::init() {if (!Layer::init()) { return false; }Size visibleSize = Director::getInstance()->getVisibleSize();/* 游戲標(biāo)題圖片 */Sprite *titleSprite = Sprite::create("title.png");titleSprite->setPosition(Point(visibleSize.width / 2, visibleSize.height - 50));this->addChild(titleSprite, 2);/* 創(chuàng)建主角 */m_player = Player::create();m_player->bindSprite(Sprite::create("sprite.png"));m_player->setPosition(Point(200, visibleSize.height / 4));this->addChild(m_player, 3);/* 初始化背景圖片 */initBG();return true; }void TollgateScene::initBG() {Size visibleSize = Director::getInstance()->getVisibleSize();m_bgSprite1 = Sprite::create("tollgateBG.jpg");m_bgSprite1->setPosition(Point(visibleSize.width / 2, visibleSize.height / 2));this->addChild(m_bgSprite1, 0);m_bgSprite2 = Sprite::create("tollgateBG.jpg");m_bgSprite2->setPosition(Point(visibleSize.width+visibleSize.width/2, visibleSize.height/2));m_bgSprite2->setFlippedX(true); // 水平翻轉(zhuǎn)精靈this->addChild(m_bgSprite2, 0); } }? ? ? ?注意:調(diào)用addChild函數(shù)時,使用了第二個參數(shù),這個參數(shù)代表對象的繪制層次,數(shù)值越大,對象層次越高(越遲被繪制),背景圖片應(yīng)該設(shè)成較低層次,否則會把主角擋住。
? ? 3.4 修改游戲窗口大小
? ? ? ?打開main.cpp,找到下面的代碼:
if (!glview) {glview = GLView::create("LittleRunner");glview->setFrameSize(480, 320);director->setOpenGLView(glview); }? ? ?3.5 我們要盡量避免使用多線程,Cocos2d-x的Node對象提供了一個update函數(shù),在游戲運行的每一幀(也就是主線程的每一個循環(huán))都會調(diào)用update函數(shù)。前提是我們允許它調(diào)用。
? ? ? ? 程序默認(rèn)是不會調(diào)用Node對象的update函數(shù)的,要開啟調(diào)用update函數(shù)的功能,只需要一句代碼:this->scheduleUpdate()。
? ? ?3.6 打開TollgateScene.cpp文件,在init函數(shù)的最后加上一句代碼:
bool TollgateScene::init() {/* 省略了很多代碼 */this->scheduleUpdate();return true; }? ? ? scheduleUpdate函數(shù)只是開啟了這個功能,我們還必須重寫Node的update函數(shù)。現(xiàn)在打開TollgateScene.h頭文件,增加一句代碼:
class TollgateScene : public Layer {public:/* 又省略了很多代碼 */virtual void update(float delta); };? ? 然后在TollgateScene.cpp文件里實現(xiàn)update函數(shù),如下:
void TollgateScene::update(float delta) {log("update"); }? ? ? 3.7 讓地圖滾動起來,修改TollgateScene的update函數(shù),如下:
void TollgateScene::update(float delta) {int posX1 = m_bgSprite1->getPositionX(); //背景地圖1的X坐標(biāo)int posX2 = m_bgSprite2->getPositionX(); //背景地圖2的X坐標(biāo)int iSpeed = 1; //地圖滾動速度/* 兩張地圖向左滾動(兩張地圖是相鄰的,所以要一起滾動,否則會出現(xiàn)空隙) */posX1 -= iSpeed;posX2 -= iSpeed;m_bgSprite1->setPositionX(posX1);m_bgSprite2->setPositionX(posX2); }? ? ? ? 效果圖:
? ? ?
? ? 還需要加上另外的代碼,如下:
void TollgateScene::update(float delta) {int posX1 = m_bgSprite1->getPositionX(); //背景地圖1的X坐標(biāo)int posX2 = m_bgSprite2->getPositionX(); //背景地圖2的X坐標(biāo)int iSpeed = 1; //地圖滾動速度/* 兩張地圖向左滾動(兩張地圖是相鄰的,所以要一起滾動,否則會出現(xiàn)空隙)*/posX1 -= iSpeed;posX2 -= iSpeed;/* 地圖大小 */Size mapSize = m_bgSprite1->getContentSize();/* 當(dāng)?shù)?個地圖完全離開屏幕時,第2個地圖剛好完全出現(xiàn)在屏幕上,這時候就讓第1個地圖緊貼在第2個地圖后面 */if (posX1 <= -mapSize.width / 2) {posX1 = mapSize.width + mapSize.width / 2;}/* 同理,當(dāng)?shù)?個地圖完全離開屏幕時,第1個地圖剛好完全出現(xiàn)在屏幕上,這時候就讓第2個地圖緊貼在第1個地圖后面 */if (posX2 <= -mapSize.width / 2) {posX2 = mapSize.width + mapSize.width / 2;}m_bgSprite1->setPositionX(posX1);m_bgSprite2->setPositionX(posX2); }? ? ? 3.8 創(chuàng)建跳躍按鈕
? ? ? ?TollgateScene.h文件:
class TollgateScene : public Layer {private:/* 前面省略了很多代碼 */void loadUI(); //加載UIvoid jumpEvent(Ref *, TouchEventType type); };? ? ? ? TollgateScene.cpp文件:
void TollgateScene::loadUI() {/* 加載UI */auto UI = cocostudio::GUIReader::getInstance()->widgetFromJsonFile("LitterRunnerUI_1.ExportJson");this->addChild(UI);/* 獲取控件對象 */auto jumpBtn = (Button *)Helper::seekWidgetByName(UI, "JumpBtn");/* 添加單擊監(jiān)聽 */jumpBtn->addTouchEventListener(this, toucheventselector(TollgateScene::jumpEvent)); }void TollgateScene::jumpEvent(Ref *, TouchEventType type) {switch (type) {case TouchEventType::TOUCH_EVENT_ENDED:m_player->jump();break;} }? ? ? ? ? 兩個函數(shù)很簡單,loadUI負(fù)責(zé)加載UI文件,并且監(jiān)聽按鈕的一個單擊事件,在單擊事件jumpEvent中調(diào)用主角的jump函數(shù),然后主角就能跳起來了。
? ? ? ? ?現(xiàn)在我們開始給主角賦予跳躍的能力,給Player類添加一些變量和函數(shù),如下:
? ? ? Player.h文件:
class Player : public Entity {public:Player();~Player();CREATE_FUNC(Player); //create函數(shù)virtual bool init();public:void jump(); //跳躍函數(shù)private:bool m_isJumping; //標(biāo)記主角是否正在跳躍 };? ? ? ? Player.cpp文件
Player::Player() {/*初始化主角跳躍標(biāo)記為false,一定不能忘記這一步 */m_isJumping = false; } Player::~Player() { } bool Player::init() {return true; }void Player::jump() {if (getSprite() == NULL) {return;}/* 如果主角還在跳躍中,則不重復(fù)執(zhí)行 */if (m_isJumping) {return;}/* 標(biāo)記主角為跳躍狀態(tài) */m_isJumping = true;/* 創(chuàng)建跳躍動作: 原地跳躍,高度為250像素,跳躍一次 */auto jump = JumpBy::create(2.0f, Point(0, 0), 250, 1);/* 創(chuàng)建回調(diào)動作,跳躍結(jié)束后修改m_isJumping標(biāo)記為false */auto callFunc = CallFunc::create([&])(){m_isJumping = false;});/* 創(chuàng)建連續(xù)動作 */auto jumpActions = Sequence::create(jump, callFunc, NULL);/* 執(zhí)行動作 */this->runAction(jumpActions); }? ? ? ?
? ? ?3.9 加入怪物
? ? ? ?Monster.h文件
class Monster : public Entity {public:Monster();~Monster();CREATE_FUNC(Monster);virtual bool init();public:void show(); //顯示怪物void hide(); //隱藏怪物void reset(); //重置怪物數(shù)據(jù)bool isAlive(); //是否活動狀態(tài)private:bool m_isAlive; };? ? ? ? Monster.cpp文件:
Monster::Monster() {m_isAlive = false; } Monster::~Monster() {} bool Monster::init() {return true; }void Monster::show() {if (getSprite() != NULL) {setVisible(true); /*設(shè)置可見*/m_isAlive = true; /*標(biāo)記怪物為活動狀態(tài)*/} }void Monster::hide() {if (getSprite() != NULL) {setVisible(false); /*設(shè)置不可見*/reset(); /*重置怪物數(shù)據(jù)*/m_isAlive = false; /*標(biāo)記怪物為非活動狀態(tài)*/} }void Monster::reset() {if (getSprite() != NULL) {/* 初始化怪物坐標(biāo)*/setPosition(Point(800+CCRANDOM_0_1()*2000, 200-CCRANDOM_0_1() * 100));} }bool Monster::isAlive() {return m_isAlive; }? ? ? ?3.10 創(chuàng)建怪物管理器
? ? ? ? ? ?MonsterManager.h文件
#ifndef __MonsterManger_H__ #define __MonsterManger_H__#include "cocos2d.h" #include "Monster.h" USING_NS_CC;#define MAX_MONSTER_NUM 10 //怪物最大數(shù)量 class MonsterManger : public Node {public:CREATE_FUNC(MonsterManager);virtual bool init();virtual void update(float dt); /*重寫update函數(shù)*/private:void createMonsters(); /*創(chuàng)建怪物對象*/private:Vector<Monster*> m_monsterArr; /*存放怪物對象列表*/ }; #endif? ? ? ? ? ?MonsterManager.cpp文件:
#include "MonsterManager.h" #include "Player.h" #include "Monster.h"bool MonsterMangager::init() {createMonsters(); /*創(chuàng)建怪物緩存*/this->scheduleUpdate(); /*開啟update函數(shù)調(diào)用*/return true; }void MonsterMangager::createMonsters() {Monster *monster = NULL;Sprite *sprite = NULL;for (int i=0; i<MAX_MONSTER_NUM; i++) {/*創(chuàng)建怪物對象*/monster = Monster::create();monster->bindSprite(Sprite::create("monster.png"));monster->reset();/*添加怪物對象*/this->addChild(monster);/*保存怪物對象到列表中,方便管理*/m_monsterArr.pushBack(monster);} }void MonsterManager::update(float dt) { }? ? ? ? ? ?現(xiàn)在,我們在TollgateScene.cpp的init函數(shù)最后面添加幾句代碼,讓怪物管理器生效,如下:
#include "MonsterManager.h" bool TollgateScene::init() {/*省略了很多代碼*//*創(chuàng)建怪物管理器*/MonsterManager *monsterMgr = MonsterManager::create();this->addChild(monsterMgr, 4);return true; }? ? ? ? ?我們的怪物管理器還要增加一個功能,就是不斷地改變怪物的坐標(biāo)。修改MonsterManager.cpp的update函數(shù),如下所示:
void MonsterManager::update(float dt) {for (auto monster : m_monsterArr) {if (monster->isAlive()) {/*如果怪物處于活動狀態(tài)*/monster->setPositionX(monster->getPositionX() - 4);/*如果怪物X坐標(biāo)小于0,則表示已經(jīng)超出屏幕范圍,隱藏怪物*/if (monster->getPositionX() < 0) {monster->hide();}} else {/*怪物處于非活動狀態(tài),讓怪物出場吧*/monster->show();}}}? ? ? ? ? ? 效果如下:
? ? ? ? ? ? ?
? ? ? ? ?3.11 怪物碰撞檢測
? ? ? ? ? ? 如果怪物碰到主角,則扣主角的血
? ? ? ? ? ? 給Player類加點東西,如下代碼:
class Player : public Entity {/* 這里省略了很多代碼 */public:void jump(); //跳躍函數(shù)void hit(); //玩家受傷害int getiHP();private:bool m_isJumping; //標(biāo)記主角是否正在跳躍int m_iHP; //主角血量 };? ? ? ? ? ? 再來看Player.cpp是如何實現(xiàn)這些函數(shù)的,如下:
Player::Player() {/*初始化主角跳躍標(biāo)記為false,一定不能忘記這一步*/m_isJumping = false;/*初始化血量*/m_iHP = 1000; }void Player::hit() {if (getSprite() == NULL) {return;}m_iHP -= 15;if (m_iHP < 0) {m_iHP = 0;} }int Player::getiHP() {return this->m_iHP; }? ? ? ? ? 接著,我們要給Monster添加一個檢測碰撞的函數(shù),如下所示:
? ? ? ? ?Monster.h文件:
class Monster : public Entity {public:/*這里省略了很多代碼*//*檢查碰撞*/bool isCollideWithPlayer(Player *player); };? ? ? ? ? ?Monster.cpp文件
bool Monster::isCollideWithPlayer(Player *player) {/*獲取碰撞檢查對象的boundingBox*/Rect entityRect = player->getBoundingBox();Point monsterPos = getPosition();/*判斷boundingBox和怪物中心點是否有交集*/return entityRect.containsPoint(monsterPos); }? ? ? ? ? 最后修改MonsterManager的update函數(shù),以及新增一個bindPlayer函數(shù),如下代碼:
? ? ? ? ?MonsterManager.h文件
class MonsterManager : public Node {/*這里省略了很多代碼*/public:/*綁定玩家對象*/void bindPlayer(Player *player);private:Player *m_player; /*玩家對象*/ };? ? ? ? ?MonsterManager.cpp文件
void MonsterManager::update(float dt) {/*這里省略了很多代碼*//*如果怪物X坐標(biāo)小于0,則表示已經(jīng)超出屏幕范圍,隱藏怪物*/if (monster->getPositionX() < 0) {monster->hide();}/*判斷怪物是否碰撞玩家*/else if (monster->isCollideWithPlayer(m_player)) {m_player->hit();monster->hide();}/*這里省略很多代碼*/ }void MonsterManager::bindPlayer(Player *player) {m_player = player; }? ? ? ? ?TollgateScene.cpp文件
bool TollgateScene::init() {/*這里省略了很多代碼*//*創(chuàng)建怪物管理器*/MonsterManager *monsterMgr = MonsterManager::create();this->addChild(monsterMgr, 4);monsterMgr->bindPlayer(m_player);return true; }? ? ? ? ?3.12 怪物碰不到主角
? ? ? ? ? ? 修改Entity.cpp的bindSprite函數(shù),如下:
void Entity::bindSprite(Sprite *sprite) {this->m_sprite = sprite;this->addChild(m_sprite);Size size = m_sprite->getContentSize();m_sprite->setPosition(Point(size.width * 0.5f, size.height * 0.5f));this->setContentSize(size); }? ? ? ? ? ? 而Sprite的中點(在嘴巴的下方,下圖中用3個箭頭指示)在Entity的左下角,于是,在金幣和實體檢測碰撞時,是以實體的寬、高和坐標(biāo)為準(zhǔn)的,這樣就會造成碰撞的位置“不準(zhǔn)確”。
? ? ? ? ? ? ?
? ? ? ? ?3.13 增加主角受傷時的動作
? ? ? ? ? ? 修改Player.cpp的hit函數(shù),如下:
void Player::hit() {if (getSprite() == NULL) {return;}/*扣血飄字特效*/FlowWord *flowWord = FlowWord::create();this->addChild(flowWord);flowWord->showWord("-15", getSprite()->getPosition());m_iHP -= 15;if (m_iHP < 0) {m_iHP = 0;}/* 創(chuàng)建幾種動作對象*/auto backMove = MoveBy::create(0.1f, Point(-20, 0));auto forwardMove = MoveBy::create(0.1f, Point(20, 0));auto backRotate = RotateBy::create(0.1f, -5, 0);auto forwardRotate = RotateBy::create(0.1f, 5, 0);/* 分別組合成兩種動作 */auto backActions = Spawn::create(backMove, backRotate, NULL);auto forwardActions = Spawn::create(forwardMove, forwardRotate, NULL);auto actions = Sequence::create(backActions, forwardActions, NULL);stopAllActions(); /*先停止所有正在執(zhí)行的動作*/resetData(); /*重置數(shù)據(jù)*/runAction(actions); /*執(zhí)行動作*/ }? ? ? ? ? 3.14 最后還有個resetData函數(shù),如下:
void Player::resetData() {if (m_isJumping) {m_isJumping = false;}setPosition(Point(200, 140);setScale(1.0f);setRotation(0); }? ? ? ? ? ? ? ? ?
? ? ? 3.15 創(chuàng)建分?jǐn)?shù)標(biāo)簽、血量等屬性對象
? ? ? ??
? ? ?這里只需要給TollgateScene類加?xùn)|西,如下:
? ? ? ? ? 頭文件:
class TollgateScene : public Layer {/*忽略了很多代碼*/private:Int m_iScore; //分?jǐn)?shù)Text *m_scoreLab; //分?jǐn)?shù)標(biāo)簽LoadingBar *m_hpBar; //血量條 };? ? ? ? ? 我們需要修改一下TollgateScene的loadUI函數(shù),如下:
void TollgateScene::loadUI() {/*這里省略了一些代碼*//*獲取控件對象*/auto jumpBtn = (Button *)Helper::seekWidgetByName(UI, "JumpBtn");m_scoreLab = (Text *)Helper::seekWidgetByName(UI, "scoreLab");m_hpBar = (LoadingBar *)Helper::seekWidgetByName(UI, "hpProgress");/*這里省略了一些代碼*/ }? ? ? ? ? 讓分?jǐn)?shù)標(biāo)簽和血量條起作用,我們稍微修改一下TollgateScene的update函數(shù)即可:
void TollgateScene::update(float delta) {/*這里繼續(xù)省略了很多代碼*//*增加分?jǐn)?shù)*/m_iScore += 1;/*修改分?jǐn)?shù)標(biāo)簽*/m_scoreLab->setText(Value(m_iScore).asString());/*修改血量進(jìn)度*/m_hpBar->setPercent(m_player->getiHP() / 1000.0f * 100); }? ? ? ?
4.修改HelloWorldScene的init函數(shù),如下代碼:
bool HelloWorld::init() {if (!Layer::init()) { return false; }/*創(chuàng)建很多個小若精靈*/for (int i=0; i<14100; i++) {Sprite *xiaoruo = Sprite::create("sprite0.png");xiaoruo->setPosition(Point(CCRANDOM_0_1()*480, 120 + CCRANDOM_0_1()*300));this->addChild(xiaoruo);} }? ? ?4.1 3.0新功能---Auto-batching
? ? ? ? ?(1) 需確保精靈對象擁有相同的TextureId(精靈表單spritesheet);
? ? ? ? ?(2) 確保它們都使用相同的材質(zhì)和混合功能;
? ? ? ? ?(3) 不再把精靈添加SpriteBatchNode上。
? ? 4.2 修改HelloWorldScene的init函數(shù),代碼如下:
bool HelloWorld::init() {if (!Layer::init()) { return false; }/*創(chuàng)建很多個精靈*/for (int i=0; i<14100; i++) {Sprite *xiaoruo = Sprite::create("sprite0.png");xiaoruo->setPosition(Point(CCRANDOM_0_1()*480, 120+CCRANDOM_0_1()*300));this->addChild(xiaoruo);xiaoruo = Sprite::create("sprite1.png");xiaoruo->setPosition(Point(CCRANDOM_0_1()*480, 120+CCRANDOM_0_1()*300));this->addChild(xiaoruo);} }? ? ?
? ? ? 怎么才算是“連續(xù)的對象”,最簡單的解釋就是:
- ? ? ? ? ?如果節(jié)點具有相同的globalZOrder值,則是連續(xù)的;
 - ? ? ? ? ?否則,如果節(jié)點具有相同的localZOrder值,則是連續(xù)的;
 - ? ? ? ? ?否則,如果節(jié)點具有相同的orderOfArrival值,則是連續(xù)的;
 - ? ? ? ? ?連續(xù)的節(jié)點還必須使用相同的紋理(簡單地說就是相同的圖片)。
 
? ? ? 我們來看HelloWorldScene的init函數(shù),代碼如下:
bool HelloWorld::init() {if (!Layer::init()) { return false; }/*創(chuàng)建很多很多個精靈*/for (int i=0; i<14100; i++) {Sprite *xiaoruo = Sprite::create("sprite0.png");xiaoruo->setPosition(Point(CCRANDOM_0_1()*480, 120+CCRANDOM_0_1()*300));this->addChild(xiaoruo);xiaoruo->setGlobalZOrder(1);xiaoruo = Sprite::create("sprite1.png");xiaoruo->setPosition(Point(CCRANDOm_0_1()*480, 120+CCRANDOM_0_1()*300));this->addChild(xiaoruo);xiaoruo->setGlobalZOrder(2);} }? ? ? ?4.3 修改HelloWorldScene的init函數(shù),如下所示:
bool HelloWorld::init() {if (!Layer::init()) { return false; }/*創(chuàng)建一個精靈,它比較文雅*/auto sprite1 = Sprite::create("sprite1.png");sprite1->setPosition(Point(240, 160));this->addChild(sprite1);/*創(chuàng)建一個精靈,它比較霸氣*/auto sprite2 = Sprite::create("sprite2.png");sprite2->setPosition(Point(200, 160));this->addChild(sprite2); }? ? ? ? ??
? ? 4.4? 繼續(xù)修改,如下:
bool HelloWorld::init() {/*這里省略了很多代碼*/sprite1->setLocalZOrder(2);sprite2->setLocalZOrder(1); }? ? ? ??
? ? 4.5? 新增一個Layer類。
? ? ? ? SecondLayer.h文件:
#ifndef __SecondLayer_H__ #define __SecondLayer_H__#include"cocos2d.h" USING_NS_CC;class SecondLayer : public Layer {public:SecondLayer();~SecondLayer();CREATE_FUNC(SecondLayer);virtual bool init(); };#endif? ? ? ? ?SecondLayer.cpp文件
#include "SecondLayer.h" SecondLayer::SecondLayer() { }SecondLayer::~SecondLayer() { }bool SecondLayer::init() {if (!Layer::init()) { return false; }auto sprite3 = Sprite::create("sprite3.png");sprite3->setPosition(Point(240, 160));this->addChild(sprite3);return false; }? ? ? ?最后,把這個layer也添加到HelloWorldScene場景里,修改createScene函數(shù),如下代碼:
#include "SecondLayer.h"? //別忘了包含頭文件Scene *HelloWorld::createScene() {auto scene = Scene::create();auto layer = HelloWorld::create();Scene->addChild(layer);auto secondLayer = SecondLayer::create();scene->addChild(secondLayer);return scene; }? ? ? ??
? ? ?4.6 修改HelloWorldScene的init函數(shù),如下代碼所示:
bool HelloWorld::init() {if (!Layer::init()) { return false; }/* 創(chuàng)建批次渲染對象,并添加到場景里*/SpriteBatchNode *batchNOde = SpriteBatchNode::create("sprite.png");this->addChild(batchNode);/*創(chuàng)建很多個小若精靈*/for (int i=0; i< 999; i++) {Sprite *xiaoruo = Sprite::create("sprite.png");xiaoruo->setPosition(Point(CCRANDOM_0_1()*480, 120+CCRANDOM_0_1() * 200));/*將精靈添加到batchNode對象*/batchNode->addChild(xiaoruo);}return true; }? ? ??
? ? ?4.7 Texture紋理
bool HelloWorld::init() {if (!Layer::init()) { return false; }Sprite *sp1 = Sprite::createWithSpriteFrame(SpriteFrame::create("sprite.png", Rect(0, 0, 60, 50)));Sprite *sp2 = Sprite::create("sprite.png");sp1->setPosition(Point(100, 200));sp2->setPosition(Point(250, 200));this->addChild(sp1);this->addChild(sp2);/*獲取兩個精靈的紋理對象*/Texture2D *t1 = sp1->getTexture();Texture2D *t2 = sp2->getTexture();return true; }? ? ??
5.用打包前的圖片創(chuàng)建動畫
? ? ?HelloWorldScene.h文件:
class HelloWorld : public cocos2d::Layer {public:/*省略了很多代碼*/private:/*用打包前圖片創(chuàng)建動畫*/cocos2d::Animate *createAnimate1(); };? ? ?HelloWorldScene.cpp文件:
cocos2d::Animate *HelloWorld::createAnimate1() {int iFrameNum = 15;SpriteFrame *frame = NULL;Vector<SpriteFrame *> frameVec;/*用一個列表保存所有SpriteFrame對象*/for (int i=1; i<=iFrameNum; i++) {/*用每一張圖盤創(chuàng)建SpriteFrame對象*/frame = SpriteFrame::create(StringUtils::format("run%d.png", i), Rect(0, 0, 130, 130));frameVec.pushBack(frame);}/*使用SpriteFrame列表創(chuàng)建動畫對象*/Animation *animation = Animation::createWithSpriteFrames(frameVec);animation->setLoops(-1);animation->setDelayPerUnit(0.1f);/*將動畫包裝成一個動作*/Animate *action = Animate::create(animation);return action; }? ? ? ?
? ? ? 創(chuàng)建動畫的步驟一般有3步:
? ? ? ? (1) 創(chuàng)建一組SpriteFrame對象,每張動畫圖片為一個SpriteFrame對象;
? ? ? ? (2) 用這組SpriteFrame對象創(chuàng)建一個Animation對象,該對象包含了動畫所需的一些配置信息;
? ? ? ? (3) 我們要利用Animation對象創(chuàng)建一個Animate對象,Animate其實也是一個動作。精靈直接調(diào)用runAction函數(shù)即可執(zhí)行Animate動畫。
? ? ? ? ? 我們修改一下HelloWorldScene的init函數(shù),測試一下,代碼如下:
bool HelloWorld::init() {if (!Layer::init()) { return false; }/*先創(chuàng)建一個精靈*/Sprite *runSp = Sprite::create("run1.png");runSp->setPosition(Point(200, 200));this->addChild(runSp);/*動畫也是動作,精靈直接執(zhí)行動畫動作即可*/runSp->runAction(createAnimate1());return true; }? ? ? ? ?
? ? ? ?在創(chuàng)建了Animation對象后,要設(shè)置動畫的屬性,代碼如下:
/*使用SpriteFrame列表創(chuàng)建動畫對象*/ Animation *animation = Animation::createWithSpriteFrames(frameVec); animation->setLoops(-1); animation->setDelayPerUnit(0.1f);? ? ? ? ?setLoops函數(shù)用于設(shè)置動畫的播放次數(shù),將setLoops的參數(shù)設(shè)為-1,就代表循環(huán)播放動畫。
6.用打包后的圖片創(chuàng)建動畫
? ? HelloWorldScene.h文件:
class HelloWorld : public cocos2d::Layer {public:/*這里省略很多代碼*/private:/*用打包前圖片創(chuàng)建動畫*/cocos2d::Animate *createAnimate1();/*用打包后的圖片創(chuàng)建動畫*/cocos2d::Animate *createAnimate2(); };? ? HelloWorldScene.cpp文件:
cocos2d::Animate *HelloWorld::createAnimate2() {/*加載圖片幀到緩存池*/SpriteFrameCache *frameCache = SpriteFrameCache::getInstance();frameCache->addSpriteFramesWithFile("boys.plist", "boys.png");int iFrameNum = 15;SpriteFrame *frame = NULL;Vector<SpriteFrame *> frameVec;/*用一個列表保存所有SpriteFrame對象*/for (int i=1; i<=iFrameNum; i++) {/*從SpriteFrame緩存池中獲取SpriteFrame對象*/frame = frameCache->getSpriteFrameByName(StringUtils::format("run%d.png", i));frameVec.pushBack(frame);}/*使用SpriteFrame列表創(chuàng)建動畫對象*/Animation *animation = Animation::createWithSpriteFrames(frameVec);animation->setLoops(-1);animation->setDelayPerUnit(0.1f);/*將動畫包裝成一個動作*/Animation *action = Animate::create(animation);return action; }7.動畫創(chuàng)建輔助類
? ?新建一個類,命名為AnimationUtil,頭文件代碼如下:
#ifndef __AnimationUtil_H__ #define __AnimationUtil_H__#include "cocos2d.h" USING_NS_CC;class AnimationUtil {public:/*根據(jù)文件名字前綴創(chuàng)建動畫對象*/static Animation *createWithSingleFrameName(const char *name, float delay, int iLoops);/*根據(jù)文件名字前綴創(chuàng)建動畫對象,指定動畫圖片數(shù)量*/static Animation *createWithFrameNameAndNum(const char *name, int iNum, float delay, int iLoops); };#endif?
Animation *AnimationUtil::createWithSingleFrameName(const char *name, float delay, int iLoops) {SpriteFrameCache *cache = SpriteFrameCache::getInstance();Vector<SpriteFrame *> frameVec;SpriteFrame *frame = NULL;int index = 1;do {frame = cache->getSpriteFrameByName(StringUtils::format("%s%d.png", name, index++));/*不斷地獲取SpriteFrame對象,直到獲取的值為NULL*/if (frame == NULL) {break;}frameVec.pushBack(frame);} while(true);Animation *animation = Animation::createWithSpriteFrames(frameVec);animation->setLoops(iLoops);animation->setRestoreOriginalFrame(true);animation->setDelayPerUnit(delay);return animation; }Animation *AnimationUtil::createWithFrameNameAndNum(const char *name, int iNum, float delay, int iLoops) {SpriteFrameCache *cache = SpriteFrameCache::getInstance();Vector<SpriteFrame *> frameVec;SpriteFrame *frame = NULL;int index = 1;for (int i=0; i<=iNum; i++) {frame = cache->getSpriteFrameByName(StringUtils::format("%s%d.png", name, i));if (frame == NULL) {break;}frameVec.pushBack(frame);}Animation *animation = Animation::createWithSpriteFrames(frameVec);animation->setLoops(iLoops);animation->setRestperOriginalFrame(true);animation->setDelayPerUnit(delay);return animation; }? ? ? ? 測試一下,修改HelloWorldScene的init函數(shù),如下:
bool HelloWorld::init() {if (!Layer::init()) { return false; }/*先創(chuàng)建一個精靈*/Sprite *runSp = Sprite::create("run1.png");runSp->setPosition(Point(200, 200));this->addChild(runSp);/*加載圖片幀到緩存池*/SpriteFrameCache *frameCache = SpriteFrameCache::getInstance();frameCache->addSpriteFramesWithFile("boys.plist", "boys.png");/*用輔助工具創(chuàng)建動畫*/Animation *animation = AnimationUtil::createWithSingleFrameName("run", 0.1f, -1);//Animation *animation = AnimationUtil::createWithFrameNameAndNum("run", 15, 0.1f, -1);/*動畫也是動作,精靈直接執(zhí)行動畫動作即可*/runSp->runAction(Animate::create(animation));return true; }? ? ? ? 使用的步驟如下:
? ? ? ? (1) 加載圖片幀到緩存池,因為一張打包好的圖片往往不是一種動畫,所以最好不要在創(chuàng)建Animation對象的函數(shù)時才加載圖片幀。
? ? ? ? (2) 調(diào)用AnimationUtil的函數(shù)創(chuàng)建Animation對象。
? ? ? ? (3) 創(chuàng)建Animate對象,精靈執(zhí)行該動作即可。
8.《跑跑跑》
? ?8.1 創(chuàng)建跑步場景
? ? ? ?TollgateScene.h文件:
#ifndef _TollgateScene_H_ #define _TollgateScene_H_#include "cocos2d.h" using namespace cocos2d;class TollgateScene : public Layer {public:static Scene *createScene();CREATE_FUNC(TollgateScene);virtual bool init(); }; #endif? ? ? ? ?TollgateScene.cpp文件:
Scene *TollgateScene::createScene() {auto scene = Scene::create();auto layer = TollgateScene::create();scene->addChild(layer);return scene; } bool TollgateScene::init() {if (!Layer::init()) { return false; }/*加載Tiled地圖,添加到場景中*/TMXTiledMap *map = TMXTiledMap::create("level01.tmx");this->addChild(map);return true; }? ?8.2 創(chuàng)建實體類和主角類
? ? ? Entity.h文件:?
#ifndef _Entity_H_ #define _Entity_H_#include "cocos2d.h" using namespace cocos2d; class Entity : public Node {public:/*綁定精靈對象*/void bindSprite(Sprite *sprite);protected:Sprite *m_sprite; };#endif? ? ? ?Entity.cpp文件:
#include "Entity.h"void Entity::bindSprite(Sprite *sprite) {m_sprite = sprite;this->addChild(m_sprite); }? ? ? ?Player.h文件
#ifndef _Player_H_ #define _Player_H_#include "Entity.h" class Player : public Entity {public:CREATE_FUNC(Player);virtual bool init();void run(); };#endif? ? ? Player.cpp文件
bool Player::init() {return true; }void Player::run() { }? ? ? 玩家有了,我們把它加到地圖里。打開TollgateScene.cpp文件,修改init方法,如下代碼:
bool TollgateScene::init() {if (!Layer::init()) { return false; }/*加載Tiled地圖,添加到場景中(這部分代碼沒貼出來)*/addPlayer(map); /*加載玩家*/return true; }? ? ? ?addPlayer函數(shù)如下:
void TollgateScene::addPlayer(TMXTiledMap *map) {Size visibleSize = Director::getInstance()->getVisibleSize();/*創(chuàng)建精靈*/Sprite *playerSprite = Sprite::create("player.png");/*將精靈綁定到玩家對象上*/Player *mPlayer = Player::create();mPlayer->bindSprite(playerSprite);mPlayer->run();/*設(shè)置玩家坐標(biāo)*/mPlayer->setPosition(Point(100, visibleSize.height / 2));/*將玩家添加到地圖*/map->addChild(mPlayer); }? ? ?效果圖:
? ? ? ? ? ? ?
? ?8.3 繼續(xù)打開TollgateScene.cpp文件,修改addPlayer函數(shù),如下:
/*加載對象層*/TMXObjectGroup *objGroup = map->getObjectGroup("objects");/*加載玩家坐標(biāo)對象*/ValueMap playerPointMap = objGroup->getObject("PlayerPoint");float playerX = playerPointMap.at("x").asFloat();float playerY = playerPointMap.at("y").asFloat();/*設(shè)置玩家坐標(biāo)*/mPlayer->setPosition(Point(playerX, playerY));? ? 8.4 讓主角跑
? ? ? ? 首先給Player類增加一個函數(shù),代碼如下:
void Player::run() {SpriteFrameCache *frameCache = SpriteFrameCache::getInstance();frameCache->addSpriteFramesWithFile("boys.plist", "boys.png");SpriteFrame *frame = NULL;Vector<SpriteFrame *> frameList;/*創(chuàng)建精靈幀對象,添加到列表里*/for (int i=1; i<=15; i++) {frame = frameCache->getSpriteFrameByName(StringUtils::format("run%d.png", i));frameList.pushBack(frame);}/*根據(jù)精靈幀對象創(chuàng)建動畫對象*/Animation *animation = Animation::createWithSpriteFrames(frameList);animation->setLoops(-1); //循環(huán)播放animation->setDelayPerUnit(0.08f); //每幀播放間隔/*創(chuàng)建動畫動作*/Animate *animate = Animate::create(animation);/*精靈執(zhí)行動作*/m_sprite->runAction(animate); }? ? ? ? ?效果:
? ? ? ? ? ?
? ? 8.5 添加角色控制器
? ? ? ?Controller.h文件:
#ifndef _Controller_H_ #define _Controller_H_#include "cocos2d.h" #include "ControllerListener.h"using namespace cocos2d; class Controller : public Node {public:/*設(shè)置監(jiān)聽對象*/void setControllerListener(ControllerListener *controllerListener);protected:ControllerListener *m_controllerListener; };#endif? ? ? Controller.cpp文件
void Controller::setControllerListener(ControllerListener *controllerListener) {this->m_controllerListener = controllerListener; }? ? ControllerListener就是將要被控制的對象,比如主角,只要繼承了ControllerListener接口,就能夠被控制器控制。
? ? ? ?ControllerListener.h頭文件:
#ifndef _ControllerListener_H_ #define _ControllerListener_H_#include "cocos2d.h" using namespace cocos2d; class ControllerListener {public:/*設(shè)置目標(biāo)坐標(biāo)*/virtual void setTagPosition(int x, int y) = 0;/*獲取目標(biāo)坐標(biāo)*/virtual Point getTagPosition() = 0; };#endif? ? ? ?8.6 主角移動控制器
? ? ? ? ? SimpleMoveController.h文件
#ifndef _SimpleMoveController_H_ #define _SimpleMoveController_H_#include "cocos2d.h" #include "Controller.h" using namespace cocos2d;class SimpleMoveController : public Controller {public:CREATE_FUNC(SimpleMoveController);virtual bool init();virtual void update(float dt);/*設(shè)置移動速度*/void setiSpeed(int iSpeed);private:int m_iSpeed; };#endif? ? ? ? ? ?SimpleMoveController.cpp文件
bool SimpleMoveController::init() {this->m_iSpeed = 0;/*每一幀都要調(diào)用update函數(shù),所以要這樣設(shè)置*/this->scheduleUpdate();return true; } void SimpleMoveController::update(float dt) {if (m_controllerListener == NULL) {return;}/*增加移動對象的X坐標(biāo)值*/Point pos = m_controllerListener->getTagPosition();pos.x += m_iSpeed;m_controllerListener->setTagPosition(pos.x, pos.y); }void SimpleMoveController::setiSpeed(int iSpeed) {this->m_iSpeed = iSpeed; }? ? ? ? ? ?我們需要修改Entity類:
? ? ? ?Entity.h文件:
#include "ControllerListener.h" #include "Controller.h" class Entity : public Node, public ControllerListener {public:/*綁定精靈對象*/void bindSprite(Sprite *sprite);/*設(shè)置控制器*/void setController(Controller *controller);/*實現(xiàn)SimpleMoveListener接口的方法*/virtual void setTagPosition(int x, int y);virtual Point getTagPosition();protected:Sprite *m_sprite;Controller *m_controller; };? ? ? ?我們還為Entity新增了一個方法,那就是setController.
void Entity::setController(Controller *controller) {this->m_controller = controller;m_controller->setControllerListener(this); }void Entity::setTagPosition(int x, int y) {setPosition(Point(x, y)); }Point Entity::getTagPosition() {return getPosition(); }? ? ? TollgateScene.cpp的addPlayer函數(shù):
#include "SimpleMoveController.h" void TollgateScene::addPlayer(TMXTiledMap *map) {/*省略了很多很多代碼*//*--------------創(chuàng)建玩家簡單移動控制器--------------*/SimpleMoveController *simpleMoveControll = SimpleMoveController::create();/*設(shè)置移動速度*/simpleMoveControll->setiSpeed(1);/*控制器要添加到場景中才能讓update被調(diào)用*/this->addChild(simpleMoveControll);/*設(shè)置控制器到主角身上*/mPlayer->setController(simpleMoveControll); }? ? ?現(xiàn)在主角就會一直往前跑了,效果:
? ? ? ? ? ?
? ? ? 8.7 讓地圖隨著主角滾動
? ? ? ? ? 為Player類增加一個函數(shù),如下:
void Player::setViewPointByPlayer() {if (m_sprite == NULL){return;}Layer *parent = (Layer *)getParent();/*地圖方塊數(shù)量*/Size mapTiledNum = m_map->getMapSize();/*地圖單個格子大小*/Size tiledSize = m_mpa->getTileSize();/*地圖大小*/Size mapSize = Size(mapTiledNum.width * tiledSize.width,mapTiledNum.height * tiledSize.height);/*屏幕大小*/Size visibleSize = Director::getInstance()->getVisibleSize();/*主角坐標(biāo)*/Point spritePos = getPosition();/*如果主角坐標(biāo)小于屏幕的一半,則取屏幕中點坐標(biāo),否則取主角的坐標(biāo)*/float x = std::max(spritePos.x, visibleSize.width / 2);float y = std::max(spritePos.y, visibleSize.height / 2);/*如果X、Y的坐標(biāo)大于右上角的極限值,則取極限值的坐標(biāo)(極限值是指不讓地圖超出屏幕造成出現(xiàn)黑邊的極限坐標(biāo)*/x = std::min(x, mapSize.width - visibleSize.width / 2);y = std::min(y, mapSize.height - visibleSize.height / 2);/*目標(biāo)點*/Point destPos = Point(x, y);/*屏幕中點*/Point centerPos = Point(visibleSize.width / 2, visibleSize.height / 2);/*計算屏幕中點和所要移動的目的點之間的距離*/Point viewPos = centerPos - destPos;parent->setPosition(viewPos); }? ? ? ? ? 這個函數(shù)的功能是讓地圖所在圖層以主角為中心進(jìn)行移動,也就是讓事件的焦點停留在主角身上,屏幕隨著主角移動。
? ? ?然后,Player要重寫父類的setTagPosition函數(shù),代碼如下:
void Player::setTagPosition(int x, int y) {Entity::setTagPosition(x, y);/*以主角為中心移動地圖*/setViewPointByPlayer(); }? ? ? ? ? 在頭文件中加入函數(shù)聲明:
virtual void setTagPosition(int x, int y);? ? ? ? 再次修改Player類,代碼如下:
? ? ?Player.h文件:
class Player : public Entity {public:/*省略了很多代碼*/void setTiledMap(TMXTiledMap *map);private:TMXTiledMap *m_map; };? ? Player.cpp文件
void Player::setTiledMap(TMXTiledMap *map) {this->m_map = map; }? ? ?打開TollgateScene的addPlayer函數(shù),在創(chuàng)建Player對象之后,再調(diào)用Player的setTiledMap函數(shù),如下:
void TollgateScene::addPlayer(TMXTiledMap *map) {/*省略了一些代碼*//*將精靈綁定到玩家對象上*/Player *mPlayer = Player::create();mPlayer->bindSprite(playerSprite);mPlayer->run();mPlayer->setTiledMap(map);/*省略了很多代碼*/ }? ? ? ?地圖會有一些細(xì)細(xì)的黑邊,可能滾動的時候特別明顯,在代碼中加入:
Director::getInstance()->setProjection(Director::Projection::_2D);? ? ? ??
? ? ? 8.8 三方移動控制器
? ? ? ? ThreeDirectionController.h文件:
#include "Controller.h" #include "cocos2d.h" using namespace cocos2d;class ThreeDirectionController : public Controller {public:CREATE_FUNC(ThreeDirectionController);virtual bool init();virtual void update(float dt);/*設(shè)置X方向的移動速度*/void setiXSpeed(int iSpeed);/*設(shè)置Y方向的移動速度*/void setiYSpeed(int iSpeed);private:int m_iXSpeed;int m_iYSpeed;/*注冊屏幕觸摸事件*/void registerTouchEvent(); };? ? ? ? ?ThreeDirectionController.cpp文件:
#include "ThreeDirectionController.h" bool ThreeDirectionController::init() {this->m_iXSpeed = 0;this->m_iYSpeed = 0;/*注冊屏幕觸摸事件*/registerTouchEvent();/*開啟update函數(shù)的調(diào)用*/this->scheduleUpdate();return true; }void ThreeDirectionController::update(float dt) {if (m_controllerListener == NULL) {return;}/*讓移動對象在X和Y方向上增加坐標(biāo)*/Point curPos = m_controllerListener->getTagPosition();curPos.x += m_iXSpeed;m_controllerListener->setTagPosition(curPos.x + m_iXSpeed, curPos.y + m_iYSpeed); }void ThreeDirectionController::setiXSpeed(int iSpeed) {this->m_iXSpeed = iSpeed; }void ThreeDirectionController::setiYSpeed(int iSpeed) {this->m_iYSpeed = iSpeed; }void ThreeDirectionController::registerTouchEvent() {auto listener = EventListenerTouchOneByOne::create();listener->onTouchBegan = [](Touch *touch, Event *event) {return true;};listener->onTouchMoved = [&](Touch *touch, Event *event) {/*獲取單擊坐標(biāo),基于Cocos2d-x*/Point touchPos = Director::getInstance()->convertToGL(touch->getLocationInView());/*被控制對象的坐標(biāo)*/Point pos = m_controllerListener->getTagPosition();/*判斷是向上移動還是向下移動*/int iSpeed = 0;if (touchPos.y > pos.y) {iSpeed = 1;} else {iSpeed = -1;}setiYSpeed(iSpeed);};listener->onTouchEnded = [&](Touch *touch, Event *event) {/*停止Y坐標(biāo)上的移動*/setiYSpeed(0);};_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this); }? ? ? ? 打開TollgateScene.cpp的addPlayer函數(shù),將SimpleMoveController替換為ThreeDirectionController,代碼如下:
#include "ThreeDirectionController.h" void TollgateScene::addPlayer(TMXTiledMap *map) {/*又忽略了很多代碼*//* ---------------創(chuàng)建玩家移動控制器-------------*/ThreeDirectionController *threeMoveControll = ThreeDirectionController::create();threeMoveControll->setiXSpeed(1);threeMoveControll->setiYSpeed(0);/*控制器要添加到場景中才能獲得update事件*/this->addChild(threeMoveControll);/*設(shè)置控制器到主角身上*/mPlayer->setController(threeMoveControl); }? ? ? ? ?為Player添加一個函數(shù)tileCoordForPosition,代碼如下:
? ? ?Player.h文件:
class Player : public Entity {/*省略了很多代碼*/private:/*標(biāo)記主角是否碰撞了障礙物,在反彈中*/bool isJumping;/*檢測碰撞的地圖層*/TMXLayer *meta;/*將像素坐標(biāo)轉(zhuǎn)換為地圖格子坐標(biāo)*/Point tileCoordForPosition(Point pos); };? ? ?Player.cpp文件:
Point Player::tileCoordForPosition(Point pos) {Size mapTiledNum = m_map->getMapSize();Size tiledSize = m_map->getTileSize();int x = pos.x / tiledSize.width;/*Cocos2d-x的默認(rèn)Y坐標(biāo)是由下至上的,所以要做一個相減操作*/int y = (700 - pos.y) / tiledSize.height;/*格子坐標(biāo)從零開始計算*/if (x > 0) {x -= 1;}if (y > 0) {y -= 0;}return Point(x, y); }? ? ? ? 再次打開Player類,修改setTiledMap函數(shù),代碼如下:
void Player::setTiledMap(TMXTiledMap *map) {this->m_map = map;/*保存meta圖層的引用*/this->meta = m_map->getLayer("meta");this->meta->setVisible(false); }? ? ? ? ?最后一步,修改Player.cpp的setTagPosition函數(shù):
void Player::setTagPosition(int x, int y) {/*----------------判斷前面是否不可通行------------*//*取主角前方的坐標(biāo)*/Size spriteSize = m_sprite->getContentSize();Point dstPos = Point(x + spriteSize.widht / 2, y);/*獲得當(dāng)前主角前方坐標(biāo)在地圖中的格子位置*/Point tiledPos = tileCoordForPosition(Point(dstPos.x, dstPos.y));/*獲取地圖格子的唯一標(biāo)識*/int tiledGid = meta->getTileGIDAt(tiledPos);/*不為0,代表存在這個格子*/if (tiledGid != 0) {/*獲取該地圖格子的所有屬性,目前我們只有一個Collidable屬性格子是屬于meta層的,但同時也是屬于整個地圖的,所以在獲取格子的所有屬性時,通過格子唯一標(biāo)識在地圖中取得*/Value properties = m_map->getPropertiesForGID(tiledGid);/*取得格子的collidate屬性值*/Value prop = properties.asValueMap().at("Collidable");/*判斷Collidable屬性是否為true,如果是,則不讓玩家移動*/if (prop.asString().compare("true") == 0) {return;}}Entity::setTagPosition(x, y);/*以主角為中心移動地圖*/setViewPointByPlayer(); }? ? ? ? ? ??
? ? ?8.9 當(dāng)遇到障礙物時,不是讓主角停止前進(jìn),而是讓主角向后彈,如代碼所示:
/*判斷Collidate屬性是否為true,如果是,不讓玩家移動*/ if (prop.asString().compare("true") == 0 && isJumping == false) {isJumping = true;auto jumpBy = JumpBy::create(0.5f, Point(-100, 0), 80, 1);CallFunc *callfunc = CallFunc::create([&](){/*恢復(fù)狀態(tài)*/isJumping = false;});/*執(zhí)行動作,碰撞到障礙物時的反彈效果*/auto actions = Sequence::create(jumpBy, callFunc, NULL);this->runAction(actions); }? ? ? 8.10 添加能吃的物品以及勝利條件
? ? ? ? ?打開Player.cpp的setTagPosition函數(shù),如下:
Value properties = m_map->getPropertiesForGID(tiledGid);ValueMap propertiesMap = properties.asValueMap();if (propertiesMap.find("Collidable") != propertiesMap.end()) {/*取得格子的Collidable屬性值*/Value prop = propertiesMap.at("Collidable");/*判斷Collidable屬性是否為true,如果是,則不讓玩家移動*/if (prop.asString().compare("true") == 0 && isJumping == false) {/*這里面的代碼沒變,所以省略*/} } if(propertiesMap.find("food") != propertiesMap.end()) {/*取得格子的food屬性值,判斷是否為true,如果是,則讓格子上的物體消失*/Value prop = properties.asValueMap().at("food");if (prop.asString().compare("true") == 0) {/*從障礙物層清除當(dāng)前格子的物體*/TMXLayer *barrier = m_map->getLayer("barrier");barrier->removeTileAt(tiledPos);} }? ? ? ? ? ?最后,依舊修改Player的setTagPosition函數(shù),代碼如下:
if (propertiesMap.find("win") != propertiesMap.end()) {Value prop = properties.asValueMap().at("win");if (prop.asString().compare("true") == 0) {/*取得格子的win屬性值,判斷是否為true,如果是,則游戲勝利,跳轉(zhuǎn)到勝利場景*/Director::getInstance()->replaceScene(WinScene::createScene());} }? ? ? ? ? ? 我們判斷地圖格子的win屬性是否為true,如果是,則代表主角達(dá)到終點,切換到勝利場景,如下圖所示:
? ? ?
9.scheduleUpdate和update
? ? HelloWorldScene的init函數(shù):
bool HelloWorld::init() {if (!Layer::init()) { return false; }this->scheduleUpdate();return true; }? ? ?HelloWorldScene.h文件:
class HelloWorld : public cocos2d::Layer {public:static cocos2d::Scene *createScene();virtual bool init();CREATE_FUNC(HelloWorld);/*重寫update函數(shù)*/virtual void update(float dt); };? ? ? this->scheduleUpdate()函數(shù)是為了把當(dāng)前節(jié)點(如Layer)添加到隊列里,只要把節(jié)點添加到隊列里(或許是其他結(jié)構(gòu),總之可以存放節(jié)點),那么這個節(jié)點就會在游戲運行的每一幀被調(diào)用一次update函數(shù)。
? ? ? 9.1 不調(diào)用update函數(shù),調(diào)用自己的函數(shù)
? ? ? ? ? 先為HelloWorldScene添加一個函數(shù),代碼如下:
? ? ? ? ?HelloWorldScene.h文件?
class HelloWorld : public cocos2d::Layer {public:static cocos2d::Scene *createScene();virtual bool init();CREATE_FUNC(HelloWorld);void mutUpdate(float dt); /*自定義的update函數(shù)*/ };? ? ? ? ?HelloWorldScene.cpp文件:
bool HelloWorld::init() {if (!Layer::init()) { return false; }/*指定每幀執(zhí)行自定義的函數(shù)*/this->schedule(schedule_selector(HelloWorld::mutUpdate));return true; }void HelloWorld::mutUpdate(float dt) {log("MutUpdate"); }? ? ? ? ? Cocos2d-x在指定回調(diào)函數(shù)時都使用*selector的形式,比如我們要知道schedule的回調(diào)函數(shù),則使用schedule_seelctor,要指定按鈕的回調(diào)函數(shù)則使用callfunc_selector等。
? ? ? ? ? 常用的selector如下:
- ? ? ? ? ? ?schedule_selector: 常用于定義定時器回調(diào)函數(shù),函數(shù)無參數(shù);
 - ? ? ? ? ? ?callfunc_selector: 常用于定義動作回調(diào)函數(shù),函數(shù)無參數(shù);
 - ? ? ? ? ? ?callfuncN_selector: 常用于定義動作回調(diào)函數(shù),函數(shù)帶一個Node *參數(shù);
 - ? ? ? ? ? ?callfuncND_selector:常用于定義動作回調(diào)函數(shù),函數(shù)帶一個Node *參數(shù)和一個void *參數(shù)(任意類型);
 - ? ? ? ? ? ?menu_selector:常用于定義菜單回調(diào)函數(shù),帶一個CCObject*參數(shù)。
 
? ? ? ? ?9.2 真正的定時器
? ? ? ? ? ?修改一下schedule的參數(shù)就可以了,如下代碼:
bool HelloWorld::init() {if (!Layer::init()) { return false; }/*指定每幀執(zhí)行自定義的函數(shù)*/this->schedule(schedule_selector(HelloWorld::mutUpdate), 2.0f);return true; } void HelloWorld::mutUpdate(float dt) {log("MutUpdate dt = %f", dt); }? ? ? ? ? ? ?輸出:
? ? ? ? ? ? ?
? ? ? ? ?9.3 讓一切都停下來-----unSchedule
? ? ? ? ? ? HelloWorldScene.h文件:
class HelloWorld : public cocos2d::CCLayer {public:static cocos2d::Scene *createScene();virtual bool init();CREATE_FUNC(HelloWorld);virtual void update(float dt); /*默認(rèn)的update函數(shù)*/void mutUpdate(float dt); /*自定義的update函數(shù)*/ };? ? ? ? ? ? ?HelloWorldScene.cpp文件:
bool HelloWorld::init() {if (!Layer::init()) { return false; }/*指定每幀執(zhí)行update函數(shù)*/this->scheduleUpdate();/*指定每幀執(zhí)行自定義的函數(shù),指定每隔n秒執(zhí)行一次*/this->schedule(schedule_selector(HelloWorld::mutUpdate), 2.0f);return true; }void HelloWorld::mutUpdate(float dt) { }void HelloWorld::update(float dt) {log("update");this->unscheduleUpdate(); }? ? ? ? ? ?如果想停止調(diào)用自定義的update函數(shù),代碼如下:
void HelloWorld::mutUpdate(float dt) {log("Mutupdate dt = %f", dt);this->unschedule(schedule_selector(HelloWorld::mutUpdate)); }? ? ? ? ? 要想停止所有的update函數(shù),只需要一句代碼:this->unscheduleAllSelectors()。
? ? ? ?9.4 scheduleOnce和回調(diào)函數(shù)
? ? ? ? ? HelloWorldScene.h文件:
class HelloWorld : public cocos2d::CCLayer {public:static cocos2d::Scene *createScene();virtual bool init();CREATE_FUNC(HelloWorld);/*回調(diào)函數(shù),西紅柿烤好了*/void cookFinish(float dt); };? ? ? ? ? ?HelloWorldScene.cpp文件
bool HelloWorld::init() {if (!Layer::init()) { return false; }/*指定若干秒后執(zhí)行一次函數(shù)*/this->scheduleOnce(schedule_selector(HelloWorld::cookFinish), 2.0f);return true; }void HelloWorld::cookFinish(float dt) {log("cookFinish!"); }? ? ? ?9.5 更準(zhǔn)確地計時----不會變慢的時間
? ? ? ? ? TimeCounter.h文件:
#include "cocos2d.h" USING_NS_CC; class TimeCounter : public Node {public:CREATE_FUNC(TimeCounter);virtual bool init();virtual void update(float dt);void start(); /*開始計時*/float getfCurTime(); /*獲取當(dāng)前時間*/private:float m_fTime; };? ? ? ? ? TimeCounter.cpp文件:
#include "TimeCounter.h" bool TimeCounter::init() {return true; } void TimeCounter::update(float dt) {m_fTime += dt; } float TimeCounter::getfCurTime() {return m_fTime; }void TimeCounter::start() {m_fTime = 0;this->scheduleUpdate(); }? ? ? ?來看看執(zhí)行情況,修改HelloWorldScene類,代碼如下:
? ? ? HelloWorldScene.h文件:
#include "TimeCounter.h" class HelloWorld : public cocos2d::Layer {public:static cocos2d::Scene *createScene();virtual bool init();CREATE_FUNC(HelloWorld);void logic(float dt);void doSomething(float dt);private:TimeCounter *m_timeCounter; };? ? ? ?HelloWorldScene.cpp文件:
bool HelloWorld::init() {if (!Layer::init()) { return false; }/*創(chuàng)建計時器,要添加到場景*/m_timeCounter = TimeCounter::create();this->addChild(m_timeCounter);/*開始計時*/m_timeCounter->start();this->schedule(schedule_selector(HelloWorld::logic), 1.0f);this->schedule(schedule_selector(HelloWorld::doSomething));return true; }void HelloWorld::logic(float dt) {log("%f", m_timeCounter->getfCurTime()); }void HelloWorld::doSomething(float dt) {for (int i=0; i<9999999; i++) {} }? ? ? ?結(jié)果如下:
? ? ??
? ? ?9.6 給TimeCounter做進(jìn)一步的強(qiáng)化,讓它可以在我們規(guī)定的時間做我們預(yù)設(shè)的事情。
? ? ? ?CallbakcTimeCounter.h文件:
class CallbackTimeCounter : public Node {public:CREATE_FUNC(CallbackTimeCounter);virtual bool init();virtual void update(float dt);/*開始計時,指定回調(diào)時間和回調(diào)函數(shù)*/void start(float fCBTime, std::function<void()> func);private:float m_fTime; /*用于計時*/float m_fCBTime; /*回調(diào)的時間*/bool m_isCounting; /*標(biāo)記是否正在計時*/std::funuction<void()> m_func; /*回調(diào)函數(shù)*/ };? ? 這次我們的定時器是這樣工作的:
- ? ?不斷調(diào)用update函數(shù),根據(jù)m_isCounting標(biāo)記,判斷是否要計時;
 - ? ?發(fā)現(xiàn)到了指定時間后,就回調(diào)m_func函數(shù);
 - ? ?使用方式是,調(diào)用start函數(shù),傳入指定時間,以及要回調(diào)的函數(shù)(lambda匿名函數(shù))
 
? ? ? CallbackTimeCounter.cpp文件:
#include "CallbackTimeCounter.h"/*在調(diào)用start函數(shù)之前都不開始計時;然后調(diào)用scheduleUpdate開啟update函數(shù)的調(diào)用*/ bool CallbackTimeCounter::init() {m_isCounting = false;this->scheduleUpdate();return true; }/*如果m_isCounting為true,則執(zhí)行計時,使用m_fTime來累計總時間。 當(dāng)m_fTime達(dá)到或超過指定時間時,就回調(diào)m_func函數(shù),并結(jié)束計時狀態(tài)(設(shè)置m_isCounting為false)*/ void CallbackTimeCounter::update(float dt) {if (m_isCounting == false) {return;}m_fTime += dt;/*到達(dá)時間,回調(diào)函數(shù)*/if (m_fTime >= m_fCBTime) {m_func();m_isCounting = false;} }void CallbackTimeCounter::start(float fCBTime, std::function<void()> func) {m_fCBTime = fCBTime;m_fTime = 0;m_func = func;m_isCounting = true; }? ? ?9.7 NotificationCenter常用的幾個函數(shù)及其參數(shù)的說明如下:
? ? ? ?(1) addObserver(訂閱消息)
? ? ? ?(2) removeObserver(取消訂閱消息)
? ? ? ?(3) postNotification(發(fā)布消息)
? ? ? ? ? ? const std::string &name:消息名稱
? ? ? ?(4) postNotification(發(fā)布消息)
? ? ? ? ? ? const std::string &name:消息名稱
? ? ? ? ? ? Ref *sender: 傳遞的數(shù)據(jù)
? ? ? ? 消息訂閱不僅僅能用于同一個Layer下的對象,它最強(qiáng)大的功能在于可以跨越不同的Layer進(jìn)行消息訂閱和發(fā)布。
? ? ? ? 來看看兩個Layer之間如何進(jìn)行消息訂閱和發(fā)布,創(chuàng)建一個新的類,命名為OtherLayer,代碼如下:
? ? ? ?OtherLayer.h文件:
class OtherLayer : public Layer {public:CREATE_FUNC(OtherLayer);virtual bool init();private:/*接收test消息的回調(diào)函數(shù)*/void testMsg(Ref *pData); };? ? ? OtherLayer.cpp文件:
bool OtherLayer::init() {if (!Layer::init()) { return false; }/*訂閱消息類型為test的消息,不傳遞消息*/NotificationCenter::getInstance()->addObserver(this,callfuncO_selector(OtherLayer::testMsg),"test",NULL);return true; }void OtherLayer::testMsg(Ref *pData) {log("testMsg in OtherLayer"); }? ? ? ? ? 我們新建一個繼承了layer的類,并且該類訂閱了test消息。我們把HelloWorldScene的testMsg函數(shù)刪除,并且把訂閱消息的代碼也刪除,同時修改scene函數(shù),代碼如下:
? ? ?
#include "OtherLayer.h"Scene *HelloWorld::createScene() {auto scene = Scene::create();auto layer = HelloWorld::create();scene->addChild(layer);/*在添加一個layer*/auto otherLayer = OtherLayer::create();scene->addChild(otherLayer);return scene; } bool HelloWorld::init() {if (!Layer::init()) { return false; }/*3秒后發(fā)布test的消息*/this->schedule(schedule_selector(HelloWorld::sendMsg), 3.0f);return true; }void HelloWorld::sendMsg(float dt) {/*發(fā)布test消息,不傳遞數(shù)據(jù)*/NotificationCenter::getInstance()->postNotification("test", NULL); }? ? ? ?再次運行項目,3秒后,將看到以下日志輸出:
testMsg in OtherLayer? ? ? ? 我們在HelloWorldScene里發(fā)布消息,在OtherLayer里接收消息,這次就能夠表現(xiàn)出消息訂閱的優(yōu)勢了。再具體一點,比如游戲中的戰(zhàn)斗,角色死亡的消息也可以被訂閱,當(dāng)角色死亡后,那些訂閱了這個消息的主體就能做出一些處理(比如怪物停止攻擊
彈出死亡提示界面,等等)。
? ? 9.8 用lambda函數(shù)來實現(xiàn)自己的觀察者吧
? ? ? ? NotifyUtil.h文件:
class NotifyUtil : public Ref {public:static NotifyUtil *getInstance();CREATE_FUNC(NotifyUtil);virtual bool init();/*訂閱消息*/void addObserver(const std::string &sMsgName, std::function<void(Ref*)> func);/*發(fā)布消息*/void postNotification(const std::string &sMsgName, Ref *data);private:static NotifyUtil *m_NotifyUtil;/*看起來很復(fù)雜,其實很簡單*/std::map<std::string, std::vector<std::function<void(Ref*)>>> m_funcMap; };? ? ? ? ?NotifyUtil.cpp文件:
#include "NotifyUtil.h" NotifyUtil *NotifyUtil::m_NotifyUtil = NULL;NotifyUtil *NotifyUtil::getInstance() {if (m_NotifyUtil == NULL) {m_NotifyUtil = NotifyUtil::create();m_NotifyUtil->retain();}return m_NotifyUtil; } bool NotifyUtil::init() {return true; }void NotifyUtil::addObserver(const std::string &sMsgName, std::function<void(Ref*)> func) {/*查找是否有已經(jīng)存在該消息的回調(diào)列表*/if (m_funcMap.find(sMsgName) != m_funcMap.end()) {/*已經(jīng)存在該回調(diào)列表(換句話說,已經(jīng)有人訂閱過同樣的消息 */std::vector<std::function<void(Ref*)>> &funcList = m_funcMap.at(sMsgName);/*將新的訂閱者添加到回調(diào)列表里*/funcList.push_back(func);} else {/*不存在該回調(diào)列表(換句話說,已經(jīng)有人訂閱過同樣的消息*/std::vector<std::function<void(Ref*)>> funcList;/*將新建的列表保存到map中*/m_funcMap[sMapName] = funcList;} }void NotifyUtil::postNotification(const std::string &sMsgName, Ref *data) {/*查找是否有人訂閱過該消息*/if (m_funcMap.find(sMsgName) != m_funcMap.end()) {/*取得回調(diào)列表*/std::vector<std::function<void(Ref*)>> funcList = m_funcMap.at(sMsgName);/*遍歷列表,回調(diào)函數(shù),并傳遞數(shù)據(jù)*/for (auto func : funcList) {func(data);}} }? ?來測試一下,修改HelloWorldScene的Init函數(shù),代碼如下:
bool HelloWorld::init() {if (!Layer::init()) { return false; }NotifyUtil::getInstance()->addObserve("LikeHer", [](Ref *data) {log("Recv Msg:%s", data);});NotifyUtil::getInstance()->addObserve("LikeHer", [](Ref *data) {log("Gongxi Gongxi~");});NotifyUtil::getInstance()->addObserve("LikeHer", [](Ref *data) {log("ai yo, bu cuo o~");});NotifyUtil::getInstance()->postNotification("LikeHer", (Ref *J) "hehe");return true; }? ?調(diào)用NotifyUtil的addObserver函數(shù)訂閱了3次消息,并且都是"LikeHer"消息,也就是有3個訂閱者對這個消息感興趣。
? 最后,調(diào)用postNotification函數(shù)發(fā)布“LikeHer”消息,同時傳遞“hehe”字符串?dāng)?shù)據(jù)
? 看到以下輸出:
Recv Msg:here Gongxi Gongxi~ ai yo, bu cuo o~? 因為NotifyUtil是單例類,所以postNotification函數(shù)在任何類里調(diào)用都能成功發(fā)布消息,在游戲開發(fā)中,消息派發(fā)幾乎是不可缺少的。
10.當(dāng)你把一個對象作為成員變量時,并且沒有把對象addChild到另外一個對象時,就需要調(diào)用retain函數(shù)。
? ? ?最后,一定要記住,必須要調(diào)用了對象的autorelease函數(shù)之后,retain和release函數(shù)才會生效,否則,一切都是徒勞。
? ? 因此,建議使用create的方式創(chuàng)建對象,Cocos2d-x的類大部分都具有create函數(shù),create函數(shù)里會在創(chuàng)建對象后調(diào)用autorelease函數(shù),代碼如下:
Sprite *Sprite::create(const std::string& filename) {Sprite *sprite = new Sprite();if (sprite && sprite->initWithFile(filename)) {sprite->autorelease();return sprite;}CC_SAFE_DELETE(sprite);return nullptr; }? ? ?10.1 看看UserDefault用于保存數(shù)據(jù)的函數(shù),代碼如下:
/* 保存布爾類型的數(shù)據(jù)*/ void setBoolForKey(const char *pKey, bool value); /* 保存整形類型的數(shù)據(jù)*/ void setIntegerForKey(const char *pKey, int value); /*保存浮點型類型的數(shù)據(jù)*/ void setFloatForKey(const char *pKey, float value); /*保存雙精度浮點型類型的數(shù)據(jù)*/ void setDoubleForKey(const char *pKey, double value); /*保存字符串類型的數(shù)據(jù)*/ void setStringForKey(const char *pKey, const std::string & value);? ? 10.2 看看UserDefault用于讀取數(shù)據(jù)的函數(shù),代碼如下:
/*獲取布爾類型的數(shù)據(jù)*/ bool getBoolForKey(const char *pKey); bool getBoolForKey(const char *pKey, bool defaultValue); /*獲取整形類型的數(shù)據(jù)*/ int getIntegerForKey(const char *pKey); int getIntegerForKey(const char *pKey, int defaultValue); /*獲取浮點型類型的數(shù)據(jù)*/ float getFloatForKey(const char *pKey); float getFloatForKey(const char *pKey, float defaultValue); /*獲取雙精度浮點型類型的數(shù)據(jù)*/ double getDoubleForKey(const char *pKey); double getDoubleForKey(const char *pKey, double defaultValue); /*獲取字符串類型的數(shù)據(jù)*/ std::string getStringForKey(const char *pKey); std::string getStringForKey(const char *pKey, const std::string & defaultValue);? ?10.3 舉例說明SaveData:
bool HelloWorld::init() {if (!Layer::init()) { return false; }/*保存角色名稱*/UserDefault::getInstance()->setStringForKey("ActorName", "mutou");/*讀取角色名稱*/std::string actorName = UserDefault::getInstance()->getStringForKey("ActorName", "none");log("ActorName = %s", actorName.c_str());return true; }? ? ?用調(diào)試模式運行項目,我們看到一下日志輸出:
mutou? ? 在運行了一次項目后,ActorName的數(shù)據(jù)已經(jīng)保存了,我們可以把setStringForKey這句代碼注釋掉,再次運行項目,依舊能夠獲取到ActorName的數(shù)據(jù)。
? 10.4 編寫字符串工具類
? ? StringUtil.h文件:
class StringUtil : public Ref { public:static StringUtil *getInstance();virtual bool init();/*用分隔符分割字符串,結(jié)果存放到一個列表中,列表中的對象為Value*/ValueVector split(const char *srcStr, const char *sSep); private:static StringUtil *m_StringUtil; };? ?StringUtil.cpp文件:
#include "StringUtil.h" StringUtil *StringUtil::m_StringUtil = NULL; StringUtil *StringUtil::getInstance() {if (m_StringUtil == NULL) {m_StringUtil = new StringUtil();if (m_StringUtil && m_StringUtil->init()) {m_StringUtil->autorelease();m_StringUtil->retain();} else {CC_SAFE_DELETE(m_StringUtil);m_StringUtil = NULL;}}return m_StringUtil; }bool StringUtil::init() {return true; } ValueVector StringUtil::split(const char *srcStr, const char *sSep) {ValueVector stringList;int size = strlen(srcStr);/*將數(shù)據(jù)轉(zhuǎn)換為字符串對象*/Value str = Value(srcStr);int startIndex = 0;int endIndex = 0;endIndex = str.asString().find(sSep);std::string lineStr;/*根據(jù)換行符拆分字符串,并添加到列表中*/while (endIndex > 0) {lineStr = str.asString().substr(startIndex, endIndex); /*截取一行字符串*/stringList.push_back(Value(lineStr)); /*添加到列表*/str = Value(str.asString().substr(endIndex + 1, size)); /*截取剩下的字符串*/endIndex = str.asString().find(sSep);}/*剩下的字符串也添加到列表*/if (str.asString().compare("") != 0) {stringList.push_back(Value(str.asString()));}return stringList; }? ?拆分的原理是,從字符串中找到第一個分隔符所在的位置,然后截取該位置之前的字符串,將截取到的字符串保存到ValueVector中。再次從剩余的字符串里執(zhí)行相同的步驟,直到找不到分隔符為止。
? ?看看StringUtil是否正常工作,修改HelloWorldScene的init函數(shù),代碼如下:
bool HelloWorld::init() {if (!Layer::init()) { return false; }/*拆分字符串*/auto strsList = StringUtil::getInstance()->split("Mutou,Xiaoruo,Cocos2d-x,Csv", ",");/*測試輸出結(jié)果*/for (auto value : strsList) {log("value=%s", value.asString().c_str());}return true; }? ? 運行項目,看到以下輸出:
Mutou Xiaoruo Cocos2d-x Csv? ?10.5 loadFile函數(shù)的存放文件數(shù)據(jù)的邏輯:
- ? ? ?有一個Map類型的mCsvMap變量,存放一個CsvData對象和Csv文件名的對應(yīng)關(guān)系:mCsvMap.insert(sPath, csvData);
 - ? ? ?CsvData存放Csv文件每一行的數(shù)據(jù),每一行的數(shù)據(jù)又由一個ValueVector列表保存;
 - ? ? ?ValueVector列表保存的是一個個Value對象(內(nèi)容是字符串),如:ID、Name、Level、HP、MP、HappyValue。
 
? ? ? 反過來,加載了Csv文件之后,讀取文件數(shù)據(jù)的情況就是這樣:
- ? ?根據(jù)文件名sPath從mCsvMap中獲取一個CsvData對象;
 - ? ?CsvData對象保存了Csv文件每一行的數(shù)據(jù);
 - ? ?如果要獲取Csv文件第一行的數(shù)據(jù),則取得CsvData的第一行,取出來的值又是一個ValueVector列表;
 - ? ?ValueVector里保存了第一行的所有數(shù)據(jù),這些數(shù)據(jù)以Value類型保存.
 
? ? 10.6 getValue函數(shù)用于獲取Csv文件的某行某列的數(shù)據(jù),邏輯很簡單,現(xiàn)貨區(qū)某行的數(shù)據(jù),再從該行數(shù)據(jù)獲取某列的值。
Value CsvUtil::getValue(int iRow, int iCol, const char *csvFilePath) {auto csvData = mCsvMap.at(csvFilePath); /*取出Csv文件對象*//*如果配置文件的數(shù)據(jù)不存在,則加載配置文件*/if (csvData == nullptr) {loadFile(csvFilePath);csvData = mCsvMap.at(csvFilePath);}ValueVector rowVector = csvData->getSingleLineData(iRow); /*獲取第iRow行數(shù)據(jù)*/Value colValue = rowVector.at(iCol); /*獲取第iCol列數(shù)據(jù)*/return colValue; }? ? ? ? ?修改HelloWorldScene的init函數(shù),代碼如下:?
bool HelloWorld::init() {if (!Layer::init()) { return false; }const char *sPath = "Monster.csv"; /*測試讀取Csv文件*/CsvUtil::getInstance()->loadFile(sPath); /*加載文件*//*獲取第一個怪物的名字*/Value firstMonsterName = CsvUtil::getInstance()->getValue(2, 1, sPath);/*獲取第二個怪物的HP值*/Value secMonsterHP = CsvUtil::getInstance()->getValue(3, 3, sPath);log("firstMonsterName = %s", firstMonsterName.asString().c_str());log("secMonsterHP = %s", secMonsterHP.asString().c_str());return true; }? ? ? ? 在確定CSV文件的編碼格式為UTF-8之后,輸出如下:
? ? ?firstMonsterName = 笨木頭
? ? ?secMonsterHP = 250
? ? 10.7 CsvUtil剩余的函數(shù)代碼如下:
CsvUtil *CsvUtil::m_CsvUtil = NULL; CsvUtil *CsvUtil::getInstance() {if (m_CsvUtil == NULL) {m_CsvUtil = new CsvUtil();if (m_CsvUtil && m_CsvUtil->init()) {m_CsvUtil->autorelease();m_CsvUtil->retain();} else {CC_SAFE_DELETE(m_CsvUtil);m_CsvUtil = NULL;}}return m_CsvUtil; } bool CsvUtil::init() {return true; } const std::string CsvUtil::get(int iRow, int iCol, const char *csvFilePath) {Value colValue = getValue(iRow, iCol, csvFilePath);return colValue.asString(); }const int CsvUtil::getInt(int iRow, int iCol, const char *csvFilePath) {Value colValue = getValue(iRow, iCol, csvFilePath);return colValue.asInt(); } const float CsvUtil::getFloat(int iRow, int iCol, const char *csvFilePath) {Value colValue = getValue(iRow, iCol, csvFilePath);return colValue.asFloat(); } const bool CsvUtil::getBool(int iRow, int iCol, const char *csvFilePath) {Value colValue = getValue(iRow, iCol, csvFilePath);return colValue.asBool(); }? ? ? 10.8 讀取JSON文件
? ? ? 修改HelloWorldScene的init函數(shù),代碼如下:
? ? ?
bool HelloWorld::init() {if (!Layer::init()) { return false; }readJson();return true; }void HelloWorld::readJson() {Json::Reader reader;Json::Value root;std::string data = FileUtils::getInstance()->getStringFromFile("test1.json");if (reader.parse(data, root, false) == true) {log("id=%d", root["id"].asInt());log("name=%s", root["name"].asCString());log("IQ=%f", root["IQ"].asDouble());} }? ? ? 進(jìn)一步解釋readJson數(shù)據(jù):
- ? ? ? ?Json::Reader,這個類就是用來解析JSON文件的,很重要。
 - ? ? ? ?Json::Value,這個類代表了JSON的一段數(shù)據(jù)。
 - ? ? ? ?調(diào)用FileUtils的getStringFromFile("test1.json"),FileUtils用來加載文件,返回字符串。
 - ? ? ? ?最重要的步驟來了,調(diào)用Reader的parse函數(shù)開始解析JSON文件,解析的結(jié)果會保存到root對象中(Json::Value類? ? ? 型),root對象就作為JSON的根節(jié)點了,所有的數(shù)據(jù)都可以通過root來獲得。
 - ? ? ? ?目前我們的test1.json只有一個數(shù)據(jù)段,所以root就是唯一的節(jié)點, 要讀取它的字段值很簡單,像獲取數(shù)組的值一樣直接調(diào)用root["id"]即可,然后根據(jù)值的類型進(jìn)行轉(zhuǎn)換,如root["id"].asInt()。
 
? ? ? ? ? ? 調(diào)試項目運行,輸出:
%d=1 name=mutou IQ=99.500000? ? ? ? ?10.9 讀取嵌套結(jié)構(gòu)的JSON文件
? ? ? ? ? ? ?test2.json文件內(nèi)容如下:
{"%d":1,"name":"mutou","IQ":99.5,"msg":{ "money":999999, "say":"hehe" } }? ? ? ? ? ? 修改HelloWorldScene的init函數(shù),代碼如下:
bool HelloWorld::init() {if (!Layer::init()) { return false; }readChildJson();return true; }void HelloWorld::readChildJson() {Json::Reader reader;Json::Value root;std::string data = FileUtils::getInstance()->getStringFromFile("test2.json");if (reader.parse(data, root, false) == true) {log("id=%d", root["id"].asInt());log("name=%s", root["name"].asCString());log("IQ=%f", root["IQ"].asDouble());log("msg value money=%d", root["msg"]["money"].asInt());log("msg value say=%s", root["msg"]["say"].asCString());} }? ? ? ?運行輸出:
id=1 name=mutou IQ=99.500000 msg value money=999999 msg value say=hehe? ? ? ? ? 10.10 讀取數(shù)組結(jié)構(gòu)的JSON文件
? ? ? ? ? ? ? test3.json文件:
[{"id":1, "model":"monster1.png", "atk":190},{"id":2, "model":"monster2.png", "atk":10},{"id":3, "model":"monster3.png", "atk":650} ]? ? ? ? ? 繼續(xù)修改HelloWorldScene的init函數(shù),代碼如下:
bool HelloWorld::init() {if (!Layer::init()) { return false; }readArrayJson();return true; }void HelloWorld::readArrayJson() {Json::Reader reader;Json::Value root;std::string data = FileUtils::getInstance()->getStringFromFile("test3.json");if (reader.parse(data, root, false) == true) {int iNum = root.size();for (int i=0; i<iNum; i++) {log("id=%d", root[i]["id"].asInt());log("model=%s", root[i]["model"].asCString());log("atk=%d", root[i]["atk"].asInt());}} }? ? ? ? ? 10.11 輸出JSON文件
? ? ? ? ? ? 繼續(xù)修改HelloWorldScene的init函數(shù),代碼如下:
bool HelloWorld::init() {if (!Layer::init()) { return false; }writeJson();return true; }void HelloWorld::writeJson() {Json::Value root;Json::FastWriter writer;root["name"] = "Who";root["IQ"] = 999;std::string json_file = writer.write(root);FILE *file = fopen("testWrite.json", "w");fprintf(file, json_file.c_str());fclose(file); }? ? ? ? ?解釋如下:
- ? ? 準(zhǔn)備一個Json::Value對象,root;
 - ? ? 往root里存放數(shù)據(jù);
 - ? ? 使用Json::FastWriter的write函數(shù)把root里的數(shù)據(jù)轉(zhuǎn)換為JSON格式的字符串;
 - ? ? 創(chuàng)建一個文件,把JSON格式的字符串寫到文件里。
 
11.有限狀態(tài)機(jī)
? ? ?11.1.實現(xiàn)簡單有限狀態(tài)機(jī)的類
? ? ? ?Mutou類:
enum EnumState {enStateWriteCode, /*狀態(tài):寫代碼*/enStateWriteArticle, /*狀態(tài): 寫教程*/enStateRest, /*狀態(tài):休息*/ };class Mutou : public Node { public:CREATE_FUNC(Mutou);virtual bool init();EnumState enCurState; /*當(dāng)前狀態(tài)*/bool isTire(); /*判斷是否寫代碼寫累了*/bool isWantToWriteArticle(); /*是否想寫教程*/void writeCode(); /*寫代碼*/void writeArticle(); /*寫教程*/void rest(); /*休息*/void changeState(EnumState enState); /*切換狀態(tài)*/virtual void update(float dt); };? ? Mutou.cpp文件:
bool Mutou::init() {this->scheduleUpdate();return true; } void Mutou::changeState(EnumState enState) {this->enCurState = enState; } bool Mutou::isTire() {/*每次問木頭累不累,他都會說:累*/return true; } bool Mutou::isWantToWriteArticle() {/*有10%的概率想寫教程(好懶)*/float ran = CCRANDOM_0_1();if (ran < 0.1f) {return true;}return false; } void Mutou::writeCode() {log("mutou is writing Code."); } void Mutou::writeArticle() {log("mutou is writing article"); } void Mutou::rest() {log("mutou is resting."); } void Mutou::update(float dt) {/*判斷在每一種狀態(tài)下應(yīng)該做什么事情*/switch(enCurState) {case enStateWriteCode:/*如果累了就休息,并且切換到修改狀態(tài)*/if (isTrue()) {rest();changeState(enStateRest);}break;case enStateWriteArticle:/*如果累了就休息,并且切換到休息狀態(tài)*/if (isTire()) {rest();changeState(enStateRest);}break;case enStateWriteArticle:/*如果累了就休息,并且切換到休息狀態(tài)*/if (isTire()) {rest();changeState(enStateRest);}break;case enStateRest:/*一定的概率寫代碼,一定的概率寫教程,并且切換到相應(yīng)的狀態(tài)*/if (isWantToWriteArticle()) {writeArticle();changeState(enStateWriteArticle);} else {writeCode();changeState(enStateWriteCode);}break;} }? ? ? ? 然后修改HelloWorldScene的init函數(shù),代碼如下:
/*一定不要忘了包含頭文件*/ #include "Mutou.h" bool HelloWorld::init() {if (!Layer::init()) { return false; }/* 新建木頭角色*/Mutou *mMutou = Mutou::create();/*初始化木頭的狀態(tài)為休息*/mMutou->changeState(enStateRest);this->addChild(mMutou);return true; }? ? ? ?運行程序看到以下輸出:
? ? ? mutou is writing Code.
? ? ? mutou is resting.
? ? ? mutou is writing Code.
? ? ? mutou is resting.
? ? ? mutou is writing article.
? ? ? mutou is resting.
? ? ? mutou is writing Code.
? ? ? mutou is resting.
? ? ? mutou is writing Code.
? ? ? 11.2 用狀態(tài)模式實現(xiàn)有限狀態(tài)機(jī)
? ? ? ? ?Mutou類:
void Mutou::update(float dt) {/*判斷在每一種狀態(tài)下應(yīng)該做什么事情*/switch(enCurState) {case enStateWriteCode:/*如果累了就休息,并且切換到休息狀態(tài)*/case enStateWriteArticle:/*如果累了就休息,并且切換到休息狀態(tài)*/case enStateRest:/*一定的概率寫代碼,一定的概率寫教程,并且切換到相應(yīng)的狀態(tài)*/} }? ? ? ?update函數(shù)會先判斷木頭當(dāng)前所在的狀態(tài),然后再去執(zhí)行相對應(yīng)的邏輯。
? ? ? ? 但是,要是Mutou類有好多狀態(tài),那這個函數(shù)也太龐大了!
? ? ? ? ? ? (1)??狀態(tài)基類
? ? ? ? ? ? ?首先,我們需要一個狀態(tài)基類,它只有一個抽象方法execute,表示要執(zhí)行一件事情,至于執(zhí)行什么事情,由它的子類來決定。
? ? ? ? ? ? ? State類:
class MutouT; class State { public:virtual void execute(MutouT *mutou) = 0; };? ? ? ? ? ? ? ?State類作為接口類使用,execute函數(shù)是某個狀態(tài)下要執(zhí)行的行為,MutouT類將在后面解釋.
? ? ? ? ? ? (2)? 狀態(tài)類
? ? ? ? ? ? ? StateWriteCode.h文件:
Class MutouT; class StateWriteCode : public State { public:virtual void execute(Mutou *mutou); };? ? ? ? ? ? ? ?StateWriteCode.cpp文件:
void StateWriteCode::execute(MutouT *mutou) {/*如果累了就休息,并且切換到休息狀態(tài)*/if (mutou->isTire()) {mutou->rest();mutou->changeState(new StateRest());} }? ? ? ? ? ? ? ?StateWriteArticle.h文件:
class StateWriteArticle : public State { public:virtual void execute(MutouT *mutou); };? ? ? ? ? ? ? ? StateWriteArticle.cpp文件:
void StateWriteArticle::execute(MutouT *mutou) {/*如果累了就休息,并切換到休息狀態(tài)*/if (mutou->isTire()) {mutou->rest();mutou->changeState(new StateRest());} }? ? ? ? ? ? ? ? StateRest類:
? ? ? ? ? ? ? ? ? StateRest.h文件:
class StateRest : public State { public:virtual void execute(MutouT *mutou); };? ? ? ? ? ? ? ? ? ?StateRest.cpp文件:
void StateRest::execute(MutouT *mutou) {/*一定的概率寫代碼,一定的概率寫教程,并且切換到相應(yīng)的狀態(tài)*/if (mutou->isWantToWriteArticle()) {mutou->writeArticle();mutou->changeState(new StateWriteArticle());} else {mutou->writeCode();mutou->changeState(new StateWriteCode());} }? ? ? ? ? ?(3) 新的木頭類
? ? ? ? ? ? ? ?MutouT.h文件:
class MutouT : public Node { public:CREATE_FUNC(MutouT);virtual bool init();bool isTire(); /*判斷是否寫代碼累了*/bool isWantToWriteArticle(); /*是否想寫教程*/void writeCode(); /*寫代碼*/void writeArticle(); /*寫教程*/void rest(); /*休息*/void changeState(State *state); /*切換狀態(tài)*/virtual void update(float dt); private:State *mCurState; /*存放當(dāng)前狀態(tài)類*/ };? ? ? ? ? ?MutouT.cpp文件:
bool MutouT::init() {mCurState = NULL;this->scheduleUpdate();return true; } bool MutouT::isTire() {/*每次問木頭累不累,他都會說:累*/return true; } bool MutouT::isWantToWriteArticle() {/*有10%的概率想寫教程(好懶)*/float ran = CCRANDOM_0_1();if (ran < 0.1f) {return true;}return false; } void MutouT::writeCode() {log("mutou is writing Code."); } void MutouT::writeArticle() {log("mutou is writing article."); } void MutouT::rest() {log("mutou is resting."); } void MutouT::changeState(State *state) {CC_SAFE_DELETE(mCurState);mCurState = state; } void MutouT::update(float dt) {mCurState->execute(this); }? ? ? ? ? 修改HelloWorld的init函數(shù),代碼如下:
bool HelloWorld::init() {if (!Layer::init()) { return false; }/*新建木頭2角色*/MutouT *mMutou = MutouT::create();/*初始化木頭的狀態(tài)為休息*/mMutou->changeState(new StateRest());this->addChild(mMutou);return true; }? ? ? ? ? ?運行后輸出:
? ? ? ? ? ? mutou is writing Code.
? ? ? ? ? ? mutou is resting.
? ? ? ? ? ? mutou is writing article.? ?
? ? ? ? ? ? mutou is resting.
? ? ? ? ? ? mutou is writing article.
? ? ? ? ? ??mutou is resting.
? ? ? ? ? ??mutou is writing Code.? ? ?
? ? ? ? ? ? mutou is resting.
? ? ? ? ? ??mutou is writing Code.?
? ? ? ? ? ? mutou is resting.
? ? ? ? ? ??mutou is writing Code.
? ? ? ? ? ? ? ? ? 這就是簡單地利用了多態(tài),神奇的地方就在execute這個函數(shù),這個函數(shù)會根據(jù)不同的情況切換MutouT的當(dāng)前狀態(tài)。
? ? ? ? 11.3 真正的狀態(tài)機(jī)來了
? ? ? ? ? ? ?(1) 創(chuàng)建狀態(tài)機(jī)類
? ? ? ? ? ? ? ? ?MutouTFSM.h文件:
class MutouTFSM : public Node { public:static MutouTFSM *createWithMutouT(MutouT *mutou);bool initWithMutouT(MutouT *mutou);virtual void update(float dt);void changeState(I_State *state); /*切換狀態(tài)*/ private:I_State *mCurState; /*存放當(dāng)前狀態(tài)類*/MutouT *mMutou; /*木頭對象*/ };? ? ? ? ? ? ? ?現(xiàn)在,所有和狀態(tài)有關(guān)的操作都放在MutouTFSM狀態(tài)機(jī)類,MutouT類只要專心地做它該做的情況就好了(休息、寫代碼、寫教程)。
? ? ? ? ? ? ? ?MutouTFSM.cpp文件:
MutouTFSM *MutouTFSM::createWithMutouT(MutouT *mutou) {MutouTFSM *fsm = new MutouTFSM();if (fsm && fsm->initWithMutouT(mutou)) {fsm->autorelease();} else {CC_SAFE_DELETE(fsm);fsm = NULL;}return fsm; } bool MutouTFSM::initWithMutouT(MutouT *mutou) {this->mCurState = NULL;this->mMutou = mutou;return true; } void MutouTFSM::changeState(I_State *state) {CC_SAFE_DELETE(mCurState);this->mCurState = state; } void MutouTFSM::update(float dt) {this->mCurState->execute(mMutou); }? ? ? ? ? ?(2) 被釋放的木頭類
? ? ? ? ? ? ? 有了MutouTFSM類之后,MutouT類就能夠得到釋放了,來看看新的MutouT類代碼:
class MutouT : public Node { public:CREATE_FUNC(MutouT);virtual bool init();bool isTire(); /*判斷是否寫代碼寫累了*/bool isWantToWriteArticle(); /*是否想寫教程*/void writeCode(); /*寫代碼*/void writeArticle(); /*寫教程*/void rest(); /*休息*/MutouTFSM *getFSM(); /*獲取狀態(tài)機(jī)對象*/virtual void update(float dt); private:MutouTFSM *mFSM; /*木頭狀態(tài)機(jī)*/ };? ? ? ? ? ? ? ?再來看看MutouT的實現(xiàn),代碼如下:
bool MutouT::init() {mFSM = MutouTFSM::createWithMutouT(this);mFSM->retain(); this->scheduleUpdate();return true; } bool MutouT::isTrue() {/*每次問木頭累不累,他都會說:累*/return true; } bool MutouT::isWantToWriteArticle() {float ran = CCRANDOM_0_1();if (ran < 0.1f) {return true;}return false; } void MutouT::writeCode() {log("mutou is writing Code."); } void MutouT::writeArticle() {log("mutou is writing article."); } void MutouT::rest() {log("mutou is resting."); } MutouTFSM *MutouT::getFSM() {return this->mFSM; } void MutouT::update(float dt) {this->mFSM->update(dt); }? ? ? ? ? ? ? 唯一變化的是,原本通過調(diào)用MutouT類的changeState函數(shù)來切換狀態(tài),而現(xiàn)在是通過調(diào)用MutouTFSM類的changeState函數(shù)來切換狀態(tài),代碼如下:
void StateWriteArticle::execute(MutouT *mutou) {/*如果累了就休息,并且切換到休息狀態(tài)*/if (mutou->isTire()) {mutou->rest();mutou->getFSM()->changeState(new StateRest());} } void StateWriteCode::execute(MutouT *mutou) {/*如果累了就休息,并且切換到休息狀態(tài)*/if (mutou->isTire()) {mutou->rest();mutou->getFSM()->changeState(new StateRest());} } void StateRest::execute(MutouT *mutou) {/*一定的概率寫代碼,一定的概率寫教程,并且切換到相應(yīng)的狀態(tài)*/if (mutou->isWantToWriteArticle()) {mutou->writeArticle();mutou->getFSM()->changeState(new StateWriteArticle());} else {mutou->writeCode();mutou->getFSM()->changeState(new StateWriteCode());} }? ? ? ? ? 最后,再次修改HelloWorldScene的init函數(shù),代碼如下:
bool HelloWorld::init() {if (!Layer::init()) { return false; }/*新建木頭2角色*/MutouT *mMutou = MutouT::create();/*初始化木頭的狀態(tài)為休息*/mMutou->getFSM()->changeState(new StateRest());this->addChild(mMutou);return true; }? ? ? ? ?運行并輸出:
? ? ? ?mutou is writing article.
? ? ? ?mutou is resting.
? ? ? ?mutou is writing Code.
? ? ? ?mutou is resting.
? ? ? ?mutou is writing Code.
? ? ? ?mutou is resting.
? ? ? ?mutou is writing Code.
? ? ? ? ?11.4 事件驅(qū)動
? ? ? ? ? ? ?(1) 徹底拋棄update函數(shù)--新的狀態(tài)機(jī)類
? ? ? ? ? ? ? ? ?MutouTFSM類
? ? ? ? ? ? ? ? ? MutouTFSM.h文件:
class MutouTFSM : public Node { public:~MutouTFSM();static MutouTFSM *createWithMutouT(MutouT *mutou);bool initWithMutouT(MutouT *mutou);void changeState(State *state); /*切換狀態(tài)*/ private:void onRecvWantToRest(Ref *obj);void onRecvWantToWriteCode(Ref *obj);void onRecvWantToWriteArticle(Ref *obj);I_State *mCurState; /*存放當(dāng)前狀態(tài)類*/MutouT *mMutou; /*木頭對象*/ };? ? ? ? ? ? ? ? ?MutouTFSM.cpp文件:
#define NOTIFY NotificationCenter::getInstance() MutouTFSM::~MutouTFSM() {NOTIFY->removeAllObservers(this); }MutouTFSM *MutouTFSM::createWithMutouT(MutouT *mutou) {MutouTFSM *fsm = new MutouTFSM();if (fsm && fsm->initWithMutouT(mutou)) {fsm->autorelease();} else {CC_SAFE_DELETE(fsm);fsm = NULL;}return fsm; }bool MutouTFSM::initWithMutouT(MutouT *mutou) {this->mCurState = NULL;this->mMutou = mutou;/*訂閱消息*/NOTIFY->addObserver(this, callfuncO_selector(MutouTFSM::onRecvWantToRest),StringUtils::toString(en_Msg_WantToRest), NULL);NOTIFY->addObserver(this, callfuncO_selector(MutouTFSM::onRecvWantToWriteCode),StringUtils::toString(en_Msg_WantToWriteCode), NULL);NOTIFY->addObserver(this, callfuncO_selector(MutouTFSM::onRecvWantToWriteArticle),StringUtils::toString(en_Msg_WantToWriteArticle), NULL);return true; }void MutouTFSM::changeState(State *state) {CC_SAFE_DELETE(mCurState);this->mCurState = state; }void MutouTFSM::onRecvWantToRest(Ref *obj) {/*將當(dāng)前事件傳遞給具體的狀態(tài)類*/this->mCurState->execute(mMutou, en_Msg_WantToRest); }void MutouTFSM::onRecvWantToWriteCode(Ref *obj) {/*將當(dāng)前事件傳遞給具體的狀態(tài)類*/this->mCurState->execute(mMutou, en_Msg_WantToWriteCode); }void MutouTFSM::onRecvWantToWriteArticle(Ref *obj) {/*將當(dāng)前事件傳遞給具體的狀態(tài)類*/this->mCurState->execute(mMutou, en_Msg_WantToWriteArticle); }? ? ? ? ? ? ? ?其中,宏定義:
? ? ? ? ? ? ? ? ? #define NOTIFY NotificationCenter::getInstance()? ?
? ? ? ? ? ? 在收到消息后,狀態(tài)機(jī)是怎么處理的。和以前一樣,調(diào)用當(dāng)前狀態(tài)的execute方法,但這次多了一個參數(shù),那就是剛剛提到的消息類型,我們需要新建一個枚舉類,EnumMsgType,代碼如下:
#ifndef _EnumMsgType_H_ #define _EnumMsgType_H_ enum EnumMsgType {en_Msg_WantToRest, /*需要休息*/en_Msg_WantToWriteCode, /*需要寫代碼*/en_Msg_WantToWriteArticle, /*需要寫教程*/ }; #endif? ? ? ? ? ? ? ? ?(2) 更智能的狀態(tài)類
? ? ? ? ? ? ? ? ? ?首先要修改的是狀態(tài)類的基類,代碼如下:
#include "EnumMsgType.h" class MutouT; class State { public:virtual void execute(MutouT *mutou, EnumMsgType enMsgType) = 0; };? ? ? ? ? ? ? ? ? ?看一下3種狀態(tài)類的execute函數(shù)的新實現(xiàn),代碼如下:
void StateRest::execute(MutouT *mutou, EnumMsgType enMsgType) {switch(enMsgType) {case en_Msg_WantToWriteCode:mutou->writeCode();mutou->getFSM()->changeState(new StateWriteCode());break;case en_Msg_WantToWriteArticle:mutou->writeArticle();mutou->getFSM()->changeState(new StateWriteArticle());break;} } void StateWriteArticle::execute(MutoT *mutou, EnumMsgType enMsgType) {switch(enMsgType) {case en_Msg_WantToRest:mutou->rest(); mutou->getFSM()->changeState(new StateRest());break;} }void StateWriteCode::execute(MutouT *mutou, EnumMsgType enMsgType) {switch(enMsgType) {case en_Msg_WantToRest:mutou->rest();mutou->getFSM()->changeState(new StateRest());break;} }void StateWriteCode::execute(MutouT *mutou, EnumMsgType enMsgType) {switch(enMsgType) {case en_Msg_WantToRest:mutou->rest();mutou->getFSM()->changeState(new StateRest());break;} }? ? ? ? ? ?每個狀態(tài)類的execute函數(shù)已經(jīng)不需要再主動去判斷木頭當(dāng)前在做什么或者想做什么或者是什么狀況。它只要知道當(dāng)前發(fā)生了什么事件,只關(guān)心它所關(guān)心的事件,在特定的事件下改變木頭的狀態(tài)即可。
? ? ? ? ?測試一下,修改HelloWorld的init函數(shù),代碼如下:
bool HelloWorld::init() {if (!Layer::init()) { return false; }/*新建木頭2角色*/MutouT *mMutou = MutouT::create();/*初始化木頭的狀態(tài)為休息*/mMutou->getFSM()->changeState(new StateRest());this->addChild(mMutou);/*模擬事件的發(fā)生*/auto notify = NotificationCenter::getInstance();notify->posNotification(StringUtils::toString(en_Msg_WantToWriteCode));notify->posNotification(StringUtils::toString(en_Msg_WantToRest));notify->posNotification(StringUtils::toString(en_Msg_WantToWriteArticle));notify->posNotification(StringUtils::toString(en_Msg_WantToRest));notify->posNotification(StringUtils::toString(en_Msg_WantToWriteArticle));notify->posNotification(StringUtils::toString(en_Msg_WantToRest));notify->posNotification(StringUtils::toString(en_Msg_WantToWriteCode));notify->posNotification(StringUtils::toString(en_Msg_WantToRest));return true; }? ? ? ? ? ? ?運行并輸出:
mutou is writing Code. mutou is resting. mutou is writing article. mutou is resting. mutou is writing article. mutou is resting. mutou is writing Code. mutou is resting.? ? ? ? ? ? ? ?描述一個場景:
? ? ? ? ? 一個擁有超能力的木頭,它所經(jīng)過的地方周圍的物體都會彈開,于是,它一邊走,周圍的物體一邊彈開。十分美妙的場景。
? ? ? ? ? ? ? 這依舊可以使用有限狀態(tài)機(jī)來實現(xiàn),木頭經(jīng)過的時候可以發(fā)出"我來了,我的能力范圍是方圓10m"的消息,然后周圍的物體訂閱了這個消息,在接收到這個消息時,就由靜止?fàn)顟B(tài)改變?yōu)閺椛錉顟B(tài),然后物體就執(zhí)行彈射動作。
12.強(qiáng)大的Lua
? ? ?C++和Lua的通信流程,如圖所示:
? ? ?
? ? ? (1) C++想獲取Lua的myName字符串的值,所以它把myName放到Lua堆棧(棧頂),以便Lua能看到。
? ? ? (2) Lua從堆棧(棧頂)中獲取myName,此時棧頂再次變?yōu)榭铡?/p>
? ? ? (3) Lua拿著這個myName去Lua全局表查找myName對應(yīng)的字符串。
? ? ? (4) 全局表返回一個字符串"beauty girl".
? ? ? (5) Lua把取得的"beauty girl"字符串當(dāng)?shù)蓝褩?棧頂).
? ? ? (6) C++可以從Lua堆棧中取得"beauty girl"。
? ? 12.1 開始使用
? ? ? ? ?HelloLua.h文件:
extern "C" { #include <lua.h> #include <lualib.h> #include <lauxlib.h> };class HelloLua : public Layer { public:CREATE_FUNC(HelloLua);virtual bool init();static Scene *createScene(); };? ? ? ?HelloLua.cpp文件:
#include "HelloLua.h" Scene *HelloLua::createScene() {auto scene = Scene::create();auto layer = HelloLua::create();scene->addChild(layer);return scene; } bool HelloLua::init() {lua_State *pL = lua_open();luaopen_base(pL);luaopen_math(pL);luaopen_string(pL);/*1.執(zhí)行Lua腳本,返回0代表成功*/int err = luaL_dofile(pL, "helloLua.lua");log("open : %d", err);/*2.重置棧頂索引*/lua_settop(pL, 0); //為了確認(rèn)讓棧頂?shù)乃饕脼?,因為我們操作棧時是根據(jù)所以你來操作的。lua_getglobal(pL, "myName"); //把myName放到了棧中,然后Lua就會通過myName去全局表中查找,找到myName對應(yīng)的字符串"beauty girl", 再放到棧中。/*3.判斷棧頂?shù)闹档念愋褪欠駷镾tring,返回非0值代表成功*/int isstr = lua_isstring(pL, 1);log("isstr = %d", isstr);/*4.獲取棧頂?shù)闹?/if (isstr != 0) {const char *str = lua_tostring(pL, 1);log("getStr = %s", str);}lua_close(pL);return true; }? ?運行輸出:
? ? ? open : 0
? ? ? isstr = 1
? ? ? getStr = beauty girl
? ? ? 12.2 GetTableData.
? ? ? ? ? 創(chuàng)建HelloLua類,代碼如下:
bool HelloLua::init() {/*初始化*/lua_State *pL = lua_open();luaopen_base(pL);/*執(zhí)行腳本*/luaL_dofile(pL, "helloLua.lua");/*重置棧頂元素*/lua_settop(pL, 0);/*取得table變量,在棧頂*/lua_getglobal(pL, "helloTable");/*將C++的字符串放到Lua的棧中,此時棧頂變?yōu)?#34;name",helloTable對象變?yōu)闂5?/lua_pushstring(pL, "name");/*從table對象尋找"name"對應(yīng)的值(table對象現(xiàn)在在索引為-2的棧中,也就是當(dāng)前的棧底)取得對應(yīng)值之后,將值放回棧頂*/lua_gettable(pL, -2);/*現(xiàn)在表的name對應(yīng)的值已經(jīng)在棧頂了,直接取出即可*/const char *sName = lua_tostring(pL, -1);log("name = %s", sName);lua_close(pL);return true; }? ? ? ? ? ?運行并輸出:
? ? name = mutou
? ? ? ?12.3 C++調(diào)用Lua函數(shù)
? ? ? ? ? ?helloLua.lua文件
myName = "beauty girl" helloTable = {name = "mutou", IQ = 125}function helloAdd(num1, num2) return (num1 + num2) end? ? ? ? ? ?HelloLua類:
bool HelloLua::init() {lua_state *pL = lua_open();luaopen_base(pL);/*執(zhí)行腳本*/luaL_dofile(pL, "hellolua.lua");/*重置棧頂元素*/lua_settop(pL, 0);/*把helloAdd函數(shù)對象放到棧中*/lua_getglobal(pL, "helloAdd");/*把函數(shù)所需要的參數(shù)入棧*/lua_pushnumber(pL, 10);lua_pushnumber(pL, 5);/*執(zhí)行函數(shù),第一個參數(shù)表示函數(shù)的參數(shù)個數(shù),第二個參數(shù)表示函數(shù)返回值個數(shù)*Lua會先去堆棧取出參數(shù),然后再取出函數(shù)對象,開始執(zhí)行哈數(shù)*/lua_call(pL, 2, 1);int iResult = lua_tonumber(pL, -1);log("iResult = %d", iResult);return true; }? ? ? ? ? ? 簡述一下步驟:
? ? ? ? ? ? ? (1) 執(zhí)行腳本。
? ? ? ? ? ? ? (2) 將helloAdd函數(shù)放到棧中:lua_getglobal(pL, "helloAdd").
? ? ? ? ? ? ? (3) helloAdd有2個參數(shù),我們要把參數(shù)傳遞給Lua,所以2個參數(shù)都要放到棧里。
? ? ? ? ? ? ? (4) 第2和第3步已經(jīng)把函數(shù)所需要的數(shù)據(jù)都放到棧里了,接下來只要告訴Lua去棧里取數(shù)據(jù),然后執(zhí)行函數(shù),調(diào)用lua_call即可。
? ? ? ? ? ?運行并輸出:
? ? ?iResult = 15
? ? ? ? ?12.4 Lua調(diào)用C++的函數(shù)
? ? ? ? ? ? ? HelloLua.h文件:
class HelloWorld : public Layer { public:CREATE_FUNC(HelloLua);virtual bool init();static Scene *createScene();static int getNumber(int num); };--------------------- int HelloLua::getNumber(int num) {log("getNumber num = %d", num);return num + 1; }? ? ? ? ? ? 在HelloLua.h中添加聲明:
? ? ? ? static int cpp_GetNumber(lua_State *pL);
? ? ? ? ? ? ?HelloLua.cpp文件:
int HelloLua::cpp_GetNumber(lua_State *pL) {/*從棧頂中取一個值*/int num = (int)lua_tonumber(pL, 1);/*調(diào)用getNumber函數(shù),將返回值入棧*/lua_pushnumber(pL, getNumber(num));/*返回值個數(shù),getNumber只有一個返回值,所以返回1*/return 1; }? ? ? ? ? ? ? Lua和C++只能通過堆棧通信,Lua是不可能直接調(diào)用getNumber函數(shù)的,所以我們建立一個cpp_GetNumber函數(shù)作為中介。cpp_GetNumber函數(shù)有一個lua_State *pL參數(shù),有了這個參數(shù),C++就能從Lua的堆棧中取值了。
? ? ? ? ? ? ? ?(1) Lua腳本里會調(diào)用cpp_GetNumber函數(shù)。
? ? ? ? ? ? ? ?(2) 當(dāng)cpp_GetNumber被調(diào)用時,一切又回到C++對Lua的操作,棧頂里會存放函數(shù)所需要的參數(shù),取出來用就可以。
? ? ? ? ? ? ? ?(3) Lua調(diào)用cpp_GetNumber之后,需要一個結(jié)果,當(dāng)然,這個結(jié)果同樣只能存放在棧里,所以理所當(dāng)然地要把getNumber的結(jié)果入棧。
? ? ? ? ? ? ? ?(4) cpp_GetNumber返回了一個值,這個值不是函數(shù)的執(zhí)行結(jié)果,而是getNumber需要返回值的個數(shù)(Lua支持多個返回值的函數(shù))。
? ? ? ? ? ? ? ?(5) Lua會從棧中取出函數(shù)的執(zhí)行結(jié)果。
? ? ? ? ? ? 現(xiàn)在,開始使用這兩個函數(shù),修改HelloLua的init函數(shù),代碼如下:
bool HelloLua::init() {lua_State *pL = lua_open();luaopen_base(pL);/*C++的函數(shù)和封裝函數(shù)都必須是靜態(tài)的,不知道可不可以不是靜態(tài)的?當(dāng)然不可以*/lua_register(pL, "cpp_GetNumber", cpp_GetNumber);luaL_dofile(pL, "helloLua.lua");lua_close(pL);return true; }? ? ? ? ? ? 最后還要修改helloLua.lua腳本文件:
? ? ? ?local num = cpp_GetNumber(10);
? ? ? ? ? ? 運行輸出:
? ? ? ?getNumber num = 10
? ? ? ? ? Lua可以通過lua_register將C++的靜態(tài)函數(shù)注冊到Lua中,這樣Lua就可以直接調(diào)用C++的函數(shù)。
? ? ? ? ? 為什么cpp_GetNumber函數(shù)為什么非得是靜態(tài)的,很簡單,如果不是靜態(tài)函數(shù),就必須在對象被創(chuàng)建之后才能調(diào)用。在Lua中是不能也不會去再次創(chuàng)建一個HelloLua對象的,因此,注冊的函數(shù)必須是靜態(tài)的。
13.自己寫UI模塊
? ? 13.1 UI模塊設(shè)計思路圖
? ? ? ? ? ?
? ? 13.2 UI的XML配置文件
? ? ? ?看看規(guī)定的XML配置文件的格式,代碼如下:
<?xml version="1.0" encoding="utf-8"?> <MMWinRoot><MMWin><!-- 窗口 --><enWinType>MMNormalWin</enWinType><bg>tollgate/msgBg.jpg</bg><x>0</x><y>450</y><MMCld><!-- 文字標(biāo)簽 --><enWinType>MMLabel</enWinType><text>Hello</text><fontSize>25</fontSize><x>7</x><y>40</y></MMCld></MMWin> </MMWinRoot>? ? ? ?我們定義三種主要的標(biāo)簽:
- ? ? ? ? MMWinRoot: 根節(jié)點,每個XML配置文件只有一對根節(jié)點;
 - ? ? ? ? MMWin: 代表一個父窗口,通常一個XML配置文件只有一個父窗口,因為我們一般會把各種UI窗口分開寫到不同XML文件里,方便管理;
 - ? ? ? ? MMCld: 代表一個子窗口,子窗口同樣也可以包含子窗口。
 - ? ? ? ? enWinType:這是一個很重要的標(biāo)簽,它代表控件的類型,比如窗口、按鈕、標(biāo)簽等類型;
 - ? ? ? ? bg:? 代表控件的背景圖片路徑,通常用在窗口和按鈕控件上;
 - ? ? ? ? x:? ?代表控件的X坐標(biāo);
 - ? ? ? ? y:? ?代表控件的Y坐標(biāo)。
 - ? ? ? ? text:? 文本內(nèi)容,通常只用在文字標(biāo)簽控件;
 - ? ? ? ? fontSize:? 字體大小,同樣只用在文字標(biāo)簽控件。
 
? ? ? 13.3 我們來讀一個XML文件,代碼如下:
<?xml version="1.0" encoding="utf-8"?> <MMWinRoot><MMWin id=10><enWinType>MMNormalWin</enWinType></MMWin> </MMWinRoot>? ? ? ? ? ?修改HelloWorldScene類,代碼如下:
class TiXmlElement; class HelloWorld : public cocos2d::CCLayer { public:virtual bool init();static cocos2d::CCScene *scene();CREATE_FUNC(HelloWorld); private:void loadXmlTest1(const char *sPath); //XML讀取測試1,Hello TinyXMLvoid loadXmlEle(TiXmlElement *rootElement); //讀取普通的XML配置文件 };? ? ? ? ? ?新增了兩個函數(shù),代碼如下:
#include "tinyxml\tinyxml.h"void HelloWorld::loadXmlTest1(const char *sPath) {TiXmlDocument *xmlDoc = new TiXmlDocument();/*讀取XML文件*/Data fileData = FileUtils::getInstance()->getDataFromFile(sPath);/*開始解析XML*/xmlDoc->Parse((const char *)fileData.getBytes());/*獲取XML根節(jié)點*/TiXmlElement *rootElement = xmlDoc->RootElement();/*開始讀取XML各個標(biāo)簽*/loadXmlEle(rootElement);/*刪除對象*/delete xmlDoc; }? ? ? ? 解釋以下loadXmlTest1函數(shù)的流程:
? ? ? ?(1) 創(chuàng)建一個TiXmlDocument對象,命名為xmlDoc,這個對象可以理解為XML文件的對象或者管理器。
? ? ? ?(2) 使用FileUtils的getDataFromFile函數(shù)獲取XML文件的數(shù)據(jù),返回Data對象,命名為fileData。
? ? ? ?(3) 調(diào)用TiXmlDocument對象的Parse函數(shù)解析XML文件數(shù)據(jù),經(jīng)過解析之后,XML文件的節(jié)點就被保存為一個個對象,也就是我們常說的節(jié)點。
? ? ? ?(4) 節(jié)點使用TiXmlDocument對象保存,為了開始讀取所有的節(jié)點對象,首先要獲取根節(jié)點對象,通過TiXmlDocument的RootElement函數(shù)可以獲取根節(jié)點對象。
? ? ? ?(5) 有了根節(jié)點對象,就可以開始讀取所有的節(jié)點了。
? ? ? ?(6) 由于xmlDoc是"new"出來的對象,并且沒有參與Cocos2d-x的內(nèi)存管理機(jī)制,因此,在使用完之后,要調(diào)用delete釋放對象。
? ? ? ? 我們來看看loadXmlEle函數(shù)的實現(xiàn),代碼如下:
void HelloWorld::loadXmlEle(TiXmlElement *rootElement) {/*取得根節(jié)點的第一個字標(biāo)簽對象*/TiXmlElement *cldElement = rootElement->FirstChildElement();/*打印標(biāo)簽的名字和標(biāo)簽的id屬性*/log("%s id=%s", cldElement->Value(), cldElement->Attribute("id"));/*再取得標(biāo)簽的第一個子對象*/cldElement = cldElement->FirstChildElement();/*打印標(biāo)簽的名字和標(biāo)簽的值*/log("%s:%s", cldElement->Value(), cldElement->GetText()); }? ? ? ? 修改HelloWorldScene的init函數(shù),代碼如下:
bool HelloWorld::init() {if (!Layer::init()) {return false;}loadXmlTest1("test1.xml");return true; }? ? ? ? 運行后輸出:
? ? ? ?MMWin id=10
? ? ? ?enWinType:MMNormalWin
? ? ? 13.4 看下面的XML配置文件:
<?xml version="1.0" encoding="utf-8"?> <MMWinRoot><MMWin><!-- 窗口 --><enWinType>MMNormalWin</enWinType><bg>tollgate/msgBg.jpg</bg><x>0</x><y>450</y><MMCld><!-- 區(qū)域1 --><enWinType>MMDiv</enWinType><x>7</x><y>40</y><MMCld><!-- 標(biāo)簽1 --><enWinType>MMLabel</enWinType><text>label1</text><fontSize>25</fontSize></MMCld><MMCld><!-- 標(biāo)簽2 --><enWinType>MMLabel</enWinType><text>label2</text><fontSize>25</fontSize></MMCld><MMCld><!-- 標(biāo)簽2 --><enWinType>MMLabel</enWinType><text>label2</text></MMCld></MMCld></MMWin> </MMWinRoot>? ? ? ? ? 給HelloWorldScene新增兩個函數(shù),代碼如下:
class HelloWorld : public cocos2d::CCLayer {/*這里省略了很多代碼*/void loadXmlTest2(const char *sPath); //XML讀取測試2,讀取MM控件的XMLvoid loadXmlEleMMWin(TiXmlElement *rootElement); //讀取MM控件的XML配置文件 };? ? ? ? ? loadXmlTest2函數(shù):
void HelloWorld::loadXmlTest2(const char *sPath) {TiXmlDocument *xmlDoc = new TiXmlDocument();Data fileData = FileUtils::getInstance()->getDataFromFile(sPath); /*讀取XML文件*/xmlDoc->Parse((const char *)fileData.getBytes()); /*開始解析XML*/TiXmlElement *rootElement = xmlDoc->RootElement(); /*獲取XML根節(jié)點*//*開始讀取XML各個標(biāo)簽*/loadXmlEleMMWin(rootElement);delete xmlDoc; /*刪除對象*/ }------------------------- void HelloWorld::loadXmlEleMMWin(TiXmlElement *rootElement) {TiXmlElement *cldElement = rootElement->FirstChildElement();while(cldElement != NULL) {/*某些節(jié)點的內(nèi)容為空,所以不獲取它的內(nèi)容(但是內(nèi)容有子節(jié)點)*/if (cldElement->GetText() != NULL) {log("%s:%s", cldElement->Value(), cldElement->GetText());}/*如果有子節(jié)點,則繼續(xù)解析,并且添加到當(dāng)前節(jié)點的子節(jié)點列表*/else if (cldElement->FirstChildElement() != NULL) {loadXmlEleMMWin(cldElement);}/*下一個同級節(jié)點*/cldElement = cldElement->NextSiblingElement();} }? ? ? ? ? ? ?修改HelloWorldScene的init函數(shù),代碼如下:
bool HelloWorld::init() {if (!Layer::init()) {return false;}loadXmlTest2("test2.xml");return true; }? ? ? ? ? ? ?運行并輸出:
? ? ? ? ?enWinType:MMNormalWin
? ? ? ? ?bg:tollgate/msgBg.jpg
? ? ? ? ?x:0
? ? ? ? ?y:450
? ? ? ? ?enWinType:MMDiv
? ? ? ? ?x:7
? ? ? ? ?y:40
? ? ? ? ?enWinType:MMLabel
? ? ? ? ?text:label1
? ? ? ? ?fontSize:25
? ? ? ? ?enWinType:MMLabel
? ? ? ? ?text:label2
? ? ? ? ?fontSize:25
? ? ? ?13.5 XML標(biāo)簽節(jié)點對象
? ? ? ? ? ?MMWinXmlData的頭文件,代碼如下:
class MMWinXmlData : public Ref { public:CREATE_FUNC(MMWinXmlData);virtual bool init();/*添加子節(jié)點*/void addCldXmlData(MMWinXmlData *cldXmlData);/*獲取子節(jié)點列表<MMWinXmlData*> */Vector<MMWinXmlData *> getCldXmlDataList();bool isHasChild(); /*是否有子節(jié)點*/ private:Vector<MMWinXmlData *> mCldXmlDataList; /*子節(jié)點列表*/CC_PRIVATE_BOOL(m_isNone, None); /*標(biāo)記本身是否為空節(jié)點*/CC_PRIVATE(EnumWinType, mEnWinType, EnWinTYpe); /*控件類型*/CC_PRIVATE(int, m_iX, iX); /*X坐標(biāo)*/CC_PRIVATE(int, m_iY, iY); /*Y坐標(biāo)*/ };? ? ? ? ? ?CC_PRIVATE和CC_PRIVATE_BOOL宏,代碼如下:
#define CC_PRIVATE(varType, varName, funName) \ private: varType varName; \ public: varType get##funName(void) const { return varName; } \ public: void set##funName(varType var) { varName = var; }/*創(chuàng)建bool私有變量,包括get和set方法*/ #define CC_PRIVATE_BOOL(varName, funcName) \ private: bool varName; \ public: bool is##funcName(void) const { return varName; } \ public: void set##funcName(bool var) { varName = var; }? ? ? ? ? ?最后新建一個枚舉類,創(chuàng)建一個頭文件,命名為EnumWinType,代碼如下:
/*控件類型字符串*/ #define WINType_C_en_Win_None "MMNone" //無 #define WINType_C_en_Win_NormalWin "MMNormalWin" //普通窗口 #define WINType_C_en_Win_Label "MMLabel" //標(biāo)簽enum EnumWinType {en_Win_None,en_Win_NormalWin, /*普通窗口*/en_Win_Label, /*普通標(biāo)簽*/ };? ? ? ? ? 修改HelloWorldScene,新增兩個函數(shù),代碼如下:
class MMWinXmlData; class TiXmlElement; class HelloWorld : public cocos2d::Layer { public:virtual bool init();static cocos2d::Scene *createScene();CREATE_FUNC(HelloWorld);/*創(chuàng)建自定義的標(biāo)簽屬性對象*/MMWinXmlData *createXmlData(TiXmlElement *xmlElement);/*設(shè)置某個具體的標(biāo)簽*/void setWinXmlData(MMWinXmlData *xmlData, const char *sName, const char *sText); };? ? ? ? ? 來看看createXmlData函數(shù),用于將XML配置文件轉(zhuǎn)換為我們的MMWinXmlData對象,代碼如下:
MMWinXmlData *HelloWorld::createXmlData(TiXmlElement *xmlElement) {MMWinXmlData *xmlData = MMWinXmlData::create();TiXmlElement *cldElement = xmlElement->FirstChildElement();log("%s %d: --------------------", __FILE__, __LINE__);/*默認(rèn)節(jié)點為空節(jié)點*/xmlData->setNone(true);while( cldElement != NULL) {/*MMCld節(jié)點的內(nèi)容為空,所以不獲取它的內(nèi)容,但是它有子節(jié)點*/if (cldElement->GetText() != NULL) { xmlData->setNone(false);/*給節(jié)點賦值*/setWinXmlData(xmlData, cldElement->Value(), cldElement->GetText());log("%s %d:[%s]-[%s]", __FILE__, __LINE__, cldElement->Value(), cldElement->GetText());} else {/*如果有子節(jié)點,則繼續(xù)解析,并且添加到當(dāng)前節(jié)點的子節(jié)點列表*/if (cldElement->FirstChildElement() != NULL) {xmlData->addCldXmlData(createXmlData(cldElement));}}/*下一個同級節(jié)點*/cldElement = cldElement->NextSiblingElement();}return xmlData; }? ? ? ? ?看看setWinXmlData函數(shù)如何給MMWinXmlData對象賦值的,代碼如下:
void HelloWorld::setWinXmlData(MMWinXmlData *xmlData, const char *sName, const cahr *sText) {if (strcmp(sName, "enWinType") == 0) {if (strcmp(sText, WINType_C_en_Win_None) == 0) {xmlData->setEnWinType(en_Win_None);} else if (strcmp(sText, WINType_C_en_Win_NormalWin) == 0) {xmlData->setEnWinType(en_Win_NormalWin);} else if (strcmp(sText, WINType_C_en_Win_Label) == 0) {xmlData->setEnWinType(en_Win_Label);}} else if (strcmp(sName, "x") == 0) {xmlData->setiX(atoi(sText));} else if (strcmp(sName, "y") == 0) {xmlData->setiY(atoi(sText));} }? ? ? ? ?三個參數(shù)的含義:
- ? ? ? ? ?MMWinXmlData *xmlData: 自定義的節(jié)點對象;
 - ? ? ? ? ?const char *sName: 標(biāo)簽節(jié)點的名字,如"enWinType";
 - ? ? ? ? ?const char *sText: 標(biāo)簽節(jié)點的內(nèi)容,如"MMNormalWin"。
 
? ? ? ? 測試一下,修改HelloWorldScene的init函數(shù),代碼如下:
bool HelloWorld::init() {if (!Layer::init()) { return false; }TiXmlDocument *xmlDoc = new TiXmlDocument();/*開始解析XML*/Data fileData = FileUtils::getInstance()->getDataFromFile("test.xml");xmlDoc->Parse((const char *)fileData.getBytes());TiXmlElement *rootElement = xmlDoc->RootElement();/*只有這里是新知識,根據(jù)標(biāo)簽根節(jié)點對象,創(chuàng)建MMWinXmlData對象*/MMWinXmlData *xmlData = createXmlData(rootElement->FirstChildElement());delete xmlDoc;return true; }? ? ? ? ?13.6 XML標(biāo)簽節(jié)點屬性設(shè)置器
? ? ? ? ? MMWinXmlData僅僅用來保存標(biāo)簽節(jié)點數(shù)據(jù),不進(jìn)行任何邏輯處理,用另外一個類去處理各種邏輯,職責(zé)分明,便于維護(hù)和擴(kuò)展。
? ? ? ? ? MMWinXmlDataSetting.h文件:
#ifndef __MM_WIN_XML_DATA_SETTING_H__ #define __MM_WIN_XML_DATA_SETTING_H__/*XML文件的節(jié)點名稱*/ #define XML_VALUE_enWinType "enWinType" //控件類型 #define XML_VALUE_x "x" //X坐標(biāo) #define XML_VALUE_y "y" //Y坐標(biāo)#include "cocos2d.h" #include "MMWinXmlData.h"USING_NS_CC;/*普通set函數(shù),為了減少重復(fù)工作 例子:void setXMLiX(MMWinXmlData *xmlData, const char *sText); */ #define MM_SET_XML(funName) \ public: void setXml##funName(MMWinXmlData *xmlData, const char *sText);class MMWinXmlDataSetting : public Ref { public:CREATE_FUNC(MMWinXmlDataSetting);virtual bool init();/*給XML data對象賦值*/void setWinXmlData(MMWinXmlData *xmlData, const char *sName, const char *sText); public:/* --------------------根據(jù)XML中的節(jié)點字符串內(nèi)容賦值-------------*/MM_SET_XML(EnWinType); /*設(shè)置控件類型*/MM_SET_XML(iX); /*設(shè)置X坐標(biāo)*/MM_SET_XML(iY); /*設(shè)置Y坐標(biāo)*/ }; #endif? ? ? ? ? ?MMWinXmlDataSetting的實現(xiàn),代碼如下:
void MMWinXmlDataSetting::setXmlEnWinType(MMWinXmlData *xmlData, const char *sText) {if (strcmp(sText, WINType_C_en_Win_None) == 0) {xmlData->setEnWinType(en_Win_None);} else if (strcmp(sText, WINType_C_en_Win_NormalWin) == 0) {xmlData->setEnWinType(en_Win_NormalWin);} else if (strcmp(sText, WINType_C_en_Win_Label) == 0) {xmlData->setEnWinType(en_Win_Label);} }void MMWinXmlDataSetting::setXmliX(MMWinXmlData *xmlData, const char *sText) {xmlData->setiX(atoi(sText)); }void MMWinXmlDataSetting::setXmliY(MMWinXmlData *xmlData, const char *sText) {xmlData->setiY(atoi(sText)); }void MMWinXmlDataSetting::setWinXmlData(MMWinXmlData *xmlData, cosnt char *sName, const char *sText) {if (strcmp(sName, XML_VALUE_enWinType) == 0) {setXmlEnWinType(xmlData, sText);} else if (strcmp(sName, XML_VALUE_x) == 0) {setXmliX(xmlData, sText);} else if (strcmp(sName, XML_VALUE_y) == 0) {setXmliY(xmlData, sText);} }? ? ? ? ? ?修改HelloWorldScene的setWinXmlData函數(shù),代碼如下:
void HelloWorld::setWinXmlData(MMWinXmlData *xmlData, const char *sName, const char *sText) {/*為xmlData對象的屬性賦值*/MMWinXmlDataSetting *winXmlDataSetting = MMWinXmlDataSetting::create();winXmlDataSetting->setWinXmlData(xmlData, sName, sText); }? ? ? 13.7 創(chuàng)建控件
? ? ? ? ? ? 在HelloWorldScene增加一個函數(shù),代碼如下:
Node *HelloWorld::createWins(MMWinXmlData *xmlData) {EnumWinType enWinType = xmlData->getEnWinType();Point pos = CCPointMake(xmlData->getiX(), xmlData->getiY());/*根據(jù)控件類型創(chuàng)建不同類型的控件*/Node *win = NULL;switch(enWinType) {case en_Win_NormalWin:win = Sprite::create("bg.png");win->setPosition(pos);break;case en_Win_Label:win = Label::create("Label", "Arial", 35);win->setPosition(pos);break;}/*創(chuàng)建子控件*/if (win != NULL && xmlData->isHasChild()) {auto childList = xmlData->getCldXmlDataList();for (auto xmlData : childList) {win->addChild(createWins(xmlData));}}return win; }? ? ? ? ? ? ? 測試一下,修改HelloWorldScene的init函數(shù),代碼如下:
bool HelloWorld::init() {if (!Layer::init()) { return false; }TiXmlDocument *xmlDoc = new TiXmlDocument();/*開始解析XML*/Data fileData = FileUtils::getInstance()->getDataFromFile("test.xml");xmlDoc->Parse((const char *)fileData.getBytes());TiXmlElement *rootElement = xmlDoc->RootElement();MMWinXmlData *xmlData = createXmlData(rootElement->FirstChildElement());/*創(chuàng)建控件*/Node *win = createWins(xmlData);this->addChild(win);delete xmlDoc;return true; }? ? ? ? ? ? ?接著編寫一個XML文件,命名為test.xml,保存到項目的resources目錄,代碼如下:
<?xml version="1.0" encoding="utf-8"?> <MMWinRoot><MMWin><!-- 窗口 --><enWinType>MMNormalWin</enWinType><x>250</x><y>150</y><MMCld><!-- 標(biāo)簽1 --><enWinType>MMLabel</enWinType><x>50</x><y>140</y></MMCld><MMCld><!-- 標(biāo)簽2 --><enWinType>MMLabel</enWinType><x>200</x><y>140</y></MMCld></MMWin> </MMWinRoot>? ? ? ? ? ?效果圖:
? ? ? ? ? ? ? ?
? ? 13.8 組件
? ? ? ? ?(1) 控件基類---MMBase
? ? ? ? ? ? MMBase類:
class MMBase : public Node { public:MMBase();~MMBase();private:CC_PRIVATE(int, m_ID, ID); /*控件ID*/CC_PRIVATE(int, m_iOrder, iOrder); /*層次*/CC_PRIVATE(EnumWinType, mEnWinType, EnWinType); /*控件類型*/CC_PRIVATE_BOOL(m_isHasParentWin, HasParentWin); /*是否有父控件*/CC_PRIVATE_BOOL(m_isHasChildWin, HasChildWin); /*是否有子控件*/ };? ? ? ? ?MMBase的實現(xiàn),代碼如下:
MMBase::MMBase(): m_ID(-1),m_iOrder(1),m_isHasParentWin(false),m_isHasChildWin(false) { }MMBase::~MMBase() { }? ? ? ?(2) 普通窗口控件-----MMNormalWin
? ? ? ? ?MMNormalWin類,代碼如下:
class MMNormalWin : public MMBase { public:MMNormalWin();~MMNormalWin();CREATE_FUNC(MMNormalWin);virtual bool init();virtual void setAnchorPoint(const Point &anchorPoint);void setBG(const char *sPath); /*設(shè)置窗口背景圖片*/ private:Sprite *m_sprite; /*用一個精靈作為窗口的表現(xiàn)*/ };? ? ? ? MMNormalWin的實現(xiàn),代碼如下:
MMNormalWin::MMNormalWin() {m_sprite = NULL; }MMNormalWin::~MMNormalWin() {}bool MMNormalWin::init() {MMBase::init();return true; }void MMNormalWin::setBG( const char *sPath) {if (m_sprite != NULL) {this->removeChild(m_sprite);}m_sprite = Sprite::create(sPath);this->addChild(m_sprite);Size size = m_sprite->getContentSize();m_sprite->setPosition(Point(size.width * 0.5f, size.height * 0.5f));this->setContentSize(size); }void MMNormalWin::setAnchorPoint(const Point &anchorPoint) {Node::setAnchorPoint(anchorPoint);/*child的描點也要設(shè)置*/m_sprite->setAnchorPoint(anchorPoint); }? ? ? ?(3) 標(biāo)簽控件----MMLabel
? ? ? ? ? MMLabel.h文件:
class MMLabel : public MMBase { public:MMLabel();~MMLabel();CREATE_FUNC(MMLabel);virtual bool init();virtual void setAnchorPoint(const Point &anchorPoint);/*設(shè)置標(biāo)簽內(nèi)容*/void setsText(const char *sText);/*設(shè)置標(biāo)簽內(nèi)容,內(nèi)容為數(shù)字*/void setsText(int iValue);/*設(shè)置標(biāo)簽文字大小*/void setiFontSize(int iFontSize);/*設(shè)置標(biāo)簽文字顏色*/void setColorRGB(int r, int g, int b); private:Label *m_label; };? ? ? ? ?來看看MMLabel的實現(xiàn):
MMLabel::MMLabel() {m_label = NULL; } MMLabel::~MMLabel() { }bool MMLabel::init() {MMBase::init();m_label = Label::create("", "Arial", 24);this->addChild(m_label);return true; } void MMLabel::setsText(const char *sText) {m_label->setString(sText); }void MMLabel::setsText(int iValue) {setsText(StringUtils::toString(iValue).c_str()); }void MMLabel::setiFontSize(int iFontSize) {m_label->setSystemFontSize(iFontSize); }void MMLabel::setAnchorPoint(const Point &anchorPoint) {Node::setAnchorPoint(anchorPoint);m_label->setAnchorPoint(anchorPoint); }void MMLabel::setColorRGB(int r, int g, int b) {m_label->setColor(Color3B(r, g, b)); }? ? ? (4) 測試新的控件類
? ? ? ? ? 修改HelloWorldScene的createWins函數(shù),代碼如下:
Node *HelloWorld::createWins(MMWinXmlData *xmlData) {EnumWinType enWinType = xmlData->getEnWinType();Point pos = Point(xmlData->getiX(), xmlData->getiY());/*根據(jù)控件類型創(chuàng)建不同類型的控件*/MMBase *win = NULL;switch(enWinType) {case en_Win_NormalWin:win = MMNormalWin::create();((MMNormalWin *)win)->setBG("bg.png");win->setPosition(pos);break;case en_Win_Label:win = MMLabel::create();((MMLabel *)win)->setsText("MMLabel");win->setPosition(pos);break;}/*創(chuàng)建子控件*/if (win != NULL && xmlData->isHasChild()) {auto childList = xmlData->getCldXmlDataList();for (auto xmlData : childList) {win->addChild(createWins(xmlData));}}return win; }? ? ? ? 然后編寫一個xml文件,命名為test.xml,代碼如下:
<?xml version="1.0" encoding="utf-8"?> <MMWinRoot><MMWin><!-- 窗口 --><enWinType>MMNormalWin</enWinType><x>50</x><y>20</y><MMCld><!-- 標(biāo)簽1 --><enWinType><MMLabel</enWinType><x>100</x><y>140</y></MMCld></MMWin> </MMWinRoot>? ? ? ? ?效果如下:
? ? ?
? ? ?(5) MMWinManager控件管理器
? ? ? ? MMWinManager.h文件:
class MMWinManager : public Ref { public:MMWinManager();~MMWinManager();static MMWinManager *getInstance();virtual bool init();/*獲取桌面*/MMDesktopWin *getDesktopWin();/*獲取控件生成器*/MMWinSystem *getWinSystem(); public:/*根據(jù)XML文件,創(chuàng)建控件*/MMBase *createWinsFromXML(const char *sXmlPath); private:/*讀取XML元素,生成一個XML控件樹形結(jié)構(gòu)對象*/MMWinXmlData *createXmlData(TiXmlElement *xmlElement);/*為XML data對象賦值*/void setWinXmlData(MMWinXmlData *xmlData, const char *sName, const char *sText); private:static MMWinManager *mWinManager; private:/*控件頂級桌面*/MMDesktopWin *mDesktopWin;/*控件生成器*/MMWinSystem *mWinSystem;/*xml屬性結(jié)構(gòu)設(shè)置器*/MMWinXmlDataSetting *mWinXmlDataSetting; };? ? ? ? ? ?我們重點看看createWinsFromXML函數(shù),該函數(shù)用于讀取XML配置文件并生成控件,它將替代之前HelloWorldScene的createWins函數(shù):
? ? ? ? ? ?cpp文件代碼如下:
MMWinManager *MMWinManager::mWinManager = NULL; MMWinManager::MMWinManager() { }MMWinManager::~MMWinManager() {CC_SAFE_RELEASE_NULL(mWinXmlDataSetting);CC_SAFE_RELEASE_NULL(mWinSystem);CC_SAFE_RELEASE_NULL(mDesktopWin); }MMWinManager *MMWinManager::getInstance() {if (mWinManager == NULL) {mWinManager = new MMWinManager();if (mWinManager && mWinManager->init()) {mWinManager->autorelease();} else {CC_SAFE_DELETE(mWinManager);mWinManager = NULL;}}return mWinManager; }bool MMWinManager::init() {mWinXmlDataSetting = MMWinXmlDataSetting::create();mWinXMlDataSetting->retain();mDesktopWin = MMDesktopWin::create();mDesktopWin->retain();mWinSystem = MMWinSystem::create(mDesktopWin);mWinSystem->retain();return true; }MMBase *MMWinManager::createWinsFromXML(const char *sXmlPath) {TiXmlDocument *xmlDoc = new TiXmlDocument();TiXmlElement *rootElement = xmlDoc->RootElement();MMWinXmlData *xmlData = createXmlData(rootElement->FirstChildElement());TiXmlElement *rootElement = xmlDoc->RootElement();MMWinXmlData *xmlData = createXmlData(rootElement->FirstChildElement());delete xmlDoc;/*生成控件*/MMBase *baseWin = mWinSystem->createWinsByXmlData(xmlData, false);if (baseWin->isHasChildWin() == false) {mWinSystem->dressPropertiesByType(baseWin, xmlData);}/*添加父控件到桌面中*/mDesktopWin->addChild(baseWin, baseWin->getiOrder());return baseWin; }MMWinXmlData *MMWinManager::createXmlData(TiXmlElement *xmlElement) {MMWinXmlData *xmlData = MMWinXmlData::create();TiXmlElement *cldElement = xmlElement->FirstChildElement();xmlData->setNone(true); /*默認(rèn)節(jié)點為空節(jié)點*/while(cldElement != NULL) {/*MMCld節(jié)點的內(nèi)容為空,所以不獲取它的內(nèi)容,但是它有子節(jié)點*/if (cldElement->GetText() != NULL) {xmlData->setNone(false);/*給節(jié)點賦值*/setWinXmlData(xmlData, cldElement->Value(), cldElement->GetText());} else {/*如果有子節(jié)點,則繼續(xù)解析,并且添加到當(dāng)前節(jié)點的子節(jié)點列表*/if (cldElement->FirstChildElement() != NULL) {xmlData->addCldXmlData(createXmlData(cldElement));}}/*下一個同級節(jié)點*/cldElement = cldElement->NextSiblingElement();}return xmlData; }void MMWinManager::setWinXmlData(MMWinXmlData *xmlData, const char *sName, const char *sText) {mWinXmlDataSetting->setWinXmlData(xmlData, sName, sText); }MMDesktopWin *MMWinManager::getDesktopWin() {return this->mDesktopWin; }MMWinSystem *MMWinManager::getWinSystem() {return this->mWinSystem; }? ? ? ? ? ? 創(chuàng)建控件的流程如下:
? ? ? ? ? ? (1) 創(chuàng)建一個XML配置文件。
? ? ? ? ? ? (2) 調(diào)用MMWinManager的createWinsFromXML函數(shù)。
? ? ? ? ? ? (3) 讀取XML文件,創(chuàng)建MMWinXmlData對象。
? ? ? ? ? ? (4) 用MMWinXmlData對象調(diào)用MMWinSystem的createWinsByXmlData函數(shù)創(chuàng)建控件。
? ? ? ? ? ? (5) 將創(chuàng)建的控件添加到MMDesktopWin.
? ? ?(6) MMWinSystem控件系統(tǒng)
? ? ? ? ?MMWinSystem.h頭文件:
class MMWinSystem : public Ref { public:MMWinSystem();~MMWinSystem();static MMWinSystem *create(MMDesktopWin *desktopWin);virtual bool init(MMDesktopWin *desktopWin);/*根據(jù)XML結(jié)構(gòu)數(shù)據(jù)創(chuàng)建控件它有可能創(chuàng)建很多個控件,但最終都會添加到一個父控件里換句話說,XML文件里只允許出現(xiàn)一個最高父控件,不能出現(xiàn)同級別的父控件*/MMBase *createWinsByXmlData(MMWinXmlData *xmlData, bool isHasParent);/*根據(jù)控件類型給控件設(shè)置屬性(就像穿衣服一樣)*/void dressPropertiesByType(MMBase *mmWin, MMWinXmlData *xmlData);/*創(chuàng)建一個唯一ID*/int createUniqueID(); private: /*------屬性 -------*//*桌面*/MMDesktopWin *mDesktopWin;/*控件工廠*/MMWinBaseFactory *mWinFactory;/*控件屬性加工廠*/MMWinPropertyFactory *mWinPropertyFactory;/*控件ID*/int m_iWinID;private: /*-------------方法 -------------*//*根據(jù)XML結(jié)構(gòu)數(shù)據(jù)創(chuàng)建一個控件*/MMBase *createWinByXmlData(MMWinXmlData *xmlData);/*根據(jù)控件類型創(chuàng)建一個控件*/MMBase *createWinByType(EnumWinType enWinType); };? ? ? ?MMWinSystem稍微有點復(fù)雜,它的主要職責(zé)就是調(diào)用控件工廠生成控件,并且產(chǎn)生控件的唯一ID。
? ? ? ?看看createWinsByXmlData函數(shù),代碼如下:
MMBase *MMWinSystem::createWinsByXmlData(MMWinXmlData *xmlData, bool isHasParent) {/*規(guī)定只能有一個MMBase,XML中生成的所有控件的最終父控件都是這個惟一的MMBase*/MMBase *baseWin = NULL;if (xmlData->isNone() == false) {baseWin = createWinByXmlData(xmlData);}if (xmlData->isHasChild()) {if (baseWin != NULL) {baseWin->setHasChildWin(true);/*如果沒有父控件,代表自身是父控件,父控件要在子控件之前設(shè)置屬性*/if (isHasParent == false) {/*根據(jù)控件類型給控件設(shè)置屬性,父控件要在子控件之前設(shè)置屬性*/dressPropertiesByType(baseWin, xmlData);}}auto cldXmlDataList = xmlData->getCldXmlDataList();for (auto cldXmlData : cldXmlDataList) {MMBase *mmWin = createWinsByXmlData(cldXmlData, true);baseWin->addChild(mmWin);mmWin->setHasParentWin(true);/*根據(jù)控件類型給控件設(shè)置屬性(如果沒有父控件,代表自身是父控件,父控件已經(jīng)設(shè)置過屬性,不重復(fù)設(shè)置) */if (mmWin->isHasParentWin() == true) {dressPropertiesByType(mmWin, cldXmlData);}}}return baseWin; }? ? ? ? ?看看createWinByXmlData函數(shù),代碼如下:
MMBase *MMWinSystem::createWinByXmlData(MMWinXmlData *xmlData) {assert(xmlData && "createWinByXmlData:xmlData is NULL!");EnumWinType enWinType = xmlData->getEnWinType();/*根據(jù)控件類型創(chuàng)建控件*/MMBase *win = createWinByType(enWinType);return win; }MMBase *MMWinSystem::createWinByType(EnumWinType enWinType) {/*從控件工廠創(chuàng)建一個控件*/return mWinFactory->createWinByType(enWinType); }? ? ? ?(7) MMWinDesktop控件頂層桌面
? ? ? ? ? ? ?MMWinDekstop.h文件:
class MMDesktopWin : public MMBase { public:MMDesktopWin();~MMDesktopWin();CREATE_FUNC(MMDesktopWin);virtual bool init(); public:/*添加一個控件*/void addWin(MMBase *mmWin);/*根據(jù)ID獲取控件*/MMBase *getWinByID(int ID);/*刪除所有控件*/void removeAllWins(); private:/*存放所有控件的字典<MMBase, CCInteger> */Map<int, MMBase *> mWinDict; };? ? ? ? ? MMWinDesktop用于存放所有控件的引用,方便查找和管理。擁有添加控件、查找控件、刪除控件的功能。
? ? ? ? ? MMWinDesktop.cpp文件:
MMDesktopWin::MMDesktopWin() { }MMDesktopWin::~MMDesktopWin() { } bool MMDesktopWin::init() {return true; } void MMDesktopWin::addWin(MMBase *mmWin) {assert(mmWin && "addWin:mmWin is NULL!");int iWinID = mmWin->getID();/*如果已經(jīng)存在該ID的控件,則先刪除*/if (mWinDict.at(iWinID) != nullptr) {mWinDict.erase(iWinID);}/*添加控件到字典中,方便索引*/mWinDict.insert(iWinID, mmWin); }MMBase *MMDesktopWin::getWinByID(int ID) {return mWinDict.at(ID); }void MMDesktopWin::removeAllWins() {mWinDict.clear();this->removeAllChildrenWithCleanup(true); }? ? ? ? ? ? ? MMDesktopWin就像一個桌子,我們把所有的控件都丟到桌子上,每個控件都有一個ID,要找哪個控件就從桌子上找。
? ? ? (8) 抽象工廠之MMWinBaseFactory
? ? ? ? ? MMWinBaseFactory.h文件:
class MMWinBaseFactory : public Ref { public:MMBase *createWinByType(EnumWinType enWinType); protected:/*由子類負(fù)責(zé)創(chuàng)建控件*/virtual MMBase *createWin(EnumWinType enWinType) = 0; };? ? ? ? ?MMWinBaseFactory.cpp文件:
MMBase *MMWinBaseFactory::createWinByType(EnumWinType enWinType) {/*從子類件工廠創(chuàng)建一個控件*/MMBase *mmWin = createWin(enWinType);/*給控件設(shè)置一個唯一ID(必須大于0)*/mmWin->setID(MMWinManager::getInstance()->getWinSystem()->createUniqueID());/*每一個控件都要添加到desktop中*/MMWinManager::getInstance()->getDesktopWin()->addWin(mmWin);return mmWin; }? ? ? (9) 控件工廠之MMWinFactory
? ? ? ?MMWinFactory.h文件:
class MMWinFactory : public MMWinBaseFactory { public:CREATE_FUNC(MMWinFactory);virtual bool init();protected:virtual MMBase *createWin(EnumWinType enWinType); };? ? ? ?MMWinFactory繼承了MMWinBaseFactory,因此它必須實現(xiàn)createWin函數(shù)。這就是使用抽象工廠的好處,我們可以有很多不同的工廠,可以用不同的方式生成控件,而這種差異就是由createWin函數(shù)來實現(xiàn)的。
? ? ?MMWinFactory.cpp文件:
bool MMWinFactory::init() {return true; }MMBase *MMWinFactory::createWin(EnumWinType enWinType) {MMBase *win = NULL;switch(enWinType) {case en_Win_None:break;case en_Win_NormalWin:win = MMNormalWin::create();break;case en_Win_Label:win = MMLabel::create();break;}if (win != NULL) {win->setEnWinType(enWinType);}return win; }? ? ? (10) 裝飾工廠之MMWinProperityFactory
? ? ? ? ?MMWinProperityFactory.h文件,代碼如下:
class MMWinPropertyFactory : public Ref { public:CREATE_FUNC(MMWinPropertyFactory);virtual bool init(); public:/*給控件設(shè)置屬性(穿衣服)*/void dressPropertiesByType(MMBase *mmWin, MMWinXmlData *xmlData);private:/*設(shè)置控件公共屬性,所有控件都必須設(shè)置*/void dressBaseProperties(MMBase *mmWin, MMWinXmlData *xmlData);private:void dressMMNormalWin(MMNormalWin *mmNormalWin, MMWinXmlData *xmlData);void dressMMLabel(MMLabel *mmLabel, MMWinXmlData *xmlData); };? ? ? ? 再看看MMWinProperityFactory的dressPropertiesByType函數(shù)的實現(xiàn),代碼如下:
void MMWinPropertyFactory::dressPropertiesByType(MMBase *mmWin, MMWinXmlData *xmlData) {/*根據(jù)控件類型設(shè)置獨特屬性*/switch(mmWin->getEnWinType()) {case en_Win_None:break;case en_Win_NormalWin:dressMMNormalWin((MMNormalWin *)mmWin, xmlData);break;case en_Win_Label:dressMMLabel((MMLabel *)mmWin, xmlData);break;}/*設(shè)置基礎(chǔ)屬性*/dressBaseProperties(mmWin, xmlData); }? ? ? ? ? 該函數(shù)要做兩個處理:
? ? ? ? ? ? (1) 根據(jù)控件類型分別調(diào)用不同控件的裝飾函數(shù),每種控件都有一個對應(yīng)的裝飾函數(shù),因為不同的空間可能有不同的屬性。
? ? ? ? ? ? (2) 調(diào)用控件的公共裝飾函數(shù),不同的控件仍然有相同的屬性,如X、Y坐標(biāo),所以把相同屬性的設(shè)置工作放到一個函數(shù)里。
? ? ? ? ?再看看其他的裝飾函數(shù),代碼如下:
void MMWinPropertyFactory::dressBaseProperties(MMBase *mmWin, MMWinXmlData *xmlData) {/*如果有父控件,則取父控件的寬高*/MMBase *mmParent = NULL;if (mmWin->isHasParentWin()) {mmParent = (MMBase *)mmWin->getParent();}mmWin->setPositionX(xmlData->getiX()); //X坐標(biāo)mmWin->setPositionY(xmlData->getiY()); //Y坐標(biāo) }void MMWinPropertyFactory::dressMMNormalWin(MMNormalWin *mmNormalWin, MMWinXmlData *xmlData) {mmNormalWin->setBG("bg.jpg"); }void MMWinPropertyFactory::dressMMLabel(MMLabel *mmLabel, MMWinXmlData *xmlData) {mmLabel->setsText("Label Test!"); }? ? ?(11) 運行項目
? ? ? ? ?修改HelloWorldScene的init函數(shù),代碼如下:
bool HelloWorld::init() {bool bRet = false;do {/*創(chuàng)建控件*/MMBase *win = MMWinManager::getInstance()->createWinsFromXML("test.xml");/*將頂級桌面添加到場景*/this->addChild(MMWinManager::getInstance()->getDesktopWin());bRet = true;} while (0);return bRet; }? ? ? ? ?效果如下:
? ? ? ? ? ?
14.《卡牌塔防》(上篇)
? ? ?最終效果圖:
? ? ?
? ? 主要功能包括:
? ? ? ?<1> 內(nèi)置關(guān)卡編輯器? ? ? ? ? ? ? ? ?<2> CocoStudio UI編輯器使用:使用CocoStudio UI編輯器編寫游戲UI。
? ? ? ?<3> 怪物靈活配置? ? ? ? ? ? ? ? ? ? ?<4> 怪我種類Csv配置
? ? ? ?<5> 英雄種類Csv配置? ? ? ? ? ? ? ?<6> 英雄升級功能
? ? ? ?<7> 怪物血量條? ? ? ? ? ? ? ? ? ? ? ? ?<8> 炮塔操作按鈕
? ? ? ?<9> 屬性刷新? ? ? ? ? ? ? ? ? ? ? ? ? ? ?<10> 場景管理器
? ? ? ?<11> 文件讀取: 讀取Csv文件、讀取XML文件? ? <12> 國際化支持
? ? ? ?<13> 移動控制器:控制怪物和子彈移動的各種控制器
? ? ? ?<14> 怪物管理器:負(fù)責(zé)管理所有怪物對象
? ? ? ?<15> 英雄管理器:負(fù)責(zé)管理所有英雄(也就是炮塔)對象。
? ?14.1 炮臺坐標(biāo)編輯器
? ? ? ? TowerPosEditorScene.h文件:
class TowerPosEditorScene : public Layer { public:TowerPosEditorScene();~TowerPosEditorScene();static Scene *createScene();virtual bool init();CREATE_FUNC(TowerPosEditorScene); };? ? ? ? TowerPosEditorScene.cpp文件:
TowerPosEditorScene::TowerPosEditorScene() { }TowerPosEditorScene::~TowerPosEditorScene() { }Scene *TowerPosEditorScene::scene() {auto scene = Scene::create();auto layer = TowerPosEditorLayer::create();scene->addChild(layer, 1);return scene; }bool TowerPosEditorScene::init() {if (!Layer::init()) {return false;}return true; }? ? ? ? ?PosBase類(炮臺坐標(biāo)類),代碼如下:
? ? ? ? ?PosBase.h文件:
class PosBase : public Layer { public:PosBase();~PosBase();static PosBase *create(Point pos);static PosBase *create(Point pos, bool isDebug);bool init(Point pos);bool init(Point pos, bool isDebug);CC_SYNTHESIZE(Point, m_pos, Pos);virtual bool isClickMe(Point pos); /*判斷坐標(biāo)是否進(jìn)入范圍*/void setDebug(bool isDebug); /*開啟或關(guān)閉調(diào)試模式*/ protected:bool m_isDebug; /*是否為調(diào)試狀態(tài)*/ };? ? ? ?PosBase.cpp文件:
PosBase::PosBase() {m_pos = Point(0, 0);m_isDebug = false; }PosBase::~PosBase() { }PosBase *PosBase::create(Point pos) {PosBase *tPos = new PosBase();if (tPos && tPos->init(pos)) {tPos->autorelease();} else {CC_SAFE_DELETE(tPos);}return tPos; }PosBase *PosBase::create(Point pos, bool isDebug) {PosBase *tPos = new PosBase();if (tPos && tPos->init(pos, isDebug)) {tPos->autorelease();} else {CC_SAFE_DELETE(tPos);}return tPos; }bool PosBase::init(Point pos) {bool bRet = false;do {setPos(pos);bRet = true;} while(0);return bRet; }bool PosBasae::init(Point pos, bool isDebug) {bool bRet = false;do {CC_BREAK_IF(! init(pos));m_isDebug = isDebug;bRet = true;} while (0);return bRet; }bool PosBase::isClickMe(Point pos) {return false; }void PosBase::setDebug(bool isDebug) {this->m_isDebug = isDebug; }? ? ? ? ? PosBase只是一個基類,現(xiàn)在我們需要一個炮臺坐標(biāo)類,創(chuàng)建一個類繼承PosBase,命名為TowerPos,代碼如下:
#define RADIUS 32 class TowerPos : public PosBase { public:TowerPos();~TowerPos();static TowerPos *create(Point pos);static TowerPos *create(Point pos, bool isDebug);bool init(Point pos);bool init(Point pos, bool isDebug);virtual bool isClickMe(Point pos) override; /*判斷坐標(biāo)是否進(jìn)入范圍*/void draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated); private:void onDraw(const kmMat4 &transform, bool transformUpdated);CustomCommand _customCommand; };? ? ? ? ?draw函數(shù)是為了調(diào)試用的,我們在配置關(guān)卡時,要設(shè)置坑的位置,當(dāng)然就是屏幕上需要設(shè)置坑的位置單擊一下了,但是如果單擊之后沒有任何表現(xiàn),那我們怎么知道點了哪里呢?因此,我們在編輯關(guān)卡時需要把坐標(biāo)的位置畫出來,這就是調(diào)試模式。
? ? ? 我們來看看draw函數(shù),代碼如下:
void TowerPos::draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated) {if (m_isDebug) {_customCommand.init(_globalZOrder);_customCommand.func = CC_CALLBACK_0(TowerPos::onDraw,this, transform, transformUpdated);renderer->addCommand(&_customCommand);} }void TowerPos::onDraw(const kmMat4 &transform, bool transformUpdated) {kmGLPushMatrix();kmGLLoadMatrix(&transform);glLineWidth(5.0f); //設(shè)置畫筆粗細(xì)/*繪制矩形*/Point srcPos = Point(m_pos.x - RADIUS, m_pos.y + RADIUS);Point destPos = Point(m_pos.x + RADIUS, m_pos.y - RADIUS);DrawPrimitives::drawRect(srcPos, destPos);glLineWidth(1); //恢復(fù)畫筆粗細(xì)kmGLPopMatrix(); //結(jié)束繪制 }? ? ? 看看isClickMe函數(shù),用于判斷某個坐標(biāo)是否進(jìn)入TowerPos的矩形范圍,代碼如下:
bool TowerPos::isClickMe(Point pos) {Point srcPos = Point(m_pos.x - RADIUS, m_pos.y + RADIUS);Point destPos = Point(m_pos.x + RADIUS, m_pos.y - RADIUS);if (pos.x >= srcPos.x && pos.x <= destPos.x && pos.y <= srcPos.y && pos.y >= destPos.y) {return true;}return false; }? ? ? 看看TowerPos剩余的幾個函數(shù),代碼如下:
TowerPos::TowerPos() {m_pos = Point(0, 0);m_isDebug = false; }TowerPos::~TowerPos() { }TowerPos *TowerPos::create(Point pos) {TowerPos *tPos = new TowerPos();if (tPos && tPos->init(pos)) {tPos->autorelease();} else {CC_SAFE_DELETE(tPos);}return tPos; }TowerPos *TowerPos::create(Point pos, bool isDebug) {TowerPos *tPos = new TowerPos();if (tPos && tPos->init(pos, isDebug)) {tPos->autorelease();} else {CC_SAFE_DELETE(tPos);}return tPos; }bool TowerPos::init(Point pos) {bool bRet = false;do {CC_BREAK_IF(! PosBase::init(pos));bRet = true;} while(0);return bRet; }bool TowerPos::init(Point pos, bool isDebug) {bool bRet = false;do {CC_BREAK_IF(! PosBase::init(pos, isDebug));bRet = true;} while(0);return bRet; }? ? ? ? 測試一下TowerPos類,我們修改一下TowerPosEditorScene的scene函數(shù),代碼如下:
Scene *TowerPosEditorScene::createScene() {auto scene = Scene::create();//auto layer = TowerPosEditorLayer::create();//scene->addChild(layer, 1);TowerPos *pos = TowerPos::create(Point(200, 200), true);scene->addChild(pos);return scene; }? ? ? 我們創(chuàng)建了一個TowerPos對象,并且將其添加到場景里。
? ? ? 最后,把游戲的初始啟動場景設(shè)置為TowerPosEditorScene(在AppDelegate里設(shè)置).
? ? ? 效果如下:
? ?
? ? 真正的炮臺坐標(biāo)編輯器
? ? ?TowerPosEditorLayer包含以下功能:單機(jī)屏幕任意位置會添加炮臺坐標(biāo)對象,單擊已存在的炮臺坐標(biāo)對象則刪除對象,最后生成配置文件。
? ? ?TowerPosEditorLayer.h文件:
class TowerPosEditorLayer : public Layer { public:TowerPosEditorLayer();~TowerPosEditorLayer();CREATE_FUNC(TowerPosEditorLayer);virtual bool init(); private:Vector<PosBase *> m_towerPosList; /*存放所有塔的坐標(biāo)對象*/int m_iCurLevel; /*當(dāng)前關(guān)卡*/void editTowerPos(Point pos); /*編輯塔坐標(biāo)*/PosBase *findExistTowerPos(Point pos); /*根據(jù)坐標(biāo)找到已經(jīng)存在的塔坐標(biāo)對象*/void createTowerPos(Point pos); /*給定坐標(biāo),生成塔坐標(biāo)對象*/void deleteTowerPos(PosBase *existPos); /*給定塔坐標(biāo)對象,刪除塔坐標(biāo)對象*/void deleteAllPos(); /*刪除所有坐標(biāo)對象*/ };? ? ? 看看init函數(shù),代碼如下:
bool TowerPosEditorLayer::init() {if (!Layer::init()) { return false; }/*監(jiān)聽觸摸事件*/auto listener = EventListenerTouchOneByOne::create();listener->onTouchBegan = [](Touch *touch, Event *event) {return true;};listener->onTouchEnded = [&](Touch *touch, Event *event) {Point pos = Director::getInstance()->convertToGL(touch->getLocationInView());editTowerPos(pos);};_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);return true; }? ? ?看看editTowerPos函數(shù):
void TowerPosEditorLayer::editTowerPos(Point pos) {/*如果單擊了已經(jīng)存在的塔坐標(biāo)對象,則刪除該坐標(biāo)對象,否則創(chuàng)建新坐標(biāo)對象*/PosBase *existPos = findExistTowerPos(pos);if (existPos != NULL) {deleteTowerPos(existPos);} else {createTowerPos(pos);} }PosBase *TowerPosEditorLayer::findExistTowerPos(Point pos) {for (auto basePos : m_towerPosList) {if (basePos->isClickMe(pos)) {return basePos;}}return NULL; }void TowerPosEditorLayer::createTowerPos(Point pos) {TowerPos *tPos = TowerPos::create(pos, true);this->addChild(tPos, 10);m_towerPosList.pushBack(tPos); }void TowerPosEditorLayer::deleteTowerPos(PosBase *existPos) {this->removeChild(existPos);m_towerPosList.eraseObject(existPos); }? ? ? ? ? TowerPosEditorLayer有一個Vector成員變量m_towerPosList,添加和刪除TowerPos的操作實際上只是對m_towerPosList的增、刪操作,查找TowerPos對象,就是遍歷m_towerPosList,判斷列表中的TowerPos的坐標(biāo)是否和單機(jī)屏幕的坐標(biāo)一致。
? ? ? ? ? ?值得注意的是,m_towerPosList存放的是PosBase對象,而不是TowerPos對象,這是多態(tài)的一種應(yīng)用。我們?nèi)匀豢梢园裈owerPos添加到m_towerPosList里,但從m_towerPosList中取出的對象類型默認(rèn)是PosBase,在需要的時候我們可以強(qiáng)制轉(zhuǎn)換為TowerPos(因為我們丟進(jìn)去的就是TowerPos對象)。
? ? ? ? ? ?來看看剩下的函數(shù),代碼如下:
TowerPosEditorLayer::TowerPosEditorLayer() {m_iCurLevel = 1; } TowerPosEditorLayer::~TowerPosEditorLayer() { }void TowerPosEditorLayer::deleteAllPos() {this->removeAllChildrenWithCleanup(true);m_towerPosList.clear(); }? ? ? ? ? 將TowerPosEditorScene的scene函數(shù)改回原來的樣子,代碼如下:
Scene *TowerPosEditorScene::createScene() {auto scene = Scene::create();auto layer = TowerPosEditorLayer::create();scene->addChild(layer, 1);return scene; }? ? ? ? ?運行并輸出:
? ? ??
? ? ?生成plist配置文件
? ? ? ?為TowerPosEditorLayer新增兩個函數(shù),代碼如下:
void TowerPosEditorLayer::outputPosToPlistFile() {ValueMap fileDataMap;/*各個屬性*/int index = 1;for (auto posBase : posList) {ValueMap data;data["x"] = posBase->getPos().x;data["y"] = posBase->getPos().y;fileDataMap[StringUtils::toString(index)] = Value(data);index++;}FileUtils::getInstance()->writeToFile(fileDataMap, sFilePath); }? ? ? ?最終生成的文件類似這樣:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC"-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"><dict><key>1</key><dict><key>x</key><real>126.4497147</real><key>y</key><real>359.8025818</real></dict><key>2</key><dict><key>x</key><real>356.071332</real><key>y</key><real>357.8025818</real></dict><key>3</key><dict><key>x</key><real>631.6184692</real><key>y</key><real>359.8025818</real></dict></dict> </plist>? ? ? ?這是Cocos2d-x支持的一種文件格式,通過FileUtils工具類可以直接生成和讀取這樣的文件。
? ? ? 一個炮臺坐標(biāo)對象對應(yīng)一個dict標(biāo)簽,如下:
<key>1</key><dict><key>x</key><real>126</real><key>y</key><real>359</real></dict>? ? ? ? 這樣一個dict標(biāo)簽代表一個坐標(biāo)為(126,359)的TowerPos對象數(shù)據(jù),一個dict對應(yīng)一個ValueMap結(jié)構(gòu),從中可以取出數(shù)據(jù)。
? ? 操作層----TowerPosEditorOperateLayer
? ? ? ?TowerPosEditorOperateLayer.h文件:
class TowerPosEditorOperateLayer : public Layer { public:TowerPosEditorOperateLayer();~TowerPosEditorOperateLayer();static TowerPosEditorOperateLayer *create(TowerPosEditorLayer *layer);virtual bool init(TowerPosEditorLayer *layer); private:/*編輯層*/TowerPosEditorLayer *m_editorLayer; };? ? ? ?TowerPosEditorOperateLayer.cpp文件:
TowerPosEditorOperateLayer::TowerPosEditorOperateLayer() { } TowerPosEditorOperateLayer::~TowerPosEditorOperateLayer() { } TowerPosEditorOperateLayer *TowerPosEditorOperateLayer::create(TowerPosEditorLayer *layer) {TowerPosEditorOperateLayer *oprLayer = new TowerPosEditorOperateLayer();if (oprLayer && oprLayer->init(layer)) {oprLayer->autorelase();} else {CC_DAFE_DELETE(oprLayer);}return oprLayer; } bool TowerPosEditorOperateLayer::init(TowerPosEditorLayer *layer) {if (!Layer::init()) { return false; }this->m_editorLayer = layer;/*加載UI*/auto UI = cocostudio::GUIReader::getInstance()-> widgetFromJsonFile("EditorOperate/EditorOperate_1.ExportJson");this->addChild(UI);/*少了這句代碼就不好玩了*/UI->setTouchEnabled(false);/*獲取控件對象*/auto outputBtn = (Button *)Helper::seekWidgetByName(UI, "outputBtn");/*添加單機(jī)監(jiān)聽*/outputBtn->addTouchEventListener(this, toucheventselector(TowerPosEditorOperateLayer::outputBtnOnClick));return true; }void TowerPosEditorOperateLayer::outputBtnOnClick(Ref *, TouchEventType type) {if (type == TouchEventType::TOUCH_EVENT_ENDED) {m_editorLayer->outputPosToPlistFile();} }? ? ? ? ?當(dāng)單擊outputBtn按鈕時,就會調(diào)用TowerPosEditorLayer的outputPosToPlistFile函數(shù)。
? ? ? ? ?UI->setTouchEnabled(false);這句代碼一定要寫上,否則,一個UI本身也是一個控件,這個控件默認(rèn)是能接受觸摸事件的。必須通過setTouchEnabled函數(shù)把UI的觸摸響應(yīng)關(guān)閉,否則,在UI下方的所有對象的觸摸事件都會被吃掉,從而無法響應(yīng)。
? ? ? ? ?最后把新加的層添加到場景里,修改TowerPosEditorScene的scene函數(shù),代碼如下:
Scene *TowerPosEditorScene::createScene() {auto scene = Scene::create();auto editorLayer = TowerPosEditorLayer::create();scene->addChild(editorLayer, 1);auto operLayer = TowerPosEditorOperateLayer::create(editorLayer);scene->addChild(operLayer, 2);return scene; }? ? ? ? ?效果:
? ? ? ? ??
? ? ? ? 單機(jī)“輸出文件”按鈕,在Resources下的tollgate目錄中多了一個towerPos_level_1.plist文件。
? ? 讀取plist配置文件,生成炮臺坐標(biāo)
? ? ? 寫一個工具類,用來讀取配置文件,PosLoadUtil類:
? ? ? PosLoadUtil.h文件:
class PosLoadUtil : public CCNode { public:static PosLoadUtil *getInstance();virtual bool init();/*根據(jù)坐標(biāo)類型從plist配置文件中讀取坐標(biāo)對象sFilePath: 配置文件路徑container: 存放坐標(biāo)對象的容器iLevel: 如果存在container,該參數(shù)表示坐標(biāo)對象在容器中的層次isDebug: 是否開啟調(diào)試模式*/Vector<PosBase *> loadPosWithFile (const char *sFilePath,Node *container,int iLevel,bool isDebug); private:static PosLoadUtil *m_posLoadUtil; };? ? ? ?PosLoadUtil.cpp文件:
PosLoadUtil *PosLoadUtil::m_posLoadUtil = NULL;PosLoadUtil *PosLoadUtil::sharedPosLoadUtil() {if (m_posLoadUtil == NULL) {m_posLoadUtil = new PosLoadUtil();if (m_posLoadUtil && m_posLoadUtil->init()) {m_posLoadUtil->autorelease();} else {CC_SAFE_DELETE(m_posLoadUtil);}}return m_posLoadUtil; }bool PosLoadUtil::init() {return true; }Vector<PosBase *>PosLoadUtil::loadPosWithFile(const char *sFilePath, Node *container, int iLevel, bool isDebug) {Vector<PosBase *> posList;ValueMap fileDataMap = FileUtils::getInstance()->getValueMapFromFile(sFilePath);int size = fileDataMap.size();for (int i=1; i<=size; i++) {Value value = fileDataMap.at(StringUtils::toString(i));ValueMap data = value.asValueMap();/*創(chuàng)建坐標(biāo)對象*/PosBase *posBase = TowerPos::create(Point(data["x"].asInt(), data["y"].asInt()), isDebug);posList.pushBack(posBase);if (container != NULL) {container->addChild(posBase, iLevel);}}return posList; }? ? ? ? 我們要給TowerPosEditorLayer新增一個函數(shù),代碼如下:
void TowerPosEditorLayer::loadConfigFile() {Size visibleSize = Director::getInstance()->getVisibleSize();/*添加地圖背景*/std::string sBG = StringUtils::format("tollgate/level_%d.jpg", m_iCurLevel);Sprite *map = Sprite::create(sBG.c_str());map->setPosition(Point(visibleSize.width / 2, visibleSize.height / 2 ));this->addChild(map, 1);/*加載塔坐標(biāo)對象*/std::string sTowerPosPath = StringUtils::format("tollgate/towerPos_level_%d.plist", m_iCurLevel);Vector<PosBase *> posList = PosLoadUtil::getInstance()->loadPosWithFile(sTowerPosPath.c_str(), this, 10, true);m_towerPosList.pushBack(posList); }? ? ? ? 這個函數(shù)就只做了兩件事件:
- ? ? ? ? 根據(jù)當(dāng)前關(guān)卡加載地圖背景
 - ? ? ? ? 使用PosLoadUtil工具類加載炮臺坐標(biāo)配置文件,給m_towerPosList炮臺坐標(biāo)列表賦值。
 
? ? ? ?運行,效果:
? ? ? ? ?
? ?怪物坐標(biāo)編輯器
enum EnumPosType {enTowerPos, /*炮臺坐標(biāo)*/enMonsterPos, /*怪物坐標(biāo)*/ };? ? ? MonsterPos.h文件:
class Monster; class MonsterPos : public PosBase { public:MonsterPos();~MonsterPos();static MonsterPos *create(Point pos);static MonsterPos *create(Point pos, bool isDebug);bool init(Point pos);bool init(Point pos, bool isDebug);/*判斷坐標(biāo)是否進(jìn)入范圍*/virtual bool isClickMe(Point pos) override;void draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated); private:void onDraw(const kmMat4 &transform, bool transformUpdated);CustomCommand _customCommand;Monster *m_monster; };? ? ?MonsterPos.cpp文件:
MonsterPos::MonsterPos() {m_pos = Point(0, 0);m_isDebug = false; } MonsterPos::~MonsterPos() { } MonsterPos *MonsterPos::create(Point pos) {MonsterPos *tPos = new MonsterPos;if (tPos && tPos->init(pos)) {tPos->autorelease();} else {CC_SAFE_DELETE(tPos);}return tPos; }MonsterPos *MonsterPos::create(Point pos, bool isDebug) {MonsterPos *tPos = new MonsterPos();if (tPos && tPos->init(pos, isDebug)) {tPos->autorelease();} else {CC_SAFE_DELETE(tPos);}return tPos; }bool MonsterPos::init(Point pos) {bool bRet = false;do {setPos(pos);bRet = true;} while(0);return bRet; }bool MonsterPos::init(Point pos, bool isDebug) {bool bRet = false;do {CC_BREAK_IF(! init(pos));m_isDebug = isDebug;bRet = true;} while(0);return bRet; }void MonsterPos::draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated) {if (m_isDebug) {_customCommand.init(_globalZOrder);_customCommand.func = CC_CALLBACK_0(MonsterPos::onDraw, this, transform, transformUpdated);renderer->addCommand(&_customCommand);} }void MonsterPos::onDraw(const kmMat4 &transform, bool transformUpdated) {kmGLPushMatrix();kmGLLoadMatrix(&transform);glLineWidth(4);/*繪制圓型*/DrawPrimitives::drawCircle(m_pos, MONSTER_RADIUS, 360, 20, false);glLineWidth(1);kmGLPopMatrix(); }bool MonsterPos::isClickMe(Point pos) {Point srcPos = Point(m_pos.x - MONSTER_RADIUS, m_pos.y + MONSTER_RADIUS);Point destPos = Point(m_pos.x + MONSTER_RADIUS, m_pos.y - MONSTER_RADIUS);if (pos.x >= srcPos.x && pos.x <= destPos.x && pos.y <= srcPos.y && pos.y >= destPos.y) {return true;}return false; }? ? ?新增怪物坐標(biāo)編輯器
? ? ? ?首先在TowerPosEditorLayer.h文件增加一些內(nèi)容,代碼如下:
EnumPosType m_enMode; /*當(dāng)前模式*/ void editMonsterPos(Point pos); /*編輯怪物坐標(biāo)*/ Vector<PosBase *> m_monsterPosList; /*存放所有怪物的坐標(biāo)對象*/ PosBase *findExistMonsterPos(Point pos); /*根據(jù)坐標(biāo)找到已經(jīng)存在的怪物坐標(biāo)對象*/ void createMonsterPos(Point pos); /*給定坐標(biāo),生成怪物坐標(biāo)對象*/ void deleteMonsterPos(PosBase *existPos); /*給定怪物坐標(biāo)對象,刪除怪物坐標(biāo)對象*/? ? ? ?新增函數(shù)的實現(xiàn):
void TowerPosEditorLayer::editMonsterPos(Point pos) {/*如果單擊了已經(jīng)存在的怪物坐標(biāo)對象,則刪除該坐標(biāo)對象,否則創(chuàng)建新坐標(biāo)對象*/PosBase *existPos = findExistMonsterPos(pos);if (existPos != NULL) {deleteMonsterPos(existPos);} else {createMonsterPos(pos);} }PosBase *TowerPosEditorLayer::findExistMonsterPos(Point pos) {for (auto basePos : m_monsterPosList) {if (basePos->isClickMe(pos)) {return basePos;}}return NULL; }void TowerPosEditorLayer::createMonsterPos(Point pos) {MonsterPos *mPos = MonsterPos::create(pos, true);this->addChild(mPos, 10);m_monsterPosList.pushBack(mPos); }void TowerPosEditorLayer::deleteMonsterPos(PosBase *existPos) {this->removeChild(existPos);m_monsterPosList.eraseObject(existPos); }? ? ? ?修改init函數(shù):
bool TowerPosEditorLayer::init() {if (!Layer::init()) { return false; }/*監(jiān)聽觸摸事件*/auto listener = EventListenerTouchOneByOne::create();listener->onTouchBegan = [](Touch *touch, Event *event) {return true;};listener->onTouchEnded = [&](Touch *touch, Event *event) {Point pos = Director::getInstance()->convertToGL(touch->getLocationInView());/*判斷當(dāng)前編輯器的模式,進(jìn)行不同的操作*/switch(m_enMode) {case enTowerPos:editTowerPos(pos);break;case enMonsterPos:editMonsterPos(pos);break;}};_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);/*讀取配置文件*/loadConfigFile();return true; }? ? ? ?效果:
? ? ? ?
? 輸出怪物坐標(biāo)配置文件
? ? ?修改TowerPosEditorLayer的outputPosToPlistFile函數(shù)即可,代碼如下:
void TowerPosEditorLayer::outputPosToPlistFile() {/*輸出炮臺坐標(biāo)配置文件*/std::string sTowerPosPath = StringUtils::format("tollgate/towerPos_level_%d.plist", m_iCurLevel);outputPosToPlistFile(m_towerPosList, sTowerPosPath.c_str());/*輸出怪物坐標(biāo)配置文件*/std::string sMonsterPosPath = StringUtils::format("tollgate/monsterPos_level_%d.plist", m_iCurLevel);outputPosToPlistFile(m_monsterPosList, sMonsterPosPath.c_str()); }? ? ?單擊"輸出文件"按鈕,就能看到resources的tollgate目錄下多了一個monsterPos_level_1.plist文件。
? ? 修改loadConfigFile函數(shù),代碼如下:
void TowerPosEditorLayer::loadConfigFile(){/*這里省略了一些代碼*//*加載塔坐標(biāo)對象*/std::string sTowerPosPath = StringUtils::format("tollgate/towerPos_level_%d.plist", m_iCurLevel); Vecotr<PosBase *> posList = PosLoadUtil::getInstance()->loadPosWithFile(sTowerPosPath.c_str(), enTowerPos, this, 10, true);m_towerPosList.pushBack(poslist);/*加載怪物坐標(biāo)對象*/std::string sMonsterPosPath = StringUtils::format("tollgate/monsterPos_level_%d.plist", m_iCurLevel);posList = PosLoadUtil::getInstance()->loadPosWithFile(sMonsterPosPath.c_str(), enMonsterPos, this, 10, true);m_monsterPosList.pushBack(posList); }? ? ? ? 修改PosLoadUtil的loadPosWithFile函數(shù),代碼如下:
Vector<PosBase *> PosloadUtil::loadPosWithFile(const char *sFilePath, EnumPosType enPosType,Node *container, int iLevel, bool isDebug) {Vector<PosBase *> posList;ValueMap fileDataMap = FileUtils::getInstance()->getValueMapFromFile(sFilePath);int size = fileDataMap.size();for (int i=1; i<=size; i++) {Value value = fileDataMap.at(StringUtils::toString(i));ValueMap data = value.asValueMap();/*創(chuàng)建坐標(biāo)對象*/PosBase *posBase = NULL;switch(enPosType) {case enTowerPos:posBase = TowerPos::create(Point(data["x"].asInt(), data["y"].asInt()), isDebug);break; case enMonsterPos:posBase = MonsterPos::create(Point(data["x"].asInt(), data["y"].asInt()), isDebug);break;default:posBase = TowerPos::create(Point(data["x"].asInt(), data["y"].asInt()), isDebug);break;}posList.pushBack(posBase);if (container != NULL) {container->addChild(posBase, iLevel);}}return posList; }? ? ? ? 運行項目,屏幕上出現(xiàn)了我們之前創(chuàng)建的怪物坐標(biāo)對象,這就證明monsterPos_level_1.plist成功讀取了。
? ?14.2 添加更多方便的操作
? ? ? ?(1) 新增切換坐標(biāo)模式類型按鈕
? ? ? ? ?如果每次編輯都要在代碼里修改坐標(biāo)類型,然后編譯,運行,那得多麻煩。我們修改TowerPosEditorOperateLayer的init函數(shù),代碼如下:
boolTowerPosEditorOperateLayer::init(TowerPosEditorLayer *layer) {/*這里省略了很多代碼*//*獲取控件對象*/auto changeModeBtn = (Button *)Helper::seekWidgetByName(UI, "changeModeBtn");/*添加點擊監(jiān)聽*/changeModeBtn->addTouchEventListener(this, toucheventselector(TowerPosEditorOperateLayer::changeModeBtnOnClick)); }? ? ?新增一個按鈕,文本屬性改為"切換模式",按鈕的名字屬性改為"changeModeBtn",然后導(dǎo)出文件,用新導(dǎo)出的文件替換舊的文件。
單擊按鈕時,執(zhí)行changeModeBtnOnClick函數(shù),代碼如下:
void TowerPosEditorOperateLayer::changeModeBtnOnClick(Ref *, TouchEventType type) {if (type == TouchEventType::TOUCH_EVENT_ENDED) {m_editorLayer->changeMode();} }TowerPosEditorOperateLayer的changeModeBtnOnClick函數(shù)里又調(diào)用了TowerPosEditorLayer的changeMode函數(shù),因為,要給TowerPosEditorLayer新增一個changeMode函數(shù),代碼如下:
void TowerPosEditorLayer::changeMode() {if(m_enMode == enMonsterPos) {m_enMode = enTowerPos;} else {m_enMode = enMonsterPos;} }? 運行項目,單擊change mode按鈕之后,再單擊屏幕的其他地方,會發(fā)現(xiàn)每次單擊change mode按鈕后,就會切換坐標(biāo)類型。
? ? (2) 新增關(guān)卡切換按鈕
? ? ? ?為TowerPosEditorLayer新增兩個函數(shù),代碼如下:
int TowerPosEditorLayer::nextLvl() {deleteAllPos(); /*刪除當(dāng)前所有的坐標(biāo)對象*/m_iCurLevel++; /*關(guān)卡計數(shù)加1*/loadConfigFile(); /*重新讀取配置文件*/return m_iCurLevel; }int TowerPosEditorLayer::preLvl() {deleteAllPos(); /*刪除當(dāng)前所有的坐標(biāo)對象*/m_iCurLevel--; /*關(guān)卡計數(shù)減1*/loadConfigFile(); /*重新讀取配置文件*/return m_iCurLevel; }? ? ?切換關(guān)卡的邏輯很簡單,刪除現(xiàn)有的坐標(biāo)對象,然后重新加載配置文件就可以了。
? ? ? 我們需要有按鈕來執(zhí)行這個操作,修改TowerPosEditorOperateLayer的init函數(shù),代碼如下:
? ??
boolTowerPosEditorOperateLayer::init(TowerPosEditorLayer *layer) {/*省略了很多代碼*//*獲取控件對象*/auto nextLevelBtn = (Button *)Helper::seekWidgetByName(UI, "nextLevelBtn");auto preLevelBtn = (Button *)Helper::seekWidgetByName(UI, "preLevelBtn");/*添加點擊監(jiān)聽*/nextLevelBtn->addTouchEventListener(this, toucheventselector(TowerPosEditorOperateLayer::nextLevelBtnOnClick));preLevelBtn->addTouchEventListener(this, toucheventselector(TowerPosEditorOperateLayer::preLevelBtnOnClick)); }void TowerPosEditorOperateLayer::nextLevelBtnOnClick(Ref *, TouchEventType type) {if (type == TouchEventType::TOUCH_EVENT_ENDED) {m_editorLayer->nextLvl();} }void TowerPosEditorOperateLayer::preLevelBtnOnClick(Ref *, TouchEventType type) {if (type == TouchEventType::TOUCH_EVENT_ENDED) {m_editorLayer->preLvl();} }? ?新增的按鈕文本屬性分別為"下一關(guān)","上一關(guān)",名字屬性分別為"nextLevelBtn","preLevelBtn".然導(dǎo)出新的文件,替換舊的文件,效果如下:
? ??
?現(xiàn)在運行項目,單擊"下一關(guān)"按鈕,現(xiàn)有的坐標(biāo)對象都消失了,編輯器清空了,其實我們已經(jīng)到了第二關(guān)了。編輯好坐標(biāo)后的,單擊output按鈕就能生成第二關(guān)的配置文件。
??
? ?(3) 繪制怪物行走路徑
? ? ? ?讓TowerPosEditorOperateLayer重寫draw函數(shù),首先重寫draw函數(shù),代碼如下:
? ??TowerPosEditorOperateLayer.h文件:
class TowerPosEditorOperateLayer : public Layer { public:/*省略了很多代碼*/void draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated);private:void onDraw(const kmMat4 &transform, bool transformUpdated);CustomCommand _customCommand; };? ? ?TowerPosEditorOperateLayer.cpp文件:
void TowerPosEditorOperateLayer::draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated) {_customCommand.init(_globalZOrder);_customCommand.func = CC_CALLBACK_0(TowerPosEditorOperateLayer::onDraw, this,transform, transformUpdated);renderer->addCommand(&_customCommand); }void TowerPosEditorOperateLayer::onDraw(const kmMat4 &transform, bool transformUpdated) {kmGLPushMatrix();kmGLLoadMatrix(&transform);Vector<PosBase *> m_monsterPosList = m_editorLayer->getMonsterPosList();for (auto posBase : m_monsterPosList) {if (prePos != NULL) {DrawPrimitives::drawLine(prePos->getPos(), posBase->getPos());}prePos = posBase;}kmGLPopMatrix(); //結(jié)束繪制 }? ? ? ?怪物坐標(biāo)對象必須從TowerPosEditorLayer里獲取,所以,我們要給TowerPosEditorLayer新增一個函數(shù),代碼如下:
Vector<PosBase *> TowerPosEditorLayer::getMonsterPosList() {return m_monsterPosList; }? ? ?效果如下:
? ??
? ?(4) 整理代碼結(jié)構(gòu):
? ? ??
? 14.3 預(yù)備知識
? ? ? (1) 場景管理器
? ? ? ? SceneManager.h文件:
class SceneManager : public Ref { public:/*場景枚舉類*/enum EnumSceneType {en_TollgateScene, /*關(guān)卡場景*/en_TollgateEditorScene, /*關(guān)卡編輯器場景*/en_WinScene, /*勝利場景*/en_GameOverScene, /*游戲結(jié)束場景*/};/*獲取場景管理器對象*/static SceneManager *getInstance();/*初始化*/virtual bool init();/*切換場景*/void changeScene(EnumSceneType enScenType);private:/*場景管理器*/static SceneManager *mSceneManager; };? ? ? ?SceneManager.cpp文件:
SceneManager *SceneManager::mSceneManager = NULL;SceneManager *SceneManger::getInstance() {if(mSceneManager == NULL) {mSceneManager = new SceneManager();if (mSceneManager && mSceneManager->init()) {mSceneManager->autorelease();mSceneManager->retain();} else {CC_SAFE_DELETE(mSceneManager);mSceneManager = NULL;}}return mSceneManager; }bool SceneManager::init() {return true; }void SceneManager::changeScene(EnumSceneType enScanType) {Scene *pScene = NULL;switch(enScenType) {case en_TollgateScene: /*關(guān)卡場景*/break;case en_TollgateEditorScene: /*關(guān)卡編輯器場景*/pScene = TowerPosEditorScene::createScene();break;case en_WinScene: /*勝利場景*/break;case en_GameOverScene: /*游戲結(jié)束場景*/break;case en_GameOverScene: /*游戲結(jié)束場景*/break;}if (pScene == NULL) {return;}Director *pDirector = Director::getInstance();Scene *curScene = pDirector->getRunningScene();if (curScene == NULL){pDirector->runWithScene(pScene);} else {pDirector->replaceScene(pScene);} }? ? 試試場景管理器,修改AppDelegate的applicationDidFinishLaunching函數(shù),代碼如下:
bool AppDelegate::applicationDidFinishLaunching() {/*省略了很多代碼*//*調(diào)用場景管理器切換場景*/SceneManager::getInstance()->changeScene(SceneManager::en_TollgateEditorScene);//auto scene = TowerPosEditorScene::createScene();//director->runWithScene(scene);return true; }? ? ?運行項目,我們將看到熟悉的關(guān)卡編輯器界面。
? ? (2) 數(shù)據(jù)讀取模塊
? ? ? 讀取Csv文件需要用到三個類:StringUtil、CsvUtil、CsvData。代碼結(jié)構(gòu)如下:
? ? ?
? ? (3) 全局參數(shù)
? ? ? GlobalDefine.h文件:
#ifndef __GLOBAL_DEFINE_H__ #define __GLOBAL_DEFINE_H__#include "cocos2d.h"/*創(chuàng)建bool變量,包括get和set方法*/ #define CC_CC_SYNTHESIZE_BOOL(varName, funName) \ protected: bool varName; \ public: bool is##funName(void) const { return varName; }\ public: void set##funName(bool var) { varName = var; }/*消息派發(fā)*/ #define NOTIFY cocos2d::NotificationCenter::getInstance() #endif? ? ? GlobalPath.h文件:(定義了一些游戲中常用的文件路徑)
#ifndef _GlobalPath_H_ #define _GlobalPath_H_#define PATH_CSV_HERO_CONF "csv/Hero.csv" //英雄配置文件路徑 #define PATH_CSV_MONSTER_CONF "csv/Monster.csv" //怪物配置文件路徑#define PATH_BULLET_NOR "sprite/bullet/bulletNor.png" //普通子彈文件路徑/* -----------語言文件 ---------------*/ #define PATH_II8N_PUBLIC "i18n/public.csv" /*公用語言文件*//* ------------字體文件---------------*/ #define PATH_FONT_PUBLIC "Arial" #endif? ? ?GlobalParam.h文件:(定義了一些游戲中的整型變量)
#ifndef __GLOBAL_PARAM_H__ #define __GLOBAL_PARAM_H__#include "cocos2d.h" using namespace cocos2d;class GlobalParam { public:/*字體*/static const int PublicFontSize = 22;static const int PublicFontSizeLarge = 35;static const int PublicFontSizeLargest = 60; }; #endif? ? ?(4) I18N工具類
? ? ? ? I18N.h文件:
class I18N : public Ref { public:static I18N *getInstance();virtual bool init();/*根據(jù)Key值獲取字符串,返回const char *對象*/const char *getcString(EnumStrKey enStrKey);/*根據(jù)整形的Key值獲取字符串,返回const char *對象*/const char *getcStringByKey(int iKey);private:/*從配置文件中讀取字符串,保存到字典里*/void loadStringsFromConf(const char *filePath);/*I18N對象*/static I18N *m_I18N;/*游戲中用到的字符串字典*/std::map<int, std::string> mStringMap; };? ? ? ?I18N主要有2個函數(shù)提供給外部調(diào)用:
- ? ? ?getcString函數(shù): 根據(jù)EnumStrKey的類型返回char字符串;
 - ? ? ?getcStringByKey: 根據(jù)字符串?dāng)?shù)字ID返回char字符串。
 
? ? ? ?I18N.cpp文件:
I18N *I18N::m_I18N = NULL;I18N *I18N::getInstance() {if (m_I18N == NULL) {m_I18N = new I18N();if (m_I18N && m_I18N->init()) {m_I18N->autorelease();m_I18N->retain();} else {CC_SAFE_DELETE(m_I18N);m_I18N = NULL;}}return m_I18N; }bool I18N::init() {/*讀取配置文件的字符串*/loadStringsFromConf("i18n/Public.csv");return true; }const char *I18N::getcString(EnumStrKey enStrKey) {return mStringMap.at(enStrKey).c_str(); }const char *I18N::getcStringByKey(int iKey) {return mStringMap.at(iKey).c_str(); }void I18N::loadStringsFromConf(const char *filePath) {CsvUtil::getInstance()->loadFile(filePath);Size size = CsvUtil::getInstance()->getFileRowColNum(filePath);int iRowNum = size.width;int iColNum = size.height;/*如果列數(shù)小于2,表示配置文件有問題*/if (iColNum < 2) {return;}/*將配置文件的所有字符串存放到字典中(忽略第一行)*/for (int i=1; i<iRowNum; i++) {int key = CsvUtil::getInstance()->getInt(i, 0, filePath);std::string value = CsvUtil::getInstance()->get(i, 1, filePath);mStringMap[key] = value;} }? ? ? ?修改TowerPosEditorScene的scene函數(shù),代碼如下:
Scene *TowerPosEditorScene::createScene() {auto scene = Scene::create();auto editorLayer = TowerPosEditorLayer::create();scene->addChild(editorLayer, 1);auto operLayer = TowerPosEditorOperateLayer::create(editorLayer);scene->addChild(operLayer, 2);/*在函數(shù)最后加上這句,測試I18N工具類*/log("I18N Test: %s", I18N::getInstance()->getcString(en_StrKey_Public_Confirm));return scene; }? ? ?運行項目輸出:
I18N Test: 確定? ? 目前為止項目的目錄結(jié)構(gòu),如下:
? ??
15.英雄誕生
? ?15.1 創(chuàng)建關(guān)卡場景
? ? ?TollgateScene.h文件如下:
#define TAG_MAP_LAYER 1 //關(guān)卡地圖圖層TAG #define TAG_DATA_LAYER 2 //關(guān)卡數(shù)據(jù)圖層TAGclass TollgateScene : public Layer { public:static Scene *createScene();virtual bool init();CREATE_FUNC(TollgateScene); };? ? ? ?TollgateScene.cpp文件:
Scene *TollgateScene::createScene() {auto scene = Scene::create();TollgateScene *tgLayer = TollgateScene::create();TollgateMapLayer *mapLayer = TollgateMapLayer::create();scene->addChild(mapLayer, 1, TAG_MAP_LAYER);scene->addChild(tgLayer, 3);return scene; }bool TollgateScene::init() {if (!Layer::init()) {return false;}return true; }? ?15.2 地圖層
? ? ?很明顯TollgateMapLayer將會成為關(guān)卡的主要層,TollgateMapLayer.h文件:
class TollgateMapLayer : public Layer { public:TollgateMapLayer();~TollgateMapLayer();CREATE_FUNC(TollgateMapLayer);virtual bool init(); private:/*當(dāng)前關(guān)卡*/int m_iCurLevel;/*讀取關(guān)卡配置*/void loadConfig(); };? ? TollgateMapLayer.cpp文件:
TollgateMapLayer::TollgateMapLayer() {m_iCurLevel = 1; }TollgateMapLayer::~TollgateMapLayer() { }bool TollgateMapLayer::init() {bool bRet = false;do {/*讀取關(guān)卡配置*/loadConfig();bRet = true;} while (0);return true; }void TollgateMapLayer::loadConfig() {Size visibleSize = Director::getInstance()->getVisibleSize();/*添加背景音樂*/CocosDenshion::SimpleAudioEngine::getInstance()->playBackgroundMusic(StringUtils::format("music/tollgate_%d.mp3", m_iCurLevel).c_str(), true);/*添加地圖背景*/std::string sBG = StringUtils::format("tollgate/level_%d.jpg", m_iCurLevel);Sprite *map = Sprite::create(sBG.c_str());map->setPosition(Point(visibleSize.width / 2, visibleSize.height / 2));this->addChild(map, 1); }? ? ? 修改SceneManager的changeScene函數(shù),代碼如下:
void SceneManager::changeScene( EnumSceneType enScenType) {Scene *pScene = NULL;switch(enScenType) {case en_TollgateScene: /*關(guān)卡場景*/pScene = TollgateScene::createScene();break;case en_TollgateEditorScene: /*關(guān)卡編輯器場景*/pScene = TowerPosEditorScene::createScene();break;case en_WinScene: /*勝利場景*/break;case en_GameOverScene: /*游戲結(jié)束場景*/break;/*這里省略了很多代碼*/ }? ? ?修改AppDelegate的applicationDidFinishLaunching函數(shù),讓默認(rèn)啟動場景變成TollgateScene,代碼如下:
bool AppDelegate::applicationDidFinishLaunching() {/*這里省略了很多代碼*//*調(diào)用場景管理器切換場景*/SceneManager::getInstance()->changeScene(SceneManager::en_TollgateScene);return true; }? ? ?運行項目,效果如下,證明新增場景成功了.
? ? ? ?
? ?15.3 實體基類
? ? ? 在我們創(chuàng)建英雄和怪物之前,必須要有一個實體基類,Entity.h文件:
class Entity : public Node { public:Entity();~Entity();void bindSprite(Sprite *sprite);Sprite *getSprite();void hurtMe(int iHurtValue); /*被攻擊*/bool isDead(); /*是否死亡*/ protected:virtual void onDead(); /*實體死亡時調(diào)用*/virtual void onBindSprite(); /*綁定精靈對象時調(diào)用*/virtual void onHurt(int iHurtValue); /*受傷害時調(diào)用*/ private:Sprite *m_sprite;CC_SYNTHESIZE(int, m_ID, ID); //實體IDCC_SYNTHESIZE(int, m_iModelID, iModelID); //模型ID(資源ID)CC_SYNTHESIZE(std::string, m_sName, sName); //名字 CC_SYNTHESIZE(int, m_iHP, iHP); //HPCC_SYNTHESIZE(int, m_iDefense, iDefense); //防御CC_SYNTHESIZE(int, m_iSpeed, iSpeed); //移動速度CC_SYNTHESIZE(int, m_iLevel, iLevel); //等級bool m_isDead; //標(biāo)記是否死亡 };? ? Entity.cpp文件:
Entity::Entity() {m_sprite = NULL;m_sName = "";m_iHP = 1;m_iDefence = 1;m_isDead = false;m_iSpeed = 1;m_iLevel =1; }Entity::~Entity() { }void Entity::bindSprite(Sprite *sprite) {if (this->m_sprite != NULL) {m_sprite->removeFromParentAndCleanup(true);}this->m_sprite = sprite;this->addChild(m_sprite);Size size = m_sprite->getContentSize();this->setContentSize(size);onBindSprite(); }Sprite *Entity::getSprite() {return this->m_sprite; }void Entity::hurtMe(int iHurtValue) {if (m_isDead) {return;}/*最小傷害值為1*/if (iHurtValue <= getiDefence()) {iHurtValue = 1;}int iCurHP = getiHP(); /*當(dāng)前HP*/ int iAfterHP = iCurHP - iHurtValue; /*被攻擊后的HP*/onHurt(iHurtValue);if (iAfterHP > 0) {setiHp(iAfterHP);} else {m_isDead = true;/*死亡*/onDead();} }bool Entity::isDead() {return this->m_isDead; }void Entity::onDead() { }void Entity::onBindSprite() { }void Entity::onHurt(int iHurtValue) { }? ? ?15.4 英雄管理器1---炮臺對象
? ? ? ?在關(guān)卡中添加英雄的邏輯如下:
- ? ? ? ? 根據(jù)炮臺坐標(biāo)配置文件創(chuàng)建所有炮臺對象;
 - ? ? ? ? 單擊炮臺后創(chuàng)建英雄,前提是被單擊的炮臺是空炮臺(沒有放置英雄)。
 
? ? ? ? ?HeroManager.h文件:
#define TOWER_POS_LAYER_LVL 5 //塔坐標(biāo)的層次 #define TOWER_BORDER_LAYER_LVL 8 //炮臺的層次 #define TOWER_LAYER_LVL 10 //塔的層次 class HeroManager : public Node { public:HeroManager();~HeroManager();static HeroManager *createWithLevel(int iCurLevel);bool initWithLevel(int iCurLevel); private:Vector<PosBase *> m_towerPosList; /*存放所有塔的坐標(biāo)對象*/Vector<TowerBorder *> m_towerBorderList; /*存放所有炮臺對象*/void createTowerBorder(int iCurLevel); /*創(chuàng)建炮臺*/void createTowerPos(Point pos); /*給定坐標(biāo),生成塔坐標(biāo)對象*/ };? ? ? 我們要從關(guān)卡編輯器生成的配置文件里讀取炮臺坐標(biāo)對象(TowerPos),讀取了所有的炮臺坐標(biāo)對象后,根據(jù)這些坐標(biāo)生成炮臺。
? ? ?HeroManager.cpp文件:
HeroManager::HeroManager() { } HeroManager::~HeroManager() { } HeroManager *HeroManager::createWithLevel(int iCurLevel) {HeroManager *heroMgr = new HeroManager();if (heroMgr && heroMgr->initWithLevel(iCurLevel)) {heroMgr->autorelease();} else {CC_SAFE_DELETE(heroMgr);}return heroMgr; }bool HeroManager::initWithLevel(int iCurLevel) {/*加載塔坐標(biāo)對象*/std::string sTowerPosPath = StringUtils::format("tollgate/towerPos_level_%d.plist", iCurLevel);Vector<PosBase *> posList = PosLoadUtil::getInstance()->loadPosWithFile(sTowerPosPath.c_str(), enTowerPos, this, 10, false);m_towerPosList.pushBack(posList);/*創(chuàng)建炮臺*/createTowerBorder(iCurLevel);return true; }void HeroManager::createTowerBorder(int iCurLevel) {for (auto tPos : m_towerPosList) {TowerBorder *border = TowerBorder::create();border->upgrade();border->upgrade();border->setPosition(tPos->getPos());this->addChild(border);m_towerBorderList.pushBack(border);} }void HeroManager::createTowerPos(Point pos) {TowerPos *tPos = TowerPos::create(pos, true);this->addChild(tPos, TOWER_POS_LAYER_LVL);m_towerPosList.pushBack(true); }? ? ? TowerBorder是一個很簡單的類:
? ? ? ?TowerBorder.h文件:
class TowerBorder : public Entity { public:TowerBorder();~TowerBorder();CREATE_FUNC(TowerBorder);virtual bool init();void upgrade(); /*升級*/ };? ? ? TowerBorder.cpp文件:
TowerBorder::TowerBorder() {m_iLevel = 1; }TowerBorder::~TowerBorder() { }bool TowerBorder::init() {return true; }void TowerBorder::upgrade() {if (getSprite() != NULL) {getSprite()->stopAllActions();}std::string sFilePath = StringUtils::format("sprite/hero/border_%d.png", m_iLevel);Sprite *sprite = Sprite::create(sFilePath.c_str());bindSprite(sprite);m_iLevel++;if (m_iLevel == 2) {auto rotateBy = RotateBy:create(25.0f, 360, 360);auto repeat = RepeatForever::create(rotateBy);sFilePath = StringUtils::format("sprite/hero/magic_border_%d.png", m_iLevel);sprite = Sprite::create(sFilePath.c_str());sprite->setOpacity(80);sprite->runAction(repeat);this->addChild(sprite, 10);} }? ? ? ? ? TowerBorder利用Action來實現(xiàn)動畫效果。
? ? ? ?修改一下TollgateMapLayer的init函數(shù),讓英雄管理器生效,代碼如下:
//在TollgateMapLayer.cpp里加上下面的宏定義: #define HOME_LAYER_LVL 3 //堡壘的層次 #define HERO_LAYER_LVL 10 //英雄的層次 #define MONSTER_LAYER_LVL 15 //怪物的層次 #define BULLET_LAYER_LVL 20 //子彈的層次bool TollgateMapLayer::init() {if (!Layer::init()) { return false; }/*讀取關(guān)卡配置*/loadConfig();/*創(chuàng)建英雄管理器*/m_heroMgr = HeroManager::createWithLevel(m_iCurLevel);this->addChild(m_heroMgr, HERO_LAYER_LVL);return true; }? ? ? 運行查看效果:
? ??
? 15.5 英雄管理器2--英雄對象
? ? ? Hero.h文件:
class Hero : public Entity { public:Hero();~Hero();static Hero* create(Sprite *sprite);bool init(Sprite *sprite);/*給定英雄ID,從配置文件中讀取英雄數(shù)據(jù)*/static Hero *createFromCsvFileByID(int iHeroID);bool initFromCsvFileByID(int iHeroID);CC_SYNTHESIZE(int, m_iBaseAtk, iBaseAtk); //基礎(chǔ)攻擊力CC_SYNTHESIZE(int, m_iCurAtk, iCurAtk); //當(dāng)前攻擊力CC_SYNTHESIZE(int, m_iAtkSpeed, iAtkSpeed); //攻擊間隔(單位:毫秒)CC_SYNTHESIZE(int, m_iAtkRange, iAtkRange); //攻擊范圍(半徑)CC_SYNTHESIZE(int, m_iUpgradeCostBase, iUpgradeCostBase); //升級消耗基礎(chǔ)值CC_SYNTHESIZE(float, m_fUpgradeAtkBase, fUpgradeAtkBase); //升級攻擊加成系數(shù)/*升級英雄*/void upgrade(); };? ? ?Hero.cpp文件:
Hero::Hero() { }Hero::~Hero() { }Hero *Hero::create(Sprite *sprite) {Hero *hero = new Hero();if (hero && hero->init(sprite)) {hero->autorelease();} else {CC_SAFE_DELETE(hero);}return hero; }bool Hero::init(Sprite *sprite) {bool bRet = false;do {CC_BREAK_IF(!sprite);bindSprite(sprite);bRet = true;} while(0);return bRet; }Hero *Hero::createFromCsvFileByID(int iHeroID) { Hero *hero = new Hero();if (hero && hero->initFromCsvFileByID(iHeroID)) {hero->autorelease();} else {CC_SAFE_DELETE(hero);}return hero; }bool Hero::initFromCsvFileByID(int iHeroID) {bool bRet = false;do {CsvUtil *csvUtil = CsvUtil::getInstance();std::string sHeroID = StringUtils::toString(iHeroID);/*尋找ID所在的行*/int iLine = csvUtil->findValueInWithLine(sHeroID.c_str(), enHeroPropConf_ID, PATH_CSV_HERO_CONF);CC_BREAK_IF(iLine < 0);setID(iHeroID);setiModelID(csvUtil->getInt(iLine, enHeroPropConf_BaseAtk, PATH_CSV_HERO_CONF));setiBaseAtk(csvUtil->getInt(iLine, enHeroPropConf_BaseAtk, PATH_CSV_HERO_CONF));setiCurAtk(getiBaseAtk());setiAtkSpeed(csvUtil->getInt(iLine, enHeroPropConf_AtkSpeed, PATH_CSV_HERO_CONF));setiAtkRange(csvUtil->getInt(iLine, enHeroPropConf_AtkRange, PATH_CSV_HERO_CONF));setiUpgradeCostBase(csvUtil->getInt(iLine, enHeroPropConf_UpgradeCostBase, PATH_CSV_HERO_CONF));setfUpgradeAtkBase(csvUtil->getFloat(iLine, enHeroPropConf_UpgradeAtkBase, PATH_CSV_HERO_CONF));Sprite *sprite = Sprite::create(StringUtils::format("sprite/hero/hero_%d.png", iHeroID).c_str());CC_BREAK_IF(!sprite);Cc_BREAK_IF(!init(sprite));bRet = true;} while(0);return bRet; }? ? ?設(shè)置英雄的速度屬性,代碼所示:
? ? ? setiAtkSpeed(csvUtil->getInt(iLine, enHeroPropConf_AtkSpeed, PATH_CSV_HERO_CONF));
? ? ?enHeroPropConf_AtkSpeed是枚舉值,包含在EnumHeroPropConfType.h文件里,代碼如下:
enum EnumHeroPropConfType {enHeroPropConf_ID, //英雄IDenHeroPropConf_Name, //英雄名字enHeroPropConf_Type, //英雄類型enHeroPropConf_ModelID, //英雄模型IDenHeroPropConf_BaseAtk, //基礎(chǔ)攻擊力enHeroPropConf_AtkSpeed, //攻擊間隔(單位:秒)enHeroPropConf_AtkRange, //攻擊范圍(半徑)enHeroPropConf_UpgradeAtkBase, //升級攻擊加成系數(shù)enHeroPropConf_UpgradeCostBase, //升級消耗基礎(chǔ)值 };? ? ?英雄屬性的配置文件我們放在resources的csv目錄下,命名為Hero.csv,如下圖:
? ? ?
15.6 英雄管理器3--炮臺和英雄的關(guān)系
? ?我們給TowerBorder新增一個成員變量和一些函數(shù),代碼如下:
public:bool isClickMe(Point pos); /*判斷坐標(biāo)是否進(jìn)入范圍*/void bindHero(Hero *hero); /*綁定英雄對象*/Hero *getHero(); /*獲取英雄對象*/void deleteHero(); /*刪除英雄對象*/ private:Hero *m_hero; /*綁定的塔*/? ? TowerBorder賦予了綁定、刪除、獲取英雄對象的功能,并且新增了一個isClickMe函數(shù)用于判斷是否單擊了炮臺對象。
? ? 代碼如下:
TowerBorder::TowerBorder() {m_iLevel = 1;m_hero = NULL; }bool TowerBorder::isClickMe(Point pos) {Size size = getSprite()->getContentSize();Point borderPos = getPosition();Point srcPos = Point(borderPos.x - size.width/2, borderPos.y + size.height) / 2;Point destPos = Point(borderPos.x + size.width/2, borderPos.y - size.height/2);if (pos.x >= srcPos.x && pos.x <= destPos.x && pos.y <= srcPos.y && pos.y >= destPos.y) {return true;}return false; }void TowerBorder::bindHero(Hero *hero) {m_hero = hero; }Hero *TowerBorder::getHero() {return m_hero; }void TowerBorder::deleteHero() {if (m_hero != NULL) {m_hero->removeFromParent();} }15.7 英雄管理器4--加入創(chuàng)建英雄的功能
? ? ?為HeroManager新增一個函數(shù),代碼如下:
private:/*找到被單擊的炮臺對象*/TowerBorder *findChickTowerBorder(Point pos); };?
TowerBorder *HeroManager::findClickTowerBorder(Point pos) {for (auto tBorder : m_towerBorderList) {if (tBorder->isClickMe(pos)) {return tBorder;}}return NULL; }bool HeroManager::initWithLevel(int iCurLevel) {/*加載塔坐標(biāo)對象(之前寫的代碼,省略了)*//*創(chuàng)建炮臺(之前寫的代碼,省略了)*//*添加觸摸監(jiān)聽*/auto listener = EventListenerTouchOneByOne::create();listener->onTouchBegan = [](Touch *touch, Event *event) {return true;};listener->onTouchEnded = [&](Touch *touch, Event *event) {Point pos = Director::getInstance()->convertToGL(touch->getLocationInView());/*獲取被單擊的塔坐標(biāo)*/TowerBorder *clickBorder = findClickTowerBorder(pos);if (clickBorder == NULL) {return;}/*當(dāng)前塔坐標(biāo)沒有英雄對象,則添加英雄*/if (clickBorder->getHero() == NULL) {Hero *hero = Hero::createFromCsvFileByID(1);hero->setPosition(clickBorder->getPosition());this->addChild(hero, TOWER_LAYER_LVL);/*綁定英雄對象到炮臺*/clickBorder->bindHero(hero);}};_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);return true; }? ? ?onTouchEnded函數(shù)邏輯如下:
- ? ? ? 獲取當(dāng)前被單擊的炮臺對象;
 - ? ? ? 判斷炮臺對象是否已經(jīng)綁定了英雄;
 - ? ? ? 如果炮臺對象沒有綁定英雄,則創(chuàng)建一個新的英雄,添加到英雄管理器里,并且綁定到炮臺對象。目前英雄都只讀取ID為的配置。
 
? ? 運行游戲,在炮臺上單擊一下,效果如下:
? ? ?
? ? 15.8 怪物管理器
? ? ? ?MonsterManager.h文件:
class MonsterPos; class Monster; class PosBase; class MonsterManager : public Node { public:MonsterManager();~MonsterManager();static MonsterManager *createWithLevel(int iCurLevel);bool initWithLevel(int iCurLevel);void createMonsters(int iCurLevel); /*讀取配置文件創(chuàng)建怪物*/int getNotShowMonsterCount(); /*獲取還沒有出場的怪物數(shù)量*/MonsterPos *getMonsterStartPos(); /*獲取怪物出場坐標(biāo)*/MonsterPos *getMonsterEndPos(); /*獲取怪物終點坐標(biāo)*/Vector<Monster *> getMonsterList(); /*獲取怪物列表*/ private:Vector<Monster *> m_monsterList; /*怪物列表*/Vector<Monster *> m_notShowMonsterList; /*未出場的怪物列表*/Vector<MonsterPos *> m_monsterPosList; /*存放所有怪物的坐標(biāo)對象*/float m_fShowTimeCount; /*用于計算怪物出場時間*/void showMonster(float dt); /*檢查是否有新怪物出場*/ };? ? ? 看一下MonsterManager比較簡單的一些函數(shù),代碼如下:
MonsterManager::MonsterManager() {m_fShowTimeCount = 0; }MonsterManager::~MonsterManager() { }MonsterManager *MonsterManager::createWithLevel(int iCurLevel) {MonsterManager *monsterMsg = new MonsterManager();if (monsterMgr && monsterMgr->initWithLevel(iCurLevel)) {monsterMgr->autorelease();} else {CC_SAFE_DELETE(monsterMgr);}return monsterMgr; }bool MonsterManager::initWithLevel(int iCurLevel) {/*創(chuàng)建怪物*/createMonsters(iCurLevel);return true; }int MonsterManager::getNotShowMonsterCount() {return m_notShowMonsterList.size(); }Vector<Monster *> MonsterManager::getMonsterList() {return m_monsterList; }MonsterPos *MonsterManager::getMonsterStartPos() {return (MonsterPos *) m_monsterPosList.at(0); }MonsterPos *MonsterManager::getMonsterEndPos() {return (MonsterPos *)m_monsterPosList.at(m_monsterPosList.size() - 1); }? ? ?在initWithLevel函數(shù)里調(diào)用了一個createMonsters函數(shù),代碼如下:
void MonsterManager::createMonsters(int iCurLevel) {/*加載怪物坐標(biāo)對象*/std::string sMonsterPosPath = StringUtils::format("tollgate/monsterPos_level_%d.plist", iCurLevel);auto posList = PosLoadUtil::getInstance()->loadPosWithFile(sMonsterPosPath.c_str(), enMonsterPos, this, 10, false);m_monsterPosList.pushBack(posList);/*讀取配置文件*/std::string sMonsterConfPath = StringUtils::format("tollgate/monster_level_%d.plist", iCurLevel);ValueMap fileDataMap = FileUtils::getInstance()->getValueMapFromFile(sMonsterConfPath.c_str());int size = fileDataMap.size();for (int i=1; i <= size; i++) {Value value = fileDataMap.at(StringUtils::toString(i));ValueMap data = value.asValueMap();/*從怪物出場配置文件讀取怪物ID和出場時間,保存所有怪物到未出場列表*/int id = data["id"].asInt();float fShowTime = data["showTime"].asFloat();auto monster = Monster::createFromCsvFileByID(id);monster->setfShowTime(fShowTime);monster->setVisible(false);/*設(shè)置怪物精靈*/std::string spPath = StringUtils::format("sprite/monster/monster_%d.png", monster->getiModelID());monster->bindSprite(Sprite::create(spPath.c_str()));/*保存怪物對象到怪物列表*/m_monsterList.pushBack(monster);/*保存怪物對象到未出場怪物列表*/m_notShowMonsterList.pushBack(monster);this->addChild(monster);}/*開始檢查是否有新怪物出場*/this->schedule(schedule_selector(MonsterManager::showMonster)); }? ? ? ?函數(shù)的邏輯如下:
? ? ? ?(1) 讀取怪物坐標(biāo)配置文件,保存到列表里,當(dāng)怪物出場時需要這些坐標(biāo)列表,怪物將按照坐標(biāo)列表的位置行走。
? ? ? ?(2) 讀取怪物配置文件,和之前的炮臺坐標(biāo)配置文件、怪物坐標(biāo)配置文件是一樣的規(guī)則,通過FileUtils的getValueMapFromFile就可以加載,內(nèi)容如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"/><plist version="1.0"><dict><key>1</key><dict><key>id</key><integer>1001</integer><key>showTime</key><integer>3</integer></dict><key>2</key><dict><key>id</key><integer>1004</integer><key>showTime</key><integer>5</integer></dict></dict> </plist>? ? ? (3) 在怪物配置文件里配置怪物的ID和出場時間(單位是秒),根據(jù)怪物ID創(chuàng)建Monster對象,為Monster對象的出場時間屬性賦值。
? ? ? (4) 所有創(chuàng)建的Monster對象都添加到怪物列表和未出場怪物列表,未出場怪物列表里記錄了所有還未出現(xiàn)的怪物。
? ? ? (5) 如果某個怪物到達(dá)了出場時間就讓怪物出現(xiàn)并開始按照怪物坐標(biāo)列表行走,同時未出場怪物列表匯總刪除該怪物。
? ? ? (6) 利用schedule每一幀都調(diào)用showMonster函數(shù),該函數(shù)用來控制怪物是否出場。
? ? ? showMonster函數(shù),代碼如下:
void MonsterManager::showMonster(float dt) { int iNotShowMonsterCount = m_notShowMonsterList.size();if (iNotShowMonsterCount > 0) {m_fShowTimeCount += dt;}/*獲取怪物的第一個坐標(biāo)點*/auto monsterFirstPos = getMonsterStartPos();Vector<Monster *> monsterWantToDelete;for (auto monster : m_notShowMonsterList) {/*時間達(dá)到怪物的出場時間,讓怪物出場*/if (m_fShowTimeCount >= monster->getfShowTime()) {/*添加怪物到刪除列表,出場后的怪物要從未出場列表中刪除*/monsterWantToDelete.pushBack(monster);monster->setPosition(monsterFirstPos->getPos());monster->setVisible(true);/*讓怪物按照指定坐標(biāo)行走*/ monster->moveByPosList(m_monsterPosList);}}/*刪除已經(jīng)出場的怪物*/for (auto monster : monsterWantToDelete) {m_notShowMonsterList.eraseObject(monster);} }? ? ? ? ? 然后就是遍歷未出場怪物列表,判斷m_fShowTimeCount是否達(dá)到或超過了某個怪物的出場時間,如果是,則讓怪物出場。
? ?15.9 移動控制器1---控制器基類
? ? ? ? ? 創(chuàng)建一個控制器基類,命名為ControllerBase,代碼如下:
class ControllerBase : public Node { };? ?15.10 移動控制器2---移動控制器基類
? ? ? ?ControllerMoveBase基類,頭文件如下:
/*檢查移動的間隔時間,時間越短,移動越短,也越平滑,時間太長,移動會卡頓*/ #define CHECK_MOVE_SPEED_LVL1 0.1f #define CHECK_MOVE_SPEED_LVL2 0.04f #define CHECK_MOVE_SPEED_LVL3 0.03f#define SPEED 1class ControllerMoveBase : public ControllerBase { public:ControllerMoveBase();~ControllerMoveBase();CC_SYNTHESIZE(int, m_iSpeed, iSpeed); //移動速度 protected:Entity *m_entity; //實體對象bool m_isMoving; //是否正在移動bool m_isXLeft; //標(biāo)記X方向是否往左移bool m_isYUp; //標(biāo)記Y方向是否往上移int m_iCheckMoveSpeed; //檢查移動的間隔時間,時間越短,移動越快。/*給定當(dāng)前坐標(biāo)和目標(biāo)坐標(biāo),計算出下一次要設(shè)置的坐標(biāo)*/Point getNextPos(Point curPos, Point destPos); };? ? ? ? ?看看ControllMoveBase.cpp文件,代碼如下:
ControllerMoveBase::ControllerMoveBase() {m_isMoving = false;m_isXLeft = false;m_isYUp = false;m_iSpeed = SPEED;m_iCheckMoveSpeed = CHECK_MOVE_SPEED_LVL2;m_entity = NULL; }ControllerMoveBase::~ControllerMoveBase() { }Point ControllerMoveBase::getNextPos(Point curPos, Point destPos) {/*判斷移動方向*/if (curPos.x > destPos.x) {m_isXLeft = true;}else {m_isXLeft = false;}if (curPos.y < destPos.y) {m_isYUp = true;}else {m_isYUp = false;}/*根據(jù)移動方向和速度值改變當(dāng)前坐標(biāo)*/if (curPos.x < destPos.x && m_isXLeft == false) {curPos.x += m_iSpeed;if (curPos.x > destPos.x) {curPos.x = destPos.x;}} else if (curPos.x > destPos.x && m_isXLeft == true) {curPos.x -= m_iSpeed;if (curPos.x < destPos.x) {curPos.x = destPos.x;}}if (curPos.y < destPos.y && m_isYUp == true) {curPos.y += m_iSpeed;if (curPos.y > destPos.y) {curPos.y = destPos.y;}}else if (curPos.y > destPos.y && m_isYUp == false) {curPos.y -= m_iSpeed;if (curPos.y < destPos.y) {curPos.y = destPos.y;}}return curPos; }? ? ?getNextPos函數(shù)根據(jù)將要移動的方向設(shè)置下一步的坐標(biāo)。
? ?15.11 移動控制器3---按指定坐標(biāo)列表移動
? ? ?ControllerSimpleMove頭文件,代碼如下:
class ControllerSimpleMove : public ControllerMoveBase { public:ControllerSimpleMove();~ControllerSimpleMove();static ControllerSimpleMove *create(Entity *entity);bool init(Entity *entity);void checkMoveUpdate(float delta);/*按照給定的坐標(biāo)點移動*/void moveByPosList(Vector<PosBase *> posList);/*按照給定的坐標(biāo)點和移動速度*/void moveByPosList(Vector<PosBase *> posList, int iSpeed);/*根據(jù)給定的坐標(biāo)點、速度、移動間隔時間移動*/void moveByPosList(Vector<PosBase *> posList, int iSpeed, int iSpanTime);/*根據(jù)給定坐標(biāo)移動*/void moveByPos(PosBase *pos); private:void moveOneStep(); /*移動一步*/void nextMovePos(); /*設(shè)置下一個移動目的點*/ private:Vector<PosBase *> m_movePosList; //移動目的列表PosBase *m_curDestPos; //當(dāng)前移動目的地float m_MoveSpan; //移動間隔時間float m_fMoveTimeCount; //計時器 };? ? ? ?看看nextMovePos函數(shù)代碼如下:
void ControllerSimpleMove::nextMovePos() {if (m_movePosList.size() < 1) {return;}m_curDestPos = m_movePosList.at(0);m_movePosList.erase(0); }? ? ? ?nextMovePos函數(shù)的邏輯如下:
- ? ? 判斷移動列表中是否已經(jīng)沒有了坐標(biāo)對象;
 - ? ? 取得移動列表中的第一個坐標(biāo)對象;
 - ? ? 刪除坐標(biāo)列表中已經(jīng)取出來的對象。
 
? ? ? ?moveByPosLis函數(shù),代碼如下:
void ControllerSimpleMove::moveByPosList(Vector<PosBase *> posList) {if (posList.size() < 1) {return;}this->m_movePosList.clear();this->m_movePosList.pushBack(posList);nextMovePos();this->m_isMoving = true; }? ? ? ?看剩余的兩個moveByPosList函數(shù),代碼如下:
void ControllerSimpleMove::moveByPosList(Vector<PosBase *> posList, int iSpeed) {this->m_iSpeed = iSpeed;moveByPosList(posList); }void ControllerSimpleMove::moveByPosList(Vector<PosBase *>posList, int iSpeed, int iSpanTime) {m_MoveSpan = iSpanTime;moveByPosList(posList, iSpeed); }? ? ? 再看看moveByPos函數(shù),代碼如下:
void ControllerSimpleMove::moveByPos(PosBase *pos) {if (m_isMoving == false && pos != NULL) {Vector<PosBase *> posList;posList.pushBack(pos);moveByPosList(posList);} }? ? 15.12 怪物來了
? ? ? ? 先看頭文件,代碼如下:
class Monster : public Entity { public:Monster();~Monster();CREATE_FUNC(Monster);virtual bool init();/*給定怪物ID,從配置文件中讀取怪物數(shù)據(jù)*/static Monster *createFromCsvFileByID(int iMonsterID);bool initFromCsvFileByID(int iMonsterID); public:/*按照給定的坐標(biāo)點移動*/void moveByPosList(Vector<PosBase *> posList; private:ControllerSimpleMove *m_moveController; //移動控制器CC_SYNTHESIZE(int, m_iLevel, iLevel); //等級CC_SYNTHESIZE(float, m_fShowTime, fShowTime); //出場間隔:秒 };? ? ? ?Monster.cpp文件,代碼如下:
Monster::Monster() {m_moveController = NULL;m_iSpeed = MONSTER_SPEED_INIT; }Monster::~Monster() { }bool Monster::init() {m_moveController = ControllerSimpleMove::create(this);this->addChild(m_moveController);return true; }Monster *Monster::createFromCsvFileByID(int iMonsterID)) {Monster *monster = new Monster();if (monster && monster->initFromCsvFileByID(iMonsterID)) {monster->autorelease();}else {CC_SAFE_DELETE(monster);}return monster; }bool Monster::initFromCsvFileByID(int iMonsterID) {bool bRet = false;do {CsvUtil *csvUtil = CsvUtil::getInstance();std::string sMonsterID = StringUtils::format("%d", iMonsterID);/*尋找ID所在的行*/int iLine = csvUtil->findValueInWithLine(sMonsterID.c_str(), enMonsterPropConf_ID, PATH_CSV_MONSTER_CONF);CC_BREAK_IF(iLine < 0);setID(iMonsterID);setiLevel(csvUtil->getInt(iLine, enMonsterPropConf_Level, PATH_CSV_MONSTER_CONF));setiModelID(csvUtil->getInt(iLine, enMonsterPropConf_ModelID, PATH_CSV_MONSTER_CONF));setiDefense(csvUtil->getInt(iLine, enMonsterPropConf_Defense, PATH_CSV_MONSTER_CONF));setiHP(csvUtil->getInt(iLine, enMonsterPropConf_Hp, PATH_CSV_MONSTER_CONF));setiSpeed(csvUtil->getInt(iLine, enMonsterPropConf_Speed, PATH_CSV_MONSTER_CONF));CC_BREAK_IF(!init());bRet = true;} while(0);return bRet; }void Monster::moveByPosList(Vector<PosBase *> posList) {if (posList.size() < 1) {return;}m_moveController->moveByPosList(posList, 2, getiSpeed()); }}? ? ?
? ? 新增一個怪物屬性的枚舉類,EnumMonsterPropConfType.h,代碼如下:
enum EnumMonsterPropConfType {enMOnsterPropConf_ID, //怪物IDenMonsterPropConf_Name, //怪物名字enMonsterPropConf_Level, //怪物等級enMonsterPropConf_Type, //怪物類型enMonsterPropConf_ModelID, //怪物模型IDenMonsterPropConf_Defence, //防御力enMonsterPropConf_Hp, //血量enMonsterPropConf_Speed, //移動速度 };?
//TollgateMapLayer.h里新增成員變量: /*怪物管理器*/ MonsterManager *m_monsterMgr;//TollgateMapLayer的init函數(shù)中添加以下代碼: /*創(chuàng)建怪物管理器*/ m_monsterMgr = MonsterManager::createWithLevel(m_iCurLevel); this->addChild(m_monsterMgr, MONSTER_LAYER_LVL);? ? ? 運行游戲,效果如下:
? ?
? ? 15.13 英雄攻擊
? ? ? ?我們?yōu)镠ero新增一個函數(shù):checkAtkMonster,代碼如下:
void Hero::checkAtkMonster(float dt, Vector<Monster *> monsterList) {if (m_atkMonster != NULL) {/*怪物已死亡*/if (m_atkMonster->isDead()) {/*從怪物列表中刪除怪物*/monsterList.eraseObject(m_atkMonster);/*清除鎖定的怪物引用*/m_atkMonster = NULL;return;}/*攻擊冷卻結(jié)束,可以攻擊*/if (m_isAtkCoolDown == false) {atk(); }/*判斷怪物是否離開攻擊范圍*/checkAimIsOutOfRange(monsterList);} else {/*選擇一個進(jìn)入攻擊范圍的怪物*/chooseAim(monsterList);} }? ? ? ? ?checkAtkMonster函數(shù)的邏輯如下:
? ? ? ? (1) 首先想辦法獲取關(guān)卡地圖層(TollgateMapLayer)里的所有出場怪物。
? ? ? ? (2) m_atkMonster記錄了英雄當(dāng)前鎖定的攻擊目標(biāo);
? ? ? ? (3) 如果攻擊目標(biāo)為空,則調(diào)用chooseAim函數(shù),從怪物列表匯總查找進(jìn)入了攻擊范圍的英雄,設(shè)為攻擊目標(biāo)。
? ? ? ? (4) 如果攻擊目標(biāo)不為空,判斷目標(biāo)是否死亡,如果目標(biāo)已死亡,則從怪物列表中刪除這個怪物,并且將英雄攻擊目標(biāo)置為空,本次攻擊結(jié)束。
? ? ? ? (5) 如果攻擊目標(biāo)不為空,且英雄的攻擊冷卻結(jié)束,則調(diào)用atk函數(shù)進(jìn)行攻擊。
? ? ? ? (6) 判斷攻擊目標(biāo)是否離開了英雄的攻擊范圍,調(diào)用checkAimIsOutOfRange函數(shù)進(jìn)行判斷,并且進(jìn)行一些處理。
? ? ? ? ? 我們至少完成以上6件事情,英雄才能攻擊怪物。
? ?15.14 在Hero里獲取怪物列表
? ? ? ?給TollgateMapLayer添加一個update函數(shù),代碼如下:
bool TollgateMapLayer::init() {/*這里省略了很多代碼*/this->schedule(schedule_selector(TollgateMapLayer::logic));return true; }void TollgateMapLayer::logic(float dt) {m_heroMgr->logic(dt, m_monsterMgr->getMonsterList()); }? ? ? ? ?給HeroManager也添加一個logic函數(shù),代碼如下:
void HeroManager::logic(float dt, Vector<Monster *> monsterList) {for (auto tBorder : m_towerBorderList) {if (tBorder->getHero() != NULL) {tBorder->getHero()->checkAtkMonster(dt, monsterList);}} }? ? ? ?經(jīng)過TollgateMapLayer調(diào)用HeroManager的logic函數(shù),再有HeroManager的logic函數(shù)調(diào)用Hero的checkAtkMonster,這就把怪物列表傳遞給了Hero。
? ? ? ?整個場景只調(diào)用一次schedule(schedule_selector(Layer::logic))操作,然后通過一個logic函數(shù)去調(diào)用其他Layer的logic函數(shù),而不是每個Layer都分別注冊一次schedule。
? ?15.15 查找并鎖定攻擊目標(biāo)
? ? ? 為Hero新增一個函數(shù):chooseAim,該函數(shù)用于查找并鎖定攻擊目標(biāo),代碼如下:
void Hero::chooseAim(Vector<Monster *> monsterList) {for (auto monster : monsterList) {if (monster->isVisible() && isInAtkRange(monster->getPosition())) {chooseAtkMonster(monster);log("InAtkRange!!!");break;}} }? ? ? ?chooseAim函數(shù)會遍歷怪物列表,判斷每一個怪物是否進(jìn)入了英雄的攻擊范圍(isInAtkRange函數(shù)),如果是,則鎖定攻擊目標(biāo)(chooseAtkMonster)。代碼如下:
bool Hero::isInAtkRange(Point pos) {int iDoubleAtkRange = getiAtkRange(); //攻擊范圍Point heroPos = getPosition();float length = pos.getDistanceSq(heroPos);if (length <= iDoubleAtkRange * iDoubleAtkRange) {return true;}return false; }void Hero::chooseAtkMonster(Monster *monster) {m_atkMonster = monster; }? ? ? ?然后,我們要用一個m_atkMonster記錄攻擊目標(biāo),所以要在Hero.h文件新增一個成員變量,代碼如下:
/*攻擊是否冷卻*/ bool m_isAtkCoolDown;/*當(dāng)前鎖定的怪物*/ Monster *m_atkMonster;/*檢測并選擇進(jìn)入攻擊范圍的怪物,或者攻擊已進(jìn)入范圍的怪物*/ void checkAtkMonster(float ft);/*判斷坐標(biāo)是否在攻擊范圍內(nèi)*/ bool isInAtkRange(Point pos);/*鎖定要攻擊的怪物*/ void chooseAtkMonster(Monster *monster);? ? 15.16 英雄的攻擊
? ? ? 完善Hero的checkAtkMonster函數(shù),代碼如下:
void Hero::checkAtkMonster(float ft, Vector<Monster *> monsterList) {if (m_atkMonster != NULL) {/*怪物死亡*/if (m_atkMonster->isDead()) {/*從怪物列表中刪除怪物(省略代碼)*//*清除鎖定的怪物引用(省略代碼)*/return;}/*攻擊冷卻結(jié)束,可以攻擊*/if (m_isAtkCoolDown == false) {atk();}/*判斷怪物是否離開攻擊范圍*/checkAimIsOutOfRange(monsterList);}else {/*選擇一個進(jìn)入攻擊范圍的怪物(省略代碼)*/} }? ? ? ?來看看atk函數(shù),代碼如下:
void Hero::atk() {log("Atk!!!");/*標(biāo)記攻擊冷卻*/m_isAtkCoolDown = true;/*英雄攻擊有間隔時間,指定若干秒后恢復(fù)攻擊*/this->scheduleOnce(schedule_selector(Hero::atkCollDownEnd), getiAtkSpeed()/1000.0f); }void Hero::atkCollDownEnd(float dt) {m_isAtkCoolDown = false; }? ? ? ? ?再看一下checkAimIsOutOfRange函數(shù),代碼如下:
void Hero::checkAimIsOutOfRange(Vector<Monster *> monsterList) {if (m_atkMonster != NULL) {if (isInAtkRange(m_atkMonster->getPosition()) == false) {missAtkMonser();}} }void Hero::missAtkMonster() {log("Out Of Range!!!");m_atkMonster = NULL; }? ? ? ? ?最后在Hero的構(gòu)造函數(shù)里做一些初始化操作,代碼如下:
Hero::Hero() {m_atkMonster = NULL;m_isAtkCoolDown = false; }? ? ? ?調(diào)試結(jié)果如下:
? ??
? ? ?15.17 子彈管理器1---子彈類
? ? ? ? BulletBase.h文件:
class BulletBase : public Entity { public:BulletBase();~BulletBase();void lockAim(Entity *entity); /*鎖定攻擊目標(biāo)*/Entity *getAim(); /*獲取攻擊目標(biāo)*/bool isArrive(); /*是否到達(dá)目標(biāo)*//*是否正在使用*/void setUsed(bool isUsed);bool isUsed(); protected:/*鎖定攻擊目標(biāo)時調(diào)用,留給子類做處理*/virtual void onLockAim(Entity *aim) = 0;bool m_isArrive; //是否達(dá)到了攻擊目標(biāo)(是否攻擊了目標(biāo)) private:bool m_isUsed; //標(biāo)記是否已經(jīng)在使用中Entity *m_aim; //攻擊目標(biāo)CC_SYNTHESIZE(int, m_iAtkValue, iAtkValue); //攻擊力CC_SYNTHESIZE(int, m_iSpeed, iSpeed); //速度 };? ? ? ? ?子彈主要有攻擊力和速度兩個屬性,提供給外部調(diào)用的功能也有兩個,一個是鎖定攻擊目標(biāo),一個是設(shè)置子彈是否在使用中。
? ? ? ? BulletBase.cpp文件,代碼如下:
BulletBase::BulletBase() {m_isUsed = false;m_aim = NULL;m_iSpeed = SPEED_DEFAULT;m_iAtkValue = 1; } BulletBase::~BulletBase() { }void BulletBase::setUsed(bool isUsed) {this->m_isUsed = isUsed;setVisible(isUsed); }bool BulletBase::isUsed() {return this->m_isUsed; } void BulletBase::lockAim(Entity *entity) {if (entity != NULL) {m_aim = entity;onLockAim(m_aim);} }Entity *BulletBase::getAim() {return this->m_aim; }bool BulletBase::isArrive() {return m_isArrive; }? ? ? ? 開始創(chuàng)建第一種類型的子彈---普通子彈。NormalBullet,頭文件如下:
class BulletNormal : public BulletBase { public:BulletNormal();~BulletNormal();CREATE_FUNC(BulletNormal);virtual bool init();static BulletNormal *create(Sprite *sprite);bool init(Sprite *sprite); protected:virtual void onLockAim(Entity *aim); private:void normal(); };? ? ? ?NormalBullet.cpp文件:
BulletNormal::BulletNormal() {m_iSpeed = SPEED_NORMAL; }BulletNormal::~BulletNormal() { }BulletNormal *BulletNormal::create(Sprite *sprite) {BulletNormal *bulletNor = new BulletNormal();if (bulletNor && bulletNor->init(sprite)) {bulletNor->autorelease();} else {CC_SAFE_DELETE(bulletNor);}return bulletNor; }bool BulletNormal::init(Sprite *sprite) {bindSprite(sprite);return true; }bool BulletNormal::init() {bool bRet = false;do {Sprite *sprite = Sprite::create(PATH_BULLET_NOR);CC_BREAK_IF(!sprite);CC_BREAK_IF(!init(sprite));bRet = true;} while(0);return bRet; }void BulletNormal::onLockAim(Entity *aim) {m_isArrive = false;Point aimWorldPos = aim->getParent()->convertToWorldSpace(aiim->getPosition());Point dstPos = this->getParent()->convertToNodeSpace(aimWorldPos);auto moveTo = MoveTo::create(0.5f, dstPos);auto callFunc = CallFunc::create([&](){moveEnd();});auto actions = Sequence::create(moveTo, callFunc, NULL);this->runAction(actions); }void BulletNormal::moveEnd() {m_isArrive = true; }? ? ?15.18 子彈管理器2---子彈管理器
? ? ? ? BulletManager.h文件:
#define BULLET_MAX_CACHE_NUM 10 //子彈緩存最大數(shù)量class BulletBase; class BulletManager : public Node { public:BulletManager();~BulletManager();static BulletManager *create();bool init();/*從緩存中獲取一個未被使用的子彈*/BulletBase *getAnyUnUsedBullet(); private:Vector<BulletBase *> m_bulletList; /*子彈列表*/void createBullets(Node *parent); /*創(chuàng)建緩存子彈*/void bulletLogicCheck(float dt); /*子彈邏輯*/ };? ? ? 看一下BulletManager比較簡單的幾個函數(shù),代碼如下;
BulletManager::BulletManager() { }BulletManager::~BulletManager() { }BulletManager *BulletManager::create() {BulletManager *bulletMgr = new BulletManager();if (bulletMgr && bulletMgr->init()) {bulletMgr->autorelease();}else {CC_SAFE_DELETE(bulletMgr);}return bulletMgr; }bool BulletManager::init() {/*創(chuàng)建子彈對象*/createBullets(parent);/*循環(huán)檢測子彈邏輯*/this->schedule(schedule_selector(BulletManager::bulletLogicCheck), BULLET_LOGIC_CHECK_INTERVAL);return true; }? ? ? ? 來看createBullets函數(shù),代碼如下:
void BulletManager::createBullets(Node *parent) {BulletBase *bullet = NULL;for (int i=0; i<BULLET_MAX_CACHE_NUM; i++) {bullet = BulletNormal::create();bullet->setUsed(false);m_bulletList.pushBack(bullet);this->addChild(bullet);} }? ? ? ? 看看bulletLogicCheck函數(shù),這個函數(shù)用于控制所有子彈的行為,代碼如下:
void BulletManager::bulletLogicCheck(float dt) {for (auto bullet : m_bulletList) {if (bullet->isUsed()) {auto aim = bullet->getAim();if (aim != NULL) {/*判斷子彈是否到達(dá)了目標(biāo)處,如是的,則傷害目標(biāo)*/if(bullet->isArrive()) {aim->hurtMe(bullet->getiAtkValue());/*子彈攻擊目標(biāo)后,重置為未使用狀態(tài)*/bullet->setUsed(false);}}}} }? ? ? ?一個很重要的函數(shù)---getAnyUnUsedBullet函數(shù),從子彈列表中查找狀態(tài)為未使用的子彈,用于英雄攻擊時從子彈管理器中獲取子彈對象,代碼如下:
BulletBase *BulletManager::getAnyUnUsedBullet() {for (auto bullet : m_bulletList) {if (bullet->isUsed() == false) {bullet->setUsed(true); return bullet;}}return NULL; }? ? ? ?15.19 子彈管理器3---英雄開始發(fā)射子彈
? ? ? ? ?為了Hero添加一個成員變量,代碼如下:
/*子彈管理類*/ BulletManager *m_bulletMgr;? ? ? ?修改Hero的init函數(shù),代碼如下:
bool Hero::init(Sprite *sprite) {bool bRet = false;do {CC_BREAK_IF(!sprite);bindSprite(sprite);/*創(chuàng)建子彈管理器*/m_bulletMgr = BulletManager::create();this->addChild(m_bulletMgr);bRet = true;} while(0);return bRet; }? ? ? ?最后,修改Hero的atk函數(shù),代碼如下:
void Hero::atk() {/*從子彈管理器中取出一個未被使用的子彈對象*/BulletBase *bullet = m_bulletMgr->getAnyUnUsedBullet();if (bullet != NULL) {/*根據(jù)英雄情況設(shè)置子彈屬性,鎖定攻擊目標(biāo)*/Point heroWorldPos = this->getParent()->convertToWorldSpace(getPosition());bullet->setPosition(bullet->getParent()->convertToNodeSpace(heroWorldPos));bullet->setiAtkValue(getiCurAtk());bullet->lockAim(m_atkMonster);/*標(biāo)記攻擊冷卻*/m_isAtkCoolDown = true;/*英雄攻擊有間隔時間,指定若干秒后恢復(fù)攻擊*/this->scheduleOnce(schedule_selector(Hero::atkCollDownEnd), getiAtkSpeed()/1000.0f);} }? ? ? ? 我們把打印日志的地方修改為從子彈管理器中獲取一個子彈對象,然后賦予子彈屬性,鎖定攻擊目標(biāo),于是,子彈就開始飛向怪物了。
? ? ? ? 運行項目,將看到如下效果:
? ? ??
? ? 15.20 怪物血量條
? ? ? ? 給Monster新增一些東西,代碼如下:
#include "editor-support/cocostudio/CCSGUIReader.h" #include "cocos-ext.h" #include "ui/CocosGUI.h" using namespace cocos2d::ui; using namespace cocostudio; USING_NS_CC_EXT;class Monster : public Entity {/*省略了很多代碼*/protected:virtual void onDead() override;virtual void onBindSprite() override;virtual void onHurt(int iHurtValue) override; private:LoadingBar *m_hpBar;int m_iMaxHP; };? ? ? 新增函數(shù)代碼如下:
void Monster::onDead() {this->removeFromParent(); } void Monster::onBindSprite() {/*創(chuàng)建血量條控件*/auto UI = cocostudio::GUIReader::getInstance()->widgetFromJsonFile("HpBar/HpBar_1.ExportJson");this->addChild(UI);/*設(shè)置坐標(biāo)*/Size size = this->getContentSize();UI->setPosition(Point(size.width * 0.5f, size.height));/*設(shè)置坐標(biāo)*/Size size = this->getContentSize();UI->setPosition(Point(size.width * 0.5f, size.height));/*獲取進(jìn)度條控件*/m_hpBar = (LoadingBar *)Helper::seekWidgetByName(UI, "hpbar");m_hpBar->setPercent(100);/*記錄初始血量*/m_iMaxHP = getiHP(); }void Monster::onHurt(int iHurtValue) {m_hpBar->setPercent(getiHP() / (float)m_iMaxHP * 100); }? ? ?onDead函數(shù)在怪物死亡時被調(diào)用,主動將自己從游戲里刪除。
? ? ?運行游戲,看到血量條出現(xiàn)了:
? ??
? ? 15.21 炮臺操作按鈕----英雄華麗升級
? ? ? ?首先,給TowerBorder新增兩個函數(shù),用于控制操作按鈕的出現(xiàn)和隱藏,代碼如下:
class TowerBorder : public Entity { public:/*省略了很多代碼*/void showTowerOprBtns(); /*顯示操作按鈕*/void deleteOprBtns(); /*刪除操作按鈕*/ };? ? ? 看看showTowerOprBtns函數(shù),代碼如下:
void TowerBorder::showTowerOprBtns() {if (m_isOprBtnsShow == true) {/*已經(jīng)是顯示狀態(tài)*/return;}if (m_cancelBtn == NULL) {/*手動創(chuàng)建一個按鈕*/auto heroOprBtn = Button::create();heroOprBtn->loadTextureNormal("button2.png");/*用clone函數(shù)復(fù)制三個按鈕對象*/m_cancelBtn = (Button *)heroOprBtn->clone();m_deleteBtn = (Button *)heroOprBtn->clone();m_upgradeBtn = (Button *)heroOprBtn->clone();/*初始化按鈕位置*/resetOprBtns();m_cancelBtn->setTitleText("cancel");m_deleteBtn->setTitleText("delete");m_upgradeBtn->setTitleText("upgrade");m_cancelBtn->addTouchEventListener(this, toucheventselector(TowerBorder::cancelClick));m_deleteBtn->addTouchEventListener(this, toucheventselector(TowerBorder::deleteClick));m_upgradeBtn->addTouchEventListener(this, toucheventselector(TowerBorder::upgradeClick));this->getParent()->addChild(m_cancelBtn, 999);this->getParent()->addChild(m_deleteBtn, 999);this->getParent()->addChild(m_upgradeBtn, 999);}m_cancelBtn->setEnabled(true);m_deleteBtn->setEnabled(true);m_upgradeBtn->setEnabled(true);/*按鈕出場特效*/m_cancelBtn->runAction(EaseBounceOut::create(MoveBy::create(0.5f, Point(0, 100))));m_deleteBtn->runAction(EaseBounceOut::create(MoveBy::create(0.5f, Point(-100, 0))));m_upgradeBtn->runAction(EaseBounceBtn::create(MoveBy::create(0.5f, Point(100, 0))));m_isOprBtnShow = true; }? ? ? ?步驟解釋:
? ? ? ?(1)首先判斷操作按鈕是否已經(jīng)在顯示,如是,就不做任何操作。
? ? ? ?(2)我們有3個操作按鈕,分別是取消、刪除和升級。第一次顯示操作按鈕時,需要先創(chuàng)建這三個按鈕控件,并做初始化。創(chuàng)建按鈕控件時,筆者使用了clone函數(shù),這個函數(shù)可以直接從現(xiàn)有的對象復(fù)制一份出來,當(dāng)成新的對象來使用。這樣可以避免重新創(chuàng)建,在一定程度上可以提高效率。
? ? ? ? (3) 創(chuàng)建完按鈕,分別監(jiān)聽按鈕的單擊事件。
? ? ? ? (4) 給按鈕執(zhí)行動作,讓按鈕出廠時沒那么生硬。EaseBounceOut是一個預(yù)定的Action,它給讓其他動作賦予回彈的效果。
? ? ? ? ? ?三個操作按鈕并不是添加到炮臺(TowerBorder)身上,而是添加到跑跳的父節(jié)點上,這樣可以避免操作按鈕被其他對象遮擋(比如英雄),體驗好一些。
? ? ? ? showTowerOprBtns函數(shù)里涉及很多新的成員變量和函數(shù),代碼如下:
private:/*操作按鈕UI*/Button *m_cancelBtn;Button *m_deleteBtn;Button *m_upgradeBtn;bool m_isOprBtnsShow;/*恢復(fù)操作按鈕的默認(rèn)位置*/void resetOprBtns();void cancelClick(Ref *target, TouchEventType type);void deleteClick(Ref *target, TouchEventType type);void upgradeClick(Ref *target, TouchEventType type);? ? ? ? ?resetOprBtns比較簡單,代碼如下;
void TowerBorder::resetOprBtns() {/*讓按鈕恢復(fù)到中點的位置*/Point pos = this->getPosition();m_cancelBtn->setPosition(pos);m_deleteBtn->setPosition(pos);m_upgradeBtn->setPosition(pos); }? ? ? ? resetOprBtns會在關(guān)閉操作按鈕時被調(diào)用,也就是在deleteOprBtns函數(shù)中調(diào)用,代碼如下:
void TowerBorder::deleteOprBtns() {if (m_cancelBtn != NULL) {m_cancelBtn->setEnabled(false);m_deleteBtn->setEnabled(false);m_upgradeBtn->setEnabled(false);resetOprBtns();}m_isOprBtnsShow = false; }? ? ? 同時,m_isOprBtnsShow用于標(biāo)記操作按鈕的顯示狀態(tài)。
? ? ? 最后,就剩下三個按鈕單擊時所觸發(fā)的函數(shù)了,代碼如下:
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函數(shù)就是關(guān)閉操作按鈕,直接調(diào)用deleteOprBtns函數(shù)即可。
? ? ? ? deleteClick函數(shù)是刪除炮臺上的英雄,先調(diào)用deleteHero函數(shù)解決炮臺對Hero的綁定,然后關(guān)閉操作按鈕。
? ? ? ? upgradeClick函數(shù)用于升級英雄,直接調(diào)用Hero的upgrade函數(shù),然后關(guān)閉操作按鈕。
? ? ? ? TowerBorder還有最后一個地方要修改,在構(gòu)造函數(shù)里對成員變量做一些初始化,代碼所示:
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) {/*加載塔坐標(biāo)對象(省略)*//*創(chuàng)建炮臺(省略)*//*添加觸摸監(jiān)聽(省略)*/listener->onTouchEnded = [&](Touch *touch, Event *event) {/*省略了一些代碼*//*當(dāng)前塔坐標(biāo)沒有英雄對象,則添加英雄*/if (clickBorder->getHero() == NULL) {}/*綁定英雄對象到炮臺(省略)*/}else {clickBorder->showTowerOprBtns(); /*顯示炮臺操作按鈕*/}};return true; }? ? ? ?給英雄增加upgrade函數(shù):
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()); }? ? ? ? 運行游戲,單擊英雄,然后進(jìn)行各種操作,如下:
? ? ?
? ?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函數(shù)創(chuàng)建的就是怪物的終點,怪物的終點也就是玩家的堡壘,createStartPoint函數(shù)負(fù)責(zé)創(chuàng)建怪物的起點。怪物的起點和終點其實就是獲取怪物坐標(biāo)列表的第一個和最后一個坐標(biāo),然后創(chuàng)建一個精靈,賦予一些Action動作讓它看起來更華麗。
? ? ? 最后,在TollgateMapLayer的init函數(shù)的最后添加兩行代碼,代碼如下:
/*創(chuàng)建起始點*/ createStartPoint();/*創(chuàng)建終點*/ createEndPoint();? ? 效果如下:
? ??
? ?
16.1 關(guān)卡信息UI
? ? ?新建一個Layer,用來處理關(guān)卡信息的邏輯,命名為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函數(shù),代碼如下:
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 關(guān)卡信息數(shù)據(jù)刷新---NotificationCenter的應(yīng)用
? ? 我們先為TollgateDataLayer.h添加三個函數(shù),代碼如下:
void recvRefreshTowerSoulNum(Ref *pData); void recvRefreshMonsterNum(Ref *pData); void recvRefreshMagicNum(Ref *pData);int m_iTowerSoulNum; /*塔魂數(shù)量*/ int m_iMonsterNum; /*怪物數(shù)量*/ int m_iMagicNum; /*魔力數(shù)量*/Text *m_towerSoulLab; /*塔魂標(biāo)簽*/ Text *m_monsterLab; /*怪物標(biāo)簽*/ Text *m_magicLab; /*魔力標(biāo)簽*/? ? ? 我們要在特定的事件發(fā)生時修改這三種數(shù)據(jù),所以我們要訂閱三種消息,分別對應(yīng)這三種數(shù)據(jù)的刷新,而刷新數(shù)據(jù)就是修改三個信息的標(biāo)簽值。
? ? ? ?修改一下TollgateDataLayer的init函數(shù),代碼如下:
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);/*塔魂標(biāo)簽*/m_towerSoulLab = (Text *)Helper::seekWidgetByName(UI, "towerSoulLab");/*怪物標(biāo)簽*/m_monsterLab = (Text *)Helper::seekWidgetByName(UI, "monsterNumLab");/*魔力標(biāo)簽*/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; }? ? ? 看看這三個消息發(fā)生時的函數(shù),代碼如下:
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; /*塔魂數(shù)量*/m_iMonsterNum = 0; /*怪物數(shù)量*/m_iMagicNum = 0; /*魔力數(shù)量*/ } TollgateDataLayer::~TollgateDataLayer() {NOTIFY->removeAllObservers(this); }? ? ?我們要修改一下TollgataMapLayer,給它添加一個initData函數(shù),代碼如下:
void TollgateMapLayer::initData() {/*初始化塔魂、怪物和魔力數(shù)量*/int iTowerSoulNum = 5;int iMonsterNum = m_monsterMgr->getNotShowMonsterCount(); /*怪物數(shù)量*/int iMagicNum = 100; /*魔力數(shù)量*/NOTIFY->postNotification("TowerSoulChange", (Ref *)iTowerSoulNum);NOTIFY->postNotification("MonsterNumChange", (Ref *)iMonsterNum);NOTIFY->postNotification("MagicChange", (Ref *)iMagicNum); }? ? ?我們在TollgateMapLayer層里發(fā)送信息,改變關(guān)卡信息UI的值。
? ? ? 修改一下TollgateScene的createScene函數(shù),代碼如下:
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 怪物數(shù)量刷新
? ? ?在MonsterManager.cpp的showMonster函數(shù)的最后加上幾句代碼,代碼如下:
/*發(fā)布怪物數(shù)量改變消息*/ int iMonsterNumChange = -monsterWantToDelete.size(); NOTIFY->postNotification("MonsterNumChange", (Ref *)iMonsterNumChange);? ? ?運行游戲,就能看到?jīng)]出現(xiàn)一只怪物,關(guān)卡信息UI上的怪物數(shù)量就會減1,如下:
? ??
16.4 怪物安息---怪物死亡后塔魂數(shù)量刷新
? ? ?我們來刷新塔魂數(shù)量,塔魂也就是我們的金錢,以后英雄升級需要花費塔魂。
? ? ?怪物死亡時會調(diào)用onDead函數(shù)。代碼如下:
void Manager::onDead() {this->removeFrom();/*發(fā)布塔魂增加消息*/int iTowerSoulNumChange = 3 * getiLevel();NOTIFY->postNotification("TowerSoulChange", (Ref *)iTowerSoulNumChange); }? ? ?運行游戲,盡量打死一只怪物試試,效果如下:
? ??
 ? ?
16.5 堡壘安息---怪物到達(dá)堡壘后扣除魔力值
? ? ControllerMoveBase.h文件,代碼如下:
class ControllerMoveBase : public ControllerBase { public:void bindMoveEndFunc(std::function<void()> moveEndFunc); protected:/*用于移動結(jié)束時的回調(diào)*/std::function<void()> m_moveEndFunc; };?
void ControllerMoveBase::bindMoveEndFunc(std::function<void()> moveEndFunc) {m_moveEndFunc = moveEndFunc; }? ?看看ControllerSimpleMove的moveOneStep函數(shù),代碼如下:
void ControllerSimpleMove::moveOneStep() {/*省略了很多代碼*//*到達(dá)當(dāng)前目的地,開始下一個目的地*/if (entityPos.x == curDestPos.x && entityPos.y == curDestPos.y) {if (m_movePosList.size() > 0) {nextMovePos();} else {/*移動結(jié)束*/if (m_moveEndFunc) {m_moveEndFunc();m_moveEndFunc = nullptr;}}} }? ? ?最后,我們還要修改Monster的init函數(shù),代碼如下:
16.6 打怪升級---英雄升級扣除塔魂
? ? ? 我們在英雄升級的時候發(fā)送消息,改變塔魂數(shù)量即可,修改一下Hero的upgrade函數(shù),代碼如下:
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;}/*發(fā)布消息,扣除塔魂*/NOTIFY->postNotification("TowerSoulChange", (Ref *)iCostTowerSoul);/*省略了很多代碼*/ }? ? ? 給TollgateDataLayer新增一個getiTowerSoulNum函數(shù),以便獲取塔魂數(shù)量。getiTowerSoulNum函數(shù)代碼如下:
int TollgateDataLayer::getiMagicNum() {return m_iMagicNum; } int TollgateDataLayer::getiTowerSoulNum() {return m_iTowerSoulNum; }? ? ?運行游戲,然后升級英雄,我們就能看到塔魂的數(shù)量減少了。
16.7 關(guān)卡選擇---根據(jù)關(guān)卡樹加載游戲
? ? 新建一個關(guān)卡選擇場景,頭文件代碼如下:
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); };? ? ?創(chuàng)建的界面比較簡陋,只有三個按鈕,來看看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); }? ? ?關(guān)卡的加載是在TollgateMapLayer層進(jìn)行的,我們在TollgateMapLayer的構(gòu)造函數(shù)里初始化m_iCurLevel的值即可,代碼如下:
TollgateMapLayer::TollgateMapLayer() {m_iCurLevel = GlobalClient::getInstance()->getiCurTollgateLevel(); }? ? ?然后,我們新增了TollgateSelectScene場景,修改SceneManager的changeScene函數(shù),添加一個switch分支,代碼如下:
case en_TollgateSelectScene: /*關(guān)卡選擇場景*/pScene = TollgateSelectScene::scene();break;? ? GlobalClient是一個單例類,存放我們的一些全局?jǐn)?shù)據(jù),方便各個場景或者層之間傳遞數(shù)據(jù),代碼如下:
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函數(shù),代碼如下:
CC_SYNTHESIZE_BOOL(m_isMoveEnd, MoveEnd); //是否達(dá)到目的地? ? ?然后,我們修改一下Monster的init函數(shù),代碼如下:
bool Monster::init() {m_moveController = ControllerSimpleMove::create(this);this->addChild(m_moveController);/*綁定移動結(jié)束回調(diào)函數(shù)*/m_moveController->bindMoveEndFunc([&](){/*發(fā)布魔力值改變消息*/int iMagicChange = -getiLevel() * 10;NOTIFY->postNotification("MagicChange", (Ref *)iMagicChange);m_isMoveEnd = true;});return true; }? ? ?當(dāng)怪物移動結(jié)束時,將m_isMoveEnd標(biāo)記為true,代表怪物達(dá)到目的地。
? ? ?我們?yōu)镸onsterManager新增一個函數(shù),代碼如下:
void MonsterManager::logic(float dt) {Vector<Monster *> monsterWantToDelete;for (auto monster : m_monsterList) {/*從列表中刪除已經(jīng)到達(dá)目的地的怪物,先記錄,后刪除*/if (monster->isMoveEnd() == true) {monsterWantToDelete.pushBack(monster);}/*從列表匯總刪除已經(jīng)死亡的怪物,先記錄,后刪除*/else if (monster->isDead() == true) {monsterWantToDelete.pushBack(monster);}}/*正式刪除的怪物*/for (auto monster : monsterWantToDelete) {monster->removeFromParent();m_monsterList.eraseObject(monster);}if (m_monsterList.size() == 0) {/*怪物數(shù)量為0,發(fā)布怪物全滅消息*/NOTIFY->postNotification("AllMonsterDead");} }? ? ? ?最后,判斷怪物列表中是否還有怪物,如果怪物數(shù)量為0,則發(fā)布一條"AllMonsterDead"消息。
? ? ?在MonsterManager的createMonsters函數(shù)最后添加一句代碼,代碼如下:
this->schedule(schedule_selector(MonsterManager::showMonster)); this->schedule(schedule_selector(MonsterManager::logic));? ? ? 然后,我們要在TollgateDataLayer層處理"AllmonsterDead"消息,為TollgateDataLayer新增一個函數(shù),代碼如下:
void TollgateDataLayer::recvAllMonsterDead(Ref *pData) {if (m_iMagicNum > 0) {SceneManager::getInstance()->changeScene(SceneManager::en_WinScene);} }? ? ? 要接收"AllMonsterDead"消息,就要訂閱消息,我們在TollgateDataLayer的init函數(shù)的最后再訂閱一條消息,代碼如下:
NOTIFY->addObserver(this, callfuncO_selector(TollgateDataLayer::recvAllMonsterDead),"AllMonsterDead", NULL);? ? ? WinScene場景只是一個很簡單的場景,關(guān)鍵的兩個函數(shù),代碼如下:
bool WinScene::init() {if (!Layer::init()) {return false;}Size visibleSize = Director::getInstance()->getVisibleSize();/*添加一個標(biāo)簽*/Label *winLab = Label::create("You Win!", PATH_FONT_PUBLIC, GlobalParam::PublicFontSizeLarge);winLab->setPosition(ccp(visibleSize.width / 2, visibleSize.height / 2));this->addChild(winLab);/*3秒后返回關(guān)卡選擇場景*/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
總結(jié)
以上是生活随笔為你收集整理的《Cocos2d-x3.x游戏开发之旅》学习的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 《深入理解Nginx 模块开发与架构解析
 - 下一篇: qsort()函数详解