Canvas实现玩家朝向发射子弹:shoot函数无绘制问题排查与实现
问题描述
我已经开发出可以创建玩家并实现移动、旋转功能的程序,现在想绑定空格键生成小型子弹,让它沿玩家朝向发射。
我写了shoot()函数,本应该在玩家位置绘制小圆,但画布上没任何内容,控制台也没报错。代码片段如下:
shoot() { ctx.beginPath(); ctx.arc(this.X, this.Y, 5, 0, 2 * Math.PI, false); ctx.fillStyle = 'white'; ctx.fill(); },
请问哪里出错了?该怎么修复?另外,我还想实现子弹真正沿朝向发射的效果。
完整代码如下:
const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); document.body.appendChild(canvas); canvas.width = window.innerWidth; canvas.height = window.innerHeight; canvas.style = "position: absolute; top: 0px; left: 0px; right: 0px; bottom: 0px; margin: auto; border: 0px"; const kbd = { ArrowLeft: false, ArrowUp: false, ArrowRight: false, ArrowDown: false, Space: false, }; 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() { ctx.beginPath(); ctx.arc(this.X, this.Y, 5, 0, 2 * Math.PI, false); ctx.fillStyle = 'white'; ctx.fill(); }, 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; }, 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("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", Space: "shoot", }; for (const key in shipActions) { if (kbd[key]) { ship[shipActions[key]](); } } ship.move(); ship.draw(ctx); requestAnimationFrame(update); })();
问题分析与修复
1. 子弹不显示的直接原因
你的shoot()函数里用了this.X和this.Y,但玩家对象里存储位置的属性是小写的this.x和this.y,大小写不匹配导致取到undefined,绘制的圆不在画布可见范围内。
修复这个错误只需要把this.X、this.Y改成this.x、this.y:
shoot() { ctx.beginPath(); ctx.arc(this.x, this.y, 5, 0, 2 * Math.PI, false); ctx.fillStyle = 'white'; ctx.fill(); },
不过改完后你会发现子弹只会闪一下就消失——因为update()函数每次都会用黑色矩形清空整个画布,且当前shoot()只是单次绘制,没有保存子弹状态让它持续存在并移动。
2. 实现子弹沿朝向发射的完整方案
要让子弹真正发射并移动,需要维护一个子弹数组存储每个子弹的位置、速度等状态,然后在每一帧更新并绘制所有子弹。
步骤1:添加子弹数组和配置
在const kbd之后添加:
// 存储所有子弹的数组 const bullets = []; // 子弹基础配置 const bulletConfig = { radius: 5, speed: 5, color: 'white' };
步骤2:修改shoot()函数生成子弹对象
不再直接绘制,而是生成子弹对象加入数组,让子弹从玩家头部发射并继承玩家移动速度:
shoot() { // 计算子弹初始位置:飞船头部(沿朝向偏移飞船height距离) const bulletX = this.x + Math.cos(this.angle) * this.height; const bulletY = this.y + Math.sin(this.angle) * this.height; // 计算子弹速度:朝向方向的基础速度 + 玩家当前移动速度 const bulletVX = Math.cos(this.angle) * bulletConfig.speed + this.vx; const bulletVY = Math.sin(this.angle) * bulletConfig.speed + this.vy; // 将子弹加入数组 bullets.push({ x: bulletX, y: bulletY, vx: bulletVX, vy: bulletVY, radius: bulletConfig.radius, color: bulletConfig.color }); },
步骤3:添加子弹更新与绘制逻辑
在update()函数的ship.draw(ctx)之后添加:
// 更新并绘制所有子弹 for (let i = bullets.length - 1; i >= 0; i--) { const bullet = bullets[i]; // 更新子弹位置 bullet.x += bullet.vx; bullet.y += bullet.vy; // 绘制子弹 ctx.beginPath(); ctx.arc(bullet.x, bullet.y, bullet.radius, 0, 2 * Math.PI); ctx.fillStyle = bullet.color; ctx.fill(); // 移除超出画布的子弹,节省性能 if (bullet.x < 0 || bullet.x > canvas.width || bullet.y < 0 || bullet.y > canvas.height) { bullets.splice(i, 1); } }
步骤4:优化按键逻辑(可选,防止按住空格连发)
如果不想按住空格时连续发射子弹,可修改键盘事件和触发逻辑:
// 修改kbd对象 const kbd = { ArrowLeft: false, ArrowUp: false, ArrowRight: false, ArrowDown: false, Space: { pressed: false, triggered: false }, }; // 修改keydown事件 document.addEventListener("keydown", event => { if (event.code === "Space") { event.preventDefault(); if (!kbd.Space.pressed) { kbd.Space.pressed = true; kbd.Space.triggered = false; } } else if (event.code in kbd) { event.preventDefault(); kbd[event.code] = true; } }); // 修改keyup事件 document.addEventListener("keyup", event => { if (event.code === "Space") { event.preventDefault(); kbd.Space.pressed = false; } else if (event.code in kbd) { event.preventDefault(); kbd[event.code] = false; } }); // 修改update()里的触发逻辑 for (const key in shipActions) { if (key === "Space") { if (kbd.Space.pressed && !kbd.Space.triggered) { ship[shipActions[key]](); kbd.Space.triggered = true; } } else if (kbd[key]) { ship[shipActions[key]](); } }
最终效果
修改完成后,按空格键会从飞船头部发射白色子弹,子弹沿飞船朝向飞行,超出画布后自动被清理;若启用按键优化,按住空格只会发射一颗子弹,否则会连续发射。
内容的提问来源于stack exchange,提问作者gamer merch
相关产品推荐
相关产品推荐

