一、基础概念
1.刚体rigidbody :刚体是指在运动中和受力作用后,形状和大小不变,而且内部各点的相对位置不变的物体。可参考 laya2.0 box2d系列一 基础概念和刚体
2.碰撞体collider:碰撞体是给物体加一个判定框,当碰撞框重叠的时候,两物体发生碰撞。碰撞体是检测物理碰撞的框架,他永远跟随物体的刚体移动,不会产生偏差。碰撞体有四种:矩形碰撞体,圆形碰撞体,线形碰撞体,多边形碰撞体。每个碰撞体都是继承自碰撞体基类。
二、矩形碰撞体例子
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&&!this.m_flag_fixedRotationFlag&&(this.m_I=a.I-this.m_mass*box2d.b2Dot_V2_V2(a.center,a.center)
这里如果a.I小于0,就会出问题。可以打开e_centerOfMassBit配置来观察重心的位置。
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;