《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,如果是,則遊戲勝