🎮ArcadeLab

地下城|5大关×10小关 钥匙开宝箱

by PhantomCaptain66
254 lines6.9 KB
▶ Play
kidhubb.com/publish<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>地下城|5大关×10小关 钥匙开宝箱</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
html,body{
  width:100%;
  height:100%;
  overflow:hidden;
  background:#000;
  touch-action:none;
}
#game{
  background:#111;
  display:block;
  width:100vw;
  height:100vh;
}
.joystick-wrap{
  position:fixed;
  left:30px;
  bottom:30px;
  width:130px;
  height:130px;
  border-radius:50%;
  background:rgba(80,120,200,0.35);
  border:3px solid #3f5a8c;
  z-index:10;
}
.joystick{
  width:54px;
  height:54px;
  border-radius:50%;
  background:#4289e7;
  position:absolute;
  left:38px;top:38px;
}
.kick-btn{
  position:fixed;
  right:36px;
  bottom:44px;
  width:92px;
  height:92px;
  border-radius:50%;
  background:#ff3333;
  color:#fff;
  font-weight:bold;
  font-size:22px;
  border:3px solid #7a0000;
  z-index:10;
}
.shopBtn{
  position:fixed;
  left:36px;
  top:20px;
  padding:8px 14px;
  font-size:16px;
  border-radius:8px;
  background:#22aa22;
  color:white;
  border:none;
  z-index:10;
}
</style>
</head>
<body>
<canvas id="game"></canvas>
<div class="joystick-wrap" id="joyWrap">
  <div class="joystick" id="joy"></div>
</div>
<button class="kick-btn" id="kickBtn">踢</button>
<button class="shopBtn" id="shopBtn">商店</button>

<script>
const canvas = document.getElementById('game')
const ctx = canvas.getContext('2d')

function resize(){
  canvas.width = window.innerWidth
  canvas.height = window.innerHeight
}
resize()
window.addEventListener('resize',resize)

function getGroundY(){
  return canvas.height * 0.82
}

const bigLevelConfig = [
  { monsterCount:1, speed:1.15, hp:3 },
  { monsterCount:1, speed:1.30, hp:3 },
  { monsterCount:2, speed:1.45, hp:4 },
  { monsterCount:2, speed:1.70, hp:4 },
  { monsterCount:2, speed:2.00, hp:5 }
]

let bigIndex = 0
let smallIndex = 0
let monsters = []
let box = {}
let keyItem = {}
let haveKey = false
let platforms = []

const playerBase = {speed:4.8,jump:-17,attack:1}
let addSpeed = 0, addJump = 0, addAttack = 0

const player = {
  x:70,y:240,w:36,h:48,
  gravity:0.72,onGround:true,facing:1,
  attacking:false,attackTimer:0,attackCd:0,
  hp:4,maxHp:4,invincible:0,
  walkFrame:0,walkTimer:0
}

let gold = 0, dead = false, reviveTimer = 0
const reviveWait = 80
let allClear = false, shopOpen = false

function getPlayerStat(){
  return {
    speed: playerBase.speed + addSpeed,
    jumpPower: playerBase.jump + addJump,
    attackPower: playerBase.attack + addAttack
  }
}

function loadStage(){
  const cfg = bigLevelConfig[bigIndex]
  const groundY = getGroundY()
  monsters = []
  platforms = []
  const pCount = 2 + Math.floor(Math.random()*2)
  for(let i=0;i<pCount;i++){
    const px = canvas.width * 0.15 + Math.random()*canvas.width*0.6
    const py = canvas.height * 0.55 + Math.random()*canvas.height*0.15
    const pw = 70 + Math.random()*50
    platforms.push({x:px,y:py,w:pw,h:16})
  }
  const targetP = platforms[Math.floor(Math.random()*platforms.length)]
  keyItem = {x:targetP.x+10, y:targetP.y-30, w:24,h:24}
  haveKey = false

  for(let i=0;i<cfg.monsterCount;i++){
    const mx = canvas.width * 0.22 + i * canvas.width * 0.32
    const dir = i%2===0 ? 1 : -1
    monsters.push({
      x:mx, y:groundY-44, w:40,h:44,
      vx: cfg.speed * dir,
      minX: Math.max(canvas.width*0.08, mx - 80),
      maxX: Math.min(canvas.width*0.92, mx + 80),
      hp:cfg.hp, flash:0, maxHp:cfg.hp
    })
  }
  box = {x:canvas.width*0.85,y:groundY-40,w:40,h:40}
  player.x=canvas.width*0.04;player.y=groundY-48;player.vx=0;player.vy=0
  player.hp=player.maxHp;player.invincible=0;player.walkFrame=0;player.walkTimer=0
  dead=false;reviveTimer=0
}
loadStage()

const joyWrap = document.getElementById('joyWrap')
const joy = document.getElementById('joy')
const kickBtn = document.getElementById('kickBtn')
const shopBtn = document.getElementById('shopBtn')
const joyR = 40
let joyDx=0,joyDy=0,touchingJoy=false

shopBtn.onclick = ()=>{shopOpen = !shopOpen}
function updateJoystick(clientX, clientY){
  if(dead || shopOpen) return
  const stat = getPlayerStat()
  const rect = joyWrap.getBoundingClientRect()
  const x = clientX - rect.left
  const y = clientY - rect.top
  let dx = x - 65, dy = y - 65
  const dist = Math.hypot(dx,dy)
  if(dist>joyR){dx=dx/dist*joyR;dy=dy/dist*joyR}
  joy.style.left=(65+dx-27)+"px"
  joy.style.top=(65+dy-27)+"px"
  joyDx=dx;joyDy=dy
  if(joyDx>8) player.facing=1
  if(joyDx<-8) player.facing=-1
  if(dy<-22 && player.onGround){
    player.vy=stat.jumpPower
    player.onGround=false
  }
}
function resetJoy(){joy.style.left="38px";joy.style.top="38px";joyDx=0;joyDy=0}
joyWrap.addEventListener('touchstart',e=>{touchingJoy=true;updateJoystick(e.touches[0].clientX,e.touches[0].clientY)})
joyWrap.addEventListener('touchmove',e=>{e.preventDefault();if(!touchingJoy)return;updateJoystick(e.touches[0].clientX,e.touches[0].clientY)})
joyWrap.addEventListener('touchend',()=>{touchingJoy=false;resetJoy()})
joyWrap.addEventListener('mousedown',e=>{touchingJoy=true;updateJoystick(e.clientX,e.clientY)})
window.addEventListener('mousemove',e=>{if(!touchingJoy)return;updateJoystick(e.clientX,e.clientY)})
window.addEventListener('mouseup',()=>{touchingJoy=false;resetJoy()})

function kick(){
  if(player.attackCd>0 || dead || shopOpen) return
  const stat = getPlayerStat()
  player.attacking=true;player.attackTimer=14;player.attackCd=22
  const attackRange = {
    x: player.facing===1 ? player.x+player.w : player.x-44,
    y:player.y+8,w:44,h:34
  }
  for(let m of monsters){
    if(m.hp<=0) continue
    if(attackRange.x<m.x+m.w && attackRange.x+attackRange.w>m.x && attackRange.y<m.y+m.h && attackRange.y+attackRange.h>m.y){
      m.hp -= stat.attackPower
      m.flash=12
      m.vx=-m.vx
    }
  }
}
kickBtn.addEventListener('touchstart',e=>{e.preventDefault();kick()})
kickBtn.addEventListener('mousedown',kick)

function rectHit(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 drawBackground(){
  const groundY = getGroundY()
  ctx.fillStyle="#221133"
  ctx.fillRect(0,0,canvas.width,groundY)
  ctx.fillStyle="#110820"
  ctx.fillRect(0,groundY,canvas.width,canvas.height-groundY)
}

function drawPlayer(){
  const px=player.x, py=player.y
  if(player.invincible>0 && Math.floor(player.invincible/4)%2===0) return
  ctx.save()
  if(player.facing<0){
    ctx.translate(px+player.w/2,0)
    ctx.scale(-1,1)
    ctx.translate(-(px+player.w/2),0)
  }
  ctx.fillStyle="#1368c7"
  ctx.fillRect(px+8,py+14,20,24)
  ctx.fillStyle="#ffd9a8"
  ctx.fillRect(px+10,py,16,16)
  ctx.fillStyle="#111"
  ctx.fillRect(px+20,py+4,4,4)

  const leftLeg={x:px+10,y:py+38,w:7,h:10}
  const rightLeg={x:px+19,y:py+38,w:7,h:10}
  ctx.fillStyle="#2f7bd1"
  if(!player.onGround){
    ctx.fillRect(leftLeg.x+1,leftLeg.y-2,leftLeg.w,8)
    ctx.fillRect(rightLeg.x+1,rightLeg.y-2,rightLeg.w,8)
  }else if(Math.abs(play

Game Source: 地下城|5大关×10小关 钥匙开宝箱

Creator: PhantomCaptain66

Libraries: none

Complexity: complex (254 lines, 6.9 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: 5-10-phantomcaptain66" to link back to the original. Then publish at arcadelab.ai/publish.