MatterJs 中的刚体
文章基于
MatterJs ^0.17.1
我们在「上一章」介绍了matterJs
的物理环境.
具备了环境, 我们就需要在环境中放置物体来丰富这个世界.
今天, 我们一起来看下物体是如何在物理环境中存在和运动的.
一、matterJs中的物体介绍
matterJs
中的物体分为两类:
- Body — 刚体
- Composite — 复合体
只支持这两种形式的物体存在于matterJs
的物理环境.
刚体是单一的物体body
, 而复合体是由刚体body
、约束constraint
或者复合体composite
组合而成.
刚体提供了最基础的一些具有固定形状的物理物体.
复合体使我们可以随意组合成我们需要的不规则物体, 为物理世界多样性提供了实现的可能.
在
matterJs
中,World
也是一个复合体, 具有复合体的一切方法.
World.create = Composite.create;
World.add = Composite.add;
World.remove = Composite.remove;
World.clear = Composite.clear;
World.addComposite = Composite.addComposite;
World.addBody = Composite.addBody;
World.addConstraint = Composite.addConstraint;
复制代码
刚体工厂Bodies
提供了几种最基本的形状:
- rectangle 矩形
- trapezoid 梯形
- circle 圆形
- polygon 多边形
- fromVertices 可扩展图形
复合体工厂Composites
提供了几种复合体:
- stack 堆叠
- chain 链
- mesh 网格
- pyramid 锥体
- newtonsCradle 牛顿摆球
- car 小车
- softBody 软体
二、刚体的独立个体-Body
在「第一章」, 我们写了一个自由落体的小球, 其中的那个小球就是一个圆形的物理刚体.
首先, 我们先来看下一个刚体具备那些属性:
属性名 | 默认值 | 介绍 |
---|---|---|
id | 0 | 每个刚体唯一id, 在创建过程中自增 |
type | ‘body’ | 类型 |
label | ‘Body’ | 标识, 可以自己取名 |
parts | [] | 刚体的组成部分, 一般数组元素都是刚体本身 |
plugin | {} | 插件 |
angle | 0 | 角度, 这里是弧度制 |
vertices | Vertices.fromPath(‘L 0 0 L 40 0 L 40 40 L 0 40’) | 计算顶点, 规定了一个刚体形状 |
position | { x: 0, y: 0 } | 位置, 以重心为计算点 |
force | { x: 0, y: 0 } | x, y向量上的受力分量 |
torque | 0 | 力矩, 转距 M = F * L |
speed | 0 | 速度 |
angularSpeed | 0 | 角速度 |
velocity | { x: 0, y: 0 } | 物理速度, x, y分量上的位移 |
angularVelocity | 0 | 物理角速度 |
isStatic | false | 是否静态刚体 |
isSleeping | false | 刚体是否睡眠 |
density | 0.001 | 刚体密度 |
restitution | 0 | 力的衰减系数(弹性) |
friction | 0.1 | 摩擦力 |
frictionStatic | 0.5 | 静滑动摩擦力 |
frictionAir | 0.01 | 空气阻力 |
collisionFilter | { category: 0x0001, mask: 0xFFFFFFFF, group: 0 } | 碰撞过滤 |
bounds | null | 刚体边界, 具有最大值和最小值 |
circleRadius | 0 | 圆形半径 |
timeScale | 1 | 时间比例系数, 0 冻结, 大于1 加速 小于 1 慢动作 |
positionPrev | null | 上一次的刚体位置 |
anglePrev | null | 上一次的刚体角度 |
axes | null | 轴 |
area | 0 | 2d平面形状的面积 |
mass | 0 | 刚体质量 |
inertia | 0 | 惯性 |
events | null | 事件 |
刚体密度 = 刚体质量 / 刚体面积
body.density = body.mass / body.area
.
角度 = 弧度 * Math.PI / 180.
restitution
可以约定具有弹性的刚体弹起高度.
friction
0 — 刚体可以做摩擦力的无限滑动,1 — 对刚体施加力后其会立刻停止.
frictionStatic
0 — 刚体几乎是静止 值越大, 移动所需的力越大.
collisionFilter
碰撞规则, 后面会单独讲.
vertices
决定了一个刚体的形状, 这就为我们自定义刚体提供了思路.
force
受力的大小, 控制刚体移动方向.
接下来, 我们来看下刚体提供了那些方法, 我们可以做到什么操作:
1、 Body.nextGroup
函数: Body.nextGroup = (isNonColliding) => {}
参数:
- isNonColliding 是否可以碰撞标识
返回自增或者自减的索引, isNonColliding
为true
时, 生成自减的索引, 刚体不能碰撞, 为false
时, 生成自增的索引, 刚体可以碰撞.
举个?:
当我们设置group = Body.nextGroup(true)
时, 圆形和我们的地面就不会碰撞了, 设置为false
时可以.
var group = Body.nextGroup(true);
/** 创建圆形刚体 */
var _circle = Bodies.circle(200, 10, 20, {
isStatic: false,
collisionFilter: {
group: group
}
});
Composite.add(world, [
Bodies.rectangle(400, 400, 800, 10, {
isStatic: true,
collisionFilter: {
group: group
}
}),
_circle
]);
复制代码
那matterJs
是如何判断碰撞的呢?
if (filterA.group === filterB.group && filterA.group !== 0)
return filterA.group > 0;
复制代码
这样就判断为碰撞了.
2、Body.nextCategory
函数: Body.nextCategory = ()=>{}
参数: 无
category
值的范围是[1, 2^31].
制定碰撞分组, 可以实现指定刚体间的碰撞.
该方法只是拿来生成collisionFilter
属性指定的category
.
使用category
和mask
配合使用判断分组碰撞.
举个例子?:
当_circle
的category
和rectangle
的mask
一样的时候, 两者可以碰撞.
下面例子中的categoryA
和categoryB
两者的值不一样.
var categoryA = Body.nextCategory(),
categoryB = Body.nextCategory();
/** 创建圆形刚体 */
var _circle = Bodies.circle(200, 10, 20, {
isStatic: false,
collisionFilter: {
category: categoryA
}
});
Composite.add(world, [
Bodies.rectangle(400, 400, 800, 10, {
isStatic: true,
collisionFilter: {
category: categoryB,
mask: categoryB //categoryB
}
}),
_circle
]);
复制代码
那matterJs
是如何判断分组碰撞的呢?
return (filterA.mask & filterB.category) !== 0 &&
(filterB.mask & filterA.category) !== 0;
复制代码
3、Body.set
函数: Body.set = (body, settings, value) => {}
参数:
- body 设置的刚体
- settings 设置的属性值或者键值对
- value 在
settings
为一个值「string」的时候生效
matterJs
在设置属性的时候, 会调用一系列的方法, 去设置相应的值.
那么有那些属性可以被设置呢?
下面列举一些可以被设置的值:
switch (property) {
case 'isStatic':
Body.setStatic(body, value);
break;
case 'isSleeping':
Sleeping.set(body, value);
break;
case 'mass':
Body.setMass(body, value);
break;
case 'density':
Body.setDensity(body, value);
break;
case 'inertia':
Body.setInertia(body, value);
break;
case 'vertices':
Body.setVertices(body, value);
break;
case 'position':
Body.setPosition(body, value);
break;
case 'angle':
Body.setAngle(body, value);
break;
case 'velocity':
Body.setVelocity(body, value);
break;
case 'angularVelocity':
Body.setAngularVelocity(body, value);
break;
case 'parts':
Body.setParts(body, value);
break;
case 'centre':
Body.setCentre(body, value);
break;
default:
body[property] = value;
}
复制代码
我们可以看到, 不是刚体的默认属性, 也是可以被设置的.
4、Body.rotate
函数: Body.rotate = (body, rotation, point) => { }
参数:
- body 旋转的刚体
- rotation 旋转的角度
- point 相对于哪一个点旋转
相对于某个给定的位置旋转一定角度, 同时自转这个给定的角度.
若没有传入point
, 则只是刚体自转rotation
.
若传入point
, 则相对于point
旋转rotation
, 同时刚体也自转rotation
.
举个例子?:
矩形相对于{x: 200,y: 100}
旋转30 * Math.PI / 180
弧度.
/** 创建矩形刚体 */
var _rect = Bodies.rectangle(100, 100, 50, 50, {
isStatic: true,
});
Composite.add(world, _rect);
Body.rotate(_rect, 30 * Math.PI / 180, {
x: 200,
y: 100
});
复制代码
如果
rotation
为0, 即使传入了point
, 物体也没有移动或者旋转哦.
我们来看下matterJs
是怎么处理的呢?
if (!point) {
Body.setAngle(body, body.angle + rotation);
} else {
var cos = Math.cos(rotation),
sin = Math.sin(rotation),
dx = body.position.x - point.x,
dy = body.position.y - point.y;
Body.setPosition(body, {
x: point.x + (dx * cos - dy * sin),
y: point.y + (dx * sin + dy * cos)
});
Body.setAngle(body, body.angle + rotation);
}
复制代码
可以看出, 我们是先设置了position
, 再设置了angle
.
5、 Body.scale
函数: Body.scale = (body, scaleX, scaleY, point) => { }
参数:
- body 刚体
- scaleX X方向缩放系数
- scaleY Y方向缩放系数
- point 缩放中心, 锚点
如果不给定缩放中心, 则默认是刚体的中心(重心).
举个例子?:
下面的矩形相对于{x: 200,y: 100}
点x, y轴个缩放了50%.
var _rect = Bodies.rectangle(100, 100, 50, 50, {
isStatic: true,
});
Composite.add(world, _rect);
Body.scale(_rect,0.5,0.5,{x: 200,y: 100});
复制代码
我们可以看到, 缩放的时候重新定义了刚体的顶点规则, 同时质量、惯性等属性也随之改变.
// scale vertices
Vertices.scale(part.vertices, scaleX, scaleY, point);
// update properties
part.axes = Axes.fromVertices(part.vertices);
part.area = Vertices.area(part.vertices);
Body.setMass(part, body.density * part.area);
// update inertia (requires vertices to be at origin)
Vertices.translate(part.vertices, { x: -part.position.x, y: -part.position.y });
Body.setInertia(part, Body._inertiaScale * Vertices.inertia(part.vertices, part.mass));
Vertices.translate(part.vertices, { x: part.position.x, y: part.position.y });
复制代码
6、Body.update
函数: Body.update = (body, deltaTime, timeScale, correction) => { }
参数:
- body 刚体
- deltaTime 每次更新的时间间隔 帧间隔, 16.67ms
- timeScale 时间比例系数 0 冻结, 大于1 加速 小于 1 慢动作
- correction 修正系数, 默认1
这个方法我们一般不主动调用, 它会在Engine.update
中被调用.
主要用来更新刚体的位置、角度、速度等物理信息和几何变换.
7、Body.applyForce
函数: Body.applyForce = (body, position, force) => { }
参数:
- body 刚体
- position 施力位置 vector
- force 施力的大小 vector
向刚体的position
位置施加大小为force
的力.force
分为x和y方向上的分量.
举个例子?:
我们对刚体_rect
施加一个力{ x: 0.1, y: 0.1 }
,物体会沿一条角度为45度的斜线移动.
var _rect = Bodies.rectangle(100, 100, 50, 50, {
isStatic: false,
mass: 1
});
Composite.add(world, _rect);
Body.applyForce(_rect,{x: 0,y: 0},{x: 0.1,y: 0.1});
复制代码
注意, 我们施加力的刚体或者复合体, 它的
isStatic
不能为true
, 不能是静态刚体.
静态刚体不受力.
刚体受力的同时会产生力矩.
applyForce
方法是如何生效的呢?
body.force.x += force.x;
body.force.y += force.y;
var offset = { x: position.x - body.position.x, y: position.y - body.position.y };
body.torque += offset.x * force.y - offset.y * force.x;
复制代码
至此, 刚体的主要属性和方法我们就讲完了.
单个刚体在matterJs
中因为具备了一系列的属性和方法, 我们可以从容的操作他们做出一些有意思的场景.
那我们如何获得一些特殊的图形呢?
matterjs
给我们提供了一个Bodies
的工厂方法, 用于生成各种形状.
三、刚体的生产车间-Bodies
前面我们讲了一个单独的刚体需要具备的属性和方法.
有了核心技术, 我们就要开始生产正式的产品了, Bodies
为我们提供了几种常用的模具(形状).
所有的形状都是通过Body.create()
生成的.
1、矩形
函数: Bodies.rectangle = (x, y, width, height, options) => { }
参数:
- x — x位置
- y — y位置
- width 宽度
- height 高度
- options 配置
矩形的具形方法:
Vertices.fromPath('L 0 0 L ' + width + ' 0 L ' + width + ' ' + height + ' L 0 ' + height)
复制代码
举个例子?:
/** 创建一个矩形 **/
var _rect = Bodies.rectangle(100, 100, 50, 50, {
isStatic: false,
});
Composite.add(world, _rect);
复制代码
2、梯形
函数: Bodies.trapezoid = (x, y, width, height, slope, options) => { }
参数:
- …
- slope 斜率
梯形的具形方法:
if (slope < 0.5) {
verticesPath = 'L 0 0 L ' + x1 + ' ' + (-height) + ' L ' + x2 + ' ' + (-height) + ' L ' + x3 + ' 0';
} else {
verticesPath = 'L 0 0 L ' + x2 + ' ' + (-height) + ' L ' + x3 + ' 0';
}
var trapezoid = {
label: 'Trapezoid Body',
position: { x: x, y: y },
vertices: Vertices.fromPath(verticesPath)
};
复制代码
举个例子?:
/** 创建一个梯形 **/
var _trapezoid = Bodies.trapezoid(100, 100, 50, 50,0.2, {
isStatic: true,
});
Composite.add(world, _trapezoid);
复制代码
3、圆形
函数: Bodies.circle = (x, y, radius, options, maxSides) => { }
参数:
- …
- radius 半径
- maxSides 最大边
圆形是如何具形的呢?
它其实是使用的多边形的具形方法.
Bodies.polygon(x, y, sides, radius, Common.extend({}, circle, options))
复制代码
matterJs
中的圆形其实是多边形, 最小10条边, 默认25边.
举个例子?:
/** 创建一个圆形 **/
var _cricle = Bodies.circle(50, 50, 50, {
isStatic: true,
},80);
Composite.add(world, _cricle);
复制代码
有意思的是: matterJs
有打算做一个真正的圆.
// TODO: true circle bodies
复制代码
4、多边形
函数: Bodies.polygon = (x, y, sides, radius, options) => { }
参数:
- …
- sides 边数
- radius 半径
多边形是如何具现的呢?
for (var i = 0; i < sides; i += 1) {
var angle = offset + (i * theta),
xx = Math.cos(angle) * radius,
yy = Math.sin(angle) * radius;
path += 'L ' + xx.toFixed(3) + ' ' + yy.toFixed(3) + ' ';
}
var polygon = {
label: 'Polygon Body',
position: { x: x, y: y },
vertices: Vertices.fromPath(path)
};
复制代码
一系列的路径绘制, 如果边数sides
小于3, 绘制的就是圆形.
举个例子?:
绘制了一个三角形.
/** 创建一个多边形**/
var _polygon = Bodies.polygon(50, 50, 3, 20, {
isStatic: true,
});
Composite.add(world, _polygon);
复制代码
5、自定义形状
函数: Bodies.fromVertices = (x, y, vertexSets, options, flagInternal, removeCollinear, minimumArea, removeDuplicatePoints) => {}
参数:
- …
- vertexSets 一个或者多个顶点数组
- flagInternal 是否具有内部标识
- removeCollinear 0.01 简化同一边的时候的阈值
- minimumArea 10 移除小部分时的阈值
- removeDuplicatePoints 0.01 简化附近的点时的阈值
flagInternal 为
true
, 无内部标识,render
时可以看出具有线形
具形方法有兴趣的可以自行阅读源码.
举个例子?:
我们来画一个五角星.
/** 自定义形状 **/
Common.setDecomp(decomp);
var star = Vertices.fromPath('50 0 63 38 100 38 69 59 82 100 50 75 18 100 31 59 0 38 37 38');
var _custom = Bodies.fromVertices(200, 200, star,{
isStatic: true
},true,0.1,10);
Composite.add(world, _custom);
复制代码
五角星一共需要10个点.
decomp
是一个插件, 用于绘制自定义形状.我这里直接用script
引用.
使用一系列点位来形成一个封闭的形状.
四、总结
本章节介绍了matterJs
中的刚体, 包括它的属性和方法.
需要注意的是每个刚体都有其独一无二的id. 同时拥有一些相似和不相似的属性.
我们可以使用Bodies
工厂方法绘制一些具有形状的刚体:
- 矩形
- 梯形
- 多边形
- 圆形
- 一些自定义形状
我们还可以对刚体进行一些操作:
- 设置基础属性
- 设置碰撞规则
- 施加力来移动刚体
同时我们需要注意:
- 静态刚体是不受任何物理作用的
- 可以通过设置刚体更新来达到某些效果, 比如冻结、匀速, 还可以防止卡顿.
下一章我们将继续介绍matterJs
中的物体, 如何去创建一个复杂的复合体. 以及如何去使用复合体搭建复杂的物理世界.