9RIA-ladeng6666 发表于 2018-2-6 14:09:10

【9RIA—ladeng6666】—【Box2D系列教程 15】运行时绘制多边形刚体

转载:9RIA游戏开发者社区(天地会)
作者:ladeng6666(拉登大叔)
作者博客:http://www.ladeng6666.com/blog/


【Box2D系列教程-导航帖】—拉登大叔出品(总贴)


学习了信手绘制线条刚体,你有没有想过信手绘制多边形刚体呢?”当然了,你不是在上一篇教程中说过了吗?快快说来!!”
是的,正如我说说的,这是重力大师里可以绘制的对象之一。在之前的Box2D多边形刚体教程中。我们学会了用组合法和原生法创建多边形。谈到运行时创建多边形刚体,你会选择哪种方法呢?如果是原生法,恭喜你,我们想到一块去了。借一步讲话,我给你细细道来。


原生法绘制多边形刚体
还记得在信手绘制线条刚体中创建的那些线段吗?而且我用红色圆点标示了他们的坐标。把这些点传递给b2Shape的SetAsVector方法就完成了多边形的绘制,简单吧!(如果你不知道如何用原生法创建多边形,请查看“Box2D多边形刚体”)。效果如下,同样请不要交叉绘制,而且不要逆时针绘制。

attach://868.swf

完整代码和注释如下:
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之后,我们用组合法完成了多边形的绘制,效果如下,点击并拖动鼠标绘制多边形。

attach://869.swf

效果还不错哦。完整的代码和注释如下:
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,这是我写的一个静态类,简化了刚体创建的过程,如果你不熟悉,请参考我的教程。

页: [1]
查看完整版本: 【9RIA—ladeng6666】—【Box2D系列教程 15】运行时绘制多边形刚体

感谢所有支持论坛的朋友:下面展示最新的5位赞助和充值的朋友……更多赞助和充值朋友的信息,请查看:永远的感谢名单

SGlW(66139)、 anghuo(841)、 whdsyes(255)、 longxia(60904)、 囫囵吞澡(58054)

下面展示总排行榜的前3名(T1-T3)和今年排行榜的前3名的朋友(C1-C3)……更多信息,请查看:总排行榜今年排行榜

T1. fhqu1462(969)、 T2. lwlpluto(14232)、 T3. 1367926921(962)  |  C1. anghuo(147)、 C2. fdisker(27945)、 C3. 囫囵吞澡(58054)