外门弟子
阅读权限 1
积分 551
侠名
UID 18802
主题
帖子
精华
好友
银子
金子
贡献
威望
推广
活跃
荣耀
注册时间 2020-2-20
最后登录 1970-1-1
在线时间 小时
个人主页
|
【游客模式】——注册会员,加入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)
|
|