Nape刚体碰撞检测
Nape帮我们实现了物理碰撞模拟,通过Nape我们可以实现各种游戏模型。但是就像我在Box2D碰撞检测里讲的一样,只是碰撞是不够的,我还喜欢碰撞后的物体可以消失、变小等等,按照游戏需求执行任何需要的动作。这就需要我们今天要讨论的话题,自定义碰撞检测处理。这篇文章比较长,深吸一口气,来吧!
跟Box2D比起来,Nape更加符合Flash开发人员的习惯,它的回调系统跟Flash的事件模型非常相似,添加事件侦听,自定义事件处理函数。而且Nape事件类型比Box2D更为强大和完善,还等什么,马上开始吧!
本节主要内容如下:
- 认识Nape事件侦听:阐述Nape中添加事件侦听的大致流程。
- Nape事件侦听器:简单说明Nape中4中不同的事件侦听器
- InteractionListener实现碰撞检测:涉及到碰撞事件类型CbEvent,和碰撞行为类型InteractionType。
- InteractionCallback获取碰撞信息:类似Box2D中的b2Contact类,强大的碰撞信息收集器InteractionCallback。
- 实例说明:通过一个示例,演示碰撞事件侦听的代码。
认识Nape事件侦听
虽说Nape的回调系统和Flash事件模型非常相似,但具体写起代码来还是存在不同的。
Flash中任何继承EventDispatcher类的对象都可以通过addEventListener对指定事件添加侦听,例如,要对Sprite对象的ENTER_FRAME事件添加侦听,代码应该写成:
stage.addEventListener(Event.ENTER_FRAME, loop);
Nape中所有的事件侦听只能通过space对象来添加。另外Nape事件侦听不是通过addEventListener()这样的函数,而是通过Listener类来实现的。简单的讲,可以理解成把addEventListener()函数拆成了两步:
- 创建Listener对象,这个对象中包含了碰撞事件所需的所有信息,稍后会详细介绍。
- 将Listener对象添加到space的listeners属性中。space.listeners是一个ListenerList对象,用来保存Nape中所有的事件侦听器。
完成上面两步就实现了Nape碰撞事件侦听,下面是具体的代码:
var listener:InteractionListener = new InteractionListener(); napeWorld.listeners.add(listener);
听起来似乎是这样啊!那Nape都有哪些事件类型呢?
Nape事件侦听器
Listener是Nape中碰撞事件帧听的核心部分。所有的事件派发后都有Listener对象侦听,然后触发相应的事件处理函数。针对不同的事件,侦听器也不同,也就衍生出了Listener的四个子类:BodyListener、ConstraintListener、InteractionListener和PreListener。这几个侦听器的用途简单说明如下:
- BodyListener:用来侦听刚体的状态WAKE和SLEEP状态之间切换,并在事件发生时派发CbEvent.WAKE或CbEvent.SLEEP事件
- ConstraintListener:侦听关节状态WAKE、SLEEP和BREAK的变化,并对应的派发事件CbEvent.WAKE、CbEvent.SLEEP和CbEvent.BREAK
- InteractionListener:在刚体发生碰撞时派发的所有事件都由InteractionListener来侦听。这些碰撞事件包括CbEvent.BEGIN、CbEvent.ONGOING和CbEvent.END
- PreListener:这是一个特殊的InteractionListener侦听器,同样也用来侦听碰撞事件,不同的是,PreListener在物理碰撞模拟之前执行,这意味着我们可以在碰撞前对碰撞模拟进行干预,如忽略碰撞而不进行碰撞模拟,调整碰撞的弹性系数、摩擦力等等。进而实现单边平台等等效果。
InteractionListener实现碰撞检测
本节我们主要是讲刚体碰撞检测,所以我们重点看一下InteractionListener侦听器,其构造函数如下:
public function IneractionListener( event:CbEvent, interactionType:InteractionType, options1:Null<Dynamic>, options2:Null<Dynamic>, handler:InteractionCallback -> Void, precedence:Int = 0)
总共有6个参数,具体说明如下:
- event:CbEvent:要侦听碰撞事件类型。Nape将刚体的碰撞事件分为3种类型,由CbEvent类的3个常量表示,包括BEGIN、END、ONGOING。
- BEGIN:当刚体之间开始发生碰撞时派发CbEvent.BEGIN事件
- ONGOING:两个刚体发生碰撞后,至分离之前,会不断派发CbEvent.ONGOING事件。比如刚体与Sensor碰撞时,刚体与Sensor刚体之间会有重叠部份,这是会持续派发CbEvent.ONGOING事件
- END:当对象之间的碰撞结束,并分离时派发CbEvent.END事件。
- interactionType:InteractionType:要侦听的碰撞行为类型。根据刚体类型的不同,刚体之家的碰撞也被分成了4类。有InteractionType的4个常量表示:
- COLLISION:正常刚体之间的碰撞。
- FLUID:刚体与模拟流体浮力的刚体发生的碰撞。
- SENSOR:刚体与Sensor感应区域刚体的碰撞。
- ANY:包含以上3中类型的任何碰撞类型。
- options1:OptionType:Nape可以通过OptionType类对碰撞侦听的两个碰撞对象类型进行描述,只有符合该类型的对象发生了碰撞,才会被侦听都,这样可以更准确的侦听碰撞事件。OptionType描述碰撞对象类型的方法有两种,一个是直接通过CbType类的常量来设定,这些常量包括
- ANY_BODY:每个刚体默认的类型描述
- ANY_COMPOUND:每个刚体组合默认的类型描述
- ANY_CONSTRAINT:每个关节默认的类型描述
- ANY_SHAPE:每个形状默认的类型描述
除此之外,我们可以新建一个CbType对象,并通过body.cbTypes.add()将其,添加到刚体的描述中。例如下面的代码:
var cicleType:CbType = new CbType(); var circle:Body = new Body(); //通过cbTypes.add()方法,将circle刚体标记为circleType类型 circle.cbTypes.add(circleType); var rectType:CbType = new CbType(); var rect:Body = new Body(); //通过cbTypes.add()方法,将rect刚体标记为rectType类型 rect.cbTypes.add(rectType);
- options1是对第一个碰撞对象类型的描述,和下面的options2搭配使用。Nape中只有符合这两种类型的对象发生碰撞时,才会派发相应的事件。
- options2: OptionType:侦听碰撞的两个刚体中,第2碰撞对象类型描述。
- handler:InteractionCallback:处理碰撞事件的函数,和Flash中addEventListener里的事件处理函数一样。
- precedence:Int = 0:当不同的事件侦听器同时侦听相同的刚体之间相同的碰撞事件时,触发侦听器的优先权。
举个简单的例子,比如现在舞台上有圆形和矩形两个形状的刚体,那么我们可以通过下面的代码创建两种不同的类型:
var cicleType:CbType = new CbType(); var rectType:CbType = new CbType();
虽然两个对象同样没有参数,但是因为是两个不同的实例对象,所以可以表示两种不同类型的刚体。如果我们可以侦听的刚体碰撞可以分为三类,对应的在侦听器中设置options1和options2的方法分别如下:
- 圆形与圆形之间的碰撞:
new InteractionListener( CbEvent.BEGIN, InteractionType.COLLISION, circleType, circleType, onCircleVsCircle );
- 圆形与矩形之间的碰撞:
new InteractionListener( CbEvent.BEGIN, InteractionType.COLLISION, rectType, circleType, onRectVsCircle );
- 矩形与矩形之间的碰撞:
new InteractionListener( CbEvent.BEGIN, InteractionType.COLLISION, rectType, rectType, onRectVsRect );
看过代码之后是不是就明白多了。
InteractionCallback获取碰撞信息
除了可以在Listener中用options1和options2参数有针对性的侦听具体的碰撞,Nape在碰撞信息的传递上,也Box2D要强大的多。
比如上面例子中圆形与矩形之间的碰撞,在Box2D中通过b2Contact里的body1和body2获取的碰撞刚体,无法明确哪个是矩形,哪个是圆形。我们不得不对body1或body2分别进行两次判断。
在Nape的事件处理函数中,有一个InteractionCallback类型的参数,包含了所有的碰撞信息,具体由它的3个参数保存:
- int1:Interactor:碰撞刚体中,符合InteractionListener中的第1个碰撞对象类型描述的刚体。
- int2:Interactor:碰撞刚体中,符合InteractionListener中的第2个碰撞对象类型描述的刚体。
- arbiters:保存了arbiter对象的碰撞信息列表。Nape中的arbiter类似于Box2D中的b2Contact类型,你可以在《什么是Arbiter》一节中找到详细的说明。
在圆形和矩形之间碰撞的实例代码中,int1就是指的rectType类型的刚体,int2指的是circleType类型的刚体。具体的代码如下:
new InteractionListener( CbEvent.BEGIN, InteractionType.COLLISION, rectType, circleType, onCircVsCircle ); private function onRectVsCircle(cb:InteractionCallback):void { //int1表示矩形刚体类型rectType cb.int1.castBody.userData.graphic.alpha = 1; //int2表示圆形刚体类型circleType cb.int2.castBody.userData.graphic.alpha=0.3; }
是不是很简单?
好啦,到这里Nape碰撞检测的内容就基本讲完了。好多吧,没事慢慢消化。接下来是该举出详细的例子说明一下啦。
下面的例子中有1个红色的矩形和2个蓝色的圆形,点击可以拖动刚体。当红色矩形刚体撞击蓝色圆形刚体,蓝色会变为半透明的。圆形之间的碰撞,会把蓝色改为完全不透明。动手试试看看效果吧!
[swfobject]801[/swfobject]
完整的代码和注释如下,点击这里下载源文件。
代码中用到了我自定义的LDEasyNape类,具体请参考这篇教程!
package { import ldEasyNape.LDEasyNape; import ldEasyNape.LDEasyUserData; import nape.callbacks.CbEvent; import nape.callbacks.CbType; import nape.callbacks.InteractionCallback; import nape.callbacks.InteractionListener; import nape.callbacks.InteractionType; [SWF( width="550", height="400", frameRate="60")] public class T15_NapeBodyInteraction extends AbstractNapeTest { private var circleType:CbType; private var rectType:CbType; private var circleVsCircleListener:InteractionListener; private var rectVsCircleListener:InteractionListener; public function T15_NapeBodyInteraction() { //在父类的构造函数中设置重力为0 super(0); trace("hello world"); LDEasyNape.version(); //创建LDEasyUserData对象,设置圆形刚体为蓝色 var circleData:LDEasyUserData=new LDEasyUserData(); circleData.setGraphicAuotmatically(0x0000FF, 1); //创建圆形刚体类型,用于侦听器中限制碰撞的对象 circleType = new CbType(); //创建两个圆形刚体,并通过cbTypes的add方法,设置圆形刚体的类型为circleType LDEasyNape.createCircle(100,100,30,false,false, circleData).cbTypes.add(circleType); LDEasyNape.createCircle(300,300,30,false,false, circleData.clone()).cbTypes.add(circleType); //创建LDEasyUserData对象,设置矩形刚体为红色 var rectData:LDEasyUserData=new LDEasyUserData(); rectData.setGraphicAuotmatically(0xff0000, 1); //创建矩形刚体类型,用于在侦听器中限制碰撞的对象 rectType = new CbType(); //创建矩形刚体,并通过cbTypes的add方法,设置圆形刚体的类型为rectType LDEasyNape.createBox(200,200,60,60,false,false, rectData).cbTypes.add(rectType); //实例化InteractionListener对象,侦听的具体的碰撞信息如下: //参数1:cbEvent.BEGIN,侦听碰撞开始的事件 //参数2:InteractionType.COLLISION侦听普通刚体碰撞 //参数3和4:circelType和circleType,限制只侦听两个圆形刚体之间的碰撞 //参数5:设置事件处理函数为onCircleVsCircle circleVsCircleListener=new InteractionListener(CbEvent.BEGIN,InteractionType.COLLISION,circleType,circleType,onCircleVsCircle); //rectVsCircleListener侦听器,除了options1和options2里限制的是矩形和圆形刚体之外 //其他的和circleVsCircleListener侦听器是一样的 rectVsCircleListener=new InteractionListener(CbEvent.BEGIN,InteractionType.COLLISION,rectType,circleType,onRectVsCircle); napeWorld.listeners.add(circleVsCircleListener); napeWorld.listeners.add(rectVsCircleListener); } private function onCircleVsCircle(cb:InteractionCallback):void { //设置圆形刚体透明度为1 cb.int1.castBody.userData.graphic.alpha=1; cb.int2.castBody.userData.graphic.alpha=1; } private function onRectVsCircle(cb:InteractionCallback):void{ //根据rectVsCircleListener中设置的options1和options2,我们知道 //cb.int1是矩形刚体,cb.int2是圆形刚体 //所以下面的代码将碰撞的圆形刚体透明度设置为0.3 cb.int2.castBody.userData.graphic.alpha=0.3; } } }
联系作者
拉登大师~请教一下,爆炸的效果怎么做呢?就是刚体把其他刚体炸开~~
请参考emanueleferonato的这篇文章
http://www.emanueleferonato.com/2012/01/05/create-real-explosions-with-box2d-exploding-objects-and-setting-the-center-of-explosion-with-mouse-click/
PRE应该是碰撞前吧
拉登大叔,想问一个问题啊,为什么上面demo可以保持固定而不受重力影响啊
请问一下,怎么实现碰撞过滤呢,比如从一个平台往上跳时,不碰撞,但是从上落到这个平台上时是碰撞的,类似b2ContactFilter的ShouldCollide
你好,谢谢你的关注,你说的应该是单边平台的效果。要实现单边平台,需要对碰撞事件进行忽略,请留下你的邮箱,回头我会专门写一篇关于单边平台的教程,届时发邮件通知你。
大致过程是这样的,在space.listenners中添加PreListener事件侦听,在侦听函数中返回PreFlag.INGORE即可忽略当前碰撞
谢谢你的热心,在NAPE的论坛上找到解决了!
nape这个碰撞事件分类真不错,少写了很多if
Nape论坛,google上的那个吗?最新在整理box2d的内容,所以回复的晚了,没帮上忙
是的,AS开发的话,物理引擎还是推荐Nape
拉登大叔,我建了一个无重力的space,然后有一个小球被四个墙体围起来。然后把它们所有的反弹系数elasticity都设为了1,它们所有的dynamicFriction与其它带有Friction字样的属性的摩擦力全部都设为0,反弹时仍然会有速率的损失,是什么原因?enterFrame事件侦听器里发现ball.velocity.length在不断的变小,虽然每次只减少一点点,但确实是在不断减少,可官方的API手册中写的是无损反弹。是还需要其它属性的设置吗?
发源文件到我的邮箱ladeng6666@163.com,我研究一下吧!谢谢你的支持!
另外,加我的QQ:1160662553,咱们实时交流!
好,大神的QQ。
拉登大神,给你QQ邮留言了,但我看你一直没有上QQ,我就把资料整理到自己的博客里了:http://blog.zinewow.com/post/515.html,我把能去掉的摩擦力系数都设为了0,弹性系数设为了最大值1,按着官方的说是无损反弹,可实际上它还是会有速率的损失,虽然每次损失的很小,但确定是有损失,过个大概几十秒可以明显的感觉到速度变小了。
就像我在QQ里回复你的,记得将space对象的worldAngularDrag 和worldLinearDrag设置为0,这两个属性用来设置全局的线性阻尼和旋转阻尼。