• Box2D教程5碰撞检测


    Box2D教程1-创建碰撞世界

    Box2D教程2-鼠标交互

    Box2D教程3-刚体绑定外观

    Box2D教程4-复杂刚体的复杂外观

    Box2D教程5-碰撞检测

    之前我们已经了解了如何通过Box2D创建一个物理世界,给刚体添加复杂材质,鼠标交互。在游戏开发里面我们通常要判断两个物体相互碰撞了,然后进行相应的操作。比如“愤怒的小鸟”,当小鸟碰撞到箱子的时候,我们需要知道这两个物体碰撞了,然后判断碰撞的力度(后面的教程会讲),然后对箱子进行操作。这个教程就是用来处理Box2D的碰撞检测问题。

    这个教程仍然基于先前的教程,关于如何创建一个物理世界,这里就不解释了。为了要实现碰撞检测,需要使用到Box2D的B2ContactListener类。该类是一个抽象类,不能直接被实例化,它包含四个方法:BeginContact, EndContact, PreSolve, PostSolve,我们必须先继承它创建自定义的ContactListener,然后override你需要的方法。这个教程主要检测两个物体产生碰撞以及碰撞结束。因此我们override BeginContact(开始碰撞)和EndContact(碰撞结束)方法。

    首先我们创建一个简单的物理世界,四个边框和三个球体,这在先前的教程有详细说明

    View Code
      1 package
    2 {
    3 import Box2D.Collision.Shapes.b2CircleShape;
    4 import Box2D.Collision.Shapes.b2PolygonShape;
    5 import Box2D.Collision.Shapes.b2Shape;
    6 import Box2D.Collision.b2AABB;
    7 import Box2D.Common.Math.b2Vec2;
    8 import Box2D.Dynamics.Joints.b2MouseJoint;
    9 import Box2D.Dynamics.Joints.b2MouseJointDef;
    10 import Box2D.Dynamics.b2Body;
    11 import Box2D.Dynamics.b2BodyDef;
    12 import Box2D.Dynamics.b2DebugDraw;
    13 import Box2D.Dynamics.b2Fixture;
    14 import Box2D.Dynamics.b2FixtureDef;
    15 import Box2D.Dynamics.b2World;
    16
    17 import comingx.jingle.common.Console;
    18 import comingx.jingle.common.CustomContactListener;
    19 import comingx.jingle.events.CollisionEvent;
    20 import comingx.jingle.userdata.BallUserData;
    21
    22 import flash.display.GradientType;
    23 import flash.display.Sprite;
    24 import flash.events.Event;
    25 import flash.events.MouseEvent;
    26 import flash.geom.Matrix;
    27 import flash.text.TextField;
    28 import flash.text.TextFieldAutoSize;
    29 import flash.text.TextFormat;
    30
    31 [SWF(width="500",height="300",frameRate="30")]
    32 public class Box2DCheckCollision extends Sprite
    33 {
    34 //屏幕像素单位转换成物理世界的距离单位
    35 private const PIXEL_TO_METER:Number = 30;
    36
    37 //物理世界
    38 private var world:Box2D.Dynamics.b2World;
    39
    40 private var _mouseXWorldPhys:Number;
    41 private var _mouseYWorldPhys:Number;
    42 private var _mouseXWorld:Number;
    43 private var _mouseYWorld:Number;
    44
    45 private var _mousePVec:b2Vec2 = new b2Vec2();
    46 private var _groundBody:b2Body;
    47 private var _mouseJoint:b2MouseJoint;
    48
    49 private var mouseDown:Boolean = false;
    50
    51 private var console:Console;
    52
    53 public function Box2DCheckCollision()
    54 {
    55 drawBackground();
    56 createWorld();
    57 createWall();
    58 createBall();
    59 createDebugDraw();
    60 addEventListener(Event.ENTER_FRAME, handleEnterFrame);
    61 addEventListener(MouseEvent.MOUSE_DOWN,handleMouseDown);
    62 addEventListener(MouseEvent.MOUSE_UP,handleMouseUp);
    63 addEventListener(MouseEvent.CLICK,handleMouseUp);
    64 addEventListener(Event.MOUSE_LEAVE,handleMouseUp);
    65 }
    66
    67 private function createWorld():void
    68 {
    69 //重力向量
    70 var gravity:b2Vec2 = new b2Vec2(0,9.0);
    71 //是否休眠
    72 var doSleep:Boolean = true;
    73 world = new b2World(gravity,doSleep);
    74 world.SetWarmStarting(true);
    75 }
    76
    77 private function createWall():void
    78 {
    79 //1.需要创建的墙刚体
    80 var leftWall:b2Body;
    81 //2.刚体定义
    82 var leftWallBodyDef:b2BodyDef = new b2BodyDef();
    83 //刚体类型和位置
    84 leftWallBodyDef.type = b2Body.b2_staticBody;
    85 //注意刚体的注册中心都是在物体的中心位置
    86 leftWallBodyDef.position.Set(10/PIXEL_TO_METER, stage.stageHeight/2/PIXEL_TO_METER);
    87 //工厂模式创建刚体
    88 leftWall = world.CreateBody(leftWallBodyDef);
    89
    90 //3.刚体修饰物定义
    91 var leftWallFixtureDef:b2FixtureDef = new b2FixtureDef();
    92 //密度
    93 leftWallFixtureDef.density = 1.0;
    94 //摩擦粗糙程度
    95 leftWallFixtureDef.friction = 0.3;
    96 //力度返回程度(弹性)
    97 leftWallFixtureDef.restitution = 1.0;
    98
    99 //4.创建墙形状
    100 var leftWallShape:b2PolygonShape = new b2PolygonShape();
    101 //此处参数为宽和高度的一半值
    102 leftWallShape.SetAsBox(10/PIXEL_TO_METER, stage.stageHeight/2/PIXEL_TO_METER);
    103
    104 //将形状添加到刚体修饰物
    105 leftWallFixtureDef.shape = leftWallShape;
    106
    107 leftWall.CreateFixture(leftWallFixtureDef);
    108
    109
    110 //下面创建其他三面墙,共用leftwall的几个变量
    111 leftWallBodyDef.position.Set((stage.stageWidth-10)/PIXEL_TO_METER, stage.stageHeight/2/PIXEL_TO_METER);
    112 var rightWall:b2Body = world.CreateBody(leftWallBodyDef);
    113 rightWall.CreateFixture(leftWallFixtureDef);
    114
    115
    116 leftWallBodyDef.position.Set( stage.stageWidth/2/PIXEL_TO_METER, (stage.stageHeight-10)/PIXEL_TO_METER);
    117 var bottomWall:b2Body = world.CreateBody(leftWallBodyDef);
    118 leftWallShape.SetAsBox(stage.stageWidth/2/PIXEL_TO_METER, 10/PIXEL_TO_METER);
    119 bottomWall.CreateFixture(leftWallFixtureDef);
    120
    121 leftWallBodyDef.position.Set( stage.stageWidth/2/PIXEL_TO_METER, 10/PIXEL_TO_METER);
    122 var topWall:b2Body = world.CreateBody(leftWallBodyDef);
    123 topWall.CreateFixture(leftWallFixtureDef);
    124 }
    125
    126 private function createBall():void
    127 {
    128 var ballDef:b2BodyDef = new b2BodyDef();
    129 ballDef.type = b2Body.b2_dynamicBody;
    130 ballDef.position.Set(50/PIXEL_TO_METER,30/PIXEL_TO_METER);
    131 var ballBig:b2Body = world.CreateBody(ballDef);
    132
    133 var circleShape:b2CircleShape = new b2CircleShape(30/PIXEL_TO_METER);
    134
    135 var ballFixtureDef:b2FixtureDef = new b2FixtureDef();
    136
    137 ballFixtureDef.shape = circleShape;
    138 ballFixtureDef.density = 1.0;
    139 ballFixtureDef.restitution = 0.5;
    140
    141 ballBig.CreateFixture(ballFixtureDef);
    142
    143 ballDef.position.Set(200/PIXEL_TO_METER, 30/PIXEL_TO_METER);
    144 var ballMedium:b2Body = world.CreateBody(ballDef);
    145 circleShape = new b2CircleShape(20/PIXEL_TO_METER);
    146 ballFixtureDef.shape = circleShape;
    147 ballMedium.CreateFixture(ballFixtureDef);
    148
    149 ballDef.position.Set(400/PIXEL_TO_METER, 30/PIXEL_TO_METER);
    150 var ballSmall:b2Body = world.CreateBody(ballDef);
    151 circleShape = new b2CircleShape(15/PIXEL_TO_METER);
    152 ballFixtureDef.shape = circleShape;
    153 ballSmall.CreateFixture(ballFixtureDef);
    154 }
    155
    156 private function createDebugDraw():void
    157 {
    158 //创建一个sprite,可以将测试几何物体放入其中
    159 var debugSprite:Sprite = new Sprite();
    160 addChild(debugSprite);
    161 var debugDraw:b2DebugDraw = new b2DebugDraw();
    162 debugDraw.SetSprite(debugSprite);
    163 //设置边框厚度
    164 debugDraw.SetLineThickness(1.0);
    165 //边框透明度
    166 debugDraw.SetAlpha(1.0);
    167 //填充透明度
    168 debugDraw.SetFillAlpha(0.5);
    169 //设置显示对象
    170 debugDraw.SetFlags(b2DebugDraw.e_shapeBit);
    171 //物理世界缩放
    172 debugDraw.SetDrawScale(PIXEL_TO_METER);
    173 world.SetDebugDraw(debugDraw);
    174 }
    175
    176 private function handleEnterFrame(evt:Event):void
    177 {
    178 UpdateMouseWorld();
    179 mouseDrag();
    180
    181 var timeStep:Number = 1/30;
    182 var velocityInterations:int = 10;
    183 var positionIterations:int = 10;
    184
    185 world.Step(timeStep,velocityInterations,positionIterations);
    186 //在2.1版本清除力,以提高效率
    187 world.ClearForces();
    188 //绘制
    189 world.DrawDebugData();
    190 }
    191
    192 private function drawBackground():void
    193 {
    194 var bg:Sprite = new Sprite();
    195 var matrix:Matrix = new Matrix();
    196 matrix.translate(100,100);
    197 bg.graphics.beginGradientFill(GradientType.RADIAL,[0xffffff,0xffaa00],[0.3,0.2],[0,255],matrix);
    198 bg.graphics.drawRect(0,0,stage.stageWidth,stage.stage.stageHeight);
    199 bg.graphics.endFill();
    200 addChild(bg);
    201
    202 //tips
    203 var tf:TextField = new TextField();
    204 tf.text = "拖动球产生碰撞";
    205 tf.autoSize = TextFieldAutoSize.LEFT;
    206 var fomat:TextFormat = new TextFormat("Kai,华文楷体", "20", 0x555555);
    207 tf.setTextFormat(fomat);
    208 tf.x = tf.y = 30;
    209 addChild(tf);
    210 }
    211
    212 private function UpdateMouseWorld():void
    213 {
    214 _mouseXWorldPhys = this.mouseX / PIXEL_TO_METER;
    215 _mouseYWorldPhys = this.mouseY / PIXEL_TO_METER;
    216
    217 _mouseXWorld = this.mouseX;
    218 _mouseYWorld = this.mouseY;
    219 }
    220 private function getBodyAtMouse(includeStatic:Boolean = false):b2Body
    221 {
    222 _mousePVec.Set(_mouseXWorldPhys,_mouseYWorldPhys);
    223 var aabb:b2AABB = new b2AABB();
    224 aabb.lowerBound.Set(_mouseXWorldPhys - 0.001, _mouseYWorldPhys - 0.001);
    225 aabb.upperBound.Set(_mouseXWorldPhys + 0.001, _mouseYWorldPhys + 0.001);
    226 var body:b2Body = null;
    227 var fixture:b2Fixture;
    228
    229 function getBodyCallback(fixture:b2Fixture):Boolean
    230 {
    231 var shape:b2Shape = fixture.GetShape();
    232 if(fixture.GetBody().GetType() != b2Body.b2_staticBody || includeStatic)
    233 {
    234 var inside:Boolean = shape.TestPoint(fixture.GetBody().GetTransform(), _mousePVec);
    235 if(inside)
    236 {
    237 body = fixture.GetBody();
    238 return false;
    239 }
    240 }
    241 return true;
    242 }
    243 world.QueryAABB(getBodyCallback, aabb);
    244 return body;
    245 }
    246
    247 private function mouseDrag():void
    248 {
    249 if(mouseDown && !_mouseJoint)
    250 {
    251 var body:b2Body = getBodyAtMouse();
    252 if(body)
    253 {
    254 var md:b2MouseJointDef = new b2MouseJointDef();
    255 md.bodyA = world.GetGroundBody();
    256 md.bodyB = body;
    257
    258 md.target.Set(_mouseXWorldPhys,_mouseYWorldPhys);
    259 md.collideConnected = true;
    260 md.maxForce = 300.0 * body.GetMass();
    261 _mouseJoint = world.CreateJoint(md) as b2MouseJoint;
    262 body.SetAwake(true);
    263 }
    264 }
    265
    266 if(!mouseDown)
    267 {
    268 if(_mouseJoint)
    269 {
    270 world.DestroyJoint(_mouseJoint);
    271 _mouseJoint = null;
    272 }
    273 }
    274
    275 if(_mouseJoint)
    276 {
    277 var p2:b2Vec2 = new b2Vec2(_mouseXWorldPhys,_mouseYWorldPhys);
    278 _mouseJoint.SetTarget(p2);
    279 }
    280 }
    281
    282 public function handleMouseDown(e:MouseEvent):void
    283 {
    284 mouseDown = true;
    285 }
    286
    287 public function handleMouseUp(e:MouseEvent):void
    288 {
    289 mouseDown = false;
    290 }
    291 }
    292 }

    下面为关键代码
    1. 创建自定义的ContactListener

     1 package comingx.jingle.common
    2 {
    3 import Box2D.Dynamics.Contacts.b2Contact;
    4 import Box2D.Dynamics.b2ContactListener;
    5
    6 import comingx.jingle.events.CollisionEvent;
    7 import comingx.jingle.userdata.BallUserData;
    8
    9 import flash.events.EventDispatcher;
    10
    11 public class CustomContactListener extends b2ContactListener
    12 {
    13 public var eventDispatcher:EventDispatcher;
    14
    15 public function CustomContactListener()
    16 {
    17 eventDispatcher = new EventDispatcher();
    18 }
    19
    20 override public function BeginContact(contact:b2Contact):void
    21 {
    22 var collisionEvent:CollisionEvent = new CollisionEvent(CollisionEvent.COLLISION_START);
    23 //墙的userdata为null,排除与墙的碰撞
    24 if(contact.GetFixtureA().GetBody().GetUserData() != null && contact.GetFixtureB().GetBody().GetUserData() != null)
    25 {
    26 collisionEvent.bodyAName = (contact.GetFixtureA().GetBody().GetUserData() as BallUserData).name;
    27 collisionEvent.bodyBName = (contact.GetFixtureB().GetBody().GetUserData() as BallUserData).name;
    28 eventDispatcher.dispatchEvent(collisionEvent);
    29 }
    30 }
    31
    32 override public function EndContact(contact:b2Contact):void
    33 {
    34 var collisionEvent:CollisionEvent = new CollisionEvent(CollisionEvent.COLLISION_END);
    35 if(contact.GetFixtureA().GetBody().GetUserData() != null && contact.GetFixtureB().GetBody().GetUserData() != null)
    36 {
    37 collisionEvent.bodyAName = (contact.GetFixtureA().GetBody().GetUserData() as BallUserData).name;
    38 collisionEvent.bodyBName = (contact.GetFixtureB().GetBody().GetUserData() as BallUserData).name;
    39 eventDispatcher.dispatchEvent(collisionEvent);
    40 }
    41 }
    42 }
    43 }

    b2ContactListener的是个方法将会每次Box2D计算时执行,因此它会不停的运行,只要有物体产生碰撞。那么我们如何知道我们想要的物体碰撞,即哪个球和哪个球碰撞了?Box2D碰撞的两个物体通过contact.GetFixtureA().GetBody()和contact.GetFixtureB().GetBody()这两个方法获取。我们预先给这两个刚体的UserData设置一个对象,对刚体进行命名,用来识别各个刚体,这样可以根据名字来知道碰撞的双方分别是什么。因此我们创建BallUserData类,存储刚体名字(还可以有其他数据,如果你需要的话)

    package comingx.jingle.userdata
    {
    import flash.display.Sprite;

    public class BallUserData
    {
    public var name:String;
    public var sprite:Sprite;
    public function BallUserData(name:String = "unanmed", sprite:Sprite = null)
    {
    this.name = name;
    this.sprite = sprite;
    }
    }
    }

    其中name属性标识了这个刚体的名字,sprite属性我们可以为其指定一个刚体外观。这样我们通过这个方法对刚体UserData进行赋值,从而给三个球命名,以期望在碰撞的时候知道是谁和谁碰撞了。
    在入口类的createBall()方法中添加如下代码:

    //对刚体进行命名
    ballBig.SetUserData(new BallUserData("big"));
    ballMedium.SetUserData(new BallUserData("medium"));
    ballSmall.SetUserData(new BallUserData("small"));

    我们看到在CustomContactListener中当两个碰撞物体的UserData不为NULL的时候(墙的UserData为NULL),即球不是与墙碰撞而是与球碰撞,此时可以抛出自定义的碰撞事件,并在事件中保存碰撞对象的名字。从而你可以在需要的地方侦听这个事件并获取到碰撞双方的名字,做相应的逻辑处理。

    下面是CollisionEvent:

    package comingx.jingle.events
    {
    import flash.events.Event;

    public class CollisionEvent extends Event
    {
    public static const COLLISION_START:String = "collision_start";
    public static const COLLISION_END:String = "collision_end";

    public var bodyAName:String = "";
    public var bodyBName:String = "";


    public function CollisionEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false)
    {
    super(type, bubbles, cancelable);
    }
    }
    }

    2. 将ContactListener添加到物理世界
    为物理世界添加碰撞侦听,并且为自定义事件创建逻辑处理。

    private function initContactListener():void
    {
    var customContactListener:CustomContactListener = new CustomContactListener();
    customContactListener.eventDispatcher.addEventListener(CollisionEvent.COLLISION_START, handleCollisionStart);
    customContactListener.eventDispatcher.addEventListener(CollisionEvent.COLLISION_END, handleCollisionEnd);
    world.SetContactListener(customContactListener);
    }
    private function handleCollisionStart(evt:CollisionEvent):void
    {
    console.addInfo(">>--<<" + evt.bodyAName + "和" + evt.bodyBName + "碰撞");
    }

    private function handleCollisionEnd(evt:CollisionEvent):void
    {
    console.addInfo("<<-->>" + evt.bodyAName + "和" + evt.bodyBName + "分离");
    }

    其中console为一个输出窗口用来显示trace信息的

    package comingx.jingle.common
    {
    import flash.display.Sprite;
    import flash.text.TextField;
    import flash.text.TextFormat;

    public class Console extends Sprite
    {
    private var _tf:TextField;
    private var _Number;
    private var _height:Number;
    private var _info:String;

    private static const PADDING:Number = 5;

    public function Console(Number = 200, height:Number = 200)
    {
    super();
    _width = width;
    _height = height;
    _info = "";
    _tf = new TextField();
    init();
    }

    private function init():void
    {
    initBG();
    initTextField();
    }

    private function initBG():void
    {
    this.graphics.beginFill(0x000000);
    this.graphics.drawRect(0,0,_width,_height);
    this.graphics.endFill();
    }

    private function initTextField():void
    {
    _tf.width = _width - PADDING * 2;
    _tf.height = _height - PADDING * 2;
    _tf.x = PADDING;
    _tf.y = PADDING;
    _tf.wordWrap = true;
    _tf.multiline = true;

    _tf.text = _info;
    _tf.textColor = 0xffffff;
    addChild(_tf);
    }

    public function set Info(infoString:String):void
    {
    if(_info != infoString)
    {
    _info = infoString;
    }
    }

    public function get Info():String
    {
    return _info;
    }

    public function addInfo(infoString:String):void
    {
    _info = _info + "\n" + infoString;
    _tf.text = _info;
    _tf.scrollV = _tf.maxScrollV;
    }
    }
    }

    最后,如果你要想愤怒的小鸟那样,碰撞之后将某个物体消失掉,那么你最好不要在handleContactStart中处理逻辑,应该在此方法中添加一个flag,然后在handleEnterFrame中,根据flag,来处理逻辑,因为handleContactStart方法执行的时候,物理世界依然进行着计算,此时对刚体操作是会出问题的。

    源码下载







  • 相关阅读:
    mormot json操作
    DELPHI优秀的一些开源框架:QDAC,MORMOT,DIOCP
    数据库中间件支持数据库集群方案
    idhttp的用法
    firedac odbc sql server driver连接占线导致另一个hstmt
    论DATASNAP远程方法支持自定义对象作参数
    咏南中间件
    JS--截取字符串常用方法详细
    使用JavaScript进行进制转换将字符串转换为十进制
    Mac--安装kubernetes并运行echoserver
  • 原文地址:https://www.cnblogs.com/jinglehit/p/2315025.html
Copyright © 2020-2023  润新知