Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

天下之事常成于困约,而败于奢靡。这篇文章主要讲述Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动相关的知识,希望能为你提供帮助。
    本文将要实现一个横版格斗类游戏,并实现摇杆控制英雄上下左右运动。这里实现了能通过虚拟摇杆控制精灵的运动,而且能够改变精灵运动时的脸部朝向。之前看了好多人写虚拟摇杆,但是就是没写控制精灵的运动和脸朝向的。所以自己就想要写个文章好好讲下它的实现思路。好了,下面我们开始吧。
最终效果:

Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

文章图片





      cocos2d-x版本:2.2.5
      工程环境:windows7+VS2010
            打开方式:将工程放在cocos2d-x安装目录下的project文件夹下用VS打开



目录:
一、修改背景图片和窗口大小
二、添加虚拟摇杆

【Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动】三、添加精灵并用摇杆控制精灵的运动

四、思路总结




一、修改背景图片和窗口大小    新建一个工程,工程名为HelloCpp.为了让效果更加好些,首先修改下窗口的大小,在main.cpp中改
      eglView-> setFrameSize(480,320); 为          eglView-> setFrameSize(640,480);

    然后在我们要显示的层上HelloWorldScene.cpp的init()函数添加:
    //得到窗口的大小            CCSize  visibleSize  =  CCDirector::sharedDirector()-> getVisibleSize();               CCPoint  origin  =  CCDirector::sharedDirector()-> getVisibleOrigin();         //修改背景图片        CCSprite*  pSprite  =  CCSprite::create("background_1.jpg");             pSprite-> setPosition(ccp(visibleSize.width/2  +  origin.x,      visibleSize.height/2  +  origin.y));             this-> addChild(pSprite,  0); //这里的0表示放在最底层

效果如下:
Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

文章图片

二、添加虚拟摇杆      首先,先将摇杆素材放入到项目的Resources文件夹下,注意,最好把要用到的素材都放在这里,项目工程默认的素材是从此文件夹下查找。
Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

文章图片

Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

文章图片



      这是摇杆的背景图片,图片不容易找啊。没有美工,只能随便网上找了点
 
Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

文章图片

Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

文章图片

      这是摇杆里面的按钮图片,它是可以移动的

      好了,接下来我们要开始加入摇杆的进入背景中了,我的思路是把摇杆封装成一个类,然后照看继承CCLayer,就可以响应触摸事件了,看看代码,新建一个HRocker.h头文件。这里要注意要把.cpp和.h都放到项目的class文件夹下,要不会提示找不到文件。代码如下:
#ifndef  __HROCKER_H__ #define  __HROCKER_H__#include  "cocos2d.h"using  namespace  cocos2d; //用于标识摇杆与摇杆的背景 typedef  enum{         tag_rocker,         tag_rockerBG, }tagForHRocker; //用于标识摇杆方向 typedef  enum{         rocker_stay,         rocker_right,         rocker_up,         rocker_left,         rocker_down, }tagDirecton; class  HRocker:public  CCLayer { public:         HRocker(void);         ~HRocker(void);         //创建摇杆(摇杆的操作题图片资源名,摇杆背景图片资源名,起始坐标)         static  HRocker*  createHRocker(const  char  *rockerImageName,const  char  *rockerBGImageName,CCPoint  position);         //启动摇杆(显示摇杆、监听摇杆触屏事件)         void  startRocker(bool  _isStopOther);         //停止摇杆(隐藏摇杆,取消摇杆的触屏监听)         void  stopRocker();         //判断控制杆方向,用来判断精灵上、下、左、右运动         int  rocketDirection;         //当前人物行走方向,用来判断精灵的朝向,精灵脸朝右还是朝左         bool  rocketRun;         CREATE_FUNC(HRocker); private:         //自定义初始化函数         void  rockerInit(const  char*  rockerImageName,const  char*  rockerBGImageName,CCPoint  position);         //是否可操作摇杆         bool  isCanMove;         //获取当前摇杆与用户触屏点的角度         float  getRad(CCPoint  pos1,CCPoint  pos2);         //摇杆背景的坐标         CCPoint  rockerBGPosition;         //摇杆背景的半径         float  rockerBGR;         //触屏事件         virtual  bool  ccTouchBegan(CCTouch  *pTouch,  CCEvent  *pEvent);         virtual  void  ccTouchMoved(CCTouch  *pTouch,  CCEvent  *pEvent);         virtual  void  ccTouchEnded(CCTouch  *pTouch,  CCEvent  *pEvent); }; #endif



      再建其相应的实现HRocker.cpp,这里要注意要把.cpp和.h都放到项目的class文件夹下,要不会提示找不到文件。代码如下:
#include  "HRocker.h" const  double  PI=3.1415; HRocker::HRocker(void) {         rocketRun=false; }HRocker::~HRocker(void) { }//创建摇杆(摇杆的操作题图片资源名,摇杆背景图片资源名,起始坐标) HRocker*  HRocker::createHRocker(const  char  *rockerImageName,const  char  *rockerBGImageName,CCPoint  position) {         HRocker  *layer  =  HRocker::create();         if  (layer)         {                 layer-> rockerInit(rockerImageName,rockerBGImageName,position);                 return  layer;         }         CC_SAFE_DELETE(layer);         return  NULL; }//自定义初始化函数 void  HRocker::rockerInit(const  char*  rockerImageName,const  char*  rockerBGImageName,CCPoint  position) {         CCSprite  *spRockerBG  =  CCSprite::create(rockerBGImageName);         spRockerBG-> setPosition(position);         spRockerBG-> setVisible(false);         addChild(spRockerBG,0,tag_rockerBG);         CCSprite  *spRocker  =  CCSprite::create(rockerImageName);         spRocker-> setPosition(position);         spRocker-> setVisible(false);         addChild(spRocker,1,tag_rocker);         rockerBGPosition  =  position;         rockerBGR  =  spRockerBG-> getContentSize().width*0.5; //         rocketDirection=-1; //表示摇杆方向不变 }//启动摇杆(显示摇杆、监听摇杆触屏事件) void  HRocker::startRocker(bool  _isStopOther) {         CCSprite  *rocker  =  (CCSprite*)this-> getChildByTag(tag_rocker);         rocker-> setVisible(true);         CCSprite  *rockerBG  =  (CCSprite  *)this-> getChildByTag(tag_rockerBG);         rockerBG-> setVisible(true);         CCDirector::sharedDirector()-> getTouchDispatcher()-> addTargetedDelegate(this,-1,_isStopOther); }//停止摇杆(隐藏摇杆,取消摇杆的触屏监听) void  HRocker::stopRocker() {         CCSprite  *rocker  =  (CCSprite  *)this-> getChildByTag(tag_rocker);         rocker-> setVisible(false);         CCSprite  *  rockerBG  =  (CCSprite  *)this-> getChildByTag(tag_rockerBG);         rockerBG-> setVisible(false);         CCDirector::sharedDirector()-> getTouchDispatcher()-> removeDelegate(this); }//获取当前摇杆与用户触屏点的角度 float  HRocker::getRad(CCPoint  pos1,CCPoint  pos2) {         float  px1  =  pos1.x;         float  py1  =  pos1.y;         float  px2  =  pos2.x;         float  py2  =  pos2.y;         //得到两点x的距离         float  x  =  px2  -  px1;         //得到两点y的距离         float  y  =  py1  -  py2;         //算出斜边长度         float  xie  =  sqrt(pow(x,2)  +  pow(y,2));         //得到这个角度的余弦值(通过三角函数中的店里:角度余弦值=斜边/斜边)         float  cosAngle  =  x  /  xie;         //通过反余弦定理获取到期角度的弧度         float  rad  =  acos(cosAngle);         //注意:当触屏的位置Y坐标< 摇杆的Y坐标,我们要去反值-0~-180         if  (py2  <   py1)         {                 rad  =  -rad;         }         return  rad; }CCPoint  getAngelePosition(float  r,float  angle){         return  ccp(r*cos(angle),r*sin(angle)); }//抬起事件 bool  HRocker::ccTouchBegan(CCTouch  *pTouch,  CCEvent  *pEvent) {         CCPoint  point  =  pTouch-> getLocation();         CCSprite  *rocker  =  (CCSprite  *)this-> getChildByTag(tag_rocker);         if  (rocker-> boundingBox().containsPoint(point))                 isCanMove  =  true;         return  true; } //移动事件 void  HRocker::ccTouchMoved(CCTouch  *pTouch,  CCEvent  *pEvent) {         if  (!isCanMove)         {                 return;         }         CCPoint  point  =  pTouch-> getLocation();         CCSprite  *rocker  =  (CCSprite  *)this-> getChildByTag(tag_rocker);         //得到摇杆与触屏点所形成的角度         float  angle  =  getRad(rockerBGPosition,point);         //判断两个圆的圆心距是否大于摇杆背景的半径         if  (sqrt(pow((rockerBGPosition.x  -  point.x),2)  +  pow((rockerBGPosition.y  -  point.y),2))  > =  rockerBGR)         {                         //保证内部小圆运动的长度限制                 rocker-> setPosition(ccpAdd(getAngelePosition(rockerBGR,angle),ccp(rockerBGPosition.x,rockerBGPosition.y)));         }         else                 rocker-> setPosition(point);         //判断方向         if(angle> =-PI/4& & angle< PI/4)         {                 rocketDirection=rocker_right;                 rocketRun=false;         }         else  if(angle> =PI/4& & angle< 3*PI/4)         {                 rocketDirection=rocker_up;         }         else  if((angle> =3*PI/4& & angle< =PI)||(angle> =-PI& & angle< -3*PI/4))         {                 rocketDirection=rocker_left;                 rocketRun=true;         }         else  if(angle> =-3*PI/4& & angle< -PI/4)         {                 rocketDirection=rocker_down;         } } //离开事件 void  HRocker::ccTouchEnded(CCTouch  *pTouch,  CCEvent  *pEvent) {         if  (!isCanMove)         {                 return;         }         CCSprite  *rockerBG  =  (CCSprite*)this-> getChildByTag(tag_rockerBG);         CCSprite  *rocker  =  (CCSprite*)this-> getChildByTag(tag_rocker);         rocker-> stopAllActions();         rocker-> runAction(CCMoveTo::create(0.08f,rockerBG-> getPosition()));         isCanMove  =  false;         rocketDirection=rocker_stay; }

这里的思路我等下再说吧
之后就在HelloWorldScene.h添加头文件#include "HRocker.h",并加入摇杆类的成员变量
private: HRocker*    rocker;





在HelloWorldScene.h的init()函数添加:
    //添加摇杆     rocker  =  HRocker::createHRocker("Direction_bt.png","Direction_bc.png",ccp(110,60));     //其中第一张图片为摇杆的按钮,第二张为背景            this-> addChild(rocker,2);             rocker-> startRocker(true);

效果如下:我们可以看到,可以移动摇杆了
Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

文章图片

效果还是不错的,很灵敏,小球也不会跳出
三、添加精灵并用摇杆控制精灵的运动    这里为了能让精灵运行,我自己创建了一个类, Hero,它继承了CCNode,里面实现了动画播放和停止.
      赵云的走的动作其实就是一系列的图片合成在一起
Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

文章图片

另外,赵云不运动时的图片为
Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

文章图片

Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

文章图片


  zhoayun.png。这是不运动时精灵的贴图图片
      这里我为了省事,直接用工具TexturePacker将它们打包生成相应的png和plist,这两个合起来在cocos2d-x可以组成一个动画,比较省事。
Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

文章图片
      命名为run_animation.png和run_animation.plist,然后还是把它们放在项目的Resources文件夹下
Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

文章图片

    为了让代码更加容易理解些,我自己创建了一个类, Hero,它继承了CCNode,并且里面有一成员变量  CCSprite* m_HeroSprite; 我们就是通过控制它的动画来实现运动的。里面实现了动画播放和停止,好了,代码如下Hero.h
#ifndef  __HERO_H__ #define  __HERO_H__ #include  "cocos2d.h" #include  "cocos-ext.h" using  namespace  cocos2d; USING_NS_CC_EXT;     class  Hero:public  cocos2d::CCNode   {   public:               Hero(void);             ~Hero(void);           //根据图片名创建英雄           void  InitHeroSprite(char  *hero_name);           //设置动画,num为图片数目,run_directon为精灵脸朝向,false朝右           void  SetAnimation(const  char  *name_plist,const  char  *name_png,const  unsigned  int  num,bool  run_directon);         //停止动画           void  StopAnimation();           //判断英雄是否运动到了窗口的中间位置           bool  JudgePositona(CCSize  visibleSize);         //判断是否在跑动画           bool  IsRunning;           //英雄运动的方向           bool  HeroDirecton;           CREATE_FUNC(Hero);   private:           CCSprite*  m_HeroSprite; //精灵           char  *Hero_name; //用来保存初始状态的精灵图片名称   }; #endif  //  __HERO_H__

然后是Hero.cpp
#include  "Hero.h" USING_NS_CC;   USING_NS_CC_EXT; Hero::Hero(void) {         IsRunning=false; //没在放动画         HeroDirecton=false; //向右运动         Hero_name=NULL; }Hero::~Hero(void) {}   void  Hero::InitHeroSprite(char  *hero_name)   {         Hero_name=hero_name;         this-> m_HeroSprite=CCSprite::create(hero_name);         this-> addChild(m_HeroSprite);   }   //原地奔跑动画   void  Hero::SetAnimation(const  char  *name_plist,const  char  *name_png,unsigned  int  num,bool  run_directon)   {           if(HeroDirecton!=run_directon)           {      HeroDirecton=run_directon;           m_HeroSprite-> setFlipX(run_directon);           }           if(IsRunning)                   return;           //将图片加载到精灵帧缓存池         CCSpriteFrameCache  *m_frameCache=CCSpriteFrameCache::sharedSpriteFrameCache();           m_frameCache-> addSpriteFramesWithFile(name_plist,name_png);           //用一个列表保存所有的CCSpriteFrameCache           CCArray*  frameArray=  CCArray::createWithCapacity(num);           unsigned  int  i;           for(i=2; i< =num; i++)           {                   CCSpriteFrame*  frame=m_frameCache-> spriteFrameByName(CCString::createWithFormat("run_%d.png",i)-> getCString());                   frameArray-> addObject(frame);           }           //使用列表创建动画对象           CCAnimation*  animation=CCAnimation::createWithSpriteFrames(frameArray);           if(HeroDirecton!=run_directon)           {      HeroDirecton=run_directon;                             }           animation-> setLoops(-1); //表示无限循环播放         animation-> setDelayPerUnit(0.1f); //每两张图片的时间隔,图片数目越少,间隔最小就越小          //将动画包装成一个动作           CCAnimate*  act=CCAnimate::create(animation);                           m_HeroSprite-> runAction(act);           IsRunning=true;   }   void  Hero::StopAnimation()   {           if(!IsRunning)                   return;         m_HeroSprite-> stopAllActions(); //当前精灵停止所有动画        //恢复精灵原来的初始化贴图          this-> removeChild(m_HeroSprite,TRUE); //把原来的精灵删除掉         m_HeroSprite=CCSprite::create(Hero_name); //恢复精灵原来的贴图样子         m_HeroSprite-> setFlipX(HeroDirecton);         this-> addChild(m_HeroSprite);         IsRunning=false;         IsRunning=false;   }     bool  Hero::JudgePositona  (CCSize  visibleSize)     {             if(this-> getPositionX()!=visibleSize.width/2)//精灵到达左边             return  false;             else                     return  true; //到达中间位置     }

效果如下:
Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

文章图片


Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

文章图片

      这时图片还是静止的为了要验证一个精灵的运动,我们来播放一个它的走动的动画,在上面
        hero=Hero::create();         hero-> InitHeroSprite("zhoayun.png");         hero-> setPosition(ccp(200,200));         this-> addChild(hero,1);

添加一句


        hero-> SetAnimation("run_animation.plist","run_animation.png",8,false);         //8表示plist中的图片数目,false表示脸朝右

来看看效果,这时精灵会一直不停的运动
Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

文章图片



如果我们要改变精灵的脸朝向呢?简单,false改成true
          hero-> SetAnimation("run_animation.plist","run_animation.png",8,true);           //8表示plist中的图片数目,false表示脸朝右


效果:
Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

文章图片

      好了,验证正确后,我们还不是把上面那一句注释掉,因为精灵应该是我们点了摇杆让他动它才动的。
      其实要控制人物的运动很简单,无非是在播房动画的同时,要移动精灵的位置,当然,这里我们也要判断精灵的脸朝向,上面的摇杆类HRocker中
        //判断控制杆方向,用来判断精灵上、下、左、右运动              int  rocketDirection;                 //当前人物行走方向,用来判断精灵的朝向,精灵脸朝右还是朝左              bool  rocketRun;

    我们只要将这两个参数传给赵去的英雄类Hero中的
      //设置动画,num为图片数目,run_directon为精灵脸朝向,false朝右       void  SetAnimation(const  char  *name_plist,const  char  *name_png,       const  unsigned  int  num,bool  run_directon);

      看到了没,一个刚好是int 型,一个刚好是bool型。那我们要怎么来控制了。前面我们在HelloWorldScene中不是创建了两个对像
      private:        HRocker*  rocker; //摇杆              Hero*        hero; ///精灵



将rocker的值传给hero不就可以控制了么?想着要能每帧都能更新,所以在HelloWorldScene.h中加入事件
              virtual  void  update(float  delta);


一定要注意在HelloWorldScene.cpp的init()函数下面添加
          //启动updata事件                  this-> scheduleUpdate();

然后就是事件了
  void  HelloWorld::update(float  delta)   {            //判断是否按下摇杆及其类型          switch(rocker-> rocketDirection)            {          case  1:                          hero-> SetAnimation("run_animation.plist","run_animation.png","run_",8,rocker-> rocketRun);           //"run_"为run_animation.png集合图片中每张图片的公共名称部分                          hero-> setPosition(ccp(hero-> getPosition().x+1,hero-> getPosition().y));   //向右走                          break;           case    2:                          hero-> SetAnimation("run_animation.plist","run_animation.png","run_",8,rocker-> rocketRun);                       hero-> setPosition(ccp(hero-> getPosition().x,  hero-> getPosition().y+1));   //向上走                        break;             case  3:                          hero-> SetAnimation("run_animation.plist","run_animation.png","run_",8,rocker-> rocketRun);                       hero-> setPosition(ccp(hero-> getPosition().x-1,hero-> getPosition().y)); //向左走                          break;             case  4:                          hero-> SetAnimation("run_animation.plist","run_animation.png","run_",8,rocker-> rocketRun);           hero-> setPosition(ccp(hero-> getPosition().x,hero-> getPosition().y-1));         //向下走                          break;             default:                          hero-> StopAnimation(); //停止所有动画和运动                          break;             }}

好了,我们来看看效果如何
Cocos2d-x《赵云要格斗》--虚拟摇杆控制精灵上下左右运动

文章图片

效果还不错,也不会很卡。总算大功告成了!


四、思路总结      其实这里的思路是把摇杆类的精灵类分开来实现,摇杆类中有可以控制按钮的运动,并有返回当前你摇杆的操作类型,是向上还是向下,是脸朝右还朝左,然后还这 些参数传给英雄精灵类,这里的英雄我们也要设置它的动画,然后就是在创建两个个类的实例对像的层中来调用它们,并在updata()函数中判断摇杆是否按 下,及方向,然后设置赵云精灵类的移动的动画的播放。思路就是这样了,由于工程还要一直改下去,如果谁现在想要我的素材和工程的,留个邮箱给我就是了哈, 我会发给你们的。











    推荐阅读