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

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

               

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

123456789//walk animationCCArray *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方法,程式碼如下:
123456789101112131415161718192021void ActionSprite::walkWithDirection(CCPoint direction){    if (_actionState == kActionStateIdle)    {        this->stopAllActions();        this
->runAction(_walkAction);        _actionState = kActionStateWalk;    }    if (_actionState == kActionStateWalk)    {        _velocity = ccp(direction.x * _walkSpeed, direction.y * _walkSpeed);        if (_velocity.x >= 0)        {            this->setScaleX(1.0);        }         else        {            this->setScaleX(-1.0);        }    }}
這段程式碼,檢查前置動作狀態是否空閒,若是的話切換動作到行走。在行走狀態時,根據_walkSpeed值改變精靈速度。同時檢查精靈的左右方向,並通過將精靈scaleX設定為1或-1來翻轉精靈。要讓英雄的行走動作跟方向鍵聯絡起來,需要藉助方向鍵的委託:GameLayer類。開啟GameLayer.cpp檔案,實現如下方法:
1234567891011121314151617void 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檔案,實現以下方法:

1234567void ActionSprite::update(float dt){    if (_actionState == kActionStateWalk)    {        _desiredPosition = ccpAdd(this->getPosition(), ccpMult(_velocity, dt));    }}
這個方法在每次遊戲更新場景的時候都會進行呼叫,當精靈處於行走狀態時,它更新精靈的期望位置。位置+速度*時間,實際上就是意味著每秒移動X和Y點。開啟GameLayer.cpp檔案,在init函式this->initTileMap();後面新增如下程式碼:
1this->scheduleUpdate();
在解構函式,新增如下程式碼:
1234GameLayer::~GameLayer(void){    this->unscheduleUpdate();}
增加如下兩個方法:
1234567891011121314void GameLayer::update(float dt){    _hero->update(dt);    this->updatePositions();}void GameLayer::updatePositions(){    float posX = MIN(_tileMap->getMapSize().width * _tileMap->getTileSize().width - _hero->getCenterToSides(),        MAX(_hero->getCenterToSides(), _hero->getDesiredPosition().x));    float posY = MIN(3 * _tileMap->getTileSize().height + _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函式裡最後新增如下程式碼:

1this->setViewpointCenter(_hero->getPosition());
新增如下方法:
1234567891011121314void 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);}

以上程式碼讓英雄處於螢幕中心位置,當然,英雄在地圖邊界時的情況除外。編譯執行,效果如下圖所示:5.建立機器人。我們已經建立了精靈的基本模型:ActionSprite。我們可以重用它來建立遊戲中電腦控制的角色。新建Robot類,派生自ActionSprite類,增加如下方法:

12CREATE_FUNC(Robot);bool init();
開啟Robot.cpp檔案,init函式程式碼如下:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152bool Robot::init(){    bool bRet = false;    do     {        CC_BREAK_IF(!ActionSprite::initWithSpriteFrameName("robot_idle_00.png"));                int i;        //idle animation        CCArray *idleFrames = CCArray::createWithCapacity(5);        for (i = 0; i < 5; i++)        {            CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(                CCString::createWithFormat("robot_idle_%02d.png", i)->getCString());            idleFrames->addObject(frame);        }        CCAnimation *idleAnimation = CCAnimation::createWithSpriteFrames(idleFrames, float(1.0 / 12.0));        this->setIdleAction(CCRepeatForever::create(CCAnimate::create(idleAnimation)));        //attack animation        CCArray *attackFrames = CCArray::createWithCapacity(5);        for (i = 0; i < 5; i++)        {            CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(                CCString::createWithFormat("robot_attack_%02d.png", i)->getCString());            attackFrames->addObject(frame);        }        CCAnimation *attackAnimation = CCAnimation::createWithSpriteFrames(attackFrames, float(1.0 / 24.0));        this->setAttackAction(CCSequence::create(CCAnimate::create(attackAnimation), CCCallFunc::create(this, callfunc_selector(Robot::idle)), NULL));        //walk animation        CCArray *walkFrames = CCArray::createWithCapacity(6);        for (i = 0; i < 6; i++)        {            CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(                CCString::createWithFormat("robot_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)));        this->setWalkSpeed(80.0);        this->setCenterToBottom(39.0);        this->setCenterToSides(29.0);        this->setHitPoints(100.0);        this->setDamage(10.0);        bRet = true;    } while (0);    return bRet;}

跟英雄一樣,以上程式碼建立一個帶有3個動作的機器人:空閒、出拳、行走。它也有兩個測量值:centerToBottomcenterToSides。注意到機器人的屬性比英雄低一點,這是合乎邏輯的,不然英雄永遠打不贏機器人。讓我們開始新增一些機器人到遊戲中去。開啟GameLayer.h檔案,新增如下程式碼:

1CC_SYNTHESIZE_RETAIN(cocos2d::CCArray*, _robots, Robots);
開啟GameLayer.cpp檔案,新增標頭檔案如下:
1#include "Robot.h"
在建構函式裡,新增如下程式碼:
1_robots = NULL;
init函式this->initTileMap();的後面新增如下程式碼:
1this->initRobots();
新增如下方法:
123456789101112131415161718192021void GameLayer::initRobots(){    int robotCount = 50;    this->setRobots(CCArray::createWithCapacity(robotCount));    for (int i = 0; i < robotCount; i++)    {        Robot *robot = Robot::create();        _actors->addChild(robot);        _robots->addObject(robot);        int minX = SCREEN.width + robot->getCenterToSides();        int maxX = _tileMap->getMapSize().width * _tileMap->getTileSize().width - robot->getCenterToSides();        int minY = robot->getCenterToBottom();        int maxY = 3 * _tileMap->getTileSize().height + robot->getCenterToBottom();        robot->setScaleX(-1);        robot->setPosition(ccp(random_range(minX, maxX), random_range(minY, maxY)));        robot->setDesiredPosition(robot->getPosition());        robot->idle();    }}

這些程式碼做了以下事情:

  • 建立一個包含50個機器人的陣列,並把它們新增到精靈表單中。
  • 使用Defines.h裡面的隨機函式隨機放置50個機器人到地圖地板上。同時,讓最小隨機值大於螢幕寬度,以確保不會有任何機器人出現在起點處。
  • 讓每個機器人都處於空閒狀態。

編譯執行,讓英雄向前走,直到看到地圖上的機器人,如下圖所示:試著走到機器人區域中,你會發現機器人的繪製有些不對。如果英雄是在機器人的下面,那麼他應該被繪製在機器人的前面,而不是在後面。我們需要明確的告訴遊戲,哪個物件先繪製,這就是Z軸來進行控制的。新增英雄和機器人時,並沒有明確指定其Z軸,預設下,後面新增的物件會比前面的物件Z軸值高,這就是為什麼機器人擋住了英雄。為了解決這個問題,我們需要動態的處理Z軸順序。每當精靈在螢幕上垂直移動時,它的Z軸值應該有所改變。螢幕上越高的精靈,其Z軸值應越低。開啟GameLayer.cpp檔案,新增如下方法:

123456789void GameLayer::reorderActors(){    CCObject *pObject = NULL;    CCARRAY_FOREACH(_actors->getChildren(), pObject)    {        ActionSprite *sprite = (ActionSprite*)pObject;        _actors->reorderChild(sprite, (_tileMap->getMapSize().height * _tileMap->getTileSize().height) - sprite->getPosition().y);    }}
然後在update函式this->updatePositions();的後面,新增如下程式碼:
1this->reorderActors();
每當精靈的位置更新,這個方法會讓CCSpriteBatchNode重新設定它的每個子節點Z軸值,其根據子節點離地圖底部的距離,當子節點離底部更高時,其Z軸值就會下降。編譯執行,可以看到正確的繪製順序,如下圖所示:6.出拳猛擊機器人,碰撞檢測。為了讓英雄能夠出拳,並且能夠實際上打在了機器人身上,需要實現一種方式的碰撞檢測。在這篇文章中,我們使用矩形建立一個非常簡單的碰撞檢測系統。在這個系統中,我們為每個角色定義兩種矩形/盒子:
  • Hit box:代表精靈的身體
  • Attack box:代表精靈的手

假如某個ActionSprite的Attack box碰撞到另一個ActionSprite的Hit box,那麼這就是一次碰撞發生。這兩個矩形之間的區別,將幫助我們知道誰打了誰。Defines.h檔案中的BoundingBox定義,包含兩種矩形:實際的,和原始的:①原始矩形,每個精靈的基本矩形,一旦設定後就不會改變。②實際矩形,這是位於世界空間中的矩形,當精靈移動時,實際的矩形也跟著變動。開啟ActionSprite.h檔案,新增如下程式碼:

1234CC_SYNTHESIZE(BoundingBox, _hitBox, Hitbox);CC_SYNTHESIZE(BoundingBox, _attackBox, AttackBox);BoundingBox createBoundingBoxWithOrigin(cocos2d::CCPoint origin, cocos2d::CCSize size);

以上建立了ActionSprite的兩個包圍盒:Hit box和Attack box。還定義了一個方法,用於根據給定的原點和大小來建立一個BoundingBox結構體。開啟ActionSprite.cpp檔案,新增如下方法:

1234567891011121314151617181920212223BoundingBox ActionSprite::createBoundingBoxWithOrigin(CCPoint origin, CCSize size){    BoundingBox boundingBox;    boundingBox.original.origin = origin;    boundingBox.original.size = size;    boundingBox.actual.origin = ccpAdd(this->getPosition(), ccp(boundingBox.original.origin.x, boundingBox.original.origin.y));    boundingBox.actual.size = size;    return boundingBox;}void ActionSprite::transformBoxes(){    _hitBox.actual.origin = ccpAdd(this->getPosition(), ccp(_hitBox.original.origin.x, _hitBox.original.origin.y));    _attackBox.actual.origin = ccpAdd(this->getPosition(), ccp(_attackBox.original.origin.x +         (this->getScaleX() == -1 ? (- _attackBox.original.size.width - _hitBox.original.size.width) : 0),        _attackBox.original.origin.y));}void ActionSprite::setPosition(CCPoint position){    CCSprite::setPosition(position);    this->transformBoxes();}

第一個方法建立一個新的包圍盒,這有助於ActionSprite的子類建立屬於它們自己的包圍盒。第二個方法,基於精靈的位置、比例因子,和包圍盒原本的原點和大小來更新每個包圍盒實際測量的原點和大小。之所以要用到比例因子,是因為它決定著精靈的方向。位於精靈右側的盒子,當比例因子設定為-1時,將會翻轉到左側。開啟Hero.cpp檔案,在init函式後面新增如下程式碼:

123this->setHitbox(this->createBoundingBoxWithOrigin(ccp(-this->getCenterToSides(), -this->getCenterToBottom()),    CCSizeMake(this->getCenterToSides() * 2this->getCenterToBottom() * 2)));this->setAttackBox(this->createBoundingBoxWithOrigin(ccp(this->getCenterToSides(), -10), CCSizeMake(2020)));
開啟Robot.cpp檔案,在init函式後面新增如下程式碼:
123this->setHitbox(this->createBoundingBoxWithOrigin(ccp(-this->getCenterToSides(), -this->getCenterToBottom()),    CCSizeMake(this->getCenterToSides() * 2this->getCenterToBottom() * 2)));this->setAttackBox(this->createBoundingBoxWithOrigin(ccp(this->getCenterToSides(), -5), CCSizeMake(2520)));
現在我們已經有了英雄和機器人各自的Hit box和Attack box。如果是視覺化的箱子,它們會像下面這樣:無論何時,當一個attack box(紅色)跟一個hit box(藍色)交叉,即一次碰撞發生。在開始編寫程式碼,檢測包圍盒交叉前,需要確保ActionSprite能夠對被擊中有所反應。我們已經添加了空閒、出拳、行走動作,但還未建立受傷和死亡動作。開啟ActionSprite.cpp檔案,實現如下方法:
1234567891011121314151617181920212223void ActionSprite::hurtWithDamage(float damage){    if (_actionState != kActionStateKnockedOut)    {        this->stopAllActions();        this->runAction(_hurtAction);        _actionState = kActionStateHurt;        _hitPoints -= damage;        if (_hitPoints <= 0)        {            this->knockout();        }    }}void ActionSprite::knockout(){    this->stopAllActions();    this->runAction(_knockedOutAction);    _hitPoints = 0;    _actionState = kActionStateKnockedOut;}

只要精靈還未死亡,被擊中時狀態將會切換到受傷狀態,執行受傷動畫,並且精靈的生命值將會減去相應的傷害值。如果生命值少於0,那麼死亡的動作將會觸發。為了完成這兩個動作,我們還需更改Hero類和Robot類。開啟Hero.cpp檔案,在init函式walk animation後面新增如下程式碼:

12345678910111213141516171819//hurt animationCCArray *hurtFrames = CCArray::createWithCapacity(3);for (i = 0; i < 3; i++){    CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("hero_hurt_%02d.png", i)->getCString());    hurtFrames->addObject(frame);}CCAnimation *hurtAnimation = CCAnimation::createWithSpriteFrames(hurtFrames, float(1.0 / 12.0));this->setHurtAction(CCSequence::create(CCAnimate::create(hurtAnimation), CCCallFunc::create(this, callfunc_selector(Hero::idle)), NULL));//knocked out animationCCArray *knockedOutFrames = CCArray::createWithCapacity(5);for (i = 0; i < 5; i++){    CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("hero_knockout_%02d.png", i)->getCString());    knockedOutFrames->addObject(frame);}CCAnimation *knockedOutAnimation = CCAnimation::createWithSpriteFrames(knockedOutFrames, float(1.0 / 12.0));this->setKnockedOutAction(CCSequence::create(CCAnimate::create(knockedOutAnimation), CCBlink::create(2.010.0), NULL));

開啟Robot.cpp檔案,在init函式walk animation後面新增如下程式碼:

12345678910111213141516171819//hurt animationCCArray *hurtFrames = CCArray::createWithCapacity(3);for (i = 0; i < 3; i++){    CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("robot_hurt_%02d.png", i)->getCString());    hurtFrames->addObject(frame);}CCAnimation *hurtAnimation = CCAnimation::createWithSpriteFrames(hurtFrames, float(1.0 / 12.0));this->setHurtAction(CCSequence::create(CCAnimate::create(hurtAnimation), CCCallFunc::create(this, callfunc_selector(Robot::idle)), NULL));//knocked out animationCCArray *knockedOutFrames = CCArray::createWithCapacity(5);for (i = 0; i < 5; i++){    CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("robot_knockout_%02d.png", i)->getCString());    knockedOutFrames->addObject(frame);}CCAnimation *knockedOutAnimation = CCAnimation::createWithSpriteFrames(knockedOutFrames, float(1.0 / 12.0));this->setKnockedOutAction(CCSequence::create(CCAnimate::create(knockedOutAnimation), CCBlink::create(2.010.0), NULL));

以上程式碼應該不陌生了。我們用建立其他動作同樣的方式建立了受傷和死亡動作。受傷動作結束時,會切換到空閒狀態。死亡動作結束時,精靈進行閃爍。開啟GameLayer.cpp檔案,新增碰撞處理,在ccTouchesBegan函式後面新增如下程式碼:

123456789101112131415161718if (_hero->getActionState() == kActionStateAttack){    CCObject *pObject = NULL;    CCARRAY_FOREACH(_robots, pObject)    {        Robot *robot = (Robot*)pObject;        if (robot->getActionState() != kActionStateKnockedOut)        {            if (fabsf(_hero->getPosition().y - robot->getPosition().y) < 10)            {                if (_hero->getAttackBox().actual.intersectsRect(robot->getHitbox().actual))                {                    robot->hurtWithDamage(_hero->getDamage());                }            }        }    }       }
以上程式碼通過三個簡單步驟來檢測碰撞:①.檢測英雄是否處於攻擊狀態,以及機器人是否處於非死亡狀態。②.檢測英雄的位置和機器人的位置垂直相距在10個點以內。這表明它們在同一平面上站立。③.檢測英雄的attack box是否與機器人的hit box進行交叉。如果這些條件都成立,那麼則一次碰撞發生,機器人執行受傷動作。英雄的傷害值作為引數進行傳遞,這樣該方法就會知道需要減去多少生命值。7.編譯執行,出拳攻擊機器人吧,效果如下圖所示:8.簡單機器人AI的實現。為了使機器人能夠移動,並且能夠使用我們為它們所建立的動作,就需要開發一個簡單的AI(人工智慧)系統。這個AI系統基於決策機制。在特定的時間間隔裡,我們給每個機器人一個機會來決定接下來該做什麼。它們需要知道的第一件事情就是何時做出選擇。開啟Robot.h檔案,新增如下程式碼:
1CC_SYNTHESIZE(float, _nextDecisionTime, NextDecisionTime);
開啟Robot.cpp檔案,在init函式後面,新增如下程式碼:
1_nextDecisionTime = 0;
這個屬性儲存下一次機器人可以作出決定的時間。開啟Defines.h檔案,修改成如下程式碼:
1234567891011121314151617181920212223242526272829303132333435363738#pragma once#include "cocos2d.h"// 1 - convenience measurements#define SCREEN CCDirector::sharedDirector()->getWinSize()#define CENTER ccp(SCREEN.width / 2, SCREEN.height / 2)#define CURTIME GetCurTime()// 2 - convenience functions#ifndef UINT64_C#define UINT64_C(val) val##ui64#endif#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;inline float GetCurTime(){    timeval time;    gettimeofday(&time, NULL);    unsigned long millisecs = (time.tv_sec * 1000) + (time.tv_usec / 1000);    return (float)millisecs;};

開啟GameLayer.cpp檔案,新增如下方法:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879void GameLayer::updateRobots(float dt){    int alive = 0;    float distanceSQ;    int randomChoice = 0;    CCObject *pObject = NULL;    CCARRAY_FOREACH(_robots, pObject)    {        Robot *robot = (Robot*)pObject;        robot->update(dt);        if (robot->getActionState() != kActionStateKnockedOut)        {            //1            alive++;                        //2            if (CURTIME > robot->getNextDecisionTime())            {                distanceSQ = ccpDistanceSQ(robot->getPosition(), _hero->getPosition());                //3                if (distanceSQ <= 50 * 50)                {                    robot->setNextDecisionTime(CURTIME + frandom_range(0.10.5) * 1000);                    randomChoice = random_range(01);                    if (randomChoice == 0)                    {                        if (_hero->getPosition().x > robot->getPosition().x)                        {                            robot->setScaleX(1.0);                        }                         else                        {                            robot->setScaleX(-1.0);                        }                        //4                        robot->setNextDecisionTime(robot->getNextDecisionTime() + frandom_range(0.10.5) * 2000);                        robot->attack();                                                if (robot->getActionState() == kActionStateAttack)                        {                            if (fabsf(_hero->getPosition().y - robot->getPosition().y) < 10)                            {                                if (_hero->getHitbox().actual.intersectsRect(robot->getAttackBox().actual))                                {                                    _hero->hurtWithDamage(robot->getDamage());                                    //end game checker here                                }                            }                        }                    }                    else                    {                        robot->idle();                    }                }                else if (distanceSQ <= SCREEN.width * SCREEN.width)                {                    //5                    robot->setNextDecisionTime(CURTIME + frandom_range(0.51.0) * 1000);                    randomChoice = random_range(02);                    if (randomChoice == 0)                    {                        CCPoint moveDirection = ccpNormalize(ccpSub(_hero->getPosition(), robot->getPosition()));                        robot->walkWithDirection(moveDirection);                    }                     else                    {                        robot->idle();                    }                }            }        }    }    //end game checker here}

這是一個漫長的程式碼片段。將程式碼分解為一段段。對於遊戲中的每個機器人:①.使用一個計數來儲存仍然存活著的機器人數量。一個機器人只要不是死亡狀態,就被認為仍然存活著。這將用於判斷遊戲是否應該結束。②.檢查當前應用程式時間的推移是否超過了機器人的下一次決定時間。如果超過了,意味著機器人需要作出一個新的決定。③.檢查機器人是否足夠接近英雄,以便於有機會出拳攻擊落在英雄身上。如果接近英雄了,那麼就進行一個隨機選擇,看是要朝著英雄出拳,還是要繼續空閒著。④.假如機器人決定攻擊,我們就用之前檢測英雄攻擊時相同的方式來進行檢測碰撞。只是這一次,英雄和機器人的角色互換了。⑤.如果機器人和英雄之間的距離小於螢幕寬度,那麼機器人將作出決定,要麼朝著英雄移動,要麼繼續空閒。機器人的移動基於英雄位置和機器人位置產生的法向量。每當機器人作出決定,它的下一個決定的時間被設定為在未來的一個隨機時間。在此期間,它將繼續執行上次作出決定時所做出的動作。接著在update函式裡,this->updatePositions();前新增如下程式碼:

1this->updateRobots(dt);
updatePositions函式後面,新增如下程式碼:
12345678910CCObject *pObject = NULL;CCARRAY_FOREACH(_robots, pObject){    Robot *robot = (Robot*)pObject;    posX = MIN(_tileMap->getMapSize().width * _tileMap->getTileSize().width - robot->getCenterToSides(),        MAX(robot->getCenterToSides(), robot->getDesiredPosition().x));    posY = MIN(3 * _tileMap->getTileSize().height + robot->getCenterToBottom(),        MAX(robot->getCenterToBottom(), robot->getDesiredPosition().y));    robot->setPosition(ccp(posX, posY));}
確保每次遊戲迴圈時,機器人AI方法都被呼叫。遍歷每個機器人,並讓它們朝著期望的位置進行移動。9.編譯執行,將會看到沿著走廊過來的機器人。效果如下圖所示:10.為遊戲新增重新開始的按鈕。開啟GameLayer.cpp檔案,新增標頭檔案引用:
1#include "GameScene.h"
新增如下方法:
1234567891011121314void GameLayer::endGame(){    CCLabelTTF *restartLabel = CCLabelTTF::create("RESTART""Arial"30);    CCMenuItemLabel *restartItem = CCMenuItemLabel::create(restartLabel, this, menu_selector(GameLayer::restartGame));    CCMenu *menu = CCMenu::create(restartItem, NULL);    menu->setPosition(CENTER);    menu->setTag(5);    _hud->addChild(menu, 5);}void GameLayer::restartGame(CCObject* pSender){    CCDirector::sharedDirector()->replaceScene(GameScene::create());}

第一個方法建立顯示一個重新開始的按鈕,當按下它時,觸發第二個方法。後者只是命令導演用新的GameScene例項替換當前場景。接著在updateRobots函式裡面,在第一個end game checker here註釋後面,新增如下程式碼:

1234if (_hero->getActionState() == kActionStateKnockedOut && _hud->getChildByTag(5) == NULL){    this->endGame();}
在第二個end game checker here註釋後面,新增如下程式碼:
1234if (alive == 0 && _hud->getChildByTag(5) == NULL){    this->endGame();}
這些語句都是檢測遊戲結束的條件。第一個檢測英雄被機器人攻擊後,是否還存活著。如果英雄死亡了,那麼遊戲就結束了。第二個檢測是否所有的機器人都死亡了。如果都死亡了,那麼遊戲也結束了。另外,在endGame方法裡,可以看到遊戲結束選單的tag值為5。因為檢測是在迴圈裡面,需要確保遊戲結束選單之前沒被建立過。否則的話,將會一直建立遊戲結束選單。11.編譯執行,可以看到遊戲結束時的樣子,如下圖所示:

12.音樂和音效。開啟GameLayer.cpp檔案,新增標頭檔案引用:

1#include "SimpleAudioEngine.h"
init函式裡,CC_BREAK_IF(!CCLayer::init());後面新增如下程式碼:
1234567// Load audioCocosDenshion::SimpleAudioEngine::sharedEngine()->preloadBackgroundMusic("latin_industries.aifc");CocosDenshion::SimpleAudioEngine::sharedEngine()->playBackgroundMusic("latin_industries.aifc");CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect("pd_hit0.wav");CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect("pd_hit1.wav");CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect("pd_herodeath.wav");CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect("pd_botdeath.wav");
開啟ActionSprite.cpp檔案,新增標頭檔案引用:
1#include "SimpleAudioEngine.h"
hurtWithDamage函式,第一個條件語句裡新增如下程式碼:
12int randomSound = random_range(01);CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect(CCString::createWithFormat("pd_hit%d.wav", randomSound)->getCString());
開啟ActionSprite.h檔案,將knockout方法宣告修改如下:
1virtual void knockout();
開啟Hero.cpp檔案,新增標頭檔案引用:
1#include "SimpleAudioEngine.h"
新增如下方法:
12345void Hero::knockout(){    ActionSprite::knockout();    CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("pd_herodeath.wav");}
開啟Robot.cpp檔案,新增標頭檔案引用:
1#include "SimpleAudioEngine.h"
新增如下方法:
12345void Robot::knockout(){    ActionSprite::knockout();    CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("pd_botdeath.wav");}
13.編譯執行,現在遊戲將有配樂,效果圖: