matterjs 实现弹珠台
webpack 构建
1 |
|
webpack.config.js
1 |
|
安装一些会用到的依赖:
package.json
1 |
|
初始化并添加墙壁等物块
位置啥的都是自己根据别人的效果图,初略调的
1 |
|
添加三角形物块
1 |
|
PATHS 里的路径可以在SvgPathEditor中使用 SVG 语法来绘制。
需要注意的是:最高的那个点要作为出发点(不知道具体原因)
去掉isStatic: true
后:
M 0 -140 L 0 0 L -180 0 L 0 -140
:最高点为出发点。
M 0 0 L -180 0 L 0 -140 L 0 0
:最高点不是出发点(和上面的形状是一样的
可以看到,这时候墙壁基本就是虚设。(具体原因不清楚,但是有可能是因为 svg 路径的凸凹啥的,之前听过一丢丢
添加半圆物块
半圆物块没有采用上面的办法,因为按上面的办法的话,半圆还得得到很多点才行。(SVG 语法的 A 命令之类的不生效,基本只能用 L 语法去得到点,即需要很多点来弄出一个半圆)
采用的方案是通过SvgPathEditor绘制半圆,并得到对应的 SVG,然后再通过Matter.Svg.pathToVertices
来将 SVG 路径转成物块。
绘制
加载 SVG,并将 path 元素通过
Matter.Svg.pathToVeritices(path, 36)
将 path 转成顶点 Vertices。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29const loadSvg = function (url) {
return fetch(url)
.then(function (response) {
return response.text();
})
.then(function (raw) {
// 把加载的svg转成document对象
return new window.DOMParser().parseFromString(raw, "image/svg+xml");
});
};
function svg(x, y, svgName) {
loadSvg(`/svg/${svgName}.svg`).then((root) => {
const vertices = [...root.querySelectorAll("path")].map((path) => {
return Matter.Svg.pathToVertices(path, 36);
});
Matter.Composite.add(
engine.world,
Matter.Bodies.fromVertices(x, y, vertices, {
isStatic: true,
render: {
fillStyle: "#495057",
lineWidth: 1,
},
})
);
});
}通过
Matter.Bodies.fromVertices
根据顶点生成物块,并添加到物理世界中。调用方法
svg
1
2
3
4function addBoundries() {
svg(236, 80, "dome");
// ...
}安装
pathseg
和poly-decomp
。pathseg
:因为Matter.Svg.pathToVertices
不支持复杂路径,所以需要pathseg
来 polyfill。poly-decomp
:将 2D 多边形分解为凸块(matter-js 不支持凹顶点)\color{red}{凹凸顶点有机会再了解}1
2
3
4
5
6import "pathseg";
function init() {
Matter.Common.setDecomp(require("poly-decomp"));
// ...
}
添加操作浆
原本看了别人的博客实现方式,觉得一个浆,加两个停止器就行了。但是实现起来才发现效果不行,浆会有很高频的抖动效果。最后有参考(抄)别人的思路。
上面的蓝色物块就是觉得没必要,后面才发现很有必要的。添加好后,设置visible
为false
即可。
添加键盘事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27let isLeftPaddleUp = false;
let isRightPaddleUp = false;
function addEvents() {
window.addEventListener("keypress", (e) => {
if (e.key.toLowerCase() === "a") {
isLeftPaddleUp = true;
} else {
isLeftPaddleUp = false;
}
if (e.key.toLowerCase() === "d") {
isRightPaddleUp = true;
} else {
isRightPaddleUp = false;
}
});
window.addEventListener("keyup", (e) => {
if (e.key.toLowerCase() === "a") {
isLeftPaddleUp = false;
}
if (e.key.toLowerCase() === "d") {
isRightPaddleUp = false;
}
});
}function load() {
init();
addBoundries();
addEvents();
}
1 |
|
matter-attractors
可以对物块添加持续的力。通过键盘事件来控制力是引力还是斥力。直接添加会导致对所有物块都有影响,所以通过label
来判断是否添加。
创建浆
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26function addPaddles() {
const paddleLeft = {};
paddleLeft.paddle = Matter.Bodies.trapezoid(134, 512, 20, 88, 0.33, {
label: "paddleLeft",
angle: 1.57,
chamfer: {},
render: {
fillStyle: "skyblue",
},
});
paddleLeft.brick = Matter.Bodies.rectangle(134, 524, 40, 40, {
render: {
fillStyle: "blue",
// visible: false
},
});
// 将两个物块组装在一起
paddleLeft.comp = Matter.Body.create({
label: "paddleLeftComp",
parts: [paddleLeft.paddle, paddleLeft.brick],
});
Matter.Composite.add(engine.world, [paddleLeft.comp]);
}添加约束,固定浆
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22function addPaddles() {
// ...
// bodyB表示给paddleLeft.comp添加约束,pointB则是相对paddleLeft.comp的坐标
// pointA是这个物理世界的坐标
const constraintLeft = Matter.Constraint.create({
bodyB: paddleLeft.comp,
pointB: {
x: -32,
y: -8,
},
pointA: {
x: paddleLeft.comp.position.x,
y: paddleLeft.comp.position.y,
},
stiffness: 0.9,
length: 0,
render: {
strokeStyle: "pink",
},
});
Matter.Composite.add(engine.world, constraintLeft);
}右边的浆同理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42const paddleRight = {};
paddleRight.paddle = Matter.Bodies.trapezoid(304, 512, 20, 88, 0.33, {
// isStatic: true,
label: "paddleRight",
angle: -1.57,
chamfer: {},
render: {
fillStyle: "skyblue",
},
});
paddleRight.brick = Matter.Bodies.rectangle(304, 524, 40, 40, {
render: {
fillStyle: "blue",
// visible: false
},
});
paddleRight.comp = Matter.Body.create({
label: "paddleRightComp",
parts: [paddleRight.paddle, paddleRight.brick],
});
const constraintRight = Matter.Constraint.create({
bodyB: paddleRight.comp,
pointB: {
x: 32,
y: -8,
},
pointA: {
x: paddleRight.comp.position.x,
y: paddleRight.comp.position.y,
},
stiffness: 0.9,
length: 0,
render: {
strokeStyle: "pink",
},
});
Matter.Composite.add(engine.world, constraintRight);
Matter.Composite.add(engine.world, [paddleLeft.comp, paddleRight.comp]);
添加弹珠,并添加碰撞事件,实现reset
效果
1 |
|
给中间的 5 个圆加弹力
现在的弹珠基本会掉到中间,浆根本没有用武之地。
1 |
|
设置group
。让 stopper、marble 它们变成“一伙”
现在弹珠碰到
stopper
物块也会有碰撞效果。所以设置group
,成群之后就不会再有碰撞效果了。
1 |
|
\color{red}{弹珠的大小从 14 变成了 10}。后面发现会被
brick
卡住,并且没法单独给brick
设置group
。
隐藏辅助物块
1 |
|
效果以及完整代码
1 |
|
代码放仓库了,有兴趣的话,可以查看。
参考链接
JavaScript Physics with Matter.js / Coder’s Block
Matter.js - a 2D rigid body JavaScript physics engine · code by @liabru
GitHub - liabru/matter-attractors: an attractors plugin for matter.js