1. 程式人生 > >如何製作一個橫版格鬥過關遊戲 Cocos2d x 2 0 4

如何製作一個橫版格鬥過關遊戲 Cocos2d x 2 0 4

               

      本文實踐自 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.新建Cocos2d-win32工程,工程名為"PompaDroid",去除"Box2D"選項,勾選"Simple Audio Engine in Cocos Den

shion"選項;2.新增遊戲場景類GameScene,派生自CCScene類。新增GameLayer類和HudLayer類,派生自CCLayer類。刪除HelloWorldScene.hHelloWorldScene.cpp檔案。3.檔案GameScene.h程式碼如下:

1234567891011121314151617#pragma once#include "cocos2d.h"#include "GameLayer.h"#include "HudLayer.h"class GameScene : public cocos2d::CCScene{public:    GameScene(void
);    ~GameScene(void);    virtual bool init();    CREATE_FUNC(GameScene);    CC_SYNTHESIZE(GameLayer*, _gameLayer, GameLayer);    CC_SYNTHESIZE(HudLayer*, _hudLayer, HudLayer);};

檔案GameScene.cpp程式碼如下:

123456789101112131415161718192021222324252627282930#include "GameScene.h"using namespace cocos2d;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();         this->addChild(_gameLayer, 0);         _hudLayer = HudLayer::create();         this->addChild(_hudLayer, 1);                  bRet = true;    } while (0);    return bRet;}

4.HudLayer類增加一個方法:

1CREATE_FUNC(HudLayer);
GameLayer類增加一個方法:
1CREATE_FUNC(GameLayer);
5.修改AppDelegate.cpp檔案,程式碼如下:
12345678910111213//#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檔案,新增如下程式碼:

1234bool init();void initTileMap();cocos2d::CCTMXTiledMap *_tileMap;

開啟GameLayer.cpp,在建構函式,新增如下程式碼:

1_tileMap = NULL;
新增如下程式碼:
1234567891011121314151617181920212223242526bool GameLayer::init(){    bool bRet = false;    do     {        CC_BREAK_IF(!CCLayer::init());        this->initTileMap();                bRet = true;    } while (0);    return bRet;}void GameLayer::initTileMap(){    _tileMap = CCTMXTiledMap::create("pd_tilemap.tmx");    CCObject *pObject = NULL;    CCARRAY_FOREACH(_tileMap->getChildren(), pObject)    {        CCTMXLayer *child = (CCTMXLayer*)pObject;        child->getTexture()->setAliasTexParameters();    }    this->addChild(_tileMap, -6);}

對所有圖層進行setAliasTexParameters設定,該方法是關閉抗鋸齒功能,這樣就能保持畫素風格。10.編譯執行,可以看到地圖顯示在螢幕上,如下圖所示:11.建立英雄。在大多數2D橫版遊戲中,角色有不同的動畫代表不同型別的動作。我們需要知道什麼時候播放哪個動畫。這裡採用狀態機來解決這個問題。狀態機就是某種通過切換狀態來改變行為的東西。單一狀態機在同一時間只能有一個狀態,但可以從一種狀態過渡到另一種狀態。在這個遊戲中,角色共有五種狀態,空閒、行走、出拳、受傷、死亡,如下圖所示:為了有一個完整的狀態流,每個狀態應該有一個必要條件和結果。例如:行走狀態不能突然轉變到死亡狀態,因為你的英雄在死亡前必須先受傷。12.新增ActionSprite類,派生自CCSprite類,ActionSprite.h檔案程式碼如下:

12345678910111213141516171819202122232425262728293031323334353637383940414243#pragma once#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    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    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檔案,建構函式如下:

12345678ActionSprite::ActionSprite(void){    _idleAction = NULL;    _attackAction = NULL;    _walkAction = NULL;    _hurtAction = NULL;    _knockedOutAction = NULL;}
各個方法實現暫時為空。以上程式碼聲明瞭基本變數和方法,可以分為以下幾類:
  • Actions:這些是每種狀態要執行的動作。這些動作是當角色切換狀態時,執行精靈動畫和其他觸發的事件。States:儲存精靈的當前動作/狀態,使用ActionState型別,這個型別待會我們將會進行定義。Attributes:包含精靈行走速度值,受傷時減少生命點值,攻擊傷害值。Movement:用於計算精靈如何沿著地圖移動。Measurements:儲存對精靈的實際影象有用的測量值。需要這些值,是因為你將要使用的這些精靈畫布大小是遠遠大於內部包含的影象。Action methods:不直接呼叫動作,而是使用這些方法觸發每種狀態。Scheduled methods:任何事需要在一定的時間間隔進行執行,比如精靈位置和速度的更新,等等。

新建一個頭檔案Defines.h,程式碼如下:

123456789101112131415161718192021222324252627282930313233#pragma once#include "cocos2d.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 - enumerationstypedef enum _ActionState {    kActionStateNone = 0,    kActionStateIdle,    kActionStateAttack,    kActionStateWalk,    kActionStateHurt,    kActionStateKnockedOut} ActionState;// 4 - structurestypedef struct _BoundingBox {    cocos2d::CCRect actual;    cocos2d::CCRect original;} BoundingBox;
簡要說明下:①.定義了一些便利的巨集,如直接使用SCREEN獲取螢幕大小;②.定義了一些便利的函式,隨機返回整型或者浮點型;③.定義ActionState型別,這個是ActionSprite可能處在不同狀態的型別列舉;④.定義BoundingBox結構體,將用於碰撞檢測。開啟GameLayer.h檔案,新增如下程式碼:
1cocos2d::CCSpriteBatchNode *_actors;
開啟GameLayer.cpp檔案,在init函式裡面新增如下程式碼:
1234CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("pd_sprites.plist");_actors = CCSpriteBatchNode::create("pd_sprites.pvr.ccz");_actors->getTexture()->setAliasTexParameters();this->addChild(_actors, -5);

載入精靈表單,建立一個CCSpriteBatchNode。這個精靈表單包含我們的所有精靈。它的z值高於CCTMXTiledMap物件,這樣才能出現在地圖前。新增Hero類,派生自ActionSprite類,新增如下程式碼:

12CREATE_FUNC(Hero);bool init();
Hero類的init函式的實現如下所示:
1234567891011121314151617181920212223242526272829bool 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 *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("hero_idle_%02d.png", i)->getCString());            idleFrames->addObject(frame);        }        CCAnimation *idleAnimation = CCAnimation::createWithSpriteFrames(idleFrames, 1.0 / 12.0);        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類新增如下程式碼:
1Hero *_hero;
開啟GameLayer.cpp檔案,在建構函式新增如下程式碼:
1_hero = NULL;
init函式this->addChild(_actors, -5);後面新增如下程式碼:
1this->initHero();
新增initHero方法,程式碼如下:
12345678void GameLayer::initHero(){    _hero = Hero::create();    _actors->addChild(_hero);    _hero->setPosition(ccp(_hero->getCenterToSides(), 80));    _hero->setDesiredPosition(_hero->getPosition());    _hero->idle();}
建立了一個英雄例項,新增到了精靈表單,並設定了設定。呼叫idle方法,讓其處於空閒狀態,執行空閒動畫。返回到ActionSprite.cpp檔案,實現idle方法,程式碼如下:
12345678910void ActionSprite::idle(){    if (_actionState != kActionStateIdle)    {        this->stopAllActions();        this->runAction(_idleAction);        _actionState = kActionStateIdle;        _velocity = CCPointZero;    }}
這個idle方法只有當ActionSprite不處於空閒狀態才能呼叫。當它觸發時,它會執行空閒動作,改變當前狀態到kActionStateIdle,並且把速度置零。13.編譯執行,可以看到英雄處於空閒狀態。如下圖所示:14.出拳動作。開啟Hero.cpp檔案,在init函式idle animation後面,新增如下程式碼:
123456789//attack animationCCArray *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);this->setAttackAction(CCSequence::create(CCAnimate::create(attackAnimation), CCCallFunc::create(this, callfunc_selector(Hero::idle)), NULL));
開啟ActionSprite.cpp檔案,實現attack方法,程式碼如下:
123456789void ActionSprite::attack(){    if (_actionState == kActionStateIdle || _actionState == kActionStateAttack || _actionState == kActionStateWalk)    {        this->stopAllActions();        this->runAction(_attackAction);        _actionState = kActionStateAttack;    }}
英雄只有在空閒、攻擊、行走狀態才能進行出拳。確保英雄正在受傷時、或者死亡時不能進行攻擊。為了觸發attack方法,開啟GameLayer.cpp檔案,在init函式新增如下程式碼:
1this->setTouchEnabled(true);
過載ccTouchesBegan方法,程式碼如下:
1234void GameLayer::ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent){    _hero->attack();}
15.編譯執行,點選螢幕進行出拳,如下圖所示:16.建立8個方向的方向鍵。我們需要建立虛擬的8個方向的方向鍵來讓英雄在地圖上進行移動。新增SimpleDPad類,派生自CCSprite類,SimpleDPad.h檔案程式碼如下:
123456789101112131415161718192021222324252627282930313233343536373839#pragma once#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這裡寫圖片描述