1. 程式人生 > >cocos2d-x小遊戲——飛機大戰

cocos2d-x小遊戲——飛機大戰

上週,我做了一個基於 cocos2d-x 的飛機大戰的遊戲,因為我剛學cocos2d-x沒多久,所以這個飛機大戰很多都是看著別人的教程,再加上自己的一些想法,來做的。

下面我想說一說我的思路。

飛機大戰有三個場景:

  1. LayerGameStart(遊戲開始)
  2. LayerGameMain(遊戲進行中)
  3. LayerGameOver(遊戲結束)

一、遊戲開始場景(LayerGameStart)

其中,遊戲開始和遊戲結束是比較簡單的,那我就先從簡單的說起,
首先說下游戲開始場景。我們在這個場景裡面只需要做一下工作:

  • 預載入一些資源(聲音,圖片快取,這些資源都是全域性的)
  • 注意要將同一類資源放在一起,便於管理
  • 檢測遊戲遊戲的本地存數資料中是否有遊戲最高分(UserDefault

在這個場景,我只想說下如果檢測遊戲的最高分
具體實現如下:

//判斷分數是否已經被儲存
bool LayerGameStart::isSaveFile()
{
    //用一個bool值作為標誌,如果有則表示分數已經被儲存
    if (!UserDefault::getInstance()->getBoolForKey("isSaveFileXml"))
    {
        //如果沒有就設定標誌並置為真
        UserDefault::getInstance()->setBoolForKey("isSaveFileXml"
,true); //設定最高分,預設值為0 UserDefault::getInstance()->setIntegerForKey("HightestScore",0); ////flush的作用是將資料寫入到xml檔案中。flush()在windows下是空的。。。呵呵。。。 UserDefault::getInstance()->flush(); return false; } else return true; } void LayerGameStart::getHightestScore
() { if (isSaveFile()) { //在這裡設定歷史最高得分 LayerGameOver::_hightestScore = UserDefault::getInstance()->getIntegerForKey("HightestScore",0); } }

然後只需要將 getHightestScore( ) 函式放在 LayerGameStartinit( ) 函式中即可。

二、遊戲結束場景(LayerGameOver)

遊戲結束場景也很容易,主要實現下面的功能:

  • 顯示本局遊戲的得分
  • 顯示歷史最高分
  • 設定“返回遊戲”按鈕,和“退出”按鈕

其中顯示本局所得分數,也是比較好實現的,需要注意的是,如何將主場景(LayerGameMain)中的分數傳遞到遊戲結束場景中去。做法如下:

    static LayerGameOver * create(int score);
    static cocos2d::Scene * scene(int score);
    bool init(int score);

在建立場景時將分數傳入,即在 init( ) 函式中傳入分數,因為 init 在 create 有被呼叫,而 create 函式又在 scene 函式中被呼叫,所以這三個函式都有引數了。在切換場景的時候直接將分數傳遞過來就行。

然後就是顯示歷史最高分,顯示歷史最高分需要在遊戲結束場景的 class 中新增一個靜態成員變數

    static int _hightestScore;//用於存到本地,記得要在class外進行初始化!
    cocos2d::Label * hightestScore;//用於顯示

具體實現如下:

    //顯示歷史最高分
    Value strHightestScore(_hightestScore);
    hightestScore = Label::createWithBMFont("font/font.fnt",strHightestScore.asString());
    hightestScore->setColor(Color3B(30,50,240));
    hightestScore->setAnchorPoint(Point::ANCHOR_MIDDLE_LEFT);
    hightestScore->setPosition(Point(150,winSize.height-40));
    this->addChild(hightestScore);

返回和退出按鈕就很簡單了,就是設定兩個圖片作為按鈕 (MenuItemSpritebackItemexitItem 然後將這兩個精靈新增到 Menu 中去:Menu * menu = Menu::create(backItem, exitItem, nullptr); 這兩個按鈕點選的回撥函式也很簡單,返回就是切換到遊戲開始場景,退出就是直接退出程式 exit(1);

三、遊戲主場景

遊戲主場景就是最重要也最難的,主要有一下功能:

  • 新增遊戲背景並讓遊戲背景滾動起來

  • 新增玩家飛機
    1.飛機的動作行為(閃三次後播放自身幀動畫,要方便控制,擴大其BoundingBox)
    2.飛機不能飛出螢幕
    3.由於其他層也需要飛機的位置,為了方便其他層獲得飛機,將其設計為單例getInstance( )
    4.玩家飛機自身爆炸的動畫(因為爆炸後要切換到遊戲結束場景,所以要在這裡將本局遊戲得分傳到結束場景)

  • 新增子彈
    1.首先得拿到玩家飛機的位置(因為子彈是從玩家飛機發出的)
    2.設定定時器來產生子彈
    3.讓子彈飛~
    4.子彈的行為:與敵機碰撞或者什麼也沒碰撞到——飛出螢幕外
    5.將子彈放在一個容器裡面,便於碰撞檢測
    6.不只有單發子彈還有多發子彈(MultiBullets

  • 新增敵機
    1.首先有一個敵機類Enemy,用於產生所有敵機(小敵機,中敵機,打敵機)
    2.敵機產生的位置(螢幕上方,隨機產生)
    3.敵機消失在螢幕下方
    4.與子彈和玩家飛機發生碰撞
    5.敵機爆炸動畫,玩家得分。

  • 新增道具
    1.在玩遊戲時,會用道具出現(從螢幕上方,隨機出現)
    2.道具有兩種:炸彈和雙發子彈(bigBoom,multiBullets)
    3.道具行為:與玩家飛機碰撞或者什麼也沒碰到——掉到螢幕外
    4.炸彈可以讓當前螢幕中所有敵機爆
    5.雙發子彈增加玩家飛機的威力(第一次吃到會變成雙發子彈,以後再吃到不會再加子彈而是直接給玩家加100分)

  • 新增控制層
    1.更新玩家得分
    2.實現遊戲的暫停和繼續的功能(新增遮蔽層)

怎麼樣,頭暈了嗎?要加這麼多層,實現這麼多功能。。。還有一些我沒寫(一時想不起來)
我也不準備,每個都詳細說明了,我就說說實現這些功能需要注意的地方吧,也是我覺得比較難的地方。。。

1. 如何實現螢幕滾動:

我在這裡其實只用了一個背景圖,但是把它載入了兩次,然後讓兩個圖片一起向下移動,具體過程請看下圖

這裡寫圖片描述

程式碼實現如下:

//新增背景
void LayerGameMain::addBackground()
{
    SimpleAudioEngine::getInstance()->playBackgroundMusic("sound/game_music.wav",true);

    auto bg1 = Sprite::createWithSpriteFrameName("background.png");
    bg1->setTag(BG1);
    bg1->setAnchorPoint(Point::ZERO);
    bg1->setPosition(Point(0,0));
    this->addChild(bg1);

    auto bg2 = Sprite::createWithSpriteFrameName("background.png");
    bg2->setTag(BG2);
    bg2->setAnchorPoint(Point::ZERO);
    bg2->setPosition(0,bg1->getContentSize().height - 5);//為了不留空隙
    this->addChild(bg2);

    //利用幀迴圈來實現背景滾動
    this->schedule(schedule_selector(LayerGameMain::movingBackgroundCallback),0.01f);
}

//使得背景滾動起來
void LayerGameMain::movingBackgroundCallback(float dt)
{
    Sprite * bg1 = (Sprite *)this->getChildByTag(BG1);
    Sprite * bg2 = (Sprite *)this->getChildByTag(BG2);

    bg1->setPositionY(bg1->getPositionY() - 2);//每個迴圈下移2個畫素
    bg2->setPositionY(bg1->getPositionY() + bg2->getContentSize().height - 2);

    if (bg2->getPositionY() < 0)
    {
        bg1->setPositionY(0);//重置背景
    }
}

2. 如何將玩家飛機設計為單例?
cocos2d中很多類都有獲得單例的函式getInstance( ),這裡我也寫了這麼一個函式來得到飛機單例

class MyPlane : public cocos2d::Sprite
{
        /*省略部分程式碼*/
        //將飛機設計成全域性的
    static MyPlane * getInstance();
    static MyPlane * _splane;
}

//初始化
MyPlane * MyPlane::_splane = nullptr;

MyPlane * MyPlane::getInstance()
{
    if (!_splane)
    {
        _splane = new MyPlane();
        if (_splane && _splane->init())
        {
            //不將其掛到渲染樹上,讓飛機的生命週期跟場景一樣
            //_splane->autorelease();
        }
    }
    return _splane;//return 在if語句外面
}

還有就是玩家飛機爆炸的函式,需要傳入飛機爆炸之前得到的分數。好讓遊戲結束場景能得到分數。

3. 子彈層的設計
因為玩家的飛機是不斷移動的,然後子彈的動作都是 MoveTo ,其 create 函式只有時間變數,我們如何使得子彈的速度是一樣的呢?很簡單,根據 v = s / t;若想要速度一樣則在距離不同的情況下,就必須改變子彈執行的時間,所以只要給不同位置發出的子彈不同的時間,就可以使得子彈的速度一樣。具體實現如下:

    //得到子彈到螢幕上邊沿的距離
    float distance = 
        winSize.height - plane->getPositionY() - plane->getBoundingBox().size.height/2;
    //確定子彈的速度 一秒跨越800個畫素。
    float velocity = 800/1;
    //根據距離和速率求得時間
    float movedt = distance / velocity;
    //子彈在movedt的時間內移動到螢幕上邊沿之外的地方(加上的 bullet->getContentSize().height 就是超出螢幕的距離)
    MoveTo * to = MoveTo::create(movedt,
        Point(birthPlace.x,winSize.height + bullet->getContentSize().height));

4. 敵機類的設計
這個不說了直接看程式碼,程式碼都有註釋的
Enemy.h:

#ifndef __Enemy_H_
#define __Enemy_H_
#include "cocos2d.h"

class Enemy : public cocos2d::Node
{
public:
    //構造器
    Enemy();
    //析構器
    ~Enemy();
    //建立敵機
    static Enemy * create();
    //將敵機與其對應的Sprite(圖片)和生命值繫結(有三類敵機)
    void bindEnemySprite(cocos2d::Sprite * spr,int life);
    //得到敵機
    cocos2d::Sprite * getSprite();
    //得到生命值
    int getLife();
    //失去生命值
    void loseLife();
    //得到敵機在世界座標內的的位置和尺寸大小boundingbox
    cocos2d::Rect Get_BoundingBox();

private:
    cocos2d::Sprite * _sprite;
    int _life;
};

#endif

Enemy.cpp

#include "Enemy.h"
USING_NS_CC;

Enemy::Enemy()
{
    //在建構函式中初始化,其實也可以在init函式中初始化,但這裡沒有init函式
    _sprite = nullptr;
    _life = 0;
}
Enemy::~Enemy()
{

}
Enemy * Enemy::create()
{
    Enemy * pRect = new Enemy();
    if (pRect != nullptr)
    {
        pRect->autorelease();
        return pRect;
    }
    else
        return nullptr;
}
//繫結敵機,不同的敵機有不同的圖片,不同的生命值
void Enemy::bindEnemySprite(cocos2d::Sprite * spr,int life)
{
    _sprite = spr;
    _life = life;
    //將_sprite加到 pRect 上!!pRect 實質就是一個Node
    this->addChild(_sprite);
}
Sprite * Enemy::getSprite()
{
    return _sprite;
}
int Enemy::getLife()
{
    return _life;
}
void Enemy::loseLife()
{
    _life--;
}
//自定義的getBoundingBox函式,便於主場景中的碰撞檢測
Rect Enemy::Get_BoundingBox()
{
    Rect rect = _sprite->getBoundingBox();
    //本來敵機是加到pRect上的它的座標是相對於pRect的
    //這裡將敵機的座標轉換為世界座標
    Point position = this->convertToWorldSpace(rect.origin);
    //這裡只需要知道敵機的起始座標,因為敵機的寬度和長度是不會改變的
    Rect enemyRect = Rect(position.x, position.y, rect.size.width, rect.size.height);
    return enemyRect;
}

5.有了敵機類,就要將敵機新增到主場景中去(LayerEnemy)
因為要加3類敵機,其實每一類敵機的新增方法都一樣,只不過他們的圖片,生命值,出場概率,被擊毀後玩家所得的分數不相同罷了。在這裡就將新增小敵機的方法說一下,中敵機和大敵機都一樣。

    //小敵機更新函式(在定時器裡面呼叫)
    void addSmallEnemyCallback(float dt);
    //小敵機移動完成後(沒有碰撞)
    void smallEnemyMoveFinished(cocos2d::Node * node);
    //小敵機爆炸
    void smallEnemyBlowup(Enemy * smallEnemy);
    //移除小敵機
    void removeSmallEnemy(cocos2d::Node * target, void * data);
    //移除所有小敵機
    void removeAllSmallEnemy();

    //容器,用來存放所有小敵機,便於碰撞檢測
    cocos2d::Vector<Enemy *> _smallVec;

實現函式:

//新增敵機的回撥函式(在幀迴圈裡面呼叫)
void LayerEnemy::addSmallEnemyCallback(float dt)
{
    Enemy * smallEnemy = Enemy::create();
    //繫結
    smallEnemy->bindEnemySprite(Sprite::createWithSpriteFrameName("enemy1.png"),SMALL_MAXLIFE);
    //加到smallVec中
    _smallVec.pushBack(smallEnemy);
    //確定敵機的座標:橫座標x是一個隨機值
    //smallEnemy->Get_BoundingBox().size.width/2 < x < winSize.width - smallEnemy->Get_BoundingBox().size.width/2
    //注意:這裡要使用 Enemy 類裡面的Get_BoundingBox() 函式!
    float x = CCRANDOM_0_1()*(winSize.width - 2*smallEnemy->Get_BoundingBox().size.width) + 
         smallEnemy->Get_BoundingBox().size.width/2;
    float y = winSize.height + smallEnemy->Get_BoundingBox().size.height/2;
    Point smallBirth = Point(x,y);
    //設定座標
    smallEnemy->setPosition(smallBirth);
    this->addChild(smallEnemy);

    MoveTo * to = MoveTo::create(3,Point(smallBirth.x,smallBirth.y - 
        winSize.height - smallEnemy->Get_BoundingBox().size.height));
    CallFuncN * actionDone = CallFuncN::create(this,
            callfuncN_selector(LayerEnemy::smallEnemyMoveFinished));
    Sequence * sequence = Sequence::create(to,actionDone,NULL);
    smallEnemy->runAction(sequence);
}

//敵機爆炸的函式
void LayerEnemy::smallEnemyBlowup(Enemy * smallEnemy)
{
    SimpleAudioEngine::getInstance()->playEffect("sound/enemy1_down.wav");
    Animate * smallAnimate = 
        Animate::create(AnimationCache::getInstance()->animationByName("smallBlowup"));


    /*利用 CallFuncN 來完成 CallFuncND 的功能 !!
    注意這裡(我花了很長時間才解決請看http://blog.csdn.net/crayondeng/article/details/18767407)*/

    auto actionDone = 
        CallFuncN::create(CC_CALLBACK_1(LayerEnemy::removeSmallEnemy,this,smallEnemy));
    Sequence * sequence = Sequence::create(smallAnimate,actionDone,NULL);
    smallEnemy->getSprite()->runAction(sequence);//這麼寫可以嗎? smallEnemy->runAction(sequence)不行!
}

//這是沒有碰撞的remove
void LayerEnemy::smallEnemyMoveFinished(cocos2d::Node * node)
{
    Enemy * smallEnemy = (Enemy *)node;
    this->removeChild(smallEnemy,true);
    _smallVec.eraseObject(smallEnemy);
    //node->removeAllChildrenWithCleanup(true);
}

//這是碰撞之後的remove
void LayerEnemy::removeSmallEnemy(cocos2d::Node * target,void * data)
{
    Enemy * smallEnemy = (Enemy *)data;
    if (smallEnemy)
    {
        _smallVec.eraseObject(smallEnemy);
        smallEnemy->removeFromParentAndCleanup(true);//和這句等效:this->removeChild(smallEnemy,true);
    }

}

//去掉所有小敵機
void LayerEnemy::removeAllSmallEnemy()
{
    for (auto node : _smallVec)
    {
        Enemy * enemy = (Enemy *)node;
        if (enemy->getLife() > 0)
        {
            this->smallEnemyBlowup(enemy);
        }
    }
}

6. 然後就新增道具層

道具有兩種,一個是大炸彈,一個是雙發子彈。它們產生的位置都是在螢幕上方,隨機產生。
這裡主要說一說炸彈,因為炸彈是可以點選的,一點選後,當前螢幕的所有敵機都會爆炸。炸彈的數量減一。所以炸彈需要在主場景的幀迴圈中不斷檢測,用一個容器來存放炸彈,玩家飛機一吃到炸彈道具,就更新炸彈數

void LayerGameMain::updateBigBoomCount(int bigBoomCount)
{
    String strBoomCount;//用來顯示炸彈的數量
    Sprite * norBoom = Sprite::createWithSpriteFrameName("bomb.png");//正常的圖片
    Sprite * selBoom = Sprite::createWithSpriteFrameName("bomb.png");//選擇的圖片
    if (bigBoomCount < 0)//如果小於0
    {
        return;//則什麼也不做
    }
    else if (bigBoomCount == 0)//如果炸彈數等於0
    {
        if (this->getChildByTag(TAG_BIGBOOM))//在主場景裡檢查是否有炸彈圖示
        {
            this->removeChildByTag(TAG_BIGBOOM,true);//如果有,就將其刪除
        }
        if (this->getChildByTag(TAG_BIGBOOMCOUNT))//在主場景裡面檢查是否有炸彈數字標籤
        {
            this->removeChildByTag(TAG_BIGBOOMCOUNT,true);//如果有,則刪除
        }
    }
    else if (bigBoomCount == 1)//如果炸彈數等於1
    {
        if ( !(this->getChildByTag(TAG_BIGBOOM)) )//檢查是否有炸彈圖示
        {
            //如果沒有,就新增一個炸彈圖示(其實是一個選單項)
            MenuItemSprite * boomItem = MenuItemSprite::create(norBoom,
                                                               selBoom,
                   CC_CALLBACK_1(LayerGameMain::boomMenuCallback,this));
            boomItem->setPosition(norBoom->getContentSize().width/2,
                                  norBoom->getContentSize().height/2);
            Menu * boomMenu = Menu::create(boomItem,nullptr);
            boomMenu->setPosition(Point::ZERO);
            this->addChild(boomMenu,0,TAG_BIGBOOM);
        }
        if ( !(this->getChildByTag(TAG_BIGBOOMCOUNT)) )//檢查是否有炸彈數字標籤
        {
            //如果沒有,就新增一個炸彈數字標籤
            strBoomCount.initWithFormat("X %d",bigBoomCount);
            LabelBMFont * labelBoomCount = 
                LabelBMFont::create(strBoomCount.getCString(),"font/font.fnt");
            labelBoomCount->setAnchorPoint(Point::ANCHOR_MIDDLE_LEFT);
            labelBoomCount->setPosition(Point(norBoom->getContentSize().width,
                                        norBoom->getContentSize().height - 30));
            this->addChild(labelBoomCount,0,TAG_BIGBOOMCOUNT);
        }
    }
    else if (bigBoomCount > 1 )//如果炸彈數大於1
    {
        //則只更新炸彈數目
        strBoomCount.initWithFormat("X %d",bigBoomCount);
        LabelBMFont * labelCount = 
            (LabelBMFont *)this->getChildByTag(TAG_BIGBOOMCOUNT);
        labelCount->setString(strBoomCount.getCString());//設定炸彈數目
    }
}

7. 最後來新增控制層
控制層主要是兩個作用:1,暫停和繼續遊戲(新增遮蔽層)2,更新玩家得分
暫停和繼續遊戲需要兩個按鈕來控制。剛開始遊戲時,遊戲是進行著的,沒有暫停。當玩家按了暫停按鈕後,遊戲暫停,按鈕變成繼續狀態(三角形)這個還是比較簡單的。下面來看實現程式碼:

LayerControl.h

#ifndef __LayerControl_H_
#define __LayerControl_H_
#include "cocos2d.h"
#include "LayerNoTouch.h"
class LayerControl : public cocos2d::Layer
{
public:
    CREATE_FUNC(LayerControl);
    bool init();

    void menuCallback(cocos2d::Ref * ref);
    void updateScore(int score);


private:
    cocos2d::MenuItemSprite * pauseMenuItem;
    cocos2d::LabelBMFont * scoreItem;
    LayerNoTouch * _noTouchLayer;
};

#endif

LayerControl.cpp

#include "LayerControl.h"
#include "AppMacros.h"
#include "SimpleAudioEngine.h"
using namespace CocosDenshion;
USING_NS_CC;

bool LayerControl::init()
{
    if (!Layer::init())
    {
        return false;
    }
    _noTouchLayer = nullptr;//初始化
    //暫停按鈕不同狀態下的兩個圖片
    Sprite * nor = Sprite::createWithSpriteFrameName("game_pause_nor.png");
    Sprite * press = Sprite::createWithSpriteFrameName("game_pause_pressed.png");
    pauseMenuItem = 
        MenuItemSprite::create(nor,press,CC_CALLBACK_1(LayerControl::menuCallback,this));
    Point menuBrith = Point(pauseMenuItem->getContentSize().width/2 + 10, 
                            winSize.height - pauseMenuItem->getContentSize().height);
    pauseMenuItem->setPosition(menuBrith);
    Menu * pauseMenu = Menu::create(pauseMenuItem,nullptr);
    pauseMenu->setPosition(Point::ZERO);
    this->addChild(pauseMenu,101);//將暫停/繼續 按鈕放在最前面

    scoreItem = LabelBMFont::create("0","font/font.fnt");
    scoreItem->setColor(Color3B(255,255,0));
    scoreItem->setAnchorPoint(Point(0,0.5));
    scoreItem->setPosition(Point(pauseMenuItem->getPositionX() + nor->getContentSize().width/2 + 5, 
                                 pauseMenuItem->getPositionY()));
    this->addChild(scoreItem);

    return true;
}

//按鈕回撥函式
void LayerControl::menuCallback(cocos2d::Ref * ref)
{
    if (!Director::getInstance()->isPaused())//如果點選按鈕之前遊戲沒有暫停
    {
        if (SimpleAudioEngine::getInstance()->isBackgroundMusicPlaying())
        {
            //如果背景音樂還在播放,則暫停其播放
            SimpleAudioEngine::getInstance()->pauseBackgroundMusic();
        }
        //則將 暫停/繼續 按鈕設定為繼續狀態的按鈕
        pauseMenuItem->setNormalImage(Sprite::createWithSpriteFrameName("game_resume_nor.png"));
    pauseMenuItem->setSelectedImage(Sprite::createWithSpriteFrameName("game_resume_pressed.png"));
        //並暫停遊戲
        Director::getInstance()->pause();
        //新增遮蔽層,遮蔽層一定要加到其它所有層前面,暫停/繼續 按鈕的後面!!!
        _noTouchLayer = LayerNoTouch::create();
        this->addChild(_noTouchLayer);
    }
    else
    {
        SimpleAudioEngine::getInstance()->resumeBackgroundMusic();//恢復背景音樂
        pauseMenuItem->setNormalImage(Sprite::createWithSpriteFrameName("game_pause_nor.png"));
    pauseMenuItem->setSelectedImage(Sprite::createWithSpriteFrameName("game_pause_pressed.png"));
        Director::getInstance()->resume();
        this->removeChild(_noTouchLayer,true);
    }
}
//更新遊戲得分,這個函式也可以放在主場景中
void LayerControl::updateScore(int score)
{
    /*2.0版本
    String * strScore = String::createWithFormat("%d",score);
    scoreItem->setString(strScore->getCString());*/

    //3.0版本
    Value strScore(score);
    scoreItem->setString(strScore.asString());//更新成績轉換為字串
}

8. 最後,我們來看看主場景裡面都做了什麼

bool LayerGameMain::init()
{
    if (!Layer::init())
    {
        return false;
    }
    _bigBoomCount = 0;
    _score = 0;//不要將_score 設定為static ,否則在場景切換時,它不能清零

    this->addBackground();

    this->addMyPlane();

    this->addBulletLayer();//執行了startShoot()

    this->addMultiBulletsLayer();//沒有執行startShoot()

    this->addEnemyLayer();

    this->addFoodLayer();

    this->addControlLayer();

    this->scheduleUpdate();//開啟定時器便於碰撞檢測

    auto listener = EventListenerTouchOneByOne::create();
    listener->setSwallowTouches(true);//觸控吞噬
    listener->onTouchBegan = CC_CALLBACK_2(LayerGameMain::onTouchBegan,this);
    listener->onTouchMoved = CC_CALLBACK_2(LayerGameMain::onTouchesMoved,this);
    this->_eventDispatcher->addEventListenerWithSceneGraphPriority(listener,this);

    return true;
}

這是主場景的初始化函式,大家都應該看到了,依次添加了背景、玩家飛機、子彈層、敵機層、食物層、控制層、最後開啟幀迴圈定時器、設定觸控事件的監聽。
怎麼樣,是不是很簡單,很清晰? 哈哈,當做完之後再會過來看自己寫的程式碼還真是挺好。。

最後的最後

來說一說主場景裡面的 update 函式。這個函式裡面做了整個遊所有的碰撞檢測。其原理都一樣,這裡我拿子彈與小敵機的碰撞檢測為例說明一下:

//單發子彈與小敵機碰撞
    /*思路:
        兩次遍歷(即兩個for迴圈),第一次遍歷子彈容器(_bulletVector),取出其第一個子彈,
    第二次遍歷小敵機容器(_smallVec)將這個取出的子彈與當前螢幕上所有的小敵機做碰撞檢測,
    如果檢測到碰撞,再判斷當前碰撞到的小敵機的生命值_life 若等於1,則小敵機失去生命值
    再分別將當前的子彈和當前的小敵機加到容器 bulletToDel_Small 和 smallToDel 中去,
    當第一個子彈與螢幕上的敵機全部碰撞檢測完以後,就把 bulletToDel_Small 和 smallToDel
    裡面的物件全部刪除,這樣可以防止在遍歷時發生錯誤!*/
    Vector<Sprite *> bulletToDel_Small;
    for (auto bt : _bulletLayer->_bulletVector)
    {
        Sprite * bullet = bt;
        Vector<Enemy *> smallToDel;
        for (auto et : _enemyLayer->_smallVec)
        {
            Enemy * enemy = et;
            if (bullet->getBoundingBox().intersectsRect(enemy->Get_BoundingBox()))
            {
                if (enemy->getLife() == 1)
                {
                    enemy->loseLife();
                    bulletToDel_Small.pushBack(bullet);
                    smallToDel.pushBack(enemy);
                    _score += SMALL_SCORE;//加上小敵機的分數
                    _controlLayer->updateScore(_score);
                }
            }

        }
        for(auto et : smallToDel)//注意for迴圈的位置,要與建立時的語句在同一層
        {
            Enemy * enemy = et;
            _enemyLayer->smallEnemyBlowup(enemy);//敵機爆炸(刪除)
        }
    }
    for (auto bt : bulletToDel_Small)//注意for迴圈的位置,要與建立時的語句在同一層
    {
        Sprite * bullet = bt;
        _bulletLayer->removeBullet(bullet);//刪除子彈
    }

好了,就寫到這裡了,寫的很亂,不知道大家能不能看懂。
要是有什麼寫錯了的地方,還望斧正。

最後附上本遊戲的完整程式碼地址: PlaneFight