SOURCE

console 命令行工具 X clear

                    
>
console
<!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>午夜老宅·逃生</title>
<script src="https://ai.builda.game/sdk/latest/builda-runtime.js"></script>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#000;overflow:hidden;touch-action:none}
canvas{display:block}
#respawnBtn{position:fixed;top:12px;right:12px;background:#992222;color:#fff;border:none;padding:8px 12px;border-radius:6px;font-size:14px;z-index:99;display:none}
#skillBtn{position:fixed;bottom:110px;right:20px;width:60px;height:60px;border-radius:50%;border:#fff 2px solid;background:#222;color:#fff;font-size:13px;z-index:99;display:none}
</style>
</head>
<body>
<button id="respawnBtn">重生</button>
<button id="skillBtn"></button>
<canvas id="c"></canvas>
<script>
Builda.runtime.ready();
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const respawnBtn = document.getElementById('respawnBtn');
const skillBtn = document.getElementById('skillBtn');
let W,H;
function resize(){
    W = window.innerWidth;
    H = window.innerHeight;
    canvas.width = W;
    canvas.height = H;
}
resize();
window.addEventListener('resize',resize);

// 关卡,全部配置安全出生点,第三关拓宽通道
const levels = [
    {
        name:"前厅",
        spawn:{x:70,y:70},
        walls:[
            {x:0,y:0,w:W,h:22},{x:0,y:H-22,w:W,h:22},{x:0,y:0,w:22,h:H},{x:W-22,y:0,w:22,h:H},
            {x:220,y:120,w:140,h:22},{x:420,y:240,w:22,h:180}
        ],
        keys:[{x:340,y:160},{x:600,y:300},{x:160,y:420}],
        door:{x:W-80,y:H-100,w:50,h:70},
        ghosts:[{x:400,y:320,speed:1.1,path:[{x:400,y:320},{x:650,y:320}]}]
    },
    {
        name:"回廊侧屋",
        spawn:{x:70,y:70},
        walls:[
            {x:0,y:0,w:W,h:22},{x:0,y:H-22,w:W,h:22},{x:0,y:0,w:22,h:H},{x:W-22,y:0,w:22,h:H},
            {x:200,y:180,w:22,h:260},{x:400,y:80,w:160,h:22},{x:400,y:300,w:22,h:200}
        ],
        keys:[{x:280,y:360},{x:540,y:140},{x:660,y:420}],
        door:{x:W-80,y:H-100,w:50,h:70},
        ghosts:[
            {x:300,y:220,speed:1,path:[{x:300,y:220},{x:300,y:440}]},
            {x:520,y:360,speed:1.2,path:[{x:520,y:360},{x:700,y:360}]}
        ]
    },
    {
        name:"地下地窖【修复版,加宽通道】",
        spawn:{x:70,y:70},
        walls:[
            {x:0,y:0,w:W,h:22},{x:0,y:H-22,w:W,h:22},{x:0,y:0,w:22,h:H},{x:W-22,y:0,w:22,h:H},
            {x:180,y:120,w:22,h:280},{x:340,y:22,w:22,h:220},{x:340,y:320,w:22,h:160},
            {x:500,y:160,w:22,h:260},{x:500,y:120,w:140,h:22},{x:660,y:240,w:22,h:220}
        ],
        keys:[{x:260,y:380},{x:420,y:140},{x:580,y:420},{x:720,y:140}],
        door:{x:W-80,y:H-100,w:50,h:70},
        ghosts:[
            {x:260,y:220,speed:0.95,path:[{x:260,y:220},{x:260,y:440}]},
            {x:580,y:340,speed:1.1,path:[{x:580,y:340},{x:720,y:340}]}
        ]
    },
    {
        name:"阁楼终章",
        spawn:{x:70,y:70},
        walls:[
            {x:0,y:0,w:W,h:22},{x:0,y:H-22,w:W,h:22},{x:0,y:0,w:22,h:H},{x:W-22,y:0,w:22,h:H},
            {x:160,y:100,w:22,h:300},{x:320,y:200,w:140,h:22},{x:320,y:340,w:22,h:140},
            {x:480,y:80,w:22,h:240},{x:480,y:340,w:140,h:22},{x:640,y:160,w:22,h:280}
        ],
        keys:[{x:240,y:360},{x:400,y:140},{x:560,y:420},{x:720,y:120},{x:720,y:380}],
        door:{x:W-80,y:H-100,w:50,h:70},
        ghosts:[
            {x:240,y:240,speed:1,path:[{x:240,y:240},{x:240,y:440}]},
            {x:400,y:300,speed:1.05,path:[{x:400,y:300},{x:600,y:300}]},
            {x:680,y:360,speed:1.15,path:[{x:680,y:360},{x:680,y:120}]}
        ]
    }
];

// 游戏状态
let state = "menu"; // menu, skillselect, playing, win, lose
let curLevel = 0;
let player = {x:0,y:0,r:14};
let keysCollected = 0;
let selectedSkill = null;
let skillCd = 0;
let skillActive = false;
let skillDur = 0;
let ghosts = [];
let keys = [];
let joystick = {show:false,baseX:0,baseY:0,stickX:0,stickY:0,maxR:55};

// 技能定义
const skillList = [
    {name:"隐匿",cd:40000,dur:3000,desc:"3秒怪物看不见你"},
    {name:"强光",cd:35000,dur:2500,desc:"怪物短暂减速"},
    {name:"感知",cd:45000,dur:4000,desc:"高亮附近钥匙"}
];

// 加载关卡【重点:强制重置出生点!】
function loadLevel(idx){
    const lv = levels[idx];
    player.x = lv.spawn.x;
    player.y = lv.spawn.y;
    keysCollected = 0;
    keys = JSON.parse(JSON.stringify(lv.keys));
    ghosts = JSON.parse(JSON.stringify(lv.ghosts));
    respawnBtn.style.display = "block";
}

// 碰撞检测 + 自动挤出修复卡墙
function rectCircleOverlap(rect,cir){
    let cx = Math.max(rect.x, Math.min(cir.x, rect.x+rect.w));
    let cy = Math.max(rect.y, Math.min(cir.y, rect.y+rect.h));
    let dx = cir.x - cx;
    let dy = cir.y - cy;
    return dx*dx + dy*dy < cir.r*cir.r;
}
function tryMovePlayer(mx,my){
    const spd = 2.4;
    let nx = player.x + mx*spd;
    let ny = player.y + my*spd;
    let okX = true, okY = true;
    for(const w of levels[curLevel].walls){
        if(rectCircleOverlap(w,{...player,x:nx})) okX = false;
        if(rectCircleOverlap(w,{...player,y:ny})) okY = false;
    }
    if(okX) player.x = nx;
    if(okY) player.y = ny;
    // 自动挤出,防止卡墙
    for(const w of levels[curLevel].walls){
        if(rectCircleOverlap(w,player)){
            const midX = w.x + w.w/2;
            const midY = w.y + w.h/2;
            if(player.x < midX) player.x = w.x - player.r - 2;
            else player.x = w.x + w.w + player.r + 2;
            if(player.y < midY) player.y = w.y - player.r - 2;
            else player.y = w.y + w.h + player.r + 2;
        }
    }
}

// 重生按钮
respawnBtn.onclick = ()=>{
    const lv = levels[curLevel];
    player.x = lv.spawn.x;
    player.y = lv.spawn.y;
}
skillBtn.onclick = ()=>{
    if(skillCd>0 || !selectedSkill) return;
    skillCd = selectedSkill.cd;
    skillActive = true;
    skillDur = selectedSkill.dur;
}

// 触摸摇杆
canvas.addEventListener("touchstart",e=>{
    if(state!=="playing") return;
    const t = e.touches[0];
    joystick.baseX = W - 110;
    joystick.baseY = H - 110;
    joystick.stickX = joystick.baseX;
    joystick.stickY = joystick.baseY;
    joystick.show = true;
})
canvas.addEventListener("touchmove",e=>{
    if(!joystick.show || state!=="playing") return;
    e.preventDefault();
    const t = e.touches[0];
    let dx = t.clientX - joystick.baseX;
    let dy = t.clientY - joystick.baseY;
    const dis = Math.hypot(dx,dy);
    if(dis>joystick.maxR){
        dx = dx/dis*joystick.maxR;
        dy = dy/dis*joystick.maxR;
    }
    joystick.stickX = joystick.baseX + dx;
    joystick.stickY = joystick.baseY + dy;
})
canvas.addEventListener("touchend",()=>{
    joystick.show = false;
})

// 主循环
let lastT = 0;
function loop(t){
    const dt = t-lastT;
    lastT = t;
    ctx.fillStyle="#050505";
    ctx.fillRect(0,0,W,H);
    if(state === "menu"){
        ctx.fillStyle="#fff";
        ctx.font="bold 32px system-ui";
        ctx.textAlign="center";
        ctx.fillText("午夜老宅·逃生",W/2,H/2 - 80);
        ctx.font="18px system-ui";
        ctx.fillText("避开黑影,收集钥匙开门逃离",W/2,H/2 - 30);
        // 开始按钮
        const btn = {x:W/2-90,y:H/2+20,w:180,h:50};
        ctx.fillStyle="#334";
        ctx.fillRect(btn.x,btn.y,btn.w,btn.h);
        ctx.fillStyle="#fff";
        ctx.font="22px system-ui";
        ctx.fillText("进入老宅",W/2,btn.y+32);
        canvas.onclick = (ev)=>{
            const x = ev.clientX,y=ev.clientY;
            if(x>btn.x && x<btn.x+btn.w && y>btn.y && y<btn.y+btn.h){
                state = "skillselect";
            }
        }
    }else if(state === "skillselect"){
        ctx.fillStyle="#fff";
        ctx.font="bold 26px system-ui";
        ctx.textAlign="center";
        ctx.fillText("选择本局技能",W/2,70);
        skillList.forEach((sk,i)=>{
            const bx = W/2 - 110;
            const by = 130 + i*90;
            ctx.fillStyle="#223";
            ctx.fillRect(bx,by,220,70);
            ctx.fillStyle="#fff";
            ctx.font="20px system-ui";
            ctx.fillText(sk.name,W/2,by+28);
            ctx.font="14px system-ui";
            ctx.fillText(sk.desc,W/2,by+52);
        })
        canvas.onclick = (ev)=>{
            const x = ev.clientX,y=ev.clientY;
            skillList.forEach((sk,i)=>{
                const bx = W/2 - 110;
                const by = 130 + i*90;
                if(x>bx && x<bx+220 && y>by && y<by+70){
                    selectedSkill = sk;
                    loadLevel(curLevel);
                    skillBtn.innerText = sk.name;
                    skillBtn.style.display = "block";
                    state = "playing";
                }
            })
        }
    }else if(state === "playing"){
        // 摇杆移动计算
        let mx=0,my=0;
        if(joystick.show){
            let dx = joystick.stickX - joystick.baseX;
            let dy = joystick.stickY - joystick.baseY;
            const d = Math.hypot(dx,dy);
            if(d>5){
                mx = dx/d;
                my = dy/d;
            }
        }
        tryMovePlayer(mx,my);
        // 技能计时
        if(skillCd>0) skillCd -= dt;
        if(skillActive){
            skillDur -= dt;
            if(skillDur<=0) skillActive = false;
        }
        // 怪物更新
        const lv = levels[curLevel];
        for(const g of ghosts){
            let sp = g.speed;
            if(skillActive && selectedSkill.name==="强光") sp *=0.4;
            let tar = g.path[g.pidx??0];
            const gd = Math.hypot(tar.x-g.x,tar.y-g.y);
            if(gd<6){
                g.pidx = ((g.pidx??0)+1) % g.path.length;
                tar = g.path[g.pidx];
            }
            const gdx = tar.x-g.x, gdy = tar.y-g.y;
            const gdis = Math.hypot(gdx,gdy);
            g.x += gdx/gdis * sp;
            g.y += gdy/gdis * sp;
            // 抓到玩家判定,隐匿可以规避
            const pdis = Math.hypot(g.x-player.x,g.y-player.y);
            if(pdis < 22 && !(skillActive && selectedSkill.name==="隐匿")){
                state = "lose";
            }
        }
        // 捡钥匙
        for(let i=keys.length-1;i>=0;i--){
            const k = keys[i];
            if(Math.hypot(k.x-player.x,k.y-player.y) < 24){
                keys.splice(i,1);
                keysCollected++;
            }
        }
        // 开门通关
        const door = lv.door;
        if(keysCollected >= lv.keys.length){
            if(player.x>door.x && player.x<door.x+door.w && player.y>door.y && player.y<door.y+door.h){
                curLevel++;
                if(curLevel >= levels.length){
                    state = "win";
                }else{
                    loadLevel(curLevel);
                }
            }
        }
        // 绘制墙
        ctx.fillStyle="#222";
        for(const w of lv.walls) ctx.fillRect(w.x,w.y,w.w,w.h);
        // 钥匙,感知高亮
        for(const k of keys){
            if(skillActive && selectedSkill.name==="感知") ctx.fillStyle="#ffdd44";
            else ctx.fillStyle="#ddaa22";
            ctx.beginPath();
            ctx.arc(k.x,k.y,10,0,Math.PI*2);
            ctx.fill();
        }
        // 门
        if(keysCollected >= lv.keys.length) ctx.fillStyle="#4a6";
        else ctx.fillStyle="#533";
        ctx.fillRect(door.x,door.y,door.w,door.h);
        // 黑影怪物
        for(const g of ghosts){
            ctx.fillStyle="#111";
            ctx.beginPath();
            ctx.arc(g.x,g.y,18,0,Math.PI*2);
            ctx.fill();
        }
        // 玩家
        ctx.fillStyle="#ddeeff";
        ctx.beginPath();
        ctx.arc(player.x,player.y,player.r,0,Math.PI*2);
        ctx.fill();
        // 摇杆绘制
        if(joystick.show){
            ctx.fillStyle="rgba(255,255,255,0.18)";
            ctx.beginPath();
            ctx.arc(joystick.baseX,joystick.baseY,joystick.maxR,0,Math.PI*2);
            ctx.fill();
            ctx.fillStyle="rgba(255,255,255,0.45)";
            ctx.beginPath();
            ctx.arc(joystick.stickX,joystick.stickY,22,0,Math.PI*2);
            ctx.fill();
        }
        // HUD
        ctx.fillStyle="#fff";
        ctx.font="16px system-ui";
        ctx.textAlign="left";
        ctx.fillText(`钥匙:${keysCollected}/${lv.keys.length}`,24,36);
        ctx.fillText(`关卡:${lv.name}`,24,62);
        if(skillCd>0){
            ctx.fillText(`技能冷却:${(skillCd/1000).toFixed(1)}s`,24,88);
        }else{
            ctx.fillText("技能就绪",24,88);
        }
        canvas.onclick = null;
    }else if(state === "win"){
        ctx.fillStyle="#fff";
        ctx.font="bold 32px system-ui";
        ctx.textAlign="center";
        ctx.fillText("恭喜,全部通关!",W/2,H/2-40);
        ctx.font="18px system-ui";
        ctx.fillText("点击返回主菜单",W/2,H/2+10);
        canvas.onclick = ()=>{
            curLevel=0;state="menu";respawnBtn.style.display="none";skillBtn.style.display="none";
        }
    }else if(state === "lose"){
        ctx.fillStyle="#fff";
        ctx.font="bold 32px system-ui";
        ctx.textAlign="center";
        ctx.fillText("被黑影找到了",W/2,H/2-40);
        ctx.font="18px system-ui";
        ctx.fillText("点击返回菜单",W/2,H/2+10);
        canvas.onclick = ()=>{
            curLevel=0;state="menu";respawnBtn.style.display="none";skillBtn.style.display="none";
        }
    }
    requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
</script>
</body>
</html>