运行时绘制Nape刚体

创建简单的Nape刚体中,我们学会了简单矩形和圆形刚体的实现方法。今天我们进一步学习,如何在SWF运行时绘制这些刚体。

所谓运行时绘制刚体,就是根据鼠标的坐标与移动,动态的绘制这些刚体。实现这个过程的重点,是计算出刚体的坐标以及尺寸(如矩形的宽高、圆形的半径),而这些坐标和尺寸是根据鼠标的位置和动作计算出来的,大体步骤如下:

  1. 鼠标按下:记录当前鼠标坐标为刚体的坐标x、y。
  2. 鼠标滑动:根据鼠标的坐标以及刚体的坐标计算刚体的尺寸,如矩形的宽高w、h。并实时的绘制对于的图形。
  3. 鼠标弹起:根据前两部计算出来的刚体坐标x、y和尺寸,动态绘制刚体。

大体过程就是这样的啦,看看下面的示例,我想应该可以更加清楚明了。

点击鼠标开始绘制图形,松开鼠标后创建于图形相同形状的刚体。默认绘制的图形是矩形,绘制的同时按下Ctrl键是圆形刚体,按下Shift键是5边形。

[swfobject]689[/swfobject]

完整的代码和注释如下:

 

package 
{
	import flash.display.Graphics;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.geom.Point;

	import nape.geom.Vec2;
	import nape.phys.Body;
	import nape.phys.BodyType;
	import nape.phys.Material;
	import nape.shape.Circle;
	import nape.shape.Polygon;
	import nape.space.Space;
	import nape.util.BitmapDebug;

	[SWF( width="550", height="400", frameRate="60")]
	public class T7_CreateBodiesOnFly extends Sprite
	{		

		private var napeWorld:Space;
		private var debug:BitmapDebug;

		private var isCtrlDown:Boolean;
		private var isShiftDown:Boolean;

		private var bodyOnFly:Object;
		private var onFlyLayer:Sprite;

		public function T7_CreateBodiesOnFly()
		{
			//1.创建Nape空间,重力,调试视图
			createNapeWorld();
			//2.添加事件
			setUpEvents();
			//用createBox()方法创建4个包围舞台的静态刚体
			createWall();
			//创建一个保存动态刚体信息的对象
			bodyOnFly={x:0, y:0, x1:0, y1:0, w:0, h:0, r:0, mouseAngle:0};
			//创建一个图层,在鼠标移动时绘制相应的图形
			onFlyLayer=new Sprite();
			addChild(onFlyLayer);
			//添加FPS监视器
			addChild(new Stats());
		}
		//创房Nape世界
		private function createNapeWorld():void
		{
			var gravity:Vec2 = new Vec2( 0, 600 );
			napeWorld =new Space( gravity );

			debug= new BitmapDebug(550, 400, 0xD6D6D6);
			addChild(debug.display);
		}
		//添加事件侦听
		private function setUpEvents():void
		{
			//listening to the EnterFrame Event to update the Nape world
			stage.addEventListener(Event.ENTER_FRAME, loop);
			//add listener to MouseEvent,like mouseDown or MouseUp
			stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseEventHanlder);
			stage.addEventListener(MouseEvent.MOUSE_UP, mouseEventHanlder);
			//侦听键盘事件
			stage.addEventListener(KeyboardEvent.KEY_DOWN, keyBoardEventHanlder);
			stage.addEventListener(KeyboardEvent.KEY_UP, keyBoardEventHanlder);
		}

		protected function keyBoardEventHanlder(event:KeyboardEvent):void
		{
			//在键盘按下时,记录Ctrl和Shift键的状态
			isCtrlDown=event.ctrlKey;
			isShiftDown=event.shiftKey;
		}
		//创建各种刚体
		private function createBody(bodyInfo:Object):void{
			if(isCtrlDown){
				//如果Ctrl按下,绘制圆形刚体
				createCircle(bodyInfo.x, bodyInfo.y, bodyInfo.r, BodyType.DYNAMIC);
			}else if(isShiftDown){
				//如果Shift键按下,绘制5边形
				createRegular(bodyInfo.x, bodyInfo.y, bodyInfo.r, bodyInfo.mouseAngle, 5, BodyType.DYNAMIC);
			}else{
				//默认绘制矩形刚体
				createBox(bodyInfo.x+bodyInfo.w/2, bodyInfo.y+bodyInfo.h/2, bodyInfo.w, bodyInfo.h,BodyType.DYNAMIC);
			}
		}
		//创建矩形刚体,之前我们都已经讲过
		private function createBox(posX:Number, posY:Number, w:Number, h:Number, type:BodyType):void{
			var box:Body = new Body(type, new Vec2(posX, posY));
			var boxShape:Polygon=new Polygon(Polygon.box(w,h), Material.glass());
			box.shapes.push(boxShape);
			box.space= napeWorld;
		}
		//创建指定边数的规则多边形刚体
		private function createRegular(posX:Number, posY:Number, r:Number, rotation:Number, edgeCount:int, type:BodyType):void{
			var regular:Body = new Body(type, new Vec2(posX, posY));
			//通过Polygon预定义的regular方法绘制规则的5变形刚体
			var regularShape:Polygon=new Polygon(Polygon.regular(r*2,r*2,edgeCount), Material.glass());
			regularShape.rotate(rotation);
			regular.shapes.push(regularShape);
			regular.space= napeWorld;
		}
		//创建圆形刚体
		private function createCircle(posX:Number, posY:Number, radius:int, type:BodyType):void
		{
			var circle:Body=new Body(type, new Vec2(posX, posY));
			var shape:Circle=new Circle(radius,null,Material.glass());
			circle.shapes.push(shape);
			circle.space=napeWorld;
		}
		//绘制包围的静态刚体
		private function createWall():void{
			createBox(stage.stageWidth/2, 0, stage.stageWidth, 10, BodyType.STATIC);
			createBox(stage.stageWidth/2, stage.stageHeight, stage.stageWidth, 10, BodyType.STATIC);
			createBox(0, stage.stageHeight/2, 10, stage.stageHeight, BodyType.STATIC);
			createBox(stage.stageWidth, stage.stageHeight/2, 10,stage.stageWidth, BodyType.STATIC);
			createBox(250, 405, 600,50, BodyType.STATIC);
		}
		protected function mouseEventHanlder(event:MouseEvent):void
		{
			switch (event.type) {
				case MouseEvent.MOUSE_DOWN:
					//鼠标按下后,添加鼠标移动事件侦听
					stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseEventHanlder);
					//记录鼠标当前坐标,作为刚体的坐标
					bodyOnFly.x=mouseX;
					bodyOnFly.y=mouseY;
					break;
				case MouseEvent.MOUSE_UP:
					//鼠标弹起后,移除鼠标移动事件侦听
					stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseEventHanlder);
					//如果刚体的半径小于1,则绘制预定义的尺寸,防止传入0尺寸,导致Nape计算错误
					if(bodyOnFly.r <1){
						bodyOnFly.w=4;
						bodyOnFly.h=3;
						bodyOnFly.r= Math.sqrt(bodyOnFly.w*bodyOnFly.w + bodyOnFly.h*bodyOnFly.h);
					}
					//根据刚体信息bodyOnFly创建刚体
					createBody(bodyOnFly);
					//清楚图形绘制层
					onFlyLayer.graphics.clear();
					break;
				case MouseEvent.MOUSE_MOVE:
					//在鼠标移动时,根据鼠标的坐标计算刚体信息
					//矩形刚体的宽和高
					bodyOnFly.w= mouseX - bodyOnFly.x;
					bodyOnFly.h= mouseY - bodyOnFly.y;
					//5边形刚体的角度
					bodyOnFly.mouseAngle=Math.atan2(mouseY-bodyOnFly.y, mouseX-bodyOnFly.x);
					//圆形刚体的半径
					bodyOnFly.r= Math.sqrt(bodyOnFly.w*bodyOnFly.w + bodyOnFly.h*bodyOnFly.h);

					//适时绘制刚体形状
					drawShape(bodyOnFly);
					break;
			}

		}
		private function drawShape(bodyInfo:Object):void{
			var gfc:Graphics=onFlyLayer.graphics;

			gfc.clear();			
			gfc.lineStyle(1);
			gfc.beginFill(0x0000FF, 0.5);

			if(isCtrlDown){
				//Ctrl按下时,绘制圆形
				gfc.drawCircle(bodyInfo.x, bodyInfo.y, bodyInfo.r);
			}else if(isShiftDown){
				//Shift按下时,绘制5边形
				var mouseAngle:Number=bodyInfo.mouseAngle;
				var angle:Number = Math.PI*2/5;//每个顶点之间的角度间隔
				var x:Number, y:Number;//保存每个顶点的坐标
				//移动到第一个顶点
				gfc.moveTo(bodyInfo.x+bodyInfo.r*Math.cos(mouseAngle), bodyInfo.y+bodyInfo.r*Math.sin(mouseAngle));
				for (var i:int=0; i<5; i++){
					//计算每个顶点
					x=bodyInfo.x + bodyInfo.r * Math.cos( i*angle+mouseAngle);
					y=bodyInfo.y + bodyInfo.r * Math.sin( i*angle+mouseAngle);
					//连线顶点
					gfc.lineTo(x,y);
				}
			}
			else{
				//默认绘制矩形
				gfc.drawRect(bodyInfo.x, bodyInfo.y, bodyInfo.w, bodyInfo.h);
			}
			gfc.endFill();
		}
		protected function loop(event:Event):void
		{
			napeWorld.step(1/60);
			debug.clear();
			debug.draw(napeWorld);
			debug.flush();
		}

	}
}

值得一提的是第97-104行的createRegular()方法,这里用到了Polygon的静态方法regular()来创建一个规则的5边形刚体,除了regular之外,Polygon的静态方法还是box和rect,它们都会返回一个由顶点组成的数组,然后传递给Polygon对象,创建多边形shape,具体的用法分别如下:

  • rect:创建一个注册点在x,y位置的矩形,除了要指定width和height之外,还要指定矩形注册点x,y,
  • box:创建一个注册点在中心点的矩形,这个方法,只需要指定刚体width和height即可。相当于rect(0,0,width,height)
  • regular:创建宽为width,高为height,顶点数为edgeCount的规则多半形,例如三角形,以及本例中的五边形。这是Box2D没有的功能,在Box2D中需要自定义顶点才能创建类似的多边形。

源文件下载地址

联系作者

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

17 Replies to “运行时绘制Nape刚体”

  1. BitmapDebug和ShapeDebug有什么区别呢?是将图形转成Bitmap类型,以减少生成矢量图对cpu减少开销么?

  2. Stats()这个类貌似找不到了。。。
    今天下午一直在纠结那个五边形的坐标计算。哎,初中的数学都忘光了!悲剧

  3. 在77行createBody生成刚体前先if一下bodyInfo.w, bodyInfo.h以及bodyInfo.r是不是大于0即可,不然确实会报错。
    135行的那段if因此就可以略掉了。

  4. 演示SWF 有个小问题,当我画一个很小很小的东西时,或者直接单击之后就画不出矩形了,能显示出绘制区域,但是松开鼠标之后没有生成矩形

  5. 不会吧,我在第135~139行的代码中,有针对绘制尺寸过小时,设置矩形尺寸默认为3*4,所以,理论上不会出现你说的情况。如果我的回答没有解答你的疑问,你可以截图后发邮件到ladeng6666@163.com,谢谢关注!

  6. 重新试了一下,是上条评论的层主发的问题,就是我鼠标按下之后水平或者垂直移动时画出来的是一条线,可能获取的宽或者高是0,所以没有画出刚体。之后就生成不了刚体了。

发表回复

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