上一篇我们已经可以看到英雄和机器人都处于无敌状态,现在让他们互相残杀吧,所以接下来将要实现碰撞检测功能。先来看看下面这张图:
这里碰撞检测采用比较简单的矩形,可以看到英雄和机器人在攻击的时候会把拳头伸出去,我们可以把英雄分成两个矩形框,身体(被攻击的部分)矩形区域和拳头(攻击部分)的矩形区域,如上图的蓝色和红色区域,机器人是一样的。这样的话,英雄攻击机器人的时候,只需要检测英雄的红色区域跟机器人的蓝色区域是否有交集,如果这两个矩形有交集,则为击中;机器人攻击英雄也是一样的道理。既然原理弄明白了,现在就开始写代码吧。
首先在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上吧。