cocos2d-x格鬥遊戲教程


    

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.hHelloWorldScene.cpp文件。
3.HudLayer類增加一個方法:

1
 
CREATE_FUNC(HudLayer);

GameLayer類增加一個方法:

1
 
CREATE_FUNC(GameLayer);

4.文件GameScene.h代碼如下:

  1. #include "cocos2d.h"  
  2. #include "GameLayer.h"  
  3. #include "HudLayer.h"  
  4. USING_NS_CC;  
  5. class GameScene:public  CCScene  
  6. {  
  7.       
  8. public:  
  9.     //構造函數  
  10.     GameScene();  
  11.     //析構函數  
  12.     ~GameScene();  
  13.       
  14.     virtual bool init();  
  15.     CREATE_FUNC(GameScene);  
  16.       
  17.     //利用cocos2d-x中的宏定義實現Get和Set方法  
  18.     CC_SYNTHESIZE(GameLayer*, _gameLayer, GameLayer);  
  19.     CC_SYNTHESIZE(HudLayer*, _hudLayer, HudLayer);  
  20. };  
#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代碼如下:

  1. GameScene::GameScene(void)  
  2. {  
  3.     //在構造函數中將指針置空防止野指針  
  4.     _gameLayer = NULL;  
  5.     _hudLayer = NULL;  
  6. }  
  7.   
  8. GameScene::~GameScene(void)  
  9. {  
  10. }  
  11.   
  12. bool GameScene::init()  
  13. {  
  14.     bool bRet = false;  
  15.     do  
  16.     {  
  17.         CC_BREAK_IF(!CCScene::init());  
  18.         //初始化遊戲層  
  19.         _gameLayer = GameLayer::create();  
  20.         //將該層加在0的位置數越小越在下面  
  21.         this->addChild(_gameLayer,0);  
  22.         //初始化遊戲的虛擬手柄層  
  23.         _hudLayer = HudLayer::create();  
  24.         //將該層加在1的位置1比0大,所以手柄層在遊戲層的上面  
  25.         this->addChild(_hudLayer,1);  
  26.           
  27.         bRet = true;  
  28.     } while (0);  
  29.       
  30.     return bRet;  
  31. }  
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文件,代碼如下:

1
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文件,添加如下代碼:

  1. //初始化方法  
  2.     bool init();  
  3.     //初始化地圖的方法  
  4.     void initTileMap();  
  5.     //創建地圖對象  
  6.     cocos2d::CCTMXTiledMap *_tileMap;  
//初始化方法
    bool init();
    //初始化地圖的方法
    void initTileMap();
    //創建地圖對象
    cocos2d::CCTMXTiledMap *_tileMap;

打開GameLayer.cpp,在構造函數,添加如下代碼:

1
 
_tileMap = NULL;

添加如下代碼:

  1. bool GameLayer::init()  
  2. {  
  3.     bool bRet = false;  
  4.     do  
  5.     {  
  6.           
  7.         CC_BREAK_IF(!CCLayer::init());  
  8.         //調用initTileMap()函數  
  9.         this->initTileMap();  
  10.           
  11.         bRet = true;  
  12.     } while (0);  
  13.       
  14.     return bRet;  
  15. }  
  16.   
  17. void GameLayer::initTileMap()  
  18. {  
  19.     //初始化地圖  大家在導入文件時要注意在記得AddTarget工程 否則回找不到資源,如果這個程序崩潰,找不到資源,就把資源刪了重新導入,記得AddTarget工程  
  20.     _tileMap = CCTMXTiledMap::create("pd_tilemap.tmx");  
  21.     //聲明一個CCObject對象 , 用來接受地圖中的對象  
  22.     CCObject *pObject = NULL;  
  23.     //-X爲我們提供的遍歷的方法  
  24.     CCARRAY_FOREACH(_tileMap->getChildren(), pObject)  
  25.     {  
  26.         //將地圖中每一個子節點就相當於一個對象 取出  
  27.         CCTMXLayer *child = (CCTMXLayer*)pObject;  
  28.         //取出的目的是爲了  setAliasTexParameters()消除鋸齒效果    
  29.         child->getTexture()->setAliasTexParameters();  
  30.     }  
  31.     //將瓦片地圖加在-6的位置  
  32.     this->addChild(_tileMap, -6);  
  33. }  
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. // 1 - convenience measurements  
  2. //得到屏幕的尺寸  
  3. #define SCREEN CCDirector::sharedDirector()->getWinSize()  
  4. //得到中心點  
  5. #define CENTER ccp(SCREEN.width / 2, SCREEN.height / 2)  
  6. #define CURTIME do {                                                        \  
  7. timeval time;                                                           \  
  8. gettimeofday(&time, NULL);                                              \  
  9. unsigned long millisecs = (time.tv_sec * 1000) + (time.tv_usec / 1000); \  
  10. return (float)millisecs;                                                \  
  11. while (0)  
  12.   
  13. //返回隨機的整形或者浮點型  
  14. // 2 - convenience functions  
  15. #define random_range(low, high) (rand() % (high - low + 1)) + low  
  16. #define frandom (float)rand() / UINT64_C(0x100000000)  
  17. #define frandom_range(low, high) ((high - low) * frandom) + low  
  18.   
  19. //設置枚舉狀態  
  20. // 3 - enumerations  
  21. typedef enum _ActionState {  
  22.     kActionStateNone = 0,  
  23.     kActionStateIdle,  
  24.     kActionStateAttack,  
  25.     kActionStateWalk,  
  26.     kActionStateHurt,  
  27.     kActionStateKnockedOut  
  28. } ActionState;  
  29.   
  30. //碰撞檢測  
  31. // 4 - structures  
  32. typedef struct _BoundingBox {  
  33.     cocos2d::CCRect actual;  
  34.     cocos2d::CCRect original;  
  35. } BoundingBox;  
  36. #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文件代碼如下:

  1. #include "cocos2d.h"  
  2. #include "Defines.h"  
  3. //這個類作爲主人公和敵人的基類包含一些公有方法  
  4. class ActionSprite : public cocos2d::CCSprite  
  5. {  
  6. public:  
  7.     //構造函數  
  8.     ActionSprite(void);  
  9.     //析構函數  
  10.     ~ActionSprite(void);  
  11.       
  12.     //動作狀態  
  13.     //action methods  
  14.     //站立狀態  
  15.     void idle();  
  16.     //攻擊狀態  
  17.     void attack();  
  18.     //受傷狀態  
  19.     void hurtWithDamage(float damage);  
  20.     //死亡狀態  
  21.     void knockout();  
  22.     //行走狀態,後面的參數目的地的座標  
  23.     void walkWithDirection(cocos2d::CCPoint direction);  
  24.       
  25.     //scheduled methods  
  26.     //每幀更新的方法  
  27.     void update(float dt);  
  28.       
  29.     //actions  創建動作集合的set和get方法  
  30.     CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _idleAction, IdleAction);  
  31.     CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _attackAction, AttackAction);  
  32.     CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _walkAction, WalkAction);  
  33.     CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _hurtAction, HurtAction);  
  34.     CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _knockedOutAction, KnockedOutAction);  
  35.       
  36.     //states  
  37.     //狀態的枚舉這個枚舉定義在Defines.h中 要記得導入頭文件  
  38.     CC_SYNTHESIZE(ActionState, _actionState, ActionState);  
  39.       
  40.     //attributes  
  41.     //行走速度  
  42.     CC_SYNTHESIZE(float, _walkSpeed, WalkSpeed);  
  43.     CC_SYNTHESIZE(float, _hitPoints, HitPoints);  
  44.     CC_SYNTHESIZE(float, _damage, Damage);  
  45.       
  46.     //movement  用於計算精靈如何沿着地圖移動  
  47.     //速度  
  48.     CC_SYNTHESIZE(cocos2d::CCPoint, _velocity, Velocity);  
  49.     //目的地  
  50.     CC_SYNTHESIZE(cocos2d::CCPoint, _desiredPosition, DesiredPosition);  
  51.       
  52.     //measurements  保存對精靈的實際圖像有用的測量值。需要這些值,是因爲你將要使用的這些精靈畫布大小是遠遠大於內部包含的圖像  
  53.       
  54.     CC_SYNTHESIZE(float, _centerToSides, CenterToSides);  
  55.     CC_SYNTHESIZE(float, _centerToBottom, CenterToBottom);  
  56. };  
#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函數如下:

1
2
3
4
5
6
7
8
 
ActionSprite::ActionSprite(void)
{
   _idleAction = NULL;
   _attackAction = NULL;
   _walkAction = NULL;
   _hurtAction = NULL;
   _knockedOutAction = NULL;
}

  1. ActionSprite::~ActionSprite()  
  2. {  
  3.       
  4. }  
  5. void ActionSprite::update(float dt)  
  6. {  
  7.       
  8. }  
ActionSprite::~ActionSprite()
{
    
}
void ActionSprite::update(float dt)
{
    
}

各個方法實現暫時爲空。以上代碼聲明瞭基本變量和方法,可以分爲以下幾類:


  • Actions:這些是每種狀態要執行的動作。這些動作是當角色切換狀態時,執行精靈動畫和其他觸發的事件。
    States:保存精靈的當前動作/狀態,使用ActionState類型,這個類型待會我們將會進行定義。
    Attributes:包含精靈行走速度值,受傷時減少生命點值,攻擊傷害值。
    Movement:用於計算精靈如何沿着地圖移動。
    Measurements:保存對精靈的實際圖像有用的測量值。需要這些值,是因爲你將要使用的這些精靈畫布大小是遠遠大於內部包含的圖像。
    Action methods:不直接調用動作,而是使用這些方法觸發每種狀態。
    Scheduled methods:任何事需要在一定的時間間隔進行運行,比如精靈位置和速度的更新,等等。

    打開GameLayer.h文件,添加如下代碼:

    1. //創建動畫表單集合對象  
    2.     cocos2d::CCSpriteBatchNode *_actors;  
    //創建動畫表單集合對象
        cocos2d::CCSpriteBatchNode *_actors;

打開GameLayer.cpp文件,在init函數裏面添加如下代碼:

  1. //加載精靈表單,這個精靈表單包含我們的所有精靈  
  2.       CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("pd_sprites.plist");  
  3.       //創建一個CCSpriteBatchNode。  
  4.       _actors = CCSpriteBatchNode::create("pd_sprites.pvr.ccz");  
  5.       //setAliasTexParameters()消除鋸齒效果  
  6.       _actors->getTexture()->setAliasTexParameters();  
  7.       //它的z值高於CCTMXTiledMap對象,這樣才能出現在地圖前。 Z軸爲-5  
  8.       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類,添加如下代碼:

1
2
 
CREATE_FUNC(Hero);
bool init();

Hero類的init函數的實現如下所示:


  1. bool Hero::init()  
  2. {  
  3.     bool bRet = false;  
  4.     do  
  5.     {  
  6.         CC_BREAK_IF(!ActionSprite::initWithSpriteFrameName("hero_idle_00.png"));  
  7.           
  8.         int i;  
  9.         //idle animation  創建休息時的動畫數組   
  10.         CCArray *idleFrames = CCArray::createWithCapacity(6);  
  11.         //用循環從精靈表單中取圖片  
  12.         for (i = 0; i <6; i++)  
  13.         {  
  14.             //從精靈表單中去圖片 ,取出的類型爲CCSpriteFrame是一個精靈幀,可以簡單理解爲一個精靈圖片  
  15.             CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("hero_idle_%02d.png", i)->getCString());  
  16.             //將精靈幀放入數組  
  17.             idleFrames->addObject(frame);  
  18.         }  
  19.         //創建動畫對象  createWithSpriteFrames方法參數的意思第一個參數動畫數組,第二個參數是每個圖片停留的時間 ,就是說該數越小動畫越快  
  20.         CCAnimation *idleAnimation = CCAnimation::createWithSpriteFrames(idleFrames,1.0 / 12.0);  
  21.         //對該動作進行設置CCRepeatForever永遠執行  CCAnimate執行的動畫  
  22.         this->setIdleAction(CCRepeatForever::create(CCAnimate::create(idleAnimation)));  
  23.           
  24.           
  25.         //從精靈中心到底部的距離  
  26.         this->setCenterToBottom(39.0);  
  27.         //從精靈中心到邊界的距離  
  28.         this->setCenterToSides(29.0);  
  29.         //攻擊範圍  
  30.         this->setHitPoints(100.0);  
  31.         //傷害力度  
  32.         this->setDamage(20.0);  
  33.         //行動速度  
  34.         this->setWalkSpeed(80.0);  
  35.           
  36.         bRet = true;  
  37.     } while (0);  
  38.       
  39.     return bRet;  
  40. }  
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文件,添加頭文件聲明:

1
 
#include"Hero.h"

GameLayer類添加如下代碼:

1
 
Hero *_hero;

  1. void initHero();  
  void initHero();
打開GameLayer.cpp文件,在構造函數添加如下代碼:

1
 
_hero = NULL;

init函數this->addChild(_actors, -5);後面添加如下代碼:

1
 
this->initHero();

添加initHero方法,代碼如下:

  1. void GameLayer::initHero()  
  2. {  
  3.     //創建英雄對象  
  4.     _hero = Hero::create();  
  5.     //讓精靈顯示在屏幕上  
  6.     _actors->addChild(_hero);  
  7.     //設置英雄的位置  
  8.     _hero->setPosition(ccp(_hero->getCenterToSides(), 80));  
  9.     //設置目標位置,因爲是初始位置,所以就是英雄當前位置  
  10.     _hero->setDesiredPosition(_hero->getPosition());  
  11.     //初始動畫爲idle()  
  12.     _hero->idle();  
  13. }  
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方法,代碼如下:

  1. void ActionSprite::idle()  
  2. {  
  3.     //這個idle方法只有當ActionSprite不處於空閒狀態才能調用。當它觸發時,它會執行空閒動作,改變當前狀態到kActionStateIdle,並且把速度置零。  
  4.     if (_actionState != kActionStateIdle)  
  5.     {  
  6.         //讓所有動作停止  
  7.         this->stopAllActions();  
  8.         //執行動作  
  9.         this->runAction(_idleAction);  
  10.         //設置動作狀態的標記  
  11.         _actionState = kActionStateIdle;  
  12.         //速度置爲零  
  13.         _velocity = CCPointZero;  
  14.     }  
  15. }  
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後面,添加如下代碼:

  1. //這個方法與上面的方法基本是一個意思,只是加載的圖片名就好  
  2.        //attack animation  
  3.        CCArray *attackFrames = CCArray::createWithCapacity(3);  
  4.        for (i = 0; i <3; i++)  
  5.        {  
  6.            CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("hero_attack_00_%02d.png", i)->getCString());  
  7.            attackFrames->addObject(frame);  
  8.        }  
  9.        CCAnimation *attackAnimation = CCAnimation::createWithSpriteFrames(attackFrames, 1.0 / 24.0);  
  10.        //這裏有些不同  執行完動畫後有一個回調方法CCCallFunc ,這個方法再次調用idle方法  
  11.        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方法,代碼如下:

  1. void ActionSprite::attack()  
  2. {  
  3.     //英雄只有在空閒、攻擊、行走狀態才能進行出拳。確保英雄正在受傷時、或者死亡時不能進行攻擊。  
  4.     if (_actionState == kActionStateIdle || _actionState == kActionStateAttack || _actionState == kActionStateWalk)  
  5.     {  
  6.         //執行其他動作前要先,停止所有的動作  
  7.         this->stopAllActions();  
  8.         //執行攻擊的動作  
  9.         this->runAction(_attackAction);  
  10.         //重置英雄的狀態  
  11.         _actionState = kActionStateAttack;  
  12.     }  
  13. }  
void ActionSprite::attack()
{
    //英雄只有在空閒、攻擊、行走狀態才能進行出拳。確保英雄正在受傷時、或者死亡時不能進行攻擊。
    if (_actionState == kActionStateIdle || _actionState == kActionStateAttack || _actionState == kActionStateWalk)
    {
        //執行其他動作前要先,停止所有的動作
        this->stopAllActions();
        //執行攻擊的動作
        this->runAction(_attackAction);
        //重置英雄的狀態
        _actionState = kActionStateAttack;
    }
}
英雄只有在空閒、攻擊、行走狀態才能進行出拳。確保英雄正在受傷時、或者死亡時不能進行攻擊。爲了觸發attack方法,打開GameLayer.cpp文件,在init函數添加如下代碼:

1
 
this->setTouchEnabled(true);       //開啓觸摸事件,多點觸摸

重載ccTouchesBegan方法,代碼如下:

  1. //添加這個方法後注意要ccTouch   es  注意要有es  這個是多點觸摸的回調方法 之後還會報錯,因爲沒有在.h文件中聲明,將該方法複製在.h文件中聲明。  
  2. void GameLayer::ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent)  
  3. {  
  4.     _hero->attack();  
  5. }  
//添加這個方法後注意要ccTouch   es  注意要有es  這個是多點觸摸的回調方法 之後還會報錯,因爲沒有在.h文件中聲明,將該方法複製在.h文件中聲明。
void GameLayer::ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent)
{
    _hero->attack();
}
在GameLayer.h添加如下代碼

  1. void ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent);  
void ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent);

15.編譯運行,點擊屏幕進行出拳,如下圖所示:



代碼例子    http://vdisk.weibo.com/s/BDn59yfnBVk57  





分類: cocos2d-x項目教程 774人閱讀 評論(0) 收藏 舉報

16.創建8個方向的方向鍵。我們需要創建虛擬的8個方向的方向鍵來讓英雄在地圖上進行移動。添加SimpleDPad類,派生自CCSprite類,SimpleDPad.h文件代碼如下:

  1. #include "cocos2d.h"  
  2.   
  3. class SimpleDPad;  
  4. class SimpleDPadDelegate  
  5. {  
  6. public:  
  7.     //改變  
  8.     virtual void didChangeDirectionTo(SimpleDPad *simpleDPad, cocos2d::CCPoint direction) =0;  
  9.     //保持  
  10.     virtual void isHoldingDirection(SimpleDPad *simpleDPad, cocos2d::CCPoint direction) =0;  
  11.     //觸摸結束  
  12.     virtual void simpleDPadTouchEnded(SimpleDPad *simpleDPad) =0;  
  13. };  
  14.   
  15. //SimpleDPad也遵循一種協議,即CCTargetedTouchDelegate。當SimpleDPad被觸摸時,進行處理觸摸事件,而GameLayer將不會得到觸摸。 否則的話,在觸摸方向鍵的時候,英雄就會出拳攻擊,顯然,這不是希望看到的。  
  16. class SimpleDPad : public cocos2d::CCSprite, public cocos2d::CCTargetedTouchDelegate  
  17. {  
  18. public:  
  19.     SimpleDPad(void);  
  20.     ~SimpleDPad(void);  
  21.     //初始化方法  
  22.     static SimpleDPad* dPadWithFile(cocos2d::CCString *fileName,float radius);  
  23.     bool initWithFile(cocos2d::CCString *filename,float radius);  
  24.       
  25.       
  26.     void onEnterTransitionDidFinish();  
  27.     void onExit();  
  28.     void update(float dt);  
  29.       
  30.     virtual bool ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);  
  31.     virtual void ccTouchMoved(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);  
  32.     virtual void ccTouchEnded(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);  
  33.       
  34.     void updateDirectionForTouchLocation(cocos2d::CCPoint location);  
  35.     //虛擬手柄的代理  
  36.     CC_SYNTHESIZE(SimpleDPadDelegate*, _delegate, Delegate);  
  37.     //isHeld:布爾值表示玩家觸摸着方向鍵。  
  38.     CC_SYNTHESIZE(bool, _isHeld, IsHeld);  
  39.       
  40. protected:  
  41.     //虛擬手柄的半徑  
  42.     float _radius;  
  43.     //當前所按下的方向。這是一個矢量,(-1.0, -1.0)是左下方向,(1.0, 1.0)是右上方向。  
  44.     cocos2d::CCPoint _direction;  
  45. };  
#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文件,添加如下代碼:


  1. USING_NS_CC;  
  2. SimpleDPad::SimpleDPad(void)  
  3. {  
  4.     _delegate = NULL;  
  5. }  
  6.   
  7. SimpleDPad::~SimpleDPad(void)  
  8. {  
  9. }  
  10.   
  11. SimpleDPad* SimpleDPad::dPadWithFile(CCString *fileName, float radius)  
  12. {  
  13.     SimpleDPad *pRet = new SimpleDPad();  
  14.     if (pRet && pRet->initWithFile(fileName, radius))  
  15.     {  
  16.         return pRet;  
  17.     }  
  18.     else  
  19.     {  
  20.         delete pRet;  
  21.         pRet = NULL;  
  22.         return NULL;  
  23.     }  
  24. }  
  25.   
  26. bool SimpleDPad::initWithFile(CCString *filename,float radius)  
  27. {  
  28.     bool bRet = false;  
  29.     do  
  30.     {  
  31.         CC_BREAK_IF(!CCSprite::initWithFile(filename->getCString()));  
  32.         //設置半徑  
  33.         _radius = radius;  
  34.         //設置方向爲精靈的中心  
  35.         _direction = CCPointZero;  
  36.         //默認手柄不被觸摸  
  37.         _isHeld = false;  
  38.         //開啓每幀更新的方法  
  39.         this->scheduleUpdate();  
  40.           
  41.         bRet = true;  
  42.     } while (0);  
  43.       
  44.     return bRet;  
  45. }  
  46. //程序加載該類是註冊觸摸事件  
  47. void SimpleDPad::onEnterTransitionDidFinish()  
  48. {  
  49.     //這裏的三個參數,第一個:是哪個對象進行觸摸註冊,第二個:觸摸優先級   第三個是否吞噬掉本次觸摸事件  爲true時時吞噬調觸摸事件 就是不讓觸摸事件向下傳遞  
  50.     CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this,1, true);  
  51. }  
  52. //在程序移除該類時,移除觸摸事件的代理  
  53. void SimpleDPad::onExit()  
  54. {  
  55.     CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);  
  56. }  
  57. //update方法是當方向鍵被觸摸時,  
  58. void SimpleDPad::update(float dt)  
  59. {  
  60.     //_isHeld是控制手柄是否被觸摸的開關  
  61.     if (_isHeld)  
  62.     {  
  63.         //傳遞方向值到委託類   方向值爲 _direction  
  64.         _delegate->isHoldingDirection(this, _direction);  
  65.     }  
  66. }  
  67. //觸摸開始時執行該方法  
  68. bool SimpleDPad::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent)  
  69. {  
  70.     //獲取觸摸的座標點  
  71.     CCPoint location = pTouch->getLocation();  
  72.     //計算觸摸點和當前類即虛擬手柄對象中心點的距離  ,this->getPosition()獲取的是精靈中心點的座標  
  73.     float distanceSQ = ccpDistanceSQ(location, this->getPosition());  
  74.   //    判斷觸摸點是否在虛擬手柄精靈內  
  75.     if (distanceSQ <= _radius * _radius)  
  76.     {  
  77.         //updateDirectionForTouchLocation方法計算觸摸點到方向鍵中心距離值,轉換成角度,得到正確的方向值,然後傳遞值到委託。  
  78.         //因此需要將觸摸點的座標傳過去  
  79.         this->updateDirectionForTouchLocation(location);  
  80.         //開啓手柄的開關  
  81.         _isHeld = true;  
  82.         return true;  
  83.     }  
  84.     return false;  
  85. }  
  86.   
  87. void SimpleDPad::ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent)  
  88. {  
  89.     //獲取移動時的座標  
  90.     CCPoint location = pTouch->getLocation();  
  91.     this->updateDirectionForTouchLocation(location);  
  92. }  
  93.   
  94. void SimpleDPad::ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent)  
  95. {  
  96.     //當觸摸事件結束的時候方向置爲中心點  
  97.     _direction = CCPointZero;  
  98.     //手柄開關關閉  
  99.     _isHeld = false;  
  100.     //出發代理事件結束的方法  
  101.     _delegate->simpleDPadTouchEnded(this);  
  102. }  
  103. //CCPoint location  參數是手指觸摸屏幕的座標點  
  104. void SimpleDPad::updateDirectionForTouchLocation(CCPoint location)  
  105. {  
  106.     //弧度  根據觸摸點的座標與精靈中心點的座標計算出弧度  
  107.     float radians = ccpToAngle(ccpSub(location, this->getPosition()));  
  108.     //角度  根據弧度計算出角度  
  109.     float degrees = -1 * CC_RADIANS_TO_DEGREES(radians);  
  110.       
  111.     if (degrees <= 22.5 && degrees >= -22.5)  
  112.     {  
  113.         //right右  
  114.         _direction = ccp(1.0,0.0);  
  115.     }  
  116.     else if (degrees >22.5 && degrees <67.5)  
  117.     {  
  118.         //bottomright右下  
  119.         _direction = ccp(1.0, -1.0);  
  120.     }  
  121.     else if (degrees >=67.5 && degrees <=112.5)  
  122.     {  
  123.         //bottom下  
  124.         _direction = ccp(0.0, -1.0);  
  125.     }  
  126.     else if (degrees >112.5 && degrees <157.5)  
  127.     {  
  128.         //bottomleft左下  
  129.         _direction = ccp(-1.0, -1.0);  
  130.     }  
  131.     else if (degrees >=157.5 || degrees <= -157.5)  
  132.     {  
  133.         //left左  
  134.         _direction = ccp(-1.0,0.0);  
  135.     }  
  136.     else if (degrees < -22.5 && degrees > -67.5)  
  137.     {  
  138.         //topright右上  
  139.         _direction = ccp(1.0,1.0);  
  140.     }  
  141.     else if (degrees <= -67.5 && degrees >= -112.5)  
  142.     {  
  143.         //top上  
  144.         _direction = ccp(0.0,1.0);  
  145.     }  
  146.     else if (degrees < -112.5 && degrees > -157.5)  
  147.     {  
  148.         //topleft左上  
  149.         _direction = ccp(-1.0,1.0);  
  150.     }  
  151.     //將得到的方向傳遞到採用代理的類中, _ direction就是得到的方向  
  152.     _delegate->didChangeDirectionTo(this, _direction);  
  153. }  
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文件,添加頭文件聲明:

1
 
#include"SimpleDPad.h"

添加如下代碼:

1
2
 
bool init();
CC_SYNTHESIZE(SimpleDPad*, _dPad, DPad);

 HudLayer();


打開HudLayer.cpp文件,添加如下代碼:

  1. HudLayer::HudLayer(void)  
  2. {  
  3.     _dPad = NULL;  
  4. }  
  5.   
  6. bool HudLayer::init()  
  7. {  
  8.     bool bRet = false;  
  9.     do  
  10.     {  
  11.         CC_BREAK_IF(!CCLayer::init());  
  12.         //初始化手柄虛擬手柄對象 ,第一個參數爲精靈對象,第二個參數爲半徑  
  13.         _dPad = SimpleDPad::dPadWithFile(CCString::create("pd_dpad.png"),64);  
  14.         //虛擬手柄的座標  
  15.         _dPad->setPosition(ccp(64.0,64.0));  
  16.         //透明度  
  17.         _dPad->setOpacity(100);  
  18.         //將精靈加到層上  
  19.         this->addChild(_dPad);  
  20.           
  21.         bRet = true;  
  22.     } while (0);  
  23.       
  24.     return bRet;  
  25. }  
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文件,添加頭文件聲明:

1
2
 
#include"SimpleDPad.h"
#include"HudLayer.h"

GameLayer類聲明修改成如下:

1
 
class GameLayer : public cocos2d::CCLayer, public SimpleDPadDelegate

並添加以下聲明:


1
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中添加如下方法

  1. void didChangeDirectionTo(SimpleDPad *simpleDPad, cocos2d::CCPoint direction)  
  2. {  
  3.       
  4. }  
  5. void isHoldingDirection(SimpleDPad *simpleDPad, cocos2d::CCPoint direction)  
  6. {  
  7.       
  8. }  
  9. void simpleDPadTouchEnded(SimpleDPad *simpleDPad)  
  10. {  
  11.       
  12. }  
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);後面,添加如下代碼:


1
2
 

 //設置代理對象爲遊戲層_gameLayer

_hudLayer->getDPad()->setDelegate(_gameLayer);
_gameLayer->setHud(_hudLayer);

17.編譯運行,可以看到左下角的虛擬方向鍵,如下圖所示:

別試着壓下方向鍵,英雄不會有任何反應,因爲還未實現協議方法,這在第二部分將完成。

代碼例子 http://vdisk.weibo.com/s/BDn59yfnBVkAS  




分類: cocos2d-x項目教程 827人閱讀 評論(0) 收藏 舉報

在第一篇《如何製作一個橫版格鬥過關遊戲》基礎上,增加角色運動、碰撞、敵人、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後面,添加如下代碼:

1
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方法,代碼如下:

  1. void ActionSprite::walkWithDirection(CCPoint direction)  
  2. {  
  3.     //檢查前置動作狀態是否空閒  
  4.     if (_actionState == kActionStateIdle)  
  5.     {  
  6.         //停止所有動作  
  7.         this->stopAllActions();  
  8.         //執行行走的動作  
  9.         this->runAction(_walkAction);  
  10.         //標記狀態爲行走  
  11.         _actionState = kActionStateWalk;  
  12.     }  
  13.     if (_actionState == kActionStateWalk)  
  14.     {  
  15.         //根據_walkSpeed值改變精靈的速度  
  16.         _velocity = ccp(direction.x * _walkSpeed, direction.y * _walkSpeed);  
  17.         //檢查精靈的左右方向  
  18.         if (_velocity.x >= 0)  
  19.         {  
  20.             //用setScaleX來翻轉精靈  看清楚是有 setScaleX是有X的  精靈可以左右變換  
  21.             this->setScaleX(1.0);  
  22.         }  
  23.         else  
  24.         {  
  25.             this->setScaleX(-1.0);  
  26.         }  
  27.     }  
  28. }  
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文件,實現如下方法:


  1. void GameLayer::didChangeDirectionTo(SimpleDPad *simpleDPad, CCPoint direction)  
  2. {  
  3.     //傳遞的參數爲方向數據  
  4.     _hero->walkWithDirection(direction);  
  5. }  
  6.   
  7. void GameLayer::isHoldingDirection(SimpleDPad *simpleDPad, CCPoint direction)  
  8. {  
  9.     _hero->walkWithDirection(direction);  
  10. }  
  11.   
  12. void GameLayer::simpleDPadTouchEnded(SimpleDPad *simpleDPad)  
  13. {  
  14.     //觸摸停止後 如果之前的狀態爲行走 讓精靈變爲空閒狀態  
  15.     if (_hero->getActionState() == kActionStateWalk)  
  16.     {  
  17.         _hero->idle();  
  18.     }  
  19. }  
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文件,實現以下方法:


  1. void ActionSprite::update(float delta)  
  2. {  
  3.     //在每次遊戲更新場景的時候都會進行調用,當精靈處於行走狀態時,  
  4.     if (_actionState == kActionStateWalk)  
  5.     {  
  6.         //它更新精靈的期望位置。位置+速度*時間,實際上就是意味着每秒移動X和Y點  
  7.         //這裏的this指針就是指  誰調用就代表誰,英雄調用的就代表英雄,敵人調用的就代表敵人  
  8.         _desiredPosition = ccpAdd(this->getPosition(), ccpMult(_velocity, delta));  
  9.     }  
  10. }  
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();後面添加如下代碼:

1
 
this->scheduleUpdate();
在析構函數,添加如下代碼:
1
2
3
4
 
GameLayer::~GameLayer(void)
{
this->unscheduleUpdate();
}
增加如下兩個方法:

  1. void GameLayer::update(float delta)  
  2. {  
  3.     _hero->update(delta);  
  4.     this->updatePositions();  
  5.       
  6.     this->setViewpointCenter(_hero->getPosition());  
  7.     this->reorderActors();  
  8. }  
  9.   
  10. void GameLayer::updatePositions()  
  11. {  
  12.     //_tileMap->getMapSize().width 得到的是地圖中橫向有多少個瓦片地圖  
  13.     float widthMap = _tileMap->getMapSize().width;  
  14.     //_tileMap->getTileSize().width  得到的時每個瓦片地圖的寬度  
  15.     float widthTile = _tileMap->getTileSize().width;  
  16.     //widthMap * widthTile 代表的是整個地圖的寬  
  17.     //widthMap * widthTile - _hero->getCenterToSides()用整個地圖的寬減去精靈中心到精靈邊界的距離  
  18.     //相減得到的值就是精靈能夠橫向移動的最大位置  
  19.     //desiredPositionX  x軸的目標位置 就是在當前位置和目標  
  20.     float desiredPositionX = MAX(_hero->getCenterToSides(), _hero->getDesiredPosition().x);  
  21.     float posX = MIN( widthMap * widthTile - _hero->getCenterToSides(),desiredPositionX);  
  22.      
  23.       
  24.     //得到每個瓦片地圖的高  
  25.     float heightTile = _tileMap->getTileSize().height;  
  26.     //下面的3   是指屬於地板的瓦片只有3個  
  27.     float posY = MIN(3 *  heightTile + _hero->getCenterToBottom(),  
  28.                      MAX(_hero->getCenterToBottom(), _hero->getDesiredPosition().y));  
  29.     _hero->setPosition(ccp(posX, posY));  
  30. }  
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的兩個測量值,centerToSidescenterToBottom,因爲ActionSprite要想保持在場景內,它的位置不能超過實際的精靈邊界。假如ActionSprite的位置在已經設置的邊界內,則GameLayer讓英雄達到期望位置,否則GameLayer會讓英雄留停在原地。
3.編譯運行,此時點擊方向鍵,移動英雄,如下圖所示:

但是,很快你就會發現英雄可以走出地圖的右邊界,然後就這樣從屏幕上消失了。
4.以上的問題,可以通過基於英雄的位置進行滾動地圖,這個方法在文章《如何製作一個基於Tile的遊戲》中有描述過。打開GameLayer.cpp文件,在update函數裏最後添加如下代碼:

1
 
this->setViewpointCenter(_hero->getPosition());
添加如下方法:
1
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);
}

以上代碼讓英雄處於屏幕中心位置,當然,英雄在地圖邊界時的情況除外。編譯運行,效果如下圖所示:


代碼例子 http://vdisk.weibo.com/s/BDn59yfnBVkre  





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章