3atv精品不卡视频,97人人超碰国产精品最新,中文字幕av一区二区三区人妻少妇,久久久精品波多野结衣,日韩一区二区三区精品

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

《Cocos2d-x3.x游戏开发之旅》学习

發(fā)布時間:2024/1/23 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《Cocos2d-x3.x游戏开发之旅》学习 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

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)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

中国女人内谢69xxxxxa片 | 无码国产乱人伦偷精品视频 | 国产乱人偷精品人妻a片 | 领导边摸边吃奶边做爽在线观看 | 免费无码的av片在线观看 | av香港经典三级级 在线 | 国产香蕉尹人综合在线观看 | 日韩av无码一区二区三区不卡 | 国产精品a成v人在线播放 | 欧美黑人巨大xxxxx | 乌克兰少妇性做爰 | 国产午夜福利100集发布 | 国产成人久久精品流白浆 | 98国产精品综合一区二区三区 | 国产suv精品一区二区五 | 青草视频在线播放 | 中文毛片无遮挡高清免费 | 久热国产vs视频在线观看 | 东京无码熟妇人妻av在线网址 | 午夜嘿嘿嘿影院 | 久久久中文久久久无码 | 国产亲子乱弄免费视频 | 性开放的女人aaa片 | 成人片黄网站色大片免费观看 | 欧美精品无码一区二区三区 | 亚洲精品久久久久久久久久久 | 亚洲精品久久久久中文第一幕 | 中文字幕精品av一区二区五区 | 蜜臀aⅴ国产精品久久久国产老师 | 成人动漫在线观看 | 99riav国产精品视频 | 东京热一精品无码av | 美女毛片一区二区三区四区 | 亚洲精品久久久久avwww潮水 | 国语自产偷拍精品视频偷 | 国产乡下妇女做爰 | 大地资源网第二页免费观看 | 国产人妻精品午夜福利免费 | 搡女人真爽免费视频大全 | 在线成人www免费观看视频 | 少妇被粗大的猛进出69影院 | 亚洲男人av天堂午夜在 | 日本成熟视频免费视频 | 亚洲一区二区三区在线观看网站 | 强开小婷嫩苞又嫩又紧视频 | 欧美日韩久久久精品a片 | 日韩视频 中文字幕 视频一区 | 欧美老人巨大xxxx做受 | 久久国产精品二国产精品 | 亚洲国产精品无码一区二区三区 | 国产精品手机免费 | 欧美freesex黑人又粗又大 | 国产成人午夜福利在线播放 | 樱花草在线播放免费中文 | www一区二区www免费 | 久久综合久久自在自线精品自 | 青青青手机频在线观看 | 风流少妇按摩来高潮 | 久久天天躁夜夜躁狠狠 | 人人妻人人澡人人爽欧美一区九九 | 国产高清av在线播放 | 国产成人一区二区三区别 | 国产精品久久久久久亚洲影视内衣 | 日本丰满熟妇videos | 国产无遮挡又黄又爽免费视频 | 少妇厨房愉情理9仑片视频 | 欧美人与动性行为视频 | 国产精品人人爽人人做我的可爱 | 国内揄拍国内精品少妇国语 | 欧洲欧美人成视频在线 | 欧洲极品少妇 | 欧美老熟妇乱xxxxx | 久久综合给久久狠狠97色 | 成人影院yy111111在线观看 | 国产乱人伦av在线无码 | 一本大道久久东京热无码av | 99久久人妻精品免费二区 | www一区二区www免费 | 牲欲强的熟妇农村老妇女视频 | 免费无码的av片在线观看 | 欧美性生交xxxxx久久久 | 亚洲精品成a人在线观看 | 亚洲啪av永久无码精品放毛片 | а√资源新版在线天堂 | 又黄又爽又色的视频 | 熟妇女人妻丰满少妇中文字幕 | 亚洲午夜久久久影院 | 牲欲强的熟妇农村老妇女 | 国产精品无码一区二区三区不卡 | 精品无人区无码乱码毛片国产 | 无码av最新清无码专区吞精 | 学生妹亚洲一区二区 | 国产精品无码mv在线观看 | 麻豆果冻传媒2021精品传媒一区下载 | 国产精品怡红院永久免费 | 国产精品久久久久9999小说 | 日韩av无码一区二区三区 | aⅴ在线视频男人的天堂 | 男女爱爱好爽视频免费看 | 国产成人无码a区在线观看视频app | 国产成人精品一区二区在线小狼 | 大色综合色综合网站 | 国产尤物精品视频 | 蜜桃臀无码内射一区二区三区 | 久久久久久国产精品无码下载 | 亚洲国产精品久久久天堂 | 亚洲欧美精品aaaaaa片 | 狠狠cao日日穞夜夜穞av | 伊人久久婷婷五月综合97色 | 无码人妻少妇伦在线电影 | 成人女人看片免费视频放人 | 国色天香社区在线视频 | 国产人妖乱国产精品人妖 | 久久亚洲中文字幕无码 | 欧美高清在线精品一区 | 亚洲精品一区二区三区四区五区 | 亚洲の无码国产の无码步美 | 色婷婷久久一区二区三区麻豆 | 老熟妇乱子伦牲交视频 | 国产sm调教视频在线观看 | 亚洲精品国偷拍自产在线麻豆 | 日日碰狠狠躁久久躁蜜桃 | 午夜丰满少妇性开放视频 | 三级4级全黄60分钟 | 88国产精品欧美一区二区三区 | 狠狠躁日日躁夜夜躁2020 | 亚洲欧美日韩国产精品一区二区 | 国产精品亚洲五月天高清 | 亚洲国产日韩a在线播放 | 国产一区二区三区四区五区加勒比 | 日欧一片内射va在线影院 | 九九综合va免费看 | 久久久亚洲欧洲日产国码αv | 国产成人无码午夜视频在线观看 | ass日本丰满熟妇pics | 人妻无码αv中文字幕久久琪琪布 | 久久国产精品偷任你爽任你 | 亚洲精品欧美二区三区中文字幕 | 永久黄网站色视频免费直播 | 久久精品国产日本波多野结衣 | 欧美日本免费一区二区三区 | 亚洲成a人一区二区三区 | 亚洲精品久久久久中文第一幕 | 国产av一区二区精品久久凹凸 | 中文字幕无码av波多野吉衣 | 成人精品视频一区二区三区尤物 | 精品熟女少妇av免费观看 | 日韩人妻少妇一区二区三区 | 日本一区二区三区免费高清 | 亚洲精品午夜国产va久久成人 | 国产一区二区三区影院 | 久激情内射婷内射蜜桃人妖 | 人妻无码久久精品人妻 | 免费视频欧美无人区码 | 亚洲乱亚洲乱妇50p | 高清不卡一区二区三区 | 色婷婷av一区二区三区之红樱桃 | 波多野结衣一区二区三区av免费 | 欧美人与善在线com | 午夜无码人妻av大片色欲 | 特黄特色大片免费播放器图片 | 中文无码成人免费视频在线观看 | 十八禁真人啪啪免费网站 | 日本护士毛茸茸高潮 | 三级4级全黄60分钟 | 中文字幕色婷婷在线视频 | 欧美xxxx黑人又粗又长 | 欧美日本日韩 | 国产综合久久久久鬼色 | 美女极度色诱视频国产 | 国产内射爽爽大片视频社区在线 | 国产高清不卡无码视频 | 青青草原综合久久大伊人精品 | 丰满肥臀大屁股熟妇激情视频 | 中文字幕无码日韩专区 | 午夜精品一区二区三区的区别 | 天天燥日日燥 | 久久精品国产99精品亚洲 | 亚洲国产精品毛片av不卡在线 | 久久午夜无码鲁丝片午夜精品 | 中文字幕av日韩精品一区二区 | 欧美 丝袜 自拍 制服 另类 | 久久久久久av无码免费看大片 | 国产极品美女高潮无套在线观看 | 久久综合久久自在自线精品自 | 色情久久久av熟女人妻网站 | 日本一卡2卡3卡四卡精品网站 | 99国产欧美久久久精品 | 丰满少妇高潮惨叫视频 | 性欧美牲交在线视频 | 无码任你躁久久久久久久 | 国产免费久久久久久无码 | 无码人妻精品一区二区三区下载 | yw尤物av无码国产在线观看 | 亚洲日韩中文字幕在线播放 | 在线a亚洲视频播放在线观看 | 日韩人妻无码中文字幕视频 | 久久人妻内射无码一区三区 | 国产三级久久久精品麻豆三级 | 亚洲中文字幕在线观看 | 久久精品女人的天堂av | 国产三级久久久精品麻豆三级 | 97色伦图片97综合影院 | 亚洲中文字幕久久无码 | 久久久久成人精品免费播放动漫 | 国产在线精品一区二区三区直播 | 久久精品99久久香蕉国产色戒 | 色妞www精品免费视频 | 扒开双腿吃奶呻吟做受视频 | 一本久道高清无码视频 | 国产精品亚洲а∨无码播放麻豆 | 国产精品久久久一区二区三区 | 熟妇人妻无乱码中文字幕 | 未满小14洗澡无码视频网站 | 久久亚洲日韩精品一区二区三区 | 无码国产色欲xxxxx视频 | 又大又硬又黄的免费视频 | 国产av人人夜夜澡人人爽麻豆 | 国产高潮视频在线观看 | 未满小14洗澡无码视频网站 | 四虎国产精品免费久久 | 欧洲精品码一区二区三区免费看 | 97夜夜澡人人爽人人喊中国片 | 国产精品毛片一区二区 | 精品人人妻人人澡人人爽人人 | 老子影院午夜伦不卡 | 久精品国产欧美亚洲色aⅴ大片 | 亚洲人交乣女bbw | 高清不卡一区二区三区 | 国产精品亚洲综合色区韩国 | 午夜精品久久久内射近拍高清 | 国产麻豆精品一区二区三区v视界 | 久9re热视频这里只有精品 | 国产精品人人爽人人做我的可爱 | 丝袜 中出 制服 人妻 美腿 | 国产一区二区三区四区五区加勒比 | 国产在线精品一区二区三区直播 | 国产精华av午夜在线观看 | 久久久久成人片免费观看蜜芽 | 午夜性刺激在线视频免费 | 国产美女精品一区二区三区 | 国产农村乱对白刺激视频 | 国产综合久久久久鬼色 | 撕开奶罩揉吮奶头视频 | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | 中文字幕 亚洲精品 第1页 | 久久综合九色综合97网 | 欧美日本精品一区二区三区 | 亚洲精品成人福利网站 | 免费视频欧美无人区码 | 在线精品国产一区二区三区 | a在线观看免费网站大全 | 精品国产精品久久一区免费式 | 日本www一道久久久免费榴莲 | 国产熟妇另类久久久久 | 人人澡人摸人人添 | 国产猛烈高潮尖叫视频免费 | 亚洲色偷偷男人的天堂 | 大肉大捧一进一出好爽视频 | 久久久久久久久蜜桃 | 东北女人啪啪对白 | 欧洲欧美人成视频在线 | 秋霞特色aa大片 | 极品尤物被啪到呻吟喷水 | 精品国产一区二区三区四区 | 亚洲精品久久久久中文第一幕 | 精品人妻人人做人人爽 | 玩弄少妇高潮ⅹxxxyw | 久久97精品久久久久久久不卡 | 国产成人精品三级麻豆 | 精品国精品国产自在久国产87 | 免费人成在线视频无码 | 亚洲精品一区三区三区在线观看 | 少妇无码一区二区二三区 | 水蜜桃色314在线观看 | 中文字幕无码视频专区 | 福利一区二区三区视频在线观看 | 亚洲一区av无码专区在线观看 | 亚洲欧美日韩综合久久久 | 精品无码av一区二区三区 | 国产综合色产在线精品 | 亚洲国产精品无码一区二区三区 | 俄罗斯老熟妇色xxxx | 中文精品无码中文字幕无码专区 | 曰本女人与公拘交酡免费视频 | 中文精品无码中文字幕无码专区 | 亚洲国产精品成人久久蜜臀 | 日韩欧美中文字幕在线三区 | 国产精品怡红院永久免费 | 日韩精品成人一区二区三区 | 国产极品视觉盛宴 | 国产精品久久久久久亚洲影视内衣 | 99久久精品无码一区二区毛片 | 久久精品丝袜高跟鞋 | 在线播放亚洲第一字幕 | 亚洲区欧美区综合区自拍区 | 国产精品久久精品三级 | 夜夜夜高潮夜夜爽夜夜爰爰 | 狠狠色色综合网站 | 伊人久久大香线焦av综合影院 | 人妻尝试又大又粗久久 | 免费无码av一区二区 | 日本熟妇人妻xxxxx人hd | 又粗又大又硬毛片免费看 | 小泽玛莉亚一区二区视频在线 | 色诱久久久久综合网ywww | 性欧美牲交xxxxx视频 | 午夜精品一区二区三区的区别 | 国产人妻人伦精品1国产丝袜 | 日日天干夜夜狠狠爱 | 日日躁夜夜躁狠狠躁 | 国产人妻大战黑人第1集 | 国产精品99爱免费视频 | 丰满人妻一区二区三区免费视频 | 美女毛片一区二区三区四区 | 国产午夜精品一区二区三区嫩草 | 中国女人内谢69xxxx | 欧美精品国产综合久久 | 无码国模国产在线观看 | 久久精品国产99久久6动漫 | 青青久在线视频免费观看 | 国产精品办公室沙发 | 国色天香社区在线视频 | 少妇无码av无码专区在线观看 | 亚洲色www成人永久网址 | 国产激情精品一区二区三区 | 久久 国产 尿 小便 嘘嘘 | 未满成年国产在线观看 | 两性色午夜免费视频 | 亚洲欧美精品aaaaaa片 | 中文字幕无码乱人伦 | 俄罗斯老熟妇色xxxx | 色综合久久久无码中文字幕 | 亚洲成在人网站无码天堂 | 亚洲精品一区二区三区在线观看 | 成人精品天堂一区二区三区 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 亚洲 欧美 激情 小说 另类 | 国产成人精品优优av | 人人妻在人人 | 女人被男人躁得好爽免费视频 | 2020久久香蕉国产线看观看 | 无码av中文字幕免费放 | 又大又黄又粗又爽的免费视频 | 精品久久久久久人妻无码中文字幕 | 国产人妻精品午夜福利免费 | 国产又爽又黄又刺激的视频 | 亚洲日本一区二区三区在线 | 国产又粗又硬又大爽黄老大爷视 | 成人三级无码视频在线观看 | 国产无遮挡又黄又爽免费视频 | 狠狠色噜噜狠狠狠7777奇米 | 麻豆国产人妻欲求不满 | 久久亚洲精品中文字幕无男同 | 国产激情无码一区二区app | 亚欧洲精品在线视频免费观看 | 亚洲精品一区国产 | 日本一区二区更新不卡 | 男人和女人高潮免费网站 | 国产真实乱对白精彩久久 | 欧美精品国产综合久久 | 久久婷婷五月综合色国产香蕉 | av香港经典三级级 在线 | 亚洲国产精品一区二区第一页 | av无码电影一区二区三区 | 成人精品视频一区二区 | 性生交片免费无码看人 | 久久99热只有频精品8 | 精品人妻中文字幕有码在线 | 国产在线精品一区二区高清不卡 | 欧美一区二区三区 | 小sao货水好多真紧h无码视频 | 国产精品毛片一区二区 | 国产一区二区三区四区五区加勒比 | 在教室伦流澡到高潮hnp视频 | 中国女人内谢69xxxxxa片 | 又湿又紧又大又爽a视频国产 | 久久99久久99精品中文字幕 | 51国偷自产一区二区三区 | 精品乱子伦一区二区三区 | 色欲人妻aaaaaaa无码 | 性色欲网站人妻丰满中文久久不卡 | 性色av无码免费一区二区三区 | 人人超人人超碰超国产 | 无码精品人妻一区二区三区av | 少妇高潮一区二区三区99 | 国产在线精品一区二区高清不卡 | 亚洲成色在线综合网站 | 亚洲日韩中文字幕在线播放 | 亚洲日韩中文字幕在线播放 | 日本爽爽爽爽爽爽在线观看免 | 无套内射视频囯产 | 亚洲aⅴ无码成人网站国产app | 欧美乱妇无乱码大黄a片 | 免费人成在线观看网站 | 兔费看少妇性l交大片免费 | 成人动漫在线观看 | 大地资源中文第3页 | 精品欧美一区二区三区久久久 | 黑人大群体交免费视频 | 人人妻人人澡人人爽欧美精品 | 精品国产一区av天美传媒 | 欧美自拍另类欧美综合图片区 | 内射老妇bbwx0c0ck | 精品国产一区二区三区av 性色 | 国产午夜亚洲精品不卡下载 | 中国女人内谢69xxxx | 又大又黄又粗又爽的免费视频 | 国产亲子乱弄免费视频 | 欧美日本精品一区二区三区 | 精品国产福利一区二区 | 欧美人与禽猛交狂配 | 亚洲成色在线综合网站 | 十八禁真人啪啪免费网站 | 人人爽人人澡人人人妻 | 精品国产福利一区二区 | 欧美日韩在线亚洲综合国产人 | 性生交大片免费看女人按摩摩 | 久久国产精品偷任你爽任你 | 中文字幕乱码中文乱码51精品 | 国产激情艳情在线看视频 | 亚洲欧美国产精品久久 | 久久综合网欧美色妞网 | 免费无码一区二区三区蜜桃大 | 欧美老妇交乱视频在线观看 | 亚洲成a人片在线观看日本 | 国语自产偷拍精品视频偷 | 55夜色66夜色国产精品视频 | av无码久久久久不卡免费网站 | 麻豆国产人妻欲求不满谁演的 | 国产熟妇另类久久久久 | 国产无遮挡又黄又爽又色 | 四十如虎的丰满熟妇啪啪 | aⅴ在线视频男人的天堂 | 久久精品成人欧美大片 | 久久久久av无码免费网 | 无码毛片视频一区二区本码 | 国产精品va在线观看无码 | 国产成人无码一二三区视频 | 黑森林福利视频导航 | 丰满妇女强制高潮18xxxx | 国产精品二区一区二区aⅴ污介绍 | 欧美性生交xxxxx久久久 | 精品无码国产一区二区三区av | 98国产精品综合一区二区三区 | 久久亚洲日韩精品一区二区三区 | 无码成人精品区在线观看 | 99精品国产综合久久久久五月天 | 人妻少妇精品无码专区二区 | 国产成人一区二区三区在线观看 | 国产精品久久久久久久影院 | 大肉大捧一进一出好爽视频 | 少妇太爽了在线观看 | 男女下面进入的视频免费午夜 | 亚洲综合在线一区二区三区 | 一本色道久久综合亚洲精品不卡 | 日本一卡2卡3卡四卡精品网站 | 无码av最新清无码专区吞精 | 午夜福利试看120秒体验区 | 国产后入清纯学生妹 | 中文字幕人成乱码熟女app | 久久精品99久久香蕉国产色戒 | 激情内射日本一区二区三区 | 免费无码肉片在线观看 | 国产成人无码区免费内射一片色欲 | 中文字幕精品av一区二区五区 | 无码人妻出轨黑人中文字幕 | 午夜免费福利小电影 | 久久综合香蕉国产蜜臀av | 欧美日韩一区二区免费视频 | 国产性生交xxxxx无码 | 扒开双腿吃奶呻吟做受视频 | 天堂а√在线中文在线 | 亚洲精品一区二区三区在线观看 | 日韩av无码一区二区三区 | 国产精品久久久久影院嫩草 | 精品国产国产综合精品 | 欧美人与禽zoz0性伦交 | 黑人玩弄人妻中文在线 | 中国女人内谢69xxxxxa片 | 亚洲高清偷拍一区二区三区 | 欧美第一黄网免费网站 | 久久精品人妻少妇一区二区三区 | 中文字幕亚洲情99在线 | 熟妇女人妻丰满少妇中文字幕 | 亚洲日本va中文字幕 | 乱人伦人妻中文字幕无码久久网 | 最近中文2019字幕第二页 | 丰满少妇熟乱xxxxx视频 | 一个人看的www免费视频在线观看 | 国产熟妇另类久久久久 | 国产女主播喷水视频在线观看 | 欧美人与善在线com | 日本精品久久久久中文字幕 | 欧美xxxxx精品 | 国产成人综合在线女婷五月99播放 | 日本熟妇浓毛 | 日韩精品无码一区二区中文字幕 | 久久99精品久久久久婷婷 | 欧美人与牲动交xxxx | 青青青爽视频在线观看 | 国产成人综合色在线观看网站 | 亚洲va中文字幕无码久久不卡 | 国产无套内射久久久国产 | 亚洲色成人中文字幕网站 | 熟妇人妻中文av无码 | 奇米影视7777久久精品 | 西西人体www44rt大胆高清 | v一区无码内射国产 | 熟妇人妻无乱码中文字幕 | 久久久成人毛片无码 | 免费网站看v片在线18禁无码 | 宝宝好涨水快流出来免费视频 | 久久伊人色av天堂九九小黄鸭 | 巨爆乳无码视频在线观看 | 亚洲精品国产品国语在线观看 | 人妻aⅴ无码一区二区三区 | 老熟妇乱子伦牲交视频 | 老子影院午夜伦不卡 | 成 人 免费观看网站 | 99久久久国产精品无码免费 | 久久久久成人精品免费播放动漫 | 久久久av男人的天堂 | 任你躁国产自任一区二区三区 | 理论片87福利理论电影 | 高潮毛片无遮挡高清免费视频 | 日本一本二本三区免费 | 国内老熟妇对白xxxxhd | 精品久久综合1区2区3区激情 | 对白脏话肉麻粗话av | 日韩欧美中文字幕在线三区 | 久久久久久a亚洲欧洲av冫 | 99久久精品无码一区二区毛片 | 少女韩国电视剧在线观看完整 | 国产亲子乱弄免费视频 | 久久久精品国产sm最大网站 | 精品久久久久久人妻无码中文字幕 | 小泽玛莉亚一区二区视频在线 | 999久久久国产精品消防器材 | 欧美成人高清在线播放 | 波多野结衣乳巨码无在线观看 | 久久精品中文闷骚内射 | 蜜臀av在线播放 久久综合激激的五月天 | 成人三级无码视频在线观看 | 亚洲欧美国产精品专区久久 | 亚洲中文字幕无码一久久区 | 无码人妻精品一区二区三区下载 | 亚洲中文字幕在线无码一区二区 | 国产成人无码午夜视频在线观看 | 色婷婷香蕉在线一区二区 | 亚洲男人av天堂午夜在 | 亚洲最大成人网站 | 内射欧美老妇wbb | 亚洲成在人网站无码天堂 | 国产在线无码精品电影网 | 国产精品人人妻人人爽 | 国产成人无码av一区二区 | 国产精品久久国产三级国 | 女人和拘做爰正片视频 | 51国偷自产一区二区三区 | 天堂在线观看www | 六十路熟妇乱子伦 | 亚洲国产精品久久久天堂 | 国产乱人无码伦av在线a | 亚洲精品国产精品乱码视色 | 国产乱人伦偷精品视频 | 熟女少妇人妻中文字幕 | 国产美女极度色诱视频www | 日日橹狠狠爱欧美视频 | 无套内谢的新婚少妇国语播放 | 久久久久国色av免费观看性色 | 99精品无人区乱码1区2区3区 | 日韩精品无码一本二本三本色 | 国产精品久久久久7777 | 狠狠噜狠狠狠狠丁香五月 | 精品国产一区av天美传媒 | 97精品国产97久久久久久免费 | 婷婷色婷婷开心五月四房播播 | 国产精品永久免费视频 | 久久人妻内射无码一区三区 | 永久免费精品精品永久-夜色 | 99er热精品视频 | 亚洲大尺度无码无码专区 | аⅴ资源天堂资源库在线 | 亚洲综合在线一区二区三区 | 精品午夜福利在线观看 | 久久人人爽人人爽人人片av高清 | 性做久久久久久久久 | 国产精品嫩草久久久久 | 亚洲熟悉妇女xxx妇女av | 久久国语露脸国产精品电影 | 爱做久久久久久 | 国产精品无码一区二区桃花视频 | 亚洲日韩av一区二区三区中文 | 亚洲国产精品一区二区美利坚 | 精品久久久久香蕉网 | 中文精品久久久久人妻不卡 | 欧美三级a做爰在线观看 | 激情五月综合色婷婷一区二区 | 国内综合精品午夜久久资源 | 色情久久久av熟女人妻网站 | 日韩人妻少妇一区二区三区 | 成人免费视频一区二区 | 国产艳妇av在线观看果冻传媒 | 亚洲a无码综合a国产av中文 | 色婷婷av一区二区三区之红樱桃 | 国产精品亚洲а∨无码播放麻豆 | 中文无码成人免费视频在线观看 | 大肉大捧一进一出视频出来呀 | 久久综合网欧美色妞网 | 国产97人人超碰caoprom | 国产成人无码av片在线观看不卡 | 少妇被粗大的猛进出69影院 | 性欧美大战久久久久久久 | 国产精品人人爽人人做我的可爱 | 精品厕所偷拍各类美女tp嘘嘘 | 67194成是人免费无码 | 欧美人妻一区二区三区 | 老子影院午夜精品无码 | 伦伦影院午夜理论片 | 国内精品人妻无码久久久影院 | 国产午夜精品一区二区三区嫩草 | 婷婷综合久久中文字幕蜜桃三电影 | 久久精品99久久香蕉国产色戒 | 中国大陆精品视频xxxx | 国产猛烈高潮尖叫视频免费 | 欧美国产日产一区二区 | 欧美成人家庭影院 | 久久久久久久人妻无码中文字幕爆 | 天天拍夜夜添久久精品大 | 日本一区二区三区免费播放 | 精品无码一区二区三区爱欲 | 日本欧美一区二区三区乱码 | 牲交欧美兽交欧美 | 国产精品久久国产精品99 | 国产9 9在线 | 中文 | 亚洲精品国产精品乱码视色 | 国产亚洲美女精品久久久2020 | 亚洲欧美精品伊人久久 | 无码国模国产在线观看 | 人妻互换免费中文字幕 | 未满成年国产在线观看 | 日本大乳高潮视频在线观看 | 国产情侣作爱视频免费观看 | 色窝窝无码一区二区三区色欲 | 亚洲日本va午夜在线电影 | 亚洲精品一区二区三区在线观看 | 国产美女极度色诱视频www | 午夜免费福利小电影 | 国产精品a成v人在线播放 | 性欧美牲交在线视频 | 亚洲精品久久久久avwww潮水 | 国产深夜福利视频在线 | 亚洲日韩一区二区三区 | 午夜无码人妻av大片色欲 | 欧美日韩人成综合在线播放 | 国产国语老龄妇女a片 | 两性色午夜免费视频 | 一本久道久久综合婷婷五月 | 日日夜夜撸啊撸 | 亚洲成av人片天堂网无码】 | 国内精品一区二区三区不卡 | 国产成人一区二区三区在线观看 | 精品无码一区二区三区的天堂 | 两性色午夜视频免费播放 | 亚洲中文字幕成人无码 | 在线a亚洲视频播放在线观看 | 亚洲日本va中文字幕 | 欧美黑人性暴力猛交喷水 | 国产人妻精品一区二区三区 | 国产精品丝袜黑色高跟鞋 | 狠狠综合久久久久综合网 | 国产综合在线观看 | 亚洲成av人片天堂网无码】 | 无码国产乱人伦偷精品视频 | 久久国内精品自在自线 | 久久99久久99精品中文字幕 | 亚洲男人av天堂午夜在 | 377p欧洲日本亚洲大胆 | 久久精品国产一区二区三区肥胖 | 无码国产乱人伦偷精品视频 | 奇米影视7777久久精品 | 精品熟女少妇av免费观看 | 久久人人爽人人人人片 | 狠狠噜狠狠狠狠丁香五月 | 兔费看少妇性l交大片免费 | 亚洲中文字幕无码中文字在线 | 国产婷婷色一区二区三区在线 | 影音先锋中文字幕无码 | 日韩视频 中文字幕 视频一区 | 国产精品嫩草久久久久 | 午夜精品久久久内射近拍高清 | 国产绳艺sm调教室论坛 | 日韩人妻少妇一区二区三区 | 中文字幕精品av一区二区五区 | 午夜无码人妻av大片色欲 | 成人免费视频一区二区 | 日本xxxx色视频在线观看免费 | 久久99精品国产麻豆 | 领导边摸边吃奶边做爽在线观看 | 亚洲国产欧美日韩精品一区二区三区 | 国产乱人伦av在线无码 | 国产午夜福利亚洲第一 | 亚洲成a人片在线观看无码 | 亚洲精品国产精品乱码不卡 | 国产精品亚洲一区二区三区喷水 | 狠狠色欧美亚洲狠狠色www | 无码免费一区二区三区 | 色五月五月丁香亚洲综合网 | 色婷婷av一区二区三区之红樱桃 | 奇米影视888欧美在线观看 | 久久久久久亚洲精品a片成人 | a在线观看免费网站大全 | 国产亚洲精品久久久久久大师 | 国产网红无码精品视频 | 国产亚洲视频中文字幕97精品 | 欧美国产日韩久久mv | 亚洲成a人片在线观看无码3d | 乌克兰少妇性做爰 | 亚洲 激情 小说 另类 欧美 | 亚洲精品国产第一综合99久久 | 免费无码午夜福利片69 | 国产精品内射视频免费 | 久久久久亚洲精品男人的天堂 | 午夜性刺激在线视频免费 | 高清国产亚洲精品自在久久 | 国产两女互慰高潮视频在线观看 | 人人超人人超碰超国产 | 无码人妻精品一区二区三区不卡 | 日本成熟视频免费视频 | 巨爆乳无码视频在线观看 | 亚洲中文字幕无码一久久区 | 国产成人无码av一区二区 | aa片在线观看视频在线播放 | 国产av一区二区精品久久凹凸 | 无遮挡国产高潮视频免费观看 | 一本色道久久综合狠狠躁 | 无码人妻黑人中文字幕 | 精品国精品国产自在久国产87 | 在教室伦流澡到高潮hnp视频 | 亚洲の无码国产の无码步美 | 性做久久久久久久久 | 学生妹亚洲一区二区 | 无码人妻丰满熟妇区毛片18 | 成人性做爰aaa片免费看 | 亚洲人亚洲人成电影网站色 | 成人一在线视频日韩国产 | 欧洲熟妇精品视频 | 国产猛烈高潮尖叫视频免费 | 少妇的肉体aa片免费 | 国产精品欧美成人 | 中文精品久久久久人妻不卡 | 熟女俱乐部五十路六十路av | 青青青手机频在线观看 | 国产精品久久久一区二区三区 | 久久久久久国产精品无码下载 | 中文字幕无码av波多野吉衣 | 国产乱人偷精品人妻a片 | 精品 日韩 国产 欧美 视频 | 人妻无码αv中文字幕久久琪琪布 | 无码国产乱人伦偷精品视频 | 日韩少妇内射免费播放 | 久久亚洲精品中文字幕无男同 | 又大又硬又黄的免费视频 | 色一情一乱一伦一视频免费看 | 色综合久久久久综合一本到桃花网 | 青青久在线视频免费观看 | 亚洲日韩乱码中文无码蜜桃臀网站 | 日日天干夜夜狠狠爱 | 中文字幕无线码 | 精品偷拍一区二区三区在线看 | 扒开双腿疯狂进出爽爽爽视频 | 日韩av激情在线观看 | 强开小婷嫩苞又嫩又紧视频 | av无码电影一区二区三区 | 色综合久久久无码网中文 | 丝袜 中出 制服 人妻 美腿 | 亚洲性无码av中文字幕 | 大屁股大乳丰满人妻 | 秋霞成人午夜鲁丝一区二区三区 | 日日摸天天摸爽爽狠狠97 | 奇米影视888欧美在线观看 | 国内丰满熟女出轨videos | 国产两女互慰高潮视频在线观看 | 无码帝国www无码专区色综合 | 熟女少妇人妻中文字幕 | 无码人妻少妇伦在线电影 | 中文字幕无线码 | 99久久人妻精品免费一区 | 人人妻在人人 | 亚拍精品一区二区三区探花 | 欧美一区二区三区 | 性生交片免费无码看人 | 熟妇激情内射com | 亚洲国产高清在线观看视频 | 亚洲精品欧美二区三区中文字幕 | 一本色道久久综合亚洲精品不卡 | 中文字幕亚洲情99在线 | 小泽玛莉亚一区二区视频在线 | 国产精品国产三级国产专播 | 永久免费观看国产裸体美女 | 少妇久久久久久人妻无码 | 牲欲强的熟妇农村老妇女 | 久久久久人妻一区精品色欧美 | 熟女体下毛毛黑森林 | 成人欧美一区二区三区 | 麻豆果冻传媒2021精品传媒一区下载 | 欧美成人免费全部网站 | 成人影院yy111111在线观看 | 成人性做爰aaa片免费看不忠 | 最新版天堂资源中文官网 | 99久久精品无码一区二区毛片 | 中文字幕人成乱码熟女app | 亚洲熟妇色xxxxx亚洲 | 99久久精品无码一区二区毛片 | 一个人免费观看的www视频 | 欧美 日韩 亚洲 在线 | 国产美女极度色诱视频www | 久久国产劲爆∧v内射 | 国产av剧情md精品麻豆 | 99国产精品白浆在线观看免费 | 三上悠亚人妻中文字幕在线 | 最近免费中文字幕中文高清百度 | 国产乱子伦视频在线播放 | 国产性猛交╳xxx乱大交 国产精品久久久久久无码 欧洲欧美人成视频在线 | 日本熟妇浓毛 | 狠狠色色综合网站 | 国产人妻人伦精品1国产丝袜 | 国产av人人夜夜澡人人爽麻豆 | 色一情一乱一伦一区二区三欧美 | 狠狠色噜噜狠狠狠7777奇米 | 亚洲精品久久久久avwww潮水 | 啦啦啦www在线观看免费视频 | 亚洲の无码国产の无码影院 | 久久综合九色综合欧美狠狠 | 天堂无码人妻精品一区二区三区 | 天堂亚洲2017在线观看 | 国产精品美女久久久 | 色情久久久av熟女人妻网站 | 成人精品一区二区三区中文字幕 | 欧美肥老太牲交大战 | 国产精品久久久久7777 | 免费无码一区二区三区蜜桃大 | 久久精品国产99精品亚洲 | 丰满诱人的人妻3 | 国产亚洲人成a在线v网站 | 一本久道久久综合婷婷五月 | 久久国语露脸国产精品电影 | 免费观看又污又黄的网站 | 未满成年国产在线观看 | 日本在线高清不卡免费播放 | 日韩精品成人一区二区三区 | 国产精华av午夜在线观看 | 丰满少妇弄高潮了www | 亚洲 另类 在线 欧美 制服 | 久久精品国产99久久6动漫 | 女人被男人爽到呻吟的视频 | 麻豆成人精品国产免费 | 99精品国产综合久久久久五月天 | 少妇无码av无码专区在线观看 | 国产97人人超碰caoprom | 日日摸天天摸爽爽狠狠97 | 日韩人妻少妇一区二区三区 | 网友自拍区视频精品 | 亚洲色无码一区二区三区 | 偷窥日本少妇撒尿chinese | 欧美国产日韩久久mv | 麻花豆传媒剧国产免费mv在线 | 精品乱子伦一区二区三区 | 黑人粗大猛烈进出高潮视频 | 丝袜 中出 制服 人妻 美腿 | 亚洲欧美色中文字幕在线 | 亚拍精品一区二区三区探花 | 色欲人妻aaaaaaa无码 | 国产精品无码成人午夜电影 | 成 人 免费观看网站 | 女人和拘做爰正片视频 | 少妇人妻偷人精品无码视频 | 色欲人妻aaaaaaa无码 | 亚洲欧美综合区丁香五月小说 | 国内丰满熟女出轨videos | 国产成人无码av一区二区 | 国产精品久久久午夜夜伦鲁鲁 | 久在线观看福利视频 | 少妇激情av一区二区 | 婷婷综合久久中文字幕蜜桃三电影 | 精品午夜福利在线观看 | 丰满人妻被黑人猛烈进入 | 偷窥村妇洗澡毛毛多 | 97se亚洲精品一区 | 77777熟女视频在线观看 а天堂中文在线官网 | 色婷婷久久一区二区三区麻豆 | 精品 日韩 国产 欧美 视频 | 午夜无码人妻av大片色欲 | 久久国内精品自在自线 | 久久久久亚洲精品中文字幕 | 国产精品亚洲а∨无码播放麻豆 | 99久久99久久免费精品蜜桃 | 色五月五月丁香亚洲综合网 | 97色伦图片97综合影院 | 午夜性刺激在线视频免费 | 久久综合网欧美色妞网 | 亚洲精品无码人妻无码 | 女人被男人爽到呻吟的视频 | 欧美35页视频在线观看 | 亚洲精品一区二区三区大桥未久 | 荡女精品导航 | 大肉大捧一进一出好爽视频 | 精品亚洲成av人在线观看 | 亚洲国精产品一二二线 | 99久久久无码国产aaa精品 | 国产成人精品久久亚洲高清不卡 | 未满成年国产在线观看 | 亚洲精品国偷拍自产在线观看蜜桃 | 波多野结衣av在线观看 | 在线a亚洲视频播放在线观看 | 鲁鲁鲁爽爽爽在线视频观看 | 熟妇人妻无码xxx视频 | 日韩人妻无码一区二区三区久久99 | 国产精品亚洲lv粉色 | 久久国产自偷自偷免费一区调 | 国产精品久久久久7777 | 自拍偷自拍亚洲精品10p | 少妇性俱乐部纵欲狂欢电影 | 国产人妻久久精品二区三区老狼 | 国产精品人人爽人人做我的可爱 | 熟妇激情内射com | 免费网站看v片在线18禁无码 | 强辱丰满人妻hd中文字幕 | 88国产精品欧美一区二区三区 | 欧美黑人乱大交 | 熟妇激情内射com | 性欧美videos高清精品 | 曰韩少妇内射免费播放 | 少妇被黑人到高潮喷出白浆 | 色情久久久av熟女人妻网站 | 高中生自慰www网站 | 国内揄拍国内精品人妻 | 亚洲成av人片天堂网无码】 | 国产香蕉尹人视频在线 | 一本色道久久综合狠狠躁 | 亚洲一区二区三区偷拍女厕 | 国产精品手机免费 | 成人性做爰aaa片免费看不忠 | 一本久道久久综合婷婷五月 | 成人免费视频在线观看 | 特大黑人娇小亚洲女 | 三上悠亚人妻中文字幕在线 | 高中生自慰www网站 | 成人av无码一区二区三区 | 欧美一区二区三区 | 人妻体内射精一区二区三四 | 亚洲欧美国产精品久久 | 18禁黄网站男男禁片免费观看 | 国内精品一区二区三区不卡 | 国语自产偷拍精品视频偷 | 免费无码午夜福利片69 | 黄网在线观看免费网站 | 欧美精品国产综合久久 | 男女下面进入的视频免费午夜 | 日韩人妻无码一区二区三区久久99 | 久久国产精品偷任你爽任你 | 国产精品a成v人在线播放 | 国产内射爽爽大片视频社区在线 | 久久精品国产一区二区三区肥胖 | 无套内谢的新婚少妇国语播放 | 国产精品久久久久久无码 | 欧美性猛交xxxx富婆 | 欧美性猛交内射兽交老熟妇 | 天天综合网天天综合色 | 国产精品久久久午夜夜伦鲁鲁 | 纯爱无遮挡h肉动漫在线播放 | 色欲久久久天天天综合网精品 | 久久精品人妻少妇一区二区三区 | 欧美激情一区二区三区成人 | 国产凸凹视频一区二区 | 中文字幕无码av波多野吉衣 | 欧美精品免费观看二区 | 成人毛片一区二区 | 国产乱人伦av在线无码 | 无码国模国产在线观看 | 欧美刺激性大交 | 日本www一道久久久免费榴莲 | 精品国精品国产自在久国产87 | 国产精品办公室沙发 | 久久久国产精品无码免费专区 | 欧洲美熟女乱又伦 | 99久久99久久免费精品蜜桃 | 蜜桃av抽搐高潮一区二区 | 亚洲码国产精品高潮在线 | 在线播放无码字幕亚洲 | 日韩精品乱码av一区二区 | 我要看www免费看插插视频 | 妺妺窝人体色www在线小说 | 性欧美videos高清精品 | 老司机亚洲精品影院无码 | 人人爽人人澡人人高潮 | 国产香蕉尹人视频在线 | 久久这里只有精品视频9 | 秋霞特色aa大片 | 九月婷婷人人澡人人添人人爽 | 亚洲熟妇色xxxxx亚洲 | 老司机亚洲精品影院无码 | 亚洲精品一区国产 | 一本色道久久综合亚洲精品不卡 | av在线亚洲欧洲日产一区二区 | 国产黄在线观看免费观看不卡 | 国产精品亚洲一区二区三区喷水 | 99久久精品日本一区二区免费 | 中文字幕人妻无码一区二区三区 | 中文字幕乱码中文乱码51精品 | 日本一区二区更新不卡 | 少妇人妻偷人精品无码视频 | 久久精品人人做人人综合试看 | 亚洲国产欧美在线成人 | 精品国产精品久久一区免费式 | 亚洲欧美综合区丁香五月小说 | 精品乱码久久久久久久 | 动漫av一区二区在线观看 | 精品久久久久香蕉网 | 欧美性生交xxxxx久久久 | 无码成人精品区在线观看 | 强辱丰满人妻hd中文字幕 | 亚洲人成网站色7799 | 日本一卡2卡3卡四卡精品网站 | 国精品人妻无码一区二区三区蜜柚 | 无码一区二区三区在线观看 | 四虎4hu永久免费 | 国产口爆吞精在线视频 | 76少妇精品导航 | 成人av无码一区二区三区 | av无码久久久久不卡免费网站 | 东京无码熟妇人妻av在线网址 | 中文字幕无码日韩专区 | 99riav国产精品视频 | 国产两女互慰高潮视频在线观看 | 久久精品国产99久久6动漫 | 精品久久久无码人妻字幂 | 老头边吃奶边弄进去呻吟 | 东京热无码av男人的天堂 | 中文字幕av无码一区二区三区电影 | 国产真实乱对白精彩久久 | 好屌草这里只有精品 | 在线观看免费人成视频 | 大肉大捧一进一出好爽视频 | 国产莉萝无码av在线播放 | 久久精品99久久香蕉国产色戒 | 性开放的女人aaa片 | 亚洲人成无码网www | 精品aⅴ一区二区三区 | 亚洲国产精品久久久天堂 | 欧美成人午夜精品久久久 | 国精产品一区二区三区 | 久久精品人人做人人综合 | 99国产欧美久久久精品 | 久久午夜无码鲁丝片午夜精品 | 婷婷综合久久中文字幕蜜桃三电影 | 99在线 | 亚洲 | 成人影院yy111111在线观看 | 色偷偷av老熟女 久久精品人妻少妇一区二区三区 | 亚洲欧美精品aaaaaa片 | 国精产品一区二区三区 | 日韩精品久久久肉伦网站 | 亚洲国产精品毛片av不卡在线 | 全黄性性激高免费视频 | 奇米影视7777久久精品 | 激情内射日本一区二区三区 | 欧美freesex黑人又粗又大 | 国产无遮挡又黄又爽免费视频 | 中文字幕无码日韩欧毛 | 国产做国产爱免费视频 | 成人免费视频视频在线观看 免费 | 狠狠噜狠狠狠狠丁香五月 | 99精品无人区乱码1区2区3区 | 中文字幕人成乱码熟女app | 在线看片无码永久免费视频 | 性欧美疯狂xxxxbbbb | 四虎国产精品一区二区 | 欧洲美熟女乱又伦 | 思思久久99热只有频精品66 | 日日天干夜夜狠狠爱 | 无码吃奶揉捏奶头高潮视频 | 国产精品理论片在线观看 | 中文字幕无码乱人伦 | 久久综合狠狠综合久久综合88 | 狠狠色色综合网站 | 国产av一区二区精品久久凹凸 | 久久熟妇人妻午夜寂寞影院 | 一本久久伊人热热精品中文字幕 | 在线精品国产一区二区三区 | 久久99热只有频精品8 | 少妇高潮喷潮久久久影院 | 97久久国产亚洲精品超碰热 | 久久精品国产大片免费观看 | 麻豆成人精品国产免费 | 亚洲国产精品美女久久久久 | 欧美性色19p | 亚洲男人av天堂午夜在 | 国产精品视频免费播放 | 丰满少妇高潮惨叫视频 | 强辱丰满人妻hd中文字幕 | 久久久久人妻一区精品色欧美 | 粉嫩少妇内射浓精videos | 漂亮人妻洗澡被公强 日日躁 | 十八禁视频网站在线观看 | 精品国产成人一区二区三区 | 免费国产成人高清在线观看网站 | 成人三级无码视频在线观看 | 久久久中文久久久无码 | 国产欧美熟妇另类久久久 | 亚洲日韩精品欧美一区二区 | 精品国精品国产自在久国产87 | 欧美成人午夜精品久久久 | 欧美丰满少妇xxxx性 | 国产亚洲日韩欧美另类第八页 | 在线看片无码永久免费视频 | 性欧美疯狂xxxxbbbb | 亚洲精品无码国产 | 东京热一精品无码av | 夜夜夜高潮夜夜爽夜夜爰爰 | 国产又粗又硬又大爽黄老大爷视 | 精品日本一区二区三区在线观看 | 国产无遮挡吃胸膜奶免费看 | 在线播放免费人成毛片乱码 | 日本熟妇人妻xxxxx人hd | 欧美成人午夜精品久久久 | 少妇无码吹潮 | 国产午夜亚洲精品不卡下载 | 国产 浪潮av性色四虎 | 亚洲成av人片在线观看无码不卡 | 久久99精品久久久久久 | 亚洲色欲色欲欲www在线 | 67194成是人免费无码 | 色婷婷综合中文久久一本 | 精品无码成人片一区二区98 | 国产凸凹视频一区二区 | 午夜精品久久久久久久久 | 丰满妇女强制高潮18xxxx | 亚洲色欲色欲天天天www | 欧美刺激性大交 | 波多野结衣aⅴ在线 | 夜精品a片一区二区三区无码白浆 | 97资源共享在线视频 | 亚洲中文字幕成人无码 | 久久午夜无码鲁丝片午夜精品 | 日日噜噜噜噜夜夜爽亚洲精品 | 国产成人亚洲综合无码 | 久久久精品人妻久久影视 | 少妇高潮一区二区三区99 | 人人妻人人藻人人爽欧美一区 | 中文字幕无码免费久久99 | 永久免费观看美女裸体的网站 | 欧美熟妇另类久久久久久多毛 | 国产人成高清在线视频99最全资源 | 成人无码精品一区二区三区 | 国产人妻精品午夜福利免费 | 久久精品一区二区三区四区 | 日韩av无码一区二区三区不卡 | 97精品人妻一区二区三区香蕉 | 老司机亚洲精品影院 | 熟女俱乐部五十路六十路av | 欧美成人午夜精品久久久 | 中文字幕av伊人av无码av | 日本爽爽爽爽爽爽在线观看免 | 亚洲日韩av片在线观看 | 亚洲成a人片在线观看日本 | 欧美日韩综合一区二区三区 | 亚洲中文字幕av在天堂 | 国产精品无码永久免费888 | 免费观看的无遮挡av | 亚洲精品中文字幕 | 国产无遮挡又黄又爽免费视频 | 亚洲成av人片在线观看无码不卡 | 欧美日韩视频无码一区二区三 | 中文精品久久久久人妻不卡 | 色情久久久av熟女人妻网站 | 亚洲成av人影院在线观看 | 欧美丰满熟妇xxxx性ppx人交 | 精品国产一区二区三区四区在线看 | 又粗又大又硬又长又爽 | 中文字幕乱码亚洲无线三区 | 色一情一乱一伦一区二区三欧美 | 日本一区二区三区免费播放 | 亚洲乱亚洲乱妇50p | 亚洲最大成人网站 | 性生交大片免费看l | 中文无码伦av中文字幕 | 亚洲日韩一区二区三区 | 鲁一鲁av2019在线 | 狠狠躁日日躁夜夜躁2020 | 无遮挡国产高潮视频免费观看 | 欧美国产亚洲日韩在线二区 | 国产精品美女久久久久av爽李琼 | 永久免费观看美女裸体的网站 | 欧美人与动性行为视频 | 亚洲国产精品成人久久蜜臀 | 午夜理论片yy44880影院 | 精品国产成人一区二区三区 | 欧美精品在线观看 | 青青青手机频在线观看 | www国产亚洲精品久久久日本 | 国产无遮挡又黄又爽免费视频 | 97夜夜澡人人双人人人喊 | 亚洲无人区一区二区三区 | 女高中生第一次破苞av | 97夜夜澡人人双人人人喊 | 美女毛片一区二区三区四区 | 无码中文字幕色专区 | 久久97精品久久久久久久不卡 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 国产精品美女久久久网av | 国产免费无码一区二区视频 | 国产一区二区不卡老阿姨 | 国产精品久久久久久亚洲影视内衣 | 欧美 日韩 人妻 高清 中文 | 国产精品亚洲五月天高清 | 久久熟妇人妻午夜寂寞影院 | 亚洲一区二区三区香蕉 | 精品国产aⅴ无码一区二区 | 亚拍精品一区二区三区探花 | 日日躁夜夜躁狠狠躁 | 久久伊人色av天堂九九小黄鸭 | 妺妺窝人体色www在线小说 | 午夜精品久久久久久久 | 亚洲精品午夜无码电影网 | 亚洲性无码av中文字幕 | 自拍偷自拍亚洲精品被多人伦好爽 | 又大又硬又爽免费视频 | 欧美怡红院免费全部视频 | 国产午夜视频在线观看 | 国产精品久免费的黄网站 | 午夜熟女插插xx免费视频 | av无码不卡在线观看免费 | 日韩人妻无码一区二区三区久久99 | 午夜精品久久久内射近拍高清 | 一个人看的视频www在线 | 国产熟女一区二区三区四区五区 | 无码一区二区三区在线 | 亚洲精品中文字幕乱码 | 暴力强奷在线播放无码 | 国产麻豆精品一区二区三区v视界 | 性色欲网站人妻丰满中文久久不卡 | 国产性猛交╳xxx乱大交 国产精品久久久久久无码 欧洲欧美人成视频在线 | 97久久国产亚洲精品超碰热 | 99精品无人区乱码1区2区3区 | 亚洲色欲色欲天天天www | 给我免费的视频在线观看 | 四虎国产精品一区二区 | 久久精品国产日本波多野结衣 | 成人三级无码视频在线观看 | 扒开双腿疯狂进出爽爽爽视频 | 欧美老人巨大xxxx做受 | 欧美性生交xxxxx久久久 | 无码纯肉视频在线观看 | 成人片黄网站色大片免费观看 | 亚洲欧美综合区丁香五月小说 | 亚洲乱码日产精品bd | 亚洲欧美精品aaaaaa片 | 国产乱人伦av在线无码 | 免费无码的av片在线观看 | 97夜夜澡人人爽人人喊中国片 | 国产精品亚洲五月天高清 | av无码不卡在线观看免费 | 亚洲第一无码av无码专区 | 欧美性猛交xxxx富婆 | 日本va欧美va欧美va精品 | 国产特级毛片aaaaaaa高清 | 99久久无码一区人妻 | 男女爱爱好爽视频免费看 | 中文字幕人妻无码一区二区三区 | 亚洲日韩中文字幕在线播放 | 欧美老熟妇乱xxxxx | 日欧一片内射va在线影院 | 人人超人人超碰超国产 | 曰韩无码二三区中文字幕 | 人人澡人人妻人人爽人人蜜桃 | 国产精品久久久久影院嫩草 | 国产小呦泬泬99精品 | 无码人妻黑人中文字幕 | 欧美成人家庭影院 | av香港经典三级级 在线 | 亚洲日韩av一区二区三区四区 | 99精品视频在线观看免费 | 纯爱无遮挡h肉动漫在线播放 | 亚洲中文字幕成人无码 | 亚洲日本va午夜在线电影 | 日韩视频 中文字幕 视频一区 | 亚洲色在线无码国产精品不卡 | 国产综合久久久久鬼色 | 波多野结衣高清一区二区三区 | 亚洲日本一区二区三区在线 | 一本久久伊人热热精品中文字幕 | 欧美国产日韩久久mv | 国产成人人人97超碰超爽8 | 国产婷婷色一区二区三区在线 | 国产艳妇av在线观看果冻传媒 | 国产成人精品必看 | 无码人妻精品一区二区三区下载 | 精品欧洲av无码一区二区三区 | 丰满妇女强制高潮18xxxx | 巨爆乳无码视频在线观看 | 亚洲精品国偷拍自产在线观看蜜桃 | 牲欲强的熟妇农村老妇女 | 中文字幕无码日韩欧毛 | 99久久久无码国产aaa精品 | 人人爽人人澡人人人妻 | 亚洲天堂2017无码 | 成人三级无码视频在线观看 | 精品久久久久久人妻无码中文字幕 | 亚洲精品成人福利网站 | 免费看少妇作爱视频 | 久久成人a毛片免费观看网站 | 国产精品久久国产三级国 | 中文字幕无码日韩欧毛 | 极品尤物被啪到呻吟喷水 | 无码免费一区二区三区 | 亚洲乱码中文字幕在线 | 色诱久久久久综合网ywww | 欧美xxxx黑人又粗又长 | 波多野结衣乳巨码无在线观看 | 无码一区二区三区在线 | 日韩av无码一区二区三区 | 一本久道久久综合婷婷五月 | 欧美大屁股xxxxhd黑色 | 自拍偷自拍亚洲精品被多人伦好爽 | 亚洲一区二区三区含羞草 | 老熟妇仑乱视频一区二区 | 日本一区二区更新不卡 | 亚洲精品成a人在线观看 | 久久人妻内射无码一区三区 | 成人性做爰aaa片免费看 | 久久精品国产99久久6动漫 | 伊人色综合久久天天小片 | 久久久久se色偷偷亚洲精品av | 天天拍夜夜添久久精品大 | 成人精品天堂一区二区三区 | 黑人巨大精品欧美一区二区 | 麻豆国产丝袜白领秘书在线观看 | 国产人妻久久精品二区三区老狼 | 爆乳一区二区三区无码 | 丰满人妻一区二区三区免费视频 | 秋霞特色aa大片 | 成在人线av无码免观看麻豆 | 国产在线aaa片一区二区99 | 双乳奶水饱满少妇呻吟 | 久久久久久久人妻无码中文字幕爆 | 久久综合九色综合97网 | 久久久久人妻一区精品色欧美 | 99er热精品视频 | 亚洲中文字幕成人无码 | 激情综合激情五月俺也去 | 中文字幕人妻无码一区二区三区 | 欧美人与牲动交xxxx | 人人爽人人爽人人片av亚洲 | 久久人人97超碰a片精品 | 午夜性刺激在线视频免费 | 日日噜噜噜噜夜夜爽亚洲精品 | av香港经典三级级 在线 | 黄网在线观看免费网站 | 熟妇女人妻丰满少妇中文字幕 | 永久免费观看美女裸体的网站 | 久久久婷婷五月亚洲97号色 | 日韩成人一区二区三区在线观看 | 4hu四虎永久在线观看 | 成人欧美一区二区三区 | 男女猛烈xx00免费视频试看 | 国产精品久久国产精品99 | 欧洲欧美人成视频在线 | 色欲综合久久中文字幕网 | 最近的中文字幕在线看视频 | 亚欧洲精品在线视频免费观看 | 国产免费久久精品国产传媒 | 大肉大捧一进一出视频出来呀 | 3d动漫精品啪啪一区二区中 | 国产精品国产三级国产专播 | 国产亚洲欧美日韩亚洲中文色 | 激情内射日本一区二区三区 | 少妇性l交大片 | 在线精品国产一区二区三区 | 欧美日韩精品 | 中文字幕日产无线码一区 | 伊在人天堂亚洲香蕉精品区 | 国产综合在线观看 | 欧美亚洲国产一区二区三区 | 国产口爆吞精在线视频 | 中文字幕无码av波多野吉衣 | 一个人看的www免费视频在线观看 | 麻豆精品国产精华精华液好用吗 | 久久99精品国产.久久久久 | 日本精品少妇一区二区三区 | 国产精品办公室沙发 | 熟妇女人妻丰满少妇中文字幕 | 亚洲中文字幕在线无码一区二区 | 亚洲成av人片天堂网无码】 | 麻豆av传媒蜜桃天美传媒 | 人妻插b视频一区二区三区 | 亚洲а∨天堂久久精品2021 | 亚洲色www成人永久网址 | 久久久精品456亚洲影院 | 久久99精品国产.久久久久 | 国产内射老熟女aaaa | 欧美日韩亚洲国产精品 | 成人影院yy111111在线观看 | 国产亚洲欧美日韩亚洲中文色 | 76少妇精品导航 | 中文字幕人妻无码一区二区三区 | 一本久久伊人热热精品中文字幕 | 亚洲精品国产精品乱码视色 | 狠狠色噜噜狠狠狠7777奇米 | 欧美日韩综合一区二区三区 | а√资源新版在线天堂 | 国产精品99久久精品爆乳 | 国产人妻人伦精品1国产丝袜 | 欧美日韩一区二区三区自拍 | 亚洲 日韩 欧美 成人 在线观看 | 乱码av麻豆丝袜熟女系列 | 国产精品99久久精品爆乳 | 成年美女黄网站色大免费全看 | а√资源新版在线天堂 | 玩弄少妇高潮ⅹxxxyw | 2020久久超碰国产精品最新 | 成人毛片一区二区 | 少妇性俱乐部纵欲狂欢电影 | 扒开双腿疯狂进出爽爽爽视频 | 娇妻被黑人粗大高潮白浆 | 国产精华av午夜在线观看 | 亚洲午夜无码久久 | 亚洲成av人片在线观看无码不卡 | 乌克兰少妇性做爰 | 亚洲色大成网站www国产 | 亚洲中文字幕无码中字 | 欧美第一黄网免费网站 | a在线观看免费网站大全 | 俄罗斯老熟妇色xxxx | 日本精品人妻无码免费大全 | 2020久久超碰国产精品最新 | 少妇高潮一区二区三区99 | 18禁止看的免费污网站 | 国产精品久久久久久亚洲毛片 | 99在线 | 亚洲 | 色婷婷欧美在线播放内射 | 少妇人妻av毛片在线看 | 东京无码熟妇人妻av在线网址 | 高潮毛片无遮挡高清免费视频 | 18黄暴禁片在线观看 | 成人无码精品1区2区3区免费看 | 国产suv精品一区二区五 | 日本免费一区二区三区最新 | 黑人巨大精品欧美一区二区 | 377p欧洲日本亚洲大胆 | 成人试看120秒体验区 | 中文字幕中文有码在线 | 少妇被黑人到高潮喷出白浆 | 国产农村妇女高潮大叫 | 久久久久久久久蜜桃 | 欧美国产亚洲日韩在线二区 | 97精品国产97久久久久久免费 | 日日躁夜夜躁狠狠躁 | 国产av久久久久精东av | 成人精品天堂一区二区三区 | 捆绑白丝粉色jk震动捧喷白浆 | 18黄暴禁片在线观看 | 久久国产自偷自偷免费一区调 | 亚洲综合伊人久久大杳蕉 | 久久精品一区二区三区四区 | 免费人成网站视频在线观看 | 99久久久无码国产精品免费 | 又大又紧又粉嫩18p少妇 | 爱做久久久久久 | 久久亚洲中文字幕精品一区 | 老司机亚洲精品影院 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 丰满少妇熟乱xxxxx视频 | 国产精品99久久精品爆乳 | 欧美国产亚洲日韩在线二区 | 久久久久亚洲精品中文字幕 | 亚洲成a人片在线观看无码 | 久久人人爽人人爽人人片av高清 | 色婷婷av一区二区三区之红樱桃 | 波多野结衣av一区二区全免费观看 | 99久久无码一区人妻 | 大胆欧美熟妇xx | 精品国产精品久久一区免费式 | 男女爱爱好爽视频免费看 | 亚洲国产综合无码一区 | 亚洲日韩一区二区三区 | 欧美性生交活xxxxxdddd | 宝宝好涨水快流出来免费视频 | 欧美丰满少妇xxxx性 | 亚洲色偷偷偷综合网 | 国产极品美女高潮无套在线观看 | 国产片av国语在线观看 | 天堂亚洲免费视频 | 国产艳妇av在线观看果冻传媒 | 国产人成高清在线视频99最全资源 | 午夜肉伦伦影院 | 久激情内射婷内射蜜桃人妖 | 亚洲欧洲无卡二区视頻 | 精品偷拍一区二区三区在线看 | 亚洲综合久久一区二区 | 内射欧美老妇wbb | 久久无码中文字幕免费影院蜜桃 | 免费观看激色视频网站 | 亚洲人成人无码网www国产 | 国产精品亚洲一区二区三区喷水 | 97久久精品无码一区二区 | 国内综合精品午夜久久资源 | 激情爆乳一区二区三区 | 帮老师解开蕾丝奶罩吸乳网站 | 欧美真人作爱免费视频 | 日本www一道久久久免费榴莲 | 十八禁视频网站在线观看 | 欧洲极品少妇 | 国产人妻人伦精品1国产丝袜 | 国产亚洲精品久久久久久 | 曰本女人与公拘交酡免费视频 | 国内老熟妇对白xxxxhd | 日韩人妻系列无码专区 | 2020最新国产自产精品 | 18无码粉嫩小泬无套在线观看 | 久久五月精品中文字幕 | 精品一区二区三区波多野结衣 | 亚洲日韩一区二区三区 | 国产黑色丝袜在线播放 | 激情人妻另类人妻伦 | 中文字幕乱码人妻无码久久 | 国产精品99爱免费视频 | 免费观看黄网站 | 精品偷拍一区二区三区在线看 | 牲欲强的熟妇农村老妇女 | 精品午夜福利在线观看 | 又湿又紧又大又爽a视频国产 | 国产午夜视频在线观看 | 亚洲国产欧美日韩精品一区二区三区 | 又色又爽又黄的美女裸体网站 | 亚洲gv猛男gv无码男同 | 欧美猛少妇色xxxxx | 久久99久久99精品中文字幕 | 无码帝国www无码专区色综合 | 自拍偷自拍亚洲精品10p | 国产人妖乱国产精品人妖 | 国产精品久久久久7777 | 18精品久久久无码午夜福利 | 狂野欧美性猛xxxx乱大交 | 久久国语露脸国产精品电影 | 又大又黄又粗又爽的免费视频 | 日韩无码专区 | 麻豆蜜桃av蜜臀av色欲av | 少妇人妻大乳在线视频 | 性色欲情网站iwww九文堂 | 国产片av国语在线观看 | 国产精品视频免费播放 | 久久精品人妻少妇一区二区三区 | 亚洲呦女专区 | 免费国产黄网站在线观看 | 精品国产乱码久久久久乱码 | 无码国模国产在线观看 | 欧美性生交xxxxx久久久 | 亚洲の无码国产の无码影院 | 久久国产精品_国产精品 | 激情综合激情五月俺也去 | 国产熟女一区二区三区四区五区 | 激情综合激情五月俺也去 | 波多野结衣 黑人 | 国内丰满熟女出轨videos | 国产凸凹视频一区二区 | 午夜精品一区二区三区在线观看 | 亚洲s色大片在线观看 | 国产人妻人伦精品1国产丝袜 | 国产精品久久久久久亚洲毛片 | 久久久中文久久久无码 | 亚洲一区二区三区含羞草 | 国产高清不卡无码视频 | 亚洲第一网站男人都懂 | 狂野欧美性猛交免费视频 | 蜜桃视频插满18在线观看 | 国产成人精品一区二区在线小狼 | 久久国产精品_国产精品 | 亚洲国精产品一二二线 | 兔费看少妇性l交大片免费 | 午夜丰满少妇性开放视频 | 无码国产色欲xxxxx视频 | 久久亚洲国产成人精品性色 | 日韩欧美群交p片內射中文 | 日韩欧美中文字幕在线三区 | 国产亚洲视频中文字幕97精品 | 在线播放免费人成毛片乱码 | 最新版天堂资源中文官网 | 又粗又大又硬又长又爽 | 国产午夜无码视频在线观看 | 久久久精品欧美一区二区免费 | 一二三四在线观看免费视频 | 国产成人无码一二三区视频 | 伊在人天堂亚洲香蕉精品区 | 亚洲色欲久久久综合网东京热 | 日本爽爽爽爽爽爽在线观看免 | 国产性生交xxxxx无码 | 亚洲熟妇色xxxxx欧美老妇 | 亚洲成av人片在线观看无码不卡 | 亚洲色大成网站www国产 | 欧美日韩色另类综合 | 亚洲欧美色中文字幕在线 | 97色伦图片97综合影院 | 成人欧美一区二区三区黑人免费 | 精品国产国产综合精品 | 国产午夜无码视频在线观看 | 性色欲网站人妻丰满中文久久不卡 | 清纯唯美经典一区二区 | 图片小说视频一区二区 | 久久综合给合久久狠狠狠97色 | 毛片内射-百度 | 性欧美videos高清精品 | 国产精品美女久久久网av | 亚洲大尺度无码无码专区 | 成人影院yy111111在线观看 | 麻豆精产国品 | 国产xxx69麻豆国语对白 | 精品夜夜澡人妻无码av蜜桃 | 18精品久久久无码午夜福利 | 亚洲精品一区二区三区在线 | 久久精品中文闷骚内射 | 国产日产欧产精品精品app | 无码成人精品区在线观看 | 久久久久久久女国产乱让韩 | 人人妻人人澡人人爽欧美一区 | 久久人人爽人人爽人人片av高清 | 亚洲色www成人永久网址 | 成人精品天堂一区二区三区 | www成人国产高清内射 | 131美女爱做视频 | 久久人人97超碰a片精品 | 在线观看免费人成视频 | 全黄性性激高免费视频 | 国内精品久久毛片一区二区 | 少妇人妻偷人精品无码视频 | 麻豆成人精品国产免费 | 亚洲综合另类小说色区 | 亚洲va欧美va天堂v国产综合 | 久久综合九色综合97网 | 国产乱人无码伦av在线a | 美女极度色诱视频国产 | 无码国模国产在线观看 | 成人aaa片一区国产精品 | 捆绑白丝粉色jk震动捧喷白浆 | 国产精品资源一区二区 | 国产午夜无码视频在线观看 | 日韩欧美成人免费观看 | 国产午夜福利100集发布 | 娇妻被黑人粗大高潮白浆 | 高潮毛片无遮挡高清免费视频 | 夜精品a片一区二区三区无码白浆 | 波多野结衣 黑人 | 麻豆精品国产精华精华液好用吗 | 国产亚洲精品久久久久久久久动漫 | 久久人妻内射无码一区三区 | 成人无码视频免费播放 | 中文字幕无码av激情不卡 | 老熟女乱子伦 | 国产精品人人妻人人爽 | 在线观看国产一区二区三区 | 无码人妻久久一区二区三区不卡 | 亚洲精品午夜无码电影网 | 人人妻人人澡人人爽欧美一区九九 | 欧美人与牲动交xxxx | 图片区 小说区 区 亚洲五月 | 狠狠躁日日躁夜夜躁2020 | 300部国产真实乱 | 久久精品人人做人人综合试看 | 日韩av无码一区二区三区 | 内射后入在线观看一区 | 国产激情一区二区三区 | 亚洲欧美日韩国产精品一区二区 | 77777熟女视频在线观看 а天堂中文在线官网 | 久久精品人妻少妇一区二区三区 | 国产亚洲tv在线观看 | 久久久久人妻一区精品色欧美 |