laya2.0 box2d系列二 碰撞体

参考
官方文档 2D物理系统概述

一、基础概念

1.刚体rigidbody :刚体是指在运动中和受力作用后,形状和大小不变,而且内部各点的相对位置不变的物体。可参考laya2.0 box2d系列一 基础概念和刚体

2.碰撞体collider:碰撞体是给物体加一个判定框,当碰撞框重叠的时候,两物体发生碰撞。碰撞体是检测物理碰撞的框架,他永远跟随物体的刚体移动,不会产生偏差。碰撞体有四种:矩形碰撞体,圆形碰撞体,线形碰撞体,多边形碰撞体。每个碰撞体都是继承自碰撞体基类。

  • PolygonCollider->ColliderBase->Component 2D多边形碰撞体
  • CircleCollider->ColliderBase->Component 2D圆形碰撞体
  • ChainCollider->ColliderBase->Component 2D线形碰撞体
  • BoxCollider->ColliderBase->Component 2D矩形碰撞体
二、矩形碰撞体例子

1.施加力
在laya2.0 box2d系列一 基础概念和刚体中介绍了刚体的许多特性,如果想用示例来演示,比如放一个静态的Sprite地面(设置一下texture,然后添加内部刚体类型static),然后一个sprite矩形盒子(设置一下texture,然后添加内部刚体类型dynamic,受重力影响)从空中落下停在地面上,如果不添加碰撞体,盒子会直接穿过地面,坠出屏幕(如果先添加碰撞体,IDE会自动添加刚体)。添加矩形刚体,IDE会把宽高设定为自动与父容器相同,然后就能看到盒子稳稳地停在地面上了。

private onKeyDown(e: Laya.Event): void {
    var rig: Laya.RigidBody = this.dropBox.getComponent(Laya.RigidBody);
    switch (e.keyCode) {
        case Laya.Keyboard.LEFT:
            // rig.setVelocity({ x: -1, y: 0 });
            //报错:this._body.applyForceToCenter is not a function
            //rig.applyForceToCenter({ x: -1, y: 0 });
            rig.applyForce(rig.body.GetWorldCenter(),{x:-1,y:0});

这里setVelocity是可以的,但是applyForceToCenter报错、applyForce无效,原因不明。applyLinearImpulseToCenter也是失效的。使用原生方式var box2d:any = window['box2d'];rig.body.ApplyForce(new box2d.b2Vec2(-1,0),rig.body.GetWorldCenter());也是不行。

2.改变重心,做出类似不倒翁的效果。这里laya没有封装SetMassData方法,需要自己写

var box2d: any = window['box2d'];
var rigid: Laya.RigidBody = this.dropBox.getComponent(Laya.RigidBody);
var md: any = new box2d.b2MassData();
rigid.body.GetMassData(md);
//重新设置重心
md.center.Set(1, 1.25);
rigid.body.SetMassData(md);

设置重心时,如果过低时,SetMassData会报错:0这里如果a.I小于0,就会出问题。可以打开e_centerOfMassBit配置来观察重心的位置。

image.png

switch (e.keyCode) {
    case Laya.Keyboard.LEFT:
    rig.applyLinearImpulse({ x: 50, y: 50 }, { x: -1, y: 0 });

按下左箭头,就能看到不倒翁效果了。

三、laya封装

1.ColliderBase.as

/**@private 获取碰撞体信息*/
protected function getDef():* {
    if (!_def) {
        var def:* = new window.box2d.b2FixtureDef();
        def.density = density;
        def.friction = friction;
        def.isSensor = isSensor;
        def.restitution = restitution;
        def.shape = _shape;
        _def = def;
    }
    return _def;
}

/**
 * @private
 * 碰撞体参数发生变化后,刷新物理世界碰撞信息
 */
public function refresh():void {
    if (enabled && rigidBody) {
        var body:* = rigidBody.body;
        if (fixture) {
            //trace(fixture);
            if (fixture.GetBody() == rigidBody.body) {
                rigidBody.body.DestroyFixture(fixture);
            }
            fixture.Destroy();
            fixture = null;
        }
        var def:* = getDef();
        def.filter.groupIndex = rigidBody.group;
        def.filter.categoryBits = rigidBody.category;
        def.filter.maskBits = rigidBody.mask;
        fixture = body.CreateFixture(def);
        fixture.collider = this;
    }
}

override protected function _onEnable():void {
    rigidBody || Laya.systemTimer.callLater(this, _checkRigidBody);
}

private function _checkRigidBody():void {
    if (!rigidBody) {
        var comp:RigidBody = owner.getComponent(RigidBody);
        if (comp) {
            this.rigidBody = comp;
            refresh();
        }
    }
}

可以看到碰撞体和刚体之间的关联,是通过owner.getComponent来获得引用的。def.shape = _shape;则用来处理形状的。

2.BoxColider.as
这里显示是要覆盖上面提到的_shape了。使用了_shape.SetAsBox(_width / 2 / Physics.PIXEL_RATIO, _height / 2 / Physics.PIXEL_RATIO, new window.box2d.b2Vec2((_width / 2 + _x) / Physics.PIXEL_RATIO, (_height / 2 + _y) / Physics.PIXEL_RATIO));矩形只是多边形类的特殊情况,注意SetASBox方法传入的是半宽和半高,需要原始的width或height/2

override protected function getDef():* {
    if (!_shape) {
        _shape = _temp || (_temp = new window.box2d.b2PolygonShape());
        _setShape(false);
    }
    this.label = (this.label || "BoxCollider");
    return super.getDef();
}

IDE上的fitsize按钮,点一下这个按钮,碰撞体的大小就会自适应为节点宽高。

这里测试一下在HTML的CANVAS中,SetASBox和坐标的关系:

var fixDef = new b2FixtureDef;
fixDef.density = 1.0;//密度
fixDef.friction = 0.5;//摩擦
fixDef.restitution = 0.2;//弹性
fixDef.shape = new b2PolygonShape;//矩形
fixDef.shape.SetAsBox(2, 2);//宽高           
//创建一个矩形地板刚体
var bodyDef = new b2BodyDef;                 
bodyDef.type = b2Body.b2_staticBody;//静态的
bodyDef.position.x = 2;    //X轴
bodyDef.position.y = 2;    //Y轴     
//世界中添加一个刚体
world.CreateBody(bodyDef).CreateFixture(fixDef);

现在能看到一个矩形刚体,宽高应该都是4,然后位于坐标系的左上角,这时候position的x,y都是2。可以改改position的x,y就知道这个坐标对应的是矩形的中心坐标,并不是左上角。LAYA在封装时,就把这个偏移累加上去了:_shape.SetAsBox(_width / 2 / Physics.PIXEL_RATIO, _height / 2 / Physics.PIXEL_RATIO, new window.box2d.b2Vec2((_width / 2 + _x) / Physics.PIXEL_RATIO, (_height / 2 + _y) / Physics.PIXEL_RATIO));

3.CircleColider.as

override protected function getDef():* {
    if (!_shape) {
        _shape = _temp || (_temp = new window.box2d.b2CircleShape());
        _setShape(false);
    }
    this.label = (this.label || "CircleCollider");
    return super.getDef();
}
private function _setShape(re:Boolean = true):void {
    _shape.m_radius = _radius / Physics.PIXEL_RATIO;
    _shape.m_p.Set((_radius + _x) / Physics.PIXEL_RATIO, (_radius + _y) / Physics.PIXEL_RATIO);
    if (re) refresh();
}

同上面的矩形,也可以看看在HTML的CANVAS中圆心和坐标系的关系:

var fixDef2 = new b2FixtureDef;
fixDef2.density = 1.0;
fixDef2.friction = 0.5;
fixDef2.restitution = 0.2;
fixDef2.shape = new b2CircleShape(1);   

var bodyDef2 = new b2BodyDef;                
bodyDef2.type = b2Body.b2_dynamicBody;
bodyDef2.position.x = 1;    
bodyDef2.position.y = 0;    
world.CreateBody(bodyDef2).CreateFixture(fixDef2);

圆的半径是1,如果将圆心的x设置为0,就只能看到一半的圆了。只有将x设置为1,才恰好看到整个圆。这也说明,和矩形一样,postion的坐标对应的是圆心坐标。LAYA和矩形一样,也进行了处理:_shape.m_p.Set((_radius + _x) / Physics.PIXEL_RATIO, (_radius + _y) / Physics.PIXEL_RATIO);,也就是与编辑器中的_x,_y保持了一致,以左上角为0,0点。

4.ChainColider.as

/**用逗号隔开的点的集合,格式:x,y,x,y ...*/
private var _points:String = "0,0,100,0";
/**是否是闭环,注意不要有自相交的链接形状,它可能不能正常工作*/
private var _loop:Boolean = false;

override protected function getDef():* {
    if (!_shape) {
        _shape = _temp || (_temp = new window.box2d.b2ChainShape());
        _setShape(false);
    }
    this.label = (this.label || "ChainCollider");
    return super.getDef();
}

private function _setShape(re:Boolean = true):void {
    var arr:Array = _points.split(",");
    var len:int = arr.length;
    if (len % 2 == 1) throw "ChainCollider points lenth must a multiplier of 2";
    
    var ps:Array = [];
    for (var i:int = 0, n:int = len; i < n; i += 2) {
        ps.push(new window.box2d.b2Vec2((_x + parseInt(arr[i])) / Physics.PIXEL_RATIO,
         (_y + parseInt(arr[i + 1])) / Physics.PIXEL_RATIO));
    }
    _loop ? _shape.CreateLoop(ps, len / 2) : _shape.CreateChain(ps, len / 2);
    
    if (re) refresh();
}

5.PolygonCollider.as
2D多边形碰撞体,暂时不支持凹多边形,如果是凹多边形,先手动拆分为多个凸多边形。节点个数最多是b2_maxPolygonVertices,这数值默认是8,所以点的数量不建议超过8个,也不能小于3个。

/**用逗号隔开的点的集合,格式:x,y,x,y ...*/
private var _points:String = "50,0,100,100,0,100";

override protected function getDef():* {
    if (!_shape) {
        _shape = _temp || (_temp = new window.box2d.b2PolygonShape());
        _setShape(false);
    }
    this.label = (this.label || "PolygonCollider");
    return super.getDef();
}

private function _setShape(re:Boolean = true):void {
    var arr:Array = _points.split(",");
    var len:int = arr.length;
    if (len < 6) throw "PolygonCollider points must be greater than 3";
    if (len % 2 == 1) throw "PolygonCollider points lenth must a multiplier of 2";
    
    var ps:Array = [];
    for (var i:int = 0, n:int = len; i < n; i += 2) {
        ps.push(new window.box2d.b2Vec2((_x + parseInt(arr[i])) / Physics.PIXEL_RATIO,
            (_y + parseInt(arr[i + 1])) / Physics.PIXEL_RATIO));
    }
    
    _shape.Set(ps, len / 2);
    if (re) refresh();
}

注意在IDE中设置时,是顺时针方向旋转的。在编辑器中,在线上单击左键增加一个点,点可以拖拽,双击点会删除这个点。这里最后一个点会自动连接到起始点,形成一个封闭的多边形。

这里使用的Set方法是有两个限制的:

(1)凹多边形(相邻两个边之间夹角有一个大于180度)在进行碰撞模拟时会出现不可预知的错误。有一些工具类如b2Separator类可以处理,在拉小登博客 运行时创建多边形刚体有提到。

(2)在向vertices数组中添加顶点坐标时,要保证顶点的顺序是顺时针的。否则也会在碰撞中出现未知错误。

关于坐标,这里在HTML的CANVAS中再做个测试:

var fixDef3 = new b2FixtureDef;
var shape = new b2PolygonShape();
var arr = [];
arr.push(new b2Vec2(5,0));
arr.push(new b2Vec2(10,5));
arr.push(new b2Vec2(0,5));
shape.SetAsVector(arr,3);
fixDef3.shape = shape;
var bodyDef3 = new b2BodyDef;
bodyDef3.type = b2Body.b2_staticBody;//静态的
bodyDef3.position.x = 0;//X轴
bodyDef3.position.y = 10;//Y轴
world.CreateBody(bodyDef3).CreateFixture(fixDef3);

这里position.x=0,可以看到三角形在最左侧。也就是position对应的是多边形的最左侧,这与LAYA中的坐标系是一致的,所以不需要再对中心点,只加上偏移的_x,_y即可:ps.push(new window.box2d.b2Vec2((_x + parseInt(arr[i])) / Physics.PIXEL_RATIO, (_y + parseInt(arr[i + 1])) / Physics.PIXEL_RATIO));

6.SetAsOrientedBox
在拉小登博客 信手绘制线条刚体中,是可以创建未封闭的多边形:

for each(var segment:Segment in segmentsArray) {
    var shapeBoxRequest:b2PolygonShape = new b2PolygonShape();
    shapeBoxRequest.SetAsOrientedBox(segment.length /2/ 30, 2 / 30, 
    new b2Vec2(segment.centerX/30, segment.centerY/30), segment.rotation);
    fixtureRequest.shape = shapeBoxRequest;
    body.CreateFixture(fixtureRequest);
}

使用的SetAsOrientedBox方法,可以在拉小登博客 Box2D多边形刚体中看到使用方式,参考API可知,四个参数分别是宽、高、偏移向量、旋转角度。但是在laya封装的physics.js中没有找到SetAsOrientedBox方法

四、属性

1.密度density:number
注意不要为了实现刚体不受重力影响,将密度设为0。系统会强制把0变成1.0。
2.摩擦力friction
和linearDamping有些相似,friction决定了两个刚体之间接触时所产生的摩擦力大小,而linearDamping影响的是刚体自由运动时速度的衰减,类似空气阻力。friction可以实现冰面、沙滩、玻璃等表面摩擦力不同的材质。
3.弹性restitution
反弹力,可以模拟橡胶、木板、钢铁等不同材质
4.碰撞筛选group,category,mask
box2d默认所有刚体进行碰撞模拟,filter就是将刚体分门别类,比如游戏中敌我之间进行碰撞,而敌人与敌人之间不进行碰撞检测。在源码中,会有一个shouldCollide函数来返回一个Boolean来确定是否碰撞:

var f1:b2FilterData = fixtureA.GetFilterData();
var f2:b2FilterData = fixtureB.GetFilterData

if(f1.groupIndex == f2.groupIndex && f1.groupIndex != 0){
    return f1.groupIndex > 0;
}

var collide:boolean = (f1.maskBits & f2.categoryBits) != 0 
&& (f1.categoryBits & f2.maskBits) != 0;

参考源码,当groupIndex相等时,如果大于0,则会碰撞;小于0,则永远不会碰撞;剩余情况就看mask和category了:在laya中,group默认为0,category默认为1,即0x0001;mask默认为-1,转二进制取反加1,也就是0xFFFF;

观察(f1.maskBits & f2.categoryBits) != 0 && (f1.categoryBits & f2.maskBits) != 0;简称前面的条件为a,后面的条件为b.因为&&的意义,想要碰撞,就要求a和b都不为0。

设置好filter属性后,如果想清除筛选条件,需要使用新建一个b2FilterData作为属性,直接置null会报错。laya是在refresh中刷新属性的:

var def:* = getDef();
def.filter.groupIndex = rigidBody.group;
def.filter.categoryBits = rigidBody.category;
def.filter.maskBits = rigidBody.mask;
fixture = body.CreateFixture(def);

5.isSensor
设置为true时,当与其他刚体发生碰撞后,只进行碰撞检测并派发碰撞事件,不进行碰撞模拟,通常与碰撞事件搭配使用,比如割绳子糖果落到气泡里上浮效果;默认为false,就是普通刚体,正常进行碰撞模拟。

你可能感兴趣的:(laya2.0 box2d系列二 碰撞体)