Search code examples
javascripthtmlcsscanvasgame-development

How to block the player from leaving the canvas without getting them stuck?


I have a special move function that works as intended, however I tried making a feature that tries to block the player from leaving the canvas.

I tried checking to see if the player's x and y was less than the canvas width/height and greater than 0, and here's my method.

const canvas = document.createElement("canvas");
// ...
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// ...
move() {
    this.angle += this.rotv;
    this.rotv *= this.drag;
    this.vx += this.ax;
    this.vy += this.ay;
    this.ax *= this.drag;
    this.ay *= this.drag;
    this.vx *= this.drag;
    this.vy *= this.drag;
    if (this.x < canvas.width - this.width + 3.75 && this.x > this.width + 3.75) {
      this.x += Math.cos(this.angle) * this.vx;
    }
    if (this.y < canvas.height - this.height + 3.75 && this.y > this.height + 3.75) {
      this.y += Math.sin(this.angle) * this.vy;
    }
  },

However, when you are at the border of the canvas, the player gets stuck and you can no longer move, but you CAN rotate. This is how it looks like.

What am I doing wrong and how can I fix the issue?

Full code:

const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var projectileArray = []
var keydown = false

const kbd = {
  ArrowLeft: false, 
  ArrowUp: false, 
  ArrowRight: false,
  ArrowDown: false,
};

const noHoldDown = {
  Space: false,
}

function Projectile(x,y,speed,direction,duration) {
  Object.assign(this, {x,y,speed,direction,duration});
  this.draw = ctx => {
    ctx.arc(this.x, this.y, 3.75, 0, 2 * Math.PI);
    ctx.fillStyle = 'white';
    ctx.fill();
  }
  this.update = ctx => {
    ctx.beginPath();
    this.x += Math.cos(this.direction) * this.speed;
    this.y += Math.sin(this.direction) * this.speed;
    this.draw(ctx);
    this.duration--;
  }
  this.isDone = () => this.duration <= 0;
}


const ship = {
  angle: 0,
  color: "white",
  x: canvas.width / 2,
  y: canvas.height / 2,
  width: 10,
  height: 12,
  drag: 0.9,
  accSpeed: 0.025,
  rotSpeed: 0.007,
  rotv: 0,
  ax: 0,
  ay: 0,
  vx: 0,
  vy: 0,
  rotateLeft() {
    this.rotv -= this.rotSpeed;
  },
  rotateRight() {
    this.rotv += this.rotSpeed;
  },
  accelerate() {
    this.ax += this.accSpeed;
    this.ay += this.accSpeed;
  },
  decelerate() {
    this.ax -= this.accSpeed;
    this.ay -= this.accSpeed;
  },
  shoot() {
    let mySpeed = Math.sqrt(this.vx * this.vx + this.vy * this.vy)
    let bullet = new Projectile(this.x, this.y, 3 + mySpeed, this.angle, 500)
    projectileArray.push(bullet);
    // projectileArray.push([this.x, this.y, this.angle])
  },
  move() {
    this.angle += this.rotv;
    this.rotv *= this.drag;
    this.vx += this.ax;
    this.vy += this.ay;
    this.ax *= this.drag;
    this.ay *= this.drag;
    this.vx *= this.drag;
    this.vy *= this.drag;
    if (this.x < canvas.width - this.width + 3.75 && this.x > this.width + 3.75) {
      this.x += Math.cos(this.angle) * this.vx;
    }
    if (this.y < canvas.height - this.height + 3.75 && this.y > this.height + 3.75) {
      this.y += Math.sin(this.angle) * this.vy;
    }
  },
  draw(ctx) {
    ctx.save();
    ctx.lineWidth = 3;
    ctx.translate(this.x, this.y);
    ctx.rotate(this.angle);
    ctx.beginPath();
    ctx.moveTo(this.height, 0);
    ctx.lineTo(-this.height, this.width);
    ctx.lineTo(-this.height, -this.width);
    ctx.closePath();
    ctx.strokeStyle = this.color;
    ctx.stroke();
    ctx.restore();
  }
};

document.addEventListener("keydown", event => {
  if (event.code in kbd) {
    event.preventDefault();
    kbd[event.code] = true;
  }
});
document.addEventListener("keydown", event => {
  if (event.code in noHoldDown) {
    if (!keydown) {
      keydown = true;
      ship.shoot();
    }
  }
});

document.addEventListener('keyup', event => {
  if (event.code in noHoldDown) {
    keydown = false;
  }
});

document.addEventListener("keyup", event => {
  if (event.code in kbd) {
    event.preventDefault();
    kbd[event.code] = false; 
  }
});

(function update() {
  ctx.fillStyle = "black";
  ctx.fillRect(0, 0, canvas.width, canvas.height);  

  const shipActions = {
    ArrowLeft: "rotateLeft",
    ArrowUp: "accelerate",
    ArrowDown: "decelerate",
    ArrowRight: "rotateRight",
  };

  for (const key in shipActions) {
    if (kbd[key]) {
      ship[shipActions[key]]();
    }
  }
  ship.move();
  ship.draw(ctx);
  for (var i = 0; i < projectileArray.length; i++) {
    let bullet = projectileArray[i];
    bullet.update(ctx)
  }
  projectileArray = projectileArray.filter(bullet => !bullet.isDone());
  requestAnimationFrame(update);
})();

Solution

  • Lets make a snippet. I fixed it by trimming the value rather then preventing it from changing when it overflows. Because once it overflowed in your case it got stuck.

    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");
    document.body.appendChild(canvas);
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    var projectileArray = []
    var keydown = false
    
    const kbd = {
      ArrowLeft: false,
      ArrowUp: false,
      ArrowRight: false,
      ArrowDown: false,
    };
    
    const noHoldDown = {
      Space: false,
    }
    
    function Projectile(x, y, speed, direction, duration) {
      Object.assign(this, {
        x,
        y,
        speed,
        direction,
        duration
      });
      this.draw = ctx => {
        ctx.arc(this.x, this.y, 3.75, 0, 2 * Math.PI);
        ctx.fillStyle = 'white';
        ctx.fill();
      }
      this.update = ctx => {
        ctx.beginPath();
        this.x += Math.cos(this.direction) * this.speed;
        this.y += Math.sin(this.direction) * this.speed;
        this.draw(ctx);
        this.duration--;
      }
      this.isDone = () => this.duration <= 0;
    }
    
    
    const ship = {
      angle: 0,
      color: "white",
      x: canvas.width / 2,
      y: canvas.height / 2,
      width: 10,
      height: 12,
      drag: 0.9,
      accSpeed: 0.025,
      rotSpeed: 0.007,
      rotv: 0,
      ax: 0,
      ay: 0,
      vx: 0,
      vy: 0,
      rotateLeft() {
        this.rotv -= this.rotSpeed;
      },
      rotateRight() {
        this.rotv += this.rotSpeed;
      },
      accelerate() {
        this.ax += this.accSpeed;
        this.ay += this.accSpeed;
      },
      decelerate() {
        this.ax -= this.accSpeed;
        this.ay -= this.accSpeed;
      },
      shoot() {
        let mySpeed = Math.sqrt(this.vx * this.vx + this.vy * this.vy)
        let bullet = new Projectile(this.x, this.y, 3 + mySpeed, this.angle, 500)
        projectileArray.push(bullet);
        // projectileArray.push([this.x, this.y, this.angle])
      },
      move() {
        this.angle += this.rotv;
        this.rotv *= this.drag;
        this.vx += this.ax;
        this.vy += this.ay;
        this.ax *= this.drag;
        this.ay *= this.drag;
        this.vx *= this.drag;
        this.vy *= this.drag;
        
        this.x += Math.cos(this.angle) * this.vx;
        this.y += Math.sin(this.angle) * this.vy;
        
        this.x = Math.min(this.x, canvas.width - this.width)
        this.x = Math.max(this.x, 0 + this.width)
    
        this.y = Math.min(this.y, canvas.height - this.height)
        this.y = Math.max(this.y, 0 + this.height)
    
      },
      draw(ctx) {
        ctx.save();
        ctx.lineWidth = 3;
        ctx.translate(this.x, this.y);
        ctx.rotate(this.angle);
        ctx.beginPath();
        ctx.moveTo(this.height, 0);
        ctx.lineTo(-this.height, this.width);
        ctx.lineTo(-this.height, -this.width);
        ctx.closePath();
        ctx.strokeStyle = this.color;
        ctx.stroke();
        ctx.restore();
      }
    };
    
    document.addEventListener("keydown", event => {
      if (event.code in kbd) {
        event.preventDefault();
        kbd[event.code] = true;
      }
    });
    document.addEventListener("keydown", event => {
      if (event.code in noHoldDown) {
        if (!keydown) {
          keydown = true;
          ship.shoot();
        }
      }
    });
    
    document.addEventListener('keyup', event => {
      if (event.code in noHoldDown) {
        keydown = false;
      }
    });
    
    document.addEventListener("keyup", event => {
      if (event.code in kbd) {
        event.preventDefault();
        kbd[event.code] = false;
      }
    });
    
    (function update() {
      ctx.fillStyle = "black";
      ctx.fillRect(0, 0, canvas.width, canvas.height);
    
      const shipActions = {
        ArrowLeft: "rotateLeft",
        ArrowUp: "accelerate",
        ArrowDown: "decelerate",
        ArrowRight: "rotateRight",
      };
    
      for (const key in shipActions) {
        if (kbd[key]) {
          ship[shipActions[key]]();
        }
      }
      ship.move();
      ship.draw(ctx);
      for (var i = 0; i < projectileArray.length; i++) {
        let bullet = projectileArray[i];
        bullet.update(ctx)
      }
      projectileArray = projectileArray.filter(bullet => !bullet.isDone());
      requestAnimationFrame(update);
    })();
    body {
      margin: 0;
      padding: 0;
      overflow: hidden;
    }