Cocos2d-X Physic 3D 教程-刚体(1)

发表于2015-07-31
评论0 4.9k浏览
 Cocos2d-X Physic 3D 教程
在这篇教程中,我们将简要介绍一下,如何在Cocos引擎中使用3D物理。Physic 3D 是基于Bullet引擎进行封装,对这个引擎感兴趣的朋友可以直接访问其官网:
http://bulletphysics.org/wordpress/
初始化物理引擎

通过cocos2dX新建的项目,默认是没有开启对Bullet引擎的集成的,要正确的使用cocos2dx中的Physic 3D模块,我们首先需要将C_ENABLE_BULLET_INTEGRATION这个宏给打开,我们先将我们期望显示的场景的物理功能进行初始化:
initWithPhysics();
为了方便调试,我们将物理的线框给显示出来。
getPhysics3DWorld()->setDebugDrawEnable(false);

这样3D物理系统的初始化就已经做好了
刚体(rigidbody)
详细的介绍请参见: http://en.wikipedia.org/wiki/Rigid_body

刚体指的是在运动中和受力情况下,形状以及大小均不会发生改变的物体。这种物体严格来说,在现实世界里并不存在,但是,在游戏开发中,大多数的3D物体我们都将其视为刚体来进行物理的模拟。
在这一节里,我们将教会大家如何创建刚体,并如何搭建一个简单的Demo
创建刚体
首先,我们需要定义一个刚体的描述类型的对象,这个对象包括了该刚体的形状,以及质量,还有局部变换等信息,一般的,我们只定义刚体的质量以及形状就足够了。
Physics3DRigidBodyDesdes;
des.mass =yourRigidMass;
des.shape= yourRigidShape;

刚体的形状通常来说,一般较常用的有长方体(Box),球体(sphere)以及圆柱体(cylinder),而其中又以前两者最为常用。
以下是创建一个长方体形状的方法:
des.shape= Physics3DShape::createBox(Vec3(60.0f,1.0f, 60.0f));
请注意,这个形状仅仅代表了参与物理计算时物体的大小,和本身物体的大小毫无关系。Box形状我们通常将其定义为3D模型局部坐标下AABB的大小。

接着我们就可以用它来创建一个模型了:
automyBox= PhysicsSprite3D::create("Sprite3DTest/box.c3t", &des);
在模型创建好之后,我们很可能对该模型的初始摆放进行了一些变换,如平移、旋转等,我们需要将物理组件同步至这些变换。
myBox->syncToNode();
这个函数会将从局部坐标到世界坐标的变换矩阵应用至物理物体上,但是请注意缩放变换不起作用。
完成这一步后,我们来看最后一步,指定业务逻辑端Node 与物理引擎端坐标变换的同步关系。物理引擎在游戏运行时,是一个相对独立的一个系统。我们可以将当前Node的变换传递给物理系统内,同时,我们也可以将进行物理模拟后的变换设到Node中。通常来讲,在设好物体初始的变换后,我们一般不直接对其进行干预,所以这里我们设置为:
sprite->setSyncFlag(Physics3DComponent::PhysicsSyncFlag::PHYSICS_TO_NODE);
如果一个物体在物理世界中是静态的,那么没有必要在从物理系统里接受它的变化了,我们可以直接简单的将其设置为:
sprite->setSyncFlag(Physics3DComponent::PhysicsSyncFlag::NONE);

例子:射击方块
现在,我们使用刚才所说的一些创建刚体的方法,来制作一个通过触摸来射击出带物理效果的方块的小例子:
创建地面
首先,我们先创建一个地面,这个地面是一个静态的刚体,为了确保其静止,我们将其的质量设为零,这样的话,地面就不会下沉,且当射击出的方块击中了地面后,地面因为受到的冲量为零而不会被弹走:

Physics3DRigidBodyDesrbDes;
rbDes.mass= 0.0f;
rbDes.shape= Physics3DShape::createBox(Vec3(60.0f, 1.0f, 60.0f));
autofloor= PhysicsSprite3D::create("Sprite3DTest/box.c3t", &rbDes);
floor->setTexture("Sprite3DTest/plane.png");
floor->setScaleX(60);
floor->setScaleZ(60);
this->addChild(floor);
floor->setCameraMask((unsignedshort)CameraFlag::USER1);
floor->syncToNode();
//static object sync is not needed
floor->setSyncFlag(Physics3DComponent::PhysicsSyncFlag::NONE);

射击方块的实现
现在我们来看如何射击方块,射击方块的大体思路是将视口坐标系下的一个点(即触摸的坐标)经过视图变换->投影变换->视口变换逆变换后,通过获得的在世界坐标系下的坐标点构造出一条射线,然后创建一个刚体,让其从相机位置为起始,朝着射线方向移动。


首先我们先构造出这条射线,Camera::unproject方法就提供了做上述逆变换的能力。
autolocation= touches[0]->getLocationInView();
Vec3nearP(location.x,location.y,-1.0f), farP(location.x,location.y,1.0f);
nearP= _camera->unproject(nearP);
farP= _camera->unproject(farP);
Vec3dir(farP- nearP);



接着,我们创建一个方块的刚体:
Physics3DRigidBodyDesrbDes;
Vec3linearVel= des- _camera->getPosition3D();
linearVel.normalize();
linearVel*= 100.0f;
rbDes.originalTransform.translate(_camera->getPosition3D());
rbDes.mass= 1.f;
rbDes.shape= Physics3DShape::createBox(Vec3(0.5f, 0.5f, 0.5f));
autosprite= PhysicsSprite3D::create("Sprite3DTest/box.c3t", &rbDes);
sprite->setTexture("Images/Icon.png");
autorigidBody= static_cast<Physics3DRigidBody*>(sprite->getPhysicsObj());
rigidBody->setLinearFactor(Vec3::ONE);
rigidBody->setLinearVelocity(linearVel);
rigidBody->setCcdMotionThreshold(0.5f);
rigidBody->setCcdSweptSphereRadius(0.4f);
在这里创建和上面不同之处在于,对于刚体的描述结构来说,它设置了初始位置,这个位置在相机的那个地方,其次,我们根据射线的方向计算出了一个线速度。我们就是通过该速度让方块射出去的。

rigidBody->setCcdMotionThreshold(0.5f);
rigidBody->setCcdSweptSphereRadius(0.4f);

后两句话比较有意思,首先这两个函数标签中的“Ccd”代表着的是continuous collisiondetection,即所谓的连续碰撞检测。在一般物体的碰撞检测中,我们只要判断当前时刻下,两个物体AB是否相交就够了。可是在一些高速运动的物体里,比方说子弹,因为其速度足够快,所以在某一时刻检测它与其他物体并未发生相交,很可能是因为速度太快,而穿透过去了,如下图所示:





因此我们必须要使用连续碰撞检测来避免这种状况的发生,但是CCD算法实现效率较一般的碰撞检测算法来说比较低,所以我们不可能给每一个物体、在任意时刻都使用CCD来进行碰撞检测,setCcdMotionThresholdsetCcdSweptSphereRadius实际上就是分别指定了CCD发生的速度阈值以及用于连续碰撞检测算法时扫过的球半径。通常来说,对于一般的物理物体我们不需要对其进行特殊设置,只需要对子弹这种高速运动物体进行设置就已经足够了。
以下是实际截图:


如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引