将一个对象逐渐旋转以面向一个点?

5
我正在制作一款JavaScript游戏,希望敌舰能够朝向指定点旋转。然后可以根据它们的角度计算它们的移动。下面的旋转代码仅在船位于其目标下方和右侧时有效。如果船在左侧,则旋转到约0度并抖动。在右上方,它不断逆时针旋转。我错在哪里了?有更好的方法吗?
obj.angle %= 360;
// Calculate angle to target point
var targetAngle = Math.atan2(obj.mode.dest.y - obj.y, obj.mode.dest.x - obj.x) * (180 / Math.PI) + 90;
// Get the difference between the current angle and the target angle
var netAngle = Math.abs(obj.angle - targetAngle) % 360;
// Turn in the closest direction to the target
netAngle > 180 ? obj.angle += obj.shipType.turnSpeed : obj.angle -= obj.shipType.turnSpeed;
if(obj.angle < 0) obj.angle += 360;
if(obj.angle > 360) obj.angle -= 360;

我的问题与这个问题非常相似,但不幸的是它是用C#编写的。 编辑:这是可行的代码,希望对需要的人有所帮助:
obj.angle %= 360;
var targetAngle = Math.atan2(obj.mode.dest.y - obj.y, obj.mode.dest.x - obj.x) * (180 / Math.PI) + 90;
targetAngle = (targetAngle + 360) % 360;
if(obj.angle != targetAngle)
{
    var netAngle = (obj.angle - targetAngle + 360) % 360;
    var delta = Math.min(Math.abs(netAngle - 360), netAngle, obj.shipType.turnSpeed);
    var sign  = (netAngle - 180) >= 0 ? 1 : -1;
    obj.angle += sign * delta + 360;
    obj.angle %= 360;
}

解决方案应该发布为自我回答,而不是编辑问题,这会让人感到困惑并绕过投票。谢谢。 - ggorlen
2个回答

3

这确实是朝着正确方向迈出的一步。但是,它仍然有一些小毛病。请查看当船位于目标的右下方时,它是如何翻转然后返回的:http://jsfiddle.net/57hAk/4/。我还注意到它会一次性跳转到目标角度高达45度,但我还没有能够复现这个问题。 - Austen
好的,这是它在左上角跳跃从3到135:http://jsfiddle.net/57hAk/5/ - Austen
稍微整理了一下并修复了过度旋转的问题。 - tobyodavies
相关的代码应该在答案本身中,而不是在外部链接中。谢谢。 - ggorlen

1

这可以在不转换为度数的情况下完成。

步骤如下:

  1. 使用 Math.atan2 计算源向量和目标向量之间的角度 theta
  2. theta 规范化到 0..Tau 的范围内。
  3. 计算源角度和 theta 之间的差值,在减法过程中注意在 -PI 以下进行包装。

这会产生一个介于 -Pi..+Pi 之间的 diff 值。如果它是负数,则顺时针旋转,否则逆时针旋转。

该公式仅适用于玩家角度限制在 -Pi..+Pi 范围内的情况。

以下是一个可行的示例。

const {PI} = Math;
const TAU = 2 * PI;
const canvas = document.querySelector("canvas");
canvas.width = canvas.height = 240;
const ctx = canvas.getContext("2d");

const player = {
  x: canvas.width / 2,
  y: canvas.height / 2,
  angle: 0,
  radius: 20,
};

const mouse = {x: canvas.width / 2, y: canvas.height / 2};
canvas.addEventListener("mousemove", e => {
  mouse.x = e.offsetX;
  mouse.y = e.offsetY;
});

(function rerender() {
  requestAnimationFrame(rerender);

  let theta = Math.atan2(mouse.y - player.y, mouse.x - player.x);
  theta = theta < 0 ? theta + TAU : theta;
  let diff = player.angle - theta;
  diff = diff < -PI ? diff + TAU : diff;

  if (Math.abs(diff) > 0.02) {
    player.angle -= 0.04 * Math.sign(diff);

    if (player.angle > PI) {
      player.angle -= TAU;
    }
    else if (player.angle < -PI) {
      player.angle += TAU;
    }
  }

  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // draw player
  ctx.save();
  ctx.strokeStyle = "black";
  ctx.lineWidth = 8;
  ctx.translate(player.x, player.y);
  ctx.rotate(player.angle);
  ctx.beginPath();
  ctx.arc(0, 0, player.radius, 0, TAU);
  ctx.stroke();
  ctx.beginPath();
  ctx.moveTo(0, 0);
  ctx.lineTo(player.radius * 2, 0);
  ctx.stroke();
  ctx.restore();

  // draw crosshair
  ctx.strokeStyle = "red";
  ctx.lineWidth = 4;
  ctx.beginPath();
  ctx.moveTo(mouse.x - 10, mouse.y);
  ctx.lineTo(mouse.x + 10, mouse.y);
  ctx.stroke();
  ctx.beginPath();
  ctx.moveTo(mouse.x, mouse.y - 10);
  ctx.lineTo(mouse.x, mouse.y + 10);
  ctx.stroke();
})();
canvas {
  border: 2px solid black;
}
<canvas></canvas>

另一个选择:
const theta = Math.atan2(mouse.y - player.y, mouse.x - player.x);
let diff = player.angle - theta;
diff = diff > PI ? diff - TAU : diff;
diff = diff < -PI ? diff + TAU : diff;

这可能可以进一步简化;欢迎留言评论。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接