上一篇我们已经可以看到英雄和机器人都处于无敌状态,现在让他们互相残杀吧,所以接下来将要实现碰撞检测功能。先来看看下面这张图:


这里碰撞检测采用比较简单的矩形,可以看到英雄和机器人在攻击的时候会把拳头伸出去,我们可以把英雄分成两个矩形框,身体(被攻击的部分)矩形区域和拳头(攻击部分)的矩形区域,如上图的蓝色和红色区域,机器人是一样的。这样的话,英雄攻击机器人的时候,只需要检测英雄的红色区域跟机器人的蓝色区域是否有交集,如果这两个矩形有交集,则为击中;机器人攻击英雄也是一样的道理。既然原理弄明白了,现在就开始写代码吧。
首先在BaseSprite.h中添加:

typedef struct _BoundingBox
{
 cocos2d::Rect actual;
 cocos2d::Rect original;
}BoundingBox;

在BaseSprite类中添加:

 CC_SYNTHESIZE(BoundingBox, m_bodyBox, BodyBox);
 CC_SYNTHESIZE(BoundingBox, m_hitBox, HitBox);

virtual void setPosition(const cocos2d::Point &position);
 BoundingBox createBoundingBox(cocos2d::Point origin, cocos2d::Size size);
 void updateBoxes();

声明结构体BoundingBox,表示碰撞盒,actual这个矩形是以屏幕左下角为原点的,在进行碰撞检测时就使用它;original用来保存精灵本身的矩形信息,以精灵左下角为起点,比如上图的蓝色或红色矩形,在每次更新actual时使用。这里还重写了setPosition函数,在更新精灵位置的时候也需要更新碰撞盒的坐标。下面看实现代码:

BoundingBox BaseSprite::createBoundingBox(cocos2d::Point origin, cocos2d::Size size)
{
 BoundingBox boundingBox;
 boundingBox.original.origin= origin;
 boundingBox.original.size= size;
 boundingBox.actual.origin = this->getPosition() + boundingBox.original.origin;
 boundingBox.actual.size= size;
 return boundingBox;
}

void BaseSprite::updateBoxes() {
 bool isFlippedX = this->isFlippedX();
 float x = 0.0f;
 if(isFlippedX) {
  x = this->getPosition().x - m_hitBox.original.origin.x;
 }else {
  x = this->getPosition().x + m_hitBox.original.origin.x;
 }
 m_hitBox.actual.origin = Point(x, this->getPosition().y + m_hitBox.original.origin.y);
    m_bodyBox.actual.origin = this->getPosition() + m_bodyBox.original.origin;
}

void BaseSprite::setPosition(const Point &position)
{
 Sprite::setPosition(position);
 this->updateBoxes();
}

需要注意:在更新碰撞盒的时候,攻击盒子需要判断精灵的朝向,面向左和面向右的坐标不一样。
现在来实现碰撞检测的代码,在GameLayer.cpp中添加:

bool collisionDetection(const BoundingBox &hitBox, const BoundingBox &bodyBox)
{
 Rect hitRect = hitBox.actual;
 Rect bodyRect = bodyBox.actual;
 if(hitRect.intersectsRect(bodyRect))
 {
  return true;
 }
 return false;
}

比较简单,就是判断攻击盒子跟身体盒子是否有交集而已。
接着更新GameLayer.cpp的onHeroAttack函数,添加下面的代码:

if(m_pHero->getCurrActionState() == ACTION_STATE_ATTACK)
  {
   Object *enemyObj = NULL;
   CCARRAY_FOREACH(m_pEnemies, enemyObj)
   {
    Enemy *pEnemy = (Enemy*)enemyObj;
    if(fabsf(m_pHero->getPosition().y - pEnemy->getPosition().y) < 10)
    {
     BoundingBox heroHitBox = m_pHero->getHitBox();
     BoundingBox enemyBodyBox = pEnemy->getBodyBox();
     if(::collisionDetection(heroHitBox, enemyBodyBox))
     {
      pEnemy->runHurtAction();
     }
    }
   }
  }

这里只是进行了碰撞检测,打中了就执行受伤动画,依然处于不死状态,同理,更新GameLayer.cpp的onEnemyAttack函数,添加:

Object *enemyObj = NULL;
 CCARRAY_FOREACH(m_pEnemies, enemyObj)
 {
  Enemy *pEnemy = (Enemy*)enemyObj;
  if(pEnemy->getCurrActionState() == ACTION_STATE_ATTACK)
  {
   pEnemy->setPositionY(m_pHero->getPositionY());
   BoundingBox heroBodyBox = m_pHero->getBodyBox();
   BoundingBox enemyHitBox = pEnemy->getHitBox();
   if(::collisionDetection(enemyHitBox, heroBodyBox))
   {
    m_pHero->runHurtAction();
   }
  }
 }

初始化英雄和机器人的碰撞盒,在Hero.cpp的init函数中添加:

Size heroShowSize = this->getDisplayFrame()->getRect().size;
  this->m_bodyBox = this->createBoundingBox(Point(-heroShowSize.width / 2, -heroShowSize.height / 2), heroShowSize);
  this->m_hitBox = this->createBoundingBox(Point(heroShowSize.width / 2, -5), Size(25, 20));

在Enemy.cpp的init函数中添加:

Size enemyShowSize = this->getDisplayFrame()->getRect().size;
  this->m_bodyBox = this->createBoundingBox(Point(-enemyShowSize.width / 2, -enemyShowSize.height / 2), enemyShowSize);
  this->m_hitBox = this->createBoundingBox(Point(enemyShowSize.width / 2, -5), Size(25, 20));

这里的25和20分别是精灵攻击盒子的宽和高,这些值从上图可以量出。
OK,编译运行项目,现在可以看到英雄和机器人被A的傻样了。

不过现在都打不死,接下来给英雄设置生命值和攻击力,然后在每次碰撞检测后更新精灵生命值和状态:
在GameLayer.cpp的init函数添加:

m_pHero->setAttack(5);
  m_pHero->setHP(100);

更新GameLayer.cpp的onHeroAttack函数:

 if(::collisionDetection(heroHitBox, enemyBodyBox))
     {
      int damage = m_pHero->getAttack();
      pEnemy->runHurtAction();
      pEnemy->setHP(pEnemy->getHP() - damage);

      if(pEnemy->getHP() <= 0)
      {
       pEnemy->runDeadAction();
      }
     }

更新onEnemyAttack函数:

  if(::collisionDetection(enemyHitBox, heroBodyBox))
   {
    int damage = pEnemy->getAttack();
    m_pHero->runHurtAction();
    m_pHero->setHP(m_pHero->getHP() - damage);

    if(m_pHero->getHP() <= 0)
    {
     m_pHero->runDeadAction();
    }
   }

 

感觉还是少了点什么,发现太安静了,一款游戏怎么能少了背景音乐和音效呢,现在就给加上吧。
在GameLayer.h中添加音频文件路径:

#define PATH_BG_MUSIC "background-music-aac.wav"
#define PATH_HERO_HIT_EFFECT "pd_hit0.wav"
#define PATH_ENEMY_HIT_EFFECT "pd_hit1.wav"
#define PATH_HERO_DEAD_EFFECT "pd_herodeath.mp3"
#define PATH_ENEMY_DEAD_EFFECT "pd_botdeath.wav"
#define PATH_HERO_TALK_EFFECT "hero_talk.mp3"

在GameLayer.cpp的init函数最后添加:

CocosDenshion::SimpleAudioEngine::getInstance()->playBackgroundMusic(PATH_BG_MUSIC, true);
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect(PATH_HERO_TALK_EFFECT);

更新onHeroAttack函数:

     if(::collisionDetection(heroHitBox, enemyBodyBox))
     {
//......
      CocosDenshion::SimpleAudioEngine::getInstance()->playEffect(PATH_HERO_HIT_EFFECT);
     }

更新onEnemyAttack函数:

 if(::collisionDetection(enemyHitBox, heroBodyBox))
   {
    int damage = pEnemy->getAttack();
    m_pHero->runHurtAction();
    m_pHero->setHP(m_pHero->getHP() - damage);
    CocosDenshion::SimpleAudioEngine::getInstance()->playEffect(PATH_ENEMY_HIT_EFFECT);
    if(m_pHero->getHP() <= 0)
    {
     m_pHero->runDeadAction();
     CocosDenshion::SimpleAudioEngine::getInstance()->playEffect(PATH_HERO_DEAD_EFFECT);
    }
   }

这里添加了LOL中大鳄鱼的“所有人都得死”的音效,英雄出场十分的霸气啊。
到目前为止,游戏的基本功能已完成了,不过现在如果英雄死了或者机器人死完之后游戏就没法继续下去了。可以在游戏结束后添加一个GameOver的提示,然后自动重新开始;还可以实现显示英雄的血条,机器人死后自动添加机器人等功能。。
下一篇就来实现把游戏移植到android上吧。

cocos2d-x手游性能优化总结

近段时间在使用cocos2d-x开发2D手游,技术方案使用的是cocos2d-x+lua,因为游戏使用的是cocos2d-x 2.1.5版本,有些优化方案在最新版的cocos2d-x版本已经实现...

阅读全文

cocos2dx-html5 实现网页版flappy bird游戏

我也是第一次使用cocos2d_html5,对js和html5也不熟,看引擎自带的例子和引擎源码,边学边做,如果使用过cocos2d-x的话,完成这个游戏还是十分简单的。游戏体...

阅读全文

【cocos2d-x开发实战 特训99-终结篇】移植到android平台和添加admob广告

上一篇已经完成特性99在win32平台下的开发,现在把它移植到android上,首先修改Android.mk文件,内容如下: LOCAL_PATH := $(call my-dir) include $(CLEAR_...

阅读全文

2 条评论

  1. 你好,看了你的教程,有点疑问请教下,关于创建碰撞盒子时 Size heroShowSize = this->getDisplayFrame()->getRect().size; 这句代码取出来的大小值是多少才是正确的呢?我用3.2已经找不到该方法,用getBoundingbox取出来的大小却是width=280 height=150
    明细比精灵要大出许多,而检测你的资源文件发现{280,150}我取到的是这个值,是我取错了呢,还是其他原因?

    1. getDisplayFrame在3.2版本中使用getSpriteFrame()代替了。
      这里没有对精灵进行缩放,所以getBoundingbox返回的跟getContentSize一样,是原始图片大小,看看plist,就是280×150的,包括透明像素区域吧,对应plist中的sourceSize。
      getDisplayFrame()->getRect()对应是plist中的frame,值是48×82。
      看看SpriteFrameCache::addSpriteFramesWithDictionary函数源码。

欢迎留言