Home > OS >  How to move object to target naturally and smoothly?
How to move object to target naturally and smoothly?

Time:12-06

Can somebody fix it script to make it works properly?

What I expects:

  1. Run script
  2. Click at the canvas to set target (circle)
  3. Object (triangle) starts to rotate and move towards to target (circle)
  4. Change target at any time

How it works:

  1. Sometimes object rotates correctly, sometimes isn't
  2. Looks like one half sphere works well, another isn't

Thanks!

// 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);
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

This turned out to be a bit more challenging than I first thought and I ended up just re-writing the code.

The challenges:

  1. Ensure the ship only rotated to the exact point of target. This required me to compare the two angle from the ship current position to where we want it to go.
  2. Ensure the target did not rotate past the target and the ship did not translate past the target. This required some buffer space for each because when animating having this.x === this.x when an object is moving is very rare to happen so we need some room for the logic to work.
  3. Ensure the ship turned in the shortest direction to the target.

I have added notes in the code to better explain. Hopefully you can implement this into yours or use it as is. Oh and you can change the movement speed and rotation speed as you see fit.

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>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related