cocos2d-x高仿捕鱼达人实例分析(三)

来源:互联网 发布:杭州软件外包情况 编辑:程序博客网 时间:2024/05/18 01:54
 资源:http://download.csdn.net/detail/hezeping888/5717513

上一节 完成了 触摸 发射子弹,不同型号大炮发射不同型号的子弹,子弹型号与大炮型号绑定 ,可以自由升级大炮型号
本节将实现 在主游戏界面动态添加鱼儿 并让鱼儿鲜活的在游戏界面活动起来。

GameLayer:加载定义的鱼儿,并调度鱼儿的创建
Fish:不同类型的鱼儿创建,并让鱼儿在游戏界面活动,并在鱼儿游出主游戏界面时自动销毁自己。
(此处不必立刻销毁,可以再完善,让鱼儿重新另外一个位置出现)

现在给出鱼儿的的头文件架构:Fish.h

#ifndef __FishingJoyStep1__Fish__
#define __FishingJoyStep1__Fish__

#include "cocos2d.h"

class GameLayer;

class Fish : public cocos2d::CCObject{
public:
    virtual ~Fish();
    /**
     创建一条鱼儿,主要负责被创建的鱼儿的内存管理设置
     @param fishType 鱼儿的类型
     @param gameLayer 游戏主界面
     @param pBatchNodeFish 装载鱼儿的地方
     */
    static Fish * createWithFishType(int fishType,GameLayer *gameLayer,cocos2d::CCSpriteBatchNode *pBatchNodeFish);
    //展示鱼儿被捕获后的死亡效果
    void showCaught();

public:
    CC_SYNTHESIZE_READONLY(bool, m_bCaught, Caught);//鱼儿是否被捕获标记

private:
    //实际创建鱼儿的地方
    bool initWithFishType(int fishType,GameLayer *gameLayer,cocos2d::CCSpriteBatchNode *pBatchNodeFish);
    //鱼儿被捕获后,把自己和相关资源冲主游戏界面销毁
    void removeSelf();
    //构造鱼儿在主界面的运动轨迹,*&moveto 用来承载构造后的鱼儿运动轨迹 
    void getPath(cocos2d::CCMoveTo *&moveto);

private:
    CC_SYNTHESIZE(GameLayer *, m_pGameLayer, GameLayer);//游戏主界面
    CC_SYNTHESIZE(cocos2d::CCSpriteBatchNode *, m_pBatchNodeFish, BatchNodeFish);//主界面中用来装载鱼儿的地方
    CC_SYNTHESIZE(int, m_nFishType, FishType);//鱼儿的类型,从fish.plist,fish2.plst,fish3.plist,fish4.plist中找到鱼儿类型定义
    CC_SYNTHESIZE(cocos2d::CCSprite *, m_pSpriteFish, SpriteFish);//被创建的鱼儿精灵
    CC_SYNTHESIZE(cocos2d::CCParticleSystem *, m_pParticleBubble, ParticleBubble);//某些鱼儿运行时可以跟随例子效果,如美人鱼运动时不断的冒气泡
    bool m_bParticleBubble;//标记当前的鱼儿是否应有的粒子效果,便于在销毁自己的时候效果相关资源
};

#endif /* defined(__FishingJoyStep1__Fish__) */

上述是否有不清晰的呢?欢迎通过邮箱沟通交流。

好的,我们下面看看相关方法的实现:Fish.cpp
//引入头文件

#include "Fish.h"

#include "GameLayer.h"

//首先看看 鱼儿创建怎么完成的:
//创建鱼儿并设置其内存管理模式
Fish *Fish::createWithFishType(int fishType, GameLayer *gameLayer, CCSpriteBatchNode *pBatchNode)
{
    Fish *fish = new Fish();
    if(fish && fish->initWithFishType(fishType, gameLayer, pBatchNode))
    {
        fish->autorelease();
        return fish;
    }
    else
    {
        delete fish;
        return NULL;
    }
}

//真正创建鱼儿的地方
//根据鱼儿类型初始话鱼儿相关状态值
bool Fish::initWithFishType(int fishType, GameLayer *gameLayer, CCSpriteBatchNode *pBatchNode)
{
    m_bCaught = false; //标记未被捕获
    this->setFishType(fishType);//设置鱼儿类型,可以考虑对类型有效性进行检查
    this->setGameLayer(gameLayer);
    this->setBatchNodeFish(pBatchNode);
    m_bParticleBubble = false;//默认鱼儿不应有粒子效果
    
    if(m_nFishType == 11 || m_nFishType == 12)  //mermaid 在正式项目中考可以虑将鱼儿类型设计成为枚举
        m_bParticleBubble = true;//标记当前种类鱼应用粒子效果,在getPath方法中使用,美人鱼有冒气泡的粒子效果
    
    CCArray *frames = CCArray::create();
    for(int i = 1; i <= 16; i++)//每一种鱼的游动动画是由多帧图片组成,最多有16帧
    {
        //根据鱼儿类型动态构建其动画的每一帧名称
        CCString *frameName = CCString::createWithFormat("fish%02d_%02d.png", fishType, i);
        //从精灵帧缓存池中查找对应帧
        CCSpriteFrame *pFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(frameName->getCString());
        if(pFrame){
            //如果找到相应名称的帧,就将其存储
            frames->addObject(pFrame);
        }
    }
    //构建鱼儿游动的帧动画,通过上面创建的帧
    CCAnimation *animation = CCAnimation::createWithSpriteFrames(frames, 0.2f);
    animation->setRestoreOriginalFrame(false);
    CCAnimate *animate = CCAnimate::create(animation);
    //重复循环动作,实现不断循环播放鱼儿游动的动画
    CCAction *swing = CCRepeatForever::create(animate);
    
    //获取鱼儿第一帧名称,用来构建鱼儿精灵
    CCString *originalFrameName = CCString::createWithFormat("fish%02d_01.png", fishType);
    m_pSpriteFish = CCSprite::createWithSpriteFrameName(originalFrameName->getCString());
    m_pSpriteFish->runAction(swing);//鱼儿精灵播放游动动画
    m_pSpriteFish->setAnchorPoint(ccp(0.5f, 0.5f));//设置锚点,以鱼儿中心点为锚点,在精灵旋转,翻转等操作时都会以锚点作为参考点
    
    CCMoveTo *moveto = NULL;
    //随机构建鱼儿运动路线
    this->getPath(moveto);
    //当鱼儿游出可见视线范围,执行自我销毁操作
    CCFiniteTimeAction *releaseFunc = CCCallFunc::create(this, callfunc_selector(Fish::removeSelf));
    CCFiniteTimeAction *sequence = CCSequence::create(moveto, releaseFunc, NULL);
    m_pSpriteFish->runAction(sequence);
    
    //让游戏主界面持有当前鱼儿精灵的指针,方便与子弹进行碰撞检测
    this->getGameLayer()->getFishes()->addObject(this);
    //到此在游戏主界面显示鲜活的鱼儿
    this->getBatchNodeFish()->addChild(m_pSpriteFish);
    
    return true;
}

在游戏中我们鱼儿是从游戏主界面四边随机出现的,下面来看看getPath方法的构建鱼儿运动路线的实现:
//此函数在getPath中用到,但没有在头文件中声明,所以必须在其被调用之前定义好。
void offsetPoint(CCPoint& pt, float offsetX, float offsetY)
{
    pt.x += offsetX;
    pt.y += offsetY;
}

void Fish::getPath(cocos2d::CCMoveTo *&moveto)
{
    CCSize fishSize = m_pSpriteFish->getContentSize();
    CCSize winSize = CCDirector::sharedDirector()->getWinSize();
    //定义鱼儿运动路线的开始结束点
    CCPoint ptStart, ptEnd;
    float radius = fmaxf(fishSize.width, fishSize.height) / 2;
    switch (rand() % 4) {
            //随机从游戏主界面的四边出现
        case 0://从左 开始
            ptStart.x = - radius;
            ptStart.y = rand() % (int)winSize.height;
            //到右 结束
            ptEnd.x = winSize.width + radius;
            ptEnd.y = rand() % (int)winSize.height;
            break;
        case 1:
            //从右边开始
            ptStart.x = winSize.width + radius;
            ptStart.y = rand() % (int)winSize.height;
            //到左边结束
            ptEnd.x = - radius;
            ptEnd.y = rand() % (int)winSize.height;
            break;
        case 2:
            //从底边开始
            ptStart.x = rand() % (int)winSize.width;
            ptStart.y = - radius;
            //到上边结束
            ptEnd.x = rand() % (int)winSize.width;
            ptEnd.y = winSize.height + radius;
            break;
        case 3:
            //从上边开始
            ptStart.x = rand() % (int)winSize.width;
            ptStart.y = winSize.height + radius;
            //到底边结束
            ptEnd.x = rand() % (int)winSize.width;
            ptEnd.y = - radius;
            break;
        default:
            break;
    }
    //计算鱼儿运动路线的方向,用于调整鱼儿身体的朝向
    float angle = atan2f(ptEnd.y - ptStart.y, ptEnd.x - ptStart.x);//返回弧度
    //素材中鱼儿的绘制都是水平朝左的。所以选取了180.0f作为被减数
    float rotation = 180.0f - angle * 180.0f / M_PI;
    
    //设计每条鱼儿游动的速度不一样。
    float duration = rand() % 10 + 4.0f;
    //初始鱼儿的位置和方向
    m_pSpriteFish->setPosition(ptStart);
    m_pSpriteFish->setRotation(rotation);
    //构建鱼儿游动路线
    moveto = CCMoveTo::create(duration, ptEnd);
    
    if(m_bParticleBubble)
    {   //如果是应用粒子效果,这里只有美人鱼会应用,就播放冒泡粒子效果
        this->setParticleBubble(CCParticleSystemQuad::create("bubble.plist"));
        m_pGameLayer->addChild(m_pParticleBubble);
        float w = m_pSpriteFish->getContentSize().width / 2.0f;
        //定位粒子效果发射器开始坐标
        offsetPoint(ptStart, cosf(angle) * w, sinf(angle) * w);
        m_pParticleBubble->setPosition(ptStart);
        //定位粒子效果发射器结束坐标
        offsetPoint(ptEnd, cosf(angle) * w, sinf(angle) * w);
        //让粒子效果和鱼儿一起移动,美人鱼
        CCAction *act = CCMoveTo::create(moveto->getDuration(), ptEnd);
        m_pParticleBubble->setAutoRemoveOnFinish(false);
        m_pParticleBubble->setPositionType(kCCPositionTypeFree);
        m_pParticleBubble->runAction(act);
    }
}

好的,创建了一条鲜活的鱼儿主要逻辑就在这里了,下面看看为其配套的方法实现。
//当鱼儿游出可视范围 及 被捕获 时会 回调此方法
void Fish::removeSelf()
{
    //清除游戏主界面对当前鱼儿精灵的引用
    this->getGameLayer()->getFishes()->removeObject(this); 
    //销毁自己并从主游戏界面移除
    m_pSpriteFish->removeFromParentAndCleanup(true);
    //如果应用了粒子效果,就将对应的粒子对象销毁
    if(m_bParticleBubble && m_pParticleBubble)
        m_pParticleBubble->removeFromParentAndCleanup(true);
}

//鱼儿被捕获时将回调此方法,设置鱼儿相关状态,播放对应效果
void Fish::showCaught()
{
    //标记鱼儿被捕获
    m_bCaught = true;
    //停止鱼儿的所有运动
    m_pSpriteFish->stopAllActions();
    
    if(m_bParticleBubble && m_pParticleBubble){
        //当前鱼儿如果应用了粒子效果,则将其停止和隐藏
        m_pParticleBubble->stopAllActions();
        m_pParticleBubble->setVisible(false);
    }
    
    CCArray *frames = CCArray::createWithCapacity(11);
    for(int i = 1; i <= 4; i++)//每种鱼儿被捕获的动最多有4张帧图构成
    {
        //动态构建每一帧的名称
        CCString *frameName = CCString::createWithFormat("fish%02d_catch_%02d.png", m_nFishType ,i);
        CCSpriteFrame *pFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(frameName->getCString());
        if(pFrame){
            //如果找到对应名称的帧,将其存储
            frames->addObject(pFrame);
        }
    }
    //构建帧动画
    CCAnimation *animation = CCAnimation::createWithSpriteFrames(frames, 0.3f);
    CCAnimate *animate = CCAnimate::create(animation);
    //动画执行完成后执行自我销毁操作
    CCFiniteTimeAction *callFunc = CCCallFunc::create(this, callfunc_selector(Fish::removeSelf));
    CCFiniteTimeAction *sequence = CCSequence::create(animate, callFunc, NULL);
    m_pSpriteFish->runAction(sequence);
    
}
//当鱼儿别销毁时输出日志
Fish::~Fish()
{
    CCLOG("destruct fish %d", m_nFishType);
}
=====================================================================
完成鱼儿相关的封装后,接下来看看是怎样被GameLayer调度的吧

首先添加相关头文件:
#include "GameConfig.h"
#include "Fish.h"
其中GameConfig.h是游戏的全局配置文件,内容如下:
#ifndef __GAME_CONFIG_H
#define __GAME_CONFIG_H

#define MAX_FISH_COUNT  15
#define GET_ARRAY_LEN(array) (sizeof(array)/sizeof(array[0]))

#endif // __GAME_CONFIG_H

接下来在GameLayer.cpp添加鱼类类型常量定义,为具体的调度做好基础准备,在最后的addFish方法中用到:
//在fish.plist 中找到定义的鱼的类型
const int FishInBatchNode1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 14};
//在fish2.plist 中找到定义的鱼的类型
const int FishInBatchNode2[] = {10, 18};
/**
在fish3.plist 中找到定义的鱼的类型 16,17
这两种鱼的被捕获到后的帧图与游动帧图被分割到了两个不同的大图中分别fish2.png与fish3.png,导致在鱼儿被捕时播放Fish::showCaught()时在其m_pBatchNodeFish3AndNets中找不到对应的被捕帧图而抛出异常。
*/
const int FishInBatchNode3[] = {};//{16, 17};
//在fish4.png中找到美人鱼的两个类型定义
const int FishInBatchNode4[] = {11, 12};

在GameLayer::init方法中增加调度函数注册
bool GameLayer::init(){
    /*省略上节中代码若干*/

    //增加如下代码
    //注册任务调度,不断检测游戏主界面的鱼儿数量并保持指定数量 
    this->schedule(schedule_selector(GameLayer::updateFish), 1.0f);
    
    return true;
}
在GameLayer::initFishes方法中增加鱼儿指针数组的初始话代码:
void GameLayer::initFishes(){
      /*省略上节中代码若干*/

    //增加如下代码
    this->setFishes(CCArray::createWithCapacity(MAX_FISH_COUNT));
    m_pFishes->removeAllObjects();
}

//被注册的调度更新鱼儿的方法实现:
void GameLayer::updateFish(cocos2d::CCTime dt){
    if(m_pFishes->count() < MAX_FISH_COUNT)
    {
        int n = MAX_FISH_COUNT - m_pFishes->count();
        int nAdd = rand() % n + 1;
        //保持主界面上鱼儿一定数量
        for(int i = 0; i < nAdd; i++)
        {
            //调用下面实际的添加鱼儿的方法
            this->addFish();
        }
    }
}

//实际添加鱼儿到主游戏界面的方法实现:
void GameLayer::addFish(){
    CCSpriteBatchNode *loadFishSpriteBatchNode=NULL;
    while (1) {
        //随机指定一个鱼的种类,有可能不存在
        int type = rand() % 18 + 1;
        
        if(NULL==loadFishSpriteBatchNode){
            for(int i=0;i<GET_ARRAY_LEN(FishInBatchNode1);++i){
                if (type==FishInBatchNode1[i]) {
                    //定义类型在 fish.plist 找到,因为m_pBatchNodeFish1加载了它,所以将其赋值给loadFishSpriteBatchNode
                    loadFishSpriteBatchNode=m_pBatchNodeFish1;
                    break;
                }
            }
        }
        if(NULL==loadFishSpriteBatchNode){
            for(int i=0;i<GET_ARRAY_LEN(FishInBatchNode2);++i){
                if (type==FishInBatchNode2[i]) {
                    loadFishSpriteBatchNode=m_pBatchNodeFish2AndBullets;
                    break;
                }
            }
        }
        if(NULL==loadFishSpriteBatchNode){
            for(int i=0;i<GET_ARRAY_LEN(FishInBatchNode3);++i){
                if (type==FishInBatchNode3[i]) {
                    loadFishSpriteBatchNode=m_pBatchNodeFish3AndNets;
                    break;
                }
            }
        }
        if(NULL==loadFishSpriteBatchNode){
            for(int i=0;i<GET_ARRAY_LEN(FishInBatchNode4);++i){
                if (type==FishInBatchNode4[i]) {
                    loadFishSpriteBatchNode=m_pBatchNodeFish4;
                    break;
                }
            }
        } 
        if(loadFishSpriteBatchNode){
            //创建一条鱼儿,并加载到游戏主界面
            Fish::createWithFishType(type, this, loadFishSpriteBatchNode);
            return;//跳出while循环
        } 
    }
    loadFishSpriteBatchNode=NULL;
}

=================================================================
好的到此就完成了 本节的内容,效果:鱼儿自由的在游戏主界面游动。
截图效果如下:
图片被删除
 


原创粉丝点击