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。具体如下图所示:

PreListenerOneWayWall

图中红色箭头表示碰撞法向量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;
		}

下面的示例中,中间的平台是一个单边平台,舞台中的刚体从上方落下时,都被掉落在了平台上,试着用鼠标拖动刚体。从平台的底部碰撞,此时刚体可以畅通无阻的穿过平台。点击查看动态效果。

oneway

对应的完整源代码如下:

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()返回值的逻辑判断,这一点前面已经详细做了介绍,这里就不再赘述了。点击下载原文件

联系作者

公众号:拉小登 | 微博:拉登Dony | B站:拉小登Excel

文章分类 > Nape

2 Replies to “PreListener实现单边碰撞”

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注