存档

2013年10月 的存档

CocoStudio创建动画帧

2013年10月31日 没有评论
  1. 进入动画编辑器
  2. 选择“形体模式”
  3. 右键点击资源窗口的资源,可以进行删除,重命名的操作;  
    可以再资源窗口下方的预览窗口,查看选中的资源预览效果图;
  4. 右键点击“对象结构”,创建图层
  5. 选择“动画模式”
  6. 右键点击动画列表窗口,选择新建动画
  7. 左键键点击帧编辑窗口的图层
  8. 把资源窗口要的帧图片拖到帧编辑窗口的图层
分类: cocos2d 标签:

【cocos2d-x 手游研发—-地图活起来了】

2013年10月29日 没有评论

谈到地图不少人都说要做地图编辑器了,但是我暂时绕过这一步,如果不用寻路地图就不能移动?寻路就是会绕过障碍物的算法。

我做了一个简单的地图的思想,就是地图分层3层:背景层、可行区域层、遮罩层,但是地图就不寻路了,通过设置可行区域层来

实现地图障碍物的方法。下面看一个视图,我把地图详细的分层了:

 

【cocos2d-x 手游研发----地图活起来了】

OK,有了这个思路,大家应该也知道我要怎么做了?代码实现上怎么处理呢?

重点:可行区域层原理是根据点击屏幕上的坐标点来取得这个点是否透明!如果不透明那就不让他进行移动,透明则为不可行区域;

首先感谢一下为我提供取色源码的哥们(firedragonpzy),帮助我实现了这个另类的地图设计;下面我贴一下他的源码,

新建了FDPixelSprite.cpp,FDPixelSprite.h代码如下:

FDPixelSprite.h

【cocos2d-x 手游研发----地图活起来了】【cocos2d-x 手游研发----地图活起来了】

//
//  FDPixelSprite.h
//  PixelDemo
//
//  Created by firedragonpzy on 13-2-19.
//
//

#ifndef __PixelDemo__FDPixelSprite__
#define __PixelDemo__FDPixelSprite__
#include "cocos2d.h"
USING_NS_CC;

class FDPixelSprite : public CCSprite, public CCTargetedTouchDelegate {
public:
    FDPixelSprite();
    virtual ~FDPixelSprite();
    
    void onEnter();
    void onExit();
    void setimg(CCString Url);
    
    FDPixelSprite* create(CCString Url);
    CCImage* img ;
    
    CCRect atlasRect();
    bool isContainTouchLocation(CCTouch *pTouch);
    
    bool ccTouchBegan(CCString thismapurl,CCTouch *pTouch, CCEvent *pEvent);
    void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent);
    void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent);

    CC_SYNTHESIZE(const char*, m_pName,Name);
};

#endif /* defined(__PixelDemo__FDPixelSprite__) */

View Code

FDPixelSprite.cpp

【cocos2d-x 手游研发----地图活起来了】【cocos2d-x 手游研发----地图活起来了】

//
//  FDPixelSprite.cpp
//  PixelDemo
//
//  Created by firedragonpzy on 13-2-19.
//
//

#include "FDPixelSprite.h"
#include "FontChina.h"

FDPixelSprite::FDPixelSprite()
{}
FDPixelSprite::~FDPixelSprite()
{}

FDPixelSprite* FDPixelSprite::create(CCString Url)
{
    FDPixelSprite *sprite = new FDPixelSprite();
    if (sprite && sprite->initWithFile(Url.getCString())) {
        sprite->setName(Url.getCString());
        sprite->autorelease();
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    sprite = NULL;
    
    
    return NULL;
}

void FDPixelSprite::setimg(CCString Url){
    img= new CCImage();
    img->initWithImageFileThreadSafe(CCFileUtils::sharedFileUtils()->fullPathForFilename(Url.getCString()).c_str());

}

void FDPixelSprite::onEnter()
{
    CCSprite::onEnter();
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 0, true);
}


void FDPixelSprite::onExit()
{
    CCSprite::onExit();
    CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);
}


bool FDPixelSprite::ccTouchBegan(CCString thismapurl,cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent)
{
    if (this->isContainTouchLocation(pTouch) ) {
        ccColor4B c = {0, 0, 0, 0};
        
        CCSize winSize = CCDirector::sharedDirector()->getWinSize();
        
        CCPoint touchPoint = pTouch->getLocationInView();
        
        CCSize cSize = this->getContentSize();
        CCPoint point =this->getAnchorPointInPoints();
        point = ccp(cSize.width - point.x,cSize.height- point.y);
        CCPoint pos(this->getPositionX() - point.x,winSize.height-this->getPositionY()- point.y);
        
        CCPoint localPoint = ccp(touchPoint.x - pos.x,
                                 touchPoint.y -pos.y);
        
        float scaleFactor = CCDirector::sharedDirector()->getContentScaleFactor();
        unsigned int x = localPoint.x  * scaleFactor, y = localPoint.y * scaleFactor;
        
        float _width = this->getContentSize().width*scaleFactor;

        //This method is currently only supports symmetric image
        //unsigned char *data_ = this->getTexture()->getFDImageData();
        
        //Efficiency of this method is relatively low
        //CCImage * img = new CCImage();
        //img->initWithImageFileThreadSafe(CCFileUtils::sharedFileUtils()->fullPathForFilename(thismapurl.getCString()).c_str());
        unsigned char *data_ = img->getData();
        
        
        unsigned int *pixel = (unsigned int *)data_;
        pixel = pixel + (y * (int)_width)* 1 + x * 1;

        c.r = *pixel & 0xff;
        c.g = (*pixel >> 8) & 0xff;
        c.b = (*pixel >> 16) & 0xff;
        c.a = (*pixel >> 24) & 0xff;
        if (c.a == 0) {
            CCLog(FontChina::G2U("不可点击!"));
            return false;
        }else
        {
            CCLog(FontChina::G2U("可点击!"));
            return true;
        }
    }
    return false;
}


void FDPixelSprite::ccTouchMoved(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent)
{
    //CCPoint pos = this->getPosition();
    //CCPoint sub = pTouch->getDelta();
    //this->setPosition(ccpAdd(pos, sub));
}


void FDPixelSprite::ccTouchEnded(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent)
{
    //CCLog("firedragonpzy:ccTouchEnded");
}


CCRect FDPixelSprite::atlasRect()
{
    CCSize cSize = this->getContentSize();
    CCPoint point = this->getAnchorPointInPoints();
    return CCRectMake( -point.x, -point.y, cSize.width,cSize.height);
}


bool FDPixelSprite::isContainTouchLocation(cocos2d::CCTouch *pTouch)
{
   return this->atlasRect().containsPoint(convertTouchToNodeSpaceAR(pTouch));
}

View Code

有了他们我们就能判断地图上是否可行了。OK废话不多,继续走向精彩,详细解决一下背景层我们应该做些什么东西,有什么内容?

背景层肯定要装载精灵,把我们之前第二章说的【cocos2d-x 大型ARPG手游研发—-精灵的八面玲珑】精灵加载出来,就可以当主角了。

这里有人说,那其他不带主角功能的怎么办?继承你写的精灵类拓展成怪物类(智能AI攻击操作),NPC(任务功能模块),可拓展行是

杠杠滴,继承下来NPC都能移动,和你打起来,我的思路是把地图做成一个大容器,每一块新地图继承一个MapsBase的同时他自己也有

有自己的特殊逻辑和特殊业务;

继续贴代码,地图是这么实现的:

Maps_Diyu.h

#include "cocos2d.h"
#include "../Commen/FDPixelSprite.h"
#include "../Spirits/SpiritsPlayer.h"

USING_NS_CC;

class Maps_Diyu :public cocos2d::CCSprite
{
public:
    Maps_Diyu(CCLayer* layer,CCString mapsurl,CCString mapsurl_1,int zOrder,CCPoint cp);
    ~Maps_Diyu(void);
   CCSprite
* nowmap; CCSprite* nowmap_zhezhao; //基本数据 float lastmove_x,lastmove_y; bool moveMapto(CCPoint cp,FDPixelSprite* mainmap_Touch); CCActionInterval* act_moveto_maps; CCActionInterval* act_moveto_maps_touch; CCActionInterval* act_moveto_maps_zhezhao; private: SpiritsPlayer* role_main; CCAnimate* playdonghua; };

Maps_Diyu.cpp

#include "Maps_Diyu.h"
#include "../GameData/GetNPCData.h"
#include "../Commen/FontChina.h"
#include "../Spirits/SpiritsMonster.h"
#include "../Effects/SkillEffects.h"

Maps_Diyu::Maps_Diyu(CCLayer* layer,CCString mapsurl,CCString mapsurl_1,int zOrder,CCPoint cp)
{
    act_moveto_maps=NULL;
    act_moveto_maps_zhezhao=NULL;
    lastmove_x=0;
    lastmove_y=0;
    float map_x , map_y;

    float center_x,center_y;

    CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin();
    CCSize size = CCDirector::sharedDirector()->getWinSize();

    nowmap = Maps_Diyu::create(mapsurl.getCString());
    nowmap_zhezhao = Maps_Diyu::create(mapsurl_1.getCString());
    center_x = size.width/2;
    center_y = size.height/2;

    map_y = nowmap->getAnchorPointInPoints().y+origin.y;
    map_x = nowmap->getAnchorPointInPoints().x;

    if(cp.getLength()>0)
    {
        nowmap->setPosition(cp);
        nowmap_zhezhao->setPosition(cp);
    }
    else
    {
        nowmap->setPosition(ccp(map_x,map_y));
        nowmap_zhezhao->setPosition(ccp(map_x,map_y));
    }

    //计算地图上绝对位置的原点坐标
    float map_fornpc_x,map_fornpc_y;
    map_fornpc_x= nowmap->getContentSize().width/2;
    map_fornpc_y=nowmap->getContentSize().height/2;

    //主角加载
    GetNPCData* basedatas = new GetNPCData();
    basedatas->GetNPCchapter1();
    basedatas->role_player.nowpoint= CCPointMake(map_fornpc_x+428,map_fornpc_y+480);
    role_main = new SpiritsPlayer(basedatas->role_player,1,false);
    role_main->npc->retain();

    //加载NPC
    basedatas->role_mengpo.nowpoint= CCPointMake(map_fornpc_x+158,map_fornpc_y+510);
    SpiritsPlayer* role_mengpo= new SpiritsPlayer(basedatas->role_mengpo,1,true);
    nowmap->addChild(role_mengpo->npc, 2);
//-------------------------------------------------------
    nowmap->addChild(role_main->npc, 2,999);
    layer->addChild(nowmap_zhezhao, zOrder+1);
    layer->addChild(nowmap, zOrder);
}

OK,地图初始化就加载了这些基础的数据,这些应该大家都能看懂,下面贴最核心的代码,如何把他们都关联起来

并且做移动操作呢??????

/*************************
参数说明:
CCPoint cp  可行区域的坐标
mainmap_Touch 可行区域,需要去随时改变他移动
**************************/
bool Maps_Diyu::moveMapto(CCPoint cp,FDPixelSprite* mainmap_Touch)
{

    float center_x,center_y,move_x,move_y, map_x , map_y ,to_c_x,to_c_y;

    CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin();
    CCSize size = CCDirector::sharedDirector()->getWinSize();
    center_x = size.width/2;
    center_y = size.height/2;
    move_x = center_x-cp.x;
    move_y = center_y-cp.y;
    map_x = nowmap->getPositionX();
    map_y = nowmap->getPositionY();
    to_c_x = nowmap->getContentSize().width/2;
    to_c_y = nowmap->getContentSize().height/2+origin.y;
    map_x = map_x + move_x;
    map_y  = map_y + move_y-origin.y;

    //计算移动时间,这边大家可以帮我优化一下
    //现在就这块移动时间有一些问题
    float a1 , b1 ;
    a1 = fabs(move_x)/size.width;
    b1 = fabs(move_y)/size.height;
    float movetime = ((a1+b1)*8);
    if(movetime<=1)
    {
        movetime=1;
    }

    //这里是精华,主要是处理任意地图放进来之后,
    //防止显示区域超出地图的长宽,移动到边界就不能移动了!
    if(map_x>=to_c_x)
    {
        map_x = to_c_x;
    }
    else if(map_x<=-((nowmap->getContentSize().width/2)-size.width))
    {
        map_x =-((nowmap->getContentSize().width/2)-size.width);
    }
    if(map_y>=to_c_y)
    {
        map_y = to_c_y;
    }
    else if(map_y <= -((nowmap->getContentSize().height/2)-size.height))
    {
        map_y = -((nowmap->getContentSize().height/2)-size.height);
    }

    //经典中的经典//
    //主角移动
    CCPoint role_move_pc = nowmap->convertToNodeSpace(ccp(cp.x,cp.y));//此处需要通过地图的视角把人物移动的坐标转化一下。
    role_main->moveTomap_dir(role_move_pc);    
    role_main->moveTomap_move(movetime,role_move_pc,false);
    //地图移动
    if(map_x!=lastmove_x&&map_y!=lastmove_y)
    {
        nowmap->stopAction(act_moveto_maps);
        nowmap_zhezhao->stopAction(act_moveto_maps_zhezhao);
        mainmap_Touch->stopAllActions();
        act_moveto_maps = CCMoveTo::create(movetime,ccp((int)map_x,(int)map_y));
        act_moveto_maps_touch = CCMoveTo::create(movetime,ccp((int)map_x,(int)map_y));
        act_moveto_maps_zhezhao = CCMoveTo::create(movetime,ccp((int)map_x,(int)map_y));
        nowmap->runAction(act_moveto_maps);
        nowmap_zhezhao->runAction(act_moveto_maps_zhezhao);
        mainmap_Touch->runAction(act_moveto_maps_touch);
        return true;
    }
    else                  
    {
        return false;
    }

}

核心的地方有三处,帮大家分析一下:

第一,就是计算移动时间,我是根据屏幕长宽来计算,这个地方一直是我心结,这个方法效果现在很不好,跑起来

长距离用时长,短距离就很快,所以请大家也帮我优化一下,可以往下贴代码,

第二,就是计算出地图移动的区域,你不可能随便丢一张图进去,地图超过边界会显示黑色,不能让黑色显示出来(除非丢进来的图小过屏幕地图);

第三,就是通过地图移动的标识来进行人物和地图的移动,在人物移动的时候需要转化一下成地图的坐标!

map_x!=lastmove_x&&map_y!=lastmove_y
可移动的标识
act_moveto_maps = CCMoveTo::create(movetime,ccp((int)map_x,(int)map_y));
act_moveto_maps_touch = CCMoveTo::create(movetime,ccp((int)map_x,(int)map_y));
act_moveto_maps_zhezhao = CCMoveTo::create(movetime,ccp((int)map_x,(int)map_y));

大家也看到,我移动的时候,移动的是3个层,这下就保证了可行区域也是不停在变动的

然后就是如何传数据了,几句话就可以搞定Scene加载的地图上所有层;

Scene_Diyu.h

【cocos2d-x 手游研发----地图活起来了】【cocos2d-x 手游研发----地图活起来了】

#include "cocos2d.h"
#include "ToScene.h"
#include "../MapSpirits/Maps_Diyu.h"
USING_NS_CC;
class Scene_Diyu : public CCLayer
{
public:
    Scene_Diyu(void);
    ~Scene_Diyu(void);
    Maps_Diyu* mainmap;
    FDPixelSprite* mainmap_Touch;
    void nextCallback(CCObject* pSender);
    virtual void registerWithTouchDispatcher(void);
    virtual bool ccTouchBegan(CCTouch *pTouch,CCEvent *pEvent);
    virtual void ccTouchMoved(CCTouch *pTouch,CCEvent *pEvent);
    virtual void ccTouchEnded(CCTouch *pTouch,CCEvent *pEvent);
    virtual void ccTouchCancelled(CCTouch *pTouch,CCEvent *pEvent);
};

View Code

Scene_Diyu.cpp

#include "Scene_Diyu.h"
#include "../ImagePaths.h"
#include "../PublicUI/BaseUI.h"

Scene_Diyu::Scene_Diyu(void)
{
    float x,y;
    CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin();
    CCSize size = CCDirector::sharedDirector()->getWinSize();
    x = size.width;
    y = size.height;
    //地图
    mainmap = new Maps_Diyu(this,"map_diyu_naihe.jpg","map_diyu_naihe1.png",0,ccp(x/2-308,y/2-486));
    mainmap_Touch =  mainmap_Touch->create("map_diyu_naihe0.png");
    mainmap_Touch->setimg("map_diyu_naihe0.png");
    mainmap_Touch->setPosition(ccp(x/2-308,y/2-486));
    mainmap_Touch->setVisible(false);//是否显示点击层

    BaseUI* baseui = new BaseUI(this);

    this->addChild(mainmap_Touch, 0);
    setTouchEnabled(true);
}


void Scene_Diyu::registerWithTouchDispatcher()  
{  
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this,0,true);  
}  


bool Scene_Diyu::ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent)  
{  
    if(mainmap_Touch->ccTouchBegan("map_diyu_naihe0.png",pTouch,pEvent)==true)
    {
        mainmap->moveMapto(pTouch->getLocation(),mainmap_Touch);
    }
    return true;  
}  
  
void Scene_Diyu::ccTouchMoved(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent)  
{  
}  
  
void Scene_Diyu::ccTouchEnded(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent)  
{  
}  
  
void Scene_Diyu::ccTouchCancelled(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent)  
{  
}  

Scene_Diyu::~Scene_Diyu(void)
{
}

 

 大家也看到了再Scene里面控制的点击事件主要就是处理可行区域的:

 if(mainmap_Touch->ccTouchBegan("map_diyu_naihe0.png",pTouch,pEvent)==true)
    {
        mainmap->moveMapto(pTouch->getLocation(),mainmap_Touch);
    }

好了,大家如果理解,可以自己研究一下自己的方式去实现一下这样一套,人物移动,地图移动的原理,当然,我在这里声明一下

这套实现思路其实是很歪门,另类的,应为他并没有采用寻路,但是你也不能完全说不采用寻路算法的地图系统就不行。

跑起来看一下效果截图:

【cocos2d-x 手游研发----地图活起来了】

跑起来后的效果图!!!

【cocos2d-x 手游研发----地图活起来了】

人物被遮罩层遮挡的效果图!!

这篇就讲这么多了,下一篇直接讲一下【怪物智能AI的制作】怪物实现追踪主角,怪物随机生成怪圈,怪物随机移动巡逻。

 

 

游戏demo及素材下载地址(demo里面包含了所有的素材资料);

http://pan.baidu.com/share/link?shareid=4012433582&uk=4097703620&third=15

 

我建了一个QQ群:和大家一起分享cocos2dx开发经验【41131516】

 

 

分类: 未分类 标签:

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

2013年10月29日 没有评论

      大家好,我是红孩儿.上一节我们学习了使用《红孩儿工具箱》开发《打地鼠》游戏.这一节我们继续学习使用《红孩儿工具箱》来开发《坦克大战》游戏.

《坦克大战》这个游戏大家小时候都玩过,它给了我们儿时一个实现英雄梦的机会.现在每每想起和儿时的小伙伴们一起玩FC时的情景,都会感觉无限美好.唉,人一长大,各种烦闷都来了,不说这个了,下面来介绍一下本次《坦克大战》的设计规划.

我们这是个单机游戏,玩家可以控制一辆坦克在游戏中对敌方进攻的坦克进行回击,保卫自已的大本营.将敌方坦克都消灭了,当前关卡就胜利结束了.如果被敌方坦克消灭,那当前关卡就失败了.

首先,我们要制做一个开始界面,有一个背景图和两个按钮-开始游戏和退出游戏.这个界面的作用是给玩家一个宣明的主题,并让玩家选择是否游戏.需要使用工具箱的界面编辑器.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

 

之后,我们做一个战争的剧情动画,做这个是为了让玩家能够迅速的进入到主题,有一种身临其境的感受.为了更好的把战争的气氛渲染出来,我们这里使用打字机效果来说明剧情背景故事并配备一个老照片的动画更好的对人的心理进行一个触动.这里需要使用工具箱的字体编辑器来生成字图,以及使用动画编辑器来制做动画.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

然后我们进入到关卡界面,本次我们设计了十五个关卡,另外要求下次玩时要保存已玩过的关卡.这样就不必每次都从第一关开始玩了.这里当然还是要使用工具箱的界面编辑器.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

再继续就是游戏的主场景了,核心的玩法都在这里面了,场景由工具箱的地图编辑器进行制作,玩法逻辑则在代码中实现.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

打完一仗后有一个关卡结束的界面,对胜利,失败和击毁的坦克有一个结算处理.让玩家稍适休息,继续战斗.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

所有十五个关卡都结束会进入到通关界面,通过这个界面中的动画将战争结束的激动心情更好的烘托出来.玩到这里,相信很多小伙伴会把悬着的一颗心放下,感叹一下幸好没生活在战争时代,幸好,幸好.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

好了,我们现在开始第一步:开始界面制作.

一.开始界面制作


 首先我们需要从www.game2z.com  网站,在左上角的 下载红孩儿工具箱最新版 链接处下载最新的0.2.3版本。下载后我们将其解压到自定义的目录。打开后我们可以看到工具箱EXE程序和一个dx9的dll.还有一个资源目录Resource,一个测试设备图片存放目录Dev以及一个配套的SDK目录.红孩儿工具箱只需要这个EXE和对应的这个dll就可以运行在Windows操作系统了.本次的更新重点是多了一个工具箱配套库目录-HHR,这里面有两个子目录include和Lib
,分别为包含头文件和静态Lib库.同时又分为基础库HHRLib和高级特效库EffectLib.
 
在工具箱所在目录的资源目录Resource中有一个《坦克大战》的资源目录TankWar。为了方便学习和使用,我把《坦克大战》所需的资源和做好的场景地图,动画源文件都放在这里了.

启动工具箱,这时我们可以看到红孩儿工具箱的登录界面,我们一般查看可以选择离线登录,但离线登录不能导出动画和场景,主要是用于查看和编辑。如果是想导出,那就最好到www.game2z.com论坛注册一个正式用户的账号。在这里我们输入注册账号和密码后,点击“登录”,稍等片刻完成验证后即可进入工具箱主界面。

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      我们首先要做的,是先设置一下我们的工程资源目录,点击“设置工程资源目录”按钮,选择我们的资源目录即可,一旦设置完成,工具箱就会开始遍历和登记工程目录下所有的文件资源,这样我们就可以使用工程资源目录下的图片了.下次再使用工具箱时也会自动的设置好资源目录.

      进入工具箱后,我们来作做一下开始界面.选择界面编辑选项卡,进入到界面编辑器.当前版本的界面编辑仍是未完全版,有很多高级控件还未实现,不过对于一些简单的界面也基本够用了.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

 

      界面编辑器分为四大区域,顶部主要是界面文件打开,保存,导出按钮与控件选取区域,左边是控件关系树.右边是属性值设置区域,中间是界面的主视图区域.

      我们要制作一个界面,首先要考虑这个界面都需要哪些控件.在我们这个开始界面中,主要是有一个背景图和两个按钮,所以我们至少需要两种控件,分别为面板控件和按钮控件.我们现在来做一下.

      在左边的控件树的Root项下我们右键单击,在弹出的菜单中选择增加子结点-面板”.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      单击后,我们可以看到在中央视窗区域会生成一个面板.同时在右边的属性编辑区域出现相应的属性值.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      我们看到此时这个界面是处于被选中状态的,因为它的顶点和边界中点有拖动块,我们可以通过鼠标直接点击这个面板并移动它,也可以通过鼠标点击小拖动块来改变他的大小.如果觉得不好控制,还可以直接在右边的属性框中输入数据值来进行修改.另外我们可以通过鼠标中健或滚轮按下后移动鼠标来方便我们改变编辑时我们观察点.

      我们希望这个面板填充背景图片,这时我们来看一下面板控件的属性都有什么.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

       Visible:是否可见

      Enable:是否有效,无效的话就不能响应屏幕触屏控制.

      PosX,PosY,PosZ,具体的位置X,Y,Z

      Width,Height:控件宽高.

      Normal_Image:所要填充的图片

      ImageShowType:图片缩放方式,有按控件大小,按图片大小和平铺填充.

    在这里我们点击Normal_Image编辑框会有一个按钮.点击后弹出选择显示资源的对话框.这时我们可以选择控件要显示的图片或动画.

     使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      如果当前选择显示资源的对话框中没有我们需要的素材,我们可以点击“导入资源文件到当前资源库中”.并查找相应的图片或动画文件加入到这里.

    使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

   

      找到start.png后,我们点击"打开",这时start.png就被加入到资源库中了.我们点击左边对应的树项,这里可以在右边看到它,这里显示会有点大,我们可以点击"限制大小"来进行界面大小重置.

    使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      看着没什么问题,我们就可以点击"应用此图片"了.之后我们的面板就会填充相应的图片.然后再点击"关闭选择窗口"还进入到界面编辑器.

 

      这时候我们的面板位置,大小还不对,我们需要修改一下属性,既然图片是800×600大小,我们这里就让控件大小按照图片大小就可以了。选择ImageShowType为图片大小,之后将PosY设为-600。因为在Cocos2d-x中使用的是Opengl的坐标系,所以这里调整为-600。基准点00点以上显示界面。

    使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

   

      这样背景图就做好了,我们继续来做一下两个按钮。我们在左边的控件树的面板控件树项上右键单击,在弹出菜单中点选“增加子控件-按钮”,这时候在面板的左上角会产生出一个按钮控件。

    使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

    
然后我们把创建出来的按钮控件放到合适的地方,然后看一下属性编辑区域。

    使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

  

      这里除了和面板一件的基本属性外,又多了一些控件特有的属性

MoveUp_Image:这里是说鼠标移上时的图片资源,适用于PC游戏的开发,如果是触屏游戏就没这个需求了。

LButtonDown_Image:鼠标按下或触屏响应时的图片资源。

      按下响应函数:这个是对应界面的Lua响应处理函数名称。在界面编辑器保存导出后,会生成一个对应的Lua文件,如果是使用Lua来处理界面逻辑,则可以在这里进行函数名的设置,当然,工具箱本身已经给他默认了一个基于控件名称的响应函数.

      

      我们点击Normal_Image,MoveUp_Image,LButtonDown_Image分别在选择图片资源对话框中将"开始游戏"图片加入.

    使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

   

    使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

    使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

   

将这几种状态的图片资源都选中之后我们再设置控件也按图片大小进行显示,并拖放到合适的位置,这时候我们把鼠标移上或点击时就可以看到相应状态时的图片了.

   使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

  

      "开始游戏"的按钮做好了,我们在左边它对应树项上右键单击,在弹出菜单中点击"复制当前控件",然后在面板控件项上右键再单击,在弹出菜单中点击"粘贴到此结点下"这样就会又复制出一个新的按钮.我们用鼠标可以将它拖到合适的位置,并按照刚才设置
开始游戏"按钮的办法,将另外的"退出游戏"的按钮也做出来.

   使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

   使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

  

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

我相信大家一定可以自已做到,这里我就不再赘述了.

这个界面基本功能控件算做完了,下面我们保存一下界面start.xml

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

 这样界面只有静态图构成,稍单调了一点,现在我们来为他增加一些动画元素.在标题处增加两个旋转闪动的星星.我们点击工具箱的编辑器选项页的“动画编辑”,进入到动画编辑器.

在动画编辑器左边的结点树区域,在根结点Root上右键,在弹出的菜单中点击"增加图片结点".

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

 

 

      我们在图片上双击,会弹出选择图片资源的对话框.然后我们将事先准备好的两个星星的图片加载进来.

   使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

   使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

  

  应用此图片后,原来显示Cocos2d-x图标的结点会变成星星图片.

  使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

 

        我们在帧显示区域的60帧处右键单击,会弹出一个菜单,点击"加入关键帧".

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

 

之后我们可以看到帧显示区域在60帧处出现了关键帧的标记.然后我们在图片结点上点击右键.在弹出菜单里找到"调整自身旋转".

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

  

      

      点击之后,会弹出一个"调整图片自身的旋转"输入对话框.在这时我们希望图片绕Z轴旋转一圈.所以我们在z处输入角度360.即旋转一周.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

确定后我们可以再在30帧处增加一个新的关键帧.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      然后我们点击右上角的"属性编辑"对话框.这时候我们可以看到结点的属性编辑面板会显示出来.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

在这个编辑面板里.我们可以调整四个顶点的透明度都调整为0.这样可以让图片完全透明.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

这样这个动画就做好了,我们点击"播放动画"可以来查看一下.

  

 使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

   

最后我们保存一下动画.这里需要在弹出的"请设置动画名称"中输入动作名称.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      现在动画文件star.ani就做好了.我们继续把另一个星星的闪烁旋转的动画做好并保存之后,我们再加到界面编辑器中,在面板上新加入两个子控件面板.并设置其图片资源为我们刚才做好的两个动画文件.

      使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

       选中相应的动画后,应用为面板所对应的动画资源即可.这样我们就把界面上的动画元素做好了.

  

  使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

  保存一下,然后导出ui_start.ui待用.

二.剧情动画制作

这里我们要做一个打字机效果来显示剧情文字.并在结束后显示一个老照片动画.我们这里可以使用工具箱的文字字图编辑器来制作字图.我们点击工具箱的"字体编辑"按钮进入到字体编辑器.

首先我们将所用到的文字都放到一个新建的txt文档中.

      

     使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

然后我们在字体编辑器中加载相应的文档文件.之后在文字编辑框中会显示出文字.

  

  使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

  然后我们点击"选择字体",在弹出的字体对话框中选择一种字体使用.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

   

然后我们点击"开始生成",在等待几秒后,字图被生成出来.

  

  使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

点击"编辑文字效果"后可以进入到文字效果编辑面板.这时我们可以对颜色,描边以及纹理做一些调整.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      调好了之后就可以点击"导出字图"来将文字导出fntpng了.这样我们的剧情介绍字图就制作完成了.

三.剧情动画制作

在剧情文字介绍的打字机效果动画结束后,我们还需要再做一个老照片动画,现在我们点击“动画编辑”进入到动画编辑器.

  首先我们在左边的结点树控件区域右健,增加一个图片结点,然后我们按照之前的方法设置其对应的图片为老照片.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

之后我们在下部帧显示区域第50帧区间内右键单击,在弹出菜单里选择新增一个关键帧,同样也在51帧区间内增加一个关键帧,然后我们点击"属性编辑"

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

在展开的属性编辑面板里我们点击"隐藏精灵"复选框.使照片不显示.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

使用同样的方法,我们在53,55,57,58帧上增加关键帧,并设置显示或隐藏精灵,以达到动画播放时的快速照片闪现动画.

 

58帧显示设置之后,我们在下部的帧显示区域滑动条"当前显示起始帧"处调整一下帧显示区域的起始帧,这样我们可以设置区域显示不到的帧信息.我们在130帧处再增加一个关键帧.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

130帧处我们通过属性设置来设计照片的四个顶点颜色改变.这样就可以实现变色动画了.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

然后我们保存为aboutwar.ani并导出aboutwar.gan.以供使用.

四.选择关卡界面制作

            在看完战争的剧情动画后,我们将会进入到关卡界面,关卡界面主要是为了显示可以玩的关卡,它主要由一些按钮组成.在我们学会了制作开始界面后,这个关卡界面就很容易了.仍然是一样的方法.通过创建面板增加背景图,然后在上面加上一系列按钮并为它们选择相应状态图.在这里就不再赘述了.相信大家可以通过自已的实践来完成它.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

五.游戏场景制作

 

             游戏场景是游戏中最重要的部分了,我们终于开始进入到游戏场景的制作了,相信大家都很期待.我们现在就开始.

      点击"场景编辑"后进入到场景编辑器.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      在场景编辑器的左上角位置,点击按钮"创建",在弹出的对话框中输入场景的宽780,高度600,点击确定后在场景显示区域出现红色区域方框,即场景的大小.

      我们用鼠标点击场景编辑器的中间分隔部分向右拖动,调整到合适位置以方便我们观察和编辑.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

我们在右下部分的"场景层元素区域"中右键单击,在弹出的菜单中点击"增加新图层".这是一个背景图的格子图层,用于显示地表.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

  

  

我们在弹出的"创建新图层"对话框中输入图层名称,格子大小.之后确定.

  使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

        确定之后,会在层元素显示区域出现一个树控件,这样这一层就建好了,我们可以在这一层里加入一些元素.在这里面的空白区域右键单击,在弹出菜单上点选"创建新图类",输入BK后点击"确定".

  使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      这样这个图类就创建好了.我们在相应的图类树项上右健点击,在弹出菜单中点选"增加新图片",然后在弹出的文件对话框中将地表图片加进来.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

这样我们就完成了地表格子元素的导入.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      点击左上角的"网络显示"后,我们可以看到场景区域会出现网格,并吸选中的图片元素会自动吸咐在网格中.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

这时候我们就可以进行场景的第一层绘制了,在鼠标按下的状态下可以在场景中刷格子,如果按着Ctrl从左上向下拖动,则对角线所在矩形区域都会被刷上相同的元素.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

然后我们来创建第二层,在第二层中,我们要进行建筑的一些布局和事件点的设置.我们在图层显示区域BK选项卡右边空白处右键,在弹出的菜单中点击"增加新图层".

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      在弹出的对话框中输入与第一层基本相同的设置.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

 

在这一层中我们导入一些砖墙,树,金属墙,木桶等图片元素.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      待元素增加完之后,我们现在也可以开始尝试在场景中刷一些建筑

  使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      是不是很犀利?现在我们还要为这些元素设置一下相应的阻挡属性,我们在砖墙,金属墙,木桶等有阻挡的图片树项上右键点击,在弹出的菜单中点击"修改图片属性",在弹出的对话框中点选"是否阻挡".之后确定.

  使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

设置完相应的阻挡后,场景会有红色圈来标识出物体所在的阻挡格子.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

我们用动画编辑器再制作一个鹰的变色动画放在场景的基地中.这个动画的制作很简单,学到这一步大家应该都会了,就是做关键帧,并在相应关键帧上调整元件的顶点色.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

做好后保存为home.ani供场景使用.并导出home.ganCocos2d-x中调用.然后我们加到场景编辑器.在第二层中按之前同样的方法增加图片资源,只是在文件类型中选择动画文件ani.找到home.ani加入进来.并选中它放在基地中间.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

 

  

      越来越像回事了,不是么?下面我们来为场景中增加一些坦克出生点,这个可以用事件格来做.我们在图层显示区域的Build树项上右键,在弹出菜单中点击“增加空事件格”,这时就可以看到在当前层分类下有一个新的子项“空事件格”。

   使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

我们选中它后在场景中的当前层相应格子中点击,就可以增加事件格了。

   使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      这些事件格都有一个ID值,默认为-1,我们可以自行设置它的ID,并在编写代码时进行访问和区分事件格,这里我们设置敌方坦克出生点的事件为0,我方坦克出生点主位为1,副位为2。我们只要在场景显示区域中事件点右键,点击弹出菜单的“设置当前的事件点ID”项,就可以为事件点设置ID了。

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

在这个场景中,我们将上边的事件点设置为0,基本左右方的事件点设置为12

  

   使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

这样这个场景就做好了。我们保存为第一关即可。

      用同样的方法,我们可以迅速的做出十五个关卡.

  使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

  

六.角色动作管理

      工具箱提供了对于分类角色的动作管理,通过对于角色动作的分类管理,我们可以更加清晰方便的在Cocos2d-x中对于角色的动作进行播放,而不必在代码中手动编写相应的动画文件名称或PLIST

   使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

 

 

      点击“角色编辑”面板,我们可以看到与动画编辑器类似的界面,在最左边的区域是分类角色与动作的一个树控件。我们在这里对游戏中的所有角色和动作进行统一管理。下面我们来做一下。

 

首先我们要确定都有哪些角色.

列举一下,应该有5个角色。

<1>我方主坦克

<2>敌方普通坦克

<3>敌方快速战车

<4>敌方重型坦克

<5>子弹

      为什么把子弹也作为一个角色了呢?因为在这个游戏里,子弹是在场景中运动的,另外它有两种,普通子弹和穿甲弹。我们可以把这两种子弹作为子弹角色的两个动作。当然,在没有好的美工辅助的情况下,我只能用单帧图片来做这样的动作。

 

我们在动画编辑器中创建出相应的图片结点并一一为其设置相应的图片资源后保存为各自的动作文件player.ani,enemy1,an,enemy2.ani,enem3.ani,bullet.ani,bullet3.ani

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

在弹出的对话框里输入类名称为“Tank”,然后确定,这样就有了一个新分类,我们在其对应树项上右键单击,在弹出菜单中点选“增加新角色”。

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

之后在弹出的对话框里输入角色名称“Player”,点击确定,这样在其树项下就又生成出一个角色树项,继续~,我们为这个角色导入动画player.ani

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

按照这种办法,我们继续将所有的角色和动作文件一一导入,只是子弹导入bullet.anibullet3.ani两个动作文件。

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      这样我们保存Tank.rol并导出Tank.gro。在Cocos2d-x中只需要根据角色名称就可以创建相应角色,通过动作名称就可以播放相应动画了。虽然这里只是一帧的单图。

八.爆炸粒子效果制作

             在这个游戏中,爆炸的粒子效果有两种,一种是基础粒子效果,在工具箱的粒子编辑面板里制作,另一种是高级粒子效果,在工具箱的效果编辑面板里制作。我们先来看一下基础粒子效果的制作,点击“粒子编辑”切换到粒子编辑器,从粒子模版库中选择“Fire”,然后更换图片为fire.png。之后设置一些属性,主要是角度要设成360度随机。

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

之后就可以看到我们需要的爆炸粒子特效了,

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

  保存为explode.plist供游戏中使用。

      然后我们来看一下高级爆炸效果的制作。点击“效果编辑”进入到效果编辑器。

  使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      在主视窗区域右键单击,在弹出菜单里选择“添加粒子系统效果”,然后在材质属性上替换图片。

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      点击粒子发射器参数,修改粒子总数目,每秒发射粒子数目和修改Z轴发散角度为180,注意这个Z轴发散角度的意思是说(-180~180)。并修改生命变化0.5速度变化50宽度10高度10宽度变化5高度变化5,经过粒子参数的设置,这个时候粒子的参数增加了很多随机性。      

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      点击粒子宽高缩放关键帧,这时会弹出关键帧编辑界面

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

在生命周期时间帧上起始帧,0.4生命周期帧,和结束帧上都双击,做一些大小的调整。

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      比如在开始帧处宽高为1.00.4生命周期处宽高为2.0,结束时宽高为3.0

      按同样的方法我们修改粒子颜色关健帧。

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

并修改粒子速度缩放关键帧,修改初始帧和结束帧的速度缩放值.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      这样这个粒子运动速度就实现了由慢变快的效果.最后,我们修改下粒子系统的喷射时间,点击基础属性,修改生命时间为0.16,修改名字为爆炸火星.

  使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      我们做好这个爆炸粒子效果后,按照同样的方式增加几个其它的粒子.

闪动火焰:修改纹理名字为huangguang.png,粒子总数目为1,每秒喷射粒子数目为1,生命变化为0,宽度为64,宽度变化为0,高度64,高度变化为0,粒子重力为0,速度为0.速度变化为0.设置粒子宽高缩放关键帧为

  使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

设置粒子颜色关键帧为

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

粒子速度缩放关键帧不设置.

 

 扩散光圈:纹理名字为qilang_mogu.png,粒子生命时间为0.12,总数目为1,每秒喷射粒子数目为1,生命为0.5,宽度为256,宽度变化为0,高度256,高度变化为0,粒子重力为0,速度为0.速度变化为0,设置粒子宽高缩放关键帧为

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

设置粒子颜色关键帧为

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

粒子速度缩放关键帧不设置

扩散光圈2:纹理名字为ring_huangquan.png,粒子生命时间为1,总数目为1,每秒喷射粒子数目为1,生命为1.0,宽度为256,宽度变化为0,高度256,高度变化为0,粒子重力为0,速度为0.速度变化为0,设置粒子宽高缩放关键帧为:

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

设置粒子颜色关键帧为

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

粒子速度缩放关键帧不设置.

最后我们点击下左测的效果元素列表,

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

将效果保存.

七.代码编写

             现在终于进入到代码编写的阶段了,是不是手早就痒痒了。我们将HelloCpp拷出一份进行改造就可以了,将其工程名改为TankWar

 

             现在我们要进行一下设计规划。首先我们再确定一下游戏的流程。

 

             首先是初始化HelloWorld层时加载开始界面和预载入一些音声文件。开始界面ui_start.ui中的按钮可以通过Lua来进行界面显示隐藏的逻辑处理,并通过回调函数来进行对于游戏界面切换时的一些逻辑进行处理。

 

             点击“开始游戏”按钮后,在回调函数中创建打字机效果来展示剧情文字。在所有的文字展示完之后播放一个老照片的显示动画。在动画结束帧处增加回调函数,进入到选关卡界面ui_chosemap.ui

 

             ui_chosemap.ui创建后根据存档信息来设置相应的关卡按钮可以被点选及其相应的回调函数,在这个回调函数中创建出场景并初始化游戏敌我双方的坦克设置。

 

             为场景中的敌我双方的坦克做逻辑处理。这个可以通过重载场景库中的现有NPC类来做到。通过其NPC私有属性结构的重载来进行私有属性的设计,在其每帧update函数的重载中做一些逻辑判断。比如键盘的按下响应或位置的更新,在这里我们按下攻击键时,要产生出一个子弹。子弹也可以通过重载场景库中的现有NPC类来做到,在重载其每帧的update函数中进行与坦克和阻挡物的碰撞判断,如果与坦克碰撞上,减少坦克的HP,如果与阻挡物碰撞上,击毁阻挡物并同时创建粒子爆炸效果。

             

             在坦克被击中减血到0时,统计击中的坦克数量,如果所有坦克被击毁了,那就释放场景。并显示胜利统计界面。当然,如果我方坦克或基地之鹰被击毁,那则显示失败统计界面。在点击Go!!!按钮后会如果是胜利就开始下一关卡,如果是失败就重新开始当前关卡。

 

 

             所有场景都结束之后,要显示一下通关的老照片动画。游戏回到开始界面。

 

             上面的流程确定了,下面我们来认识一下工具箱的配套库。

 

             工具箱的配套库暂时由两个模块来构成,一个是基础模块HHRLib,一个是高级特效模块EffectLib.基础模块HHRLib中包括动画解析与播放,界面解析与播放,场景解析与播放,角色列表解析,资源加密解密等功能。高级特效模块EffectLib中主要是特效与技能的解析与播放。当然,这也都只是当前版本的功能,未来肯定还会不断的扩展和强大。

             我们将这两个目录拷到坦克大战的目录下,并在TankWar工程设置里加入包含目录与库目录。

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

如上图示,将HHRLib_d.lib,EffectLib_d.lib放在debug模式下工程设置的引用库中。如果是Release模式,则换成HHRLib.libEffectLib.lib

 

      现在工具箱的配套库和头文件就引入到我们的工程中了,我们就可以解析和显示工具箱导出的界面和场景等内容了。

 

      这个游戏规模很小,我们的所有游戏界面处理主要放在HelloWorldScene所在的中就行了。下面我们开始流程的第一步:加载和显示开始界面。

      首先我们这个游戏画面大小约定为800×600像素,我们修改一下main.cpp中的大小设置。

USING_NS_CC;

int APIENTRY _tWinMain(HINSTANCE hInstance,
                       HINSTANCE hPrevInstance,
                       LPTSTR    lpCmdLine,
                       int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // create the application instance
    AppDelegate app;
    CCEGLView* eglView = CCEGLView::sharedOpenGLView();
    eglView->setViewName("HelloCpp");
    eglView->setFrameSize(800, 600);
    // The resolution of ipad3 is very large. In general, PC's resolution is smaller than it.
    // So we need to invoke 'setFrameZoomFactor'(only valid on desktop(win32, mac, linux)) to make the window smaller.
    eglView->setFrameZoomFactor(1.0f);
    return CCApplication::sharedApplication()->run();
}

      然后我们打开AppMacros.h,将ipad的大小修改成800×600,注意:这样做只针对于win32平台的这个DEMO,如果你是真的为ipad写游戏,那还是要根据相应的设备大小进行游戏的开发。

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

在完成了WIN32模式下窗口大小的修改后,我们在HelloWorld.h中定义一个枚举,用来标识所要用到的界面ID。这是一个好的习惯,方便后面的ID获取,如果用数字的话,往往容易忘记。

enumNodeID

{

   NID_UI_START
= 1,      
//开始界面

   NID_ANI_WAR
= 2,       
//战争介绍动画

   NID_UI_CHOSEMAP
= 3,   
//选关卡界面

   NID_UI_FXP
= 4,        
//方向与攻击控制界面

   NID_UI_FINISH
= 5,     
//关卡结束界面

   NID_UI_WIN
= 6         
//通关界面

};

然后我们可以在HelloWorldinit函数中加入创建开始界面的代码:

//开始界面
	CGameUI*		pNewUI = new CGameUI();
	pNewUI->autorelease();
	pNewUI->LoadUITree("ui_start.ui");
	//设置界面的位置
	pNewUI->setOffset(0, 0);
	this->addChild(pNewUI,1,NID_UI_START);

这样我们把做好的ui_start.ui和相应的图片资源拷到工程的Resource下的ipad目录中。我们就可以运行一下,看到相应的界面了,但是这个界面还不能响应点击,我们可以来处理一下:

      首先我们把ui_start.lua也拷到ipad中。并修改其中代码:

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      我们在“开始游戏”的按钮对应的LUA函数中加入了对于开始界面的隐藏处理。

      在“结束游戏”的按钮对应的LUA函数中加入了退出程序的处理。

      在完成后,大家可以运行下测试一下。在点击“开始游戏”的按钮后,我们还希望能够启动后面的剧情文字介绍。这时候我们用LUA做就没必要了,可以直接对按钮增加回调函数:

//通过控件名称获取到相应的控件
	CUICtrl*	pUICtrl = pNewUI->QueryUICtrl("UICtrl2_Button");
	if(pUICtrl)
	{
		//转化为按钮
		CUIButton*	pUIButton = dynamic_cast<CUIButton*>(pUICtrl);
		if(pUIButton)
		{
			//设置在按下时响应的回调函数
			pUIButton->setClickEndCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::CreateAboutUI), (void*)0xbebabeba));
		}
	}

      在这个CreateAboutUI的回调函数中,我们将创建相应的文字行并设定一个定时回调的函数CreateAboutPrintAni函数来显示打字机动画。因为文字是通过我们工具箱创建的字图来生成的,所以我们要创建出每一行的CCLabelBMFont打字机效果的实现通过每次调用定时回调函数时对这一行的文字要显示的字数加一就可以实现了,这样我们就需要统计出当前行的字数和总共的字数,我们在HelloWorld类中增加相应的成员。

private:

	//第一行
	int				m_nPrintCharSize1_Total;
	int				m_nPrintCharSize1_Curr;
	string			m_strPrintText1;
	CCLabelBMFont*  m_pAboutTextLable1;

	//第二行
	int				m_nPrintCharSize2_Total;
	int				m_nPrintCharSize2_Curr;
	string			m_strPrintText2;
	CCLabelBMFont*  m_pAboutTextLable2;

	//第三行
	int				m_nPrintCharSize3_Total;
	int				m_nPrintCharSize3_Curr;
	string			m_strPrintText3;
	CCLabelBMFont*  m_pAboutTextLable3;

	//第四行
	int				m_nPrintCharSize4_Total;
	int				m_nPrintCharSize4_Curr;
	string			m_strPrintText4;
	CCLabelBMFont*  m_pAboutTextLable4;

	//第五行
	int				m_nPrintCharSize5_Total;
	int				m_nPrintCharSize5_Curr;
	string			m_strPrintText5;
	CCLabelBMFont*  m_pAboutTextLable5;

因为我们使用到中文,所以我们按照之前做《打地鼠》的经验,可以将这些文字放到PLIST中再读取。

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

并在CPP中增加CreateAboutUI函数:

	//开始介绍战争
void HelloWorld::CreateAboutUI(CCNode* pSender, void* data)
{
	if(m_pAboutTextLable1)
	{
		CGameUI*		pNewUI
= dynamic_cast<CGameUI*>(getChildByTag(NID_UI_START));
		if(pNewUI)
		{
			pNewUI->setVisible(false);
		}
		return ;
	}
	//因为我们要显示的文字是汉字,所以为了避免乱码,我们在这里将文字存入到XML中,然后在Cocos2d-x中读取。
	CCDictionary *strings = CCDictionary::createWithContentsOfFile("string.plist"); 
	//第一行
	m_strPrintText1 = ((CCString*)strings->objectForKey("line1"))->m_sString; 
	m_nPrintCharSize1_Total = m_strPrintText1.size();
	m_nPrintCharSize1_Curr = 0;
	//第二行
	m_strPrintText2 = ((CCString*)strings->objectForKey("line2"))->m_sString; 
	m_nPrintCharSize2_Total = m_strPrintText2.size();
	m_nPrintCharSize2_Curr = 0;
	//第三行
	m_strPrintText3 = ((CCString*)strings->objectForKey("line3"))->m_sString; 
	m_nPrintCharSize3_Total = m_strPrintText3.size();
	m_nPrintCharSize3_Curr = 0;
	//第四行
	m_strPrintText4 = ((CCString*)strings->objectForKey("line4"))->m_sString; 
	m_nPrintCharSize4_Total = m_strPrintText4.size();
	m_nPrintCharSize4_Curr = 0;
	//第五行
	m_strPrintText5 = ((CCString*)strings->objectForKey("line5"))->m_sString; 
	m_nPrintCharSize5_Total = m_strPrintText5.size();
	m_nPrintCharSize5_Curr = 0;

	//创建相应的文字标签
	CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
	m_pAboutTextLable1 = CCLabelBMFont::create("", "card_0_text.fnt");
    m_pAboutTextLable1->setPosition(ccp(20, visibleSize.height - 100));
	m_pAboutTextLable1->setAlignment(kCCTextAlignmentLeft);
	m_pAboutTextLable1->setAnchorPoint(ccp(0,0));
    this->addChild(m_pAboutTextLable1,3);

	m_pAboutTextLable2 = CCLabelBMFont::create("", "card_0_text.fnt");
    m_pAboutTextLable2->setPosition(ccp(20, visibleSize.height - 150));
	m_pAboutTextLable2->setAlignment(kCCTextAlignmentLeft);
	m_pAboutTextLable2->setAnchorPoint(ccp(0,0));
    this->addChild(m_pAboutTextLable2,3);

	m_pAboutTextLable3 = CCLabelBMFont::create("", "card_0_text.fnt");
    m_pAboutTextLable3->setPosition(ccp(20, visibleSize.height - 200));
	m_pAboutTextLable3->setAlignment(kCCTextAlignmentLeft);
	m_pAboutTextLable3->setAnchorPoint(ccp(0,0));
    this->addChild(m_pAboutTextLable3,3);

	m_pAboutTextLable4 = CCLabelBMFont::create("", "card_0_text.fnt");
    m_pAboutTextLable4->setPosition(ccp(20, visibleSize.height - 250));
	m_pAboutTextLable4->setAlignment(kCCTextAlignmentLeft);
	m_pAboutTextLable4->setAnchorPoint(ccp(0,0));
    this->addChild(m_pAboutTextLable4,3);

	m_pAboutTextLable5 = CCLabelBMFont::create("", "card_0_text.fnt");
    m_pAboutTextLable5->setPosition(ccp(20, visibleSize.height - 300));
	m_pAboutTextLable5->setAlignment(kCCTextAlignmentLeft);
	m_pAboutTextLable5->setAnchorPoint(ccp(0,0));
    this->addChild(m_pAboutTextLable5,3);
	//每0.06秒出现一个字
	schedule(schedule_selector(HelloWorld::CreateAboutPrintAni), 0.06f);
	//播放打字机的音效
m_nSoundEffectID = SimpleAudioEngine::sharedEngine()->playEffect("1943.mp3");
	m_bShowAboutTextAndAni = true;
	m_nAwardTotal = 0;

}

      在这个函数里只是初始化了每一行的文字标签,真正的打字机效果定时更新文字是在每0.06秒定时调用的CreateAboutPrintAni函数中。

//开始战争介绍-显示打字机动画效果
void HelloWorld::CreateAboutPrintAni(float dt)
{		
	char	tTemp[1024];
	memset(tTemp,0,sizeof(tTemp));
	m_nPrintCharSize1_Curr++;
	if(m_nPrintCharSize1_Curr >= m_nPrintCharSize1_Total)
	{
		m_nPrintCharSize2_Curr++;

		if(m_nPrintCharSize2_Curr >= m_nPrintCharSize2_Total)
		{
			m_nPrintCharSize3_Curr++;

			if(m_nPrintCharSize3_Curr >= m_nPrintCharSize3_Total)
			{
				m_nPrintCharSize4_Curr++;

				if(m_nPrintCharSize4_Curr >= m_nPrintCharSize4_Total)
				{
					m_nPrintCharSize5_Curr++;

					if(m_nPrintCharSize5_Curr >= m_nPrintCharSize5_Total)
					{
						m_nPrintCharSize5_Curr++;
								unschedule(schedule_selector(HelloWorld::CreateAboutPrintAni));		
						//停止音效ID
						SimpleAudioEngine::sharedEngine()->pauseEffect(m_nSoundEffectID);
						//文字隐藏
						removeChild(m_pAboutTextLable1);
						removeChild(m_pAboutTextLable2);
						removeChild(m_pAboutTextLable3);
						removeChild(m_pAboutTextLable4);
						removeChild(m_pAboutTextLable5);

						//播放动画
scheduleOnce(schedule_selector(HelloWorld:: CreateAboutPictureAni), 1.0f);

					}
					else
					{
						strcpy(tTemp,m_strPrintText5.c_str());
						memset(tTemp+m_nPrintCharSize5_Curr,0,1024-m_nPrintCharSize5_Curr);
						m_pAboutTextLable5->setCString(tTemp);
					}
				}
				else
				{
					strcpy(tTemp,m_strPrintText4.c_str());
					memset(tTemp+m_nPrintCharSize4_Curr,0,1024-m_nPrintCharSize4_Curr);
					m_pAboutTextLable4->setCString(tTemp);
				}

			}
			else
			{
				strcpy(tTemp,m_strPrintText3.c_str());
				memset(tTemp+m_nPrintCharSize3_Curr,0,1024-m_nPrintCharSize3_Curr);
				m_pAboutTextLable3->setCString(tTemp);
			}

		}
		else
		{
			strcpy(tTemp,m_strPrintText2.c_str());
			memset(tTemp+m_nPrintCharSize2_Curr,0,1024-m_nPrintCharSize2_Curr);
			m_pAboutTextLable2->setCString(tTemp);
		}

	}
	else
	{
		strcpy(tTemp,m_strPrintText1.c_str());
		memset(tTemp+m_nPrintCharSize1_Curr,0,1024-m_nPrintCharSize1_Curr);
		m_pAboutTextLable1->setCString(tTemp);
	}
}

      这样就实现了带感的打字机介绍剧情的动画效果。在所有文字都介绍完之后,我们调用了停止音效的函数并注销了定时回调函数,并调用一个CreateAboutPictureAni函数来显示老照片动画。这个函数很简单,加载工具箱制做的老照片动画并播放就OK了。

//显示老照片动画
void HelloWorld::CreateAboutPictureAni(float dt)
{
		//创建动画
	C2DSkinAni*		pNewSkinAni = new C2DSkinAni();
	if(pNewSkinAni)
	{
		if(true == pNewSkinAni->ReadAllSubBodyFromBin("card_0_about.gan"))
		{
			CCSize tBoundingBox = pNewSkinAni->GetBoundingBoxSize();
			pNewSkinAni->autorelease();
			pNewSkinAni->Play(1);
			this->addChild(pNewSkinAni,4,NID_ANI_WAR);
			CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
			pNewSkinAni->SetOffset(visibleSize.width/2,visibleSize.height/2 );
			//最后一帧加一个函数调用
			pNewSkinAni->SetEndFrameCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::AboutUIEndFrameCallBack), (void*)0xbebabeba));
		}
	}

      我们在动画的最后一帧加入了一个函数调用AboutUIEndFrameCallBack。我们将在这个函数里做一下稍后调用显示选关卡界面的处理:

//老照片动画显示结束的回调
void HelloWorld::AboutUIEndFrameCallBack(CCNode* pSender, void* data)
{
	//停留一秒后调用显示选关卡函数ShowCardUI。
	scheduleOnce(schedule_selector(HelloWorld::ShowCardUI), 1.0f);
}

增加一个ShowCardUI函数,在这个函数里,我们将加载选关卡的界面,并为相应的函数增加回调函数:

//选择关卡界面
void HelloWorld::ShowCardUI(float dt)
{
	//显示剧情动画结束,将动画设为不显示并移除
	m_bShowAboutTextAndAni = false;
	C2DSkinAni*		pNewSkinAni = dynamic_cast<C2DSkinAni*>(getChildByTag(NID_ANI_WAR));
	if(pNewSkinAni)
	{
		pNewSkinAni->setVisible(false);
		removeChild(pNewSkinAni);
		//pNewSkinAni->release();
	}

	//取得选关卡的界面.如果存在,则直接显示,如果不存在则新建并加载.这一步是因为有个后退按钮可以退到开始界面,再次点击"开始游戏"按钮时就不需要重复创建了.
	CGameUI*		pChoseChardUI = dynamic_cast<CGameUI*>(getChildByTag(NID_UI_CHOSEMAP));
	if(pChoseChardUI)
	{
		pChoseChardUI->setVisible(true);
	}
	else
	{
		//选关卡界面
		CGameUI*		pNewUI = new CGameUI();
		pNewUI->autorelease();
		pNewUI->LoadUITree("ui_chosecard.ui");
		//设置界面的位置
		pNewUI->setOffset(0, 0);
		this->addChild(pNewUI,1,NID_UI_CHOSEMAP);

		//设置按钮的回调,只有玩过的能选择。
		char	szCtrlName[64];
		for(int i = 0 ; i < 15 ; i++)
		{
			sprintf(szCtrlName,"UICtrl%d_Button",i+3);
			CUICtrl*	pUICtrl = pNewUI->QueryUICtrl(szCtrlName);
			if(pUICtrl)
			{
				CUIButton*	pUIButton = dynamic_cast<CUIButton*>(pUICtrl);
				if(pUIButton)
				{
					//如果关卡能玩,则设置回调函数响应按钮,否则不能响应并置为灰色处理
					if( i <= m_nLastPlayMapID )
					{
						//设置在按下时响应的回调函数,可带自定义参数
						pUIButton->setClickBeginCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::ChoseCardBtnCallBack), (void*)i));

						//关卡上的文字
						char szText[10];
						sprintf(szText,"%d",i+1);
						m_pTankTextLabel[i] = CCLabelBMFont::create(szText, "number.fnt");
						POINT tPoint = pUICtrl->GetWorldPos();
						int   nWidth = pUICtrl->getWidth();
						int   nHeight = pUICtrl->getHeight();
						if(i >= 10)
						{
							m_pTankTextLabel[i]->setPosition(ccp(tPoint.x+nWidth/4,tPoint.y-nHeight/2));
						}
						else
						{
							m_pTankTextLabel[i]->setPosition(ccp(tPoint.x+nWidth/3,tPoint.y-nHeight/2));
						}
						m_pTankTextLabel[i]->setAlignment(kCCTextAlignmentLeft);
						m_pTankTextLabel[i]->setAnchorPoint(ccp(0,0));
						pNewUI->addChild(m_pTankTextLabel[i],1);
					}
					else
					{
						//设置按钮灰色
						CCSprite*	pScrpit = pUIButton->getSprite();
						if( pScrpit )
						{
							pScrpit->setColor(ccc3(128,128,128));
						}
					}
				}
				
			}
		}
	}
}

增加一个空函数,用于在后面编写创建关卡的处理

//开始第N关
void HelloWorld::ChoseCardBtnCallBack(CCNode* pSender, void* data)
{

}

    在这些可以选择关卡的按钮上我们设定了相应的点击回调处理函数,这个函数将实现相应关卡的场景加载与敌我坦克的设置,到这里,我们将开始游戏场景的处理.

    首先我们要熟悉一下工具箱的场景类.我们打开HHRLib的HHR_SceneManage.h看一下,这个CGameScene类是由CCLayer派生出来的。它的主要功能是解析场景文件并显示。

    它的头文件里有一个类CGameSceneNpc,这是一个NPC的基类,在CGameScene里会有相应的容器来存放游戏场景中需要的NPC实例,而我们只要由CGameSceneNpc派生出我们需要的NPC并调用CGameScene的成员函数AddNew2DActiveObj就可以把我们的NPC实体放到场景中了。我们的NPC的逻辑可以在其重载基类的update中进行处理。

    比如,现在我们加入我们玩家的主坦克类。

    新建文件CMyTank.h/cpp,由CGameSceneNpc类派生出CMyTank类。

#ifndef _CMYTANK_H
#define _CMYTANK_H
//===========================================================
//Auto:火云红孩儿 QQ:285421210
//Date:2013-10-11
//Desc:玩家的坦克
//===========================================================
#include "Header.h"
#include "HHR_Character.h"
#include "HHR_SceneManage.h"

//当前玩家的坦克
class CMyTank	:public CGameSceneNPC
{
public:
	CMyTank();
	~CMyTank();
public:
	//初始化
	virtual void	Init();
	//每帧调用的更新
	virtual void	update(float fDelaySecond);
	//每帧调用的渲染
	virtual void	Render();
public:
	//设置当前触屏按键
	void	SetDirKey(enuDirKey vDir);
	//设置开炮
	void	SetAttackKey(bool bAttack);

	//设置无敌状态
	void	setNBState();
	//是否无敌状态
	bool	IsNBState();

	//设置发射穿甲弹
	void	setNBBulletState();
	//是否发射穿甲弹
	bool	IsNBBulletState();

	//坦克血量
	VAR_SETGET(int,m_nHP,HP);
private:
	//上次的攻击时间
#include "CMyTank.h"
CMyTank::CMyTank()
{
	m_dwLastAttackTime = 0;
	m_nDir = DK_UP;
	m_nDirKey = DK_NONE;
	m_bAttackKey = false;
	m_nHP = 10;
	m_bNB = false;
	m_NBTime = 0;
	m_bNBBullet = false;
	m_NBBulletTime = 0;
}
CMyTank::~CMyTank()
{

}

//初始化
void		CMyTank::Init()
{

}
//每帧调用的更新
Void		CMyTank::update(float fDelaySecond)
{
	if( m_pCharacter && CGameSceneNPC::m_pCurrGameMap)
	{	
		//无敌状态时间计算
		if(m_bNB)
		{
			m_NBTime += fDelaySecond ;
			if(m_NBTime > 10000)
			{
				m_bNB = false;
				m_NBTime = 0;
			}
		}
		//穿甲弹状态时间计算
		if(m_bNBBullet)
		{
			m_NBBulletTime += fDelaySecond ;
			if(m_NBBulletTime > 10000)
			{
				m_bNBBullet = false;
				m_NBBulletTime = 0;
			}
		}

		fDelaySecond = min(fDelaySecond,0.2);
		//取得场景信息
		stSceneHeader*	tpSceneHeader =	CGameSceneNPC::m_pCurrGameMap->GetSceneHeader();
		CCPoint	tScenePos_Old;
		CCPoint	tScenePos;
		tScenePos_Old =	tScenePos = m_pCharacter->GetScenePos();
		CCSize	tBoundingBox = m_pCharacter->GetBoundingBoxSize();

		enuDirKey		nDir = m_nDirKey;

		//按下左键
		if ((::GetKeyState('A'))&0x8000) 
		{
			nDir = DK_LEFT;
		}
		//按下右键
		if ((::GetKeyState('D'))&0x8000) 
		{
			nDir = DK_RIGHT;
		}
		//按下上键
		if ((::GetKeyState('W'))&0x8000) 
		{
			nDir = DK_UP;
		}
		//按下下键
		if ((::GetKeyState('S'))&0x8000) 
		{
			nDir = DK_DOWN;
		}

		//按下下键
		if (m_bAttackKey || (::GetKeyState('J'))&0x8000) 
		{
			struct timeval tv;
			gettimeofday(&tv,NULL);
			double	   	    dwCurrTime = tv.tv_sec*1000.0+tv.tv_usec* 0.001;
			//开炮时间
			if(dwCurrTime - m_dwLastAttackTime > 1000)
			{	
				//创建一个子弹角色
				//这里待加入创建子弹的代码
				m_dwLastAttackTime = dwCurrTime ;
			}
		}

		if(nDir<0)return;
		m_nDir = nDir;
		float	fHalfWidth = tBoundingBox.width * 0.5 ;
		float	fHalfHeight = tBoundingBox.height * 0.5 ;

		float	fMoveSpeed		= 80;
		bool	bCollResult		= false;
		
		int		nLayer			= 1;
		//当前所在格子
		POINT	tTile			= CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x,tScenePos.y,false);

		//取得格子对应的中心点
		CCPoint	tTile_Center	= CGameSceneNPC::m_pCurrGameMap->GetTileCenterPoint(nLayer,tTile.x,tTile.y,false);		
		//设置图片的旋转方向
		m_pCharacter->GetCurrBody()->setRotateZ(90 * nDir);	

		switch(nDir)
		{
		case DK_LEFT:
			{
				//左
				tScenePos.x  -= fMoveSpeed*fDelaySecond ;

				//取得相应层的相应格子数据
				POINT	tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x-fHalfWidth,tScenePos.y,false);
		
				stBlockRenderInfo*	tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);

				if(tpBlockRenderInfo)
				{
					if(tpBlockRenderInfo->m_bIsObstruct)
					{
						bCollResult = true;
						tScenePos = tScenePos_Old ;
					}
				}
				if(false == bCollResult)
				{
					tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x-fHalfWidth,tScenePos.y - fHalfHeight * 0.6,false);

					tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);
					if(tpBlockRenderInfo)
					{
						if(tpBlockRenderInfo->m_bIsObstruct)
						{
							bCollResult = true;
							tScenePos = tScenePos_Old ;
						}
					}
				}
				if(false == bCollResult)
				{
					tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x-fHalfWidth,tScenePos.y + fHalfHeight * 0.6,false);

					tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);
					if(tpBlockRenderInfo)
					{
						if(tpBlockRenderInfo->m_bIsObstruct)
						{
							bCollResult = true;
							tScenePos = tScenePos_Old ;
						}
					}
				}
			}
			break;
		case DK_UP:
			{
				//上
				tScenePos.y  += fMoveSpeed*fDelaySecond ;
				//取得相应层的相应格子数据
				POINT	tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x,tScenePos.y+fHalfHeight,false);
		
				stBlockRenderInfo*	tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);

				if(tpBlockRenderInfo)
				{
					if(tpBlockRenderInfo->m_bIsObstruct)
					{
						bCollResult = true;
						tScenePos = tScenePos_Old ;
					}
				}
				if(false == bCollResult)
				{
					tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x-fHalfWidth * 0.6,tScenePos.y + fHalfHeight,false);

					tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);
					if(tpBlockRenderInfo)
					{
						if(tpBlockRenderInfo->m_bIsObstruct)
						{
							bCollResult = true;
							tScenePos = tScenePos_Old ;
						}
					}
				}
				if(false == bCollResult)
				{
					tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x+fHalfWidth* 0.6,tScenePos.y + fHalfHeight ,false);

					tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);
					if(tpBlockRenderInfo)
					{
						if(tpBlockRenderInfo->m_bIsObstruct)
						{
							bCollResult = true;
							tScenePos = tScenePos_Old ;
						}
					}
				}
			}
			break;
		case DK_RIGHT:
			{
				//右
				tScenePos.x  += fMoveSpeed*fDelaySecond ;

				//取得相应层的相应格子数据
				POINT	tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x+fHalfWidth,tScenePos.y,false);
		
				stBlockRenderInfo*	tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);

				if(tpBlockRenderInfo)
				{
					if(tpBlockRenderInfo->m_bIsObstruct)
					{
						bCollResult = true;
						tScenePos = tScenePos_Old ;
					}
				}
				if(false == bCollResult)
				{
					tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x+fHalfWidth,tScenePos.y - fHalfHeight * 0.6,false);

					tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);
					if(tpBlockRenderInfo)
					{
						if(tpBlockRenderInfo->m_bIsObstruct)
						{
							bCollResult = true;
							tScenePos = tScenePos_Old ;
						}
					}
				}
				if(false == bCollResult)
				{
					tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x+fHalfWidth,tScenePos.y + fHalfHeight * 0.6,false);

					tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);
					if(tpBlockRenderInfo)
					{
						if(tpBlockRenderInfo->m_bIsObstruct)
						{
							bCollResult = true;
							tScenePos = tScenePos_Old ;
						}
					}
				}
			}
			break;
		case DK_DOWN:
			{
				//下
				tScenePos.y -= fMoveSpeed*fDelaySecond ;
				//取得相应层的相应格子数据
				POINT	tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x,tScenePos.y-fHalfHeight,false);
		
				stBlockRenderInfo*	tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);

				if(tpBlockRenderInfo)
				{
					if(tpBlockRenderInfo->m_bIsObstruct)
					{
						bCollResult = true;
						tScenePos = tScenePos_Old ;
					}
				}
				if(false == bCollResult)
				{
					tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x-fHalfWidth * 0.6,tScenePos.y - fHalfHeight,false);

					tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);
					if(tpBlockRenderInfo)
					{
						if(tpBlockRenderInfo->m_bIsObstruct)
						{
							bCollResult = true;
							tScenePos = tScenePos_Old ;
						}
					}
				}
				if(false == bCollResult)
				{
					tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x+fHalfWidth* 0.6,tScenePos.y - fHalfHeight ,false);

					tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);
					if(tpBlockRenderInfo)
					{
						if(tpBlockRenderInfo->m_bIsObstruct)
						{
							bCollResult = true;
							tScenePos = tScenePos_Old ;
						}
					}
				}
			}
			break;
		}

		if(false == bCollResult)
		{
			if(tScenePos.x < tBoundingBox.width*0.5)
			{
				tScenePos.x = tBoundingBox.width*0.5;
				bCollResult = true;
			}
			else if(tScenePos.x > (tpSceneHeader->m_nSceneWidth-tBoundingBox.width*0.5))
			{
				tScenePos.x = tpSceneHeader->m_nSceneWidth - tBoundingBox.width*0.5;
				bCollResult = true;
			}
			else if(tScenePos.y < tBoundingBox.height*0.5)
			{
				tScenePos.y = tBoundingBox.height*0.5;
				bCollResult = true;
			}
			else if(tScenePos.y > (tpSceneHeader->m_nSceneHeight-tBoundingBox.height*0.5))
			{
				tScenePos.y = (tpSceneHeader->m_nSceneHeight-tBoundingBox.height*0.5);
				bCollResult = true;
			}
		}

		m_pCharacter->SetScenePos(tScenePos);

		//接到物品
		tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x,tScenePos.y,false);
		//取得相应层的相应格子数据
		stBlockRenderInfo*	tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(2,tTile.x,tTile.y);
		if(tpBlockRenderInfo)
		{
			if(RET_IMAGE == tpBlockRenderInfo->m_sRenderElementType)
			{
				//HP
				if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),"eff3.png"))
				{
					m_nHP = 10;
					CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(2,tTile.x,tTile.y,-1);
				}
				//无敌
				if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),"eff2.png"))
				{
					setNBState();
					CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(2,tTile.x,tTile.y,-1);
				}
				//穿甲弹
				if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),"eff1.png"))
				{
					setNBBulletState();
					CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(2,tTile.x,tTile.y,-1);
				}
				
			}
		}
	}
}
//每帧调用的渲染
Void		CMyTank::Render()
{
	CGameSceneNPC::Render();
	//显示血条
	C2DCharacter*	tpCharacter = getCharInfo();
	if( tpCharacter )
	{
		CCPoint		tCenetrPos   = tpCharacter->GetScenePos();
		CCSize		tBoundingBox = tpCharacter->GetBoundingBoxSize();
		CCPoint		tScreenPos	 = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tCenetrPos.x,tCenetrPos.y);

		int nHPBorder = 4;
		ccDrawColor4B(255, 255, 255, 255);
		ccDrawRect(ccp(tScreenPos.x-tBoundingBox.width*0.5,tScreenPos.y+tBoundingBox.height*0.5),ccp(tScreenPos.x+tBoundingBox.width*0.5,tScreenPos.y+tBoundingBox.height*0.5-nHPBorder));

		//普通
		int nLeft = tBoundingBox.width - tBoundingBox.width * m_nHP / 10 ;
		ccDrawSolidRect(ccp(tScreenPos.x-tBoundingBox.width*0.5+nLeft+1,tScreenPos.y+tBoundingBox.height*0.5-1),ccp(tScreenPos.x+tBoundingBox.width*0.5-1,tScreenPos.y+tBoundingBox.height*0.5-nHPBorder+1),ccc4f(1.0,0,0,1.0));
	}
}
//设置当前触屏按键
void		CMyTank::SetDirKey(enuDirKey vDir)
{
	m_nDirKey = vDir;
}
//设置开炮
void		CMyTank::SetAttackKey(bool bAttack)
{
	m_bAttackKey = bAttack;
}	
//设置无敌状态
Void		CMyTank::setNBState()
{
	m_bNB = true;
	m_NBTime = 0;
	
}
//是否无敌状态
bool		CMyTank::IsNBState()
{
	return m_bNB;
}	
//设置发射穿甲弹
void		CMyTank::setNBBulletState()
{
	m_bNBBullet = true;
	m_NBBulletTime = 0;
}
//是否发射穿甲弹
bool		CMyTank::IsNBBulletState()
{
	return m_bNBBullet;
}

double m_dwLastAttackTime;//当前的方向int m_nDir;//触屏按键enuDirKey m_nDirKey;//触屏开炮bool m_bAttackKey;//无敌状态bool m_bNB;//无敌的起始时间float m_NBTime;//是否发射穿甲弹bool m_bNBBullet;//发射穿甲弹的起始时间float m_NBBulletTime;};#endif

    这样,我们的主坦克就实现了。在这个类里我们实现了坦克的控制与行走,发射子弹等处理。接下来我们来一下由AI来控制的敌方坦克, 我们新建文件AI_Tank.h/cpp,在红孩儿工具箱的场景中,CGameSceneNPC类是可以挂接私有属性结构CGameSceneNPCAttrib和AI处理功能接口类CGameSceneNPCAI。这样可以方便的进行扩展,每一个NPC类实例有单独的属性结构CGameSceneNPCAttrib的实例。而CGameSceneNPCAI则可以实现所有同类的NPC都具有的相同处理方法。

	#ifndef _AI_TANK_H
#define _AI_TANK_H
//===========================================================
//Auto:火云红孩儿 QQ:285421210
//Date:2013-10-11
//Desc:敌人的坦克
//===========================================================

#include "Header.h"
#include "HHR_Character.h"
#include "HHR_SceneManage.h"
//坦克类
class CEnemyTank	:public CGameSceneNPC
{
public:
	//每帧调用的渲染
	virtual void		Render();
}
;
//坦克的私有属性
class CAttrib_Tank	:public	CGameSceneNPCAttrib
{		
public:
	//构造与析构
	CAttrib_Tank();
	~CAttrib_Tank();
public:
	//坦克类型
	VAR_SETGET(int,m_nTankType,TankType);
	//坦克血量
	VAR_SETGET(int,m_nHP,HP);
	//运动速度
	VAR_SETGET(float,m_fMoveSpeed,MoveSpeed);
	//方向 0左 1上 2右 3下
	VAR_SETGET(int,m_nDir,Dir);
	//设置上次换方向的时间
	VAR_SETGET(double,m_dwLastChangeDirTime,LastChangeDirTime);
	//改变方向的机率
	VAR_SETGET(int,m_nChangeDirProbability,ChangeDirProbability);
	//设置上次开炮的时间
	VAR_SETGET(double,m_dwLastAttackTime,LastAttackTime);

public:
	//按照相应机率取得是否改变方向的判定
	bool	GetRandChangeDirValue();
	//设置无敌状态
	void	setNBState(bool bNB = true);
	//是否无敌状态
	bool	IsNBState();

	//
	//设置发射穿甲弹
	void	setNBBulletState(bool bNBBullet = true);
	//是否发射穿甲弹
	bool	IsNBBulletState();


	//设置上次开炮的时间
	VAR_SETGET(float,m_NBTime,NBTime);
	//设置上次开炮的时间
	VAR_SETGET(float,m_NBBulletTime,NBBulletTime);

private:
	//无敌状态
	bool			m_bNB;
	//是否发射穿甲弹
	bool			m_bNBBullet;
}
;

//坦克的AI
class CAi_Tank	:public CGameSceneNPCAI
{
public:
	//构造与析构
	CAi_Tank();
	~CAi_Tank();
public:
	//设置NPC所用的私有属性
	void	registerDestNPC(CGameSceneNPC*	pNPC);
	//每帧调用的更新
	virtual void	update(CGameSceneNPC*	pNPC,float delay);
private:
	//取得一个随机可移动的方向
	int	GetRandDir(int vLayer,int vTileX,int vTileY);
};

#endif

对应的CPP:

#include "AI_Tank.h"

//每帧调用的渲染
void	CEnemyTank::Render()
{
	CGameSceneNPC::Render();

	//显示血条
	C2DCharacter*	tpCharacter = getCharInfo();
	if( tpCharacter )
	{
		CCPoint		tCenetrPos   = tpCharacter->GetScenePos();
		CCSize		tBoundingBox = tpCharacter->GetBoundingBoxSize();
		CCPoint		tScreenPos	 = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tCenetrPos.x,tCenetrPos.y);
		//取得所用的属性
		CGameSceneNPCAttrib*	pTankAttrib = getNpcAttrib();
		CAttrib_Tank*			pEnemyTankAttrib = dynamic_cast<CAttrib_Tank*>(pTankAttrib);
		if(pEnemyTankAttrib)
		{
			int nHP = pEnemyTankAttrib->getHP();
			int nHPBorder = 4;
			ccDrawColor4B(255, 255, 255, 255);
			ccDrawRect(ccp(tScreenPos.x-tBoundingBox.width*0.5,tScreenPos.y+tBoundingBox.height*0.5),ccp(tScreenPos.x+tBoundingBox.width*0.5,tScreenPos.y+tBoundingBox.height*0.5-nHPBorder));

			int nType = pEnemyTankAttrib->getTankType();

			switch(nType)
			{
			case 0:
				{
					//普通
					int nLeft = tBoundingBox.width - tBoundingBox.width * nHP / 2 ;
					ccDrawSolidRect(ccp(tScreenPos.x-tBoundingBox.width*0.5+nLeft+1,tScreenPos.y+tBoundingBox.height*0.5-1),ccp(tScreenPos.x+tBoundingBox.width*0.5-1,tScreenPos.y+tBoundingBox.height*0.5-nHPBorder+1),ccc4f(1.0,0,0,1.0));
				}
				break;
			case 1:
				{
					//快速
					int nLeft = tBoundingBox.width - tBoundingBox.width * nHP ;
					ccDrawSolidRect(ccp(tScreenPos.x-tBoundingBox.width*0.5+nLeft+1,tScreenPos.y+tBoundingBox.height*0.5-1),ccp(tScreenPos.x+tBoundingBox.width*0.5-1,tScreenPos.y+tBoundingBox.height*0.5-nHPBorder+1),ccc4f(1.0,0,0,1.0));
				}
				break;
			case 2:
				{
					//重型
					int nLeft = tBoundingBox.width - tBoundingBox.width * nHP / 4;
					ccDrawSolidRect(ccp(tScreenPos.x-tBoundingBox.width*0.5+nLeft+1,tScreenPos.y+tBoundingBox.height*0.5-1),ccp(tScreenPos.x+tBoundingBox.width*0.5-1,tScreenPos.y+tBoundingBox.height*0.5-nHPBorder+1),ccc4f(1.0,0,0,1.0));
				}
				break;
			}

		}
	}
}

//构造与析构
CAttrib_Tank::CAttrib_Tank()
{
	m_nTankType				= 0;
	m_nHP					= 100;
	m_fMoveSpeed			= 50;
	m_nDir					= 3;
	m_dwLastChangeDirTime	= 0;
	m_nChangeDirProbability = 30;
	m_dwLastAttackTime		= 0;
	m_bNB					= false;
	m_NBTime				= 0;
	m_bNBBullet				= false;
	m_NBBulletTime			= 0;
}
CAttrib_Tank::~CAttrib_Tank()
{

}
//按照相应机率取得是否改变方向的判定
bool CAttrib_Tank::GetRandChangeDirValue()
{
	int nRand = rand() % 100 ;
	return nRand < m_nChangeDirProbability ;
}
//设置无敌状态
void	CAttrib_Tank::setNBState(bool bNB)
{
	m_bNB = bNB;
	m_NBTime = 0;
}
//是否无敌状态
bool	CAttrib_Tank::IsNBState()
{
	return m_bNB;
}	
//设置发射穿甲弹
void	CAttrib_Tank::setNBBulletState(bool bNBBullet)
{
	m_bNBBullet = bNBBullet;
	m_NBBulletTime = 0;
}
//是否发射穿甲弹
bool	CAttrib_Tank::IsNBBulletState()
{
	return m_bNBBullet;
}
//构造
CAi_Tank::CAi_Tank()
{

}

CAi_Tank::~CAi_Tank()
{

}
//设置NPC所用的私有属性
void	CAi_Tank::registerDestNPC(CGameSceneNPC*	pNPC)
{
	if(pNPC)
	{
		pNPC->setNpcAttrib(new CAttrib_Tank());
	}
}
//每帧调用的更新
void	CAi_Tank::update(CGameSceneNPC*	pNPC,float fDelaySecond)
{
	if(!pNPC)return ;
			
	fDelaySecond = min(fDelaySecond,0.2);
	C2DCharacter*	pCharacter = pNPC->getCharInfo();
	CAttrib_Tank*	pAttrib_Tank = dynamic_cast<CAttrib_Tank*>(pNPC->getNpcAttrib());
	if(pCharacter && pAttrib_Tank && CGameSceneNPC::m_pCurrGameMap)
	{	
		//无敌状态时间计算
		if(pAttrib_Tank->IsNBState())
		{
			float	fNBTime = pAttrib_Tank->getNBTime();
			fNBTime += fDelaySecond ;
			if(fNBTime > 10000)
			{
				pAttrib_Tank->setNBState(false);
				pAttrib_Tank->setNBTime(0);
			}
		}
		//穿甲弹状态时间计算
		if(pAttrib_Tank->IsNBBulletState())
		{
			float	fNBBulletTime = pAttrib_Tank->getNBBulletTime();
			fNBBulletTime += fDelaySecond ;
			if(fNBBulletTime > 10000)
			{
				pAttrib_Tank->setNBBulletState(false);
				pAttrib_Tank->setNBBulletTime(0);
			}
		}

		//播放判断
		struct timeval tv;
		gettimeofday(&tv,NULL);
		double	   	    dwCurrTime = tv.tv_sec*1000.0+tv.tv_usec* 0.001;
		//坦克类型
		int nTankType = pAttrib_Tank->getTankType();
		//取得场景信息
		stSceneHeader*	tpSceneHeader =	CGameSceneNPC::m_pCurrGameMap->GetSceneHeader();
		CCPoint	tScenePos_Old;
		CCPoint	tScenePos;
		tScenePos_Old =	tScenePos = pCharacter->GetScenePos();
		CCSize	tBoundingBox = pCharacter->GetBoundingBoxSize();
		float	fHalfWidth = tBoundingBox.width * 0.5 ;
		float	fHalfHeight = tBoundingBox.height * 0.5 ;

		float	fMoveSpeed		= pAttrib_Tank->getMoveSpeed();
		bool	bChangeDir		= false;
		int		nDir			= pAttrib_Tank->getDir();
		int		nLayer			= 1;
		//当前所在格子
		POINT	tTile			= CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x,tScenePos.y,false);
		//取得格子对应的中心点
		CCPoint	tTile_Center	= CGameSceneNPC::m_pCurrGameMap->GetTileCenterPoint(nLayer,tTile.x,tTile.y,false);
		//是否进入格子中央
		bool	bInTileCenter	= false;			
		//设置图片的旋转方向
		pCharacter->GetCurrBody()->setRotateZ(90 * nDir);	

		switch(nDir)
		{

		case DK_UP:
			{
				//上
				float fPosY  = tScenePos.y + fMoveSpeed*fDelaySecond ;
				if(tScenePos.y < tTile_Center.y && fPosY >= tTile_Center.y)
				{
					bInTileCenter = true;
				}
				tScenePos.y  = fPosY;
				//取得相应层的相应格子数据
				POINT	tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x,tScenePos.y+fHalfHeight,false);
		
				stBlockRenderInfo*	tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);

				if(tpBlockRenderInfo)
				{
					if(tpBlockRenderInfo->m_bIsObstruct)
					{
						bChangeDir = true;
						tScenePos = tScenePos_Old ;
					}
				}
			}
			break;
		case DK_RIGHT:
			{
				//右
				float fPosX  = tScenePos.x + fMoveSpeed*fDelaySecond ;
				if(tScenePos.x < tTile_Center.x && fPosX >= tTile_Center.x)
				{
					bInTileCenter = true;
				}
				tScenePos.x  = fPosX;

				//取得相应层的相应格子数据
				POINT	tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x+fHalfWidth,tScenePos.y,false);
		
				stBlockRenderInfo*	tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);

				if(tpBlockRenderInfo)
				{
					if(tpBlockRenderInfo->m_bIsObstruct)
					{
						bChangeDir = true;
						tScenePos = tScenePos_Old ;
					}
				}
			}
			break;
		case DK_DOWN:
			{
				//下
				float fPosY  = tScenePos.y - fMoveSpeed*fDelaySecond ;
				if(tScenePos.y > tTile_Center.y && fPosY <= tTile_Center.y)
				{
					bInTileCenter = true;
				}
				tScenePos.y  = fPosY;
				//取得相应层的相应格子数据
				POINT	tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x,tScenePos.y-fHalfHeight,false);
		
				stBlockRenderInfo*	tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);

				if(tpBlockRenderInfo)
				{
					if(tpBlockRenderInfo->m_bIsObstruct)
					{
						bChangeDir = true;
						tScenePos = tScenePos_Old ;
					}
				}
			}
			break;
		case DK_LEFT:
			{
				//左
				float fPosX  = tScenePos.x - fMoveSpeed*fDelaySecond ;
				if(tScenePos.x > tTile_Center.x && fPosX <= tTile_Center.x)
				{
					bInTileCenter = true;
				}
				tScenePos.x  = fPosX;

				//取得相应层的相应格子数据
				POINT	tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x-fHalfWidth,tScenePos.y,false);
		
				stBlockRenderInfo*	tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);

				if(tpBlockRenderInfo)
				{
					if(tpBlockRenderInfo->m_bIsObstruct)
					{
						bChangeDir = true;
						tScenePos = tScenePos_Old ;
					}
				}
			}
			break;
		}

		if(false == bChangeDir)
		{
			if(tScenePos.x < tBoundingBox.width*0.5)
			{
				tScenePos.x = tBoundingBox.width*0.5;
				bChangeDir = true;
			}
			else if(tScenePos.x > (tpSceneHeader->m_nSceneWidth-tBoundingBox.width*0.5))
			{
				tScenePos.x = tpSceneHeader->m_nSceneWidth - tBoundingBox.width*0.5;
				bChangeDir = true;
			}
			else if(tScenePos.y < tBoundingBox.height*0.5)
			{
				tScenePos.y = tBoundingBox.height*0.5;
				bChangeDir = true;
			}
			else if(tScenePos.y > (tpSceneHeader->m_nSceneHeight-tBoundingBox.height*0.5))
			{
				tScenePos.y = (tpSceneHeader->m_nSceneHeight-tBoundingBox.height*0.5);
				bChangeDir = true;
			}
		}


		pCharacter->SetScenePos(tScenePos);

		//接到物品
		POINT	tNewTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x,tScenePos.y,false);
		//取得相应层的相应格子数据
		stBlockRenderInfo*	tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tNewTile.x,tNewTile.y);
		if(tpBlockRenderInfo)
		{
			if(RET_IMAGE == tpBlockRenderInfo->m_sRenderElementType)
			{

				//穿甲弹
				if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),"eff1.png"))
				{
					pAttrib_Tank->setNBBulletState();
				}
				//无敌
				if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),"eff2.png"))
				{
					pAttrib_Tank->setNBState();
				}
				//HP
				if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),"eff3.png"))
				{
					switch(nTankType)
					{
					case 0:
						{
							//普通
							pAttrib_Tank->setHP(2);
						}
						break;
					case 1:
						{
							//快速
							pAttrib_Tank->setHP(1);
						}
						break;
					case 2:
						{
							//重型
							pAttrib_Tank->setHP(4);
						}
						break;
					}
					
				}
				CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(nLayer,tNewTile.x,tNewTile.y,-1);
			}
		}

		//开炮时间
		double dwLastAttackTime = pAttrib_Tank->getLastAttackTime() ;
		if(dwCurrTime - dwLastAttackTime > 4000)
		{	
			//发射子弹的代码
			pAttrib_Tank->setLastAttackTime(dwCurrTime) ;
		}

		//如果方向改变
		if(bChangeDir)
		{
			//取得相应层的相应格子数据
			int nNextDir = GetRandDir(nLayer,tNewTile.x,tNewTile.y);
			pAttrib_Tank->setDir(nNextDir);
			//设置图片的旋转方向
			pCharacter->GetCurrBody()->setRotateZ(90 * nNextDir);
		}
		else
		{
			if(bInTileCenter)	
			{
				//放在中心点
				pCharacter->SetScenePos(tTile_Center);

				switch(nDir)
				{
				case DK_LEFT:
				case DK_RIGHT:
					{
						//取得当前层的格子信息
						stEditLayerInfo*	tpLayerInfo = CGameSceneNPC::m_pCurrGameMap->GetCurrLayerInfo(nLayer);

						//左
						//tScenePos.x  -= fMoveSpeed*fDelaySecond ;
						//取得相应层的相应格子数据
						vector<int>	nRandDirVec;
						stBlockRenderInfo*	tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y-1);
						if(tpBlockRenderInfo)
						{
							if(false == tpBlockRenderInfo->m_bIsObstruct)
							{
								nRandDirVec.push_back(1);
							}
						}
						else if(tTile.y > 0)
						{
							nRandDirVec.push_back(1);
						}
						tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y+1);
						if(tpBlockRenderInfo)
						{
							if(false == tpBlockRenderInfo->m_bIsObstruct)
							{
								nRandDirVec.push_back(3);
							}
						}
						else if(tTile.y < tpLayerInfo->m_nTileRows)
						{
							nRandDirVec.push_back(3);
						}
						int		nNextDirNum = nRandDirVec.size();
						bool	bCheckChangeDir  = pAttrib_Tank->GetRandChangeDirValue();
						if( nNextDirNum > 0 && bCheckChangeDir)
						{
							double dwLastChangeDirTime = pAttrib_Tank->getLastChangeDirTime() ;
							if(dwCurrTime - dwLastChangeDirTime > 5000)
							{					
								int nNextDirIndex = rand()%nNextDirNum;
								int nNextDir = nRandDirVec[nNextDirIndex];
								pAttrib_Tank->setLastChangeDirTime(dwCurrTime);
								pAttrib_Tank->setDir(nNextDir);
							}
						}
					}
					break;
				case DK_UP:
				case DK_DOWN:
					{
						stEditLayerInfo*	tpLayerInfo = CGameSceneNPC::m_pCurrGameMap->GetCurrLayerInfo(nLayer);
						//下
						//tScenePos.y  -= fMoveSpeed*fDelaySecond ;
						vector<int>	nRandDirVec;
						stBlockRenderInfo*	tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x-1,tTile.y);
						if(tpBlockRenderInfo)
						{
							if(false == tpBlockRenderInfo->m_bIsObstruct)
							{
								nRandDirVec.push_back(0);
							}
						}
						else if(tTile.x > 0)
						{
							nRandDirVec.push_back(0);
						}
						tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x+1,tTile.y);
						if(tpBlockRenderInfo)
						{
							if(false == tpBlockRenderInfo->m_bIsObstruct)
							{
								nRandDirVec.push_back(2);
							}
						}
						else if(tTile.x < tpLayerInfo->m_nTileCows)
						{
							nRandDirVec.push_back(2);
						}
						int		nNextDirNum = nRandDirVec.size();
						bool	bCheckChangeDir  = pAttrib_Tank->GetRandChangeDirValue();
						if( nNextDirNum > 0 && bCheckChangeDir)
						{
							double dwLastChangeDirTime = pAttrib_Tank->getLastChangeDirTime() ;
							if(dwCurrTime - dwLastChangeDirTime > 5000)
							{					
								int nNextDirIndex = rand()%nNextDirNum;
								int nNextDir = nRandDirVec[nNextDirIndex];
								pAttrib_Tank->setLastChangeDirTime(dwCurrTime);
								pAttrib_Tank->setDir(nNextDir);
							}
						}

					}
					break;
				}
			}
		}
	}
}

//取得一个随机可移动的方向
int	CAi_Tank::GetRandDir(int vLayer,int vTileX,int vTileY)
{
	//取得相应层的相应格子数据
	vector<int>	nRandDirVec;
	stBlockRenderInfo*	tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(vLayer,vTileX,vTileY-1);
	if(tpBlockRenderInfo)
	{
		if(false == tpBlockRenderInfo->m_bIsObstruct)
		{
			nRandDirVec.push_back(1);
		}
	}
	else
	{
		nRandDirVec.push_back(1);
	}
	tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(vLayer,vTileX,vTileY+1);
	if(tpBlockRenderInfo)
	{
		if(false == tpBlockRenderInfo->m_bIsObstruct)
		{
			nRandDirVec.push_back(3);
		}
	}
	else
	{
		nRandDirVec.push_back(3);
	}
	tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(vLayer,vTileX-1,vTileY);
	if(tpBlockRenderInfo)
	{
		if(false == tpBlockRenderInfo->m_bIsObstruct)
		{
			nRandDirVec.push_back(0);
		}
	}
	else
	{
		nRandDirVec.push_back(0);
	}
	tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(vLayer,vTileX+1,vTileY);
	if(tpBlockRenderInfo)
	{
		if(false == tpBlockRenderInfo->m_bIsObstruct)
		{
			nRandDirVec.push_back(2);
		}
	}
	else
	{
		nRandDirVec.push_back(2);
	}
	int nNextDirNum = nRandDirVec.size();
	if( nNextDirNum > 0 )
	{
		int nNextDirIndex = rand()%nNextDirNum;
		return nRandDirVec[nNextDirIndex];
	}

	return -1;
}

通过这些代码的编写,我们给每一个坦克定义了一个单独的属性结构CAttrib_Tank以及对应的AI处理接口类CAi_Tank。

我们可以看到,在发射子弹的代码处没有做处理,我们下面来完成子弹的实现,创建

CBullet.h/cpp:

#ifndef _CBULLET_H
#define _CBULLET_H
//===========================================================
//Auto:火云红孩儿 QQ:285421210
//Date:2013-10-11
//Desc:子弹类
//===========================================================
#include "Header.h"
#include "HHR_Character.h"
#include "HHR_SceneManage.h"
#include "EffectManager.h"
#include "EffectNode.h"
//子弹
class CBullet	:public CGameSceneNPC
{
public:
	CBullet();
	~CBullet();
public:
	//初始化
	virtual void	Init();
	//每帧调用的更新
	virtual void	update(float fDelaySecond);
public:
	//敌方还是多方
	VAR_SETGET(int,m_bIsEnemy,Enemy);
	//运动速度
	VAR_SETGET(float,m_fMoveSpeed,MoveSpeed);
	//方向 0左 1上 2右 3下
	VAR_SETGET(int,m_nDir,Dir);
	//是否能打穿铁皮
	VAR_SETIS(bool,m_bNB,NB);
};

#endif

对应CPP:

#include "CBullet.h"
#include "HelloWorldScene.h"
#include "AI_Tank.h"
#include "CMyTank.h"
#include "SimpleAudioEngine.h"
using namespace CocosDenshion;
CBullet::CBullet()
{
	m_fMoveSpeed = 100;
	m_nDir = -1;
	m_bIsEnemy = true;
	m_bNB = false;
}
CBullet::~CBullet()
{

}

//初始化
void	CBullet::Init()
{

}

//每帧调用的更新
void	CBullet::update(float fDelaySecond)
{
	if( m_pCharacter && CGameSceneNPC::m_pCurrGameMap)
	{	
		fDelaySecond = min(fDelaySecond,0.2);
		//取得场景信息
		CCPoint	tScenePos = m_pCharacter->GetScenePos();
		//取得绑定盒大小
		CCSize	tBoundingBox = m_pCharacter->GetBoundingBoxSize();
		//取得场景信息
		stSceneHeader*	tpSceneHeader =	CGameSceneNPC::m_pCurrGameMap->GetSceneHeader();
		//设置图片的旋转方向
		m_pCharacter->GetCurrBody()->setRotateZ(90 * m_nDir);	
		switch(m_nDir)
		{
		case DK_UP:
			{
				//上
				tScenePos.y  += m_fMoveSpeed*fDelaySecond ;
			}
			break;
		case DK_RIGHT:
			{
				//右
				tScenePos.x  += m_fMoveSpeed*fDelaySecond ;	
			}
			break;
		case DK_DOWN:
			{
				//下
				tScenePos.y  -= m_fMoveSpeed*fDelaySecond ;
			}
			break;
		case DK_LEFT:
			{
				//左
				tScenePos.x  -= m_fMoveSpeed*fDelaySecond ;	
			}
			break;
		}
			
		m_pCharacter->SetScenePos(tScenePos);
		int		nBulletLayerIndex = 2;
		int		nLayerIndex = 1;
		POINT	tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayerIndex,tScenePos.x,tScenePos.y,false);
		//取得相应层的相应格子数据
		stBlockRenderInfo*	tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayerIndex,tTile.x,tTile.y);
		if(tpBlockRenderInfo)
		{
			if(RET_IMAGE == tpBlockRenderInfo->m_sRenderElementType)
			{
				if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),"wall2.png"))
				{
					//破损墙
					CCParticleSystemQuad*	pFireSystem = new CCParticleSystemQuad();
					pFireSystem->initWithFile("explode.plist");
					CCPoint		tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tScenePos.x,tScenePos.y);
					pFireSystem->setPosition(tScreenPos.x,tScreenPos.y);
					CGameSceneNPC::m_pCurrGameMap->AddNew2DParticleSystem(nBulletLayerIndex,pFireSystem);
					CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
					CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(nLayerIndex,tTile.x,tTile.y,-1);
					SimpleAudioEngine::sharedEngine()->playEffect("hurt.mp3");

				}
				if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),"wall.png"))
				{
					//完整墙
					CCParticleSystemQuad*	pFireSystem = new CCParticleSystemQuad();
					pFireSystem->initWithFile("explode.plist");
					CCPoint		tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tScenePos.x,tScenePos.y);
					pFireSystem->setPosition(tScreenPos.x,tScreenPos.y);
					CGameSceneNPC::m_pCurrGameMap->AddNew2DParticleSystem(nBulletLayerIndex,pFireSystem);
					CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
					//将其变为破损墙
					int	nWall2ResID = CGameSceneNPC::m_pCurrGameMap->GetRenderInfoID(RET_IMAGE,"wall2.png","");
					CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(nLayerIndex,tTile.x,tTile.y,nWall2ResID);
					SimpleAudioEngine::sharedEngine()->playEffect("hurt.mp3");
				}
				if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),"tone.png"))
				{
					//木桶
					CCParticleSystemQuad*	pFireSystem = new CCParticleSystemQuad();
					pFireSystem->initWithFile("explode.plist");
					CCPoint		tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tScenePos.x,tScenePos.y);
					pFireSystem->setPosition(tScreenPos.x,tScreenPos.y);
					CGameSceneNPC::m_pCurrGameMap->AddNew2DParticleSystem(nBulletLayerIndex,pFireSystem);
					CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
					SimpleAudioEngine::sharedEngine()->playEffect("hurt.mp3");

					//随机产生
					int randvalue = rand()%3;
					switch(randvalue)
					{
					case 0:
						{
							//穿甲弹
							int	nEffectIndex = CGameSceneNPC::m_pCurrGameMap->GetRenderInfoID(RET_IMAGE,"eff1.png","");
							CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(nLayerIndex,tTile.x,tTile.y,nEffectIndex);

						}
						break;
					case 1:
						{
							//HP
							int	nEffectIndex = CGameSceneNPC::m_pCurrGameMap->GetRenderInfoID(RET_IMAGE,"eff2.png","");
							CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(nLayerIndex,tTile.x,tTile.y,nEffectIndex);

						}
						break;
					case 2:
						{
							//无敌
							int	nEffectIndex = CGameSceneNPC::m_pCurrGameMap->GetRenderInfoID(RET_IMAGE,"eff3.png","");
							CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(nLayerIndex,tTile.x,tTile.y,nEffectIndex);
						}
						break;
					}
				}
				if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),"metal.png"))
				{
					//金属块打不动
					CCParticleSystemQuad*	pFireSystem = new CCParticleSystemQuad();
					pFireSystem->initWithFile("explode.plist");
					CCPoint		tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tScenePos.x,tScenePos.y);
					pFireSystem->setPosition(tScreenPos.x,tScreenPos.y);
					CGameSceneNPC::m_pCurrGameMap->AddNew2DParticleSystem(nBulletLayerIndex,pFireSystem);
					CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
					SimpleAudioEngine::sharedEngine()->playEffect("hurt.mp3");
					if(m_bNB)
					{
						//如果够牛逼,就能击穿
						CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(nLayerIndex,tTile.x,tTile.y,-1);
					}
				}
			}
			if(RET_ANI == tpBlockRenderInfo->m_sRenderElementType)
			{
				if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),"home.gan"))
				{
					//基地被炸,结束了
					CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
					CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(nLayerIndex,tTile.x,tTile.y,-1);

					//击毁一个敌方坦克
					HelloWorld*	tpHelloWorld = dynamic_cast<HelloWorld*>(CGameSceneNPC::m_pCurrGameMap->getParent());
					if(tpHelloWorld)
					{
						tpHelloWorld->KillMyTank();

						CCPoint		tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tScenePos.x,tScenePos.y);									

						//加载爆炸效果
						EffectNode*		gpEffect = EffectManager::GetInstance()->LoadEffect("101");
						MyVector3 vPos;
						vPos.x = tScreenPos.x;
						vPos.y = tScreenPos.y;
						vPos.z = 10;
						//设置效果位置
						gpEffect->SetPos(vPos);
						MyVector3 vScale;
						vScale.x=0.5;
						vScale.y=0.5;
						vScale.z=0;
						//设置效果缩放
						gpEffect->SetScale(vScale);
						//播放效果
						gpEffect->PlayEffect();

					}
					return ;
				}
			}
		}
		//取得坦克数量
		
		int nTankNum  = CGameSceneNPC::m_pCurrGameMap->Get2DActiveObjNum(nLayerIndex);
		for(int t = 0 ; t < nTankNum ; t++)
		{
			CGameSceneNPC*	tpTank = CGameSceneNPC::m_pCurrGameMap->Get2DActiveObj(nLayerIndex,t);
			if( tpTank )
			{
				bool		bEnemyTank = false;
				//取得所用的属性
				CGameSceneNPCAttrib*	pTankAttrib = tpTank->getNpcAttrib();
				CAttrib_Tank*			pEnemyTankAttrib = dynamic_cast<CAttrib_Tank*>(pTankAttrib);
				if(pEnemyTankAttrib)
				{
					bEnemyTank = true;
				}

				//敌方子弹只攻击我方
				if(m_bIsEnemy && bEnemyTank)
				{
					continue;
				}
				//我方子弹只攻击敌方
				if(!m_bIsEnemy && !bEnemyTank)
				{
					continue;
				}
				
				//取得角色信息
				C2DCharacter*	tpCharacter = tpTank->getCharInfo();
				if( tpCharacter )
				{
					CCPoint		tCenetrPos   = tpCharacter->GetScenePos();
					CCSize		tBoundingBox = tpCharacter->GetBoundingBoxSize();

					if(tScenePos.x < (tCenetrPos.x - tBoundingBox.width*0.5))continue ;
					if(tScenePos.y < (tCenetrPos.y - tBoundingBox.height*0.5))continue ;
					if(tScenePos.x > (tCenetrPos.x + tBoundingBox.width*0.5))continue ;
					if(tScenePos.y > (tCenetrPos.y + tBoundingBox.height*0.5))continue ;

					CCParticleSystemQuad*	pFireSystem = new CCParticleSystemQuad();
					pFireSystem->initWithFile("explode.plist");
					CCPoint		tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tScenePos.x,tScenePos.y);
					pFireSystem->setPosition(tScreenPos.x,tScreenPos.y);
					CGameSceneNPC::m_pCurrGameMap->AddNew2DParticleSystem(nBulletLayerIndex,pFireSystem);

					CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
					SimpleAudioEngine::sharedEngine()->playEffect("hurt.mp3");

					if(true == bEnemyTank)
					{
						CAttrib_Tank*	tpTankAttrib = dynamic_cast<CAttrib_Tank*>(tpTank->getNpcAttrib());
						if(tpTankAttrib&&tpTankAttrib->IsNBState() == false)
						{
							int	tHP = tpTankAttrib->getHP();
							if(m_bNB)
							{
								tHP-=2;
							}
							else
							{
								tHP--;
							}

							tpTankAttrib->setHP(tHP);
							if(tHP <= 0)
							{
								CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nLayerIndex,tpTank);
								//击毁一个敌方坦克
								HelloWorld*	tpHelloWorld = dynamic_cast<HelloWorld*>(CGameSceneNPC::m_pCurrGameMap->getParent());
								if(tpHelloWorld)
								{
									tpHelloWorld->KillEmenyTank(tpTankAttrib->getTankType());

									CCPoint		tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tScenePos.x,tScenePos.y);									
													
									//加载爆炸效果
									EffectNode*		gpEffect = EffectManager::GetInstance()->LoadEffect("101");
									MyVector3 vPos;
									vPos.x = tScreenPos.x;
									vPos.y = tScreenPos.y;
									vPos.z = 10;
									//设置效果位置
									gpEffect->SetPos(vPos);
									MyVector3 vScale;
									vScale.x=0.5;
									vScale.y=0.5;
									vScale.z=0;
									//设置效果缩放
									gpEffect->SetScale(vScale);
									//播放效果
									gpEffect->PlayEffect();

								}
							}
						}
					}
					else
					{
						
						CMyTank*	tpMyTank = dynamic_cast<CMyTank*>(tpTank);
						if(tpMyTank&&tpMyTank->IsNBState() == false)
						{

							int	tHP = tpMyTank->getHP();
							if(m_bNB)
							{
								tHP-=2;
							}
							else
							{
								tHP--;
							}

							tpMyTank->setHP(tHP);
							if(tHP <= 0)
							{
								CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(1,tpTank);
								//击毁一个敌方坦克
								HelloWorld*	tpHelloWorld = dynamic_cast<HelloWorld*>(CGameSceneNPC::m_pCurrGameMap->getParent());
								if(tpHelloWorld)
								{
									tpHelloWorld->KillMyTank();

									CCPoint		tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tScenePos.x,tScenePos.y);									
													
									//加载爆炸效果
									EffectNode*		gpEffect = EffectManager::GetInstance()->LoadEffect("101");
									MyVector3 vPos;
									vPos.x = tScreenPos.x;
									vPos.y = tScreenPos.y;
									vPos.z = 10;
									//设置效果位置
									gpEffect->SetPos(vPos);
									MyVector3 vScale;
									vScale.x=0.5;
									vScale.y=0.5;
									vScale.z=0;
									//设置效果缩放
									gpEffect->SetScale(vScale);
									//播放效果
									gpEffect->PlayEffect();


								}
							}
						}
						
					}
					return ;
				}
				
			}
		}

		//删除
		if(tScenePos.x < tBoundingBox.width*0.5)
		{
			CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
		}
		else if(tScenePos.x > (tpSceneHeader->m_nSceneWidth-tBoundingBox.width*0.5))
		{
			CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
		}
		else if(tScenePos.y < tBoundingBox.height*0.5)
		{
			CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
		}
		else if(tScenePos.y > (tpSceneHeader->m_nSceneHeight-tBoundingBox.height*0.5))
		{
			CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
		}
	}
}

    这个子弹类的重点,是在每次update更新位置时对所在格子里的物体进行判断并在击中相应的物体后创建粒子爆炸效果,删除子弹及相应物体。

    字弹类完成后,我们在敌我坦克的update函数中的发射子弹处理中加入相应的代码.

CMyTank.cpp:
//按下下键
		if (m_bAttackKey || (::GetKeyState('J'))&0x8000) 
		{
			struct timeval tv;
			gettimeofday(&tv,NULL);
			double	   	    dwCurrTime = tv.tv_sec*1000.0+tv.tv_usec* 0.001;
			//开炮时间
			if(dwCurrTime - m_dwLastAttackTime > 1000)
			{	
				//创建一个子弹角色
				C2DCharacter*	tpPlayerChar = g_CharacterManage.CreateNewCharacter("Bullet");
				if(tpPlayerChar)
				{
					//如果是穿甲弹,则播放穿甲弹对应的角色动作
					if(m_bNBBullet)
					{
						tpPlayerChar->PlayAction("run_nb",-1);
					}
					else
					{
						tpPlayerChar->PlayAction("run",-1);
					}
					//产生炮弹
					tpPlayerChar->SetScenePos(tScenePos);
					tpPlayerChar->GetCurrBody()->setZ(3);

					CBullet*	pNewBullet = new CBullet();
					pNewBullet->setCharInfo(tpPlayerChar);
					pNewBullet->setEnemy(false);
					pNewBullet->setDir(m_nDir);
					pNewBullet->setNB(m_bNBBullet);
					pNewBullet->setMoveSpeed(250);
					pNewBullet->Init();

					int nBulletLayerIndex = 2;
					CGameSceneNPC::m_pCurrGameMap->AddNew2DActiveObj(nBulletLayerIndex,pNewBullet);
					m_dwLastAttackTime = dwCurrTime ;
				}
			}
		}

AI_Tank.cpp:

//开炮时间
		double dwLastAttackTime = pAttrib_Tank->getLastAttackTime() ;
		if(dwCurrTime - dwLastAttackTime > 4000)
		{	

			C2DCharacter*	tpPlayerChar = g_CharacterManage.CreateNewCharacter("bullet");
			if(tpPlayerChar)
			{
				if(2 == nTankType || pAttrib_Tank->IsNBBulletState())
				{
					//穿甲弹
					tpPlayerChar->PlayAction("run_nb",-1);
				}
				else
				{
					//普通子弹
					tpPlayerChar->PlayAction("run",-1);
				}
				//产生炮弹
				tpPlayerChar->SetScenePos(tScenePos);
				tpPlayerChar->GetCurrBody()->setZ(3);
				CBullet*	pNewBullet = new CBullet();
				pNewBullet->setCharInfo(tpPlayerChar);
				pNewBullet->setEnemy(true);
				pNewBullet->setDir(nDir);
				pNewBullet->setNB(pAttrib_Tank->IsNBBulletState());
				pNewBullet->setMoveSpeed(250);
				pNewBullet->Init();
				CGameSceneNPC::m_pCurrGameMap->AddNew2DActiveObj(2,pNewBullet);
				pAttrib_Tank->setLastAttackTime(dwCurrTime) ;
			}
		}

   这样运行起来就可以发射子弹了.到这时为止,游戏场景,坦克,攻击逻辑和效果都算做完了.我们回到HelloWorldScene. Cpp,在点选关卡按钮函数中加入相应的处理:

//开始第N关
void HelloWorld::ChoseCardBtnCallBack(CCNode* pSender, void* data)
{
	//关闭选关卡的界面
	CGameUI*		pChoseChardUI = dynamic_cast<CGameUI*>(getChildByTag(NID_UI_CHOSEMAP));
	if(pChoseChardUI)
	{
		pChoseChardUI->setVisible(false);
	}
	//如果没有创建场景,在这里创建出来.
	if(NULL == m_pGameScene)
	{
		m_pGameScene = new CGameScene();
		m_pGameScene->autorelease();
		m_pGameScene->SetCameraPosition(0,0);
		m_pGameScene->setTouchEnabled(false);
		//pNewScene->ShowTileInfo(true);
		this->addChild(m_pGameScene,1,101);
	}

	//取得关卡ID并初始化一些变量
	int nCardIndex = (int)data;
	m_nCurrMapID = nCardIndex+1 ;
	m_EnemyTankVec.clear();
	//坦克总数
	m_EnemyDeadTotal = 0;
	//每关卡坦克数量递增以增加难度
	m_EnemyTankTotal = 4 + m_nCurrMapID ;
	m_EnenyTankCount = 0;
	m_nEnemyTank1_DeadCount = m_nEnemyTank2_DeadCount = m_nEnemyTank3_DeadCount = 0;
	m_nEnemyTank1_AwardCount = m_nEnemyTank2_AwardCount = m_nEnemyTank3_AwardCount = 0;
	char szCardName[100];
	
	sprintf(szCardName,"card%d.gsc",m_nCurrMapID++);
	if(true == m_pGameScene->LoadSceneFile_Bin(szCardName))
	{
		//为场景增加一些图片资源,分别为穿甲弹,无敌药瓶和回血瓶.末参数设置为不阻挡,
		m_pGameScene->AddRenderInfo(RET_IMAGE,"eff1.png","",false);
		m_pGameScene->AddRenderInfo(RET_IMAGE,"eff2.png","",false);
		m_pGameScene->AddRenderInfo(RET_IMAGE,"eff3.png","",false);
		//增加第二种墙的资源,即被击中后的半击毁墙体。末参数设为阻挡。
		m_pGameScene->AddRenderInfo(RET_IMAGE,"wall2.png","",true);
		//增加一层用于子弹显示
		m_pGameScene->AddNewLayer();
		//附加效果管理器到当前层。效果库是一个单件。在这里加入一下。
		m_pGameScene->addChild(EffectManager::GetInstance(), 10); 
		//加载角色包。
		if(true == g_CharacterManage.LoadRolTreeFromBin("tank.gro"))
		{
			//创建玩家角色
			C2DCharacter*	tpPlayerChar = g_CharacterManage.CreateNewCharacter("Player");
			if(tpPlayerChar)
			{
				//调用其动作
				tpPlayerChar->PlayAction("run",-1);
				//取得事件点数量,
				int		nLayerIndex = 1;
				int		nEventTileNum =	 m_pGameScene->GetEventTileNum(nLayerIndex);
				//取得对应层的指定事件点
				for(int n = 0 ; n < nEventTileNum ; n++)
				{
					stEventTile*	tpEventTile = m_pGameScene->GetEventTile(nLayerIndex,n);
					if(tpEventTile)
					{
						//如果事件点ID为1,设置为我方主坦克位。
						if(tpEventTile->m_nEventID == 1)
						{
							//取得相应格子的实际位置点。
							CCPoint		tBorthPt = m_pGameScene->GetTileCenterPoint(nLayerIndex,tpEventTile->m_sTile.x,tpEventTile->m_sTile.y,false);
							//设置我方主坦克在这个位置上。
							tpPlayerChar->SetScenePos(tBorthPt);
							//创建一个逻辑NPC类。
							m_pMyTank = new CMyTank();
							m_pMyTank->setCharInfo(tpPlayerChar);
							m_pMyTank->Init();
							//将NPC放入到场景中。
							m_pGameScene->AddNew2DActiveObj(1,m_pMyTank);
						}
						else if(tpEventTile->m_nEventID == 0)
						{
							//记录敌人坦克出生点
							POINT tPt;
							tPt.x = tpEventTile->m_sTile.x;
							tPt.y = tpEventTile->m_sTile.y;
							m_EnemyTankVec.push_back(tPt);
						}
					}
				}
				//创建出敌人坦克AI
				m_pEnemyTankAI = new CAi_Tank();
				//每2秒出现一个坦克
				schedule(schedule_selector(HelloWorld::EnemyTankBorth), 2.0f);
}
			}
		}
	}

}

     在初始化场景时,这里会遍历所有的事件点,根据不同的事件点做不同的处理,比如在事件点ID为0处创建敌方坦克,在事件点ID为1处创建我方坦克.因为创建敌方坦克是个持续的过程,这里通过每2秒调用的回调函数EnemyTankBorth来进行处理.

 

void HelloWorld::EnemyTankBorth(float dt)
{
	
	if(m_EnenyTankCount < m_EnemyTankTotal)
	{
		m_EnenyTankCount++;	
		int nBorthTileIndex = m_EnenyTankCount % m_EnemyTankVec.size() ; 

		//创建一个2D动画角色
		char szRandEnemy[64];
		int  nRandValue = rand()%3;
		sprintf(szRandEnemy,"Enemy%d",nRandValue+1);
		C2DCharacter*	tpEnemyChar = g_CharacterManage.CreateNewCharacter(szRandEnemy);
		if(tpEnemyChar)
		{

			//播放相应的动画名称
			tpEnemyChar->PlayAction("run",-1);
			int			nLayerIndex = 1;
			CCPoint		tBorthPt = m_pGameScene->GetTileCenterPoint(nLayerIndex,m_EnemyTankVec[nBorthTileIndex].x,m_EnemyTankVec[nBorthTileIndex].y,false);
			tpEnemyChar->SetScenePos(tBorthPt);

			//创建敌人坦克	
			CEnemyTank*	m_pEnemyTank = new CEnemyTank();
			m_pEnemyTank->setCharInfo(tpEnemyChar);
			m_pEnemyTank->Init();
				
			//设置坦克AI
			m_pEnemyTank->setNpcAI(m_pEnemyTankAI);
			m_pGameScene->AddNew2DActiveObj(1,m_pEnemyTank);

			//设置速度
			CAttrib_Tank*	tpTankAttrib = dynamic_cast<CAttrib_Tank*>(m_pEnemyTank->getNpcAttrib());
			if(tpTankAttrib)
			{
				tpTankAttrib->setTankType(nRandValue);
				switch(nRandValue)
				{
				case 0:
					//普通
					tpTankAttrib->setHP(2);
					tpTankAttrib->setMoveSpeed(50);
					break;
				case 1:
					//快速
					tpTankAttrib->setHP(1);
					tpTankAttrib->setMoveSpeed(70);
					break;
				case 2:
					//重型
					tpTankAttrib->setHP(4);
					tpTankAttrib->setMoveSpeed(30);
					break;
				}
				
			}

		}
	}
	else
	{
		//注销当前定时触发的回调函数
		unschedule(schedule_selector(HelloWorld::EnemyTankBorth));		
	}
}

这样刷敌人坦克的处理也就做好了.下面我们还要处理一下击毁敌方坦克和我方坦克或基地被击毁的函数.

//击毁一个敌人坦克
void HelloWorld::KillEmenyTank(int vTankType)
{
	m_EnemyDeadTotal++;
	//统计每种坦克被击毁的数量
	switch(vTankType)
	{
	case 0:
		m_nEnemyTank1_DeadCount++;
		break;
	case 1:
		m_nEnemyTank2_DeadCount++;
		break;
	case 2:
		m_nEnemyTank3_DeadCount++;
		break;
	}
	//如果所有敌坦克被灭,则设置本关卡完成
	if(m_EnemyDeadTotal == m_EnemyTankTotal)
	{
		m_bWinThisMap = true;
	}
}	
//击毁我方坦克
void HelloWorld::KillMyTank()
{
	//我方被毁,也要进入到关卡结束界面,所以可以在这里设置一下变量以进入关卡结束界面.
	m_EnemyDeadTotal = m_EnemyTankTotal ;
	//判定失败
	m_bWinThisMap = false;
	//因为关卡开始时m_nCurrMapID++,所以这里要还原一下.
	m_nCurrMapID--;
}

重载一下HelloWorld的darw函数,判断是否结束当前关卡.

//渲染
void HelloWorld::draw(void)
{
	//如果敌坦克被灭,进入到关卡结束界面.
	if(m_EnemyTankTotal >0 && m_EnemyDeadTotal == m_EnemyTankTotal)
	{
		FinishThisMap();
	}
	CCLayer::draw();
}

FinishThisMap函数中我们将需要显示一个统计界面.使用工具箱制作一个界面,然后将数字也用工具箱转成需要的字图以便显示统计数字.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏


做完需要的界面和字图后将其保存为ui_finish.uinumber.fnt,我们继续完成FinishThisMap函数.

//关卡结束界面
void HelloWorld::FinishThisMap()
{
	if(m_pGameScene)
	{
		//隐藏场景与控制界面
		m_pGameScene->setVisible(false);
		CGameUI*	pFXPUI = dynamic_cast<CGameUI*>(getChildByTag(NID_UI_FXP));
		if(pFXPUI)
		{
			pFXPUI->setVisible(false);
		}
	}
	//方向盘与攻击按键界面
	CGameUI*		pFinishUI = dynamic_cast<CGameUI*>(getChildByTag(NID_UI_FINISH));
	if(pFinishUI)
	{
		pFinishUI->setVisible(true);
		CUICtrl*	pUICtrl = pFinishUI->QueryUICtrl("UICtrl6_Rect");
		if(pUICtrl)
		{
			pUICtrl->setVisible(false);
		}
		pUICtrl = pFinishUI->QueryUICtrl("UICtrl7_Rect");
		if(pUICtrl)
		{
			pUICtrl->setVisible(false);
		}
	}
	else
	{
		pFinishUI = new CGameUI();
		pFinishUI->autorelease();
		pFinishUI->LoadUITree("ui_finish.ui");
		//设置界面的位置
		pFinishUI->setOffset(0, 0);
		this->addChild(pFinishUI,2,NID_UI_FINISH);

		m_pTankTextLabel1 = CCLabelBMFont::create("","number.fnt");
		m_pTankTextLabel1->setPosition(ccp(170, 380));
		m_pTankTextLabel1->setAlignment(kCCTextAlignmentLeft);
		m_pTankTextLabel1->setAnchorPoint(ccp(0,0));
		pFinishUI->addChild(m_pTankTextLabel1,1);

		m_pTankTextLabel2 = CCLabelBMFont::create("","number.fnt");
		m_pTankTextLabel2->setPosition(ccp(170, 300));
		m_pTankTextLabel2->setAlignment(kCCTextAlignmentLeft);
		m_pTankTextLabel2->setAnchorPoint(ccp(0,0));
		pFinishUI->addChild(m_pTankTextLabel2,1);

		m_pTankTextLabel3 = CCLabelBMFont::create("","number.fnt");
		m_pTankTextLabel3->setPosition(ccp(170, 220));
		m_pTankTextLabel3->setAlignment(kCCTextAlignmentLeft);
		m_pTankTextLabel3->setAnchorPoint(ccp(0,0));
		pFinishUI->addChild(m_pTankTextLabel3,1);

		m_pWardTotalLabel = CCLabelBMFont::create("","number.fnt");
		m_pWardTotalLabel->setPosition(ccp(230, 140));
		m_pWardTotalLabel->setAlignment(kCCTextAlignmentLeft);
		m_pWardTotalLabel->setAnchorPoint(ccp(0,0));
		pFinishUI->addChild(m_pWardTotalLabel,1);
	}
	//显示数量
	char	tTemp[1024];
	sprintf(tTemp,"x %d = %d", m_nEnemyTank1_DeadCount,0);
	m_pTankTextLabel1->setCString(tTemp);
	sprintf(tTemp,"x %d = %d", m_nEnemyTank2_DeadCount,0);
	m_pTankTextLabel2->setCString(tTemp);
	sprintf(tTemp,"x %d = %d", m_nEnemyTank3_DeadCount,0);
	m_pTankTextLabel3->setCString(tTemp);
	sprintf(tTemp,"%d", m_nAwardTotal);
	m_pWardTotalLabel->setCString(tTemp);	
	//设置攻击按钮的回调
	CUICtrl*	pUICtrl = pFinishUI->QueryUICtrl("UICtrl6_Button");
	if(pUICtrl)
	{
		CUIButton*	pUIButton = dynamic_cast<CUIButton*>(pUICtrl);
		if(pUIButton)
		{
			//设置在按下时响应的回调函数,可带自定义参数
			pUIButton->setClickBeginCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::NextMapBtnCallBack), (void*)0));
		}
	}
	//胜利还是失败的显示
	if(m_bWinThisMap)
	{
		CUICtrl*	pUICtrl = pFinishUI->QueryUICtrl("UICtrl7_Rect");
		if(pUICtrl)
		{
			CUIRect*	pUIRect = dynamic_cast<CUIRect*>(pUICtrl);
			if(pUIRect)
			{
				pUIRect->setVisible(true);
				C2DSkinAni*	pSkinAni = pUIRect->getSkinAni();
				if(pSkinAni)
				{
					pSkinAni->Play(1);
				}
			}
		}
	}
	else
	{
		CUICtrl*	pUICtrl = pFinishUI->QueryUICtrl("UICtrl8_Rect");
		if(pUICtrl)
		{
			CUIRect*	pUIRect = dynamic_cast<CUIRect*>(pUICtrl);
			if(pUIRect)
			{
				pUIRect->setVisible(true);
				C2DSkinAni*	pSkinAni = pUIRect->getSkinAni();
				if(pSkinAni)
				{
					pSkinAni->Play(1);
				}
			}
		}
	}

//快速更新统计数字的效果,使用每0.01秒的回调函数来更新数字从0到指定数字的变化。

schedule(schedule_selector(HelloWorld::ShowEnemyTankCountText), 0.01f);

 

      m_EnemyTankTotal= 0;

}

 

//敌人坦克被击毁统计

void HelloWorld::ShowEnemyTankCountText(floatdt)

{

      if(m_pTankTextLabel1&&m_pTankTextLabel2&&m_pTankTextLabel3)

      {

           boolbFinish = true;

           if(m_nEnemyTank1_AwardCount< m_nEnemyTank1_DeadCount*10)

           {

                 m_nEnemyTank1_AwardCount++;

                 bFinish= false;

           }

           if(m_nEnemyTank2_AwardCount< m_nEnemyTank2_DeadCount*8)

           {

                 m_nEnemyTank2_AwardCount++;

                 bFinish= false;

           }

           if(m_nEnemyTank3_AwardCount< m_nEnemyTank3_DeadCount*30)

           {

                 m_nEnemyTank3_AwardCount++;

                 bFinish= false;

           }

           m_nAwardTotal= m_nEnemyTank1_AwardCount + m_nEnemyTank2_AwardCount +m_nEnemyTank3_AwardCount;

           //显示数量

           char     tTemp[1024];

           sprintf(tTemp,"x%d=%d",m_nEnemyTank1_DeadCount,m_nEnemyTank1_AwardCount);

           m_pTankTextLabel1->setString(tTemp,true);

           sprintf(tTemp,"x%d=%d",m_nEnemyTank2_DeadCount,m_nEnemyTank2_AwardCount);

           m_pTankTextLabel2->setString(tTemp,true);

           sprintf(tTemp,"x%d=%d",m_nEnemyTank3_DeadCount,m_nEnemyTank3_AwardCount);

           m_pTankTextLabel3->setString(tTemp,true);

           sprintf(tTemp,"%d", m_nAwardTotal);

           m_pWardTotalLabel->setString(tTemp,true);

           if(bFinish)

           {

      //注销显示敌方坦克数量的函数.

                       unschedule(schedule_selector(HelloWorld::ShowEnemyTankCountText));      

           }

      }

}

 

在ui_finish.ui中我们为继续游戏按钮增加了一个点击响应的回函数.

//下一关按钮回调
void HelloWorld::NextMapBtnCallBack(CCNode* pSender, void* data)
{
	LoadNextMap();
}

//开始下一关
void HelloWorld::LoadNextMap()
{
	CGameUI*		pChoseChardUI = dynamic_cast<CGameUI*>(getChildByTag(NID_UI_FINISH));
	if(pChoseChardUI)
	{
		pChoseChardUI->setVisible(false);
	}

	if(m_pGameScene)
	{
		m_EnemyTankVec.clear();
		//坦克总数
		m_EnemyDeadTotal = 0;
		m_EnemyTankTotal = 4 + m_nCurrMapID ;
		m_EnenyTankCount = 0;
		m_nEnemyTank1_DeadCount = m_nEnemyTank2_DeadCount = m_nEnemyTank3_DeadCount = 0;
		m_nEnemyTank1_AwardCount = m_nEnemyTank2_AwardCount = m_nEnemyTank3_AwardCount = 0;
		

		//保存关卡
		if(m_nLastPlayMapID < m_nCurrMapID-1)
		{
			m_nLastPlayMapID = m_nCurrMapID-1;
			CCUserDefault::sharedUserDefault()->setIntegerForKey("MapID", m_nLastPlayMapID);
			CCUserDefault::sharedUserDefault()->flush(); 
		}

		//根据是否胜利进入关卡
		char szCardName[100];
		if(m_bWinThisMap)
		{
			sprintf(szCardName,"card%d.gsc",m_nCurrMapID++);
		}
		else
		{
			sprintf(szCardName,"card%d.gsc",m_nCurrMapID);
		}
		
		if(m_nCurrMapID > 15)
		{
			//播放通关动画
			scheduleOnce(schedule_selector(HelloWorld::CreatWinnerUI), 0.0f);
		}
		else
		{
			if(true == m_pGameScene->LoadSceneFile_Bin(szCardName))
			{
				//增加一些物件资源
				m_pGameScene->AddRenderInfo(RET_IMAGE,"eff1.png","",false);
				m_pGameScene->AddRenderInfo(RET_IMAGE,"eff2.png","",false);
				m_pGameScene->AddRenderInfo(RET_IMAGE,"eff3.png","",false);
				//增加第二种墙的资源
				m_pGameScene->AddRenderInfo(RET_IMAGE,"wall2.png","",true);
				//增加一层用于子弹显示
				m_pGameScene->AddNewLayer();
				//设置显示
				m_pGameScene->setVisible(true);

				if(true == g_CharacterManage.LoadRolTreeFromBin("tank.gro"))
				{

					C2DCharacter*	tpPlayerChar = g_CharacterManage.CreateNewCharacter("Player");
					if(tpPlayerChar)
					{
						tpPlayerChar->PlayAction("run",-1);
						//取得事件点数量
						int		nLayerIndex = 1;
						int		nEventTileNum =	 m_pGameScene->GetEventTileNum(nLayerIndex);
						//取得对应层的指定事件点
						for(int n = 0 ; n < nEventTileNum ; n++)
						{
							stEventTile*	tpEventTile = m_pGameScene->GetEventTile(nLayerIndex,n);
							if(tpEventTile)
							{
								if(tpEventTile->m_nEventID == 1)
								{
									//产生我方坦克
									CCPoint		tBorthPt = m_pGameScene->GetTileCenterPoint(nLayerIndex,tpEventTile->m_sTile.x,tpEventTile->m_sTile.y,false);
									tpPlayerChar->SetScenePos(tBorthPt);

									m_pMyTank = new CMyTank();
									m_pMyTank->setCharInfo(tpPlayerChar);
									m_pMyTank->Init();
									m_pGameScene->AddNew2DActiveObj(1,m_pMyTank);
								}
								else if(tpEventTile->m_nEventID == 0)
								{
									//记录敌人坦克出生点
									POINT tPt;
									tPt.x = tpEventTile->m_sTile.x;
									tPt.y = tpEventTile->m_sTile.y;
									m_EnemyTankVec.push_back(tPt);
								}
							}
						}
						//创建出敌人坦克AI
						m_pEnemyTankAI = new CAi_Tank();
						//每2秒出现一个坦克
						schedule(schedule_selector(HelloWorld::EnemyTankBorth), 2.0f);
					}

				}
			}
		}
	}
}

      播放通关动画的处理在函数CreatWinnerUI中,当然,我们先用工具箱制作好通过的胜利动画,这里简单的做了一个老照片的渐渐显示的动画,与开始时的老照片动画类似,不多做讲述.

   使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

做好后保存为sl.ani并导出sl.gan供调用.

//通关的界面
void HelloWorld::CreatWinnerUI(float dt)
{
	//过场动画
	C2DSkinAni*		pNewSkinAni = new C2DSkinAni();
	if(pNewSkinAni)
	{
		pNewSkinAni->ReadAllSubBodyFromBin("sl.gan");
		CCSize tBoundingBox = pNewSkinAni->GetBoundingBoxSize();
		pNewSkinAni->autorelease();
		pNewSkinAni->Play(1);
		this->addChild(pNewSkinAni,4,NID_ANI_WAR);
		CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
		pNewSkinAni->SetOffset(visibleSize.width/2,visibleSize.height/2 );
		//最后一帧加一个动画调用
		pNewSkinAni->SetEndFrameCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::WinnerEndFrameCallBack), (void*)0xbebabeba));
	}
}	
//通关的动画显示结束的回调,再次回到开始界面.
void	HelloWorld::WinnerEndFrameCallBack(CCNode* pSender, void* data)
{
	C2DSkinAni*		pNewSkinAni = dynamic_cast<C2DSkinAni*>(getChildByTag(NID_ANI_WAR));
	if(pNewSkinAni)
	{
		pNewSkinAni->setVisible(false);
		removeChild(pNewSkinAni);
		//pNewSkinAni->release();
	}
	
	CGameUI*		pNewUI = dynamic_cast<CGameUI*>(getChildByTag(NID_UI_START));
	if(pNewUI)
	{
		pNewUI->setVisible(true);
	}
}

到现在这一步,基本逻辑就算完成了.最后我们再为它增加一个触屏控制界面,以使其可以在移动设备上进行控制.

使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

导出ui_fxp.ui.在场景初始化完成时加入代码:

//方向盘与攻击按键界面
			CGameUI*		pFXPUI = new CGameUI();
			pFXPUI->autorelease();
			pFXPUI->LoadUITree("ui_fxp.ui");
			//设置界面的位置
			pFXPUI->setOffset(0, 0);
			this->addChild(pFXPUI,4,NID_UI_FXP);

			//设置方向盘按钮的回调
			CUICtrl*	pUICtrl = pFXPUI->QueryUICtrl("UICtrl6_Button");
			if(pUICtrl)
			{
				CUIButton*	pUIButton = dynamic_cast<CUIButton*>(pUICtrl);
				if(pUIButton)
				{
					//设置在按下时响应的回调函数,可带自定义参数
					pUIButton->setClickBeginCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::FXPBtnCallBack), (void*)0));
					pUIButton->setClickEndCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::FXPBtnCallBack), (void*)-1));
				}
			}
			pUICtrl = pFXPUI->QueryUICtrl("UICtrl7_Button");
			if(pUICtrl)
			{
				CUIButton*	pUIButton = dynamic_cast<CUIButton*>(pUICtrl);
				if(pUIButton)
				{
					//设置在按下时响应的回调函数,可带自定义参数
					pUIButton->setClickBeginCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::FXPBtnCallBack), (void*)1));
					pUIButton->setClickEndCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::FXPBtnCallBack), (void*)-1));
				}
			}
			pUICtrl = pFXPUI->QueryUICtrl("UICtrl8_Button");
			if(pUICtrl)
			{
				CUIButton*	pUIButton = dynamic_cast<CUIButton*>(pUICtrl);
				if(pUIButton)
				{
					//设置在按下时响应的回调函数,可带自定义参数
					pUIButton->setClickBeginCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::FXPBtnCallBack), (void*)2));
					pUIButton->setClickEndCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::FXPBtnCallBack), (void*)-1));
				}
			}
			pUICtrl = pFXPUI->QueryUICtrl("UICtrl9_Button");
			if(pUICtrl)
			{
				CUIButton*	pUIButton = dynamic_cast<CUIButton*>(pUICtrl);
				if(pUIButton)
				{
					//设置在按下时响应的回调函数,可带自定义参数
					pUIButton->setClickBeginCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::FXPBtnCallBack), (void*)3));
					pUIButton->setClickEndCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::FXPBtnCallBack), (void*)-1));
				}
			}
			//设置攻击按钮的回调
			pUICtrl = pFXPUI->QueryUICtrl("UICtrl21_Button");
			if(pUICtrl)
			{
				CUIButton*	pUIButton = dynamic_cast<CUIButton*>(pUICtrl);
				if(pUIButton)
				{
					//设置在按下时响应的回调函数,可带自定义参数
					pUIButton->setClickBeginCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::AttackBtnCallBack), (void*)1));
					pUIButton->setClickEndCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::AttackBtnCallBack), (void*)0));
				}
			}


增加两个回调函数:
//方向盘按钮回调
void HelloWorld::FXPBtnCallBack(CCNode* pSender, void* data)
{
	if(m_pMyTank)
	{
		int		nValue = (int)data;
		switch(nValue)
		{
		case 0:
			{//up
				m_pMyTank->SetDirKey(DK_UP);
			}
			break;
		case 1:
			{//right
				m_pMyTank->SetDirKey(DK_RIGHT);
			}
			break;
		case 2:
			{//down
				m_pMyTank->SetDirKey(DK_DOWN);
			}
			break;
		case 3:
			{//left
				m_pMyTank->SetDirKey(DK_LEFT);
			}
			break;
		default:
			{
				m_pMyTank->SetDirKey(DK_NONE);
			}
			break;
		}
	}
}
//攻击按钮回调
void HelloWorld::AttackBtnCallBack(CCNode* pSender, void* data)
{
	if(m_pMyTank)
	{
		if( 0 == data )
		{
			m_pMyTank->SetAttackKey(false);
		}
		else
		{
			m_pMyTank->SetAttackKey(true);
		}
	}
}

这样这个《坦克大战》就算基本完成了.我们再找一些背景音乐与音效加入到其中播放,最后编译运行一下,看着坦克勇猛的向前冲击,是否很带感呢?

 使用《红孩儿工具箱》开发基于Cocos2d-x的《坦克大战》游戏

      谢谢大家的学习,下一节《超级玛丽》再见!

分类: cocos2d, cocos2d-x 标签:

Cocos2d-x 3.0 开发(八)骨骼动画的动态换肤

2013年10月25日 没有评论

1、   概述

    游戏中人物的状态会发生改变,而这种改变通常要通过局部的变化来表现出来。比如获得一件装备后人物形象的改变,或者战斗中武器、防具的损坏等。这些变化的实现就要通过动态换肤来实现。在接下来的这个Demo中,点击屏幕会动态更换小人手中的武器。先上图:

 

Cocos2d-x 3.0 开发(八)骨骼动画的动态换肤

 

2、制作小人

    首先我们先制作一个UI小人,并将显示资源绑定到骨骼上。有不太明白的同学,可以看看:Cocos2d-x 3.0开发(六)使用cocoStudio创建一个骨骼动画。我随便找了些资源,做出来大概是这样:

Cocos2d-x 3.0 开发(八)骨骼动画的动态换肤

    接下来我们想这样一个问题。武器是拿在手里的,当手移动时,武器当然要跟着动。因此武器与手之间是有关系的,即武器的状态会受到手的影响而改变。这实际上就是父子关系。所幸cocoStudio中的骨骼支持这种操作,我们需要右击“武器骨骼”,在菜单中找到“绑定父关系”,点击后再点击它的父骨骼——我们的“手臂骨骼”即可。

Cocos2d-x 3.0 开发(八)骨骼动画的动态换肤

3、制作动画

    制作动画的过程就没什么需要特别注意的了,大家自己发挥就成。我在这里制作了一个名为“go”的动作。

    做完后将文件导出。

4、运行到程序

    运行脚本,创建一个新项目。将导出文件复制到Resource中。

    修改init的代码:

bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Layer::init() )
    {
        return false;
    }
    
    Size visibleSize = Director::getInstance()->getVisibleSize();
    Point origin = Director::getInstance()->getVisibleOrigin();

    ArmatureDataManager::getInstance()->addArmatureFileInfo("changeShape.ExportJson");

    Armature* arm = Armature::create("changeShape");
    arm->setPosition(Point(visibleSize.width/2,visibleSize.height/2));
    arm->setTag(1);
    addChild(arm);
    arm->getAnimation()->play("go");
  
    setTouchEnabled(true);
    return true;
}

    编译运行,应能够看到动画播放。

5、 添加与更换皮肤

     添加皮肤方式分动态添加与静态添加两种。动态添加是指在编辑器中更改,这样可以不需要重新编译程序。静态添加则是将其写死在程序中,通常这种做法不推荐,但我们也应该会使用。更换的做法是我们首先要取到具体的骨骼,然后更改它显示的“皮肤”。

    5.1动态添加

    在“动画编辑”的模式下选中我们的武器。然后将我们需要更换的武器资源依次拖动到“渲染资源”中。点开下拉列表,我们能看到添加过的资源,右击也可以删除。

Cocos2d-x 3.0 开发(八)骨骼动画的动态换肤

    添加好后,保存并导出。 

    在工程中,我创建了一个int的成员变量displayIndex。并在init中初始化为0。接下来我们加入点击响应来更换皮肤。

void HelloWorld::onTouchesEnded(const std::vector<Touch*>&  touches, Event* event)
{
    ++displayIndex;
    displayIndex = (displayIndex) % 4;
    ((Armature*)getChildByTag(1))->getBone("weapon")->changeDisplayByIndex(displayIndex, true);
}

    5.2静态添加

    使用静态添加有一点要注意:要保证创建的资源已经导出了。

    我们在init中添加:


//……
    Skin* skin = Skin::createWithSpriteFrameName("sliderProgress.png");
    arm->getBone("weapon")->addDisplay(skin, 4);
//……

    并将点击响应中循环量从4更改为5。

    编译运行即可看到效果。

6、总结

    创建皮肤分为动态和静态两种形式。动态是将相应的资源直接导出。静态则是要写到代码中进行创建。虽然这种也是动态创建,但相较于json配置,我还是倾向于称它为“静态”。叫什么不重要,哪种方便用哪种。换肤则都是要取到对于的骨骼,然后更改显示。

    Demo下载:http://download.csdn.net/detail/fansongy/6454703

    本篇博客出自阿修罗道,转载请注明出处,禁止用于商业用途http://blog.csdn.net/fansongy/article/details/13024265  

分类: 未分类 标签:

【cocos2d-x 手游研发—-精灵的八面玲珑】

2013年10月24日 没有评论

  继续上一篇文章继续聊吧,这章内容会比较多,也会附上代码,很多朋友加了群,大家在群里面探讨了很多东西,这让大家都觉得受益匪浅,这便是极好的,废话不多了,精灵是游戏的重要组成部分,那ARPG里面的精灵必然是要做得很细的,因为精灵要能动,能跑,能打,甚至要能做各种交互动作等等。

      大家可以看一下下面的题,是精灵制作的流程思路:

  【cocos2d-x 手游研发----精灵的八面玲珑】

 

  上图的人物素材来自于网络流传的梦幻西游,地图还有其他素材是以前公司同事制作的,如果游戏正式上线,会换一套完整的自制的素材。图中大家可以看到一个人物有很多部件组合而成,高端一点的游戏甚至部件多达几十上百种,甚至做出骨骼动画。不过以我现在的做法是按照帧动画方式实现各个人物的动作,人物的body部分细节处理会很多,还有大家看到图中的字体(字体渲染描边,我做的是最简单的了,两个LABEL合成在一起,效果能达到就行),由于人物要有方向,而且我们一次性就做8方向的人物动画,那就需要8个方向的连帧图片:

【cocos2d-x 手游研发----精灵的八面玲珑】

  首先,要组装起来我们首先要建一个实体角色类,这个类里面不放别的,就统一放角色的部件属性:

  

MainRoledata.h类
#ifndef _MAIN_ROLE_DATA_
#define _MAIN_ROLE_DATA_

#include "cocos2d.h"
#include "../Commen_ActionToDo.h"
#include "../Commen_Direction.h"
USING_NS_CC;

class MainRoledata
{
public :

    //人物的TAG
    int tags;
    //人物姓名
    CCString* spiritname;
    //人物初始坐标
    CCPoint nowpoint;
    //人物默认像素图
    CCString* spiritUrl;
    //人物站立像素图路径
    CCString* spiritUrl_zhan;
    //人物跑动像素图路径
    CCString* spiritUrl_pao;
    //人物攻击像素图路径
    CCString* spiritUrl_attack;
    //人物施法像素图路径/增加人物BUF
    CCString* spiritUrl_magic;
    //人物站立最大帧
    int maxcut_zhan;
    //人物跑动最大帧
    int maxcut_pao;
    //人物战斗最大帧
    int maxcut_attack;
    //人物施法最大帧
    int maxcut_magic;
    //人物当前动作
    Commen_ActionToDo acttodo;
    //人物当前朝向
    Commen_Direction dir;
    //动画时间
    float actiontime;
    
};

#endif

枚举几个方向,和动作的类:

enum Commen_Direction
{
    up=0,
    down=1,
    lefts=2,
    rigth=3,
    rigth_up=4,
    rigth_down=5,
    left_down=6,
    left_up=7
};
enum Commen_ActionToDo
{
    run=1,
    stand=2,
    attack=3,
    death=4,
    funny=5,
    magicup=6
};

OK,然后配置精灵数据,建了一个GetNPCData.cpp,GetNPCData.h,主要就是拿来初始化数据,大致的思路是要将上面的Model填充数据,相信大家

能够用很多种方式去实现,填充数据(读取XML配置文件,直接写在代码中配置);

接下来我们正式组装完整的八面玲珑的精灵,建立SpiritsPlayer.cpp,SpiritsPlayer.h;

 文件内容如下:

#ifndef _SPIRIT_PLAYER_
#define _SPIRIT_PLAYER_

#include "cocos2d.h"
#include "../Commen_ActionToDo.h"
#include "../Commen_Direction.h"
#include "../GameData/MainRoledata.h"
#include "../Commen/PublicShowUI.h"
#include "../Effects/EffectsCommen.h"

USING_NS_CC;

class SpiritsPlayer : cocos2d::CCSprite 
{
public: 
    
    CCSprite* npc;
    CCSprite* yinzi;
    CCSprite* sp_liaotianbd;

    PublicShowUI* p_ui_name;
    
    CCArray *stringArray;
    CCAnimate* playdonghua;
    CCAnimate* playdonghua2;
    Commen_Direction move_dir;

    bool endflag;
    bool endflag2;
    bool thiszhujiao_flag;


    void Spirits_talkabout_hid();

    SpiritsPlayer(MainRoledata roledata,int zOrder,bool zhujiaoflag);
    ~SpiritsPlayer(void);
    CCAnimation* getNowAnt(MainRoledata roledata);
    CCAnimate* updateNowAnt(MainRoledata roledata);
    void updateNpcPoint(CCPoint newpoint);
    void moveTomap_dir(CCPoint newpoint);
    void moveTomap_move(int uestime,CCPoint newpoint,bool npcflag);
    //人物移动完成的回调
    void moveoverCallBack(void);
    //普通NPC移动完成的回调
    void moveoverCallBackforNpc(void);
    //根据点击坐标获得人物的朝向
    Commen_Direction getNowPointDir(CCPoint newpoint);
    // 触摸点是否在精灵上
    bool isTouchInside(CCPoint thisPos);
    //移动方式
    void movemethod(int uestime,CCPoint newpoint);

private:
    //角色基本数据
    MainRoledata thisroledata;
    CCFiniteTimeAction *actall;
    CCActionInterval* act_moveto_zi;
    CCActionInterval* act_moveto_npc;
    CCActionInterval* act_moveto_yinzi;
    CCActionInterval* act_moveto_eff;
    CCActionInterval* act_moveto_eff_zhujiao;
    CCFiniteTimeAction *actbackfun; 
    int flag ;

private:
    CCRect rect();
    
 
};
#endif//_SPIRIT_PLAYER_


  
#include "../ImagePaths.h"
#include "../GameData/GetNPCData.h"
#include "../Commen/FontChina.h"

SpiritsPlayer::SpiritsPlayer(MainRoledata roledata,int zOrder,bool zhujiaoflag)
{

    //先初始化部分数据
    thisroledata = roledata;
    act_moveto_zi =NULL;
    act_moveto_npc =NULL;
    act_moveto_yinzi =NULL;
    actall=NULL;
    thiszhujiao_flag = zhujiaoflag;
    p_ui_name = new PublicShowUI();
    flag = 0;

    npc = SpiritsPlayer::create(roledata.spiritUrl->getCString());
    if(npc==NULL)
    {
        CCLog("图层路径有误,请检查路径");
        return;
    }
    //设置NPC初始位置坐标(该坐标取决于当前画层)
    npc->setPosition(roledata.nowpoint);
    //NPC动画设置
    playdonghua = SpiritsPlayer::updateNowAnt(roledata);
    npc->runAction(playdonghua);

    /**开始添加角色各部件**/
    //添加角色名称
    CCLabelTTF* label = CCLabelTTF::create(roledata.spiritname->getCString(), "微软雅黑",12);
    label->setColor(ccWHITE);
    label->setDirty(true);
    label->setPosition(ccp(npc->getContentSize().width/2,npc->getContentSize().height+6));

    CCLabelTTF* labelback = CCLabelTTF::create(roledata.spiritname->getCString(), "微软雅黑",12);
    labelback->setColor(ccBLACK);
    labelback->setDirty(true);
    labelback->setPosition(ccp(npc->getContentSize().width/2+1,npc->getContentSize().height+6-1));

    //添加NPC人物脚下阴影
    yinzi = CCSprite::create(p_yinzi);
    if(yinzi==NULL)
    {
        CCLog("图层路径有误,请检查路径");
        return;
    }
    if(zhujiaoflag==true)
    {
        yinzi->setPosition(ccp(npc->getContentSize().width/2,12));
    }
    else
    {
        yinzi->setPosition(ccp(npc->getContentSize().width/2,1));
    }

    npc->addChild(yinzi,-1,110);
    npc->addChild(label,2,111);
    npc->addChild(labelback,1,112);
    
}


cocos2d::CCRect SpiritsPlayer::rect()
{
    //获取精灵区域大小
    return CCRectMake(npc->getPositionX()- npc->getContentSize().width  * npc->getAnchorPoint().x,npc->getPositionY()-npc->getContentSize().height* npc->getAnchorPoint().y,npc->getContentSize().width, npc->getContentSize().height); 

}

bool SpiritsPlayer::isTouchInside(CCPoint thisPos)
{
    CCPoint localPos = thisPos;
    CCRect rc = rect();
    bool isTouched = rc.containsPoint(localPos);
    if (isTouched == true) {
        CCLog(FontChina::G2U("触发点击"));

    }else
    {
        CCLog(FontChina::G2U("未点击"));
    }
    return isTouched;
}



void SpiritsPlayer::Spirits_talkabout_hid()
{
    CCLog(FontChina::G2U("************调用了*****************"));
}


CCAnimate* SpiritsPlayer::updateNowAnt(MainRoledata roledata)
{
    //NPC动画
    CCAnimation* donghua = SpiritsPlayer::getNowAnt(roledata);
    if(roledata.actiontime>0)
    {
        donghua->setDelayPerUnit(roledata.actiontime/roledata.maxcut_zhan);
    }
    else  
    {
        donghua->setDelayPerUnit(2.0f/15.0f);//执行默认时间
    }
    donghua->setRestoreOriginalFrame(true);
    donghua->setLoops(-1);
    CCAnimate* playdonghua = CCAnimate::create(donghua);

    return playdonghua;
}


/*************
* 主角位移移动
*************/
void SpiritsPlayer::moveTomap_move(int uestime, CCPoint newpoint,bool npcflag)
{
    if(npcflag==true)
    {
        actbackfun = CCCallFunc::create(this, callfunc_selector(SpiritsPlayer::moveoverCallBackforNpc));
    }
    else
    {
        actbackfun = CCCallFunc::create(this, callfunc_selector(SpiritsPlayer::moveoverCallBack));
    }
    movemethod(uestime,newpoint);
}

void SpiritsPlayer::movemethod(int uestime,CCPoint newpoint)
{
    npc->stopAction(actall);
    act_moveto_npc = CCMoveTo::create(uestime,ccp(newpoint.x,newpoint.y+20));
    actall = CCSequence::create(act_moveto_npc,actbackfun,NULL);
    npc->runAction(actall);
}


/*************
* 改变移动方向
*************/
void SpiritsPlayer::moveTomap_dir(CCPoint newpoint)
{
    GetNPCData npcdata = GetNPCData();
    npcdata.GetNPCchapter1();
    move_dir=SpiritsPlayer::getNowPointDir(newpoint);
    npcdata.role_player.dir=move_dir;
    npcdata.role_player.acttodo = run;
    npcdata.role_player.actiontime=0.5;
    npc->stopAction(playdonghua);
    playdonghua = SpiritsPlayer::updateNowAnt(npcdata.role_player);
    npc->runAction(playdonghua);
}


/*************
* 根据点击坐标获得人物的朝向
*************/
Commen_Direction SpiritsPlayer::getNowPointDir(CCPoint newpoint)
{
    Commen_Direction thisdir = rigth_down; //默认为右下
    //计算移动数据
    float center_x,center_y,npc_x,npc_y;
    int move_x,move_y;
    //更新NPC方向,状态
    CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin();
    CCSize size = CCDirector::sharedDirector()->getWinSize();

    center_x = size.width/2;
    center_y = size.height/2;
    npc_x = npc->getPositionX();
    npc_y = npc->getPositionY();
     
    move_x =  (int)(npc_x -newpoint.x );
    move_y =  (int)(npc_y -newpoint.y - 20);

    if(move_x>=10&&move_y<=-10)
    {
        //左上
        thisdir = left_up;
    }
    else if(move_x>=10&&move_y>=10)
    {
        //左下
        thisdir = left_down;
    }
    else if(move_x<=-10&&move_y<=-10)
    {
        //右上
        thisdir = rigth_up;
    }
    else if(move_x<=-10&&move_y>=10)
    {
        //右下
        thisdir =rigth_down;
    }
    else if(move_x>-10&&move_x<10&&move_y>0)
    {
        //
        thisdir =down;
    }
    else if(move_x>-10&&move_x<10&&move_y<0)
    {
        //
        thisdir =up;
    }
    else if(move_x>0&&move_y>-10&&move_y<10)
    {
        //
        thisdir = lefts;
    }
    else if(move_x<0&&move_y>-10&&move_y<10)
    {
        //
        thisdir =rigth;
    }
    return thisdir;
}

/*************
* 移动完成后的回调
*************/
void SpiritsPlayer::moveoverCallBack()
{
    //移动完成之后恢复站立状态
    GetNPCData npcdata = GetNPCData();
    npcdata.GetNPCchapter1();
    npcdata.role_player.dir=move_dir;
    npcdata.role_player.acttodo = stand;
    npcdata.role_player.actiontime=1.1f;
    npc->stopAction(playdonghua);
    playdonghua = SpiritsPlayer::updateNowAnt(npcdata.role_player);
    npc->runAction(playdonghua);
}

/*************
* 普通NPC移动完成后的回调
*************/
void SpiritsPlayer::moveoverCallBackforNpc()
{

}

/*************
* 点击瞬移至此
*************/
void SpiritsPlayer::updateNpcPoint(CCPoint newpoint)
{
    p_ui_name->updataGameText(ccp(newpoint.x,newpoint.y+npc->getContentSize().height/2+10));
    npc->setPosition(newpoint);
    yinzi->setPosition(ccp(newpoint.x,newpoint.y-npc->getContentSize().height/2+5));
}


/*********************
* 八方向人物动作合成器
*********************/
CCAnimation* SpiritsPlayer::getNowAnt(MainRoledata roledata)
{
    CCAnimation* thisdonghua = CCAnimation::create();
    switch (roledata.dir)
    {
    case up:

        switch (roledata.acttodo)
        {
        case run:
            for(int i = 0; i<=roledata.maxcut_pao ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s06%03d.png",roledata.spiritUrl_pao->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case stand:
            for(int i = 0; i<=roledata.maxcut_zhan ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s06%03d.png",roledata.spiritUrl_zhan->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case attack:
            break;
        case death:
            break;
        case funny:
            break;
        default:
            break;
        }

        break;
    case down:
        switch (roledata.acttodo)
        {
        case run:
            for(int i = 0; i<=roledata.maxcut_pao ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s04%03d.png",roledata.spiritUrl_pao->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case stand:
            for(int i = 0; i<=roledata.maxcut_zhan ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s04%03d.png",roledata.spiritUrl_zhan->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case attack:
            break;
        case death:
            break;
        case funny:
            break;
        default:
            break;
        }
        break;
    case lefts:
        switch (roledata.acttodo)
        {
        case run:
            for(int i = 0; i<=roledata.maxcut_pao ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s05%03d.png",roledata.spiritUrl_pao->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case stand:
            for(int i = 0; i<=roledata.maxcut_zhan ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s05%03d.png",roledata.spiritUrl_zhan->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case attack:
            break;
        case death:
            break;
        case funny:
            break;
        default:
            break;
        }
        break;
    case rigth:
        switch (roledata.acttodo)
        {
        case run:
            for(int i = 0; i<=roledata.maxcut_pao ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s07%03d.png",roledata.spiritUrl_pao->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case stand:
            for(int i = 0; i<=roledata.maxcut_zhan ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s07%03d.png",roledata.spiritUrl_zhan->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case attack:
            break;
        case death:
            break;
        case funny:
            break;
        default:
            break;
        }
        break;
    case rigth_up:
        switch (roledata.acttodo)
        {
        case run:
            for(int i = 0; i<=roledata.maxcut_pao ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s03%03d.png",roledata.spiritUrl_pao->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case stand:
            for(int i = 0; i<=roledata.maxcut_zhan ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s03%03d.png",roledata.spiritUrl_zhan->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case attack:
            for(int i = 0; i<=roledata.maxcut_attack ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s03%03d.png",roledata.spiritUrl_attack->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case magicup:
            for(int i = 0; i<=roledata.maxcut_magic ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s03%03d.png",roledata.spiritUrl_magic->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case death:
            break;
        case funny:
            break;
        default:
            break;
        }
        break;
    case rigth_down:
        switch (roledata.acttodo)
        {
        case run:
            for(int i = 0; i<=roledata.maxcut_pao ; i++)
            {
                char donghuaurl[1000] = {0};
                sprintf(donghuaurl,"%s00%03d.png",roledata.spiritUrl_pao->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case stand:
            for(int i = 0; i<=roledata.maxcut_zhan ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s00%03d.png",roledata.spiritUrl_zhan->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case attack:
            for(int i = 0; i<=roledata.maxcut_attack ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s00%03d.png",roledata.spiritUrl_attack->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case magicup:
            for(int i = 0; i<=roledata.maxcut_magic ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s00%03d.png",roledata.spiritUrl_magic->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case death:
            break;
        case funny:
            break;
        default:
            break;
        }
        break;
    case left_down:
        switch (roledata.acttodo)
        {
        case run:
            for(int i = 0; i<=roledata.maxcut_pao ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s01%03d.png",roledata.spiritUrl_pao->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case stand:
            for(int i = 0; i<=roledata.maxcut_zhan ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s01%03d.png",roledata.spiritUrl_zhan->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case attack:
            for(int i = 0; i<=roledata.maxcut_attack ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s01%03d.png",roledata.spiritUrl_attack->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case magicup:
            for(int i = 0; i<=roledata.maxcut_magic ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s01%03d.png",roledata.spiritUrl_magic->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case death:
            break;
        case funny:
            break;
        default:
            break;
        }
        break;
    case left_up:
        switch (roledata.acttodo)
        {
        case run:
            for(int i = 0; i<=roledata.maxcut_pao ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s02%03d.png",roledata.spiritUrl_pao->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case stand:
            for(int i = 0; i<=roledata.maxcut_zhan ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s02%03d.png",roledata.spiritUrl_zhan->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case attack:
            for(int i = 0; i<=roledata.maxcut_attack ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s02%03d.png",roledata.spiritUrl_attack->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case magicup:
            for(int i = 0; i<=roledata.maxcut_magic ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s02%03d.png",roledata.spiritUrl_magic->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case death:
            break;
        case funny:
            break;
        default:
            break;
        }
        break;
    default:
        break;
    }

    return thisdonghua;
}

SpiritsPlayer::~SpiritsPlayer(void)
{
}

sprintf(donghuaurl,”%s06%03d.png”,roledata.spiritUrl_pao->getCString(),i);

总体思路就是,我们通过了帧连接的拼接来构成动画,通过我们之前写好的model数据来定义我们任务的朝向等问题

比如00000代表右,01000就代表右上,这个也得根据自己素材的模型来写不同的处理逻辑,万变不离其中;

如果我们的图片是在一张大图的集合中,我们可以同过CCRect来处理获取帧图片方式!

CCAnimation* getNowAnt(MainRoledata roledata);
CCAnimate* updateNowAnt(MainRoledata roledata);

通过这两个方法集合,我们就能获取到八面玲珑的朝向,甚至我们连,后续的动画机制也加入了,比如,跑动,打斗,做各种动作等!

Commen_Direction SpiritsPlayer::getNowPointDir(CCPoint newpoint); //获取朝向

void SpiritsPlayer::moveTomap_dir(CCPoint newpoint) //改变朝向方法

以上两个方法,我们是专门处理,之后精灵移动,移动完之后还得恢复到站立状态,移动过程中改变人物朝向问题!所以要考虑的很清楚

很清晰,思路清楚了,才能方便以后的拓展。

OK了,有了以上的精灵类,将其实例化到一个简单的图片地图上,他就会动了;

添加的时候我们直接addchild方法!!

SpiritsPlayer* role_main = new SpiritsPlayer(basedatas->role_player,1,false);

nowmap->addChild(role_main->npc, 999);

nowmap 暂且可以用一张图片CCSprite代替!!!

下一篇博客我会非常详细的将地图制作方法写出来给大家一起分享,由于其实我之前都是C#,JAVA做的很多,很多地方和细节还需要重构

还需要大家多指点一下,这也是我学习的好机会;

 

游戏demo及素材下载地址(demo里面包含了所有的素材资料);

http://pan.baidu.com/share/link?shareid=4012433582&uk=4097703620&third=15

 

ps:广告我建了一个QQ群:和大家一起分享cocos2dx开发经验【41131516】

 

分类: 未分类 标签:

cocos2d CCLayer.m UIAccelerometer is deprecated warning

2013年10月23日 没有评论

I’m not actually using the accelerometer in my app, but this warning is the only one I get during the build process. I’d like to correct it or eliminate it. This warning directs me to the CCLayer.m cocos2d original files. There a 4 warnings about the UIAccelerometer deprecations.

(LINES 91 & 93 is where the warnings start) ! UIAccelerometer is deprecated in iOS 5.0 – UIAccelerometer has been replaced by the CoreMotion framework

    85:-(void) setIsAccelerometerEnabled:(BOOL)enabled
    86:{
    87: if( enabled != isAccelerometerEnabled_ ) {
    88:     isAccelerometerEnabled_ = enabled;
    89:     if( isRunning_ ) {
    90:         if( enabled )
    91:             [[UIAccelerometer sharedAccelerometer] setDelegate:self];
    92:         else
    93:             [[UIAccelerometer sharedAccelerometer] setDelegate:nil];
    94:     }
    95: }
    96:}

When I dig deeper into the warnings it brings me deeper into the UIAccelerometer.h UIKit

Have others had this problem? How to deal with it? Should I just ignore it? Any suggestions would be greatly appreciated. Thanks, Justin

Cocos2d-iphone as of version 2.1 has not been updated to be fully compatible with iOS 7.

As far as accelerometer is concerned you can safely comment out all references to / uses of UIAccelerometer. If you do need accelerometer in your app use CMMotionManager.

Alternatively you can use Kobold2D whose github version includes the necessary fixes to cocos2d-iphone.

I’m personally not the biggest fan of how Kobold2d “solved” the issue. They’re wrapping the code with pragmas that disable the warning. Personally, I’d prefer to offer that it’s better to replace the deprecated code with CoreMotion class.

To maintain the usability of a singleton class; use this answer: How do I initialize CMMotionManager globaly to be used by different classes?
then in CCLayer.h at line 32 import MotionManagerSingleton.h
Next in CCLayer.m replace everything that looks like UIAccelerometer with the new call to this class and it works quite well.

分类: cocos2d, stackoverflow精选 标签:

cocos2d CCLayer.m UIAccelerometer is deprecated warning

2013年10月23日 没有评论

I’m not actually using the accelerometer in my app, but this warning is the only one I get during the build process. I’d like to correct it or eliminate it. This warning directs me to the CCLayer.m cocos2d original files. There a 4 warnings about the UIAccelerometer deprecations.

(LINES 91 & 93 is where the warnings start) ! UIAccelerometer is deprecated in iOS 5.0 – UIAccelerometer has been replaced by the CoreMotion framework

    85:-(void) setIsAccelerometerEnabled:(BOOL)enabled
    86:{
    87: if( enabled != isAccelerometerEnabled_ ) {
    88:     isAccelerometerEnabled_ = enabled;
    89:     if( isRunning_ ) {
    90:         if( enabled )
    91:             [[UIAccelerometer sharedAccelerometer] setDelegate:self];
    92:         else
    93:             [[UIAccelerometer sharedAccelerometer] setDelegate:nil];
    94:     }
    95: }
    96:}

When I dig deeper into the warnings it brings me deeper into the UIAccelerometer.h UIKit

Have others had this problem? How to deal with it? Should I just ignore it? Any suggestions would be greatly appreciated. Thanks, Justin

Cocos2d-iphone as of version 2.1 has not been updated to be fully compatible with iOS 7.

As far as accelerometer is concerned you can safely comment out all references to / uses of UIAccelerometer. If you do need accelerometer in your app use CMMotionManager.

Alternatively you can use Kobold2D whose github version includes the necessary fixes to cocos2d-iphone.

I’m personally not the biggest fan of how Kobold2d “solved” the issue. They’re wrapping the code with pragmas that disable the warning. Personally, I’d prefer to offer that it’s better to replace the deprecated code with CoreMotion class.

To maintain the usability of a singleton class; use this answer: How do I initialize CMMotionManager globaly to be used by different classes?
then in CCLayer.h at line 32 import MotionManagerSingleton.h
Next in CCLayer.m replace everything that looks like UIAccelerometer with the new call to this class and it works quite well.

分类: cocos2d, stackoverflow精选 标签:

Cocos2d-x 3.0 开发(七)在程序中处理cocoStudio导出动画

2013年10月22日 没有评论

1、概述

    使用cocoStudio可以方便的制作动画,接下来的工作就是在我们的程序中使用制作的动画。这篇中,我将使用程序将两个动画连接起来。有图有真相:

 

Cocos2d-x 3.0 开发(七)在程序中处理cocoStudio导出动画

 

2、制作动画

    承接上一篇,我们再制作一个动画。制作动画的方法与之前没有差别,不太熟悉的同学可以看:Cocos2d-x 3.0开发(六)使用cocoStudio创建一个骨骼动画。在“动作列表”中右击,“添加动画”然后编辑就成。

    我们新制作的动画的结束点,要与上一篇中制作动画的开始点重合,这样在连接的时候,画面就不会跳动。


Cocos2d-x 3.0 开发(七)在程序中处理cocoStudio导出动画

    制作好后我们将动画导出。

 

3、制作UI

    既然能够方便的制作UI,我就顺手做了一个控制动画播放的UI。制作方法之前也提到过。没有什么差别。使用UI编辑器制作UI,并将其导出。

Cocos2d-x 3.0 开发(七)在程序中处理cocoStudio导出动画

4、关联到项目

    运行脚本创建我们的项目,将导出的动画、UI放到Resource文件夹中。

    然后重写init方法:

bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Layer::init() )
    {
        return false;
    }
    
    Size visibleSize = Director::getInstance()->getVisibleSize();
    Point origin = Director::getInstance()->getVisibleOrigin();

	auto ui = dynamic_cast<Layout*>(CCUIHELPER->createWidgetFromJsonFile("ControlUI.ExportJson"));
	ui->getChildByTag(UI_BUTTON_PLAY1)->addTouchEventListener(this, toucheventselector(HelloWorld::touchCallBack));
	ui->getChildByTag(UI_BUTTON_PLAY2)->addTouchEventListener(this, toucheventselector(HelloWorld::touchCallBack));
	ui->getChildByTag(UI_BUTTON_CONN)->addTouchEventListener(this, toucheventselector(HelloWorld::touchCallBack));
	ui->getChildByTag(UI_BUTTON_DISCONN)->addTouchEventListener(this, toucheventselector(HelloWorld::touchCallBack));

	auto uiLayer = UILayer::create();
	uiLayer->addWidget(ui);
	this->addChild(uiLayer);

    return true;
}

void HelloWorld::touchCallBack(Object* obj,TouchEventType type)
{
	//will play 
}

5、加载动画

    动画的导出文件也是一个json。载入后被封装到一个Armature对象中。Armature是NodeRGBA的子类,所以它可以直接被addChild到父节点中。加载所用的是ArmatureManager中的方法。它是一个单例,管理整个场景中的Armature。我们在编辑器中编辑的动画是Animation,它被封装在Armature中了。因此这是一个三层的结构。ArmatureManager最大,然后是Armature,最后是Animation。我们播放动画用的都是Animation中的方法。

    说完了原理,我们来看看代码。首先在init中添加加载Armature。

ArmatureDataManager::getInstance()->addArmatureFileInfo("MyAnimation.ExportJson");
Armature* armature = Armature::create("MyAnimation");
armature->setTag(AM_MYANIMATION);	

armature->setPosition(Point(origin.x + visibleSize.width/2 ,
                                origin.y + visibleSize.height/2));
this->addChild(armature);

    然后重写touchCallback方法控制播放动画。

void HelloWorld::touchCallBack(Object* obj,TouchEventType type)
{
	auto uiBt = dynamic_cast<UIButton*>(obj);
	if(!uiBt)
	{
		return;
	}
	int tag = uiBt->getTag();
	auto armature = (Armature*)getChildByTag(AM_MYANIMATION);
	switch (type)
	{
	case TouchEventType::TOUCH_EVENT_ENDED:
			if(tag == UI_BUTTON_PLAY1)
			{
				armature->getAnimation()->play("hit");
			}
			else if(tag ==UI_BUTTON_PLAY2)
			{
				armature->getAnimation()->play("fall");
			}
			else if(tag == UI_BUTTON_CONN)
			{
				//will conn
			}
			else if(tag == UI_BUTTON_DISCONN)
			{
				//will dis conn
			}
			break;
	default:
		break;
	}
}

6、处理动画事件

    在Animation中有动画事件的概念,每一个动画开始和结束都会事件。我们需要做的就是监听这个事件并为其写好响应函数。

    所以接下来我们完善touchCallback函数,并添加一个监听函数。


//......
else if(tag == UI_BUTTON_CONN)
{
	armature->getAnimation()->setMovementEventCallFunc(this,movementEvent_selector(HelloWorld::movementCallback));
}
else if(tag == UI_BUTTON_DISCONN)
{
	armature->getAnimation()->setMovementEventCallFunc(this,nullptr);
}
//......

void HelloWorld::movementCallback(Armature * armature, MovementEventType type, const char * name)
{
	if (type == COMPLETE)
	{
		if (strcmp(name,"fall") == 0)
		{
			Armature* arm = (Armature*) getChildByTag(AM_MYANIMATION);
			arm->getAnimation()->play("hit");
		}
	}
}

    编译运行,就可以看到动画连接起来了。

 

7、总结

    通过ArmatureDataManager单例来加载动画,将其关联到程序中。动画事件的监听,对动画的行为进行处理。使用这些方法我们可以灵活的使用cocoStudio创建的动画了。

 

    Demo下载:http://download.csdn.net/detail/fansongy/6439225



    本篇博客出自阿修罗道,转载请注明出处,禁止用于商业用途http://blog.csdn.net/fansongy/article/details/12955989 


分类: 未分类 标签:

【cocos2d-x 手游研发—-研发思路及感想】

2013年10月22日 没有评论

      我半年前进入了目前的这家做教育行业的公司(在此之前一直从事原生态开发手游的迷茫之路),学习是一件很快乐的事情,来到这家公司我有了很多时间去学习,不管是公司业务,还是其他技术相关的。于是开始了cocos2dx的研发之路,觉得此路不在迷茫。通过对cocos2dx的了解,一步一步去实践,几乎现在每天下班回家,都会继续研究这方面的知识。我曾经研发过页游,和大型的RPG手游的一些经验,首先说游戏类型,如果我现在要做我绝对不会做MMORPG,虽然自己也能做一些服务器端得东西。目前纵观市场上我自认为比较经典的也就几款:忘仙,神雕侠侣,逆天仙魔录,这些都属于在上系上属于大型的客户端手机网游,无论从画风,技术层面,都还是算得上精品的;当然很多开发会觉得涉足这种大型客户端网游会耗费很多时间精力,而且必须是大团队来完成。实际上我们如果换一个角度去尝试,这些问题,应该是可以克服的。

      下面正式说一下,我们的主题:ARPG(即时战斗类游戏)这类游戏往往注重打击感,地图场景的处理,更深层次一点就是怪物,技能,人物,场景很连贯的处理。要能很完美的去处理好这些层次关系需要我们搭建出副很健壮的骨架,当然大部分人都会说分层,然后MVC框架就出来了,实际上我研发的时候没有刻意去分层,是必然的自由形成依赖关系根据cocos2dx的特性和包含关系,我做的ARPG的框架思路是这样的:

【cocos2d-x 手游研发----研发思路及感想】

 

 

首先以世界为中心点,然后以地图展开的思路,我们将主角,NPC,怪物都添加到地图上,然后通过主从移动的依赖关系,来处理其中的细节!

由于很久不写博客,语言很生硬,话语不生动,不过还是请大家相信我,能做出一款比较正式的ARPG手游,下面先附上成品的一部分截图;

下面晚上正式给大家一步步介绍具体研发过程,会附上部分源码,下一节主要讲精灵系统,然后再是地图系统!

 

【cocos2d-x 手游研发----研发思路及感想】

 

【cocos2d-x 手游研发----研发思路及感想】

 

 

我建了一个QQ群:和大家一起分享cocos2dx开发经验【41131516】

分类: 未分类 标签:

『HTML5梦幻之旅』-滚动播放的幻灯片效果

2013年10月20日 没有评论

很久又没写博客了,一者是因为要上课,平时没有什么零碎的时间研究新东西;二者最近在开发一款塔防游戏,有整块的时间都拿去开发这个项目了。因此最近没有什么好的东西和大家分享。这周末看在游戏快完工的份上,抽出了半天的时间研究了一下html5幻灯片制作,效果还不错,展示如下:

首先是一张《真三国无双7》关云长的头像

『HTML5梦幻之旅』-滚动播放的幻灯片效果

其次是《真三国无双7》貂蝉的头像

『HTML5梦幻之旅』-滚动播放的幻灯片效果

切换时的效果

『HTML5梦幻之旅』-滚动播放的幻灯片效果

怎么样?效果还是很不错,对吧~

测试链接:http://www.cnblogs.com/yorhom/articles/html5_dream_trip_slideshowsample1.html

接下来就来讲一讲制作过程。

一,准备工作

首先,需要下载html5开源引擎lufylegend.js,它是完全免费+开源的,可以下载下来学习一下。

下载地址:http://lufylegend.com/lufylegend

API文档:http://lufylegend.com/lufylegend/api

二,设计原理

要搞清楚本次开发的原理,我们不妨先画一张示意图:

『HTML5梦幻之旅』-滚动播放的幻灯片效果

假设绿色方框内的区域为显示区域,那么,我们可以用到lufylegend中的mask的方法来将整个红色方框裁减为绿色方框的大小,这时,我们只能看到A区域,而B,C被遮盖住了。然后如果我们要显示B区域,我们可以直接将红框区域往左移动一格的距离,绿色区域不动,就能将B显示出来了,就像放映机一样。

但是如果一直播放下去,那么我们播放到C区域时,红色区域再往左移动,那就会显示为空白。解决措施是又移回A区域,但是出现的问题又在于红色区域如果要移到A处,那应该右移动才能到达,但是我们要的是红色区域往左移动,因此就不能直接移回。那该怎么办呢?我想的解决措施就是把最前面的那个区域移动到最后的那个区域的右边。如果是刚才那种情况,那就让A接到C后方,这时候,A变成了最后一个。这时红色区域又往左移动时,取到的就是B,然后将B移动到A后面。

当然,上面我们只提到了向左移动时的处理方式,其实向右也是一样的,只是取的是最后一个区域,然后把这个区域移动到第一个区域的前面罢了。

光说原理只能算是纸上谈兵,接下来就来看看代码。

三,代码讲解

首先我们为了方便起见,建立一个LSlideshowSample1类,因为以后万一要拓展别的幻灯片效果,所以就在这个类的名字后面写上了"Sample1”,名字只是一种代号,我们主要看代码,构造器如下:

function LSlideshowSample1(width,height,isLoop,loopData){
	var self = this;
	base(self,LSprite,[]);
	
	self._slideshowList = new Array();
	self._slideshowIndex = 0;
	self._currentIndex = self._slideshowIndex;
	
	self.rangeWidth = width;
	self.rangeHeight = height;
	self.isLoop = isLoop;
	
	self._toX = 0;
	
	self.nextChildList = new Array();
	self.previousChildList = new Array();
	
	self.borderLayer = new LSprite();
	self.borderLayer.graphics.drawRect(0,"",[0,0,width,height],true,"transparent");
	
	self.contentLayer = new LSprite();
	self.contentLayer.mask = self.borderLayer;
	self.addChild(self.contentLayer);
	
	if(self.isLoop == true){
		self.loopData = loopData;
		self.frameIndex = 0;
		self.maxFrame = Math.floor(self.loopData.delay*1000/LGlobal.speed);
		self.addEventListener(LEvent.ENTER_FRAME,self.loopPlay);
	}
}

这个类有4个参数,意思分别是:[显示宽度,显示高度,是否自动播放,自动播放方式],如果第三个参数填false,就可以不用添第4个参数。

接下来解释一下构造器中的代码:
self._slideshowList原先是用来装每一帧的数据的,后来直接在LSprite的childList中取了,所以它没有什么用,直接忽略掉。

接下来看_slideshowIndex属性,这个属性很怪异,主要用于取出要显示的帧,以后慢慢解释。_currentIndex是用来表示显示位置的,如果往左移动,表示显示位置增加1格,往右移动,表示显示位置减少1格。

self.rangeWidth,self.rangeHeight,self.isLoop是将参数存放起来的属性,后面会用到的。

然后是self._toX,这个表示要移动到的位置,和_currentIndex联用,以后也会提到,到时候慢慢讲。接下来是self.nextChildList和self.previousChildList属性,这两个属性和_slideshowIndex联用,_slideshowIndex主要负责作为这两个数组的取值下标。接下来,我们初始化显示层:

self.borderLayer = new LSprite();
self.borderLayer.graphics.drawRect(0,"",[0,0,width,height],true,"transparent");
	
self.contentLayer = new LSprite();
self.contentLayer.mask = self.borderLayer;
self.addChild(self.contentLayer);

borderLayer等于上面我们讲解原理时的绿色区域,contentLayer代表红色区域。borderLayer要作为contentLayer的遮罩,因此在contentLayer中写道:

self.contentLayer.mask = self.borderLayer;

然后进入判断是否自动播放:

if(self.isLoop == true){
	self.loopData = loopData;
	self.frameIndex = 0;
	self.maxFrame = Math.floor(self.loopData.delay*1000/LGlobal.speed);
	self.addEventListener(LEvent.ENTER_FRAME,self.loopPlay);
}

代码很简单,值得注意的是,我们实例化构造器时,第4个参数是一个json对象,如下格式:

{delay:每帧停留时间(单位s),order:出现时滚动的样式(向右或者向左)}

ok,构造器就讲完了,接下来看看设置帧的setFrame:

LSlideshowSample1.prototype.setFrame = function(o){
	var self = this;
	var cl = self.contentLayer.childList;
	o.x = self.contentLayer.childList.length * self.rangeWidth;
	o.y = 0;
	self.contentLayer.addChild(o);
	
	self._sortChild();
};

这个函数也有一个参数,是一个LDisplayObject对象。其中代码很简单,就是将加入的这一帧放到最后面。其中调用到一个_sortChild函数,这个函数十分重要,如下:

LSlideshowSample1.prototype._sortChild = function(){
	var self = this;
	
	self.nextChildList = new Array();
	self.previousChildList = new Array();
	var duplicate = new Array();
	for(var i=0; i<self.contentLayer.childList.length; i++){
		self.nextChildList.push(i);
		duplicate.push(i);
	}
	self.nextChildList = self.nextChildList.sort();
	duplicate.splice(0,1);
	var sortedList = duplicate.sort(function(a,b){
		return b - a;
	});
	self.previousChildList.push(0);
	for(var key in sortedList){
		self.previousChildList.push(sortedList[key]);
	}
};

我们能看到,主要是对self.nextChildList和self.previousChildList两个属性进行操作。也许有朋友不理解为什么要将他们排续,这个就设计到显示时的原理,还是用刚才那张图来讲解:
『HTML5梦幻之旅』-滚动播放的幻灯片效果

我们加入ABC后,A在contentLayer中的成员序号是0,B是1,C是2。在self.nextChildList我们将其加入先后顺序排列为[0,1,2],如果我们要显示B区域,那我们就要在contentLayer中取B对象,B对象的序号是1,因此我们要将显示位置往后调一格,于是self._slideshowIndex+=1; self._currentIndex += 1; 然后通过self._slideshowIndex的值来取出contentLayer的成员列表中对应的对象,正好self._slideshowIndex初始值是0,+1后变为1,取出的刚好是B对象。

但是,我们的红色区域如果往右移动时,按理说要显示最后的那一个对象C,C的序号是2,但是self._slideshowIndex – 1 != 2,因此我们要在新建self.previousChildList这个属性来确保往右移动时,能正常地取出对象的序号,因此我们对self.previousChildList里的成员排列做了特殊处理,将其变成了[0,2,1]。但是还是不对啊,self._slideshowIndex – 1 = -1时,应该显示C对象,但是C对象序号在self.previousChildList中对应的下标是1不是-1,怎么办呢?其实很简单,取的时候用self._slideshowIndex的绝对值就ok啦。

比如说我们F5一下界面,回到第一格的位置,然后,我们将红色部分向有移动一格,self._slideshowIndex -= 1; self._currentIndex -= 1; 现在按理说要显示C,这时我们在self.previousChildList中用self._slideshowIndex的绝对值取出要找的对象,self._slideshowIndex这时的值是-1,绝对值就是1,在[0,2,1]中对应的值正好是2,也就是C在contentLayer的成员列表中对应的序号,然后用这个序号取出contentLayer的成员列表中对应的成员,位置移动到响应的地方,然后将其通过LTweenLite缓动类缓缓地显示出来,达到想要的效果。

有了上面的介绍,我们就来看看显示部分的代码next()和previous():

LSlideshowSample1.prototype.next = function(){
	var self = this;
	
	self._currentIndex += 1;
	self._slideshowIndex += 1;
	
	if(self._slideshowIndex >= self.contentLayer.childList.length){
		self._slideshowIndex = 0;
	}
	
	if(self._slideshowIndex < 0){
		var obj = self.contentLayer.getChildAt(self.previousChildList[Math.abs(self._slideshowIndex)]);
	}else{
		var obj = self.contentLayer.getChildAt(self.nextChildList[Math.abs(self._slideshowIndex)]);
	}
	obj.x = self.rangeWidth*self._currentIndex;
			
	self._toX = -(self._currentIndex*self.rangeWidth);
	
	var tweenObj = LTweenLite.to(self.contentLayer,1,{
		x:self._toX
	});
};
LSlideshowSample1.prototype.previous = function(){
	var self = this;
	
	self._currentIndex -= 1;
	self._slideshowIndex -= 1;
	
	if(self._slideshowIndex < -(self.contentLayer.childList.length-1)){
		self._slideshowIndex = 0;
	}
	
	if(self._slideshowIndex < 0){
		var obj = self.contentLayer.getChildAt(self.previousChildList[Math.abs(self._slideshowIndex)]);
	}else{
		var obj = self.contentLayer.getChildAt(self.nextChildList[Math.abs(self._slideshowIndex)]);
	}
	obj.x = self.rangeWidth*self._currentIndex;
		
	self._toX = -(self._currentIndex*self.rangeWidth);
	
	var tweenObj = LTweenLite.to(self.contentLayer,1,{
		x:self._toX
	});
};

其中用到了LTweenLite,这个是Lufylegend中给的一个用于实现缓动的类,具体方法请移步API文档。

还有一个getChildAt,这是lufylegend中LSprite类的一个成员函数,用于取出参数值在LSprite成员列表中相应的对象。

这个类在设计时为了方便大家使用,还提供了showFrameAt函数,可以直接跳到播放某一帧:

LSlideshowSample1.prototype.showFrameAt = function(index,order){
	var self = this;
	if(self._slideshowIndex < 0){
		if(self.previousChildList[Math.abs(self._slideshowIndex)] == index)return;
	}else{
		if(self.nextChildList[Math.abs(self._slideshowIndex)] == index)return;
	}
	if(order == LSlideshow.LEFTWARD){
		self._currentIndex -= 1;
	}else if(order == LSlideshow.RIGHTWARD){
		self._currentIndex += 1;
	}else{
		self._currentIndex += 1;
	}
	self._slideshowIndex = index;
	
	var obj = self.contentLayer.getChildAt(index);
	obj.x = self.rangeWidth*self._currentIndex;
	
	self._toX = -(self._currentIndex*self.rangeWidth);
	
	var tweenObj = LTweenLite.to(self.contentLayer,1,{
		x:self._toX
	});
};

在上面也提到过自动播放这个功能,我们不妨温习一下调用自动播放的地方:

if(self.isLoop == true){
	self.loopData = loopData;
	self.frameIndex = 0;
	self.maxFrame = Math.floor(self.loopData.delay*1000/LGlobal.speed);
	self.addEventListener(LEvent.ENTER_FRAME,self.loopPlay);
}

在时间轴事件ENTER_FRAME中,我们调用了loopPlay函数,这个函数的代码如下:

LSlideshowSample1.prototype.loopPlay = function(self){
	if(self.contentLayer.childList.length == 0)return;
	if(self.frameIndex++ < self.maxFrame)return;
	self.frameIndex = 0;
	if(self.loopData.order == LSlideshow.RIGHTWARD){
		self.next();
	}else if(self.loopData.order == LSlideshow.LEFTWARD){
		self.previous();
	}else if(self.loopData.order == LSlideshow.RANDOM){
		var index = Math.floor(Math.random()*(self.contentLayer.childList.length-1));
		var order = Math.random() > 0.5 ? LSlideshow.LEFTWARD : LSlideshow.RIGHTWARD;
		self.showFrameAt(index,order);
	}
};

其中我们能看到了LSlideshow.RIGHTWAR,LSlideshow.LEFTWARD,LSlideshow.RANDOM这几种播放方式,它们分别是在LSlideshow静态类中得到定义的。如下:

var LSlideshow = function(){throw "LSlideshow cannot be instantiated";};
LSlideshow.type = "LSlideshow";
LSlideshow.RIGHTWARD = "rightward";
LSlideshow.LEFTWARD = "leftward";
LSlideshow.RANDOM = "random";

LSlideshow.RIGHTWAR代表向右滚动,LSlideshow.LEFTWARD代表向左滚动,LSlideshow.RANDOM代表随机滚动。

最后还加了一个getFrameIndex的函数,是用于取当前帧的序号的:

LSlideshowSample1.prototype.getFrameIndex = function(){
	var self = this;
	if(self._slideshowIndex < 0){
		var v = self.previousChildList[Math.abs(self._slideshowIndex)];
	}else{
		var v = self.nextChildList[Math.abs(self._slideshowIndex)];
	}
	return v;
};

ok,幻灯片类就搞定了~只要搞清楚原理,其实还是挺简单的,不是吗?接下来是使用举例:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>幻灯片效果</title>
</head>
<body>
<div id="mylegend">loading......</div>
<script type="text/javascript" src="./lufylegend-1.8.0.min.js"></script>
<script type="text/javascript" src="./lufylegend.ui-0.2.0.min.js"></script>
<script type="text/javascript" src="./LSlideshow.js"></script>
<script type="text/javascript" src="./LSlideshowSample1.js"></script>
<script>
init(50,"mylegend",980,609,main);
var backLayer,slideshowObj;
var loadData = [
	{name:"caocao",path:"./images/cao_cao.jpg"},
	{name:"diaochan",path:"./images/diao_chan.jpg"},
	{name:"guanyu",path:"./images/guan_yu.jpg"},
	{name:"zhaoyun",path:"./images/zhao_yun.jpg"},
	{name:"sunshangxiang",path:"./images/sun_shang_xiang.jpg"}
];
var datalist = {};
function main(){
	LStage.setDebug(true);
	if(LGlobal.canTouch){
		LGlobal.stageScale = LStageScaleMode.EXACT_FIT;
		LSystem.screen(LStage.FULL_SCREEN);
	}
	
	var loadingLayer = new LoadingSample5();
	addChild(loadingLayer);
	
	LLoadManage.load(
		loadData,
		function(p){
			loadingLayer.setProgress(p);
		},
		function(r){
			datalist = r;
			removeChild(loadingLayer);
			initPage();
		}
	);
}	
function initPage(){
	backLayer = new LSprite();
	addChild(backLayer);
	
	slideshowObj = new LSlideshowSample1(971,609,true,{delay:3,order:LSlideshow.RANDOM});
	backLayer.addChild(slideshowObj);
	
	slideshowObj.setFrame(new LBitmap(new LBitmapData(datalist["caocao"])));
	slideshowObj.setFrame(new LBitmap(new LBitmapData(datalist["diaochan"])));
	slideshowObj.setFrame(new LBitmap(new LBitmapData(datalist["guanyu"])));
	slideshowObj.setFrame(new LBitmap(new LBitmapData(datalist["zhaoyun"])));
	slideshowObj.setFrame(new LBitmap(new LBitmapData(datalist["sunshangxiang"])));
	
	addButton();
}
function addButton(){
	var next_btn = new LButtonSample2(">",20,"黑体","white");
	next_btn.backgroundCorl = "#008800";
	next_btn.x = LStage.width-next_btn.getWidth()-40;
	next_btn.y = (LStage.height-next_btn.getHeight())*0.5;
	backLayer.addChild(next_btn);
	next_btn.addEventListener(LMouseEvent.MOUSE_DOWN,function(){
		slideshowObj.next();
	});
	
	var last_btn = new LButtonSample2("<",20,"黑体","white");
	last_btn.backgroundCorl = "#008800";
	last_btn.x = 20;
	last_btn.y = (LStage.height-last_btn.getHeight())*0.5;
	backLayer.addChild(last_btn);
	last_btn.addEventListener(LMouseEvent.MOUSE_DOWN,function(){
		slideshowObj.previous();
	});
	
	for(var i=0; i<5; i++){
		var page_btn = new LButtonSample2(i+1,20,"黑体","white");
		page_btn.backgroundCorl = "#008800";
		page_btn.x = 50*i+600;
		page_btn.y = LStage.height-page_btn.getHeight()-40;
		backLayer.addChild(page_btn);
		page_btn.addEventListener(LMouseEvent.MOUSE_DOWN,function(event,o){
			var textObj = o.getChildAt(1).getChildAt(0);
			var toIndex = parseInt(textObj.text)-1;
			slideshowObj.showFrameAt(toIndex,LSlideshow.LEFTWARD);
		});
	}
}
function ondown(event){
	if(event.offsetX <= 100){
		slideshowObj.previous();
	}else if(event.offsetX >= LStage.width-100){
		slideshowObj.next();
	}
}
</script> 
</body>
</html>

运行代码后,就得到了本文最上面展示的效果。

四,源代码

上面讲解得有些乱,大家可以把源代码下载下来看看。

最后,奉上源代码:http://files.cnblogs.com/yorhom/Slideshow_source.zip

本章就到此为止,以上就是本篇所有内容,欢迎大家交流。

—————————————————————-

欢迎大家转载我的文章。

转载请注明:转自Yorhom’s Game Box

http://blog.csdn.net/yorhomwang

欢迎继续关注我的博客

分类: html5 标签: