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()函数拆成了两步:

  1. 创建Listener对象,这个对象中包含了碰撞事件所需的所有信息,稍后会详细介绍。
  2. 将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;
		}
	}
}

 

联系作者

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

18 Replies to “Nape刚体碰撞检测”

  1. 请问一下,怎么实现碰撞过滤呢,比如从一个平台往上跳时,不碰撞,但是从上落到这个平台上时是碰撞的,类似b2ContactFilter的ShouldCollide

  2. 你好,谢谢你的关注,你说的应该是单边平台的效果。要实现单边平台,需要对碰撞事件进行忽略,请留下你的邮箱,回头我会专门写一篇关于单边平台的教程,届时发邮件通知你。

  3. 拉登大叔,我建了一个无重力的space,然后有一个小球被四个墙体围起来。然后把它们所有的反弹系数elasticity都设为了1,它们所有的dynamicFriction与其它带有Friction字样的属性的摩擦力全部都设为0,反弹时仍然会有速率的损失,是什么原因?enterFrame事件侦听器里发现ball.velocity.length在不断的变小,虽然每次只减少一点点,但确实是在不断减少,可官方的API手册中写的是无损反弹。是还需要其它属性的设置吗?

  4. 拉登大神,给你QQ邮留言了,但我看你一直没有上QQ,我就把资料整理到自己的博客里了:http://blog.zinewow.com/post/515.html,我把能去掉的摩擦力系数都设为了0,弹性系数设为了最大值1,按着官方的说是无损反弹,可实际上它还是会有速率的损失,虽然每次损失的很小,但确定是有损失,过个大概几十秒可以明显的感觉到速度变小了。

  5. 就像我在QQ里回复你的,记得将space对象的worldAngularDrag 和worldLinearDrag设置为0,这两个属性用来设置全局的线性阻尼和旋转阻尼。

发表回复

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