搜索

Cocos2dx物理引擎碰撞检测总结

gecimao 发表于 2019-06-18 19:36 | 查看: | 回复:

  通常在游戏简单逻辑判断和模拟真实的物理世界时,我们只需要在定时器中判断游戏中各个精灵的条件是否满足判断条件就可以了。例如,在飞机大战中,判断我方子弹和敌机是否发生碰撞一般在定时器中通过敌机所在位置的矩形是否包括了子弹的位置来判断是否发生碰撞。在消除类游戏中,判断在y轴或x轴上是否要消除相同物品一般在定时器中通过循环来检测在某个方向上是否有连续的相同物品满足消除个数来移除精灵就可以了。

  但是要进行复杂的逻辑判断和模拟真实的物理世界时,完全靠自己手写代码来判断工作量太大了。例如,在愤怒的小鸟中,首先,你要模拟小鸟受力后弹出的轨迹路线。然后,你要判断小鸟和箱子是否发生碰撞。最后,假如小鸟和箱子碰撞后,你还要模拟小鸟和箱子往下倒下的轨迹路线。这时候,物理引擎就帮上忙了,它通过我们设定参数来为刚性物体赋予真实的物理属性的方式来计算物体运动、旋转和碰撞。同时,我们只要在回调函数中编写代码就可以处理不同的情况了。

  比较出名的游戏引擎有EADICE的寒霜引擎、BigWorld公司的BigWorld引擎、Emergent公司的Gamebryo引擎、EPIC公司的虚幻引擎、搜狐畅游公司的黑火引擎、完美世界公司的Athena引擎等。游戏引擎包含以下系统:渲染引擎(即“渲染器”,含二维图像引擎和三维图像引擎)、物理引擎、碰撞检测系统、音效、脚本引擎、电脑动画、人工智能、网络引擎以及场景管理。而对于手机游戏来说,并不需要功能那么多且复杂的引擎,毕竟手机的资源有限不像电脑那么高性能。

  因此,Cocos2d-x采用了Box2D物理引擎和Chipmunk物理引擎来模拟线D几乎能模拟所有的物理效果,而chipmunk则是个更轻量的引擎等。而在Cocos2d-x3.2版本中默认采用Chipmunk,所以我就以Chip-munk来讲解。并且为了简化物理引擎和Cocos2d-x的交接,Cocos2d-x直接提供函数来设置物体参数,不需要我们采用Chip-munk原生的函数来设置,这大大简化的代码的编写。

  Chipmunk是采用C语言编写的2D刚体物理仿线种基本的对象类型,分别是:

  空间:空间是Chipmunk中模拟对象的容器。你将刚体、形状、关节添加进入一个空间,然后将空间作为一个整体进行更新。空间控制着所有的刚体、形状和约束之间的相互作用。

  刚体:一个刚体容纳着一个对象的物理属性(如质量、位置、角度、速度等)。默认情况下,它并不具有任何形状,直到你为它添加一个或者多个碰撞形状进去。如果你以前做过物理粒子,你会发现它们的不同之处是刚体可以旋转。在游戏中,通常刚体都是和一个精灵一一对应关联的。你应该构建你的游戏以便可以使用刚体的位置和角度来绘制你的精灵。

  碰撞形状:因为形状与刚体相关联,所以你可以为一个刚体定义形状。为了定义一个复杂的形状,你可以给刚体绑定足够多的形状。形状包含着一个对象的表面属性如摩擦力、弹性等。

  人们经常对Chipmunk中的刚体和碰撞形状以及两者与精灵之间的关系产生混淆。精灵是对象的可视化表现,而碰撞形状是定义对象应该如何碰撞的不可见的属性。精灵和碰撞形状两者的位置和角度都是由刚体的运动控制的。通常你应该创建一个游戏对象类型,把这些东西捆绑在一起。

  在Cocos2d-x3.2中,物理世界被融入到Scene中,即当创建一个场景时,就可以指定这个场景是否使用物理引擎。

  sprite自带body属性,直接设置body,而形状、约束等属性添加到body中即可。碰撞的检测通过事件分发器来监控,所以你首先要创建--EventListenerPhysicsContact,再把其添加到分发器中。

  第一步,新建一个工程,并且删除HelloWorld.h/cpp中代码,在HelloWorld.h中添加代码1,它的作用是声明变量。

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

  第二步,在createScene()中添加代码2,它的作用是创建一个有物体空间的场景。

  在代码3中,我们看到了物体的创建并且创建是就自带形状,然后物体的物理属性。一个物体可以先创建,然后再添加形状。一个物体可以有多个形状,只要不冲突就可以,并且同一个物体多个形状之间不发生碰撞,因为他们属于同一碰撞组。所以我们看到getShape(0)的调用,因为我们添加形状时默认从零开始,上面我们只添加了一个形状。添加形状代码在Cocos2d-x3.2代码为PhysicsBody::addShape()。你可以在...\cocos2d\cocos\physics目录下CCPhysicsBody.cpp中看到代码4。代码4:

  代码4为物体在同一组中添加形状,如果你想再进一步追寻下去,可以继续转到不同函数的定义中,最后你会看到Chipmunk原生的代码。对于不规则的形状,我们可以使用辅助工具--VertexHelper、PhysicsEditor等等来生成点数组来设置形状,在这里,我就使用简单得形状来做示例,如果后面有时间,我也会写一篇使用辅助工具来生成不规则形状的文章。

  在代码3中,恢复力和摩擦力要成对使用,即使你把恢复力设置为1.0而不设置摩擦力为0,依旧不是完全弹性碰撞,自己可以修改调试就可以知道正确与否。关于弹性碰撞可以参考一些物理书。对于恢复力,在Chipmunk中文手册有“0.0表示没有弹性,1.0b表示“富有”弹性。然而由于存在模拟误差,不推荐使用1.0或更高的值,碰撞的弹性是由单个形状的弹性相乘得到”。对于摩擦力,在Chipmunk中文手册有“Chipmunk使用的是库仑摩擦力模型,0.0值表示无摩擦。碰撞间的摩擦是由单个形状的摩擦相乘找到”。

  在代码3中,冲力的设置为在x轴和y轴上各为500000.0,最后根据物理中合力来计算结果。applyImpulse在离重心相对偏移量为r的位置施加冲量于物体上,默认偏移量为0,你可以在...\cocos2d\cocos\physics目录下CCPhysics-Body.cpp中看到代码5。并且在绝对坐标系中施加力或者冲量,并在绝对坐标系中产生相对的偏移(偏移量相对于重心位置,但不随刚体旋转)。Chipmunk使用的坐标是x轴向右为正,y轴向上为正。关于摩擦力,你可以在...\cocos2d\cocos\physics目录下CCPhysicsShape.cpp中看到代码6。

  在代码3,setDensity的作用是设置密度,其实它最后通过乘以面积转变成质量来设置质量的。根据物理公式在2维中密度乘以面积等于质量。所以,你能在...\cocos2d\cocos\physics目录下CCPhysicsShape.cpp中看到代码7。

  代码7首先是判断密度是否小于0,然后再判断密度是否是无限大,最后才设置密度。

  在代码3中,setTag的作用是当发生碰撞时能让我们知道那两个精灵发生碰撞,具体见下文碰撞检测。在代码3中,setDynamic函数,此作用为设置物体是不是静态刚体。如果你设置了物体为静态,你再设置其他参数,也是没有作用的,原因是静态物体不更新状态。关于静态刚体,Chipmunk中文手册有这么一段,如下:

  静态刚体有两个目的。最初,它们被加入用来实现休眠功能。因为静态刚体不移动,Chipmunk知道让那些与静态刚体接触或者连接的物体安全的进入休眠。接触或连接常规游离刚体的物体从不允许休眠。静态刚体的第二个目的就是让Chipmunk知道,关联到静态刚体的碰撞形状是不需要更新碰撞检测数据的。Chipmunk也不需要操心静态物体之间的碰撞检测。通常所有的关卡几何图形都会被关联到一个静态刚体上除了那些能够移动的东西,例如平台或门等。在Chipmunk5.3版本之前,你要创建一个无限大质量的游离刚体,通过`cpSpaceAddStaticShape()`来添加静态形状。现在你不必这样做了,并且如果你想使用休眠功能也不应该这样做了。每一个空间都有一个专用的静态刚体,你可以使用它来添加静态形状。Chipmunk也会自动将形状作为静态形状添加到静态刚体上。

  也就是说,如果要让两个物体发生碰撞,至少有一个不为静态,因为两个都为静态,就不用更新数据了。如果两个物体都是静态的,即使你强行用runAction来发生碰撞,两个物体也不会发生碰撞。具体见下文碰撞检测。这个在我调试一个例子中,花了半天时间去看Chipmunk中文手册后才调试出来的。

  上面我们可以看到两个球在一个盒子中弹来弹去。这里我只是给出了简单的例子来说明,没有给出通过关节和约束来把两个物体关联在一起的例子。如果你对两个物体通过关节和约束关联在一起不是很明白的话,可以在打开以下的视频:

  对于要处理相互碰撞事件来说,还要进行以下步骤。这里我们想看到的效果是两球碰撞后,两球消失。

  第四步,我们要在onEnter函数中添加碰撞检测并且把添加到事件分发器中,并且添加处理函数。我们要重写onEnetr函数如代码8,添加处理函数如代码9。

  对于代码8,你是否有这样的疑问--是否可以在其他地方添加,而不是在onEnter函数中。这是可以的,只要在发生碰撞前添加就可以,这里在onEnter添加也只是为了在发生碰撞前添加而已。关于CC_CALLBACK--回调函数的更详细的讲解,你可以阅读这篇文章《浅析schedule和回调函数》。

  代码9中,我只给出了碰撞begin函数,Chipmunk一共有4个回调函数,Chipmunk中文手册如下说明:

  1.begin():该步中两个形状刚开始第一次接触。回调返回true则会处理正常碰撞,返回false,Chipmunk会完全忽略碰撞。如果返回false,则preSolve()和postSolve()回调将永远不会被执行,但你仍然会在形状停止重叠的时候接收到一个单独的事件。

  3.postSolve():两种形状相互接触并且它们的碰撞响应已被处理。如果你想使用它来计算音量或者伤害值,这时你可以检索碰撞冲力或动能。

  4.separate():该步中两个形状刚第一次停止接触。确保begin()/separate()总是被成对调用,当删除接触中的形状时或者析构space时它也会被调用。

  注1:标记为传感器的形状(cpShape.sensor==true)从来不会得到碰撞处理,所以传感器形状和其他形状间永远不会调用postSolve()回调。它们仍然会调用begin()和separate()回调,而preSolve()仍然会在每帧调用回调,即使这里不存在线:preSolve()回调在休眠算法运行之前被调用。如果一个对象进入休眠状态,postSolve()回调将不会被调用,直到它被唤醒。

  那Cocos2d-x3.2又是如何和上面那4个函数联系在一起并且把它们封装到哪些函数中去?我第一感觉是Scene::

  什么?效果2中小球碰撞后怎么不消失,这不是我预想的结果啊!是不是哪里出了问题。我马上就在onContact-Begin中设置断点进行调试,发现两球即使碰撞也没有触发碰撞函数,这不是坑爹吗?就这个问题,一开始我想是不是每帧刷新的速度太快了导致检测不了碰撞,但是从效果2中可以看到明明两球发生了碰撞,结果就是两球受力弹开了

  后来上网查阅其他文章和Chipmunk中文手册,发现这是碰撞过滤的问题。从猜想各种各样的原因到发现是碰撞过滤的问题,这其中花费了我很多时间。

  首先,大家有没有这样的疑问--为什么需要碰撞过滤?玩过游戏的人都知道,自己的队友放出的技能会在敌方身上发生效果,不会作用在自己身上。此时,碰撞过滤就发挥作用了,它使得物体A和物体B会发生碰撞,而物体A和物体C不会发生碰撞,并且决定了发生了碰撞后回调函数是否接收此碰撞事件。由于上面默认碰撞过滤中没有发出通知,所以回调函数收不到两球碰撞的事件。

  关于碰撞过滤,Chipmunk中文手册中有如下描述:在空间索引找出彼此靠近的形状对后,将它们传给space,然后再执行一些额外的筛选。在进行任何操作前,Chipmunk会执行几个简单的测试来检测形状是否会发生碰撞。包围盒测试:如果形状的包围盒没有重叠,那么形状便没发生碰撞。对象如对角线线段会引发许多误报,但你不应该担心。层测试:如果形状不在同一层内则不会发生碰撞。(他们的层掩码按位与运算结果为0)群组测试:在相同的非零群组中的形状不会发生碰撞。对应代码在...\cocos2d\external\chipmunk\src\cpSpaceStep.c中,如代码12。

  分类掩码,定义了物体属于哪个分类。场景中的每个物理刚体可以被赋值一个多达32位的值(因为categoryBitmask为int型),每个对应32位掩码中的每一位,你在你的游戏中定义掩码值。结合collisionBitMask和contactTestBitMask属性,你可以定义哪些物理刚体相互作用并且你的游戏何时接受这些相互作用的通知。默认值为0xFFFFFFFF(所有位都被设置)。

  接触测试掩码,定义哪些刚体分类可以与本刚体产生相互作用的通知。当两个刚体在同一个空间,即物理世界中,每个刚体的分类掩码会和其他刚体的接触测试掩码进行逻辑与的运算。如果任意一个比较结果为非零值,产生一个PhysicsContact对象并且传递到物理世界协议中,这里协议指我们的对应的回调函数。为了最好的性能,仅设置你感兴趣的接触测试掩码中的位,也就是说通过设置接触测试掩码,你可以决定发生碰撞后,回调函数是否有响应。默认值为0x00000000(所有位都被清除)。

  碰撞掩码,定义了哪些物理刚体分类可以和这个物理刚体发生碰撞。当两个物理刚体相互接触时,可能发生碰撞。这个刚体的碰撞掩码和另一个刚体的分类掩码进行逻辑与运算比较。如果结果是一个非零值,这个刚体会发生碰撞。每个刚体独立选择接受与哪个刚体发生碰撞。例如,你可以使用此掩码来忽略那些对于本刚体的速度有影响的刚体碰撞,也就是说你可以使用此掩码使得本刚体与某些刚体碰撞不会对本刚体产生影响。默认值为0xFFFFFFFF(所有位都被设置)。

  从上面三个掩码的说明中,我们可以做一个小结。假设刚体A的接触测试掩码和碰撞掩码已知,刚体B的分类掩码决定了能否和A进行碰撞和在碰撞的前提下能否发出PhysicsContact对象触发回调函数。如果B的分类掩码与A的碰撞掩码做逻辑与运算的结果为0,则不会发生碰撞,因此也不会继续和A的接触测试掩码进行逻辑与运算。如果B的分类掩码与A的碰撞掩码做逻辑与运算的结果非0,则发生碰撞,并且B的分类掩码继续与A的接触测试掩码做逻辑与运算,如果结果非0,则发出PhysicsContact对象触发回调函数。

  现在,我把ballTwo的分类掩码设置为0x0001,碰撞掩码设置为0x0001,把ballTwo的分类掩码设置为0x0010,碰撞掩码设置为0x0010,使用16进制表示只是为了更好观察逻辑与运算的结果,边缘盒子的值保留为默认值。在init()函数设置每个刚体的属性后面添加代码13。

  从效果3中可以到两球不会发生碰撞,会相互穿过对方。因为ballOne的分类掩码和ballTwo的碰撞掩码做逻辑与的结果为0,ballTwo的分类掩码和ballOne的碰撞掩码做逻辑与的结果为0,所以不会发生碰撞。注意,即使ballOne的ballOne的分类掩码和ballTwo的碰撞掩码做逻辑与的结果非0,但是ballTwo的分类掩码和ballOne的碰撞掩码做逻辑与的结果为0,依旧不会发生碰撞的。这正如物理书上所说,作用力和反作用力是相互的。读者可以自己修改代码,观察运行效果。

  下面我们再次修改代码如代码14,使得两球可以碰撞,并且碰撞后发出PhysicsContact对象并触发回调函数使得两球消失。

  从效果4中,我们看到两球相撞后就消失了,终于和我们想要的效果是一样了。因为ballOne的分类掩码和

  ballTwo的碰撞掩码做逻辑与的结果为非0,ballTwo的分类掩码和ballOne的碰撞掩码做逻辑与的结果为非0,所以ballOne的分类掩码继续和ballTwo的接触测试掩码做逻辑与运算,结果为非0,ballTwo的分类掩码继续和ballOne的接触测试掩码做逻辑与运算,结果为非0,因此会发出PhysicsContact对象并触发回调函数,在onContactBegin函数中判断两个精灵是否为ballOne和ballTwo,如果是,则移除它们,所以ballOne和ballTwo会消失。注意,即使ballOne的分类掩码继续和ballTwo的接触测试掩码做逻辑与运算,结果为非0,但是ballTwo的分类掩码继续和ballOne的接触测试掩码做逻辑与运算,结果为0,依旧不会发出PhysicsContact对象和触发回调函数。读者可以自己修改代码,观察运行效果。

  到此为止,通过上面详细的讲述,使用Cocos2d-x3.2物理引擎进行碰撞检测算正式讲完了。下面我将讨论一个比较趣的例子。

  大家有没有想过一个精灵设置了刚体后,再使用runAction来运行动作?如果像上面例子中的又受到冲力又运行动作,小球的运行轨迹,我都不知道怎么样分析,如果谁知道,请告诉我一下。下面的例子的小球是没有受到冲力的。

  从效果5中,我们可以看到精灵运行动作也是可以使小球相互碰撞并且触发回调函数。到底精灵的runAction是如何和物理碰撞关联在一起的,之间是如何相互影响的?目前,我也不太清楚,如果谁知道,请告诉我一下。

  现在我强行用runAction使得小球运动。什么是强行使小球运动?你是否还记得我在前面说过setDynamic函数的作用?默认情况下,刚体是为动态的。现在我把两个小球都设置为静态刚体,即setDynamic(false)。由于,静态刚体是在空间中不会更新状态,但是我用精灵的runAction使小球运动,所以叫做强行。

  我现在在两球设置代码中添加setDynamic(false),运行效果如效果6:

  以上就是我这周学习物理引擎的总结,真心累,看资料有点累,而到最后写这篇总结时思考要怎么写,要总结些什么就更累了。最后我就以图2来做整个流程的总结。

本文链接:http://kingstonflowers.net/dingshiyueshu/609.html
随机为您推荐歌词

联系我们 | 关于我们 | 网友投稿 | 版权声明 | 广告服务 | 站点统计 | 网站地图

版权声明:本站资源均来自互联网,如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

Copyright @ 2012-2013 织梦猫 版权所有  Powered by Dedecms 5.7
渝ICP备10013703号  

回顶部