SOURCE

console 命令行工具 X clear

                    
>
console
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>简易马里奥</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#63a8ff;display:flex;justify-content:center;padding-top:20px;font-family:system-ui}
canvas{border:3px solid #222;background:#63a8ff}
.info{width:800px;margin-bottom:8px;color:#fff}
</style>
</head>
<body>
<div>
<div class="info">⬅️➡️移动 | 空格跳跃 | 吃到金币加分</div>
<canvas id="game" width="800" height="400"></canvas>
</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');

// 马里奥
const mario = {
  x: 50,
  y: 300,
  w: 28,
  h: 32,
  vx: 0,
  vy: 0,
  speed: 4,
  jumpPower: -11,
  gravity: 0.45,
  onGround: false
}

// 地面
const ground = {y:350, h:50}

// 砖块
const bricks = [
  {x:220,y:270,w:40,h:40},
  {x:260,y:270,w:40,h:40},
  {x:300,y:270,w:40,h:40},
  {x:400,y:220,w:40,h:40},
]

// 金币
const coins = [
  {x:240,y:230,w:24,h:24,collected:false},
  {x:420,y:180,w:24,h:24,collected:false},
]

let score = 0
const keys = {}

document.addEventListener('keydown',e=>{
  keys[e.code]=true
  if(e.code==='Space' && mario.onGround){
    mario.vy = mario.jumpPower
    mario.onGround = false
    e.preventDefault()
  }
})
document.addEventListener('keyup',e=>keys[e.code]=false)

function rectCollision(a,b){
  return a.x < b.x+b.w && a.x+a.w > b.x && a.y < b.y+b.h && a.y+a.h > b.y
}

function loop(){
  ctx.clearRect(0,0,canvas.width,canvas.height)

  // 左右移动
  mario.vx = 0
  if(keys['ArrowLeft']) mario.vx = -mario.speed
  if(keys['ArrowRight']) mario.vx = mario.speed
  mario.x += mario.vx

  mario.vy += mario.gravity
  mario.y += mario.vy

  // 边界
  if(mario.x<0) mario.x=0
  if(mario.x + mario.w > canvas.width) mario.x = canvas.width - mario.w

  // 地面碰撞
  mario.onGround = false
  if(mario.y + mario.h >= ground.y){
    mario.y = ground.y - mario.h
    mario.vy = 0
    mario.onGround = true
  }

  // 砖块碰撞
  for(let b of bricks){
    if(rectCollision(mario,b)){
      // 头顶撞砖
      if(mario.vy>0 && mario.y + mario.h - mario.vy <= b.y){
        mario.y = b.y - mario.h
        mario.vy = 0
        mario.onGround = true
      }
    }
  }

  // 金币收集
  for(let c of coins){
    if(!c.collected && rectCollision(mario,c)){
      c.collected = true
      score +=100
    }
  }

  // 绘制地面
  ctx.fillStyle='#9b5c27'
  ctx.fillRect(0,ground.y,canvas.width,ground.h)
  ctx.fillStyle='#58aa38'
  ctx.fillRect(0,ground.y,canvas.width,8)

  // 砖块
  ctx.fillStyle='#c84c28'
  for(let b of bricks){
    ctx.fillRect(b.x,b.y,b.w,b.h)
    ctx.strokeStyle='#882c14'
    ctx.strokeRect(b.x,b.y,b.w,b.h)
  }

  // 金币
  for(let c of coins){
    if(!c.collected){
      ctx.fillStyle='#ffd700'
      ctx.beginPath()
      ctx.arc(c.x+c.w/2,c.y+c.h/2,c.w/2,0,Math.PI*2)
      ctx.fill()
    }
  }

  // 画马里奥(简易色块版)
  ctx.fillStyle='#ff0000'
  ctx.fillRect(mario.x,mario.y,28,12)
  ctx.fillStyle='#ffd2a8'
  ctx.fillRect(mario.x+4,mario.y+10,20,10)
  ctx.fillStyle='#000'
  ctx.fillRect(mario.x+16,mario.y+12,3,3)
  ctx.fillStyle='#0068d8'
  ctx.fillRect(mario.x+4,mario.y+20,20,12)

  // 分数
  ctx.fillStyle='#fff'
  ctx.font='20px bold'
  ctx.fillText(`分数:${score}`,10,25)

  requestAnimationFrame(loop)
}
loop()
</script>
</body>
</html>