🎮ArcadeLab

平面2D射击(机器人AI会射击+障碍物)

by SparkShark50
289 lines8.4 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0,user-scalable=no">
<title>平面机器人射击</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;font-family:system-ui,sans-serif}
body{background:#111;overflow:hidden}
#game{width:100vw;height:100vh;background:#222;position:relative;overflow:hidden}
.player{width:44px;height:44px;background:#42a5f5;border-radius:6px;position:absolute}
.robot{width:44px;height:44px;background:#ef5350;border-radius:6px;position:absolute}
.wall{background:#668;border-radius:3px;position:absolute}
.bulletPlayer{width:6px;height:14px;background:#ffd600;border-radius:3px;position:absolute}
.bulletRobot{width:6px;height:14px;background:#ff2222;border-radius:3px;position:absolute}
#control{position:fixed;bottom:12px;left:0;right:0;display:flex;justify-content:space-between;padding:0 20px;z-index:6}
.btn{width:70px;height:70px;border-radius:50%;font-size:24px;background:#ffffffcc;border:none;margin:3px}
.shootBtn{background:#ff4444cc;color:white}
.scoreText{position:fixed;top:14px;left:14px;color:#fff;font-size:22px;font-weight:bold;z-index:6}
#gameOverBox{position:fixed;width:100vw;height:100vh;background:rgba(0,0,0,0.75);display:none;align-items:center;justify-content:center;z-index:10}
.panel{background:white;padding:28px;border-radius:12px;text-align:center}
</style>
</head>
<body>
<div id="game">
  <div class="scoreText">分数:0</div>
  <div class="player" id="player"></div>
  <div id="gameOverBox">
    <div class="panel">
      <h2>游戏结束</h2>
      <p id="finalScore"></p>
      <br>
      <button class="btn" onclick="restartGame()">重新开始</button>
    </div>
  </div>
</div>
<div id="control">
  <div>
    <button class="btn" id="leftBtn">←</button>
    <button class="btn" id="rightBtn">→</button>
    <button class="btn" id="upBtn">↑</button>
    <button class="btn" id="downBtn">↓</button>
  </div>
  <button class="btn shootBtn" id="shootBtn">射击</button>
</div>

<script>
const game = document.getElementById("game");
const player = document.getElementById("player");
const gameOverBox = document.getElementById("gameOverBox");
const finalScoreText = document.getElementById("finalScore");
const scoreText = document.querySelector(".scoreText");

let px = 80, py = 200;
const pSpeed = 4.2;
let score = 0;
let gameRunning = true;

let leftHold=false,rightHold=false,upHold=false,downHold=false;
let playerBullets = [];
let robotBullets = [];
let robots = [];
let walls = [];

// 地图障碍物墙
const wallData = [
  {x:220,y:120,w:240,h:22},
  {x:120,y:260,w:22,h:180},
  {x:420,y:180,w:220,h:22},
  {x:520,y:300,w:22,h:160}
]
// 机器人位置
const robotData = [
  {x:400,y:120,shootTimer:0},
  {x:600,y:320,shootTimer:0}
]

// 生成墙
wallData.forEach(w=>{
  const div=document.createElement("div");
  div.className="wall";
  div.style.left=w.x+"px";
  div.style.top=w.y+"px";
  div.style.width=w.w+"px";
  div.style.height=w.h+"px";
  game.appendChild(div);
  walls.push({el:div,...w});
})

//生成机器人
robotData.forEach(r=>{
  const div=document.createElement("div");
  div.className="robot";
  div.style.left=r.x+"px";
  div.style.top=r.y+"px";
  game.appendChild(div);
  robots.push({el:div,x:r.x,y:r.y,shootTimer:r.shootTimer});
})

//触屏
document.getElementById("leftBtn").ontouchstart=()=>leftHold=true;
document.getElementById("leftBtn").ontouchend=()=>leftHold=false;
document.getElementById("rightBtn").ontouchstart=()=>rightHold=true;
document.getElementById("rightBtn").ontouchend=()=>rightHold=false;
document.getElementById("upBtn").ontouchstart=()=>upHold=true;
document.getElementById("upBtn").ontouchend=()=>upHold=false;
document.getElementById("downBtn").ontouchstart=()=>downHold=true;
document.getElementById("downBtn").ontouchend=()=>downHold=false;
document.getElementById("shootBtn").ontouchstart=shootPlayerBullet;

//电脑键盘WASD+空格射击
document.addEventListener("keydown",e=>{
  if(e.code==="KeyA")leftHold=true;
  if(e.code==="KeyD")rightHold=true;
  if(e.code==="KeyW")upHold=true;
  if(e.code==="KeyS")downHold=true;
  if(e.code==="Space")shootPlayerBullet();
})
document.addEventListener("keyup",e=>{
  if(e.code==="KeyA")leftHold=false;
  if(e.code==="KeyD")rightHold=false;
  if(e.code==="KeyW")upHold=false;
  if(e.code==="KeyS")downHold=false;
})

//玩家发射子弹向右
function shootPlayerBullet(){
  if(!gameRunning)return;
  const b=document.createElement("div");
  b.className="bulletPlayer";
  const bx=px+44;
  const by=py+18;
  b.style.left=bx+"px";
  b.style.top=by+"px";
  game.appendChild(b);
  playerBullets.push({el:b,x:bx,y:by,speed:8});
}

//机器人射子弹(射向玩家)
function robotShoot(robot){
  const b=document.createElement("div");
  b.className="bulletRobot";
  const bx=robot.x;
  const by=robot.y+18;
  b.style.left=bx+"px";
  b.style.top=by+"px";
  game.appendChild(b);
  robotBullets.push({el:b,x:bx,y:by,speedX:0,speedY:0,targetX:px,targetY:py});
}

//碰撞检测
function isHit(ax,aw,ay,ah,bx,bw,by,bh){
  return ax < bx+bw && ax+aw > bx && ay < by+bh && ay+ah > by;
}

//游戏主循环
function gameLoop(){
  if(!gameRunning)return;
  //玩家移动+撞墙检测
  let nx=px, ny=py;
  if(leftHold)nx -= pSpeed;
  if(rightHold)nx += pSpeed;
  if(upHold)ny -= pSpeed;
  if(downHold)ny += pSpeed;
  let canMove=true;
  for(let w of walls){
    if(isHit(nx,44,ny,44,w.x,w.w,w.y,w.h)){canMove=false;break;}
  }
  if(canMove){px=nx;py=ny;}
  //边界
  if(px<0)px=0;
  if(py<0)py=0;
  if(px+44>window.innerWidth)px=window.innerWidth-44;
  if(py+44>window.innerHeight)py=window.innerHeight-44;
  player.style.left=px+"px";
  player.style.top=py+"px";

  //玩家子弹更新
  for(let i=playerBullets.length-1;i>=0;i--){
    let blt=playerBullets[i];
    blt.x += blt.speed;
    blt.el.style.left=blt.x+"px";
    //撞墙移除
    let hitWall=false;
    for(let w of walls){
      if(isHit(blt.x,6,blt.y,14,w.x,w.w,w.y,w.h)){hitWall=true;break;}
    }
    if(hitWall || blt.x>window.innerWidth){
      blt.el.remove();
      playerBullets.splice(i,1);
      continue;
    }
    //打中机器人
    for(let ri=robots.length-1;ri>=0;ri--){
      let r=robots[ri];
      if(isHit(blt.x,6,blt.y,14,r.x,44,r.y,44)){
        blt.el.remove();
        playerBullets.splice(i,1);
        r.el.remove();
        robots.splice(ri,1);
        score +=10;
        scoreText.innerText="分数:"+score;
        break;
      }
    }
  }

  //机器人AI,每隔一段时间开枪
  robots.forEach(r=>{
    r.shootTimer++;
    if(r.shootTimer>90){
      robotShoot(r);
      r.shootTimer=0;
    }
  })

  //机器人子弹追踪玩家
  for(let i=robotBullets.length-1;i>=0;i--){
    let blt=robotBullets[i];
    const dx = px - blt.x;
    const dy = py - blt.y;
    const dist = Math.sqrt(dx*dx+dy*dy);
    if(dist>0){
      blt.x += (dx/dist)*3;
      blt.y += (dy/dist)*3;
    }
    blt.el.style.left=blt.x+"px";
    blt.el.style.top=blt.y+"px";
    //撞墙消失
    let hitWall=false;
    for(let w of walls){
      if(isHit(blt.x,6,blt.y,14,w.x,w.w,w.y,w.h)){hitWall=true;break;}
    }
    if(hitWall || blt.x<0||blt.y<0||blt.x>window.innerWidth||blt.y>window.innerHeight){
      blt.el.remove();
      robotBullets.splice(i,1);
      continue;
    }
    //打中玩家
    if(isHit(blt.x,6,blt.y,14,px,44,py,44)){
      gameOver();
      return;
    }
  }

  requestAnimationFrame(gameLoop);
}

function gameOver(){
  gameRunning=false;
  finalScoreText.innerText="得分:"+score;
  gameOverBox.style.display="flex";
}
function restartGame(){
  //清空子弹、机器人、墙
  playerBullets.forEach(b=>b.el.remove());
  robotBullets.forEach(b=>b.el.remove());
  robots.forEach(r=>r.el.remove());
  walls.forEach(w=>w.el.remove());
  playerBullets=[];robotBullets=[];robots=[];walls=[];
  px=80;py=200;score=0;
  scoreText.innerText="分数:0";
  gameRunning=true;
  gameOverBox.style.display="none";
  //重建地图
  wallData.forEach(w=>{
    const div=document.createElement("div");
    div.className="wall";
    div.style.left=w.x+"px";
    div.style.top=w.y+"px";
    div.style.width=w.w+"px";
    div.style.height=w.h+"px";
    game.appendChild(div);
    walls.push({el:div,...w});
  })
  robotData.forEach(r=>{
    const div=document.createElement("div");
    div.className="robot";
    div.style.left=r.x+"px";
    div.style.top=r.y+"px";
    game.appendChild(div);
    robots.push({el:div,x:r.x,y:r.y,shootTimer:r.shootTimer});
  })
  gameLoop();
}
gameLoop();
</script>
</body>
</html>

Game Source: 平面2D射击(机器人AI会射击+障碍物)

Creator: SparkShark50

Libraries: none

Complexity: complex (289 lines, 8.4 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: 2d-ai-sparkshark50" to link back to the original. Then publish at arcadelab.ai/publish.