上一篇我们已经可以看到英雄和机器人都处于无敌状态,现在让他们互相残杀吧,所以接下来将要实现碰撞检测功能。先来看看下面这张图:
这里碰撞检测采用比较简单的矩形,可以看到英雄和机器人在攻击的时候会把拳头伸出去,我们可以把英雄分成两个矩形框,身体(被攻击的部分)矩形区域和拳头(攻击部分)的矩形区域,如上图的蓝色和红色区域,机器人是一样的。这样的话,英雄攻击机器人的时候,只需要检测英雄的红色区域跟机器人的蓝色区域是否有交集,如果这两个矩形有交集,则为击中;机器人攻击英雄也是一样的道理。既然原理弄明白了,现在就开始写代码吧。
首先在BaseSprite.h中添加:
1 2 3 4 5 | typedef struct _BoundingBox { cocos2d::Rect actual; cocos2d::Rect original; }BoundingBox; |
在BaseSprite类中添加:
1 2 3 4 5 6 | 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函数,在更新精灵位置的时候也需要更新碰撞盒的坐标。下面看实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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中添加:
1 2 3 4 5 6 7 8 9 10 | 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函数,添加下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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函数,添加:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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函数中添加:
1 2 3 | 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函数中添加:
1 2 3 | 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函数添加:
1 2 | m_pHero->setAttack(5); m_pHero->setHP(100); |
更新GameLayer.cpp的onHeroAttack函数:
1 2 3 4 5 6 7 8 9 10 11 | if (::collisionDetection(heroHitBox, enemyBodyBox)) { int damage = m_pHero->getAttack(); pEnemy->runHurtAction(); pEnemy->setHP(pEnemy->getHP() - damage); if (pEnemy->getHP() <= 0) { pEnemy->runDeadAction(); } } |
更新onEnemyAttack函数:
1 2 3 4 5 6 7 8 9 10 11 | 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中添加音频文件路径:
1 2 3 4 5 6 | #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函数最后添加:
1 2 | CocosDenshion::SimpleAudioEngine::getInstance()->playBackgroundMusic(PATH_BG_MUSIC, true ); CocosDenshion::SimpleAudioEngine::getInstance()->playEffect(PATH_HERO_TALK_EFFECT); |
更新onHeroAttack函数:
1 2 3 4 5 | if (::collisionDetection(heroHitBox, enemyBodyBox)) { //...... CocosDenshion::SimpleAudioEngine::getInstance()->playEffect(PATH_HERO_HIT_EFFECT); } |
更新onEnemyAttack函数:
1 2 3 4 5 6 7 8 9 10 11 12 | 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上吧。