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

JavaScript HTML5 Canvas如何实现子弹发射后不跟随玩家位置变化

问题根因
  • 全局复用同一个Bullet实例:你仅在init函数中初始化了一次全局bullet对象,每次调用shootGun都是将同一个对象的引用推入bullets数组,所有已发射的子弹本质上指向同一块内存地址,玩家旋转时修改该全局对象的属性,所有已发射的子弹会同步生效
  • 未固化子弹发射时的方向参数:目前子弹速度仅写死了固定的speedX = 3,没有根据发射瞬间的玩家朝向计算对应独立的速度分量,子弹移动时会读取实时的玩家角度而非发射时的角度
  • 存在无效的数组操作逻辑:控制模块中bullets.slice(bullet, 1)属于错误用法,slice不会修改原数组,反而可能引发索引混乱
修复步骤
  1. 移除init函数中全局初始化bullet的逻辑,改为每次射击时新建独立的Bullet实例,避免引用共享
  2. 每次创建子弹时,基于发射瞬间的玩家朝向计算固定的x、y轴速度分量,存储到当前子弹实例的私有属性中,后续移动仅读取该私有值,不关联实时玩家状态
  3. 清理无效的数组操作代码,调整超时销毁逻辑的触发时机,避免重复注册计时器
代码修改示例

修改后的shootGun函数

let bullets = [];
function shootGun() {
  // 每次射击新建独立实例
  const newBullet = new Bullet({
    x: ammo.position.x,
    y: ammo.position.y,
    width: ammo.style.width,
    height: ammo.style.height,
    color: "black",
  });
  // 替换为你实际存储玩家/枪口朝向角度的变量,若弧度已提前转换可去掉角度转弧度的计算
  const fireAngle = 玩家当前朝向角度;
  // 固化当前发射方向的速度分量
  newBullet.speedX = 3 * Math.cos(fireAngle * Math.PI / 180);
  newBullet.speedY = 3 * Math.sin(fireAngle * Math.PI / 180);
  // 注册一次超时销毁逻辑,不用放在遍历循环中重复触发
  setTimeout(() => {
    newBullet.alive = false;
  }, 1000);
  bullets.push(newBullet);
}

修改控制模块代码

// 原有数组清理逻辑保留即可
for (let i = 0; i < bullets.length; i++) {
  if (bullets[i] === undefined) continue;
  if (bullets[i].alive === false) {
    bullets.splice(i, 1);
    // splice后索引会前移,补充i--避免漏遍历元素
    i--;
    continue;
  }
}

if (punching) {
  arm1.position.x = 60;
  // 仅保留射击调用,移除无效的slice操作
  shootGun();
}

修改遍历更新逻辑

bullets.forEach((bullet) => {
  bullet.update();
  bullet.newPos();
  // 移除这里的setTimeout,已移到创建子弹的逻辑中
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 12:21:01