如何自然流畅地将物体移动到目标位置?

How to move object to target naturally and smoothly?

有人可以修复它的脚本以使其正常工作吗?

我的期望:

  1. 运行 脚本
  2. 点击canvas设置目标(圆圈)
  3. 物体(三角形)开始旋转并向目标(圆形)移动
  4. 随时更改目标

工作原理:

  1. 有时对象旋转正确,有时不正确
  2. 看起来一个半球效果不错,另一个不行

谢谢!

// prepare 2d context
const c = window.document.body.appendChild(window.document.createElement('canvas'))
  .getContext('2d');
c.canvas.addEventListener('click', e => tgt = { x: e.offsetX, y: e.offsetY });

rate = 75 // updates delay
w = c.canvas.width;
h = c.canvas.height;
pi2 = Math.PI * 2;

// object that moves towards the target
obj = {
  x: 20,
  y: 20,
  a: 0, // angle
};

// target
tgt = undefined;

// main loop
setInterval(() => {
  c.fillStyle = 'black';
  c.fillRect(0, 0, w, h);

  // update object state
  if (tgt) {
    // draw target
    c.beginPath();
    c.arc(tgt.x, tgt.y, 2, 0, pi2);
    c.closePath();
    c.strokeStyle = 'red';
    c.stroke();
    
    // update object position
    
    // vector from obj to tgt
    dx = tgt.x - obj.x;
    dy = tgt.y - obj.y;
    
    // normalize
    l = Math.sqrt(dx*dx + dy*dy);
    dnx = (dx / l);// * 0.2;
    dny = (dy / l);// * 0.2;
    
    // update object position
    obj.x += dnx;
    obj.y += dny;
    
    // angle between +x and tgt
    a = Math.atan2(0 * dx - 1 * dy, 1 * dx + 0 * dy);
    
    // update object angle
    obj.a += -a * 0.04;
  }

  // draw object
  c.translate(obj.x, obj.y);
  c.rotate(obj.a);
  c.beginPath();
  c.moveTo(5, 0);
  c.lineTo(-5, 4);
  c.lineTo(-5, -4);
  //c.lineTo(3, 0);
  c.closePath();
  c.strokeStyle = 'red';
  c.stroke();
  c.rotate(-obj.a);
  c.translate(-obj.x, -obj.y);
}, rate);

事实证明这比我最初想象的更具挑战性,我最终只是重写了代码。

挑战:

  1. 确保飞船只旋转到目标的精确点。这需要我比较从船当前位置到我们想要它去的地方的两个角度。
  2. 确保目标没有旋转超过目标并且飞船没有平移超过目标。这需要一些缓冲区 space 因为当动画有 this.x === this.x 当一个对象移动时很少发生所以我们需要一些空间让逻辑工作。
  3. 确保船只以最短的方向转向目标。

我在代码中添加了注释以便更好地解释。希望您可以将其实施到您的系统中或按原样使用它。哦,您可以根据需要更改移动速度和旋转速度。

let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 400;

let mouse = { x: 20, y: 20 };
let canvasBounds = canvas.getBoundingClientRect();
let target;
canvas.addEventListener("mousedown", (e) => {
  mouse.x = e.x - canvasBounds.x;
  mouse.y = e.y - canvasBounds.y;
  target = new Target();
});

class Ship {
  constructor() {
    this.x = 20;
    this.y = 20;
    this.ptA = { x: 15, y: 0 };
    this.ptB = { x: -15, y: 10 };
    this.ptC = { x: -15, y: -10 };
    this.color = "red";
    this.angle1 = 0;
    this.angle2 = 0;
    this.dir = 1;
  }
  draw() {
    ctx.save();
    //use translate to move the ship
    ctx.translate(this.x, this.y);
    //angle1 is the angle from the ship to the target point
    //angle2 is the ships current rotation angle. Once they equal each other then the rotation stops. When you click somewhere else they are no longer equal and the ship will rotate again.
    if (!this.direction(this.angle1, this.angle2)) {
      //see direction() method for more info on this
      if (this.dir == 1) {
        this.angle2 += 0.05; //change rotation speed here
      } else if (this.dir == 0) {
        this.angle2 -= 0.05; //change rotation speed here
      }
    } else {
      this.angle2 = this.angle1;
    }
    ctx.rotate(this.angle2);
    ctx.beginPath();
    ctx.strokeStyle = this.color;
    ctx.moveTo(this.ptA.x, this.ptA.y);
    ctx.lineTo(this.ptB.x, this.ptB.y);
    ctx.lineTo(this.ptC.x, this.ptC.y);
    ctx.closePath();
    ctx.stroke();
    ctx.restore();
  }
  driveToTarget() {
    //get angle to mouse click
    this.angle1 = Math.atan2(mouse.y - this.y, mouse.x - this.x);
    //normalize vector
    let vecX = mouse.x - this.x;
    let vecY = mouse.y - this.y;
    let dist = Math.hypot(vecX, vecY);
    vecX /= dist;
    vecY /= dist;
    //Prevent continuous x and y increment by checking if either vec == 0
    if (vecX != 0 || vecY != 0) {
      //then also give the ship a little buffer incase it passes the given point it doesn't turn back around. This allows time for it to stop if you increase the speed.
      if (
        this.x >= mouse.x + 3 ||
        this.x <= mouse.x - 3 ||
        this.y >= mouse.y + 3 ||
        this.y <= mouse.y - 3
      ) {
        this.x += vecX; //multiple VecX by n to increase speed (vecX*2)
        this.y += vecY; //multiple VecY by n to increase speed (vecY*2)
      }
    }
  }
  direction(ang1, ang2) {
    //converts rads to degrees and ensures we get numbers from 0-359
    let a1 = ang1 * (180 / Math.PI);
    if (a1 < 0) {
      a1 += 360;
    }
    let a2 = ang2 * (180 / Math.PI);
    if (a2 < 0) {
      a2 += 360;
    }
    //checks whether the target is on the right or left side of the ship.
    //We use then to ensure it turns in the shortest direction
    if ((360 + a1 - a2) % 360 > 180) {
      this.dir = 0;
    } else {
      this.dir = 1;
    }
    //Because of animation timeframes there is a chance the ship could turn past the target if rotating too fast. This gives the ship a 1 degree buffer to either side of the target to determine if it is pointed in the right direction.
    //We then correct it to the exact degrees in the draw() method above once the if statment defaults to 'else'
    if (
      Math.trunc(a2) <= Math.trunc(a1) + 1 &&
      Math.trunc(a2) >= Math.trunc(a1) - 1
    ) {
      return true;
    }
    return false;
  }
}

let ship = new Ship();

class Target {
  constructor() {
    this.x = mouse.x;
    this.y = mouse.y;
    this.r = 3;
    this.color = "red";
  }
  draw() {
    ctx.strokeStyle = this.color;
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2, false);
    ctx.stroke();
  }
}

function animate() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  ship.draw();
  ship.driveToTarget();
  if (target) {
    target.draw();
  }
  requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>