我正在尝试在 Matter-js 中创建一个球,它将从矩形反弹并跳转到下一个矩形。我所知道的只是球的位置和下一个矩形的位置。我在统一论坛上发现了一个类似的问题。我尝试在我的项目中实现 Iron-Warrior 解决方案中的代码,但我遇到的问题是我的球不断地强烈地跳过下一个矩形。
这是我得到的代码:
const ball = Bodies.circle(0, 0, 10);
const rectangles = Array(20)
.fill(0)
.map((_, i) => Bodies.rectangle(i * 140, 200, 20, 20, { isStatic: true }));
Events.on(engine, 'collisionStart', (event) => {
event.pairs.forEach((pair) => {
if (pair.bodyA === ball) {
const target = rectangles[rectangles.indexOf(pair.bodyB) + 1];
const gravity = engine.gravity.y;
const initialAngle = 60;
const yOffset = ball.position.y - target.position.y;
const angle = initialAngle * (Math.PI / 180);
const distance = Vector.magnitude(Vector.sub(ball.position, target.position));
const initialVelocity =
(1 / Math.cos(angle)) *
Math.sqrt(
(0.5 * gravity * Math.pow(distance, 2)) / (distance * Math.tan(angle) + yOffset),
);
const velocity = {
x: initialVelocity * Math.cos(angle),
y: -initialVelocity * Math.sin(angle),
};
const angleBetweenObjects = Vector.angle(ball.position, target.position);
const finalVelocity = Vector.rotate(velocity, angleBetweenObjects);
Body.setVelocity(ball, finalVelocity);
}
});
});
Composite.add(engine.world, [ball, ...rectangles]);
尝试增加
engine.velocityIterations
这是codesandbox示例: https://codesandbox.io/p/sandbox/relaxed-shape-9wy3kt
我相信这主要是 Matter-js 问题。我尝试更改公式、更改球选项、矩形选项,但没有任何帮助。也许有一些可以替代 Matterjs 的引擎?
终于破解了!主要的困难是看到这样一个事实 如果您
setVelocity
在 collisionStart
中,效果
碰撞仍然施加到球上,因此它的设定速度
被改变,因此其轨迹不可预测。
因此,主要的变化是将动作移至 collisionActive
处理程序。
还有一些小的补充和更正 到你的代码:
distance
其 x 分量。我用了 dx
和 dy
(而不是
yOffset
) 为距离的两个分量。matter.js
中的重力加速度值为
实际上 gravity.y
* gravity.scale
+rectangle.height/2
) 和球的中心
应该是 +ball.radius
以上。ball.airFriction = 0
;
理论上考虑空气摩擦力
可行,这是一个更困难的提议。Body.setAngularVelocity(ball, 0)
——虽然旋转
球的大小并不影响它的能量,有一个有趣的现象
球虽然接触到旋转的效果
矩形在正确的位置没有碰撞并且
球从表面滑落。这里是一个代码片段,其中矩形的一些随机定位以及当球到达最后一个/第一个矩形时简单的方向反转:
const Engine = Matter.Engine,
Render = Matter.Render,
Runner = Matter.Runner,
Bodies = Matter.Bodies,
Composite = Matter.Composite,
Vector = Matter.Vector,
Body = Matter.Body,
World = Matter.World,
Events = Matter.Events;
// create an engine
const engine = Engine.create();
engine.positionIterations = 200;
engine.velocityIterations = 200;
engine.enableSleeping = true;
const width = 800, height = 400;
// create a renderer
const render = Render.create({
element: document.body,
engine: engine,
options: {
width,
height,
showAngleIndicator: true,
showVelocity: true,
},
});
const ball = Bodies.circle(0, 0, 10);
ball.frictionAir = 0;
let reverse = false;
const intRnd = (min, max) => Math.floor(min + Math.random() * (max - min));
const rectangles = Array(60)
.fill(0)
.map((_, i) =>
Bodies.rectangle(i * 150 + (i === 0 ? 0 : intRnd(-30, 30)), 200 + intRnd(-50, 50), 30, 30, {isStatic: true}),
);
Events.on(engine, "collisionActive", (event) => {
event.pairs.forEach((pair) => {
if (pair.bodyA === ball) {
let target = rectangles[rectangles.indexOf(pair.bodyB) + (reverse ? -1 : 1)];
if(!target){
reverse = !reverse;
target = rectangles[rectangles.indexOf(pair.bodyB) + (reverse ? -1 : 1)];
}
const gravity = engine.gravity.y * engine.gravity.scale * Body._baseDelta * Body._baseDelta;
const initialAngle = reverse ? -60 : 60;
const angle = deg2rad(initialAngle);
const dx = target.position.x - ball.position.x,
dy = target.bounds.min.y - ball.position.y - ball.circleRadius;
const initialVelocity = dx / Math.cos(angle) * Math.sqrt((0.5 * gravity) / (dy + dx * Math.tan(angle)));
if (initialVelocity) {
const velocity = Vector.create(
initialVelocity * Math.cos(angle),
-initialVelocity * Math.sin(angle),
);
Body.setVelocity(ball, velocity);
}
else{
Runner.stop(runner)
}
Body.setAngularVelocity(ball, 0);
}
});
});
Composite.add(engine.world, [ball, ...rectangles]);
function deg2rad(degrees) {
return degrees * (Math.PI / 180);
}
// create runner
const runner = Runner.create();
const updateCameraPosition = () => {
// Calculate camera position based on ball's position - just x
if (ball.position.y > height * 0.9) {
stop();
}
const xOffset = ball.position.x - width / 2;
Render.lookAt(render, {
min: {x: xOffset, y: 0},
max: {
x: xOffset + width,
y: height,
},
});
};
Events.on(engine, "afterUpdate", () => {
updateCameraPosition();
});
const stop = function(){
Render.stop(render);
World.clear(engine.world, false);
Engine.clear(engine);
document.querySelector('#stop').style.visibility = 'hidden';
}
// run the renderer
Render.run(render);
// run the engine
Runner.run(runner, engine);
// setTimeout(() => {
// stop()
// }, 300000) // stop after 5min
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.js" integrity="sha512-rsntMCBgWYEWKl4HtqWmQ3vdHgvSq8CTtJd19YL7lCtKokLPWt7UEoHVabK1uiNfUdaLit8O090no5BZjcz+bw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<button id='stop' onclick="stop()">Stop</button>
或jsFiddle。