1. 程式人生 > >《Cocos2d-x3.x遊戲開發之旅》學習

《Cocos2d-x3.x遊戲開發之旅》學習

1.addEventListenerWidthSceneGraphPriority函式,這個函式的兩個引數作用如下:

  •    EventListener *listener:事件監聽物件,當觸控事件發生時通過它來回調;
  •    Node *node:繫結的物件,當node物件被釋放時,監聽事件的註冊也會被取消,同時,有多個觸控事件發生時(比如幾個按鈕疊加在一起),會根據node層次優先回調(越在上面的物件越先回調);

   addEventListenerWithFixedPriority,也是用於註冊監聽事件,但這個函式需要手動指定觸控事件回撥的優先順序,並且需要手動取消監聽事件。一幫情況下,我們使用addEventListenerWidthSceneGraphPriority就可以了。

bool HelloWorld::init() {
    if (!Layer::init()) { return false; }
    
    Size visibleSize = Director::getInstance()->getVisibleSize();

    /* 建立兩個精靈,相互有重疊的部分 */
    
    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) {
        /* 註冊監聽事件時綁定了一個Node物件,在這裡就可以取出這個物件 */
        auto target = static_cast<Sprite *>(event->getCurrentTarget());
        Point pos = Director::getInstance()->convertToGL(touch->getLocationInView());

        /* 判斷單擊的座標是否在精靈的範圍內 */
        if (target->getBoundingBox().containsPoint(pos)) {
            /* 設定精靈的透明度為100 */
            target->setOpacity(100);
            return true;
        }

        return false;
    };
    listener->onTouchEnded = [](Touch *touch, Event *event) {
        /* 恢復精靈的透明度 */
        auto target = static_cast<Sprite *>(event->getCurrentTarget());
        target->setOpacity(255);
    };
    /* 註冊監聽事件,繫結精靈1 */
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, sp1);

    /*註冊監聽事件,繫結精靈2,這裡要注意,listener物件複製了一份*/
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener->clone(), sp2);
    return true;
}

      分步驟來講解:

       a) 建立兩個精靈,讓這兩個精靈剛好有部分位置是重疊的;

       b) 建立EventListenerTouchOneByOne監聽事件;

       c) 在onTouchBegan函式裡獲取事件繫結的精靈物件,判斷單擊的座標是否在精靈的範圍內,是的話,則修改精靈的透明度為100;

       d) 呼叫addEventListenerWithSceneGraphPriority函式分別新增兩個精靈的監聽事件;

     通常我們要求在單擊重疊部位的時候,只有上面的按鈕能獲得響應。要實現這個效果,很簡單,我們給listener呼叫一個函式即可:

     .listener->setSwallowTouches(true);

    setSwallowTouches函式用於設定是否吞沒事件。

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按鈕,主角可以向上彈跳;

   遊戲包含的開發要點:

  •      地圖無限滾動;
  •      精靈動作的使用;
  •      Cocostudio UI使用:按鈕、標籤、進度條;
  •      簡單的碰撞檢測;
  •      update函式的應用.

  3.1 建立實體基類

      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 建立主角類

       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 建立遊戲場景

         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();   //初始化關卡背景
    private:
        Sprite *m_bgSprite1; //背景精靈1
        Sprite *m_bgSprite2; //背景精靈2

        Player *m_player; //主角
};

#endif

         TollgateScene.cpp檔案:

bool TollgateScene::init() {
    if (!Layer::init()) { return false; }
    
    Size visibleSize = Director::getInstance()->getVisibleSize();

    /* 遊戲標題圖片 */
    Sprite *titleSprite = Sprite::create("title.png");
    titleSprite->setPosition(Point(visibleSize.width / 2, visibleSize.height - 50));
    this->addChild(titleSprite, 2);

    /* 建立主角 */
    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); // 水平翻轉精靈
    this->addChild(m_bgSprite2, 0);
}
}

       注意:呼叫addChild函式時,使用了第二個引數,這個引數代表物件的繪製層次,數值越大,物件層次越高(越遲被繪製),背景圖片應該設成較低層次,否則會把主角擋住。

    3.4 修改遊戲視窗大小

       開啟main.cpp,找到下面的程式碼:

if (!glview) {
    glview = GLView::create("LittleRunner");
    glview->setFrameSize(480, 320);
    director->setOpenGLView(glview);
}

     3.5 我們要儘量避免使用多執行緒,Cocos2d-x的Node物件提供了一個update函式,在遊戲執行的每一幀(也就是主執行緒的每一個迴圈)都會呼叫update函式。前提是我們允許它呼叫。

        程式預設是不會呼叫Node物件的update函式的,要開啟呼叫update函式的功能,只需要一句程式碼:this->scheduleUpdate()。

     3.6 開啟TollgateScene.cpp檔案,在init函式的最後加上一句程式碼:

bool TollgateScene::init() {
    /* 省略了很多程式碼 */
    this->scheduleUpdate();
    return true;
}

      scheduleUpdate函式只是開啟了這個功能,我們還必須重寫Node的update函式。現在開啟TollgateScene.h標頭檔案,增加一句程式碼:

class TollgateScene : public Layer {
    public:
        /* 又省略了很多程式碼 */
        virtual void update(float delta);
};

    然後在TollgateScene.cpp檔案裡實現update函式,如下:

void TollgateScene::update(float delta) {
    log("update");
}

      3.7 讓地圖滾動起來,修改TollgateScene的update函式,如下:

void TollgateScene::update(float delta) {
    int posX1 = m_bgSprite1->getPositionX(); //背景地圖1的X座標
    int posX2 = m_bgSprite2->getPositionX(); //背景地圖2的X座標

    int iSpeed = 1;    //地圖滾動速度
    
    /* 兩張地圖向左滾動(兩張地圖是相鄰的,所以要一起滾動,否則會出現空隙) */
    posX1 -= iSpeed;
    posX2 -= iSpeed;

    m_bgSprite1->setPositionX(posX1);
    m_bgSprite2->setPositionX(posX2);
}

        效果圖:

     

    還需要加上另外的程式碼,如下:

void TollgateScene::update(float delta) {
    int posX1 = m_bgSprite1->getPositionX(); //背景地圖1的X座標
    int posX2 = m_bgSprite2->getPositionX(); //背景地圖2的X座標
    
    int iSpeed = 1; //地圖滾動速度
    
    /* 兩張地圖向左滾動(兩張地圖是相鄰的,所以要一起滾動,否則會出現空隙)*/
    posX1 -= iSpeed;
    posX2 -= iSpeed;

    /* 地圖大小 */
    Size mapSize = m_bgSprite1->getContentSize();

    /* 當第1個地圖完全離開螢幕時,第2個地圖剛好完全出現在螢幕上,這時候就讓第1個地圖緊貼在第2個地圖後面 */
    if (posX1 <= -mapSize.width / 2) {
        posX1 = mapSize.width + mapSize.width / 2;
    }

    /* 同理,當第2個地圖完全離開螢幕時,第1個地圖剛好完全出現在螢幕上,這時候就讓第2個地圖緊貼在第1個地圖後面 */
    if (posX2 <= -mapSize.width / 2) {
        posX2 = mapSize.width + mapSize.width / 2;
    }

    m_bgSprite1->setPositionX(posX1);
    m_bgSprite2->setPositionX(posX2);
}
    

      3.8 建立跳躍按鈕

       TollgateScene.h檔案:

class TollgateScene : public Layer {
    private:
        /* 前面省略了很多程式碼 */
        void loadUI(); //載入UI
        void 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");

    /* 新增單擊監聽 */
    jumpBtn->addTouchEventListener(this, toucheventselector(TollgateScene::jumpEvent));
}

void TollgateScene::jumpEvent(Ref *, TouchEventType type) {
    switch (type) {
        case TouchEventType::TOUCH_EVENT_ENDED:
            m_player->jump();
            break;
    }
}

          兩個函式很簡單,loadUI負責載入UI檔案,並且監聽按鈕的一個單擊事件,在單擊事件jumpEvent中呼叫主角的jump函式,然後主角就能跳起來了。

         現在我們開始給主角賦予跳躍的能力,給Player類新增一些變數和函式,如下:

      Player.h檔案:

class Player : public Entity {
    public:
        Player();
        ~Player();
        CREATE_FUNC(Player); //create函式
        virtual bool init();
    public:
        void jump(); //跳躍函式
    private:
        bool m_isJumping; //標記主角是否正在跳躍
};

        Player.cpp檔案

Player::Player() {
    /*初始化主角跳躍標記為false,一定不能忘記這一步 */
    m_isJumping = false;
}
Player::~Player() {
}
bool Player::init() {
    return true;
}

void Player::jump() {
    if (getSprite() == NULL) {
        return;
    }

    /* 如果主角還在跳躍中,則不重複執行 */
    if (m_isJumping) {
        return;
    }

    /* 標記主角為跳躍狀態 */
    m_isJumping = true;

    /* 建立跳躍動作: 原地跳躍,高度為250畫素,跳躍一次 */
    auto jump = JumpBy::create(2.0f, Point(0, 0), 250, 1);

    /* 建立回撥動作,跳躍結束後修改m_isJumping標記為false */
    auto callFunc = CallFunc::create([&])(){
        m_isJumping = false;
    });
    
    /* 建立連續動作 */
    auto jumpActions = Sequence::create(jump, callFunc, NULL);

    /* 執行動作 */
    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(); //重置怪物資料
        bool isAlive(); //是否活動狀態
    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); /*設定可見*/
        m_isAlive = true; /*標記怪物為活動狀態*/
    }
}

void Monster::hide() {
    if (getSprite() != NULL) {
        setVisible(false); /*設定不可見*/
        reset();           /*重置怪物資料*/
        m_isAlive = false; /*標記怪物為非活動狀態*/
    }
}

void Monster::reset() {
    if (getSprite() != NULL) {
        /* 初始化怪物座標*/
        setPosition(Point(800+CCRANDOM_0_1()*2000, 200-CCRANDOM_0_1() * 100));
    }
}

bool Monster::isAlive() {
    return m_isAlive;
}

       3.10 建立怪物管理器

           MonsterManager.h檔案

#ifndef __MonsterManger_H__
#define __MonsterManger_H__

#include "cocos2d.h"
#include "Monster.h"
USING_NS_CC;

#define MAX_MONSTER_NUM 10 //怪物最大數量
class MonsterManger : public Node {
    public:
        CREATE_FUNC(MonsterManager);
        virtual bool init();

        virtual void update(float dt); /*重寫update函式*/
    private:
        void createMonsters(); /*建立怪物物件*/
    private:
        Vector<Monster*> m_monsterArr; /*存放怪物物件列表*/
};
#endif

           MonsterManager.cpp檔案:

#include "MonsterManager.h"
#include "Player.h"
#include "Monster.h"

bool MonsterMangager::init() {
    createMonsters();   /*建立怪物快取*/
    this->scheduleUpdate();  /*開啟update函式呼叫*/
    return true;
}

void MonsterMangager::createMonsters() {
    Monster *monster = NULL;
    Sprite *sprite = NULL;

    for (int i=0; i<MAX_MONSTER_NUM; i++) {
        /*建立怪物物件*/
        monster = Monster::create();
        monster->bindSprite(Sprite::create("monster.png"));
        monster->reset();

        /*新增怪物物件*/
        this->addChild(monster);
        
        /*儲存怪物物件到列表中,方便管理*/
        m_monsterArr.pushBack(monster);
    }
}

void MonsterManager::update(float dt) {
}

           現在,我們在TollgateScene.cpp的init函式最後面新增幾句程式碼,讓怪物管理器生效,如下:

#include "MonsterManager.h"   

bool TollgateScene::init() {
    /*省略了很多程式碼*/
       
    /*建立怪物管理器*/
    MonsterManager *monsterMgr = MonsterManager::create();
    this->addChild(monsterMgr, 4);

    return true;
}

         我們的怪物管理器還要增加一個功能,就是不斷地改變怪物的座標。修改MonsterManager.cpp的update函式,如下所示:

void MonsterManager::update(float dt) {
    for (auto monster : m_monsterArr) {
        if (monster->isAlive()) {
            /*如果怪物處於活動狀態*/
            monster->setPositionX(monster->getPositionX() - 4);

            /*如果怪物X座標小於0,則表示已經超出螢幕範圍,隱藏怪物*/
            if (monster->getPositionX() < 0) {
                monster->hide();
            }
        } else {
            /*怪物處於非活動狀態,讓怪物出場吧*/
            monster->show();
        }
    }
 }

            效果如下:

             

         3.11 怪物碰撞檢測

            如果怪物碰到主角,則扣主角的血

            給Player類加點東西,如下程式碼:

class Player : public Entity {
    /* 這裡省略了很多程式碼 */
    public:
        void jump();    //跳躍函式
        void hit();     //玩家受傷害
        int getiHP();
    private:
        bool m_isJumping;   //標記主角是否正在跳躍
        int m_iHP;   //主角血量
};

            再來看Player.cpp是如何實現這些函式的,如下:

Player::Player() {
    /*初始化主角跳躍標記為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新增一個檢測碰撞的函式,如下所示:

         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函式,以及新增一個bindPlayer函式,如下程式碼:

         MonsterManager.h檔案

class MonsterManager : public Node {
    /*這裡省略了很多程式碼*/
    public:
        /*繫結玩家物件*/
        void bindPlayer(Player *player);
    private:
        Player *m_player; /*玩家物件*/
};

         MonsterManager.cpp檔案

void MonsterManager::update(float dt) {
    /*這裡省略了很多程式碼*/
    /*如果怪物X座標小於0,則表示已經超出螢幕範圍,隱藏怪物*/
    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() {
    /*這裡省略了很多程式碼*/
    /*建立怪物管理器*/
    MonsterManager *monsterMgr = MonsterManager::create();
    this->addChild(monsterMgr, 4);
    monsterMgr->bindPlayer(m_player);

    return true;
}

         3.12 怪物碰不到主角

            修改Entity.cpp的bindSprite函式,如下:

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的左下角,於是,在金幣和實體檢測碰撞時,是以實體的寬、高和座標為準的,這樣就會造成碰撞的位置“不準確”。

             

         3.13 增加主角受傷時的動作

            修改Player.cpp的hit函式,如下:

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;
    }

    /* 建立幾種動作物件*/
    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(); /*先停止所有正在執行的動作*/
    resetData();   /*重置資料*/
    runAction(actions); /*執行動作*/
}

          3.14 最後還有個resetData函式,如下:

void Player::resetData() {
    if (m_isJumping) {
        m_isJumping = false;
    }
    setPosition(Point(200, 140);
    setScale(1.0f);
    setRotation(0);
}

                 

      3.15 建立分數標籤、血量等屬性物件

        

     這裡只需要給TollgateScene類加東西,如下:

          標頭檔案:

class TollgateScene : public Layer {
    /*忽略了很多程式碼*/
    private:
        Int m_iScore;   //分數
        Text *m_scoreLab;  //分數標籤
        LoadingBar *m_hpBar;  //血量條
};

          我們需要修改一下TollgateScene的loadUI函式,如下:

void TollgateScene::loadUI() {
    /*這裡省略了一些程式碼*/
    /*獲取控制元件物件*/
    auto jumpBtn = (Button *)Helper::seekWidgetByName(UI, "JumpBtn");
    m_scoreLab = (Text *)Helper::seekWidgetByName(UI, "scoreLab");
    m_hpBar = (LoadingBar *)Helper::seekWidgetByName(UI, "hpProgress");
    /*這裡省略了一些程式碼*/
}

          讓分數標籤和血量條起作用,我們稍微修改一下TollgateScene的update函式即可:

void TollgateScene::update(float delta) {
    /*這裡繼續省略了很多程式碼*/
    /*增加分數*/
    m_iScore += 1;

    /*修改分數標籤*/
    m_scoreLab->setText(Value(m_iScore).asString());

    /*修改血量進度*/
    m_hpBar->setPercent(m_player->getiHP() / 1000.0f * 100);
}

       

4.修改HelloWorldScene的init函式,如下程式碼:

bool HelloWorld::init() {
    if (!Layer::init()) { return false; }

    /*建立很多個小若精靈*/
    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) 確保它們都使用相同的材質和混合功能;

         (3) 不再把精靈新增SpriteBatchNode上。

    4.2 修改HelloWorldScene的init函式,程式碼如下:

bool HelloWorld::init() {
    if (!Layer::init()) { return false; }

    /*建立很多個精靈*/
    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);
    }
}

     

      怎麼才算是“連續的物件”,最簡單的解釋就是:

  •          如果節點具有相同的globalZOrder值,則是連續的;
  •          否則,如果節點具有相同的localZOrder值,則是連續的;
  •          否則,如果節點具有相同的orderOfArrival值,則是連續的;
  •          連續的節點還必須使用相同的紋理(簡單地說就是相同的圖片)。

      我們來看HelloWorldScene的init函式,程式碼如下:

bool HelloWorld::init() {
    if (!Layer::init()) { return false; }

    /*建立很多很多個精靈*/
    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函式,如下所示:

bool HelloWorld::init() {
    if (!Layer::init()) { return false; }

    /*建立一個精靈,它比較文雅*/
    auto sprite1 = Sprite::create("sprite1.png");
    sprite1->setPosition(Point(240, 160));
    this->addChild(sprite1);
    
    /*建立一個精靈,它比較霸氣*/
    auto sprite2 = Sprite::create("sprite2.png");
    sprite2->setPosition(Point(200, 160));
    this->addChild(sprite2);
}

          

    4.4  繼續修改,如下:

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函式,如下程式碼:

#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函式,如下程式碼所示:

bool HelloWorld::init() {
    if (!Layer::init()) { return false; }

    /* 建立批次渲染物件,並新增到場景裡*/
    SpriteBatchNode *batchNOde = SpriteBatchNode::create("sprite.png");
    this->addChild(batchNode);

    /*建立很多個小若精靈*/
    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.用打包前的圖片建立動畫

     HelloWorldScene.h檔案:

class HelloWorld : public cocos2d::Layer {
    public:
        /*省略了很多程式碼*/
    private:
        /*用打包前圖片建立動畫*/
        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++) {
        /*用每一張圖盤建立SpriteFrame物件*/
        frame = SpriteFrame::create(StringUtils::format("run%d.png", i), Rect(0, 0, 130, 130));
        frameVec.pushBack(frame);
    }

    /*使用SpriteFrame列表建立動畫物件*/
    Animation *animation = Animation::createWithSpriteFrames(frameVec);
    animation->setLoops(-1);
    animation->setDelayPerUnit(0.1f);

    /*將動畫包裝成一個動作*/
    Animate *action = Animate::create(animation);
    return action;
}

       

      建立動畫的步驟一般有3步:

        (1) 建立一組SpriteFrame物件,每張動畫圖片為一個SpriteFrame物件;

        (2) 用這組SpriteFrame物件建立一個Animation物件,該物件包含了動畫所需的一些配置資訊;

        (3) 我們要利用Animation物件建立一個Animate物件,Animate其實也是一個動作。精靈直接呼叫runAction函式即可執行Animate動畫。

          我們修改一下HelloWorldScene的init函式,測試一下,程式碼如下:

bool HelloWorld::init() {
    if (!Layer::init()) { return false; }
    /*先建立一個精靈*/
    Sprite *runSp = Sprite::create("run1.png");
    runSp->setPosition(Point(200, 200));
    this->addChild(runSp);

    /*動畫也是動作,精靈直接執行動畫動作即可*/
    runSp->runAction(createAnimate1());
    return true;
}

         

       在建立了Animation物件後,要設定動畫的屬性,程式碼如下:

/*使用SpriteFrame列表建立動畫物件*/
Animation *animation = Animation::createWithSpriteFrames(frameVec);
animation->setLoops(-1);
animation->setDelayPerUnit(0.1f);

         setLoops函式用於設定動畫的播放次數,將setLoops的引數設為-1,就代表迴圈播放動畫。

6.用打包後的圖片建立動畫

    HelloWorldScene.h檔案:

class HelloWorld : public cocos2d::Layer {
    public:
        /*這裡省略很多程式碼*/
    private:
        /*用打包前圖片建立動畫*/
        cocos2d::Animate *createAnimate1();

        /*用打包後的圖片建立動畫*/
        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列表建立動畫物件*/
    Animation *animation = Animation::createWithSpriteFrames(frameVec);
    animation->setLoops(-1);
    animation->setDelayPerUnit(0.1f);

    /*將動畫包裝成一個動作*/
    Animation *action = Animate::create(animation);
    
    return action;
}

7.動畫建立輔助類

   新建一個類,命名為AnimationUtil,標頭檔案程式碼如下:

#ifndef __AnimationUtil_H__
#define __AnimationUtil_H__

#include "cocos2d.h"
USING_NS_CC;

class AnimationUtil {
    public:
        /*根據檔名字字首建立動畫物件*/
        static Animation *createWithSingleFrameName(const char *name, float delay, int iLoops);

        /*根據檔名字字首建立動畫物件,指定動畫圖片數量*/
        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函式,如下:

bool HelloWorld::init() {
    if (!Layer::init()) { return false; }

    /*先建立一個精靈*/
    Sprite *runSp = Sprite::create("run1.png");
    runSp->setPosition(Point(200, 200));
    this->addChild(runSp);

    /*載入圖片幀到快取池*/
    SpriteFrameCache *frameCache = SpriteFrameCache::getInstance();
    frameCache->addSpriteFramesWithFile("boys.plist", "boys.png");

    /*用輔助工具建立動畫*/
    Animation *animation = AnimationUtil::createWithSingleFrameName("run", 0.1f, -1);
    //Animation *animation = AnimationUtil::createWithFrameNameAndNum("run", 15, 0.1f, -1);

    /*動畫也是動作,精靈直接執行動畫動作即可*/
    runSp->runAction(Animate::create(animation));

    return true;
}

        使用的步驟如下:

        (1) 載入圖片幀到快取池,因為一張打包好的圖片往往不是一種動畫,所以最好不要在建立Animation物件的函式時才載入圖片幀。

        (2) 呼叫AnimationUtil的函式建立Animation物件。

        (3) 建立Animate物件,精靈執行該動作即可。

8.《跑跑跑》

   8.1 建立跑步場景

       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 建立實體類和主角類

      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函式如下:

void TollgateScene::addPlayer(TMXTiledMap *map) {
    Size visibleSize = Director::getInstance()->getVisibleSize();

    /*建立精靈*/
    Sprite *playerSprite = Sprite::create("player.png");

    /*將精靈繫結到玩家物件上*/
    Player *mPlayer = Player::create();
    mPlayer->bindSprite(playerSprite);
    mPlayer->run();

    /*設定玩家座標*/
    mPlayer->setPosition(Point(100, visibleSize.height / 2));

    /*將玩家新增到地圖*/
    map->addChild(mPlayer);
}

     效果圖:

             

   8.3 繼續開啟TollgateScene.cpp檔案,修改addPlayer函式,如下:

/*載入物件層*/
    TMXObjectGroup *objGroup = map->getObjectGroup("objects");

    /*載入玩家座標物件*/
    ValueMap playerPointMap = objGroup->getObject("PlayerPoint");
    float playerX = playerPointMap.at("x").asFloat();
    float playerY = playerPointMap.at("y").asFloat();

    /*設定玩家座標*/
    mPlayer->setPosition(Point(playerX, playerY));

    8.4 讓主角跑

        首先給Player類增加一個函式,程式碼如下:

void Player::run() {
    SpriteFrameCache *frameCache = SpriteFrameCache::getInstance();
    frameCache->addSpriteFramesWithFile("boys.plist", "boys.png");

    SpriteFrame *frame = NULL;
    Vector<SpriteFrame *> frameList;

    /*建立精靈幀物件,新增到列表裡*/
    for (int i=1; i<=15; i++) {
        frame = frameCache->getSpriteFrameByName(StringUtils::format("run%d.png", i));
        frameList.pushBack(frame);
    }

    /*根據精靈幀物件建立動畫物件*/
    Animation *animation = Animation::createWithSpriteFrames(frameList);
    animation->setLoops(-1); //迴圈播放
    animation->setDelayPerUnit(0.08f);  //每幀播放間隔

    /*建立動畫動作*/
    Animate *animate = Animate::create(animation);
    
    /*精靈執行動作*/
    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:
        /*設定監聽物件*/
        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:
        /*設定目標座標*/
        virtual void setTagPosition(int x, int y) = 0;

        /*獲取目標座標*/
        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);

        /*設定移動速度*/
        void setiSpeed(int iSpeed);
    private:
        int m_iSpeed;
};

#endif

           SimpleMoveController.cpp檔案

bool SimpleMoveController::init() {
    this->m_iSpeed = 0;
    
    /*每一幀都要呼叫update函式,所以要這樣設定*/
    this->scheduleUpdate();

    return true;
}
void SimpleMoveController::update(float dt) {
    if (m_controllerListener == NULL) {
        return;
    }
    /*增加移動物件的X座標值*/
    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);

        /*設定控制器*/
        void setController(Controller *controller);

        /*實現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函式:

#include "SimpleMoveController.h" 

void TollgateScene::addPlayer(TMXTiledMap *map) {
    /*省略了很多很多程式碼*/
    /*--------------建立玩家簡單移動控制器--------------*/
    SimpleMoveController *simpleMoveControll = SimpleMoveController::create();

    /*設定移動速度*/
    simpleMoveControll->setiSpeed(1);

    /*控制器要新增到場景中才能讓update被呼叫*/
    this->addChild(simpleMoveControll);

    /*設定控制器到主角身上*/
    mPlayer->setController(simpleMoveControll);
}

     現在主角就會一直往前跑了,效果:

           

      8.7 讓地圖隨著主角滾動

          為Player類增加一個函式,如下:

void Player::setViewPointByPlayer() {
    if (m_sprite == NULL){
        return;
    }
    Layer *parent = (Layer *)getParent();

    /*地圖方塊數量*/
    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();

    /*主角座標*/
    Point spritePos = getPosition();

    /*如果主角座標小於螢幕的一半,則取螢幕中點座標,否則取主角的座標*/
    float x = std::max(spritePos.x, visibleSize.width / 2);
    float y = std::max(spritePos.y, visibleSize.height / 2);

    /*如果X、Y的座標大於右上角的極限值,則取極限值的座標(極限值是指不讓地圖超出螢幕造成出現黑邊的極限座標*/
    x = std::min(x, mapSize.width - visibleSize.width / 2);
    y = std::min(y, mapSize.height - visibleSize.height / 2);

    /*目標點*/
    Point destPos = Point(x, y);
    
    /*螢幕中點*/
    Point centerPos = Point(visibleSize.width / 2, visibleSize.height / 2);

    /*計算螢幕中點和所要移動的目的點之間的距離*/
    Point viewPos = centerPos - destPos;

    parent->setPosition(viewPos);
}

          這個函式的功能是讓地圖所在圖層以主角為中心進行移動,也就是讓事件的焦點停留在主角身上,螢幕隨著主角移動。

     然後,Player要重寫父類的setTagPosition函式,程式碼如下:

void Player::setTagPosition(int x, int y) {
    Entity::setTagPosition(x, y);

    /*以主角為中心移動地圖*/
    setViewPointByPlayer();
}

          在標頭檔案中加入函式宣告:

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函式,在建立Player物件之後,再呼叫Player的setTiledMap函式,如下:

void TollgateScene::addPlayer(TMXTiledMap *map) {
    /*省略了一些程式碼*/
    
    /*將精靈繫結到玩家物件上*/
    Player *mPlayer = Player::create();
    mPlayer->bindSprite(playerSprite);
    mPlayer->run();
    mPlayer->setTiledMap(map);

    /*省略了很多程式碼*/
}

       地圖會有一些細細的黑邊,可能滾動的時候特別明顯,在程式碼中加入:

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);

        /*設定X方向的移動速度*/
        void setiXSpeed(int iSpeed);

        /*設定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函式的呼叫*/
    this->scheduleUpdate();
    return true;
}

void ThreeDirectionController::update(float dt) {
    if (m_controllerListener == NULL) {
        return;
    }

    /*讓移動物件在X和Y方向上增加座標*/
    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) {
        /*獲取單擊座標,基於Cocos2d-x*/
        Point touchPos = Director::getInstance()->convertToGL(touch->getLocationInView());

        /*被控制物件的座標*/
        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座標上的移動*/
        setiYSpeed(0);
    };

    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
}

        開啟TollgateScene.cpp的addPlayer函式,將SimpleMoveController替換為ThreeDirectionController,程式碼如下:

#include "ThreeDirectionController.h"
void TollgateScene::addPlayer(TMXTiledMap *map) {
    /*又忽略了很多程式碼*/
    
    /* ---------------建立玩家移動控制器-------------*/
    ThreeDirectionController *threeMoveControll = ThreeDirectionController::create();
    threeMoveControll->setiXSpeed(1);
    threeMoveControll->setiYSpeed(0);

    /*控制器要新增到場景中才能獲得update事件*/
    this->addChild(threeMoveControll);

    /*設定控制器到主角身上*/
    mPlayer->setController(threeMoveControl);
}

         為Player新增一個函式tileCoordForPosition,程式碼如下:

     Player.h檔案:

class Player : public Entity {
    /*省略了很多程式碼*/
    private:
        /*標記主角是否碰撞了障礙物,在反彈中*/
        bool isJumping;

        /*檢測碰撞的地圖層*/
        TMXLayer *meta;

        /*將畫素座標轉換為地圖格子座標*/
        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的預設Y座標是由下至上的,所以要做一個相減操作*/
    int y = (700 - pos.y) / tiledSize.height;

    /*格子座標從零開始計算*/
    if (x > 0) {
        x -= 1;
    }
    if (y > 0) {
        y -= 0;
    }

    return Point(x, y);
}

        再次開啟Player類,修改setTiledMap函式,程式碼如下:

void Player::setTiledMap(TMXTiledMap *map) {
    this->m_map = map;
    
    /*儲存meta圖層的引用*/
    this->meta = m_map->getLayer("meta");
    this->meta->setVisible(false);
}

         最後一步,修改Player.cpp的setTagPosition函式:

void Player::setTagPosition(int x, int y) {
    /*----------------判斷前面是否不可通行------------*/
    /*取主角前方的座標*/
    Size spriteSize = m_sprite->getContentSize();
    Point dstPos = Point(x + spriteSize.widht / 2, y);

    /*獲得當前主角前方座標在地圖中的格子位置*/
    Point tiledPos = tileCoordForPosition(Point(dstPos.x, dstPos.y));

    /*獲取地圖格子的唯一標識*/
    int tiledGid = meta->getTileGIDAt(tiledPos);

    /*不為0,代表存在這個格子*/
    if (tiledGid != 0) {
        /*獲取該地圖格子的所有屬性,目前我們只有一個Collidable屬性
        格子是屬於meta層的,但同時也是屬於整個地圖的,所以在獲取格子
        的所有屬性時,通過格子唯一標識在地圖中取得*/
        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 當遇到障礙物時,不是讓主角停止前進,而是讓主角向後彈,如程式碼所示:

/*判斷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([&](){
        /*恢復狀態*/
        isJumping = false;
    });

    /*執行動作,碰撞到障礙物時的反彈效果*/
    auto actions = Sequence::create(jumpBy, callFunc, NULL);
    this->runAction(actions);
}
        

      8.10 新增能吃的物品以及勝利條件

         開啟Player.cpp的setTagPosition函式,如下:

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) {
        /*從障礙物層清除當前格子的物體*/
        TMXLayer *barrier = m_map->getLayer("barrier");
        barrier->removeTileAt(tiledPos);
    }
}

           最後,依舊修改Player的setTagPosition函式,程式碼如下:

if (propertiesMap.find("win") != propertiesMap.end()) {
    Value prop = properties.asValueMap().at("win");
    if (prop.asString().compare("true") == 0) {
        /*取得格子的win屬性值,判斷是否為true,如果是,則遊戲勝