Nape刚体切割贴图

前两天,一直有同学在问刚体切割教程的效果如何贴图。好的,今天我们就来研究一下刚体切割贴图这个话题。

首先,我们先来看一下拉登大叔实现的效果。在下面的示例中,点击并拖动鼠标实现切割,同时拉登大叔的头像也被切成一块一块的,惨不忍睹啊!

[swfobject]1053[/swfobject]

这个效果中要解决的问题主要有两个:

  1. 如何切割贴图
  2. 如何随刚体更新贴图坐标

下面我们来自己看看每一个的具体实现方法。

切割贴图

先暂时离开Nape一会儿,忘记rayCast()方法。我们回归到原始的Flash绘图API,当然了,因为切割后的形状不是固定的,所以不能使用像drawRect或drawCircle这样预设的方法了,而是要用moveTo和lineTo沿着每个顶点把形状绘制出来。

那么我们就要知道图形的所有顶点。这个不难在Nape中,任何Polygon形状的顶点都保存在一个叫做worldVerts数组中,我们可以通过遍历这个数组,逐个绘制线条,代码如下:

			b.shapes.at(0).castPolygon.worldVerts.foreach(function (v:Vec2){
				if(i==0){
					s.graphics.moveTo(v.x, v.y);
				}else{
					s.graphics.lineTo(v.x, v.y);
				}
				i++;
			});

 

其中b是指刚体body,s是Flash里的一个Shape对象。如果是第一个顶点,即i为0,则调用moveTo将绘制点移动至该顶点处。其他的调用lineTo进行绘制。

绘制完了图形,接下来是填充了拉登大叔的头像了。这里我用的是ActionScript的beginBitmapFill()方法,这也是Flash内置的一个绘图API,可以将一个已有的图像,作为填充图案绘制到图形中。它的结构如下:

beginBitmapFill (
	bitmap:BitmapData, 
	matrix:Matrix = null,
	repeat:Boolean = true, 
	smooth:Boolean = false
	) : void

每个参数说明如下:

  • bitmap:要填充的图像。
  • matrix:用来移动、缩放和/或旋转位图的Matrix对象,保存了位图变换信息。
  • repeat:如果要填充的图形尺寸比填充图像大,用这个参数定义超出部分的填充方式。
  • smooth:设置这个参数为true,告诉Flash Player绘制图像时如果有放大,要使用平滑算法,减少锯齿和模糊。

第一个bitmap参数实际上是一个BitmapData对象,表示我们用来填充的图像。这里我新建了一个BitmapData对象,并用draw()方法把整个舞台绘制到这个BitmapData中,作为要填充的图像。

	var bmd:BitmapData = new BitmapData(stage.stageWidth, stage.stageHeight);
	bmd.draw(stage);
	s.graphics.beginBitmapFill(bmd);

图像也填充完了,可以进入下一节了,不过在这之前我要声明一个问题。我们现在绘制的内容都是基于全局坐标的,也就是说贴图的注册点在舞台上左上角(0,0)处,不过这也未必是件外事,下一节,我会详细说明。

更多关于Flash绘图和图像处理的知识,推荐大家去看《Foundation ActionScript3.0 Image Effects》,中文版名称为《ActionScript3.0图像处理基础教程》有拉登大叔执笔翻译,将于近期出版,也期待大家的支持。

 

随刚体更新切割贴图

刚体的切割方法在上一节我们就已经学过了,结合刚刚讲过的刚体切割方法,现在需要随刚体移动更新贴图坐标了。

前面我说过了,刚刚我们绘制的切割图像的注册点是在(0,0)处的,所以当我们旋转时,图像不会绕着自身的中心点旋转。那么我们就要想办法,把图像的注册点移动到图像的中心位置,但是对于非矩形、圆形来说,这并不是一件容易的事,好在Nape可以帮我们解决这个困难。

具体的讲,在我们切割完刚体之后,生成的刚体片段和我们绘制的图像结果是一样的,注册点都在(0,0)处,然后我们可以通过刚体的localCOM属性获取其中心坐标位置。然后我们可以把绘制好的图像反向移动这个距离,然后添加到一个container容器中,就可以实现注册点移动了。说了这么多,还是看代码更好理解些。

	var localCOM:Vec2 = b.localCOM;
	s.x = -localCOM.x;
	s.y = -localCOM.y;
	container.addChild(s);

这里的container是一个Sprite对象,是我们真正的要用在刚体上的贴图。另外一点要注意的是,在引用localCOM属性时,一定要在调用body.align()方法之前,因为一点调用align()之后,这个localCOM的坐标就成了(0,0)。

然后我们再和正常贴图一样,更新贴图坐标就可以了。下面是完整的代码和源文件。

package learnNape {
	import ldEasyNape.LDEasyUserData;
	import flash.display.Bitmap;
	import flash.geom.Matrix;
	import flash.display.BitmapData;
	import flash.geom.Rectangle;
	import flash.display.Shape;
	import nape.phys.BodyType;
	import nape.geom.RayResult;
	import nape.geom.Ray;
	import flash.events.Event;
	import nape.phys.Material;
	import nape.shape.Polygon;
	import nape.geom.GeomPolyList;
	import nape.geom.GeomPoly;
	import flash.display.Sprite;
	import nape.geom.Vec2;
	import flash.events.MouseEvent;
	import ldEasyNape.LDEasyNape;
	import nape.phys.Body;
	[SWF( frameRate="60", width="550", height="400")]
	public class T35_CutBodyWithFixture extends AbstractNapeTest
	{
		[Embed(source="../assets/ladeng6666.jpg")]
		private var PIC : Class;

		public function T35_CutBodyWithFixture(){}

		private var sp : Vec2 , ep : Vec2;
		private var layer : Sprite;
		private var isDrawing : Boolean = false;

		private var geomPoly : GeomPoly;

		override protected function onNapeWorldReady() : void {
			sp = new Vec2();
			ep = new Vec2();

			var pic:Bitmap = new PIC();
			pic.x = 175;
			pic.y = 100;
			addChild(pic);

			layer = new Sprite();
			addChild(layer);

			var box : Body = LDEasyNape.createBox(275, 200, 200, 200, true);
			box.userData.graphic = pic;
		}

		override protected function mouseEventHanlder(event : MouseEvent) : void {
			super.mouseEventHanlder(event);

			switch(event.type){
				case MouseEvent.MOUSE_DOWN:
					if(LDEasyNape.getBodyAtMouse()!=null) return;
					isDrawing = true;
					setChildIndex(layer, this.numChildren-1);
					sp = new Vec2(mouseX,mouseY);
					break;
				case MouseEvent.MOUSE_UP:
					if(isDrawing){
						isDrawing = false;
						ep = new Vec2(mouseX,mouseY);
						if(Vec2.distance(sp, ep)>10){
							cutBody();
						}
						layer.graphics.clear();
					}

					break;
			}
		}

		override protected function loop(event : Event) : void {
			super.loop(event);
			if(isDrawing){
				layer.graphics.clear();
				layer.graphics.lineStyle(2,0xff0000);
				layer.graphics.moveTo(sp.x, sp.y);
				layer.graphics.lineTo(mouseX, mouseY);
			}
		}
		private function cutBody():void{

			var ray : Ray = Ray.fromSegment(sp, ep);
			var rayResult : RayResult = napeWorld.rayCast(ray);
			var parentBody: Body;

			if(rayResult!=null){
				parentBody = rayResult.shape.body;
				geomPoly = new GeomPoly((rayResult.shape as Polygon).worldVerts);
				var geomPolyList : GeomPolyList = geomPoly.cut(sp, ep,true,true);

				geomPolyList.foreach(function (s:*):void{
					var body : Body = new Body();
					var graphic : Sprite;

					body.shapes.push(new Polygon(s,Material.ice()));

					graphic = getGraphic(body);
					addChild(graphic);

					body.align();
					body.userData.graphic = graphic;

					if(body.contains(Vec2.weak(275,200))){
						body.type = BodyType.STATIC;
						graphic.x = body.position.x;
						graphic.y = body.position.y;
					}
					body.space = napeWorld;
				});
				if(parentBody.userData.graphic) removeChild(parentBody.userData.graphic);
				parentBody.space = null;
			}
		}
		private function getGraphic(b:Body):Sprite{
			var container:Sprite = new Sprite();
			var s : Shape = new Shape();

			var bmd:BitmapData = new BitmapData(stage.stageWidth, stage.stageHeight);
			bmd.draw(stage);

			s.graphics.beginBitmapFill(bmd);

			var i:int = 0;
			b.shapes.at(0).castPolygon.worldVerts.foreach(function (v:Vec2){
				if(i==0){
					s.graphics.moveTo(v.x, v.y);
				}else{
					s.graphics.lineTo(v.x, v.y);
				}
				i++;
			});
			s.graphics.endFill();

			var localCOM:Vec2 = b.localCOM;
			s.x = -localCOM.x;
			s.y = -localCOM.y;
			container.addChild(s);

			return container;
		}
	}
}

点击下载源文件

联系作者

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

8 Replies to “Nape刚体切割贴图”

发表回复

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