You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为JS、HTML、CSS俯视角2D游戏添加墙体碰撞检测

为2D俯视角JS游戏添加墙体碰撞检测

我开发了一款基于JS、HTML、CSS实现的小型2D俯视角游戏,希望为其添加可与玩家产生碰撞的墙体/障碍物(先实现少量方块即可,掌握方法后再扩展其他墙体)。游戏操作说明可查看代码片段底部内容,原游戏代码如下:

原HTML代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" type="text/css" href="style.css">
    <meta charset="utf-8" />
<canvas id = "mycan" > </canvas>
    <p id="info"></p>
    <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>
   
   <h2>
   Direction:
   Player1(Blue), is controlled by the WASD keys, and player2(red), is controlled by the arrow keys. The objecitve of the game is to stop in front
   of the other player, and let them run into you, if they run into you, then they die, at the same time you have avod running into the other
   player.Good luck, and have fun.
   </h2>
  </body>
</html>

原CSS代码

html, body {
    margin: 20;
    padding: 10;
  }
  canvas {
    display: block;
  }
  
  #mycan {
    background-size: 30px 30px;
    background-image:
      linear-gradient(to right, black 1px, transparent 1px),
      linear-gradient(to bottom, black 1px, rgb(136, 136, 136) 1px);
  
  }

  body {
    background-color: grey;
  }

原JS代码

//Canvas
mycan.style.display = "block";
mycan.height = 600;
mycan.width = 600;
//make players
let player = {x:510, y: 541, w:30, h:30};
let player2 = {x:60, y:31, w:30, h:30};

//Context
const ctx = mycan.getContext("2d");

const moveHandler = (isPlayer1) => {
    if (player.x == player2.x && player.y == player2.y) {
        document.getElementById('info').textContent = isPlayer1 ? 'Player1 killed player2' : 'Player2 killed player1'
    }
}

//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.key;
    //player1(red)
    switch (key) {
        case 'w':
            player2.y -= 30;
            moveHandler(false);
            break;
        case 'a':
            player2.x -= 30;
            moveHandler(false);
            break;
        case 's':
            player2.y += 30;
            moveHandler(false);
            break;
        case 'd':
            player2.x += 30;
            moveHandler(false);
            break;
        case 'ArrowRight':
            player.x += 30;
            moveHandler(true);
            break;
        case 'ArrowLeft':
            player.x -= 30;
            moveHandler(true);
            break;
        case 'ArrowDown':
            player.y += 30;
            moveHandler(true);
            break;
        case 'ArrowUp':
            player.y -= 30;
            moveHandler(true);
            break;
    }
}

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);

实现墙体碰撞的步骤与修改后的代码

核心思路

  1. 定义墙体数组,存储每个墙体的位置和尺寸
  2. 编写轴对齐矩形碰撞检测函数,判断玩家移动后的位置是否与墙体重叠
  3. 在玩家移动前预判目标位置,若碰撞则取消本次移动

修改后的完整JS代码

//Canvas
mycan.style.display = "block";
mycan.height = 600;
mycan.width = 600;
//make players
let player = {x:510, y: 541, w:30, h:30};
let player2 = {x:60, y:31, w:30, h:30};

// 定义墙体数组,每个元素是{x, y, w, h},可按需扩展
const walls = [
  {x: 150, y: 150, w: 30, h: 120}, // 垂直墙体
  {x: 300, y: 300, w: 120, h: 30}, // 水平墙体
  {x: 450, y: 150, w: 30, h: 30}   // 小方块墙体
];

//Context
const ctx = mycan.getContext("2d");

// 碰撞检测函数:判断两个轴对齐矩形是否重叠
function isColliding(rect1, rect2) {
  return rect1.x < rect2.x + rect2.w &&
         rect1.x + rect1.w > rect2.x &&
         rect1.y < rect2.y + rect2.h &&
         rect1.y + rect1.h > rect2.y;
}

// 检查玩家移动后的位置是否与任意墙体碰撞
function willCollideWithWall(newPos) {
  return walls.some(wall => isColliding(newPos, wall));
}

const moveHandler = (isPlayer1) => {
    if (player.x == player2.x && player.y == player2.y) {
        document.getElementById('info').textContent = isPlayer1 ? 'Player1 killed player2' : 'Player2 killed player1'
    }
}

//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.key;
    let tempPos;
    
    switch (key) {
        // Player2(蓝色)控制逻辑
        case 'w':
            tempPos = {...player2, y: player2.y - 30};
            if (!willCollideWithWall(tempPos)) {
              player2.y -= 30;
              moveHandler(false);
            }
            break;
        case 'a':
            tempPos = {...player2, x: player2.x - 30};
            if (!willCollideWithWall(tempPos)) {
              player2.x -= 30;
              moveHandler(false);
            }
            break;
        case 's':
            tempPos = {...player2, y: player2.y + 30};
            if (!willCollideWithWall(tempPos)) {
              player2.y += 30;
              moveHandler(false);
            }
            break;
        case 'd':
            tempPos = {...player2, x: player2.x + 30};
            if (!willCollideWithWall(tempPos)) {
              player2.x += 30;
              moveHandler(false);
            }
            break;
        // Player1(红色)控制逻辑
        case 'ArrowRight':
            tempPos = {...player, x: player.x + 30};
            if (!willCollideWithWall(tempPos)) {
              player.x += 30;
              moveHandler(true);
            }
            break;
        case 'ArrowLeft':
            tempPos = {...player, x: player.x - 30};
            if (!willCollideWithWall(tempPos)) {
              player.x -= 30;
              moveHandler(true);
            }
            break;
        case 'ArrowDown':
            tempPos = {...player, y: player.y + 30};
            if (!willCollideWithWall(tempPos)) {
              player.y += 30;
              moveHandler(true);
            }
            break;
        case 'ArrowUp':
            tempPos = {...player, y: player.y - 30};
            if (!willCollideWithWall(tempPos)) {
              player.y -= 30;
              moveHandler(true);
            }
            break;
    }
}

const draw = ()=>{
  ctx.clearRect(0,0, mycan.width, mycan.height);
  
  // 绘制墙体
  ctx.fillStyle = '#333';
  walls.forEach(wall => {
    ctx.fillRect(wall.x, wall.y, wall.w, wall.h);
  });
  
  // 绘制玩家
  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);
};

setInterval(()=>{
  draw();
},1000/60);

关键修改点说明

  • 墙体定义:新增walls数组,每个墙体对象包含位置和尺寸,后续只需向数组添加新元素即可扩展墙体
  • 碰撞检测逻辑:isColliding使用行业标准的轴对齐矩形碰撞公式,确保检测准确性
  • 移动预判机制:每次按键时先创建临时位置对象,通过willCollideWithWall检查是否与墙体碰撞,只有不碰撞才执行移动
  • 墙体绘制:在draw函数中遍历墙体数组,统一绘制所有墙体,保证画面更新同步

内容的提问来源于stack exchange,提问作者Jude Mauldin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 04:40:34