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

JavaScript Canvas射击游戏实现多枚独立属性子弹的问题咨询

子弹封装独立对象的实现方案

核心改动点

  • 定义独立的子弹类,将每颗子弹的坐标、角度、速度、宽高属性和更新、绘制方法封装在一起,每个实例完全独立互不干扰
  • 用数组存储所有存活的子弹实例,支持同时存在多颗子弹
  • 触发发射时新建子弹实例存入数组,每帧遍历数组更新所有子弹状态,自动清理超出画布的子弹避免内存浪费
  • 移除原有的全局子弹状态变量,解决只能存在一颗子弹的问题

修改后完整可运行代码

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.translate(canvas.width / 2, canvas.height / 2);
// 全局存储所有子弹的数组
const projectiles = [];

var playerAltura = 60;
var playerLargura = 60;
var playerPosX = 0 - playerLargura / 2;
var playerPosY = 0 - playerAltura / 2;

var enemieLargura = playerLargura / 2;
var enemieAltura = playerAltura / 2;
var enemiePosX = rand(- canvas.width / 2, canvas.width / 2);
var enemiePosY = rand(- canvas.height / 2, canvas.height / 2);

// 子弹类:每个实例对应一颗独立的子弹
class Projectile {
  constructor(x, y, angle, speed = 150, width = 20, height = 20) {
    this.x = x; // 子弹X坐标
    this.y = y; // 子弹Y坐标
    this.angle = angle; // 发射角度
    this.speed = speed; // 飞行速度
    this.width = width; // 宽度
    this.height = height; // 高度
    // 提前计算速度分量,避免每帧重复计算
    const rads = angle * Math.PI / 180;
    this.velX = Math.cos(rads) * speed / 60;
    this.velY = Math.sin(rads) * speed / 60;
  }
  // 更新子弹位置
  update() {
    this.x += this.velX;
    this.y -= this.velY;
  }
  // 绘制子弹
  draw() {
    ctx.beginPath();
    ctx.fillStyle = "yellow"; // 按需求改为黄色子弹
    ctx.fillRect(this.x - this.width / 2, this.y - this.height / 2, this.width, this.height);
    ctx.closePath();
  }
  // 判断子弹是否超出画布边界
  isOutOfBounds() {
    return Math.abs(this.x) > canvas.width / 2 || Math.abs(this.y) > canvas.height / 2;
  }
}

function rand(min, max){
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function drawEnemie(){
  ctx.beginPath();
  ctx.fillStyle = "black";
  ctx.fillRect(enemiePosX, enemiePosY, enemieLargura, enemieAltura);
  ctx.closePath();
}

function drawPlayer(){
  ctx.beginPath()
  ctx.fillStyle = "red";
  ctx.fillRect(playerPosX, playerPosY, playerLargura, playerAltura);
  ctx.closePath();
}

function calcDegrees(anchorX, anchorY, pointX, pointY){
  const degree = Math.atan2(anchorY - pointY, anchorX - pointX) * 180 / Math.PI + 180;
  return degree;
}

// 点击画布发射子弹
canvas.addEventListener('click', () => {
  // 计算朝向敌人的发射角度
  const shootAngle = -calcDegrees(playerPosX, playerPosY, enemiePosX, enemiePosY);
  // 子弹初始位置为玩家中心
  const startX = playerPosX + playerLargura / 2;
  const startY = playerPosY + playerAltura / 2;
  // 新建子弹实例存入数组
  projectiles.push(new Projectile(startX, startY, shootAngle));
})

function draw(){
  ctx.clearRect(-canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height);
  // 倒序遍历子弹数组,更新、绘制、清理超出边界的子弹
  for(let i = projectiles.length - 1; i >= 0; i--) {
    const bullet = projectiles[i];
    bullet.update();
    bullet.draw();
    if(bullet.isOutOfBounds()) {
      projectiles.splice(i, 1);
    }
  }
  drawPlayer();
  drawEnemie();
}

// 优先用requestAnimationFrame代替setInterval,渲染更流畅
function animate() {
  draw();
  requestAnimationFrame(animate);
}
animate();

补充说明

如果需要改成自动发射,只需要在合适的位置调用新建子弹的逻辑即可,不需要修改子弹类本身的代码。如果需要加碰撞检测,遍历子弹数组和敌人实例判断坐标重叠即可。

内容的提问来源于stack exchange,提问作者Matels Alves de Sousa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 12:27:02