如何使用 MatterJS 计算跳跃到目标的速度?

问题描述 投票:0回答:1

我正在尝试在 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 的引擎?

javascript reactjs math game-physics matter.js
1个回答
0
投票

终于破解了!主要的困难是看到这样一个事实 如果您

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

© www.soinside.com 2019 - 2024. All rights reserved.