zoukankan      html  css  js  c++  java
  • 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方法执行的时候,物理世界依然进行着计算,此时对刚体操作是会出问题的。

    源码下载







  • 相关阅读:
    Laravel 初始化
    ant design pro 左上角 logo 修改
    请求到服务端后是怎么处理的
    Websocket 知识点
    王道数据结构 (7) KMP 算法
    王道数据结构 (6) 简单的模式匹配算法
    王道数据结构 (4) 单链表 删除节点
    王道数据结构 (3) 单链表 插入节点
    王道数据结构 (2) 单链表 尾插法
    王道数据结构 (1) 单链表 头插法
  • 原文地址:https://www.cnblogs.com/jinglehit/p/2315025.html
Copyright © 2011-2022 走看看