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
相关产品推荐
相关产品推荐

