🎮ArcadeLab

迷宫逃生 | Maze Escape | Escape del Laberinto

by SuperPhoenix65
439 lines14.9 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>迷宫逃生 | Maze Escape | Escape del Laberinto</title>
    <style>
        *{margin:0;padding:0;box-sizing:border-box}
        body{background:#111;display:flex;justify-content:center;align-items:center;min-height:100vh;color:#fff;font-family:Arial}
        
        /* 左上角语言按钮 */
        #langBtn{
            position:fixed;top:12px;left:12px;z-index:9999;
            padding:6px 10px;font-size:13px;background:#2266cc;color:#fff;
            border:1px solid #fff;border-radius:6px;cursor:pointer;
        }
        /* 语言选择弹窗面板 */
        #langPanel{
            position:fixed;top:50px;left:12px;background:#222;
            border:1px solid #fff;border-radius:6px;padding:10px;
            z-index:9998;display:none;
        }
        .lang-item{
            display:block;width:100%;margin:6px 0;padding:5px 8px;
            background:#444;border:none;color:#fff;border-radius:4px;
            cursor:pointer;text-align:left;
        }
        .lang-item:hover{background:#666;}

        #startScreen{
            position:fixed;z-index:999;
            top:0;left:0;width:100%;height:100%;
            background:#000;display:flex;flex-direction:column;
            justify-content:center;align-items:center;
        }
        #startBtn{
            padding:16px 40px;font-size:22px;background:#09f;color:#fff;
            border:none;border-radius:10px;cursor:pointer;margin-top:20px;
        }

        #gameOverScreen{
            position:fixed;z-index:990;
            top:0;left:0;width:100%;height:100%;
            background:rgba(0,0,0,0.9);display:none;
            flex-direction:column;justify-content:center;align-items:center;
        }
        #winScreen{
            position:fixed;z-index:990;
            top:0;left:0;width:100%;height:100%;
            background:rgba(0,0,0,0.9);display:none;
            flex-direction:column;justify-content:center;align-items:center;
        }
        .restartBtn{
            padding:14px 32px;font-size:18px;background:#f30;color:#fff;
            border:none;border-radius:8px;margin-top:20px;cursor:pointer;
        }

        #viewBox{
            width:640px;height:420px;
            border:3px solid #fff;
            overflow:hidden;
            position:relative;
            background:#3a3a3a;
            display:none;
        }
        #mazeWorld{position:absolute}
        .wall{background:#000;position:absolute}
        .player{width:20px;height:20px;background:#0cf;position:absolute;border-radius:50%;z-index:2}
        .monster{width:24px;height:24px;background:#f33;position:absolute;border-radius:50%;z-index:2}
        .ball{width:14px;height:14px;background:#ffd700;border-radius:50%;position:absolute;box-shadow:0 0 8px gold}
        .fog{position:absolute;top:0;left:0;right:0;bottom:0;pointer-events:none;z-index:10}
        .info{position:absolute;top:10px;left:10px;font-size:20px;z-index:99}

        /* 手机+电脑通用遥感 */
        #joystick{
            position:fixed;bottom:20px;left:20px;
            width:120px;height:120px;background:rgba(255,255,255,0.2);
            border-radius:50%;z-index:100;display:none;cursor:grab;
        }
        #stick{
            position:absolute;width:50px;height:50px;background:#fff;
            border-radius:50%;top:35px;left:35px;cursor:grabbing;
        }
    </style>
</head>
<body>
    <!-- 左上角多语言按钮 -->
    <button id="langBtn">语言 / Lang / Idioma</button>
    <!-- 语言选择面板 -->
    <div id="langPanel">
        <button class="lang-item" data-lang="zh">中文 | Chinese | Chino</button>
        <button class="lang-item" data-lang="en">英文 | English | Inglés</button>
        <button class="lang-item" data-lang="es">西语 | Spanish | Español</button>
    </div>

<!-- 开始界面 -->
<div id="startScreen">
    <h1 id="startTitle">迷宫逃生</h1>
    <p id="startTip">收集金球消灭怪物|电脑WASD/鼠标拖摇杆,手机触屏摇杆</p>
    <button id="startBtn">开始游戏</button>
</div>

<!-- 失败界面 -->
<div id="gameOverScreen">
    <h1 id="loseText">💀 被抓住了</h1>
    <button class="restartBtn" id="restartLose">重新开始</button>
</div>

<!-- 胜利界面 -->
<div id="winScreen">
    <h1 id="winText">🏆 胜利通关</h1>
    <button class="restartBtn" id="restartWin">重新开始</button>
</div>

<!-- 游戏界面 -->
<div class="info"><span id="hpLabel">怪物血量:</span> <span id="hp">7</span></div>
<div id="viewBox">
    <div id="mazeWorld"></div>
    <div class="fog"></div>
</div>

<!-- 手机+电脑遥感 -->
<div id="joystick">
    <div id="stick"></div>
</div>

<script>
// 多语言词典
const langData = {
    zh:{
        title:"迷宫逃生",
        tip:"收集金球消灭怪物|电脑WASD/鼠标拖摇杆,手机触屏摇杆",
        start:"开始游戏",
        hp:"怪物血量:",
        lose:"💀 被抓住了",
        win:"🏆 胜利通关",
        restart:"重新开始"
    },
    en:{
        title:"Maze Escape",
        tip:"Collect gold balls to defeat monster | WASD/Joystick for PC, Touch joystick for mobile",
        start:"Start Game",
        hp:"Monster HP:",
        lose:"💀 Caught",
        win:"🏆 Victory",
        restart:"Restart"
    },
    es:{
        title:"Escape del Laberinto",
        tip:"Recoge oros para derrotar al monstruo | WASD/Mando PC, Mando táctil móvil",
        start:"Empezar",
        hp:"HP Monstruo:",
        lose:"💀 Capturado",
        win:"🏆 Victoria",
        restart:"Reiniciar"
    }
};
let nowLang = "zh";//默认中文

//DOM元素
const langBtn = document.getElementById("langBtn");
const langPanel = document.getElementById("langPanel");
const langItems = document.querySelectorAll(".lang-item");

const startTitle = document.getElementById("startTitle");
const startTip = document.getElementById("startTip");
const startBtn = document.getElementById("startBtn");
const hpLabel = document.getElementById("hpLabel");
const loseText = document.getElementById("loseText");
const winText = document.getElementById("winText");
const restartLose = document.getElementById("restartLose");
const restartWin = document.getElementById("restartWin");

//切换语言函数
function setLang(type){
    nowLang = type;
    const d = langData[type];
    startTitle.textContent = d.title;
    startTip.textContent = d.tip;
    startBtn.textContent = d.start;
    hpLabel.textContent = d.hp;
    loseText.textContent = d.lose;
    winText.textContent = d.win;
    restartLose.textContent = d.restart;
    restartWin.textContent = d.restart;
    langPanel.style.display = "none";
}

//打开关闭语言面板
langBtn.onclick = ()=>{
    langPanel.style.display = langPanel.style.display==="none"?"block":"none";
};
//点击空白关闭面板
document.body.onclick = (e)=>{
    if(!langBtn.contains(e.target) && !langPanel.contains(e.target)){
        langPanel.style.display = "none";
    }
}
//选中语言
langItems.forEach(item=>{
    item.onclick = ()=>setLang(item.dataset.lang);
})

//游戏原有逻辑不变
const gameOverScreen = document.getElementById('gameOverScreen');
const winScreen = document.getElementById('winScreen');
const viewBox = document.getElementById('viewBox');
const joystick = document.getElementById('joystick');
const stick = document.getElementById('stick');
const world = document.getElementById('mazeWorld');
const hpText = document.getElementById('hp');
const fog = document.querySelector('.fog');

const tile = 32;
const mapW = 32, mapH = 24;
let gameStarted = false;
let gameOver = false;
let isMouseDown = false;

const map = [
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[1,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,1],
[1,0,0,0,0,1,0,1,1,1,1,0,1,0,1,1,1,1,0,1,0,1,1,1,1,0,1,0,1,1,0,1],
[1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,1,0,0,1],
[1,0,1,0,1,1,1,1,0,0,1,1,1,1,1,0,0,1,1,1,1,0,0,1,1,1,1,0,1,0,0,1],
[1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1],
[1,1,1,0,1,0,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,0,0,1,0,1,1,1,1,1,1],
[1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1],
[1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,0,1],
[1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,1],
[1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1],
[1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1],
[1,1,1,1,1,0,1,1,1,1,1,1,0,1,0,1,1,1,0,1,1,1,1,1,1,0,1,1,1,1,1,1],
[1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1],
[1,0,1,0,1,1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0,1],
[1,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1],
[1,0,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1],
[1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1],
[1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
];

const player = {x: tile*1.5, y: tile*1.5, size:20, speed:3};
const monster = {x:900, y:600, size:24, speed:1.2, hp:7};
const keys = {w:false,a:false,s:false,d:false};
let balls = [];
let p, m;
let stickMove = {x:0,y:0};

// 开始游戏
startBtn.onclick = () => {
    startScreen.style.display = 'none';
    viewBox.style.display = 'block';
    joystick.style.display = 'block';
    gameStarted = true;
    gameOver = false;
};

// 重新开始
restartLose.onclick = ()=>location.reload();
restartWin.onclick = ()=>location.reload();

// 生成墙体
function initMap(){
    world.style.width = mapW*tile+'px';
    world.style.height = mapH*tile+'px';
    for(let y=0;y<mapH;y++){
        for(let x=0;x<mapW;x++){
            if(map[y][x]===1){
                const e = document.createElement('div');
                e.className='wall';
                e.style.left=x*tile+'px';
                e.style.top=y*tile+'px';
                e.style.width=tile+'px';
                e.style.height=tile+'px';
                world.appendChild(e);
            }
        }
    }
}

// 生成角色
function initPlayer(){
    p = document.createElement('div'); p.className='player'; world.appendChild(p);
    m = document.createElement('div'); m.className='monster'; world.appendChild(m);
}

// 生成金球
function makeBalls(n=16){
    const list = [];
    for(let y=0;y<mapH;y++)for(let x=0;x<mapW;x++)if(map[y][x]===0)list.push({x,y});
    for(let i=0;i<n;i++){
        const o = list.splice(Math.random()*list.length|0,1)[0];
        const e = document.createElement('div'); e.className='ball';
        e.style.left = o.x*tile+9+'px';
        e.style.top = o.y*tile+9+'px';
        world.appendChild(e);
        balls.push({e,x:o.x*tile+9,y:o.y*tile+9});
    }
}

// 碰撞
function canGo(x,y){
    const t = tile;
    const pts = [
        [x+2,y+2],[x+player.size-2,y+2],
        [x+2,y+player.size-2],[x+player.size-2,y+player.size-2]
    ];
    for(const [px,py] of pts){
        const gx = (px/t)|0, gy = (py/t)|0;
        if(gx<0||gy<0||gx>=mapW||gy>=mapH||map[gy][gx])return false;
    }
    return true;
}

// 镜头
function cam(){
    const vw=viewBox.clientWidth,vh=viewBox.clientHeight;
    world.style.transform = `translate(${vw/2-player.x}px,${vh/2-player.y}px)`;
    fog.style.background = `radial-gradient(circle 110px at ${vw/2}px ${vh/2}px,transparent 0,#000 160px)`;
}

// ========== 【触屏+鼠标双适配摇杆代码】 ==========
const joyCenterX = 60;
const joyCenterY = 60;
const maxStickRange = 45;

joystick.ontouchstart = updateJoyPos;
joystick.ontouchmove = updateJoyPos;
joystick.ontouchend = resetJoy;

joystick.onmousedown = (e)=>{
    e.preventDefault();
    isMouseDown = true;
    updateJoyPos({clientX:e.clientX,clientY:e.clientY});
}
document.onmousemove = (e)=>{
    if(!isMouseDown)return;
    updateJoyPos({clientX:e.clientX,clientY:e.clientY});
}
document.onmouseup = ()=>{
    isMouseDown = false;
    resetJoy();
}

function updateJoyPos(e){
    const rect = joystick.getBoundingClientRect();
    let dx = e.clientX - (rect.left+joyCenterX);
    let dy = e.clientY - (rect.top+joyCenterY);
    let dist = Math.hypot(dx,dy);
    if(dist>maxStickRange){
        dx = dx/dist*maxStickRange;
        dy = dy/dist*maxStickRange;
    }
    stickMove.x = dx;
    stickMove.y = dy;
    stick.style.left = joyCenterX+dx+'px';
    stick.style.top = joyCenterY+dy+'px';
}
function resetJoy(){
    stickMove.x=0;stickMove.y=0;
    stick.style.left='35px';stick.style.top='35px';
}

// 键盘
document.addEventListener('keydown',e=>{const k=e.key.toLowerCase();if(k in keys)keys[k]=true});
document.addEventListener('keyup',e=>{const k=e.key.toLowerCase();if(k in keys)keys[k]=false});

// 失败效果
function doGameOver(){
    gameOver = true;
    viewBox.style.filter = 'brightness(0.4) saturate(0)';
    setTimeout(()=>{
        gameOverScreen.style.display = 'flex';
    },300);
}

// 胜利效果
function doWin(){
    gameOver = true;
    winScreen.style.display = 'flex';
}

// 主循环
function loop(){
    if(!gameStarted || gameOver){requestAnimationFrame(loop);return;}
    
    let nx=player.x, ny=player.y;
    const sx = stickMove.x;
    const sy = stickMove.y;
    
    if(keys.a || sx < -18) nx-=player.speed;
    if(keys.d || sx > 18) nx+=player.speed;
    if(keys.w || sy < -18) ny-=player.speed;
    if(keys.s || sy > 18) ny+=player.speed;

    if(canGo(nx,player.y))player.x=nx;
    if(canGo(player.x,ny))player.y=ny;

    // 怪物穿墙追击
    const dx=player.x-monster.x, dy=player.y-monster.y;
    const dist=Math.hypot(dx,dy);
    if(dist>5){
        monster.x += dx/dist*monster.speed;
        monster.y += dy/dist*monster.speed;
    }

    // 拾取金球
    for(let i=balls.length-1;i>=0;i--){
        const b=balls[i];
        if(Math.hypot(player.x-b.x,player.y-b.y)<18){
            monster.hp--; hpText.textContent=monster.hp;
            b.e.remove(); balls.splice(i,1);
            if(monster.hp<=0){doWin();return;}
        }
    }

    // 被怪物抓到
    if(Math.hypot(player.x-monster.x,player.y-monster.y)<18){
        doGameOver();return;
    }

    p.style.left=player.x+'px';p.style.top=player.y+'px';
    m.style.left=monster.x+'px';m.style.top=monster.y+'px';
    cam();
    requestAnimationFrame(loop);
}

initMap();
initPlayer();
makeBalls();
loop();
</script>
</body>
</html>

Game Source: 迷宫逃生 | Maze Escape | Escape del Laberinto

Creator: SuperPhoenix65

Libraries: none

Complexity: complex (439 lines, 14.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: maze-escape-escape-del-laberinto-superphoenix65-mq2bfbjq" to link back to the original. Then publish at arcadelab.ai/publish.