JavaScript碰撞检测问题:英雄移动未贴合碰撞块及速度调节矛盾
问题根源
当前碰撞逻辑的核心问题:当英雄以较大步长(比如10像素)移动时,碰撞检测触发时机是英雄已经尝试移动到步长叠加后的位置,此时直接停止会让英雄停在“穿透”碰撞区域或与碰撞块有间隙的位置。另外,setInterval受浏览器最小间隔限制,无法通过缩小间隔提升步长为1时的移动速度,反而可能引发性能问题。
解决方案
一、保持步长为1时的移动速度与流畅性
放弃setInterval,改用requestAnimationFrame实现基于时间的移动控制。这种方式与浏览器刷新同步,更流畅,且可通过「每秒移动像素数」精准控制速度,不受步长大小限制。
示例代码:
let interV; let lastTime = 0; const moveSpeed = 150; // 每秒移动150像素,可按需调整 function runH(xDir, yDir) { cancelAnimationFrame(interV); inmotion = true; // 标准化方向向量,确保方向正确且不影响速度计算 const dirX = xDir !== 0 ? xDir / Math.abs(xDir) : 0; const dirY = yDir !== 0 ? yDir / Math.abs(yDir) : 0; function moveHero(currentTime) { if (!lastTime) lastTime = currentTime; const deltaTime = (currentTime - lastTime) / 1000; // 转换为秒级时间差 lastTime = currentTime; const HeroP = $("#BTB_Hero").position(); const HeroW = $("#BTB_Hero").width(); const HeroH = $("#BTB_Hero").height(); // 根据时间差计算实际移动距离 const moveX = dirX * moveSpeed * deltaTime; const moveY = dirY * moveSpeed * deltaTime; let HeroX = HeroP.left + moveX; let HeroY = HeroP.top + moveY; let collision = false; let collisionBlock = null; // 碰撞检测并记录碰撞块信息 for (let i = 0; i < blocksA.length; i++) { const block = $(blocksA[i]); const blockP = block.position(); const blockX = blockP.left; const blockY = blockP.top; const blockW = block.width(); const blockH = block.height(); if (HeroX + HeroW >= blockX && HeroX <= blockX + blockW && HeroY + HeroH >= blockY && HeroY <= blockY + blockH) { collision = true; collisionBlock = { x: blockX, y: blockY, w: blockW, h: blockH }; break; } } if (collision) { // 碰撞时修正位置到贴合状态 if (dirX > 0) HeroX = collisionBlock.x - HeroW; // 向右移动,贴住块左侧 else if (dirX < 0) HeroX = collisionBlock.x + collisionBlock.w; // 向左移动,贴住块右侧 if (dirY > 0) HeroY = collisionBlock.y - HeroH; // 向下移动,贴住块顶部 else if (dirY < 0) HeroY = collisionBlock.y + collisionBlock.h; // 向上移动,贴住块底部 $("#BTB_Hero").css({ "top": HeroY + "px", "left": HeroX + "px" }); inmotion = false; } else { $("#BTB_Hero").css({ "top": HeroY + "px", "left": HeroX + "px" }); interV = requestAnimationFrame(moveHero); } checkLevelChange(HeroX, HeroY); } interV = requestAnimationFrame(moveHero); }
二、步长为10时碰撞后贴合碰撞块
如果坚持使用原有setInterval逻辑,需在碰撞检测触发时,根据移动方向计算英雄的贴合位置,更新位置后再停止移动。
修改后的碰撞处理代码(需配合碰撞块记录逻辑):
function runH(x, y) { clearInterval(interV); inmotion = true; interV = setInterval(function () { var HeroP = $("#BTB_Hero").position(); var HeroW = $("#BTB_Hero").width(); var HeroH = $("#BTB_Hero").height(); var HeroX = HeroP.left + x; var HeroY = HeroP.top + y; let collision = false; let collisionBlock = null; for (let i = 0; i < blocksA.length; i++){ var block = $(blocksA[i]); var blockP = block.position(); var blockX = blockP.left; var blockY = blockP.top; var blockW = block.width(); var blockH = block.height(); if ( HeroX + HeroW >= blockX && HeroX <= blockX + blockW && HeroY + HeroH >= blockY && HeroY <= blockY + blockH) { collision = true; collisionBlock = { x: blockX, y: blockY, w: blockW, h: blockH }; break; } } if (collision) { // 根据移动方向修正到贴合位置 let finalX = HeroP.left; let finalY = HeroP.top; if (x > 0) finalX = collisionBlock.x - HeroW; // 右移贴块左 else if (x < 0) finalX = collisionBlock.x + collisionBlock.w; // 左移贴块右 if (y > 0) finalY = collisionBlock.y - HeroH; // 下移贴块顶 else if (y < 0) finalY = collisionBlock.y + collisionBlock.h; // 上移贴块底 $("#BTB_Hero").css({ "top": finalY + "px", "left": finalX + "px" }); clearInterval(interV); inmotion = false; } else { $("#BTB_Hero").css({ "top": HeroY + "px", "left": HeroX + "px" }); } checkLevelChange(HeroX,HeroY); }, 16); // 改用16ms间隔,接近浏览器60帧刷新率 }
关键说明
- 基于
requestAnimationFrame的时间驱动方案是最优选择,同时解决了步长1时的速度问题与碰撞贴合问题,移动端体验更流畅。 - 碰撞贴合的核心逻辑:不要在碰撞时直接停止,而是根据移动方向将英雄位置修正到与碰撞块边缘完全贴合的坐标。
内容的提问来源于stack exchange,提问作者shailesh Singh
相关产品推荐
相关产品推荐

