11RIA 闪客社区 - 最赞 Animate Flash 论坛

搜索
查看: 2126|回复: 0
上一主题 下一主题

[2D 物理引擎] 【9RIA—ladeng6666】—【Box2D系列教程 篇外篇】切割Box2D对象(四)—添加纹理

[复制链接] TA的其它主题
发表于 2018-2-6 13:38:12 | 显示全部楼层 |阅读模式

【游客模式】——注册会员,加入11RIA 闪客社区吧!一起见证Flash的再次辉煌……

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

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


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


本系列教程的第四部分由Antoan Angelov完成,他不仅实现了在Box2D对象中映射各种位图的功能,同时还提升了代码的效率和鲁棒性。
最终结果如下:



试着用鼠标切割对象。
下面的代码以及做了非常全面的注解,我就不再赘述了:

[Actionscript3] 纯文本查看 复制代码
package {
        import flash.display.*;
        import Box2D.Dynamics.*;
        import Box2D.Dynamics.Joints.*;
        import Box2D.Collision.*;
        import Box2D.Collision.Shapes.*;
        import Box2D.Common.Math.*;
        import flash.events.Event;
        import flash.events.KeyboardEvent;
        import flash.events.MouseEvent;
        import flash.geom.Matrix;

        public class main extends MovieClip {
/*
                        Box2D 刚体切割,Antoan Angelov制作.
                */

                var world:b2World;
                var tempBox:b2Body;
                var stageW:Number=stage.stageWidth,stageH:Number=stage.stageHeight;
                var cont:Sprite = new Sprite();
                var begX:Number,begY:Number,endX:Number,endY:Number;
                var polyShape:b2PolygonShape;
                var enterPointsVec:Vector.<b2Vec2> = new Vector.<b2Vec2>();
                var mouseReleased:Boolean=false;
                var objectsCont:Sprite = new Sprite();
                var laserCont:Sprite = new Sprite();
                var numEnterPoints:int=0,i:int;
                var boxDef:b2PolygonShape;
                var fixtureDef:b2FixtureDef,bodyDef:b2BodyDef,body:b2Body;
                var woodTexture:BitmapData,rockTexture:BitmapData;

                public function main() {
                        // 首先用BitmapData对象创建纹理
                        var tempSpr:Sprite;

                        tempSpr = new texture1();
                        woodTexture=new BitmapData(tempSpr.width,tempSpr.height);
                        woodTexture.draw(tempSpr);

                        tempSpr = new texture2();
                        rockTexture=new BitmapData(tempSpr.width,tempSpr.height);
                        rockTexture.draw(tempSpr);

                        //设置Box2D世界
                        world=new b2World(new b2Vec2(0,10),true);

                        // 创建一个静态的刚体作为地面,然后创建对应的sprite对象.
                        bodyDef = new b2BodyDef();
                        bodyDef.type=b2Body.b2_staticBody;
                        fixtureDef = new b2FixtureDef();
                        fixtureDef.density=5;
                        fixtureDef.friction=1;
                        fixtureDef.restitution=0;
                        boxDef = new b2PolygonShape();
                        boxDef.SetAsBox((stageW)/30, 10/30);
                        bodyDef.position.Set((0.5*stageW)/30, stageH/30);
                        fixtureDef.shape=boxDef;
                        tempBox=world.CreateBody(bodyDef);
                        tempBox.CreateFixture(fixtureDef);

                        tempSpr = new Sprite();
                        tempSpr.graphics.lineStyle(2, 0x00FF00);
                        tempSpr.graphics.beginFill(0x00FF00, 0.3);
                        tempSpr.graphics.drawRect(-0.5*stageW, (stageH-10), 2*stageW, 20);
                        tempSpr.graphics.endFill();
                        addChild(tempSpr);

                        //初始化被切割的刚体对象
                        fixtureDef.density=5;
                        fixtureDef.friction=0.2;
                        fixtureDef.restitution=0;

                        createBody((230)/30, 50/30, [new b2Vec2(-100/30, -75/30), new b2Vec2(100/30, -75/30), new b2Vec2(100/30, 75/30), new b2Vec2(-100/30, 75/30)], woodTexture);
                        createBody((stageW-230)/30, 50/30, [new b2Vec2(3.0795984417042894, 1.275611441216966), new b2Vec2(1.2756114412169661, 3.0795984417042894), new b2Vec2(-1.275611441216966, 3.0795984417042894), new b2Vec2(-3.0795984417042894, 1.2756114412169663), new b2Vec2(-3.0795984417042894, -1.2756114412169657), new b2Vec2(-1.2756114412169677, -3.0795984417042885), new b2Vec2(1.2756114412169666, -3.079598441704289), new b2Vec2(3.0795984417042885, -1.275611441216968)], rockTexture);

                        //为什么要创建这个enterPointsVec变量?你可以在intersection()方法的注解中找到答案
                        enterPointsVec=new Vector.<b2Vec2>(numEnterPoints);

                        this.addChild(cont);
                        cont.addChild(objectsCont);
                        cont.addChild(laserCont);

                        stage.addEventListener(MouseEvent.MOUSE_DOWN, mDown);
                        addEventListener(Event.ENTER_FRAME, update);
                }

                private function createBody(xPos:Number, yPos:Number, verticesArr:Array, texture:BitmapData) {
                        var vec:Vector.<b2Vec2>=Vector.<b2Vec2>(verticesArr);
                        bodyDef = new b2BodyDef();
                        bodyDef.type=b2Body.b2_dynamicBody;
                        boxDef = new b2PolygonShape();
                        boxDef.SetAsVector(vec);
                        bodyDef.position.Set(xPos, yPos);
                        //用userData类存储每个刚体的ID、vertices和texture
                        bodyDef.userData=new userData(numEnterPoints,vec,texture);
                        objectsCont.addChild(bodyDef.userData);
                        fixtureDef.shape=boxDef;
                        tempBox=world.CreateBody(bodyDef);
                        tempBox.CreateFixture(fixtureDef);
                        tempBox.SetBullet(true);
                        numEnterPoints++;
                }

                private function mDown(e:MouseEvent) {
                        begX=mouseX;
                        begY=mouseY;

                        stage.addEventListener(MouseEvent.MOUSE_UP, mUp);
                        stage.addEventListener(MouseEvent.MOUSE_MOVE, mMove);
                }

                private function mMove(e:MouseEvent) {
                        laserCont.graphics.clear();
                        laserCont.graphics.lineStyle(2);
                        laserCont.graphics.moveTo(begX, begY);
                        laserCont.graphics.lineTo(mouseX, mouseY);
                }

                private function mUp(e:MouseEvent) {
                        mouseReleased=true;

                        stage.removeEventListener(MouseEvent.MOUSE_UP, mUp);
                        stage.removeEventListener(MouseEvent.MOUSE_MOVE, mMove);
                }

                public function update(e:Event):void {
                        if (mouseReleased) {
                                // 使用world.RayCast()方法(这个方法我用了两次,为什么呢?你可以在inersection()方法中找到答案)获得你所绘制的线条与所有刚体之间的交点.

                                endX=mouseX;
                                endY=mouseY;

                                var p1:b2Vec2=new b2Vec2(begX/30,begY/30);
                                var p2:b2Vec2=new b2Vec2(endX/30,endY/30);

                                world.RayCast(intersection, p1, p2);
                                world.RayCast(intersection, p2, p1);
                                enterPointsVec=new Vector.<b2Vec2>(numEnterPoints);
                                mouseReleased=false;
                        }

                        world.Step(1/24, 90, 90);
                        world.ClearForces();

                        var p:b2Body,spr:Sprite;

                        //将所有刚体对应的Sprite对象同步到每个刚体上.

                        for (p = world.GetBodyList(); p; p = p.GetNext()) {
                                spr=p.GetUserData();
                                if (spr) {
                                        spr.x=p.GetPosition().x*30;
                                        spr.y=p.GetPosition().y*30;
                                        spr.rotation=p.GetAngle()*180/Math.PI;
                                }
                        }
                }

                private function intersection(fixture:b2Fixture, point:b2Vec2, normal:b2Vec2, fraction:Number):Number {
                        var spr:Sprite=fixture.GetBody().GetUserData();

                        // 整个成想后总我只创建了一个全局vector变量:enterPointsVec,为什么我一定要让你知道这一点呢? 
                        //OK,问题在于world.RayCast()方法只在检测到给定的线条穿过刚体时才会调用这个函数。
                        //线条与刚体之间必须有2个交点才可以说明刚体被切割,所以我需要再次调用world.RayCast()方法,不过这次是由B到A——BA进入刚体的点刚好是AB离开刚体的点。
                        // 由于这个原因,我用一个名为enterPointsVec的Vector变量存储这个点(AB进入刚体的点)。然后如果发现BA也进入了刚体,则调用splitObj()函数!
                        // 我需要每个刚体都有唯一的ID,来知道它所对应的enter point,我将这个信息存储到刚体的userData中去了。

                        if (spr is userData) {
                                var userD:userData=spr as userData;

                                if (enterPointsVec[userD.id]) {
                                        //如果刚体已经有了一个交互点,那么现在就有了两个交互点,因此先要将它切割成两块——这就是splitObjt()的工作了。
                                        // 但是在调用splitObj()方法之前,首先要绘制两个焦点——蓝色表示起点,红色表示终点。
                                        laserCont.graphics.lineStyle(4, 0x0000FF);
                                        laserCont.graphics.drawCircle(enterPointsVec[userD.id].x*30, enterPointsVec[userD.id].y*30, 7);

                                        laserCont.graphics.lineStyle(4, 0xFF0000);
                                        laserCont.graphics.drawCircle(point.x*30, point.y*30, 7);

                                        splitObj(fixture.GetBody(), enterPointsVec[userD.id], point.Copy());
                                } else {
                                        enterPointsVec[userD.id]=point;
                                }

                        }
                        return 1;
                }

                private function splitObj(sliceBody:b2Body, A:b2Vec2, B:b2Vec2):void {
                        var origFixture:b2Fixture=sliceBody.GetFixtureList();
                        var poly:b2PolygonShape=origFixture.GetShape() as b2PolygonShape;
                        var verticesVec:Vector.<b2Vec2>=poly.GetVertices(),numVertices:int=poly.GetVertexCount();
                        var shape1Vertices:Vector.<b2Vec2> = new Vector.<b2Vec2>(), shape2Vertices:Vector.<b2Vec2> = new Vector.<b2Vec2>();
                        var origUserData:userData=sliceBody.GetUserData(),origUserDataId:int=origUserData.id,d:Number;

                        // 首先,销毁原始的刚体,然后将其对应的Sprite对象从childList中移除
                        world.DestroyBody(sliceBody);
                        objectsCont.removeChild(origUserData);

                        // world.RayCast()方法返回的point是一个世界坐标,所以我使用b2Body.GetLocalPoint()方法将其转换为本地坐标
                        A=sliceBody.GetLocalPoint(A);
                        B=sliceBody.GetLocalPoint(B);

                        //使用shape1Vertices和shape2Vertices存储切割后生成的两个图形 
                        //因为这两个图形都包含顶点A和B,将它们添加到图形中 
                        shape1Vertices.push(A, B);
                        shape2Vertices.push(A, B);

                        //声明原始刚体的所有顶点 
                        //用det()(det即determinant)方法来判断给定的点在AB两个点分别在哪一边。这个方法需要的参数是3个points
                        //-如果返回值>0,则给定点与AB成顺时针顺序(即给定点在AB的下方)
                        //-如果返回值=0,则给定点与AB在同一条直线上
                        //-如果返回值<0,则给定点与AB成逆时针顺序(即给定点在AB的上方) 
                        for (i=0; i<numVertices; i++) {
                                d=det(A.x,A.y,B.x,B.y,verticesVec[i].x,verticesVec[i].y);
                                if (d>0) {
                                        shape1Vertices.push(verticesVec[i]);
                                } else {
                                        shape2Vertices.push(verticesVec[i]);
                                }
                        }

                        //为了得到连个图形,需要让顶点呈顺时针排列.
                        //我自定义了方法arrangeClockwise()(参数是以vector变量)重新将图形的顶点排列呈顺时针并返回包含这些顶点的vector
                        shape1Vertices=arrangeClockwise(shape1Vertices);
                        shape2Vertices=arrangeClockwise(shape2Vertices);

                        //设置两个新图形的属性
                        bodyDef = new b2BodyDef();
                        bodyDef.type=b2Body.b2_dynamicBody;
                        bodyDef.position.SetV(sliceBody.GetPosition());
                        fixtureDef = new b2FixtureDef();
                        fixtureDef.density=origFixture.GetDensity();
                        fixtureDef.friction=origFixture.GetFriction();
                        fixtureDef.restitution=origFixture.GetRestitution();

                        //创建第一个图形
                        polyShape = new b2PolygonShape();
                        polyShape.SetAsVector(shape1Vertices);
                        fixtureDef.shape=polyShape;

                        bodyDef.userData=new userData(origUserDataId,shape1Vertices,origUserData.texture);
                        objectsCont.addChild(bodyDef.userData);
                        enterPointsVec[origUserDataId]=null;

                        body=world.CreateBody(bodyDef);
                        body.SetAngle(sliceBody.GetAngle());
                        body.CreateFixture(fixtureDef);
                        body.SetBullet(true);

                        //创建第二个图形
                        polyShape = new b2PolygonShape();
                        polyShape.SetAsVector(shape2Vertices);
                        fixtureDef.shape=polyShape;

                        bodyDef.userData=new userData(numEnterPoints,shape2Vertices,origUserData.texture);
                        objectsCont.addChild(bodyDef.userData);
                        enterPointsVec.push(null);
                        numEnterPoints++;

                        body=world.CreateBody(bodyDef);
                        body.SetAngle(sliceBody.GetAngle());
                        body.CreateFixture(fixtureDef);
                        body.SetBullet(true);
                }

                private function arrangeClockwise(vec:Vector.<b2Vec2>):Vector.<b2Vec2> {
                        // 算法很简单 
                        // 首先,将所有的points安装它们的x坐标由小到大排列
                        // 其次,用最左边和最右边的两个点(即C点和D点)创建一个tempVec,用来存储重新排序后的顶点
                        // 再次,声明每一个顶点,利用前面提过的det()方法,从CD上方的顶点开始添加每个顶点,随后添加CD下方的顶点 
                        // 就这些!

                        var n:int=vec.length,d:Number,i1:int=1,i2:int=n-1;
                        var tempVec:Vector.<b2Vec2>=new Vector.<b2Vec2>(n),C:b2Vec2,D:b2Vec2;

                        vec.sort(comp1);

                        tempVec[0]=vec[0];
                        C=vec[0];
                        D=vec[n-1];

                        for (i=1; i<n-1; i++) {
                                d=det(C.x,C.y,D.x,D.y,vec[i].x,vec[i].y);
                                if (d<0) {
                                        tempVec[i1++]=vec[i];
                                } else {
                                        tempVec[i2--]=vec[i];
                                }
                        }

                        tempVec[i1]=vec[n-1];

                        return tempVec;
                }

                private function comp1(a:b2Vec2, b:b2Vec2):Number {
                        //这个是arrangeClockwise()方法里用到的一个比较函数——它可以很快的判断两个point的x坐标的大小
                        if (a.x>b.x) {
                                return 1;
                        } else if (a.x<b.x) {
                                return -1;

                        }
                        return 0;
                }

                private function det(x1:Number, y1:Number, x2:Number, y2:Number, x3:Number, y3:Number):Number {
                        // 这个方法用来返回3x3矩阵的行列式值
                        // 如果你学过矩阵,你肯定知道如果三个给定的点顺时针排列则返回正值,如果逆时针排列则返回负值,如果三点在一条线,则返回0
                        // 另外一个有用的知识点,行列式值的绝对值是这个三个点组成的三角形面积的两倍
                        return x1*y2+x2*y3+x3*y1-y1*x2-y2*x3-y3*x1;
                }

        }

}


userData类:
[Actionscript3] 纯文本查看 复制代码
package 
{
        import Box2D.Common.Math.b2Vec2;
        import flash.display.Sprite;
        import flash.display.BitmapData;
        import flash.geom.Matrix;

        public class userData extends Sprite
        {
                var id:int, texture:BitmapData;

                public function userData(id:int, verticesVec:Vector.<b2Vec2>, texture:BitmapData)
                {
                        this.id = id;
                        this.texture = texture;

                        // 使用matrix可以让映射位图与图形的中心点一致——对映射位图向左移动半宽,向上移动半高
                        var m:Matrix = new Matrix();
                        m.tx = -texture.width*0.5;
                        m.ty = -texture.height*0.5;

                        //顺时针连续在每个顶点绘制线条,然后用beginBitmapFill()方法添加纹理
                        this.graphics.lineStyle(2);
                        this.graphics.beginBitmapFill(texture, m, true, true);
                        this.graphics.moveTo(verticesVec[0].x*30, verticesVec[0].y*30);
                        for (var i:int=1; i<verticesVec.length; i++) this.graphics.lineTo(verticesVec[i].x*30, verticesVec[i].y*30);
                        this.graphics.lineTo(verticesVec[0].x*30, verticesVec[0].y*30);
                        this.graphics.endFill();
                }
        }
}


结果非常神奇,所以我相信你对切割技术在游戏中的应用一定有了自己的想法。

下载:
main.zip (2.98 MB, 下载次数: 1)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐 上一条 /1 下一条

感谢所有支持论坛的朋友:下面展示最新的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)



快速回复 返回顶部 返回列表