如何用动画将圆形体移动到另一个位置?当我使用
translate()
时,该对象会消失并立即重新出现在新位置。
我正在开发简单的游戏。玩家必须躲避来自顶部的障碍物。他们只能向左或向右移动三个字段。我想为这些字段上的运动制作动画。目前,我有这样的事情:
public move = (entities, {touches, time}) => {
touches.filter(t => t.type === 'press').forEach(t => {
const player = entities['player']
const direction = this.getPlayerMoveDirection(t.event.pageX)
const nextFieldId = this.getNextFieldIdByDirection(direction)
if (nextFieldId !== this.fieldId) {
this.setFieldId(nextFieldId)
const nextField = this.game.fields[nextFieldId]
const nextFieldXPosition = nextField.getCenter()
const newXPosition = direction === 'left' ? (player.body.position.x - nextFieldXPosition) *-1 : nextFieldXPosition - player.body.position.x
Matter.Body.translate( player.body, {x: newXPosition, y: 0});
this.playMoveSound()
}
})
return entities
}
这效果很好,但精灵没有动画。它只是出现在新位置。我想制作它的运动动画。
我在react-native-game-engine中使用Matter.js。
这是一个很好的问题——如何在 MJS 中实现这一点并不完全明显,因为没有
goto(body, x, y)
函数。
至少有几种不同的方法可用,每种方法都涉及许多潜在的繁琐细节和可用的调整。哪个最好似乎在很大程度上取决于您的用例。
Matter.Body.applyForce(body, position, force)
推动身体朝目的地前进。请参阅Matter.js 计算所需的力,了解设置这些调用的典型方法。给定一个目的地,您可以根据到目的地的距离来缩放力并应用一次。这会产生很大的力,随着身体接近目的地,该力会减弱。
另一种方法是逐帧逐渐用力。这就是下面的示例的工作原理。问题是,当物体接近目的地时,要减慢和停止它是有点棘手的。如果不小心,很容易超出目标,从而产生可能不理想的弹簧或橡皮筋效果。我检查了距离以确定当目标接近目的地时何时减慢力量。这里还有改进的空间。
无论哪种方式,如果你想旋转身体以指向它前进的方向,都需要做更多的工作。我根据我在旋转物体逐渐面对一个点?中的答案使用了手动旋转方法来找出转向的方向,然后
Matter.Body.setAngularVelocity(body, velocity)
告知MJS转向速度。与这里的其他所有内容一样,这些数字非常敏感,需要进行调整才能正确,因此请将它们视为概念证明。
您可以使用
applyForce
和 Body.setPosition
调用来手动重新定位移动主体,而不是 Body.setVelocity
。这对于常见情况可能不合适,因为它会导致身体闯入其他身体并忽略其大部分相关的物理属性,但对于某些用例,它可能是合适的。
与问题相关,如果需要,您可以跳过在 y 轴上施加力来限制水平轴的移动(具体来说,在下面的示例中在调用
y: 0
时设置 applyForce
)。
这是自上而下游戏的概念证明,该游戏具有转动和逐帧力,可将身体推向目的地点。
const {PI} = Math;
const TAU = PI * 2;
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");
canvas.height = 180;
canvas.width = 400;
const engine = Matter.Engine.create();
engine.gravity.y = 0; // enable top-down
const ship = {
body: Matter.Bodies.rectangle(
canvas.width / 2,
canvas.height / 2,
20,
20,
{
frictionAir: 0.03,
density: 0.3,
friction: 0.8,
}
),
size: 20,
destX: canvas.width / 2,
destY: canvas.height / 2,
color: "#eaf",
setDestination(x, y) {
this.destX = x;
this.destY = y;
},
draw(ctx) {
ctx.save();
ctx.translate(this.body.position.x, this.body.position.y);
ctx.rotate(this.body.angle);
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(this.size / 1.2, 0);
ctx.stroke();
ctx.fillStyle = this.color;
ctx.fillRect(
this.size / -2,
this.size / -2,
this.size,
this.size
);
ctx.strokeRect(
this.size / -2,
this.size / -2,
this.size,
this.size
);
ctx.restore();
},
};
Matter.Composite.add(engine.world, [ship.body]);
canvas.addEventListener("click", (e) => {
ship.setDestination(e.offsetX, e.offsetY);
});
(function update() {
requestAnimationFrame(update);
ctx.clearRect(0, 0, canvas.width, canvas.height);
const {x, y} = ship.body.position;
const dist = Math.sqrt(
(ship.destX - x) ** 2 + (ship.destY - y) ** 2
);
if (dist > 10) {
const dx = ship.destX - ship.body.position.x;
const dy = ship.destY - ship.body.position.y;
let theta = Math.atan2(dy, dx);
const a =
ship.body.angle > 0
? ((ship.body.angle + TAU) % TAU) - TAU
: -(((-ship.body.angle + TAU) % TAU) - TAU);
let diff = a - theta;
diff = diff > PI ? diff - TAU : diff;
diff = diff < -PI ? diff + TAU : diff;
const f = dist < 70 ? Math.min(0.02, dist / 10000) : 0.03;
Matter.Body.applyForce(
ship.body,
{x, y},
{
x: Math.cos(theta) * f,
y: Math.sin(theta) * f,
}
);
if (dist > 15) {
Matter.Body.setAngularVelocity(ship.body, diff / -8);
} else {
Matter.Body.setAngularVelocity(ship.body, 0);
}
}
ship.draw(ctx);
Matter.Engine.update(engine);
})();
body {
margin: 0;
font-family: monospace;
display: flex;
align-items: center;
}
html, body {
height: 100%;
}
canvas {
background: #eee;
margin: 1em;
border: 4px solid #222;
}
#instructions {
transform: rotate(-90deg);
background: #222;
color: #fff;
padding: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.20.0/matter.min.js"></script>
<div id="instructions">click to move</div>