cocos2d-x格鬥遊戲教程(一)
本文實踐自 Allen Tan 的文章《How To Make A Side-Scrolling Beat ‘Em Up Game Like Scott Pilgrim with Cocos2D – Part 1》,文中使用Cocos2D,我在這裏使用Cocos2D-x 2.0.4進行學習和移植。在這篇文章,將會學習到如何製作一個簡單的橫版格鬥過關遊戲。在這當中,學習如何跟蹤動畫狀態、碰撞盒、添加方向鍵、添加簡單敵人AI和更多其它的。
步驟如下:
1.新建Xcode工程,工程名爲"PompaDroid",去除"Box2D"選項,勾選"Simple Audio Engine in Cocos Denshion"選項;
2.添加遊戲場景類GameScene,派生自CCScene類。添加GameLayer類和HudLayer類,派生自CCLayer類。刪除HelloWorldScene.h和HelloWorldScene.cpp文件。
3.HudLayer類增加一個方法:
|
CREATE_FUNC(HudLayer);
|
和GameLayer類增加一個方法:
|
CREATE_FUNC(GameLayer);
|
4.文件GameScene.h代碼如下:
- #include "cocos2d.h"
- #include "GameLayer.h"
- #include "HudLayer.h"
- USING_NS_CC;
- class GameScene:public CCScene
- {
- public:
- //構造函數
- GameScene();
- //析構函數
- ~GameScene();
- virtual bool init();
- CREATE_FUNC(GameScene);
- //利用cocos2d-x中的宏定義實現Get和Set方法
- CC_SYNTHESIZE(GameLayer*, _gameLayer, GameLayer);
- CC_SYNTHESIZE(HudLayer*, _hudLayer, HudLayer);
- };
#include "cocos2d.h"
#include "GameLayer.h"
#include "HudLayer.h"
USING_NS_CC;
class GameScene:public CCScene
{
public:
//構造函數
GameScene();
//析構函數
~GameScene();
virtual bool init();
CREATE_FUNC(GameScene);
//利用cocos2d-x中的宏定義實現Get和Set方法
CC_SYNTHESIZE(GameLayer*, _gameLayer, GameLayer);
CC_SYNTHESIZE(HudLayer*, _hudLayer, HudLayer);
};
文件GameScene.cpp代碼如下:
- GameScene::GameScene(void)
- {
- //在構造函數中將指針置空防止野指針
- _gameLayer = NULL;
- _hudLayer = NULL;
- }
- GameScene::~GameScene(void)
- {
- }
- bool GameScene::init()
- {
- bool bRet = false;
- do
- {
- CC_BREAK_IF(!CCScene::init());
- //初始化遊戲層
- _gameLayer = GameLayer::create();
- //將該層加在0的位置數越小越在下面
- this->addChild(_gameLayer,0);
- //初始化遊戲的虛擬手柄層
- _hudLayer = HudLayer::create();
- //將該層加在1的位置1比0大,所以手柄層在遊戲層的上面
- this->addChild(_hudLayer,1);
- bRet = true;
- } while (0);
- return bRet;
- }
GameScene::GameScene(void)
{
//在構造函數中將指針置空防止野指針
_gameLayer = NULL;
_hudLayer = NULL;
}
GameScene::~GameScene(void)
{
}
bool GameScene::init()
{
bool bRet = false;
do
{
CC_BREAK_IF(!CCScene::init());
//初始化遊戲層
_gameLayer = GameLayer::create();
//將該層加在0的位置數越小越在下面
this->addChild(_gameLayer,0);
//初始化遊戲的虛擬手柄層
_hudLayer = HudLayer::create();
//將該層加在1的位置1比0大,所以手柄層在遊戲層的上面
this->addChild(_hudLayer,1);
bRet = true;
} while (0);
return bRet;
}
5.修改AppDelegate.cpp文件,代碼如下:
2 3 4 5 6 7 8 9 10 11 12 13 |
//#include "HelloWorldScene.h"
#include"GameScene.h" bool AppDelegate::applicationDidFinishLaunching() { //... // create a scene. it's an autorelease object //CCScene *pScene = HelloWorld::scene(); CCScene *pScene = GameScene::create(); //... } |
6.編譯運行,此時只是空空的界面。
7.下載本遊戲所需資源,將資源放置"Resources"目錄下;
8.用Tiled工具打開pd_tilemap.tmx,就可以看到遊戲的整個地圖:
地圖上有兩個圖層:Wall和Floor,即牆和地板。去掉每個圖層前的打鉤,可以查看層的組成。你會發現下數第四行是由兩個圖層一起組成的。每個tile都是32x32大小。可行走的地板tile位於下數三行。
9.打開GameLayer.h文件,添加如下代碼:
- //初始化方法
- bool init();
- //初始化地圖的方法
- void initTileMap();
- //創建地圖對象
- cocos2d::CCTMXTiledMap *_tileMap;
//初始化方法
bool init();
//初始化地圖的方法
void initTileMap();
//創建地圖對象
cocos2d::CCTMXTiledMap *_tileMap;
打開GameLayer.cpp,在構造函數,添加如下代碼:
|
_tileMap = NULL;
|
添加如下代碼:
- bool GameLayer::init()
- {
- bool bRet = false;
- do
- {
- CC_BREAK_IF(!CCLayer::init());
- //調用initTileMap()函數
- this->initTileMap();
- bRet = true;
- } while (0);
- return bRet;
- }
- void GameLayer::initTileMap()
- {
- //初始化地圖 大家在導入文件時要注意在記得AddTarget工程 否則回找不到資源,如果這個程序崩潰,找不到資源,就把資源刪了重新導入,記得AddTarget工程
- _tileMap = CCTMXTiledMap::create("pd_tilemap.tmx");
- //聲明一個CCObject對象 , 用來接受地圖中的對象
- CCObject *pObject = NULL;
- //-X爲我們提供的遍歷的方法
- CCARRAY_FOREACH(_tileMap->getChildren(), pObject)
- {
- //將地圖中每一個子節點就相當於一個對象 取出
- CCTMXLayer *child = (CCTMXLayer*)pObject;
- //取出的目的是爲了 setAliasTexParameters()消除鋸齒效果
- child->getTexture()->setAliasTexParameters();
- }
- //將瓦片地圖加在-6的位置
- this->addChild(_tileMap, -6);
- }
bool GameLayer::init()
{
bool bRet = false;
do
{
CC_BREAK_IF(!CCLayer::init());
//調用initTileMap()函數
this->initTileMap();
bRet = true;
} while (0);
return bRet;
}
void GameLayer::initTileMap()
{
//初始化地圖 大家在導入文件時要注意在記得AddTarget工程 否則回找不到資源,如果這個程序崩潰,找不到資源,就把資源刪了重新導入,記得AddTarget工程
_tileMap = CCTMXTiledMap::create("pd_tilemap.tmx");
//聲明一個CCObject對象 , 用來接受地圖中的對象
CCObject *pObject = NULL;
//-X爲我們提供的遍歷的方法
CCARRAY_FOREACH(_tileMap->getChildren(), pObject)
{
//將地圖中每一個子節點就相當於一個對象 取出
CCTMXLayer *child = (CCTMXLayer*)pObject;
//取出的目的是爲了 setAliasTexParameters()消除鋸齒效果
child->getTexture()->setAliasTexParameters();
}
//將瓦片地圖加在-6的位置
this->addChild(_tileMap, -6);
}
對所有圖層進行setAliasTexParameters設置,該方法是關閉抗鋸齒功能,這樣就能保持像素風格。
10.編譯運行,可以看到地圖顯示在屏幕上,如下圖所示:
11.創建英雄。在大多數2D橫版遊戲中,角色有不同的動畫代表不同類型的動作。我們需要知道什麼時候播放哪個動畫。這裏採用狀態機來解決這個問題。狀態機就是某種通過切換狀態來改變行爲的東西。單一狀態機在同一時間只能有一個狀態,但可以從一種狀態過渡到另一種狀態。在這個遊戲中,角色共有五種狀態,空閒、行走、出拳、受傷、死亡,如下圖所示:
爲了有一個完整的狀態流,每個狀態應該有一個必要條件和結果。例如:行走狀態不能突然轉變到死亡狀態,因爲你的英雄在死亡前必須先受傷。
12.新建一個頭文件Defines.h,代碼如下:
- // 1 - convenience measurements
- //得到屏幕的尺寸
- #define SCREEN CCDirector::sharedDirector()->getWinSize()
- //得到中心點
- #define CENTER ccp(SCREEN.width / 2, SCREEN.height / 2)
- #define CURTIME do { \
- timeval time; \
- gettimeofday(&time, NULL); \
- unsigned long millisecs = (time.tv_sec * 1000) + (time.tv_usec / 1000); \
- return (float)millisecs; \
- } while (0)
- //返回隨機的整形或者浮點型
- // 2 - convenience functions
- #define random_range(low, high) (rand() % (high - low + 1)) + low
- #define frandom (float)rand() / UINT64_C(0x100000000)
- #define frandom_range(low, high) ((high - low) * frandom) + low
- //設置枚舉狀態
- // 3 - enumerations
- typedef enum _ActionState {
- kActionStateNone = 0,
- kActionStateIdle,
- kActionStateAttack,
- kActionStateWalk,
- kActionStateHurt,
- kActionStateKnockedOut
- } ActionState;
- //碰撞檢測
- // 4 - structures
- typedef struct _BoundingBox {
- cocos2d::CCRect actual;
- cocos2d::CCRect original;
- } BoundingBox;
- #endif /* defined(__HeroGame1__Defines__) */
// 1 - convenience measurements
//得到屏幕的尺寸
#define SCREEN CCDirector::sharedDirector()->getWinSize()
//得到中心點
#define CENTER ccp(SCREEN.width / 2, SCREEN.height / 2)
#define CURTIME do { \
timeval time; \
gettimeofday(&time, NULL); \
unsigned long millisecs = (time.tv_sec * 1000) + (time.tv_usec / 1000); \
return (float)millisecs; \
} while (0)
//返回隨機的整形或者浮點型
// 2 - convenience functions
#define random_range(low, high) (rand() % (high - low + 1)) + low
#define frandom (float)rand() / UINT64_C(0x100000000)
#define frandom_range(low, high) ((high - low) * frandom) + low
//設置枚舉狀態
// 3 - enumerations
typedef enum _ActionState {
kActionStateNone = 0,
kActionStateIdle,
kActionStateAttack,
kActionStateWalk,
kActionStateHurt,
kActionStateKnockedOut
} ActionState;
//碰撞檢測
// 4 - structures
typedef struct _BoundingBox {
cocos2d::CCRect actual;
cocos2d::CCRect original;
} BoundingBox;
#endif /* defined(__HeroGame1__Defines__) */
簡要說明下:
①.定義了一些便利的宏,如直接使用SCREEN獲取屏幕大小;
②.定義了一些便利的函數,隨機返回整型或者浮點型;
③.定義ActionState類型,這個是ActionSprite可能處在不同狀態的類型枚舉;
④.定義BoundingBox結構體,將用於碰撞檢測。
添加ActionSprite類,派生自CCSprite類,ActionSprite.h文件代碼如下:
- #include "cocos2d.h"
- #include "Defines.h"
- //這個類作爲主人公和敵人的基類包含一些公有方法
- class ActionSprite : public cocos2d::CCSprite
- {
- public:
- //構造函數
- ActionSprite(void);
- //析構函數
- ~ActionSprite(void);
- //動作狀態
- //action methods
- //站立狀態
- void idle();
- //攻擊狀態
- void attack();
- //受傷狀態
- void hurtWithDamage(float damage);
- //死亡狀態
- void knockout();
- //行走狀態,後面的參數目的地的座標
- void walkWithDirection(cocos2d::CCPoint direction);
- //scheduled methods
- //每幀更新的方法
- void update(float dt);
- //actions 創建動作集合的set和get方法
- CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _idleAction, IdleAction);
- CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _attackAction, AttackAction);
- CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _walkAction, WalkAction);
- CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _hurtAction, HurtAction);
- CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _knockedOutAction, KnockedOutAction);
- //states
- //狀態的枚舉這個枚舉定義在Defines.h中 要記得導入頭文件
- CC_SYNTHESIZE(ActionState, _actionState, ActionState);
- //attributes
- //行走速度
- CC_SYNTHESIZE(float, _walkSpeed, WalkSpeed);
- CC_SYNTHESIZE(float, _hitPoints, HitPoints);
- CC_SYNTHESIZE(float, _damage, Damage);
- //movement 用於計算精靈如何沿着地圖移動
- //速度
- CC_SYNTHESIZE(cocos2d::CCPoint, _velocity, Velocity);
- //目的地
- CC_SYNTHESIZE(cocos2d::CCPoint, _desiredPosition, DesiredPosition);
- //measurements 保存對精靈的實際圖像有用的測量值。需要這些值,是因爲你將要使用的這些精靈畫布大小是遠遠大於內部包含的圖像
- CC_SYNTHESIZE(float, _centerToSides, CenterToSides);
- CC_SYNTHESIZE(float, _centerToBottom, CenterToBottom);
- };
#include "cocos2d.h"
#include "Defines.h"
//這個類作爲主人公和敵人的基類包含一些公有方法
class ActionSprite : public cocos2d::CCSprite
{
public:
//構造函數
ActionSprite(void);
//析構函數
~ActionSprite(void);
//動作狀態
//action methods
//站立狀態
void idle();
//攻擊狀態
void attack();
//受傷狀態
void hurtWithDamage(float damage);
//死亡狀態
void knockout();
//行走狀態,後面的參數目的地的座標
void walkWithDirection(cocos2d::CCPoint direction);
//scheduled methods
//每幀更新的方法
void update(float dt);
//actions 創建動作集合的set和get方法
CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _idleAction, IdleAction);
CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _attackAction, AttackAction);
CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _walkAction, WalkAction);
CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _hurtAction, HurtAction);
CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _knockedOutAction, KnockedOutAction);
//states
//狀態的枚舉這個枚舉定義在Defines.h中 要記得導入頭文件
CC_SYNTHESIZE(ActionState, _actionState, ActionState);
//attributes
//行走速度
CC_SYNTHESIZE(float, _walkSpeed, WalkSpeed);
CC_SYNTHESIZE(float, _hitPoints, HitPoints);
CC_SYNTHESIZE(float, _damage, Damage);
//movement 用於計算精靈如何沿着地圖移動
//速度
CC_SYNTHESIZE(cocos2d::CCPoint, _velocity, Velocity);
//目的地
CC_SYNTHESIZE(cocos2d::CCPoint, _desiredPosition, DesiredPosition);
//measurements 保存對精靈的實際圖像有用的測量值。需要這些值,是因爲你將要使用的這些精靈畫布大小是遠遠大於內部包含的圖像
CC_SYNTHESIZE(float, _centerToSides, CenterToSides);
CC_SYNTHESIZE(float, _centerToBottom, CenterToBottom);
};
打開ActionSprite.cpp文件,構造函數、析構函數、Update函數如下:
2 3 4 5 6 7 8 |
ActionSprite::ActionSprite(void)
{ _idleAction = NULL; _attackAction = NULL; _walkAction = NULL; _hurtAction = NULL; _knockedOutAction = NULL; } |
- ActionSprite::~ActionSprite()
- {
- }
- void ActionSprite::update(float dt)
- {
- }
ActionSprite::~ActionSprite()
{
}
void ActionSprite::update(float dt)
{
}
各個方法實現暫時爲空。以上代碼聲明瞭基本變量和方法,可以分爲以下幾類:
-
Actions:這些是每種狀態要執行的動作。這些動作是當角色切換狀態時,執行精靈動畫和其他觸發的事件。
States:保存精靈的當前動作/狀態,使用ActionState類型,這個類型待會我們將會進行定義。
Attributes:包含精靈行走速度值,受傷時減少生命點值,攻擊傷害值。
Movement:用於計算精靈如何沿着地圖移動。
Measurements:保存對精靈的實際圖像有用的測量值。需要這些值,是因爲你將要使用的這些精靈畫布大小是遠遠大於內部包含的圖像。
Action methods:不直接調用動作,而是使用這些方法觸發每種狀態。
Scheduled methods:任何事需要在一定的時間間隔進行運行,比如精靈位置和速度的更新,等等。打開GameLayer.h文件,添加如下代碼:
- //創建動畫表單集合對象
- cocos2d::CCSpriteBatchNode *_actors;
//創建動畫表單集合對象 cocos2d::CCSpriteBatchNode *_actors;
打開GameLayer.cpp文件,在init函數裏面添加如下代碼:
- //加載精靈表單,這個精靈表單包含我們的所有精靈
- CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("pd_sprites.plist");
- //創建一個CCSpriteBatchNode。
- _actors = CCSpriteBatchNode::create("pd_sprites.pvr.ccz");
- //setAliasTexParameters()消除鋸齒效果
- _actors->getTexture()->setAliasTexParameters();
- //它的z值高於CCTMXTiledMap對象,這樣才能出現在地圖前。 Z軸爲-5
- this->addChild(_actors, -5);
//加載精靈表單,這個精靈表單包含我們的所有精靈
CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("pd_sprites.plist");
//創建一個CCSpriteBatchNode。
_actors = CCSpriteBatchNode::create("pd_sprites.pvr.ccz");
//setAliasTexParameters()消除鋸齒效果
_actors->getTexture()->setAliasTexParameters();
//它的z值高於CCTMXTiledMap對象,這樣才能出現在地圖前。 Z軸爲-5
this->addChild(_actors, -5);
加載精靈表單,創建一個CCSpriteBatchNode。這個精靈表單包含我們的所有精靈。它的z值高於CCTMXTiledMap對象,這樣才能出現在地圖前。
添加Hero類,派生自ActionSprite類,添加如下代碼:
2 |
CREATE_FUNC(Hero);
bool init(); |
Hero類的init函數的實現如下所示:
- bool Hero::init()
- {
- bool bRet = false;
- do
- {
- CC_BREAK_IF(!ActionSprite::initWithSpriteFrameName("hero_idle_00.png"));
- int i;
- //idle animation 創建休息時的動畫數組
- CCArray *idleFrames = CCArray::createWithCapacity(6);
- //用循環從精靈表單中取圖片
- for (i = 0; i <6; i++)
- {
- //從精靈表單中去圖片 ,取出的類型爲CCSpriteFrame是一個精靈幀,可以簡單理解爲一個精靈圖片
- CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("hero_idle_%02d.png", i)->getCString());
- //將精靈幀放入數組
- idleFrames->addObject(frame);
- }
- //創建動畫對象 createWithSpriteFrames方法參數的意思第一個參數動畫數組,第二個參數是每個圖片停留的時間 ,就是說該數越小動畫越快
- CCAnimation *idleAnimation = CCAnimation::createWithSpriteFrames(idleFrames,1.0 / 12.0);
- //對該動作進行設置CCRepeatForever永遠執行 CCAnimate執行的動畫
- this->setIdleAction(CCRepeatForever::create(CCAnimate::create(idleAnimation)));
- //從精靈中心到底部的距離
- this->setCenterToBottom(39.0);
- //從精靈中心到邊界的距離
- this->setCenterToSides(29.0);
- //攻擊範圍
- this->setHitPoints(100.0);
- //傷害力度
- this->setDamage(20.0);
- //行動速度
- this->setWalkSpeed(80.0);
- bRet = true;
- } while (0);
- return bRet;
- }
bool Hero::init()
{
bool bRet = false;
do
{
CC_BREAK_IF(!ActionSprite::initWithSpriteFrameName("hero_idle_00.png"));
int i;
//idle animation 創建休息時的動畫數組
CCArray *idleFrames = CCArray::createWithCapacity(6);
//用循環從精靈表單中取圖片
for (i = 0; i <6; i++)
{
//從精靈表單中去圖片 ,取出的類型爲CCSpriteFrame是一個精靈幀,可以簡單理解爲一個精靈圖片
CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("hero_idle_%02d.png", i)->getCString());
//將精靈幀放入數組
idleFrames->addObject(frame);
}
//創建動畫對象 createWithSpriteFrames方法參數的意思第一個參數動畫數組,第二個參數是每個圖片停留的時間 ,就是說該數越小動畫越快
CCAnimation *idleAnimation = CCAnimation::createWithSpriteFrames(idleFrames,1.0 / 12.0);
//對該動作進行設置CCRepeatForever永遠執行 CCAnimate執行的動畫
this->setIdleAction(CCRepeatForever::create(CCAnimate::create(idleAnimation)));
//從精靈中心到底部的距離
this->setCenterToBottom(39.0);
//從精靈中心到邊界的距離
this->setCenterToSides(29.0);
//攻擊範圍
this->setHitPoints(100.0);
//傷害力度
this->setDamage(20.0);
//行動速度
this->setWalkSpeed(80.0);
bRet = true;
} while (0);
return bRet;
}
我們用初始空閒精靈幀創建了英雄角色,配備了一個CCArray數組包含所有的屬於空閒動畫的精靈幀,然後創建一個CCAction動作播放來這個動畫。以每秒12幀的速率進行播放。接下去,爲英雄設置初始屬性,包括精靈中心到邊到底部的值。如下圖所示:
英雄的每個精靈幀都在280x150像素大小的畫布上創建,但實際上英雄精靈只佔據這個空間的一部分。所以需要兩個測量值,以便更好的設置精靈的位置。需要額外的空間,是因爲每個動畫精靈繪製的方式是不同的,而有些就需要更多的空間。
打開GameLayer.h文件,添加頭文件聲明:
|
#include"Hero.h"
|
GameLayer類添加如下代碼:
|
Hero *_hero;
|
- void initHero();
void initHero();
打開GameLayer.cpp文件,在構造函數添加如下代碼:
|
_hero = NULL;
|
在init函數this->addChild(_actors, -5);後面添加如下代碼:
|
this->initHero();
|
添加initHero方法,代碼如下:
- void GameLayer::initHero()
- {
- //創建英雄對象
- _hero = Hero::create();
- //讓精靈顯示在屏幕上
- _actors->addChild(_hero);
- //設置英雄的位置
- _hero->setPosition(ccp(_hero->getCenterToSides(), 80));
- //設置目標位置,因爲是初始位置,所以就是英雄當前位置
- _hero->setDesiredPosition(_hero->getPosition());
- //初始動畫爲idle()
- _hero->idle();
- }
void GameLayer::initHero()
{
//創建英雄對象
_hero = Hero::create();
//讓精靈顯示在屏幕上
_actors->addChild(_hero);
//設置英雄的位置
_hero->setPosition(ccp(_hero->getCenterToSides(), 80));
//設置目標位置,因爲是初始位置,所以就是英雄當前位置
_hero->setDesiredPosition(_hero->getPosition());
//初始動畫爲idle()
_hero->idle();
}
創建了一個英雄實例,添加到了精靈表單,並設置了設置。調用idle方法,讓其處於空閒狀態,運行空閒動畫。返回到ActionSprite.cpp文件,實現idle方法,代碼如下:
- void ActionSprite::idle()
- {
- //這個idle方法只有當ActionSprite不處於空閒狀態才能調用。當它觸發時,它會執行空閒動作,改變當前狀態到kActionStateIdle,並且把速度置零。
- if (_actionState != kActionStateIdle)
- {
- //讓所有動作停止
- this->stopAllActions();
- //執行動作
- this->runAction(_idleAction);
- //設置動作狀態的標記
- _actionState = kActionStateIdle;
- //速度置爲零
- _velocity = CCPointZero;
- }
- }
void ActionSprite::idle()
{
//這個idle方法只有當ActionSprite不處於空閒狀態才能調用。當它觸發時,它會執行空閒動作,改變當前狀態到kActionStateIdle,並且把速度置零。
if (_actionState != kActionStateIdle)
{
//讓所有動作停止
this->stopAllActions();
//執行動作
this->runAction(_idleAction);
//設置動作狀態的標記
_actionState = kActionStateIdle;
//速度置爲零
_velocity = CCPointZero;
}
}
這個idle方法只有當ActionSprite不處於空閒狀態才能調用。當它觸發時,它會執行空閒動作,改變當前狀態到kActionStateIdle,並且把速度置零。
13.編譯運行,可以看到英雄處於空閒狀態。如下圖所示:
14.出拳動作。打開Hero.cpp文件,在init函數idle
animation後面,添加如下代碼:
- //這個方法與上面的方法基本是一個意思,只是加載的圖片名就好
- //attack animation
- CCArray *attackFrames = CCArray::createWithCapacity(3);
- for (i = 0; i <3; i++)
- {
- CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("hero_attack_00_%02d.png", i)->getCString());
- attackFrames->addObject(frame);
- }
- CCAnimation *attackAnimation = CCAnimation::createWithSpriteFrames(attackFrames, 1.0 / 24.0);
- //這裏有些不同 執行完動畫後有一個回調方法CCCallFunc ,這個方法再次調用idle方法
- this->setAttackAction(CCSequence::create(CCAnimate::create(attackAnimation), CCCallFunc::create(this, callfunc_selector(Hero::idle)),NULL));
//這個方法與上面的方法基本是一個意思,只是加載的圖片名就好
//attack animation
CCArray *attackFrames = CCArray::createWithCapacity(3);
for (i = 0; i <3; i++)
{
CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("hero_attack_00_%02d.png", i)->getCString());
attackFrames->addObject(frame);
}
CCAnimation *attackAnimation = CCAnimation::createWithSpriteFrames(attackFrames, 1.0 / 24.0);
//這裏有些不同 執行完動畫後有一個回調方法CCCallFunc ,這個方法再次調用idle方法
this->setAttackAction(CCSequence::create(CCAnimate::create(attackAnimation), CCCallFunc::create(this, callfunc_selector(Hero::idle)),NULL));
打開ActionSprite.cpp文件,實現attack方法,代碼如下:
- void ActionSprite::attack()
- {
- //英雄只有在空閒、攻擊、行走狀態才能進行出拳。確保英雄正在受傷時、或者死亡時不能進行攻擊。
- if (_actionState == kActionStateIdle || _actionState == kActionStateAttack || _actionState == kActionStateWalk)
- {
- //執行其他動作前要先,停止所有的動作
- this->stopAllActions();
- //執行攻擊的動作
- this->runAction(_attackAction);
- //重置英雄的狀態
- _actionState = kActionStateAttack;
- }
- }
void ActionSprite::attack()
{
//英雄只有在空閒、攻擊、行走狀態才能進行出拳。確保英雄正在受傷時、或者死亡時不能進行攻擊。
if (_actionState == kActionStateIdle || _actionState == kActionStateAttack || _actionState == kActionStateWalk)
{
//執行其他動作前要先,停止所有的動作
this->stopAllActions();
//執行攻擊的動作
this->runAction(_attackAction);
//重置英雄的狀態
_actionState = kActionStateAttack;
}
}
英雄只有在空閒、攻擊、行走狀態才能進行出拳。確保英雄正在受傷時、或者死亡時不能進行攻擊。爲了觸發attack方法,打開GameLayer.cpp文件,在init函數添加如下代碼:
|
this->setTouchEnabled(true); //開啓觸摸事件,多點觸摸
|
重載ccTouchesBegan方法,代碼如下:
- //添加這個方法後注意要ccTouch es 注意要有es 這個是多點觸摸的回調方法 之後還會報錯,因爲沒有在.h文件中聲明,將該方法複製在.h文件中聲明。
- void GameLayer::ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent)
- {
- _hero->attack();
- }
//添加這個方法後注意要ccTouch es 注意要有es 這個是多點觸摸的回調方法 之後還會報錯,因爲沒有在.h文件中聲明,將該方法複製在.h文件中聲明。
void GameLayer::ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent)
{
_hero->attack();
}
在GameLayer.h添加如下代碼
- void ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent);
void ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent);
15.編譯運行,點擊屏幕進行出拳,如下圖所示:
代碼例子 http://vdisk.weibo.com/s/BDn59yfnBVk57
16.創建8個方向的方向鍵。我們需要創建虛擬的8個方向的方向鍵來讓英雄在地圖上進行移動。添加SimpleDPad類,派生自CCSprite類,SimpleDPad.h文件代碼如下:
- #include "cocos2d.h"
- class SimpleDPad;
- class SimpleDPadDelegate
- {
- public:
- //改變
- virtual void didChangeDirectionTo(SimpleDPad *simpleDPad, cocos2d::CCPoint direction) =0;
- //保持
- virtual void isHoldingDirection(SimpleDPad *simpleDPad, cocos2d::CCPoint direction) =0;
- //觸摸結束
- virtual void simpleDPadTouchEnded(SimpleDPad *simpleDPad) =0;
- };
- //SimpleDPad也遵循一種協議,即CCTargetedTouchDelegate。當SimpleDPad被觸摸時,進行處理觸摸事件,而GameLayer將不會得到觸摸。 否則的話,在觸摸方向鍵的時候,英雄就會出拳攻擊,顯然,這不是希望看到的。
- class SimpleDPad : public cocos2d::CCSprite, public cocos2d::CCTargetedTouchDelegate
- {
- public:
- SimpleDPad(void);
- ~SimpleDPad(void);
- //初始化方法
- static SimpleDPad* dPadWithFile(cocos2d::CCString *fileName,float radius);
- bool initWithFile(cocos2d::CCString *filename,float radius);
- void onEnterTransitionDidFinish();
- void onExit();
- void update(float dt);
- virtual bool ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
- virtual void ccTouchMoved(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
- virtual void ccTouchEnded(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
- void updateDirectionForTouchLocation(cocos2d::CCPoint location);
- //虛擬手柄的代理
- CC_SYNTHESIZE(SimpleDPadDelegate*, _delegate, Delegate);
- //isHeld:布爾值表示玩家觸摸着方向鍵。
- CC_SYNTHESIZE(bool, _isHeld, IsHeld);
- protected:
- //虛擬手柄的半徑
- float _radius;
- //當前所按下的方向。這是一個矢量,(-1.0, -1.0)是左下方向,(1.0, 1.0)是右上方向。
- cocos2d::CCPoint _direction;
- };
#include "cocos2d.h"
class SimpleDPad;
class SimpleDPadDelegate
{
public:
//改變
virtual void didChangeDirectionTo(SimpleDPad *simpleDPad, cocos2d::CCPoint direction) =0;
//保持
virtual void isHoldingDirection(SimpleDPad *simpleDPad, cocos2d::CCPoint direction) =0;
//觸摸結束
virtual void simpleDPadTouchEnded(SimpleDPad *simpleDPad) =0;
};
//SimpleDPad也遵循一種協議,即CCTargetedTouchDelegate。當SimpleDPad被觸摸時,進行處理觸摸事件,而GameLayer將不會得到觸摸。 否則的話,在觸摸方向鍵的時候,英雄就會出拳攻擊,顯然,這不是希望看到的。
class SimpleDPad : public cocos2d::CCSprite, public cocos2d::CCTargetedTouchDelegate
{
public:
SimpleDPad(void);
~SimpleDPad(void);
//初始化方法
static SimpleDPad* dPadWithFile(cocos2d::CCString *fileName,float radius);
bool initWithFile(cocos2d::CCString *filename,float radius);
void onEnterTransitionDidFinish();
void onExit();
void update(float dt);
virtual bool ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
virtual void ccTouchMoved(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
virtual void ccTouchEnded(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
void updateDirectionForTouchLocation(cocos2d::CCPoint location);
//虛擬手柄的代理
CC_SYNTHESIZE(SimpleDPadDelegate*, _delegate, Delegate);
//isHeld:布爾值表示玩家觸摸着方向鍵。
CC_SYNTHESIZE(bool, _isHeld, IsHeld);
protected:
//虛擬手柄的半徑
float _radius;
//當前所按下的方向。這是一個矢量,(-1.0, -1.0)是左下方向,(1.0, 1.0)是右上方向。
cocos2d::CCPoint _direction;
};
對以上的一些聲明,解釋如下:
-
radius:圓形方向鍵的半徑。
direction:當前所按下的方向。這是一個矢量,(-1.0, -1.0)是左下方向,(1.0, 1.0)是右上方向。
delegate:方向鍵的委託,後續進行介紹。
isHeld:布爾值表示玩家觸摸着方向鍵。
對於SimpleDPad類,使用了委託模式。意味着一個委託類(並非SimpleDPad),將會處理由被委託類(SimpleDPad)啓動的任務。在某些你指定的點上,主要是當涉及到處理任何遊戲相關的東西,SimpleDPad將會將職責傳遞給委託類。這使得SimpleDPad無需知道任何遊戲邏輯,從而允許你在開發任何其他遊戲時,可以進行重用。如下圖所示:
當SimpleDPad檢測到在方向鍵內的觸摸,它會計算觸摸的方向,然後發送消息到委託類指明方向。在這之後的任何事情都不是SimpleDPad所關心的了。爲了實施這個模式,SimpleDPad需要至少了解其委託的有關信息,特別是將觸摸方向傳遞給委託的方法。這是另一種設計模式:協議。可以看到SimpleDPad的委託定義了所需的方法,在這種方式中,SimpleDPad強制其委託有三個指定的方法,以便確保每當它想傳遞東西放到委託中時,它能調用這些方法中的任何一種。事實上,SimpleDPad也遵循一種協議,即CCTargetedTouchDelegate。當SimpleDPad被觸摸時,進行處理觸摸事件,而GameLayer將不會得到觸摸。否則的話,在觸摸方向鍵的時候,英雄就會出拳攻擊,顯然,這不是希望看到的。打開SimpleDPad.cpp文件,添加如下代碼:
- USING_NS_CC;
- SimpleDPad::SimpleDPad(void)
- {
- _delegate = NULL;
- }
- SimpleDPad::~SimpleDPad(void)
- {
- }
- SimpleDPad* SimpleDPad::dPadWithFile(CCString *fileName, float radius)
- {
- SimpleDPad *pRet = new SimpleDPad();
- if (pRet && pRet->initWithFile(fileName, radius))
- {
- return pRet;
- }
- else
- {
- delete pRet;
- pRet = NULL;
- return NULL;
- }
- }
- bool SimpleDPad::initWithFile(CCString *filename,float radius)
- {
- bool bRet = false;
- do
- {
- CC_BREAK_IF(!CCSprite::initWithFile(filename->getCString()));
- //設置半徑
- _radius = radius;
- //設置方向爲精靈的中心
- _direction = CCPointZero;
- //默認手柄不被觸摸
- _isHeld = false;
- //開啓每幀更新的方法
- this->scheduleUpdate();
- bRet = true;
- } while (0);
- return bRet;
- }
- //程序加載該類是註冊觸摸事件
- void SimpleDPad::onEnterTransitionDidFinish()
- {
- //這裏的三個參數,第一個:是哪個對象進行觸摸註冊,第二個:觸摸優先級 第三個是否吞噬掉本次觸摸事件 爲true時時吞噬調觸摸事件 就是不讓觸摸事件向下傳遞
- CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this,1, true);
- }
- //在程序移除該類時,移除觸摸事件的代理
- void SimpleDPad::onExit()
- {
- CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);
- }
- //update方法是當方向鍵被觸摸時,
- void SimpleDPad::update(float dt)
- {
- //_isHeld是控制手柄是否被觸摸的開關
- if (_isHeld)
- {
- //傳遞方向值到委託類 方向值爲 _direction
- _delegate->isHoldingDirection(this, _direction);
- }
- }
- //觸摸開始時執行該方法
- bool SimpleDPad::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent)
- {
- //獲取觸摸的座標點
- CCPoint location = pTouch->getLocation();
- //計算觸摸點和當前類即虛擬手柄對象中心點的距離 ,this->getPosition()獲取的是精靈中心點的座標
- float distanceSQ = ccpDistanceSQ(location, this->getPosition());
- // 判斷觸摸點是否在虛擬手柄精靈內
- if (distanceSQ <= _radius * _radius)
- {
- //updateDirectionForTouchLocation方法計算觸摸點到方向鍵中心距離值,轉換成角度,得到正確的方向值,然後傳遞值到委託。
- //因此需要將觸摸點的座標傳過去
- this->updateDirectionForTouchLocation(location);
- //開啓手柄的開關
- _isHeld = true;
- return true;
- }
- return false;
- }
- void SimpleDPad::ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent)
- {
- //獲取移動時的座標
- CCPoint location = pTouch->getLocation();
- this->updateDirectionForTouchLocation(location);
- }
- void SimpleDPad::ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent)
- {
- //當觸摸事件結束的時候方向置爲中心點
- _direction = CCPointZero;
- //手柄開關關閉
- _isHeld = false;
- //出發代理事件結束的方法
- _delegate->simpleDPadTouchEnded(this);
- }
- //CCPoint location 參數是手指觸摸屏幕的座標點
- void SimpleDPad::updateDirectionForTouchLocation(CCPoint location)
- {
- //弧度 根據觸摸點的座標與精靈中心點的座標計算出弧度
- float radians = ccpToAngle(ccpSub(location, this->getPosition()));
- //角度 根據弧度計算出角度
- float degrees = -1 * CC_RADIANS_TO_DEGREES(radians);
- if (degrees <= 22.5 && degrees >= -22.5)
- {
- //right右
- _direction = ccp(1.0,0.0);
- }
- else if (degrees >22.5 && degrees <67.5)
- {
- //bottomright右下
- _direction = ccp(1.0, -1.0);
- }
- else if (degrees >=67.5 && degrees <=112.5)
- {
- //bottom下
- _direction = ccp(0.0, -1.0);
- }
- else if (degrees >112.5 && degrees <157.5)
- {
- //bottomleft左下
- _direction = ccp(-1.0, -1.0);
- }
- else if (degrees >=157.5 || degrees <= -157.5)
- {
- //left左
- _direction = ccp(-1.0,0.0);
- }
- else if (degrees < -22.5 && degrees > -67.5)
- {
- //topright右上
- _direction = ccp(1.0,1.0);
- }
- else if (degrees <= -67.5 && degrees >= -112.5)
- {
- //top上
- _direction = ccp(0.0,1.0);
- }
- else if (degrees < -112.5 && degrees > -157.5)
- {
- //topleft左上
- _direction = ccp(-1.0,1.0);
- }
- //將得到的方向傳遞到採用代理的類中, _ direction就是得到的方向
- _delegate->didChangeDirectionTo(this, _direction);
- }
USING_NS_CC;
SimpleDPad::SimpleDPad(void)
{
_delegate = NULL;
}
SimpleDPad::~SimpleDPad(void)
{
}
SimpleDPad* SimpleDPad::dPadWithFile(CCString *fileName, float radius)
{
SimpleDPad *pRet = new SimpleDPad();
if (pRet && pRet->initWithFile(fileName, radius))
{
return pRet;
}
else
{
delete pRet;
pRet = NULL;
return NULL;
}
}
bool SimpleDPad::initWithFile(CCString *filename,float radius)
{
bool bRet = false;
do
{
CC_BREAK_IF(!CCSprite::initWithFile(filename->getCString()));
//設置半徑
_radius = radius;
//設置方向爲精靈的中心
_direction = CCPointZero;
//默認手柄不被觸摸
_isHeld = false;
//開啓每幀更新的方法
this->scheduleUpdate();
bRet = true;
} while (0);
return bRet;
}
//程序加載該類是註冊觸摸事件
void SimpleDPad::onEnterTransitionDidFinish()
{
//這裏的三個參數,第一個:是哪個對象進行觸摸註冊,第二個:觸摸優先級 第三個是否吞噬掉本次觸摸事件 爲true時時吞噬調觸摸事件 就是不讓觸摸事件向下傳遞
CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this,1, true);
}
//在程序移除該類時,移除觸摸事件的代理
void SimpleDPad::onExit()
{
CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);
}
//update方法是當方向鍵被觸摸時,
void SimpleDPad::update(float dt)
{
//_isHeld是控制手柄是否被觸摸的開關
if (_isHeld)
{
//傳遞方向值到委託類 方向值爲 _direction
_delegate->isHoldingDirection(this, _direction);
}
}
//觸摸開始時執行該方法
bool SimpleDPad::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent)
{
//獲取觸摸的座標點
CCPoint location = pTouch->getLocation();
//計算觸摸點和當前類即虛擬手柄對象中心點的距離 ,this->getPosition()獲取的是精靈中心點的座標
float distanceSQ = ccpDistanceSQ(location, this->getPosition());
// 判斷觸摸點是否在虛擬手柄精靈內
if (distanceSQ <= _radius * _radius)
{
//updateDirectionForTouchLocation方法計算觸摸點到方向鍵中心距離值,轉換成角度,得到正確的方向值,然後傳遞值到委託。
//因此需要將觸摸點的座標傳過去
this->updateDirectionForTouchLocation(location);
//開啓手柄的開關
_isHeld = true;
return true;
}
return false;
}
void SimpleDPad::ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent)
{
//獲取移動時的座標
CCPoint location = pTouch->getLocation();
this->updateDirectionForTouchLocation(location);
}
void SimpleDPad::ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent)
{
//當觸摸事件結束的時候方向置爲中心點
_direction = CCPointZero;
//手柄開關關閉
_isHeld = false;
//出發代理事件結束的方法
_delegate->simpleDPadTouchEnded(this);
}
//CCPoint location 參數是手指觸摸屏幕的座標點
void SimpleDPad::updateDirectionForTouchLocation(CCPoint location)
{
//弧度 根據觸摸點的座標與精靈中心點的座標計算出弧度
float radians = ccpToAngle(ccpSub(location, this->getPosition()));
//角度 根據弧度計算出角度
float degrees = -1 * CC_RADIANS_TO_DEGREES(radians);
if (degrees <= 22.5 && degrees >= -22.5)
{
//right右
_direction = ccp(1.0,0.0);
}
else if (degrees >22.5 && degrees <67.5)
{
//bottomright右下
_direction = ccp(1.0, -1.0);
}
else if (degrees >=67.5 && degrees <=112.5)
{
//bottom下
_direction = ccp(0.0, -1.0);
}
else if (degrees >112.5 && degrees <157.5)
{
//bottomleft左下
_direction = ccp(-1.0, -1.0);
}
else if (degrees >=157.5 || degrees <= -157.5)
{
//left左
_direction = ccp(-1.0,0.0);
}
else if (degrees < -22.5 && degrees > -67.5)
{
//topright右上
_direction = ccp(1.0,1.0);
}
else if (degrees <= -67.5 && degrees >= -112.5)
{
//top上
_direction = ccp(0.0,1.0);
}
else if (degrees < -112.5 && degrees > -157.5)
{
//topleft左上
_direction = ccp(-1.0,1.0);
}
//將得到的方向傳遞到採用代理的類中, _ direction就是得到的方向
_delegate->didChangeDirectionTo(this, _direction);
}
以上方法中,onEnterTransitionDidFinish註冊SimpleDPad委託類,onExit移除SimpleDPad委託類,update方法是當方向鍵被觸摸時,傳遞方向值到委託類。ccTouchBegan方法檢測觸摸位置是否在方向鍵圓內,如果是,則將isHeld置爲true,並更新方向值,返回true以擁有觸摸事件優先權。ccTouchMoved當觸摸點移動時,更新方向值。ccTouchEnded將isHeld置爲false,重置方向,並通知委託觸摸結束。updateDirectionForTouchLocation方法計算觸摸點到方向鍵中心距離值,轉換成角度,得到正確的方向值,然後傳遞值到委託。
打開HudLayer.h文件,添加頭文件聲明:
|
#include"SimpleDPad.h"
|
添加如下代碼:
2 |
bool init();
CC_SYNTHESIZE(SimpleDPad*, _dPad, DPad); HudLayer(); |
打開HudLayer.cpp文件,添加如下代碼:
- HudLayer::HudLayer(void)
- {
- _dPad = NULL;
- }
- bool HudLayer::init()
- {
- bool bRet = false;
- do
- {
- CC_BREAK_IF(!CCLayer::init());
- //初始化手柄虛擬手柄對象 ,第一個參數爲精靈對象,第二個參數爲半徑
- _dPad = SimpleDPad::dPadWithFile(CCString::create("pd_dpad.png"),64);
- //虛擬手柄的座標
- _dPad->setPosition(ccp(64.0,64.0));
- //透明度
- _dPad->setOpacity(100);
- //將精靈加到層上
- this->addChild(_dPad);
- bRet = true;
- } while (0);
- return bRet;
- }
HudLayer::HudLayer(void)
{
_dPad = NULL;
}
bool HudLayer::init()
{
bool bRet = false;
do
{
CC_BREAK_IF(!CCLayer::init());
//初始化手柄虛擬手柄對象 ,第一個參數爲精靈對象,第二個參數爲半徑
_dPad = SimpleDPad::dPadWithFile(CCString::create("pd_dpad.png"),64);
//虛擬手柄的座標
_dPad->setPosition(ccp(64.0,64.0));
//透明度
_dPad->setOpacity(100);
//將精靈加到層上
this->addChild(_dPad);
bRet = true;
} while (0);
return bRet;
}
以上代碼實例化SimpleDPad,並且添加到HudLayer上。現在GameScene同時控制GameLayer和HudLayer,但有時候想直接通過HudLayer訪問GameLayer。打開GameLayer.h文件,添加頭文件聲明:
2 |
#include"SimpleDPad.h"
#include"HudLayer.h" |
將GameLayer類聲明修改成如下:
|
class GameLayer : public cocos2d::CCLayer, public SimpleDPadDelegate
|
並添加以下聲明:
2 3 4 5 |
virtual void didChangeDirectionTo(SimpleDPad *simpleDPad, cocos2d::CCPoint direction);
virtual void isHoldingDirection(SimpleDPad *simpleDPad, cocos2d::CCPoint direction); virtual void simpleDPadTouchEnded(SimpleDPad *simpleDPad); //下面方法是爲了在GameLayer中添加了HudLayer的引用 就是可以對HudLayer進行操作 CC_SYNTHESIZE(HudLayer*, _hud, Hud); |
以上方法的實現暫時爲空。
在GameLayer.cpp中添加如下方法
- void didChangeDirectionTo(SimpleDPad *simpleDPad, cocos2d::CCPoint direction)
- {
- }
- void isHoldingDirection(SimpleDPad *simpleDPad, cocos2d::CCPoint direction)
- {
- }
- void simpleDPadTouchEnded(SimpleDPad *simpleDPad)
- {
- }
void didChangeDirectionTo(SimpleDPad *simpleDPad, cocos2d::CCPoint direction)
{
}
void isHoldingDirection(SimpleDPad *simpleDPad, cocos2d::CCPoint direction)
{
}
void simpleDPadTouchEnded(SimpleDPad *simpleDPad)
{
}
這樣我們就在GameLayer中添加了HudLayer的引用,同時還讓GameLayer遵循SimpleDPad所創建的協議。打開GameScene.cpp文件,在init函數this->addChild(_hudLayer,
1);後面,添加如下代碼:
2 |
//設置代理對象爲遊戲層_gameLayer _hudLayer->getDPad()->setDelegate(_gameLayer);
_gameLayer->setHud(_hudLayer); |
17.編譯運行,可以看到左下角的虛擬方向鍵,如下圖所示:
別試着壓下方向鍵,英雄不會有任何反應,因爲還未實現協議方法,這在第二部分將完成。
代碼例子 http://vdisk.weibo.com/s/BDn59yfnBVkAS
在第一篇《如何製作一個橫版格鬥過關遊戲》基礎上,增加角色運動、碰撞、敵人、AI和音樂音效,原文《How
To Make A Side-Scrolling Beat ‘Em Up Game Like Scott Pilgrim with Cocos2D – Part 2》,在這裏繼續以Cocos2d-x進行實現。有關源碼、資源等在文章下面給出了地址。
步驟如下:
1.使用上一篇的工程;
2.移動英雄。在第一部分我們創建了虛擬方向鍵,但是還未實現按下方向鍵移動英雄,現在讓我們進行實現。打開Hero.cpp文件,在init函數attack animation後面,添加如下代碼:
2 3 4 5 6 7 8 9 |
//walk animation
CCArray *walkFrames = CCArray::createWithCapacity(8); for (i = 0; i < 8; i++) { CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("hero_walk_%02d.png", i)->getCString()); walkFrames->addObject(frame); } CCAnimation *walkAnimation = CCAnimation::createWithSpriteFrames(walkFrames, float(1.0 / 12.0)); this->setWalkAction(CCRepeatForever::create(CCAnimate::create(walkAnimation))); |
打開ActionSprite.cpp文件,實現walkWithDirection方法,代碼如下:
- void ActionSprite::walkWithDirection(CCPoint direction)
- {
- //檢查前置動作狀態是否空閒
- if (_actionState == kActionStateIdle)
- {
- //停止所有動作
- this->stopAllActions();
- //執行行走的動作
- this->runAction(_walkAction);
- //標記狀態爲行走
- _actionState = kActionStateWalk;
- }
- if (_actionState == kActionStateWalk)
- {
- //根據_walkSpeed值改變精靈的速度
- _velocity = ccp(direction.x * _walkSpeed, direction.y * _walkSpeed);
- //檢查精靈的左右方向
- if (_velocity.x >= 0)
- {
- //用setScaleX來翻轉精靈 看清楚是有 setScaleX是有X的 精靈可以左右變換
- this->setScaleX(1.0);
- }
- else
- {
- this->setScaleX(-1.0);
- }
- }
- }
void ActionSprite::walkWithDirection(CCPoint direction)
{
//檢查前置動作狀態是否空閒
if (_actionState == kActionStateIdle)
{
//停止所有動作
this->stopAllActions();
//執行行走的動作
this->runAction(_walkAction);
//標記狀態爲行走
_actionState = kActionStateWalk;
}
if (_actionState == kActionStateWalk)
{
//根據_walkSpeed值改變精靈的速度
_velocity = ccp(direction.x * _walkSpeed, direction.y * _walkSpeed);
//檢查精靈的左右方向
if (_velocity.x >= 0)
{
//用setScaleX來翻轉精靈 看清楚是有 setScaleX是有X的 精靈可以左右變換
this->setScaleX(1.0);
}
else
{
this->setScaleX(-1.0);
}
}
}
這段代碼,檢查前置動作狀態是否空閒,若是的話切換動作到行走。在行走狀態時,根據_walkSpeed值改變精靈速度。同時檢查精靈的左右方向,並通過將精靈scaleX設置爲1或-1來翻轉精靈。要讓英雄的行走動作跟方向鍵聯繫起來,需要藉助方向鍵的委託:GameLayer類。打開GameLayer.cpp文件,實現如下方法:
- void GameLayer::didChangeDirectionTo(SimpleDPad *simpleDPad, CCPoint direction)
- {
- //傳遞的參數爲方向數據
- _hero->walkWithDirection(direction);
- }
- void GameLayer::isHoldingDirection(SimpleDPad *simpleDPad, CCPoint direction)
- {
- _hero->walkWithDirection(direction);
- }
- void GameLayer::simpleDPadTouchEnded(SimpleDPad *simpleDPad)
- {
- //觸摸停止後 如果之前的狀態爲行走 讓精靈變爲空閒狀態
- if (_hero->getActionState() == kActionStateWalk)
- {
- _hero->idle();
- }
- }
void GameLayer::didChangeDirectionTo(SimpleDPad *simpleDPad, CCPoint direction)
{
//傳遞的參數爲方向數據
_hero->walkWithDirection(direction);
}
void GameLayer::isHoldingDirection(SimpleDPad *simpleDPad, CCPoint direction)
{
_hero->walkWithDirection(direction);
}
void GameLayer::simpleDPadTouchEnded(SimpleDPad *simpleDPad)
{
//觸摸停止後 如果之前的狀態爲行走 讓精靈變爲空閒狀態
if (_hero->getActionState() == kActionStateWalk)
{
_hero->idle();
}
}
此時,編譯運行程序的話,通過方向鍵移動英雄,發現英雄只是原地踏步。改變英雄的位置是ActionSprite和GameLayer共同的責任。一個ActionSprite永遠不會知道它在地圖上的位置。因此,它並不知道已經到達了地圖的邊緣,它只知道它想去哪裏。而GameLayer的責任就是將它的期望位置轉換成實際的位置。打開ActionSprite.cpp文件,實現以下方法:
- void ActionSprite::update(float delta)
- {
- //在每次遊戲更新場景的時候都會進行調用,當精靈處於行走狀態時,
- if (_actionState == kActionStateWalk)
- {
- //它更新精靈的期望位置。位置+速度*時間,實際上就是意味着每秒移動X和Y點
- //這裏的this指針就是指 誰調用就代表誰,英雄調用的就代表英雄,敵人調用的就代表敵人
- _desiredPosition = ccpAdd(this->getPosition(), ccpMult(_velocity, delta));
- }
- }
void ActionSprite::update(float delta)
{
//在每次遊戲更新場景的時候都會進行調用,當精靈處於行走狀態時,
if (_actionState == kActionStateWalk)
{
//它更新精靈的期望位置。位置+速度*時間,實際上就是意味着每秒移動X和Y點
//這裏的this指針就是指 誰調用就代表誰,英雄調用的就代表英雄,敵人調用的就代表敵人
_desiredPosition = ccpAdd(this->getPosition(), ccpMult(_velocity, delta));
}
}
這個方法在每次遊戲更新場景的時候都會進行調用,當精靈處於行走狀態時,它更新精靈的期望位置。位置+速度*時間,實際上就是意味着每秒移動X和Y點。打開GameLayer.cpp文件,在init函數this->initTileMap();後面添加如下代碼:
|
this->scheduleUpdate();
|
2 3 4 |
GameLayer::~GameLayer(void)
{ this->unscheduleUpdate(); } |
- void GameLayer::update(float delta)
- {
- _hero->update(delta);
- this->updatePositions();
- this->setViewpointCenter(_hero->getPosition());
- this->reorderActors();
- }
- void GameLayer::updatePositions()
- {
- //_tileMap->getMapSize().width 得到的是地圖中橫向有多少個瓦片地圖
- float widthMap = _tileMap->getMapSize().width;
- //_tileMap->getTileSize().width 得到的時每個瓦片地圖的寬度
- float widthTile = _tileMap->getTileSize().width;
- //widthMap * widthTile 代表的是整個地圖的寬
- //widthMap * widthTile - _hero->getCenterToSides()用整個地圖的寬減去精靈中心到精靈邊界的距離
- //相減得到的值就是精靈能夠橫向移動的最大位置
- //desiredPositionX x軸的目標位置 就是在當前位置和目標
- float desiredPositionX = MAX(_hero->getCenterToSides(), _hero->getDesiredPosition().x);
- float posX = MIN( widthMap * widthTile - _hero->getCenterToSides(),desiredPositionX);
- //得到每個瓦片地圖的高
- float heightTile = _tileMap->getTileSize().height;
- //下面的3 是指屬於地板的瓦片只有3個
- float posY = MIN(3 * heightTile + _hero->getCenterToBottom(),
- MAX(_hero->getCenterToBottom(), _hero->getDesiredPosition().y));
- _hero->setPosition(ccp(posX, posY));
- }
void GameLayer::update(float delta)
{
_hero->update(delta);
this->updatePositions();
this->setViewpointCenter(_hero->getPosition());
this->reorderActors();
}
void GameLayer::updatePositions()
{
//_tileMap->getMapSize().width 得到的是地圖中橫向有多少個瓦片地圖
float widthMap = _tileMap->getMapSize().width;
//_tileMap->getTileSize().width 得到的時每個瓦片地圖的寬度
float widthTile = _tileMap->getTileSize().width;
//widthMap * widthTile 代表的是整個地圖的寬
//widthMap * widthTile - _hero->getCenterToSides()用整個地圖的寬減去精靈中心到精靈邊界的距離
//相減得到的值就是精靈能夠橫向移動的最大位置
//desiredPositionX x軸的目標位置 就是在當前位置和目標
float desiredPositionX = MAX(_hero->getCenterToSides(), _hero->getDesiredPosition().x);
float posX = MIN( widthMap * widthTile - _hero->getCenterToSides(),desiredPositionX);
//得到每個瓦片地圖的高
float heightTile = _tileMap->getTileSize().height;
//下面的3 是指屬於地板的瓦片只有3個
float posY = MIN(3 * heightTile + _hero->getCenterToBottom(),
MAX(_hero->getCenterToBottom(), _hero->getDesiredPosition().y));
_hero->setPosition(ccp(posX, posY));
}
設定GameLayer的更新方法,每次循環時,GameLayer讓英雄更新它的期望位置,然後通過以下這些值,將期望位置進行檢查是否在地圖地板的範圍內:
-
mapSize:地圖tile數量。總共有10x100個tile,但只有3x100屬於地板。
tileSize:每個tile的尺寸,在這裏是32x32像素。
GameLayer還使用到了ActionSprite的兩個測量值,centerToSides和centerToBottom,因爲ActionSprite要想保持在場景內,它的位置不能超過實際的精靈邊界。假如ActionSprite的位置在已經設置的邊界內,則GameLayer讓英雄達到期望位置,否則GameLayer會讓英雄留停在原地。
3.編譯運行,此時點擊方向鍵,移動英雄,如下圖所示:
但是,很快你就會發現英雄可以走出地圖的右邊界,然後就這樣從屏幕上消失了。
4.以上的問題,可以通過基於英雄的位置進行滾動地圖,這個方法在文章《如何製作一個基於Tile的遊戲》中有描述過。打開GameLayer.cpp文件,在update函數裏最後添加如下代碼:
|
this->setViewpointCenter(_hero->getPosition());
|
2 3 4 5 6 7 8 9 10 11 12 13 14 |
void GameLayer::setViewpointCenter(CCPoint position)
{ CCSize winSize = CCDirector::sharedDirector()->getWinSize(); int x = MAX(position.x, winSize.width / 2); int y = MAX(position.y, winSize.height / 2); x = MIN(x, (_tileMap->getMapSize().width * _tileMap->getTileSize().width) - winSize.width / 2); y = MIN(y, (_tileMap->getMapSize().height * _tileMap->getTileSize().height) - winSize.height / 2); CCPoint actualPosition = ccp(x, y); CCPoint centerOfView = ccp(winSize.width / 2, winSize.height / 2); CCPoint viewPoint = ccpSub(centerOfView, actualPosition); this->setPosition(viewPoint); } |
以上代碼讓英雄處於屏幕中心位置,當然,英雄在地圖邊界時的情況除外。編譯運行,效果如下圖所示: