如何为Canvas中两个fill.Rect玩家实现半径触发的碰撞检测?
为Canvas双人游戏添加距离碰撞检测
需求说明
我开发了一款双人游戏:
- 一名玩家通过方向键控制,另一名通过WSAD键控制
- 游戏画布尺寸为600×600
- 两个玩家均通过
fillRect绘制 - 需要添加碰撞检测:当两名玩家处于指定半径范围内时,结束游戏或显示“game over”文本
现有代码
HTML代码
<!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" type="text/css" href="style.css"> <meta charset="utf-8" /> <canvas id="mycan"></canvas> <font color='blue'><h1>Player1 = blue</h1></font> <font color='red'><h1>Player2 = red</h1></font> </head> <body> <main></main> <script src="script.js"></script> </body> </html>
CSS代码
html, body { margin:20; padding: 20; } canvas { display: block; } #mycan { background-size: 30px 30px; background-image: linear-gradient(to right, black 1px, transparent 1px), linear-gradient(to bottom, black 1px, green 1px); }
JavaScript代码
//Canvas mycan.style.display = "block"; mycan.height = 600; mycan.width = 600; //make players let player = {x:511, y: 541, w:29, h:29}; let player2 = {x:60, y:31, w:30, h:29}; //Context const ctx = mycan.getContext("2d"); //Start-position ctx.fillRect(player.x, player.y, player.w, player.h); ctx.fillRect(player2.x, player2.y, player2.w, player2.h); //No-smooth-movement window.onkeydown = move = (e) => { let key = e.keyCode; //player1(red) if (key === 68 && player2.x <= mycan.width-30) {player2.x += 30;} //right else if(key === 65 && player2.x >= 10) {player2.x -= 30;} //left else if(key === 83 && player2.y <= mycan.height-30) {player2.y += 30;} //down else if(key === 87 && player2.y >= 10) {player2.y -= 30;} //up //player2(blue) if (key === 39 && player.x <= mycan.width-25) {player.x += 30;} //right else if(key === 37 && player.x >= 10) {player.x -= 30;} //left else if(key === 40 && player.y <= mycan.height-25) {player.y += 30;} //down else if(key === 38 && player.y >= 10) {player.y -= 30;} //up } const draw = ()=>{ //player draw, and player colors ctx.clearRect(0,0, mycan.width, mycan.height); ctx.fillRect(player.x, player.y, player.w, player.h); ctx.fillStyle = "blue"; ctx.fillRect(player2.x,player2.y, player2.w, player2.h); ctx.fillStyle = 'red'; }; setInterval(()=>{ draw(); },1000/60); //collision //this is where i want to add collision
解决方案
1. 实现距离碰撞检测逻辑
先计算两个玩家的中心坐标,再通过勾股定理计算两点间的距离平方(避免开根号提升性能),最后和设定的碰撞半径平方对比:
// 检测碰撞的函数 function checkCollision() { // 计算两个玩家的中心坐标 const playerCenterX = player.x + player.w / 2; const playerCenterY = player.y + player.h / 2; const player2CenterX = player2.x + player2.w / 2; const player2CenterY = player2.y + player2.h / 2; // 计算两点间距离的平方 const distanceSquared = Math.pow(playerCenterX - player2CenterX, 2) + Math.pow(playerCenterY - player2CenterY, 2); // 设定碰撞半径的平方(示例半径为50) const collisionRadius = 50; const collisionRadiusSquared = Math.pow(collisionRadius, 2); // 距离小于等于设定半径时触发碰撞 return distanceSquared <= collisionRadiusSquared; }
2. 整合到游戏循环并处理结束逻辑
修改游戏循环逻辑,添加碰撞检测后的终止操作:
// 保存定时器ID,方便后续清除 let gameLoop = setInterval(()=>{ draw(); // 每次绘制时检测碰撞 if (checkCollision()) { // 停止游戏循环 clearInterval(gameLoop); // 绘制Game Over文本 ctx.fillStyle = 'black'; ctx.font = '48px Arial'; ctx.textAlign = 'center'; ctx.fillText('GAME OVER', mycan.width / 2, mycan.height / 2); // 禁用键盘控制 window.onkeydown = null; } },1000/60);
3. 完整修改后的JavaScript代码
//Canvas mycan.style.display = "block"; mycan.height = 600; mycan.width = 600; //make players let player = {x:511, y: 541, w:29, h:29}; let player2 = {x:60, y:31, w:30, h:29}; //Context const ctx = mycan.getContext("2d"); //Start-position ctx.fillRect(player.x, player.y, player.w, player.h); ctx.fillRect(player2.x, player2.y, player2.w, player2.h); //No-smooth-movement window.onkeydown = move = (e) => { let key = e.keyCode; //player1(red) if (key === 68 && player2.x <= mycan.width-30) {player2.x += 30;} //right else if(key === 65 && player2.x >= 10) {player2.x -= 30;} //left else if(key === 83 && player2.y <= mycan.height-30) {player2.y += 30;} //down else if(key === 87 && player2.y >= 10) {player2.y -= 30;} //up //player2(blue) if (key === 39 && player.x <= mycan.width-25) {player.x += 30;} //right else if(key === 37 && player.x >= 10) {player.x -= 30;} //left else if(key === 40 && player.y <= mycan.height-25) {player.y += 30;} //down else if(key === 38 && player.y >= 10) {player.y -= 30;} //up } const draw = ()=>{ //player draw, and player colors ctx.clearRect(0,0, mycan.width, mycan.height); ctx.fillStyle = 'red'; ctx.fillRect(player.x, player.y, player.w, player.h); ctx.fillStyle = "blue"; ctx.fillRect(player2.x,player2.y, player2.w, player2.h); }; // 检测碰撞的函数 function checkCollision() { const playerCenterX = player.x + player.w / 2; const playerCenterY = player.y + player.h / 2; const player2CenterX = player2.x + player2.w / 2; const player2CenterY = player2.y + player2.h / 2; const distanceSquared = Math.pow(playerCenterX - player2CenterX, 2) + Math.pow(playerCenterY - player2CenterY, 2); const collisionRadius = 50; // 可根据需求调整半径值 const collisionRadiusSquared = Math.pow(collisionRadius, 2); return distanceSquared <= collisionRadiusSquared; } // 游戏循环 let gameLoop = setInterval(()=>{ draw(); if (checkCollision()) { clearInterval(gameLoop); ctx.fillStyle = 'black'; ctx.font = '48px Arial'; ctx.textAlign = 'center'; ctx.fillText('GAME OVER', mycan.width / 2, mycan.height / 2); window.onkeydown = null; } },1000/60);
说明
- 可通过修改
collisionRadius值调整碰撞触发的严格程度,数值越小触发越灵敏 - 使用距离平方对比是为了避免开根号运算,提升游戏性能
- 碰撞触发后的逻辑可自定义,比如添加重新开始按钮、弹窗提示等
内容的提问来源于stack exchange,提问作者Jude Mauldin
相关产品推荐
相关产品推荐

