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>