Nape刚体切割贴图
前两天,一直有同学在问刚体切割教程的效果如何贴图。好的,今天我们就来研究一下刚体切割贴图这个话题。
首先,我们先来看一下拉登大叔实现的效果。在下面的示例中,点击并拖动鼠标实现切割,同时拉登大叔的头像也被切成一块一块的,惨不忍睹啊!
[swfobject]1053[/swfobject]
这个效果中要解决的问题主要有两个:
- 如何切割贴图
- 如何随刚体更新贴图坐标
下面我们来自己看看每一个的具体实现方法。
切割贴图
先暂时离开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; } } }
联系作者
柔体贴图~~柔体贴图~~柔体贴图~~
还在研究中,敬请期待!
不错,我非常喜欢您的教程!
You are amazing 🙂
Thanks, hope my tuts can help you!
大叔能讲讲Box2d中的RayCast以及相关的类的用法吗
我在《Box2D物理游戏编程基础》一章有详细的介绍,Box2D中Raycast的4中用法,敬请期待哦!
我主要想做个爆炸效果,请问大叔能给个思路吗