如何製作一個橫版格鬥過關遊戲 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
1234567891011121314151617 | #pragma once#include "cocos2d.h"#include "GameLayer.h"#include "HudLayer.h"class GameScene : public cocos2d::CCScene{public: GameScene(void |
檔案GameScene.cpp程式碼如下:
123456789101112131415161718192021222324252627282930 | #include "GameScene.h"using namespace cocos2d;GameScene::GameScene(void |
4.HudLayer類增加一個方法:
1 | CREATE_FUNC(HudLayer); |
1 | CREATE_FUNC(GameLayer); |
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檔案,新增如下程式碼:
1234 | bool init();void initTileMap();cocos2d::CCTMXTiledMap *_tileMap; |
開啟GameLayer.cpp,在建構函式,新增如下程式碼:
1 | _tileMap = NULL; |
1234567891011121314151617181920212223242526 | bool 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檔案,建構函式如下:
12345678 | ActionSprite::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; |
1 | cocos2d::CCSpriteBatchNode *_actors; |
1234 | CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("pd_sprites.plist");_actors = CCSpriteBatchNode::create("pd_sprites.pvr.ccz");_actors->getTexture()->setAliasTexParameters();this->addChild(_actors, -5); |
載入精靈表單,建立一個CCSpriteBatchNode。這個精靈表單包含我們的所有精靈。它的z值高於CCTMXTiledMap物件,這樣才能出現在地圖前。新增Hero類,派生自ActionSprite類,新增如下程式碼:
12 | CREATE_FUNC(Hero);bool init(); |
1234567891011121314151617181920212223242526272829 | 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 *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" |
1 | Hero *_hero; |
1 | _hero = NULL; |
1 | this->initHero(); |
12345678 | void GameLayer::initHero(){ _hero = Hero::create(); _actors->addChild(_hero); _hero->setPosition(ccp(_hero->getCenterToSides(), 80)); _hero->setDesiredPosition(_hero->getPosition()); _hero->idle();} |
12345678910 | void ActionSprite::idle(){ if (_actionState != kActionStateIdle) { this->stopAllActions(); this->runAction(_idleAction); _actionState = kActionStateIdle; _velocity = CCPointZero; }} |
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)); |
123456789 | void ActionSprite::attack(){ if (_actionState == kActionStateIdle || _actionState == kActionStateAttack || _actionState == kActionStateWalk) { this->stopAllActions(); this->runAction(_attackAction); _actionState = kActionStateAttack; }} |
1 | this->setTouchEnabled(true); |
1234 | void GameLayer::ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent){ _hero->attack();} |
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 |