分享一款cocos2d-x横版格斗教程,包括打怪以及人物的移动等等,目前我使用的是最新版:cocos2d-x-3.0alpha1,开发环境为win7+vs2012。这个游戏demo是在网上看到的,觉得挺有意思,网上也已经有很多了文章和例子了,不过基本上都是用cocos2d-x较早的版本实现的,本文使用cocos2d-x 3.0重新实现了一遍。cocos2d-x 3.0更新了一些API,加入了c++ 11特性。我们将要学习精灵动作切换和保存、碰撞检测、简单的机器人AI、3.0新功能的使用等等,最终效果如下图:

 1. 创建项目
切换到tools\project-creator目录下,shift+右键,打开命令提示符窗口,输入:
python create_project.py -p PompaDroid -k com.alexzhou.pompadroid -l cpp
项目目录如下图:

双击cocos2d-x-3.0\projects\PompaDroid\proj.win32\PompaDroid.sln,打开项目。
2. 下载游戏所需资源文件:http://download.csdn.net/detail/zhoujianghai/6880595
 使用Tiled打开pd_tilemap.tmx,就可以看到游戏的整个地图:

根据地图,我们可以得到以下信息:
1.地图分成两层:Wall(墙)和Floor(地板),关闭每个层的透明度可以查看每层的构成。
2.每个瓦片都是32*32,地图瓦片数100×10(宽x高)。
3.从下往上数,前三行瓦片是可以行走的。
3. 添加地图
创建GameLayer类,代码如下:

  1. GameLayer.h
  2. class GameLayer : public cocos2d::Layer
  3. {
  4. public:
  5.  GameLayer();
  6.  ~GameLayer();
  7.  virtual bool init();
  8.  CREATE_FUNC(GameLayer);
  9. private:
  10.  cocos2d::TMXTiledMap *m_pTiledMap;
  11. };
  12.   
  13. GameLayer.cpp
  14. GameLayer::GameLayer()
  15.  :m_pTiledMap(NULL)
  16. {
  17. }
  18. GameLayer::~GameLayer()
  19. {
  20. }
  21. bool GameLayer::init()
  22. {
  23.  bool ret = false;
  24.  do {
  25.   CC_BREAK_IF( !Layer::init());
  26.   m_pTiledMap = TMXTiledMap::create("pd_tilemap.tmx");
  27.   this->addChild(m_pTiledMap, -10);
  28.   ret = true;
  29.  } while(0);
  30.  return ret;
  31. }
这里设置z-order的值为-10,可以设置为其他值,因为地图一般是显示在最底层,所以添加其他元素的时候可以设置z-order的值大于该值即可。
接着创建场景类GameScene:

  1. GameScene.h
  2. class GameScene
  3. {
  4. public:
  5.  static cocos2d::Scene* createScene();
  6. };
  7. GameScene.cpp
  8. Scene* GameScene::createScene()
  9. {
  10.  auto scene = Scene::create();
  11.   
  12.  auto gameLayer = GameLayer::create();
  13.  scene->addChild(gameLayer, 0);
  14.  return scene;
  15. }
然后修改AppDelegate.cpp的applicationDidFinishLaunching函数:

  1. bool AppDelegate::applicationDidFinishLaunching() {
  2.     // initialize director
  3.     auto director = Director::getInstance();
  4.     auto eglView = EGLView::getInstance();
  5.     director->setOpenGLView(eglView);
  6.  eglView->setDesignResolutionSize(480, 320, ResolutionPolicy::SHOW_ALL);
  7.     // turn on display FPS
  8.     director->setDisplayStats(false);
  9.     // set FPS. the default value is 1.0/60 if you don't call this
  10.     director->setAnimationInterval(1.0 / 60);
  11.     // create a scene. it's an autorelease object
  12.  auto scene = GameScene::createScene();
  13.     // run
  14.     director->runWithScene(scene);
  15.     return true;
  16. }
setDesignResolutionSize这个设置资源分辨率尺寸,为了适配多个分辨率的。
因为这里资源分辨率是480×320,所以在main.cpp中设置宽高为480×320效果最好。
编译运行项目会看到地图已经显示出来了,效果如下:


现在cocos2d-x横版格斗教程的地图上啥也没有,太单调了,那就创造一个英雄吧。

4. 添加英雄
英雄的资源文件是pd_sprites.pvr.ccz,可以使用TexturePacker打开,如下图:

英雄有5种状态:
1. 空闲(站着不动时)
2. 行走
3. 攻击
4. 被攻击
5. 死亡

每个状态都对应一组动画,因为英雄不可能同时处于多种状态下,所以我们要考虑的是如何实现状态切换,然后播放状态对应的动画。
这里当英雄处于空闲状态时,只能切换到行走、攻击、受伤状态,不会还没受伤就死亡了,当英雄死亡后,就不能切换到其他四种状态了。
创建BaseSprite类,作为精灵的基类:

  1. BaseSprite.h
  2. typedef enum {
  3.  ACTION_STATE_NONE = 0,
  4.  ACTION_STATE_IDLE,
  5.  ACTION_STATE_WALK,
  6.  ACTION_STATE_ATTACK,
  7.  ACTION_STATE_HURT,
  8.  ACTION_STATE_DEAD
  9. }ActionState;
  10.   
  11. class BaseSprite : public cocos2d::Sprite
  12. {
  13. public:
  14.  BaseSprite();
  15.  ~BaseSprite();
  16.  void runIdleAction();
  17.  void runWalkAction();
  18.  void runAttackAction();
  19.  void runHurtAction();
  20.  void runDeadAction();
  21.  CC_SYNTHESIZE_RETAIN(cocos2d::Action*, m_pIdleAction, IdleAction);
  22.  CC_SYNTHESIZE_RETAIN(cocos2d::Action*, m_pWalkAction, WalkAction);
  23.  CC_SYNTHESIZE_RETAIN(cocos2d::Action*, m_pAttackAction, AttackAction);
  24.  CC_SYNTHESIZE_RETAIN(cocos2d::Action*, m_pHurtAction, HurtAction);
  25.  CC_SYNTHESIZE_RETAIN(cocos2d::Action*, m_pDeadAction, DeadAction);
  26.  CC_SYNTHESIZE(ActionState, m_currActionState, CurrActionState);
  27.  cocos2d::CallFunc* createIdleCallbackFunc();
  28. protected:
  29.  static cocos2d::Animation* createAnimation(const char* formatStr, int frameCount, int fps);
  30. private:
  31.  bool changeState(ActionState actionState);
  32. };
  33.   
  34. BaseSprite.cpp
  35. BaseSprite::BaseSprite():
  36.  m_pIdleAction(NULL),
  37.  m_pWalkAction(NULL),
  38.  m_pAttackAction(NULL),
  39.  m_pHurtAction(NULL),
  40.  m_pDeadAction(NULL),
  41.  m_currActionState(ACTION_STATE_NONE)
  42. {
  43. }
  44. BaseSprite::~BaseSprite()
  45. {
  46.  CC_SAFE_RELEASE_NULL(m_pIdleAction);
  47.  CC_SAFE_RELEASE_NULL(m_pWalkAction);
  48.  CC_SAFE_RELEASE_NULL(m_pAttackAction);
  49.  CC_SAFE_RELEASE_NULL(m_pHurtAction);
  50.  CC_SAFE_RELEASE_NULL(m_pDeadAction);
  51. }
  52. void BaseSprite::runIdleAction()
  53. {
  54.  if(changeState(ACTION_STATE_IDLE))
  55.  {
  56.   this->runAction(m_pIdleAction);
  57.  }
  58. }
  59.   
  60. void BaseSprite::runWalkAction()
  61. {
  62.  if(changeState(ACTION_STATE_WALK))
  63.  {
  64.   this->runAction(m_pWalkAction);
  65.  }
  66. }
  67. void BaseSprite::runAttackAction()
  68. {
  69.  if(changeState(ACTION_STATE_ATTACK))
  70.  {
  71.   this->runAction(m_pAttackAction);
  72.  }
  73. }
  74. void BaseSprite::runHurtAction()
  75. {
  76.  if(changeState(ACTION_STATE_HURT))
  77.  {
  78.   this->runAction(m_pHurtAction);
  79.  }
  80. }
  81. void BaseSprite::runDeadAction()
  82. {
  83.  if(changeState(ACTION_STATE_DEAD))
  84.  {
  85.   this->runAction(m_pDeadAction);
  86.  }
  87. }
  88. Animation* BaseSprite::createAnimation(const char* formatStr, int frameCount, int fps)
  89. {
  90.  Array *pFrames = Array::createWithCapacity(frameCount);
  91.  for(int i = 0; i < frameCount; ++ i)
  92.  {
  93.   const char* imgName = String::createWithFormat(formatStr, i)->getCString();
  94.   SpriteFrame *pFrame = SpriteFrameCache::getInstance()->getSpriteFrameByName(imgName);
  95.   pFrames->addObject(pFrame);
  96.  }
  97.  return Animation::createWithSpriteFrames(pFrames, 1.0f / fps);
  98. }
  99. bool BaseSprite::changeState(ActionState actionState)
  100. {
  101.  if(m_currActionState == ACTION_STATE_DEAD || m_currActionState == actionState)
  102.  {
  103.   return false;
  104.  }
  105.  this->stopAllActions();
  106.  this->m_currActionState = actionState;
  107.  return true;
  108. }
  109. CallFunc* BaseSprite::createIdleCallbackFunc()
  110. {
  111.  return CallFunc::create(CC_CALLBACK_0(BaseSprite::runIdleAction, this));
  112. }
ActionState枚举表示精灵的各种状态,runXXXAction表示切换状态以及播放各状态对应的动画。变量m_currActionState表示当前精灵的状态。createIdleCallbackFunc函数后面需要用到,表示当精灵切换到攻击或者受伤状态后立即回到空闲状态。createAnimation函数是一个工具函数,根据图片路径、帧数、每秒显示的帧数来创建动画。changeState函数就是状态切换函数,当精灵死亡的时候就GameOver了。
现在该创建咱们的英雄类Hero了:

  1. Hero.h
  2. class Hero : public BaseSprite
  3. {
  4. public:
  5.  Hero();
  6.  ~Hero();
  7.  bool init();
  8.  CREATE_FUNC(Hero);
  9. };
  10.   
  11. Hero.cpp
  12. Hero::Hero()
  13. {}
  14. Hero::~Hero()
  15. {}
  16. bool Hero::init()
  17. {
  18.  bool ret = false;
  19.  do {
  20.   CC_BREAK_IF( !this->initWithSpriteFrameName("hero_idle_00.png") );
  21.   Animation *pIdleAnim = this->createAnimation("hero_idle_%02d.png", 6, 12);
  22.   this->setIdleAction(RepeatForever::create(Animate::create(pIdleAnim)));
  23.   Animation *pWalkAnim = this->createAnimation("hero_walk_%02d.png", 7, 14);
  24.   this->setWalkAction(RepeatForever::create(Animate::create(pWalkAnim)));
  25.   
  26.   Animation *pAttackAnim = this->createAnimation("hero_attack_00_%02d.png", 3, 20);
  27.   this->setAttackAction(Sequence::create(Animate::create(pAttackAnim), BaseSprite::createIdleCallbackFunc(), NULL));
  28.   Animation *pHurtAnim = this->createAnimation("hero_hurt_%02d.png", 3, 12);
  29.   this->setHurtAction(Sequence::create(Animate::create(pHurtAnim), BaseSprite::createIdleCallbackFunc(), NULL));
  30.   Animation *pDeadAnim = this->createAnimation("hero_knockout_%02d.png", 5, 12);
  31.   this->setDeadAction(Sequence::create(Animate::create(pDeadAnim), Blink::create(3, 9), NULL));
  32.   ret = true;
  33.  } while(0);
  34.  return ret;
  35. }
  36. Hero类比较简单,就是初始化各种动作。
  37. 我们来修改GameLayer类,加载精灵图片所需的资源。
  38. 在GameLayer.h中添加:

  39. CC_SYNTHESIZE_READONLY(Hero*, m_pHero, Hero);
  40.   
  41.  float m_fScreenWidth;
  42.  float m_fScreenHeight;
  43.  cocos2d::Point m_origin;
  44.  cocos2d::SpriteBatchNode *m_pSpriteNodes;
  45. 在GameLayer.cpp的init函数中添加:

  46. auto visibleSize = Director::getInstance()->getVisibleSize();
  47.   this->m_origin = Director::getInstance()->getVisibleOrigin();
  48.   this->m_fScreenWidth = visibleSize.width;
  49.   this->m_fScreenHeight = visibleSize.height;
  50.   SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pd_sprites.plist");
  51.   m_pSpriteNodes = SpriteBatchNode::create("pd_sprites.pvr.ccz");
  52.   this->addChild(m_pSpriteNodes);
  53.   m_pHero = Hero::create();
  54.   m_pHero->setPosition( m_origin + Point(100, 100) );
  55.   m_pHero->runIdleAction();
  56.   m_pHero->setZOrder(m_fScreenHeight - m_pHero->getPositionY());
  57.   m_pSpriteNodes->addChild(m_pHero);
这里添加了一个英雄,然后切换到空闲状态,设置ZOrder值跟Y坐标相关,这样后期可以解决英雄和怪物的遮挡问题,关于SpriteFrameCache和SpriteBatchNode这里就不介绍了,不明白的可以查看http://codingnow.cn/cocos2d-x/795.html
现在编译运行项目,就可以看到一个骚包在屏幕上晃来晃去了:

OK,cocos2d-x横版格斗教程就到此为止了,