You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何阻止玩家移出Canvas且避免角色出现卡顿?

问题:Canvas角色触碰到边界后卡住无法移动

我有一个运行正常的特殊移动函数,尝试实现阻止玩家移出Canvas的功能。通过检测玩家的x、y坐标是否在Canvas范围内来实现,核心代码如下:

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;
    }
  },

但当角色处于Canvas边界时会卡住无法移动,仅可旋转。请问哪里出错了?该如何修复?


错误原因

当前的边界检测逻辑是仅当角色完全处于安全区域内时才允许移动,一旦角色触碰到边界(比如this.x等于或超出canvas.width - this.width + 3.75),后续所有移动操作都会被禁止——哪怕你想往Canvas内部移动也不行,这就导致角色卡在边界无法动弹。

正确的逻辑应该是:允许角色先执行移动操作,若移动后超出边界,再将位置修正到边界上,而非直接禁止移动。


修复方案

修改move函数,调整执行顺序:先完成移动计算,再对x、y坐标进行边界约束,确保角色始终处于Canvas范围内:

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;

    // 定义边界范围
    const minX = this.width + 3.75;
    const maxX = canvas.width - this.width + 3.75;
    const minY = this.height + 3.75;
    const maxY = canvas.height - this.height + 3.75;

    // 约束x坐标在合法范围内
    this.x = Math.max(minX, Math.min(this.x, maxX));
    // 约束y坐标在合法范围内
    this.y = Math.max(minY, Math.min(this.y, maxY));
  },

完整修复后的代码

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);
  },
  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;

    // 边界约束
    const minX = this.width + 3.75;
    const maxX = canvas.width - this.width + 3.75;
    const minY = this.height + 3.75;
    const maxY = canvas.height - this.height + 3.75;

    this.x = Math.max(minX, Math.min(this.x, maxX));
    this.y = Math.max(minY, Math.min(this.y, maxY));
  },
  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);
})();

内容的提问来源于stack exchange,提问作者gamer merch

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 12:18:26