运行时创建多边形刚体
学习了信手绘制线条刚体,你有没有想过信手绘制多边形刚体呢?”当然了,你不是在上一篇教程中说过了吗?快快说来!!”
是的,正如我说说的,这是重力大师里可以绘制的对象之一。在之前的Box2D多边形刚体教程中。我们学会了用组合法和原生法创建多边形。谈到运行时创建多边形刚体,你会选择哪种方法呢?如果是原生法,恭喜你,我们想到一块去了。借一步讲话,我给你细细道来。
原生法绘制多边形刚体
还记得在信手绘制线条刚体中创建的那些线段吗?而且我用红色圆点标示了他们的坐标。把这些点传递给b2Shape的SetAsVector方法就完成了多边形的绘制,简单吧!(如果你不知道如何用原生法创建多边形,请查看“Box2D多边形刚体”)。效果如下,同样请不要交叉绘制,而且不要逆时针绘制。
完整代码和注释如下:
package { import Box2D.Collision.b2AABB; import Box2D.Collision.Shapes.b2PolygonShape; import Box2D.Common.Math.b2Vec2; import Box2D.Dynamics.b2Body; import Box2D.Dynamics.b2BodyDef; import Box2D.Dynamics.b2DebugDraw; import Box2D.Dynamics.b2FixtureDef; import Box2D.Dynamics.b2World; import Box2DSeparator.b2Separator; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.Point; /** * http://www.ladeng6666.com * @author ladeng6666 */ public class Main extends Sprite { private var world:b2World; private var spriteCanvas:Sprite; private var prePoint:Point = new Point(); private var curPoint:Point = new Point(); private var segmentLength:Number = 20; private var verticesList:Vector.<b2Vec2> = new Vector.<b2Vec2>(); private var isDrawing:Boolean = false; public function Main() { //创建box2D世界 world = LDEasyBox2D.createWorld(); //创建box2D调试图 addChild(LDEasyBox2D.createDebug(world)); //创建地面 LDEasyBox2D.createWrapWall(world, stage); //创建绘制线条的画布 spriteCanvas = new Sprite(); addChild(spriteCanvas); //侦听事件 addEventListener(Event.ENTER_FRAME, loop); stage.addEventListener(MouseEvent.MOUSE_DOWN, onStageMouseDown); stage.addEventListener(MouseEvent.MOUSE_UP, onStageMouseUp); stage.addEventListener(MouseEvent.MOUSE_MOVE, onStageMouseMove); } private function onStageMouseMove(e:MouseEvent):void { //如果鼠标没有按下,isDrawing为false,跳出 if(!isDrawing) return; spriteCanvas.graphics.lineTo(mouseX, mouseY); //记录鼠标坐标为当前curPoint,并计算curPoint与上一个点prePoint的距离 curPoint = new Point(mouseX, mouseY); var distance:Number = Point.distance(prePoint, curPoint); //当前后两个点的距离大于线段距离时,添加顶点 if (distance >= segmentLength) { verticesList.push(new b2Vec2(mouseX / 30, mouseY / 30)); prePoint = curPoint.clone(); } } private function onStageMouseUp(e:MouseEvent):void { isDrawing = false; spriteCanvas.graphics.clear(); //在鼠标位置随机创建一个圆形或矩形刚体 createPolygon(verticesList); //清空verticesList verticesList = new Vector.<b2Vec2>(); } private function onStageMouseDown(e:MouseEvent):void { //鼠标按下后,开始绘制 isDrawing = true; //设置线条样式 spriteCanvas.graphics.lineStyle(2); spriteCanvas.graphics.moveTo(mouseX, mouseY); //定义鼠标点为起点,并添加到Vector类型的verticesList数组中 curPoint = new Point(mouseX, mouseY); prePoint = curPoint.clone(); verticesList.push(new b2Vec2(mouseX / 30, mouseY / 30)); } private function loop(e:Event):void { world.Step(1 / 30, 10, 10); world.ClearForces(); world.DrawDebugData(); } private function createPolygon(vertices:Vector.<b2Vec2>):void { //1.创建刚体需求b2BodyDef var bodyRequest:b2BodyDef = new b2BodyDef(); bodyRequest.type = b2Body.b2_dynamicBody; bodyRequest.position.Set(0 ,0);//记得米和像素的转换关系 //2.Box2D世界工厂更具需求创建createBody()生产刚体 var body:b2Body=world.CreateBody(bodyRequest); //3.创建敢提形状需求fixtureRequest的子类 var fixtureRequest:b2FixtureDef = new b2FixtureDef(); fixtureRequest.density = 3; fixtureRequest.friction = 0.3; fixtureRequest.restitution = 0.2; //创建刚体形状 var polygonShape:b2PolygonShape = new b2PolygonShape(); //将vertices顶点传递给shape的SetAsVector方法,创建多边形形状 polygonShape.SetAsVector(vertices, vertices.length); fixtureRequest.shape = polygonShape; body.CreateFixture(fixtureRequest); } } }
多画几个刚体,慢慢你会发现。在绘制凹多边形时,刚体的胖子出现了很多错误的现象,甚至穿过了其他的对象。这是由Box2D引擎本身的算法引起的。Box2D的碰撞检测算法是基于凸多边形的,所以对于凹多边形,会出现很多意想不到的结果。
这条路是行不通了,只好选择另一条路了:组合法。
组合法创建刚体
用组合发创建刚体,就是不管刚体是凹多边形还是凸多边形,把它分成多个凸多边形,然后组合成一个整体。
你可能会问:“可谁知道怎么分啊?”
问的好,Antoan Angelov就知道。由他开发的b2Separator类可以根据b2Shape顶点,把它分成一个个凸多边形,然后用组合法组成复杂的多边形刚体。b2Separator主要有下面两个函数:
Separate
Separate( body:b2Body, fixtureDef:b2FixtureDef, verticesVec:Vector.<b2Vec2>, scale:Number = 30 ):void
Separator方法用来将verticeVec中的顶点分成多个小的凸多边形,并赋值给fixtrueDef,创建body多边形刚体。
每个参数说明如下:
- body:多边形刚体对象
- fixtureDef:fixture需求对象
- verticeVec:存储多边形所顶点的Vector数组
- scale:Box2D模拟时的缩放比例,这个值与b2DebugDraw中的SetDrawScale的参数值是一样的。
Validate
Validate( verticesVec:Vector.<b2Vec2> ):int
Validate的功能是检测多边形的刚体是否符合创建的标准,在不符合标准时,返回不符合标准的原因。
每个参数说明如下:
- verticeVec:存储多边形所顶点的Vector数组
返回值类型有下面几种:
- 0:顶点符合绘制多边形的标准
- 1:顶点连接的线段之间有交叉
- 2:顶点的顺序非顺时针绘制
- 3:出现1和2两种错误
只有返回值为0时,才可以用Separator方法创建多边形。
了解了b2Separator之后,我们用组合法完成了多边形的绘制,效果如下,点击并拖动鼠标绘制多边形。
效果还不错哦。完整的代码和注释如下:
package { import Box2D.Collision.b2AABB; import Box2D.Collision.Shapes.b2PolygonShape; import Box2D.Common.Math.b2Vec2; import Box2D.Dynamics.b2Body; import Box2D.Dynamics.b2BodyDef; import Box2D.Dynamics.b2DebugDraw; import Box2D.Dynamics.b2FixtureDef; import Box2D.Dynamics.b2World; import Box2DSeparator.b2Separator; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.Point; /** * http://www.ladeng6666.com * @author ladeng6666 */ public class MainWithSeparator extends Sprite { private var world:b2World; private var spriteCanvas:Sprite; private var prePoint:Point = new Point(); private var curPoint:Point = new Point(); private var segmentLength:Number = 20; private var verticesList:Vector.<b2Vec2> = new Vector.<b2Vec2>(); private var isDrawing:Boolean = false; public function MainWithSeparator() { //创建box2D世界 world = LDEasyBox2D.createWorld(); //创建box2D调试图 addChild(LDEasyBox2D.createDebug(world)); //创建地面 LDEasyBox2D.createWrapWall(world,stage); spriteCanvas = new Sprite(); addChild(spriteCanvas); //侦听事件 addEventListener(Event.ENTER_FRAME, loop); stage.addEventListener(MouseEvent.MOUSE_DOWN, onStageMouseDown); stage.addEventListener(MouseEvent.MOUSE_UP, onStageMouseUp); stage.addEventListener(MouseEvent.MOUSE_MOVE, onStageMouseMove); } private function onStageMouseMove(e:MouseEvent):void { //如果鼠标没有按下,isDrawing为false,跳出 if(!isDrawing) return; spriteCanvas.graphics.lineTo(mouseX, mouseY); //记录鼠标坐标为当前curPoint,并计算curPoint与上一个点prePoint的距离 curPoint = new Point(mouseX, mouseY); var distance:Number = Point.distance(prePoint, curPoint); //当前后两个点的距离大于线段距离时,添加顶点 if (distance >= segmentLength) { //记录顶点到verticesList数组中 verticesList.push(new b2Vec2(mouseX / 30, mouseY / 30)); prePoint = curPoint.clone(); } } private function onStageMouseUp(e:MouseEvent):void { isDrawing = false; spriteCanvas.graphics.clear(); //在鼠标位置随机创建一个圆形或矩形刚体 createPolygon(); //清空存储顶点的Vector数组 verticesList = new Vector.<b2Vec2>(); } private function onStageMouseDown(e:MouseEvent):void { //鼠标按下后,开始绘制 isDrawing = true; //设置线条样式 spriteCanvas.graphics.lineStyle(2); spriteCanvas.graphics.moveTo(mouseX, mouseY); //定义鼠标点为起点,并添加到Vector类型的verticesList数组中 curPoint = new Point(mouseX, mouseY); prePoint = curPoint.clone(); verticesList.push(new b2Vec2(mouseX / 30, mouseY / 30)); } private function loop(e:Event):void { world.Step(1 / 30, 10, 10); world.ClearForces(); world.DrawDebugData(); } private function createPolygon():void { //1.创建刚体需求b2BodyDef var bodyRequest:b2BodyDef = new b2BodyDef(); bodyRequest.type = b2Body.b2_dynamicBody; bodyRequest.position.Set(0 ,0);//记得米和像素的转换关系 //2.Box2D世界工厂更具需求创建createBody()生产刚体 var body:b2Body=world.CreateBody(bodyRequest); //3.创建敢提形状需求b2ShapeDef的子类 //创建矩形刚体形状需求 var fixtureRequest:b2FixtureDef = new b2FixtureDef(); fixtureRequest.density = 3; fixtureRequest.friction = 0.3; fixtureRequest.restitution = 0.2; //创建一个Separator对象 var separator:b2Separator = new b2Separator(); //验证顶点是否符合创建多边形的标准 var validate:int = separator.Validate(verticesList); //如果是顶点因非顺时针不符标准,则反转数组中的顶点顺序 if (validate == 2) { verticesList.reverse(); }else if (validate != 0) { //如果不符合多边形标准,跳出 return; } //将顶点分解成多个凸多边形,组合成复杂的多边形 separator.Separate(body, fixtureRequest, verticesList); } } }
值得一提的是,因为非顺时针顺序的顶点是无法创建多边形的,所以在validate检测顶点返回2之后,调用verticesList.reverse()方法,反转顶点的顺序。这样用户不管顺时针还是逆时针都可以绘制出刚体来。
上面两个示例中,都用到了LDEasyBox2D,这是我写的一个静态类,简化了刚体创建的过程,如果你不熟悉,请参考我的教程。
联系作者
不错不错,昨天被折腾死了,我说怎么画个圆不受世界控制
只要我的教程对你有帮助就好了!
碉堡了!
PS:有没有办法可以把人工绘制的矩形组成的地图元件让Box2D自动生成刚体?
酷~
这么一来就弥补了凸多边形算法的不足!
求指教
import Box2DSeparator.b2Separator;
这个东西在什么里 我的里面没有这个啊
这个类是外国高手写的类库,不是Box2D默认的类,可以在我的源文件中下载到。
你好,拉登大师,我想请教1下,怎么可以改变一个已经实例化的刚体大小呢?
谢谢你的关注和提问,我会在稍后的教程中讲到这个问题,思路是把shape删掉后重新绘制,你想看到什么样的示例呢?
shape删除掉再重新绘制不会突然造成一个外力么?实例做一个收缩放大的心脏在物理空间跳动如何呢?还有拉登大师,水的效果又怎么做呢?我想到的是是,1个水珠是1个圆形刚体,然后皮肤用滤镜模糊,但不知道效率如何。还有皮球的刚体又怎么实现呢。就像用手压1个皮球,会有1部分陷进去,松手又弹回来。。。哎呀,BOX2D想问好多问题啊~~~~继续期待您的教程~~~~
你的问题好多啊,继续关注我的网站吧,我会继续写更多的Box2D教程的,你的问题慢慢都会出教程的!!
如果多边形是由2个三角形构成,也就是说构造多边形的数组是[{x:0, y:50}, {x:50, y:0}, {x:100, y:50}, {x:150, y:0}, {x:200, y:50}],那么b2Separator就会判断出现线条交叉,创建不出刚体,请问这个问题怎么解决?
另外,我觉得新的评论系统看起来没有以前的好,我觉得以前那种嵌套模式阅读评论比较容易
如果是有多个形状构成的话,建议你用createFixture()或createFixture2()创建多个b2Fixture对象来组合成一个刚体!
另外,评论系统也改回来了,是不是你想要的效果?
是的,这么一改,评论框就漂亮多了
对了,有没有感觉我的网站速度快了一点?
感觉访问速度一直都很快啊