PreListener实现单边碰撞
在《Nape刚体碰撞检测》一节中,我们认识了Nape中的4个侦听器,这一节我们来仔细研究一下PreListener碰前侦听器。
PreListener和Box2D中b2ContactListener的PreSolve()函数。当刚体之间发生碰撞时,Nape会进行相应的计算,来模拟物理碰撞。PreListener侦听的事件在这个模拟计算之前派发,你可以这么理解,想象一下两辆汽车相撞的情节,这时电影里给了一个慢放镜头,在碰撞的瞬间,两辆汽车刚刚发生接触,这时候一个神仙出现了,把两辆汽车的挪到了不同的车道,然后镜头恢复正常播放速度,两辆汽车相安无事的继续前行。
PreListener用法
PreListener就像是一个神仙,可以在物理碰撞计算之前,对碰撞进行干预,来忽略当前碰撞。首先我们来看一下PreListener的构造函数:
public function PreListener( interactionType:InteractionType, options1:*, options2:*, handler:Function, precedence:int=0, pure:Boolean=false )
PreListener的参数与InteractionListener基本一致,具体说明如下:
- 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:当不同的事件侦听器同时侦听相同的刚体之间相同的碰撞事件时,触发侦听器的优先权。
- pure:Boolean:是否允许刚体在碰撞过程中睡眠。只有在handler函数返回PreFlag.IGNORE_ONCE或PreFlag.ACCEPT_ONCE时起作用。主要用来节省资源的,对PreListener的实际应用意义不是很大。
回调函数返回值
PreListener对碰撞的干预取决于handler函数中返回值。如果handler函数返回值为void,则PreListener对碰撞不产生干预。另外,PreListener如果返回PreFlag中的4个常量,则会相应的影响碰撞模拟:
- PreFlag.ACCEPT:视此次碰撞为有效碰撞,并进行正常的物理碰撞模拟,并停止派发PreListener事件,直至两个刚体分离后再次碰撞。
- PreFlag.IGNORE:忽略此次碰撞,不进行物理碰撞模拟,并停止派发PreListener事件,直至两个刚体分离后再次碰撞。
- PreFlag.ACCEPT_ONCE:视此次碰撞为有效碰撞,并进行正常的物理碰撞模拟,但PreListener事件会继续派发,下一次返回值,按handler函数中的逻辑处理返回。
- PreFlag.IGNORE_ONCE:忽略此次碰撞,不进行物理碰撞模拟,但PreListener事件会继续派发,下一次返回值,按handler函数中的逻辑处理返回。
通常模拟单边效果时,使用PreFlag.ACCEPT和PreFlag.IGNORE即可。
PreCallback
另外需要注意的是是handler函数中的PreCallback参数,它和InteractoinCallback一样,都包含了碰撞的一些信息。我们再来一起回顾一下:
- int1:Interactor:碰撞刚体中,符合PreListener中的第1个碰撞对象类型描述的刚体。
- int2:Interactor:碰撞刚体中,符合PreListener中的第2个碰撞对象类型描述的刚体。
- arbiters:保存了arbiter对象的碰撞信息列表。Nape中的arbiter类似于Box2D中的b2Contact类型,具体请参考《什么是Arbiter》。
除了这些基本的信息,PreCallback还有一个特有的属性swapped。
- swapped:Boolean:表示arbiter中属性与int1和int2的所属关系,以及碰撞法向量normal指向。具体如下:
- swapped = true:abiter.shape1属于int2,abiter.shape2属于int1。normal由int2指向int1。
- swapped = false:abiter.shape1属于int1,abiter.shape2属于int2。normal由int1指向int2。
在模拟单边碰撞效果时,我们的判定方法是,如果刚体body从平台plat的上方发生碰撞,则进行正常的碰撞模拟,不允许刚体穿透plat。如果刚体body从平台plat的下方发生碰撞,则忽略此次碰撞,使刚体可以穿透plat。具体如下图所示:
图中红色箭头表示碰撞法向量normal,当箭头方向向上时,normal.y<0,结合swapped的不同取值,我们得到了上面的碰撞和忽略的判定条件,用代码表示如下:
private function onPre(cb:PreCallback):PreFlag { var ca:CollisionArbiter = cb.arbiter.collisionArbiter; if (cb.swapped && ca.normal.y>0 ) return PreFlag.IGNORE; if(!cb.swapped && ca.normal.y<0) return PreFlag.IGNORE; return PreFlag.ACCEPT; }
下面的示例中,中间的平台是一个单边平台,舞台中的刚体从上方落下时,都被掉落在了平台上,试着用鼠标拖动刚体。从平台的底部碰撞,此时刚体可以畅通无阻的穿过平台。点击查看动态效果。
对应的完整源代码如下:
package { import flash.events.Event; import ldEasyNape.LDEasyBody; import nape.callbacks.CbType; import nape.callbacks.InteractionType; import nape.callbacks.PreCallback; import nape.callbacks.PreFlag; import nape.callbacks.PreListener; import nape.dynamics.CollisionArbiter; import nape.phys.Body; import nape.phys.BodyType; public class WhatisPreListener extends AbstractNapeTest { public function WhatisPreListener(gravity:Number=600) { super(gravity); } override protected function onNapeAppReady():void { LDEasyBody.createRectangle(0,0,stage.stageWidth,stage.stageHeight); LDEasyBody.createRegular(100,100,40,5); LDEasyBody.createBox(300,50,50,40); LDEasyBody.createCircle(200,100,30); var plat:Body = LDEasyBody.createBox(250,200,300,20,BodyType.STATIC); var platOption:CbType = new CbType(); plat.cbTypes.add(platOption); var preListener:PreListener = new PreListener(InteractionType.ANY,CbType.ANY_BODY,platOption,onPre); space.listeners.add(preListener); } private function onPre(cb:PreCallback):PreFlag { var ca:CollisionArbiter = cb.arbiter.collisionArbiter; if (cb.swapped && ca.normal.y>0 ) return PreFlag.IGNORE; if(!cb.swapped && ca.normal.y<0) return PreFlag.IGNORE; return PreFlag.ACCEPT; } override protected function loop(event:Event):void { super.loop(event); } } }
代码非常的简单,重点是onPre()返回值的逻辑判断,这一点前面已经详细做了介绍,这里就不再赘述了。点击下载原文件。
联系作者
勤快的拉登,这么快就写了单边碰撞教程了
不快,只要是被问到的问题,我都会尽量写一篇教程来讲解。谢谢你的关注!