Cocos2d-x3.x塔防游戲(保衛蘿卜)從零開始(三)


一、前提:

完成前一篇的內容。

具體參考:Cocos2d-x3.x塔防游戲(保衛蘿卜)從零開始(二)篇

二、本篇目標:

l  說說游戲中各種角色的動作、屬性以及重構思路

l  進行代碼重構讓色狼大叔和女主角生動鮮活起來

三、內容:

說說游戲中各種角色的動作、屬性以及重構思路

通過前兩篇我們建立的一個簡陋的游戲原型,但是游戲中的人物比如色狼大叔、女主角都看去來很呆板不夠鮮活,比如色狼會沿着道路移動,那這個只能說是移動根本不像是在走路,沒有走的動作感覺就是沿着道路在漂移,女主角也是一動不動的站那里。這樣的游戲很沒有樂趣,所以需要給這些游戲角色加入動作和表情,讓人看去來他們是鮮活的,對這些角色進行一下簡單的動畫以及屬性分析如下:

色狼大叔的動畫:

1、          靜止動畫:游戲剛開始處於靜止狀態

2、          走路動畫:沿着道路走

3、          死亡動畫:當子彈擊中血量消耗完時死亡消失

色狼大叔的屬性:

1、          是否運動:色狼是否處於激活沿着道路行走

2、          是否死亡:是否被炮塔打死

3、          行走速度:不同色狼的行走速度不同

4、          色狼血量:不同色狼血量不同

5、          行走:沿着道路行走

女主角的動畫:

1、          靜止動畫:游戲剛開始處於靜止狀態

2、          賣萌動畫:不能像木頭似的,就加點表情動作

3、          死亡動畫:當純潔值被色狼玷污光了就死亡了

女主角的屬性:

1、          女主角貞潔值:相當於生命值

 

根據上面的分析我們把每個角色拆分成動畫顯示和業務屬性邏輯兩個部分,對色狼和女主角進行代碼重構。

重構后大概結構如上圖:

ActionSprite:屬於CCSprite類,負責游戲角色精靈的動畫顯示,Luoli(蘿莉)、DaShu(大叔)、JiaoShou(叫獸)等角色精靈均繼承自ActionSprite,都屬於動畫顯示類。

Luoli(蘿莉):屬ActionSprite的子類,負責女主角的動畫效果展示。

DaShu(大叔)屬ActionSprite的子類,負責大叔色狼的動畫效果展示。

JiaoShou (叫獸)屬ActionSprite的子類,負責叫獸色狼的動畫效果展示。

Selang(色狼):屬於CCNode類,負責色狼的行走、血量、速度、攻擊等具體的業務,每個Selang都包含一個DaShu(大叔)或JiaoShou(叫獸)類的游戲精靈。並且具備最重要的行為實現沿着道路行走。

Girl(女孩):屬於CCNode類,負責女主角的血量等具體的業務,每個Girl都包含一個Luoli類的游戲精靈。

進行代碼重構讓色狼大叔和女主角生動鮮活起來

打開項目工程按照上面的思路重點對色狼和女主角的代碼實現進行重構。

色狼大叔代碼重構

第一步:

新建ActionSprite.h、ActionSprite.cpp類(角色動畫類),這個類繼承自CCSprite負責游戲角色的動畫效果顯示,色狼和女孩都會是這個類的子類。

ActionSprite.h代碼:

//聲明一個動作狀態的枚舉類型
typedef enum _ActionState{
    kActionStateNone = 0, //無狀態
    kActionStateIdle, //靜止狀態
    kActionStateWalk, //行走狀態
    kActionStateDeath //死亡狀態
}ActionState;

class ActionSprite: public cocos2d::CCSprite
{
public:
    ActionSprite(void);
    ~ActionSprite(void);
    //靜止
    void idle();
    //死亡
    void death();
    //行走
    void walk();
    //價格
    CC_SYNTHESIZE(int,_price,Price);
    //生命值
    CC_SYNTHESIZE(float,_hp,HP);
    //靜止狀態動作
    CC_SYNTHESIZE_RETAIN(cocos2d::Action*,_idleAction,IdleAction);
    //死亡狀態動作
    CC_SYNTHESIZE_RETAIN(cocos2d::Action*,_deathAction,DeathAction);
    //行走狀態動作
    CC_SYNTHESIZE_RETAIN(cocos2d::Action*,_walkAction,WalkAction);
    //當前動作狀態
    CC_SYNTHESIZE(ActionState,_actionState,ActionState);
    //行走速度
    CC_SYNTHESIZE(float,_walkSpeed,WalkSpeed);
    //傷害值
    CC_SYNTHESIZE(float,_damage,Damage);
    //金錢
    CC_SYNTHESIZE(float,_money,Money);
    //是否有光環
    CC_SYNTHESIZE(bool,_halo,Halo);
    
};

ActionSprite.cpp代碼:

ActionSprite::ActionSprite(void)
{
    _price=0;
    _idleAction=NULL;
    _walkAction=NULL;
    _deathAction=NULL;
}

ActionSprite::~ActionSprite(void)
{
    //釋放內存
    CC_SAFE_RELEASE_NULL(_idleAction);
    CC_SAFE_RELEASE_NULL(_deathAction);
    CC_SAFE_RELEASE_NULL(_walkAction);
}

//設置精靈為靜止狀態
void ActionSprite::idle()
{
    if (_actionState != kActionStateIdle)
    {
        //先停止所有動作
        this->stopAllActions();
        //運行靜止動作
        this->runAction(_idleAction);
        _actionState=kActionStateIdle;
    }
}

//設置精靈為行走狀態
void ActionSprite::walk()
{
    if (_actionState != kActionStateWalk)
    {
        //先停止所有動作
        this->stopAllActions();
        //運行行走動作
        this->runAction(_walkAction);
        _actionState=kActionStateWalk;
    }

}

//設置精靈為死亡狀態
void ActionSprite::death()
{
    //先停止所有動作
    this->stopAllActions();
    this->runAction(_deathAction);
    _actionState=kActionStateDeath;
}

第二步:

素材准備,設計2張大叔不同動作的圖片,交替顯示模擬色狼行走動畫,完成后把圖片拷貝到Resources的iphonehd文件夾中,為了適應小分辨率的手機把這個2張圖片按比例縮小一半並且拷貝到Resources的iphone文件夾中。

第三步:

新建DaShu.h、DaShu.cpp類(色狼大叔動畫類),這個類繼承自上面的ActionSprite負責游戲色狼大叔的動畫效果顯示。

DaShu.h:

class DaShu : public ActionSprite
{
public:
    DaShu(void);
    ~DaShu(void);

    CREATE_FUNC(DaShu);
    //初始化方法
    bool init();
    //設置光環,擁有光環的色狼生命值加倍
    void setHalo(bool halo);
};

DaShu.cpp:

bool DaShu::init()
{
    bool bRet=false;
    do 
    {
        CC_BREAK_IF(!ActionSprite::initWithFile("dashu_2.png"));
        
        //設置靜止狀態動作
        Vector<SpriteFrame *> idleFrames(1);
        SpriteFrame *frame1=SpriteFrame::create("dashu_2.png", Rect(0, 0, 60, 83));
        idleFrames.pushBack(frame1);
        Animation *idleAnimation=Animation::createWithSpriteFrames(idleFrames,float(6.0 / 12.0));
        this->setIdleAction(CCRepeatForever::create(CCAnimate::create(idleAnimation)));

        int i=0;
        //設置行走狀態動作
        Vector<SpriteFrame *> walkFrames(2);
        for (i=0;i<2;i++)
        {
            SpriteFrame *frame2=SpriteFrame::create(CCString::createWithFormat("dashu_%d.png", i+1)->getCString(), Rect(0, 0, 60, 83));
            walkFrames.pushBack(frame2);
        }
        Animation *walkAnimation=Animation::createWithSpriteFrames(walkFrames,float(6.0 / 12.0));
        this->setWalkAction(CCRepeatForever::create(CCAnimate::create(walkAnimation)));

        //設置死亡狀態動作
        Vector<SpriteFrame *> deathFrames(1);
        SpriteFrame *frame3=SpriteFrame::create("dashu_2.png", Rect(0, 0, 60, 83));
        deathFrames.pushBack(frame3);
        Animation *deathAnimation=Animation::createWithSpriteFrames(deathFrames,float(6.0 / 12.0));
        this->setDeathAction(Animate::create(deathAnimation));
        //設置攻擊值
        this->setDamage(1);
        //設置行走速度
        this->setWalkSpeed(0.4f);
        //設置生命值
        this->setHP(18);
        //設置金錢數
        this->setMoney(1.0f/10.0f);
        bRet=true;
    } while (0);

    return bRet;
}

//設置光環
void DaShu::setHalo(bool halo)
{
    if (halo)
    {
        //擁有光環后生命值加4倍
        float h=this->getHP()*4.0f;
        this->setHP(h);
    }
    
}

第四步:

新建Selang.h、Selang.cpp類(色狼類),這個類繼承自CCNode游戲場景中的每一個色狼都有這個類產生,它肯定包含一個ActionSprite的色狼動畫類,並且之前在MainScene.cpp的update方法中實現的色狼沿路行走代碼也將轉移到這個類的update方法中。

Selang.h:

#include "cocos2d.h"
#include "Waypoint.h"
#include "GameMediator.h"
#include "ActionSprite.h"
class Selang : public cocos2d::CCNode
{
public:
    Selang(void);
    ~Selang(void);
    //根據提供的spriteIndex實例化成不同的色狼
    static Selang* nodeWithType(int spriteIndex);
    //初始化方法
    bool initWithType(int spriteIndex,bool halo);
    //激活色狼
    void doActivate(float dt);
    //獲取精靈Rect
    virtual cocos2d::Rect getRect();
    //設置精靈是否激活
    void setActive(bool active);
    //是否死亡
    bool isDie;
    void update(float delta);
    //色狼精靈
    CC_SYNTHESIZE_RETAIN(ActionSprite*,_mySprite,MySprite);

private:
    //精靈序號,為每種精靈編一個序號
    int _spriteIndex;
    GameMediator* m;
    //當前精靈的位置
    cocos2d::Point myPosition;
    //走路速度
    float walkingSpeed;
    //開始路點
    Waypoint *beginningWaypoint;
    //結束路點
    Waypoint *destinationWaypoint;
    //是否激活
    bool active; 
    //色狼高度
    float myHeight;
    //兩個點的碰撞檢測
    bool collisionWithCircle(cocos2d::Point circlePoint,float radius,cocos2d::Point circlePointTwo, float radiusTwo);
};

Selang.cpp

//根據提供的spriteIndex實例化成不同的色狼
Selang* Selang::nodeWithType(int spriteIndex)
{
    Selang* pRet=new Selang();
    bool b=false;
    if (pRet && pRet->initWithType(spriteIndex,b))
    {
        pRet->autorelease();
        return pRet;
    } 
    else
    {
        delete pRet;
        pRet=NULL;
        return NULL;
    }
}
//初始化方法
bool Selang::initWithType(int spriteIndex,bool halo)
{
    bool bRet=false;
    do 
    {
        //色狼類型index
        _spriteIndex=spriteIndex;
        //
        m = GameMediator::sharedMediator();
        //不激活
        active=false;
        //行走速度
        walkingSpeed=0.2f;
        ActionSprite* sprite=NULL;
        if (spriteIndex==1)//如果類型是1初始化成大叔色狼
        {
            sprite=DaShu::create();
            sprite->setHalo(halo);
            //設置速度
            walkingSpeed=sprite->getWalkSpeed();
        }
        this->setMySprite(sprite);
        //添加精靈到當前Selang中
        this->addChild(_mySprite);
        //計算當前色狼精靈1/2高
        myHeight=sprite->getTextureRect().size.height/2.0f;
        //獲得路點集合中的最后一個點
        Waypoint *waypoint=(Waypoint*)m->getWayPoints().back();
        //設置為色狼出發點
        beginningWaypoint=waypoint;
        //獲取出發點的下個點為色狼目標點
        destinationWaypoint=waypoint->getNextWaypoint();
        //獲得出發點坐標
        Point pos=waypoint->getMyPosition();
        //對坐標進行校正提供半個身位高度
        pos.add(Vec2(0,myHeight));
        //記錄位置坐標
        myPosition=pos;
        //設置精靈的初始坐標
        _mySprite->setPosition(pos);
        //設置初始不可見
        this->setVisible(false);
        //把當前色狼添加到游戲的MainScene場景中顯示
        m->getNowScene()->addChild(this);
        //啟動定時器
        this->scheduleUpdate();
        bRet=true;
    } while (0);
    return bRet;
}

void Selang::doActivate(float dt)
{
    //激活色狼
    active=true;
    //設置色狼可見
    this->setVisible(true);
}

//獲取精靈Rect
Rect Selang::getRect()
{
    Rect rect =Rect(_mySprite->getPosition().x - _mySprite->getContentSize().width * 0.5f,
        _mySprite->getPosition().y - _mySprite->getContentSize().height* 0.5f,
        _mySprite->getContentSize().width,
        _mySprite->getContentSize().height);
    return rect;
}

//設置精靈是否激活
void Selang::setActive(bool aactive)
{
    active=aactive;
    this->setVisible(true);
}

void Selang::update(float delta)
{
    if (!active)
    {
        return;
    }
    Point destinationPos=destinationWaypoint->getMyPosition();
    //提升色狼半個身位
    destinationPos.add(Vec2(0,myHeight));
    //是否拐彎
    if (this->collisionWithCircle(myPosition,1,destinationPos,1))
    {
        if (destinationWaypoint->getNextWaypoint())
        {
            //設置新的出發點和目標點
            beginningWaypoint=destinationWaypoint;
            destinationWaypoint=destinationWaypoint->getNextWaypoint();
        }
    }
    Point targetPoint=destinationWaypoint->getMyPosition();
    //提升色狼半個身位
    targetPoint.add(Vec2(0,myHeight));
    float movementSpeed=walkingSpeed;
    //計算目標點的向量
    Point normalized=Point(targetPoint.x-myPosition.x,targetPoint.y-myPosition.y).getNormalized();
    //根據速度和向量分別計算x,y方式上的偏移值
    float ox=normalized.x * walkingSpeed;
    float oy=normalized.y *walkingSpeed;
    //更新色狼移動后的位置
    myPosition = Point(myPosition.x + ox, myPosition.y +oy);
    _mySprite->setPosition(myPosition);
}
//兩個點的碰撞檢測
bool Selang::collisionWithCircle(cocos2d::Point circlePoint,float radius,cocos2d::Point circlePointTwo, float radiusTwo)
{
    float xdif = circlePoint.x - circlePointTwo.x;
    float ydif = circlePoint.y - circlePointTwo.y;
    //計算兩點間的距離
    float distance = sqrt(xdif * xdif + ydif * ydif);
    if(distance <= radius + radiusTwo) 
    {
        return true;
    }
    return false;
}

第五步:

如果運行一下那么上面的代碼中Waypoint *waypoint=(Waypoint*)m->getWayPoints().back();這行應該會報錯,因為GameMediator中沒有提供getWayPoints()這個方法,所以我們要對GameMediator類進行修改加上這個方法,代碼如下:

void  GameMediator::setWayPoints(cocos2d::Vector<Waypoint*> wayPoints)
{
    _wayPoints=wayPoints;
}

Vector<Waypoint*> GameMediator::getWayPoints()
{
    return _wayPoints;
}

第六步:

在MainScene的init方法中把路點集合通過setWayPoints方法賦值給GameMediator,這樣在Selang.cpp中就可以取到路點集合了:

……
this->wayPositions.pushBack(waypoint12);
GameMediator::sharedMediator()->setWayPoints(wayPositions);
……

第七步:

測試這個Selang類具體效果,先給MainScene添加一個void startGame(float delta)的方法,用這個方法開始游戲。

//開始游戲
void MainScene::startGame(float delta)
{
    //實例化一個大叔類型的色狼
    Selang* selang=Selang::nodeWithType(1);
    //激活這個色狼
    selang->setActive(true);
    //設置色狼動畫為行走動畫
    selang->getMySprite()->walk();
    //取消定時器方法,保證startGame只執行一次
    this->unschedule(schedule_selector(MainScene::startGame));
}

第八步:

我們在MainScene的init方法末尾處調用這個startGame的方法:

//0.5秒后調用startGame方法
this->schedule(schedule_selector(MainScene::startGame),0.5f);

到這里,把第一篇中臨時添加色狼的代碼刪除,就可以運行測試游戲了,會看到色狼大叔一扭一扭的沿着道路靠近女主角。效果非常好,我們成功的對色狼的代碼進行了重構。

女主角代碼重構

第一步:

素材准備,設計4張蘿莉不同動作的圖片,交替顯示模擬蘿莉賣萌動畫,完成后把圖片拷貝到Resources的iphonehd文件夾中,為了適應小分辨率的手機把這個4張圖片按比例縮小一半並且拷貝到Resources的iphone文件夾中。

第二步:

新建Luoli.h、Luoli.cpp類(女主角動畫類),這個類繼承自上面的ActionSprite負責游戲女主角的動畫效果顯示。

Luoli.h:

class Luoli : public ActionSprite
{
public:
    Luoli(void);
    ~Luoli(void);

    CREATE_FUNC(Luoli);
    bool init();
};

Luoli.cpp:

bool Luoli::init()
{
    bool bRet=false;
    do 
    {
        CC_BREAK_IF(!ActionSprite::initWithFile("girl1_1.png"));

        //設置靜止狀態動作
        Vector<SpriteFrame *> idleFrames(1);
        SpriteFrame *frame1=SpriteFrame::create("girl1_1.png", Rect(0, 0, 100, 126));
        idleFrames.pushBack(frame1);
        Animation *idleAnimation=Animation::createWithSpriteFrames(idleFrames,float(6.0 / 12.0));
        this->setIdleAction(CCRepeatForever::create(CCAnimate::create(idleAnimation)));

        //設置行走狀態動作
        int i;
        Vector<SpriteFrame *> walkFrames(4);
        for (i=0;i<4;i++)
        {
            SpriteFrame *frame1=SpriteFrame::create(CCString::createWithFormat("girl1_%d.png", i+1)->getCString(), Rect(0, 0, 100, 126));
            walkFrames.pushBack(frame1);
        }
        
        Animation *walkAnimation=Animation::createWithSpriteFrames(walkFrames,float(6.0 / 12.0));
        this->setWalkAction(CCRepeatForever::create(CCAnimate::create(walkAnimation)));


        bRet=true;

    } while (0);

    return bRet;
}

第三步:

新建Girl.h、Girl.cpp類(女孩類),這個類繼承自CCNode游戲場景中的女主角由這個類產生,它肯定包含一個ActionSprite的蘿莉動畫類。

Girl.h:

class Girl : public cocos2d::CCNode
{
public:
    Girl(void);
    ~Girl(void);

    //根據提供的type實例化成不同的女主角
    static Girl* nodeWithType(int type);
    //初始化方法
    bool initWithLocation(cocos2d::Point location);
    //獲取精靈Rect
    cocos2d::Rect getRect();

private:
    //蘿莉精靈
    CC_SYNTHESIZE_RETAIN(ActionSprite*,_mySprite,MySprite);
};

Girl.cpp

//根據提供的type實例化成不同的女主角
Girl* Girl::nodeWithType(int type)
{
    Girl* pRet=new Girl();
    GameMediator* m = GameMediator::sharedMediator();
    Waypoint *waypoint=(Waypoint*)m->getWayPoints().front();
    Point pos=waypoint->getMyPosition();
    
    if (pRet && pRet->initWithLocation(pos))
    {
        pRet->autorelease();
        return pRet;
    } 
    else
    {
        delete pRet;
        pRet=NULL;
        return false;
    }
}

//初始化方法
bool Girl::initWithLocation(cocos2d::Point location)
{
    bool bRet=false;
    do 
    {
        //實例化一個蘿莉
        ActionSprite *sprite= Luoli::create();
        this->setMySprite(sprite);
        //添加精靈到當前Gril中
        this->addChild(sprite);
        //設置為靜止
        sprite->idle();
        //計算當前蘿莉精靈1/2高
        int myHeight=sprite->getTextureRect().size.height/2.0f;
        //對坐標進行校正提供半個身位高度
        location.add(Vec2(0,myHeight));
        sprite->setPosition(location);
        //把當前女主角添加到游戲的MainScene場景中顯示
        GameMediator* m = GameMediator::sharedMediator();
        m->getNowScene()->addChild(this,10000);
        
        bRet=true;
    }
    while (0);
    return bRet;
}

Rect Girl::getRect()
{
    Rect rect = Rect(_mySprite->getPosition().x - _mySprite->getContentSize().width * 0.5f,
        _mySprite->getPosition().y - _mySprite->getContentSize().height* 0.5f+20,
        _mySprite->getContentSize().width,
        _mySprite->getContentSize().height-40);
    return rect;
}

第四步:

在MainScene的 startGame(float delta)的方法中加上初始化女主角的代碼。

……
//初始一個女主角
    Girl* girl=Girl::nodeWithType(1);
    //設置女主角動畫為賣萌動畫
    girl->getMySprite()->walk();
    //取消定時器方法,保證startGame只執行一次
    this->unschedule(schedule_selector(MainScene::startGame));

到這里,把第一篇中臨時添加女主角的代碼刪除,就可以運行測試游戲了,本篇的任務到此為止,本篇完成后android真機的運行效果如下:

 結束語:

這個塔防游戲系列已經寫了3篇了,到現在為止還沒有出現炮塔,說好的炮塔呢?請期待下一篇炮塔姑娘的保護神~

 

作者交流QQ:2303452599

           郵箱:mymoney1001@126.com


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM