Cocos2d-x3.2总结:使用物理引擎进行碰撞检测

    Cocos2d-x采用了Box2D物理引擎和Chipmunk物理引擎来模拟真实的物理世界。Box2D几乎能模拟所有的物理效果,而chipmunk则是个更轻量的引擎等。而在Cocos2d-x3.2版本中默认采用Chipmunk,所以我就以Chip-munk来讲解。并且为了简化物理引擎和Cocos2d-x的交接,Cocos2d-x直接提供函数来设置物体参数,不需要我们采用Chip-munk原生的函数来设置,这大大简化的代码的编写。
    Chipmunk是采用C语言编写的2D刚体物理仿真库,包含4种基本的对象类型,分别是:
    空间:空间是Chipmunk中模拟对象的容器。你将刚体、形状、关节添加进入一个空间,然后将空间作为一个整体进行更新。空间控制着所有的刚体、形状和约束之间的相互作用。
    刚体:一个刚体容纳着一个对象的物理属性(如质量、位置、角度、速度等)。默认情况下,它并不具有任何形状,直到你为它添加一个或者多个碰撞形状进去。如果你以前做过物理粒子,你会发现它们的不同之处是刚体可以旋转。在游戏中,通常刚体都是和一个精灵一一对应关联的。你应该构建你的游戏以便可以使用刚体的位置和角度来绘制你的精灵。
    碰撞形状:因为形状与刚体相关联,所以你可以为一个刚体定义形状。为了定义一个复杂的形状,你可以给刚体绑定足够多的形状。形状包含着一个对象的表面属性如摩擦力、弹性等。
    约束/关节:约束和关节被用来描述刚体之间是如何关联的
    人们经常对Chipmunk中的刚体和碰撞形状以及两者与精灵之间的关系产生混淆。精灵是对象的可视化表现,而碰撞形状是定义对象应该如何碰撞的不可见的属性。精灵和碰撞形状两者的位置和角度都是由刚体的运动控制的。通常你应该创建一个游戏对象类型,把这些东西捆绑在一起。
   在Cocos2d-x3.2中,物理世界被融入到Scene中,即当创建一个场景时,就可以指定这个场景是否使用物理引擎。
sprite自带body属性,直接设置body,而形状、约束等属性添加到body中即可。碰撞的检测通过事件分发器来监控,所以你首先要创建监听器--EventListenerPhysicsContact,再把其添加到分发器中。

    说了那么多铺垫,接下来通过代码来讲解具体使用。
    第一步,新建一个工程,并且删除HelloWorldScene.h/cpp中代码,在HelloWorldScene.h中添加如下代码:

    代码:

#include "cocos2d.h"


class HelloWorld : public cocos2d::Layer
{
public:
    static cocos2d::Scene* createScene();


    virtual bool init();  
    
    CREATE_FUNC(HelloWorld);


virtual void onEnter();


bool onContactBegin(const cocos2d::PhysicsContact& contact);


void setPhyWorld(cocos2d::PhysicsWorld* world) { m_world = world; };


cocos2d::Sprite* ballOne; // 第一个球
cocos2d::Sprite* ballTwo; // 第二个球
cocos2d::PhysicsWorld* m_world; // 空间,物体世界
};

m_world是用来保存在scene中创建的物理世界,也就是上面说的空间,以备得到空间中的参数,如重力系数等。 

HelloWorldScene.cpp代码如下:


#include "HelloWorldScene.h"

USING_NS_CC;

Scene* HelloWorld::createScene()
{
    // 创建有物理空间的场景
auto scene = Scene::createWithPhysics();
// 设置Debug模式,你会看到物体的表面被线条包围,主要为了在调试中更容易地观察
scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
auto layer = HelloWorld::create();
// 把空间保持在我们创建的层中,就是上面所说的m_world的作用,方便后面设置空间的参数
layer->setPhyWorld(scene->getPhysicsWorld());
scene->addChild(layer);

return scene;
}


bool HelloWorld::init()
{
if (!Layer::init())
{
return false;
}

auto visibleSize = Director::getInstance()->getVisibleSize();
auto origin = Director::getInstance()->getVisibleOrigin();

// 创建并设置第一个球
ballOne = Sprite::create("Ball.jpg");
ballOne->setPosition(visibleSize.width / 2, visibleSize.height / 2);


// 创建物体,并且物体的形状为圆形,第一个参数为半径,第二个参数为物体材质
// 第三个参数为边的厚度,就是在Debug模式下看到的物体外面线条的厚度,默认为0
auto ballBodyOne = PhysicsBody::createCircle(ballOne->getContentSize().width / 2, PHYSICSBODY_MATERIAL_DEFAULT);

// 设置物体的恢复力
ballBodyOne->getShape(0)->setRestitution(1.0f);


// 设置物体的摩擦力
ballBodyOne->getShape(0)->setFriction(0.0f);

// 设置物体密度
ballBodyOne->getShape(0)->setDensity(1.0f);

// 设置物体是否受重力系数影响
ballBodyOne->setGravityEnable(false);

// 设置物体的冲力
auto force = Vect(500000.0f, 500000.0f);
ballBodyOne->applyImpulse(force);

// 把物体添加到精灵中
ballOne->setPhysicsBody(ballBodyOne);
// 设置标记
ballOne->setTag(1);
// 将精灵添加到层中
this->addChild(ballOne);

// 创建并设置第二个球
ballTwo = Sprite::create("Ball.jpg");
ballTwo->setPosition(visibleSize.width / 3, visibleSize.height / 3);

// 创建物体,并且物体的形状为圆形,第一个参数为半径,第二个参数为物体材质
// 第三个参数为边的厚度,就是在Debug模式下看到的物体外面线条的厚度,默认为0
auto ballBodyTwo = PhysicsBody::createCircle(ballOne->getContentSize().width / 2, PHYSICSBODY_MATERIAL_DEFAULT);

// 设置物体的恢复力
ballBodyTwo->getShape(0)->setRestitution(1.0f);

// 设置物体的摩擦力
ballBodyTwo->getShape(0)->setFriction(0.0f);

// 设置物体密度
ballBodyTwo->getShape(0)->setDensity(1.0f);

// 设置物体是否受重力系数影响
ballBodyTwo->setGravityEnable(false);

// 设置物体的冲力
force = Vect(-500000.0f, -500000.0f);
ballBodyTwo->applyImpulse(force);

// 把物体添加到精灵中
ballTwo->setPhysicsBody(ballBodyTwo);
// 设置标记
ballTwo->setTag(2);
// 将精灵添加到层中
this->addChild(ballTwo);

// 创建一个精灵
auto edgeSpace = Sprite::create();
edgeSpace->setPosition(visibleSize.width / 2, visibleSize.height / 2);
// 创建一个盒子,用来碰撞
auto boundBody = PhysicsBody::createEdgeBox(visibleSize, PHYSICSBODY_MATERIAL_DEFAULT, 3); 
// 设置盒子的摩擦力
boundBody->getShape(0)->setFriction(0.0f);
// 设置盒子的恢复力
boundBody->getShape(0)->setRestitution(1.0f);
// 把盒子添加到精灵中
edgeSpace->setPhysicsBody(boundBody);
// 设置标记
edgeSpace->setTag(0);
// 将精灵添加到层中
this->addChild(edgeSpace);

// 设置掩码
ballBodyOne->setCategoryBitmask(0x0001);
ballBodyOne->setCollisionBitmask(0x0001);
ballBodyOne->setContactTestBitmask(0x0001);

ballBodyTwo->setCategoryBitmask(0x0001);
ballBodyTwo->setCollisionBitmask(0x0001);
ballBodyTwo->setContactTestBitmask(0x0001);

return true;
}
    
void HelloWorld::onEnter()
{
Layer::onEnter();
// 添加监听器
auto contactListener = EventListenerPhysicsContact::create();
// 设置监听器的碰撞开始函数
contactListener->onContactBegin = CC_CALLBACK_1(HelloWorld::onContactBegin, this);
// 添加到事件分发器中
_eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);
}

bool HelloWorld::onContactBegin(const cocos2d::PhysicsContact& contact)
{


Sprite* spriteA = (Sprite*)contact.getShapeA()->getBody()->getNode();
Sprite* spriteB = (Sprite*)contact.getShapeB()->getBody()->getNode();
int tagA = spriteA->getTag();
int tagB = spriteB->getTag();
if (tagA == 1 && tagB == 2 || tagA == 2 && tagB == 1)
{
spriteA->removeFromParent();
spriteB->removeFromParent();
}

return true;
}

(1)上述代码中都带有注释,在onEnter函数中添加碰撞检测监听器并且把监听器添加到事件分发器中,并且添加处理函数。

(2)以下是一些关于碰撞检测的小知识:

    categoryBitmask:
    分类掩码,定义了物体属于哪个分类。场景中的每个物理刚体可以被赋值一个多达32位的值(因为categoryBitmask为int型),每个对应32位掩码中的每一位,你在你的游戏中定义掩码值。结合collisionBitMask和contactTestBitMask属性, 你可以定义哪些物理刚体相互作用并且你的游戏何时接受这些相互作用的通知。默认值为0xFFFFFFFF(所有位都被设置)。
    contactTestBitmask:
    接触测试掩码,定义哪些刚体分类可以与本刚体产生相互作用的通知。当两个刚体在同一个空间,即物理世界中,每个刚体的分类掩码会和其他刚体的接触测试掩码进行逻辑与的运算。如果任意一个比较结果为非零值,产生一个PhysicsContact对象并且传递到物理世界协议中,这里协议指我们的监听器对应的回调函数。 为了最好的性能,仅设置你感兴趣的接触测试掩码中的位,也就是说通过设置接触测试掩码,你可以决定发生碰撞后,回调函数是否有响应。默认值为0x00000000(所有位都被清除)。
    collisionBitmask:
    碰撞掩码,定义了哪些物理刚体分类可以和这个物理刚体发生碰撞。当两个物理刚体相互接触时,可能发生碰撞。这个刚体的碰撞掩码和另一个刚体的分类掩码进行逻辑与运算比较。如果结果是一个非零值,这个刚体会发生碰撞。每个刚体独立选择接受与哪个刚体发生碰撞。例如,你可以使用此掩码来忽略那些对于本刚体的速度有影响的刚体碰撞,也就是说你可以使用此掩码使得本刚体与某些刚体碰撞不会对本刚体产生影响。默认值为0xFFFFFFFF(所有位都被设置)。
    从上面三个掩码的说明中,我们可以做一个小结。假设刚体A的接触测试掩码和碰撞掩码已知,刚体B的分类掩码决定了能否和A进行碰撞和在碰撞的前提下能否发出PhysicsContact对象触发回调函数。如果B的分类掩码与A的碰撞掩码做逻辑与运算的结果为0,则不会发生碰撞,因此也不会继续和A的接触测试掩码进行逻辑与运算。如果B的分类掩码与A的碰撞掩码做逻辑与运算的结果非0,则发生碰撞,并且B的分类掩码继续与A的接触测试掩码做逻辑与运算,如果结果非0,则发出PhysicsContact对象触发回调函数。
    在上面两球碰撞的例子中,由于categoryBitmask的默认值为0xFFFFFFFF,contactTestBitmask的默认值为0x00000000,collisionBitmask默认值为0xFFFFFFFF,所以ballOne的分类掩码与ballTwo的碰撞掩码做逻辑与的结果为0xFFFFFFFF即非0,即可以发生碰撞,ballOne的分类掩码继续与ballTwo的接触测试掩码做逻辑与运算,结果为0,因此不会发出PhysicsContact对象,不会触发回调函数,所以我们上面的onContactBegin不会被触发,所以两球碰撞后不会消失。
    现在,修改碰撞编码,使得两球可以碰撞,并且碰撞后发出PhysicsContact对象并触发回调函数使得两球消失。使用16进制表示只是为了更好观察逻辑与运算的结果,边缘盒子的值保留为默认值。

(3)到此为止,通过上面详细的讲述,使用Cocos2d-x3.2物理引擎进行碰撞检测算正式讲完了。






阅读更多
换一批

没有更多推荐了,返回首页