赞
踩
当我们试图做一款复杂的游戏的时候,那游戏需要模拟现实世界的情境,比如模拟两个物体碰撞,模拟物体收到重力等。
当你的需求很简单时,就不要使用物理引擎。比如只需要确定两个对象是否有碰撞,结合使用节点对象的update
函数和Rect对象的containsPoint()
,intersectsRect()
方法可能就足够了。例如:
void update(float dt)
{
auto p = touch->getLocation();
auto rect = this->getBoundingBox();
if(rect.containsPoint(p))
{
//do something,intersection
}
}
这种检查交集以确定两个对象是否有碰撞的方法,只能解决非常简单的需求,无法扩展。比如你要开发一个游戏,一个场景有100个精灵对象,需要判断它们互相是否碰撞,如果使用这种方式那将非常复杂,同时性能消耗还会严重影响CPU的使用率和游戏运行的帧率,这游戏根本没法玩。
这个时候就需要物理引擎了,在模拟物理情景上,物理引擎的扩展性好,性能的消耗也低。像刚才提到的那个情景,使用物理引擎就能很好的解决。初次了解物理引擎的话,肯定会觉得很陌生,下面有个简单的例子:
//创建一个静态物理体
auto physicsBody = PhysicsBody::createBox(Size(65.0f,81.0f),PhysicsMaterial(0.1f,1.0f,0.0f));
physicsBody->setDynamic(false);
//创建一个精灵
auto sprite = Sprite::create("whiteSprite.png");
sprite->setPosition(Vec2(400,400));
//精灵使用physicsBody物理体
sprite->addComponent(physicsBody);
//添加联系事件监听器
auto contactListener = EventListenerPhysicsContact::create();
contactListener->onContactBegin = CC_CALLBACK_1(onContactBegin,this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);
代码流程:
PhysicsBody
对象创建Sprite
对象创建PhysicsBody
对象以组件的形式被添加到Sprite
对象onContactBegin()
事件为了更好的理解物理引擎,需要先了解下面的一些术语,概念。
刚体(Bodies)描述了抽象物体的物理属性,包括:质量、位置、旋转角度、速度和阻尼。Cocos2d-x中用PhysicsBody
对象表示刚体。当刚体和形状关联后,刚体对象才具有几何形状,未关联形状,刚体只是一个抽象物体的物理属性集。
材质(Material)描述了抽象物体的材质属性:
- density:密度,用于计算物体的质量
- friction:摩擦,用于模拟物体间的接触滑动
- restitution:恢复系数,模拟物体反弹的一个系数,系数一般设置为0到1之间。0代表不反弹,1代表完成反弹。
形状(Shape)描述了抽象物体的几何属性,将形状关联到刚体,刚体才具有几何形状。如果需要刚体具有复杂的形状,可以为它关联多个形状,每个形状对象都与一个PhysicsMaterial
相关,并且拥有以下属性:type,area,mass,moment,offset和tag。
- type:描述了形状的类别,如圆形,矩形,多边形等。
- area:用于计算刚体的质量,密度和面积决定了刚体的质量
- mass:刚体的质量,影响物体在给定的力下获得的加速度大小,物体在一个引力场中物体受到力的大小
- moment:刚体获得特定角加速度所需要的扭矩
- offset:在刚体的当前坐标中,相对于刚体中心的偏移量
- tag:形状对象的一个标签,你可能还记得,所有的Node对象都可以被分配一个tag,用来进行辨识,实现更容易访问。形状对象的tag作用也一样。
Cocos2d-x中预定义了这些形状对象:
PhysicsShape
:物体形状的基类PhysicsShapeCircle
:实心的圆形,无法用它实现一个空心圆PhysicsShapePolygon
:实心且外凸的多边形PhysicsShapeBox
:矩形,它是一种特殊的外凸多边形PhysicsShapeEdgeSegment
:表示一种线段.PhysicsShapeEdgePolygon
:空心多边形,由多个线段构成的多边形边缘。PhysicsShapeEdgeBox
:空心矩形,由四个线段组成的矩形边缘PhysicsShapeEdgeChain
: 链形,它可以有效的把许多边缘连接起来
连接(Contacts)和关节(joint)对象描述了刚体互相关联的方式。
世界(World)是现实物理世界的一个游戏模拟,容纳着所有被添加进去的抽象物体。你可以将刚体,形状,约束都添加到物理世界中,然后将整个世界作为一个整体进行更新。物理世界控制着所有元素的相互作用。其中,用物理API实现的许多互动都与*世界(World)*有关。
物理世界(PhysicsWorld)是Cocos2d-x进行物理模拟的核心对象。物理世界会同时发生很多事情,就像我们生活的世界一样。来想象一个简单的现实场景——厨房,你在思考的时候,就在脑中描绘出了一个厨房的物体世界!厨房世界里拥有一些物体,比如食物,刀具,电器,在这个世界中,这些物体会相互作用。它们会相互接触,并对接触做出反应。比如:用刀子切开食物,并把它放到电器中,做这样的一件事。刀子切到食物了吗?可能切到了,也可能还没有,还可能这个刀子根本就不适合做这个。
物理世界(PhysicsWorld)与场景(Scene)进行了深入的整合,只需要调用Scene
对象的initWithPhysics()
方法,就可以创建一个包含物理世界的场景,注意在初始化的时候要进行函数返回值的判断。initWithPhysics()
初始化成功返回true,失败返回false。
if(!Scene::initWithPhysics())
{
}
每一个物理世界(PhysicsWorld)都有与之相关的属性:
- gravity:全局重力,应用于整个物理世界,默认值为Vec2(0.0f,-98.0f);
- speed:物理世界的速度,这里的速度指的是则个模拟世界运行的一种比率,默认值为1.0
- updateRate:物理世界的刷新率,这里的刷新率指的是游戏引擎刷新时间与物理世界刷新时间的比值
- substeps:物理世界中每次刷新的子步数量
刷新物理世界的过程被称为步进,按照默认设置,物理世界会不停地进行自动刷新,这被称为自动步进。每一帧,都会不停地刷新,你可以通过setAutoStep(false)
禁用一个物理世界的自动步进,然后通过PhysicsWorld::step(time)
设定步进时间来手动刷新物理世界。游戏世界是按帧刷新的,物理世界可以通过子步(substeps)的设置,获得更加频繁的刷新,从而进行更精细的步进控制。
物理刚体(PhysicsBody)对象具有位置和速度,你可以在物理刚体上应用力(forces),运动(movement),阻尼(damping),冲量(impulses)等等。刚体可以是静态的,也可以是动态的,静态的刚体在模拟世界中不会移动,看起来就好像无限大的质量,动态的刚体则是一种完全仿真的模拟。刚体可以被玩家手动移动,更常见的是它们受到力的作用而移动。动态刚体可以与所有类型的刚体发生碰撞。Cocos2d-x提供了Node::setPhysicsbody()
方法实现节点对象和物理刚体对象的关联。
让我们来创建一个静态的物理刚体对象,和五个动态的物理刚体对象,并让五个动态的刚体对象动起来:
auto physicsBody = PhysicsBody::createBox(Size(65.0f,81.0f),PhysicsMaterial(0.1f,1.0f,0.0f));
physicsBody->setDynamic(false);
//创建一个精灵
auto sprite = Sprite::create("whiteSprite.png");
sprite->setPosition(s_centre);
this->addChild(sprite);
//将物理体应用于精灵
sprite->addComponent(physicsBody);
//增加五个动态刚体
for(int i=0;i<5;i++)
{
physicsBody = PhysicsBody::createBox(Size(65.0f,81.0f),PhysicsMaterial(0.1f,1.0f,0.0f));
//设置物体不受物理世界的引力力量的影响
physicsBody->setGravityEnable(false);
//设置物理体的初始速度
physicsBody->setVelocity(Vec2(cocos2d::random(-500,500),cocos2d::random(-500,500)));
physicsBody->setTag(DRAG_BODYS_TAG);
sprite = Sprite::create("blueSprite.png");
sprite->setPosition(Vec2(s_centre.x + cocos2d::random(-300,300),
s_centre.y + cocos2d::random(-300,300)));
sprite->addComponent(physicsBody);
this->addChild(sprite);
}
结果是,五个动态的物理刚体对象和一个静态的物理刚体对象不断的发生碰撞,如图:
刚体对象可以互相碰撞,当它们相互接触的时候,就认为发生了碰撞。当碰撞发生时,会触发一系列的事件,它可以被完全忽略。
碰撞筛选允许你启用或者阻止形状之间碰撞的发生,引擎支持使用类型,组位掩码来进行碰撞筛选。
Cocos2d-x有32个支持的碰撞类型,对于每个形状都可以指定其所属的类型。还可以指定有哪些类型可以与这个形状进行碰撞,这些是通过掩码来完成的。例如:
auto sprite1 = addSpriteAtPosition(Vec2(s_centre.x - 150,s_centre.y));
sprite1->getPhysicsBody()->setCategoryBitmask(0x02); //0010
sprite1->getPhysicsBody()->setCollisionBitmask(0x01); //0001
sprite1 = addSpriteAtPosition(Vec2(s_centre.x - 150,s_centre.y + 100));
sprite1->getPhysicsBody()->setCategoryBitmask(0x02); // 0010
sprite1->getPhysicsBody()->setCollisionBitmask(0x01); // 0001
auto sprite2 = addSpriteAtPosition(Vec2(s_centre.x + 150,s_centre.y),1);
sprite2->getPhysicsBody()->setCategoryBitmask(0x01); // 0001
sprite2->getPhysicsBody()->setCollisionBitmask(0x02); // 0010
auto sprite3 = addSpriteAtPosition(Vec2(s_centre.x + 150,s_centre.y + 100),2);
sprite3->getPhysicsBody()->setCategoryBitmask(0x03); // 0011
sprite3->getPhysicsBody()->setCollisionBitmask(0x03); // 0011
你可以通过检查判断类型和掩码来确定碰撞的发生:
if((shepeA->getCategoryBitmask() & shapeB->getCollisionBitmask()) == 0
|| (shapeB->getCategoryBitmask() & shapeA->getCollisionBitmask()) == 0)
{
//shapes can't collide
ret = false;
}
碰撞组允许你指定一个完整的组索引,你可以让具有相同组索引的形状总是一直碰撞(正索引)或者一直不碰撞(负索引和零索引)。对于组索引不同的形状。可以根据类型和掩码进行筛选,也就是说,组筛选比类型筛选具有跟高的优先级。
关节是把不同刚体连接在一起的一种方式,就好像人体的关节是把人体的不同部分连接在一起。关节连接了不同的刚体,刚体可以是静态的,每个关节类都是PhysicsJoint
的子类,可以通过设置joint->setCollisionEnable(false)
来避免相互关联的刚体互相碰撞。关节的定义需要你提供一些几何数据,大多关节都是通过锚点来定义的,其余一些关节有各自的定义方式。
PhysicsJointFixed
:固定点关节,将两个刚体固定在一个特定的点上。如果要创建一些后续会断裂的复合刚体,使用固定关节是非常合适的。PhysicsJointLimit
:限制关节,限制了两个刚体的最大距离,就好像它们被绳子连接了一样。PhysicsJointPin
:钉式关节,可以让两个刚体独立的围绕一个锚点进行旋转,就好像被钉在一起了一样。PhysicsJointDistance
:固定距离关节,设定了两个刚体间的固定距离。PhysicsJointSpring
:弹簧关节,就好像将一个弹簧连接了两个刚体,刚体会互相牵引和弹开。PhysicsJointRotarySpring
:弹簧旋转关节,类似弹簧关节,只是两个刚体位置的互相影响变成了旋转的互相影响。PhysicsJointRotaryLimit
:限制旋转关节,类似限制关节,只是两个刚体位置的互相影响变成了旋转的互相影响PhysicsJointRatchet
:与套筒扳手的工作类似。PhysicsJointGear
:传动关节,使一对刚体的角速度比值保持不变。PhysicsJointMotor
:马达关节,使一对刚体的相对角速度保持不变。
碰撞(Contact)是一种由物理引擎创建的用于管理两个形状碰撞的对象。Contact
对象不是由用户手动创建的,而是自动创建的。这里有两个香瓜的术语:
contact point
:碰撞点指两个形状想接触的那个点contact normal
:碰撞法线指从一个形状指向另一个形状的单位矢量
你可以从一个contact
对象中获取到PhysicsShape
,从而获取到刚体:
bool onContactBegin(PhysicsContact& contact)
{
auto bodyA = contact.getShapeA()->getBody();
auto bodyB = contact.getShapeB()->getBody();
return true;
}
你可以通过碰撞监听器来访问碰撞,碰撞监听器支持四种事件:begin,pre-solve,post-solve,separate。
- begin:收到这个事件时两个形状刚开始接触。在回调函数中返回true可以使碰撞继续被处理,若返回false,则物理引擎会将整个碰撞忽略掉,
preSolve()
和postSolve()
回调函数也会被跳过。不过当两个形状停止重叠时,仍然可以收到separete
事件。- pre-solve:收到这个事件时两个形状接触在一起。如果在回调函数中返回false,那么物理引擎会忽略掉这次碰撞,如果返回true,碰撞会继续被处理。此外,可以使用
setResitution()
,setFriction()
,或setSurfaceVelocity()
方法设置自定义的恢复系统,摩擦,表面速度,从而覆盖默认的碰撞属性。- post-solve:收到这个事件时两个形状已经接触,并且它们的碰撞已被处理。
- separate:收到这个事件时两个形状刚刚停止了接触。
可以使用EventListenerPhysicsContactWithBodies
,EventListenerPhysicsContactWithShapes
,EventListenerPhysicsContactWithGroup
来监听感兴趣的刚体,形状,组的一些事件。额外的,还需要设置与物理碰撞相关的掩码。注意:默认情况下,只是创建事件监听器,是收不到碰撞事件的。
示例:
bool init()
{
//创建一个静态物理刚体
auto sprite = addSpriteAtPosition(s_centre.1);
sprite->setTag(10);
sprite->getPhysicsBody()->setContactTestBitmask(0xFFFFFFFF);
sprite->getPhysicsBody()->setDynamic(false);
//添加一个事件监听器
auto contactListener = EventListenerPhysicsContact::create();
contactListener->onContactBegin = CC_CALLBACK_1(PhysicsDemoCollisionProcessing::onContactBegin, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);
this->schedule(CC_SCHEDULE_SELECTOR(PhysicsDemoCollisionProcessing::tick), 0.3f);
return true;
}
void tick(float dt)
{
auto sprite1 = addSpriteAtPosition(Vec2(s_centre.x + cocos2d::random(-300,300),
s_centre.y + cocos2d::random(-300,300)));
auto physicsBody = sprite1->getPhysicsBody();
physicsBody->setVelocity(Vec2(cocos2d::random(-500,500),cocos2d::random(-500,500)));
physicsBody->setContactTestBitmask(0xFFFFFFFF);
}
bool onContactBegin(PhysicsContact& contact)
{
auto nodeA = contact.getShapeA()->getBody()->getNode();
auto nodeB = contact.getShapeB()->getBody()->getNode();
if (nodeA && nodeB)
{
if (nodeA->getTag() == 10)
{
nodeB->removeFromParentAndCleanup(true);
}
else if (nodeB->getTag() == 10)
{
nodeA->removeFromParentAndCleanup(true);
}
}
return true;
}
你肯定有站在一个地方往四周看的经历,你能看到离你近的地方,也能看到离你远的地方,你能判断它们离你有多远。物理引擎也提供了类似的空间查询功能。
Cocos2d-x提供PhysicsWorld
对象支持点查询,射线查询和矩形查询。
当年碰到什么东西,比如说你的桌子的时候,你可以将这种情景作为一个点查询的例子。点查询是检查一个点周围的一定距离内是否有物体。通过点查询你可以找到一个物体中距离某定点最近的点,或者找到距离一个定点最近的物体,这非常适合于判断鼠标点击拾取的对象,也可以利用它进行一些其它的简单感知。
当你四处看的时候,在你视线内的某个物体肯定会引起你的注意,你可以将这种情景作为一个射线查询的例子。射线查询是检查从一个定点发出的射线是否相交于一个物体,如果相交可以获取到一个交叉点,这非常适合于判断子弹(忽略子弹的飞行时间)是否命中。
示例:
void tick(float dt)
{
Vec2 d(300 * cosf(_angle), 300 * sinf(_angle));
Vec2 point2 = s_centre + d;
if (_drawNode)
{
removeChild(_drawNode);
}
_drawNode = DrawNode::create();
Vec2 points[5];
int num = 0;
auto func = [&points, &num](PhysicsWorld& world,
const PhysicsRayCastInfo& info, void* data)->bool
{
if (num < 5)
{
points[num++] = info.contact;
}
return true;
};
s_currScene->getPhysicsWorld()->rayCast(func, s_centre, point2, nullptr);
_drawNode->drawSegment(s_centre, point2, 1, Color4F::RED);
for (int i = 0; i < num; ++i)
{
_drawNode->drawDot(points[i], 3, Color4F(1.0f, 1.0f, 1.0f, 1.0f));
}
addChild(_drawNode);
_angle += 1.5f * (float)M_PI / 180.0f;
}
矩阵查询提供了一种快速检查区域中有哪些物体的方法,实现起来非常容易:
auto func = [](PhysicsWorld& world, PhysicsShape& shape, void* userData)->bool
{
//Return true from the callback to continue rect queries
return true;
}
scene->getPhysicsWorld()->queryRect(func, Rect(0,0,200,200), nullptr);
这是在制作Logo击碎时使用矩形查询的例子:
如果你希望在刚体周围绘制红框来帮助调试,那么可以简单的将这两个添加到物理场景的初始化代码中。你当然也可以学习官方的测试项目,加一个菜单,在菜单的回调函数里控制是否打开调试功能。
Director::getInstance()->getRunningScene()->getPhysics3DWorld()->setDebugDrawEnable(true);
Director::getInstance()->getRunningScene()->setPhysics3DDebugCamera(cameraObjecct);
使用内置的物理引擎是个好的选择,它稳定又强大。不过,如果你的确想使用一些其他的物理引擎,只需要在base/ccConfig.h
文件中将CC_USE_PHYSICS得之改为0禁用内置的物理引擎即可。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。