实现从定点向鼠标位置发射抛射物的JavaScript技术求助
实现鼠标精准控制炮弹发射目标位置的解决方案
我正在开发一个大炮发射炮弹的项目,目前已完成具备重力效果和地面弹跳功能的炮弹绘制代码,现希望实现通过鼠标精准控制炮弹的发射目标位置。以下是我正在使用的JavaScript代码:
const canvas = document.querySelector('canvas') const c = canvas.getContext('2d') canvas.width = innerWidth canvas.height = innerHeight // const mouse = { // x: 0, // y: 0 // } const mouse = { x: innerWidth / 2, y: innerHeight / 2 } const center = {x: canvas.width / 2, y: canvas.height / 2} // Event Listeners addEventListener('mousemove', (event) => { mouse.x = event.clientX mouse.y = event.clientY - canvas.height angle = Math.atan2(mouse.y, mouse.x) //let distBetween = function(p1, p2){ //return Math.sqrt(Math.pow((p2.x - p1.x), 2) + Math.pow((p2.y - p1.y), 2)); //} //let angleBetween = function(p1, p2){ //return Math.atan2(p2.y - p1.y, p2.x - p1.x) //} //}) addEventListener('resize', () => { canvas.width = innerWidth canvas.height = innerHeight init() }) let angle = 0 // Objects class Particle { constructor(x, y, radius, color, distanceFromCenter) { this.x = x this.y = y this.radius = radius this.color = color this.distanceFromCenter = distanceFromCenter } draw() { c.beginPath() c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false) c.fillStyle = this.color c.fill() c.closePath() } update() { this.draw() this.x = (0) + (this.distanceFromCenter + 30) * Math.cos(angle) this.y = canvas.height + (this.distanceFromCenter +30) * Math.sin(angle) } } class Cannonball { constructor(x, y, radius, color){ this.x = x this.y = y this.radius = radius this.color = color this.dx = Math.cos(angle) * 2; this.dy = Math.sin(angle) * 2; this.velocity = 2 this.gravity = 1 this.friction = -0.75 } draw(){ c.beginPath() c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false) c.fillStyle = 'blue' c.fill() c.closePath() } update(){ if (this.y + this.radius + 5 > canvas.height){ this.velocity = -this.velocity * this.gravity } else { this.velocity += this.gravity } this.y += this.velocity this.velocity += this.friction this.draw() } } // let cannonball = new Cannonball(center.x, center.y, 30, 'blue') // particle Implementation let particleCount = 400 let particles; // let cannonballCount = 400 let cannonballs; function init() { particles = [] for (let i = 0; i < particleCount; i++) { const x = 0 + i * Math.cos(1) const y = canvas.height + this.distanceFromCenter * Math.sin() particles.push(new Particle(x, y, 30, 'red', i)) } cannonballs = [] } // Animation Loop function animate() { requestAnimationFrame(animate) c.clearRect(0, 0, canvas.width, canvas.height) particles.forEach((particle) => { particle.update() }) cannonballs.forEach(cannonball => { cannonball.update() }) } init() animate() addEventListener('click', (event) => { mouse.x = event.clientX mouse.y = event.clientY cannonballs.push(new Cannonball(mouse.x, mouse.y, 30, 'blue', { x: Math.cos(angleIncrement * i), y: Math.sin(angleIncrement * i) }) }
核心修改方案
1. 修正鼠标位置与角度计算
原代码中鼠标Y轴计算错误,且角度未基于大炮位置计算,导致指向偏差。调整后:
- 鼠标位置直接取
event.clientX和event.clientY - 基于大炮固定位置(画布底部中心)到鼠标的向量计算发射角度
2. 重构炮弹运动逻辑
原炮弹类的velocity与dx/dy混用,运动逻辑混乱。改为拆分水平/垂直速度分量,重力仅作用于垂直方向,同时添加合理的弹跳摩擦力和空气阻力。
3. 调整发射触发逻辑
点击时从大炮位置发射炮弹,而非鼠标位置,初速度由计算出的角度和固定速度(可调整)生成。
修改后的完整代码
const canvas = document.querySelector('canvas') const c = canvas.getContext('2d') canvas.width = innerWidth canvas.height = innerHeight const mouse = { x: innerWidth / 2, y: innerHeight / 2 } // 大炮固定在画布底部中心 const cannonPos = { x: canvas.width / 2, y: canvas.height - 30 } let angle = 0 // 事件监听 addEventListener('mousemove', (event) => { mouse.x = event.clientX mouse.y = event.clientY // 计算大炮到鼠标的角度(画布坐标系Y轴向下为正) angle = Math.atan2(mouse.y - cannonPos.y, mouse.x - cannonPos.x) }) addEventListener('resize', () => { canvas.width = innerWidth canvas.height = innerHeight // 窗口 resize 时重新设置大炮位置 cannonPos.x = canvas.width / 2 cannonPos.y = canvas.height - 30 init() }) // 粒子类(作为大炮的视觉指示器) class Particle { constructor(x, y, radius, color, distanceFromCenter) { this.x = x this.y = y this.radius = radius this.color = color this.distanceFromCenter = distanceFromCenter } draw() { c.beginPath() c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false) c.fillStyle = this.color c.fill() c.closePath() } update() { this.draw() this.x = cannonPos.x + (this.distanceFromCenter + 30) * Math.cos(angle) this.y = cannonPos.y + (this.distanceFromCenter + 30) * Math.sin(angle) } } // 重写炮弹类,优化运动逻辑 class Cannonball { constructor(x, y, radius, color, angle, speed) { this.x = x this.y = y this.radius = radius this.color = color // 根据发射角度计算初速度分量 this.dx = Math.cos(angle) * speed this.dy = Math.sin(angle) * speed this.gravity = 0.5 this.bounceFriction = 0.7 // 弹跳时的垂直速度衰减 this.airResistance = 0.995 // 水平方向空气阻力 } draw() { c.beginPath() c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false) c.fillStyle = this.color c.fill() c.closePath() } update() { // 应用空气阻力 this.dx *= this.airResistance // 应用重力 this.dy += this.gravity // 地面碰撞检测与弹跳处理 if (this.y + this.radius >= canvas.height) { this.y = canvas.height - this.radius this.dy = -this.dy * this.bounceFriction } // 更新炮弹位置 this.x += this.dx this.y += this.dy this.draw() } } let particleCount = 400 let particles; let cannonballs; function init() { particles = [] for (let i = 0; i < particleCount; i++) { particles.push(new Particle(cannonPos.x, cannonPos.y, 3, 'red', i)) } cannonballs = [] } // 动画循环 function animate() { requestAnimationFrame(animate) c.clearRect(0, 0, canvas.width, canvas.height) // 绘制大炮底座 c.fillStyle = 'gray' c.fillRect(cannonPos.x - 50, cannonPos.y, 100, 20) particles.forEach((particle) => { particle.update() }) cannonballs.forEach(cannonball => { cannonball.update() }) } init() animate() // 点击鼠标发射炮弹,速度设为20,可根据需求调整 addEventListener('click', () => { cannonballs.push(new Cannonball(cannonPos.x, cannonPos.y, 10, 'blue', angle, 20)) })
关键说明
- 大炮固定在画布底部中心,添加了灰色底座增强视觉效果
- 鼠标移动时,粒子组件会跟随角度变化,直观显示发射方向
- 炮弹运动逻辑更符合物理规律,弹跳和减速效果自然
- 点击鼠标即可从大炮位置向鼠标指向发射炮弹,精准度完全匹配鼠标位置
内容的提问来源于stack exchange,提问作者bg560581
相关产品推荐
相关产品推荐

