转载请注明来源:http://blog.csdn.net/pur_e/article/details/50599344
Box2d物理引擎还提供一个很重要的功能:碰撞检测。如马里奥游戏中,需要检测马里奥与怪物、蘑菇、金币等的碰撞,通过判断不同的碰撞点、碰撞对象做出不同的处理。
我们要在马里奥中实现的碰撞效果如下:
一、理论
Box2d通过设置碰撞监听来处理碰撞,在创建世界后,可以进行设置监听:
_b2World->SetContactListener(this);
设置的监听对象需要继承b2ContactListener类,实现函数
virtual void BeginContact(b2Contact* contact):碰撞开始时的回调函数,一般简单的碰撞检测使用;
virtual void EndContact(b2Contact* contact):碰撞发生后的回调函数,一般简单的碰撞检测使用;
virtual void PreSolve(b2Contact* contact, const b2Manifold* oldManifold):碰撞求解前的回调函数,求解就是指计算碰撞产生的冲击力,需要计算碰撞冲击力造成的破坏等效果时,需要使用此回调函数;
virtual void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse):碰撞求解后的回调函数,需要计算碰撞冲击力造成的破坏等效果时,需要使用此回调函数。
这四个回调方法中,前两个功能有限但使用起来简单,后两个提供的信息量大,但使用起来比较复杂,这个要根据游戏的具体要求而定,如果我们的游戏过程对物理要求不高,仅仅是实现碰撞检测功能,那么我们主要使用BeginContact(b2Contact* contact)这个回调函数就足够了,如果我们要处理碰撞之前和碰撞之后的效果,根据碰撞中产生的相互作用力来计算物理碰撞后的移动,则我们必须好好的利用全部这四个函数,它们联合作用起来,可以模拟出比较真实而复杂的物理碰撞效果。
发生碰撞后,Box2d会自动调用对应函数,开发者进行具体实现。
二、实践
1.规划
2.碰撞基类实现
其实就是一个虚类,定义碰撞处理函数,后面为了方便处理,还加了一个碰撞方向检测函数
class BaseContactNode : public Node{
public:
typedef enum{
DIRECTION_UP = 1,
DIRECTION_DOWN = -1,
DIRECTION_RIGHT = 2,
DIRECTION_LEFT = -2
}DIRECTION;
public:
//碰撞处理函数
virtual void beginContact(Node*, b2Contact*) = 0;
//获取碰撞方向,这里只简单判断上下左右
virtual int getContactDirection(Node* node,b2Contact* contact){
int isReverse = 1;
auto manifold = contact->GetManifold();
Node* other = static_cast(contact->GetFixtureB()->GetBody()->GetUserData());
//判断碰撞参考者
if((node != other && manifold->type == b2Manifold::e_faceB)
|| (node == other && manifold->type == b2Manifold::e_faceA)){
isReverse = -1;
}
int ret = 0;
if (manifold->localNormal.y == -1) {
ret = DIRECTION_UP ;
}else if(manifold->localNormal.y == 1){
ret = DIRECTION_DOWN;
}else if(manifold->localNormal.x == 1){
ret = DIRECTION_RIGHT;
}else if(manifold->localNormal.x == -1){
ret = DIRECTION_LEFT;
}
return ret * isReverse;
}
};
简单说一下其中用到的属性,box2d会将碰撞的大量信息保存下来,包括但不限于
3.调度者实现
实现很简单,直接分发^_^
void MarioScene::BeginContact(b2Contact *contact){
if (contact && contact->IsTouching())
{
auto A = static_cast(contact->GetFixtureA()->GetBody()->GetUserData());
auto B = static_cast(contact->GetFixtureB()->GetBody()->GetUserData());
auto pBaseBoxSprite = static_cast(A);
if(pBaseBoxSprite){
pBaseBoxSprite->beginContact(B, contact);
}
pBaseBoxSprite = static_cast(B);
if(pBaseBoxSprite){
pBaseBoxSprite->beginContact(A, contact);
}
}
}
4.碰撞处理
不同对象的碰撞处理不相同,这里拿马里奥和怪物两个做例子。
4.1 马里奥碰撞处理
注意:
下面的代码中可以看到,在碰到问号后,我的逻辑是生成一个蘑菇,本来我是想直接生成,同时添加body到Box2d世界中,但直接在一个Assert上报错了b2Assert(m_world->IsLocked() == false),这是因为在Box2d的碰撞处理中(世界步中in the middle of a time step),会将世界锁定,不允许修改,所以我们只能将状态缓存下来,在后续的update中进行添加
然后直接上代码:
void MarioPlayer::beginContact(Node* node, b2Contact* contact){
int direction =getContactDirection(this, contact);
if(direction == DIRECTION_UP){
//受力方向是上,说明已经碰到了地面,可以重新起跳
delStatus(MarioPlayer::STATUS_JUMP);
}
if (node && node->getTag() == MarioScene::CONTACT_MONSTER) {
//如果碰到了怪物
auto monster = static_cast(node);
if(direction != DIRECTION_UP && monster->getStatus() != MarioMonster::STATUS_DIED){
//如果不是踩到怪物头上且怪物不是死亡状态,则Game Over
MarioScene::die();
}else if(direction == DIRECTION_UP && monster->getStatus() == MarioMonster::STATUS_DIED){
//碰撞死后的乌龟顶端,则乌龟会以更快的速度移动,当然,这样写是因为我当前场景里只有乌龟
if(this->getPosition().x >= monster->getPosition().x){
monster->diedImpluse(DIRECTION_RIGHT);
}else{
monster->diedImpluse(DIRECTION_LEFT);
}
}
}else if(node && node->getTag() == MarioScene::CONTACT_BONUS){
//如果碰到问号,则生出一个蘑菇
auto marioScene = static_cast(this->getParent());
if(marioScene){
marioScene->addBonusList(node);
}
}
}
4.2 怪物碰撞处理
相对来说比较简单,直接上代码:
void MarioMonster::beginContact(Node* node, b2Contact* contact){
int direction = getContactDirection(this,contact);
if(node && node->getTag() == MarioScene::CONTACT_PLAYER){
if(direction == DIRECTION_DOWN){
//如果被马里奥踩到,死亡
die();
}
}else if(node && node->getTag() == MarioScene::CONTACT_MONSTER){
//如果被其他怪物碰到,死亡(其他乌龟是可以被用来做炮弹的)
die(false);
}
}
三、结语
至此,马里奥世界中,和怪物等的碰撞交互也实现了,已经很接近真实的马里奥世界,剩余的就是继续丰满,添加不同的怪物、增加不同的马里奥状态如开枪等、设计更大的地图、增加更多的地图原素了。