🎮ArcadeLab

西游横版闯关(造梦风格)

by MegaComet18
297 lines9.7 KB
▶ Play
<!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>
<style>
*{box-sizing:border-box;margin:0;padding:0;font-family:system-ui}
body{background:#111;color:#fff;text-align:center;overflow:hidden}
#gameCanvas{background:#222;display:block;margin:0 auto}
.info{position:fixed;top:5px;left:5px;text-align:left;font-size:15px;z-index:99}
#selectBox{position:fixed;inset:0;background:rgba(0,0,0,0.9);display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:10px;z-index:100}
.charSel{padding:12px 20px;font-size:18px;border-radius:8px;border:none}

/* 左半边移动区域 */
.left-panel{position:fixed;bottom:16px;left:16px;display:grid;grid-template-columns:65px 65px;gap:8px;z-index:98}
.move-btn{width:65px;height:65px;border-radius:50%;border:none;font-size:20px;background:#4477ff;color:#fff}

/* 右半边技能攻击区域,竖向排列,分散开 */
.right-panel{position:fixed;bottom:16px;right:16px;display:flex;flex-direction:column;gap:8px;z-index:98}
.action-btn{width:70px;height:52px;border-radius:10px;border:none;font-size:15px;color:#fff}
.atk{background:#ff7722}
.skill{background:#22aa55}
.ult{background:#dd2222}
</style>
</head>
<body>
<div id="selectBox">
<div>选择角色</div>
<button class="charSel" onclick="pick('wukong')">孙悟空</button>
<button class="charSel" onclick="pick('bajie')">猪八戒</button>
<button class="charSel" onclick="pick('shaseng')">沙僧</button>
<button class="charSel" onclick="pick('tangseng')">唐僧</button>
</div>
<div class="info">
<div>关卡:<span id="lvName">火焰山</span></div>
<div>血量:<span id="hp">100</span></div>
<div>技能点:<span id="sp">0</span>/4</div>
<div>BOSS血量:<span id="bossHp">无</span></div>
</div>
<canvas id="gameCanvas"></canvas>

<!-- 左边移动按键 -->
<div class="left-panel">
<button class="move-btn" id="btnLeft">←</button>
<button class="move-btn" id="btnRight">→</button>
<button class="move-btn" id="btnJump">跳</button>
</div>

<!-- 右边竖向排列:普攻、技能1、2、3、大招,分散不挤 -->
<div class="right-panel">
<button class="action-btn atk" id="btnAtk">普攻</button>
<button class="action-btn skill" id="s1">技1</button>
<button class="action-btn skill" id="s2">技2</button>
<button class="action-btn skill" id="s3">技3</button>
<button class="action-btn ult" id="ult" disabled>大招</button>
</div>

<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
canvas.width = 720; canvas.height = 360;

const chars = {
wukong:{name:"孙悟空",hp:100,skill:["金箍棒横扫","筋斗云冲刺","火眼金睛"],ult:"大圣齐天",atk:22},
bajie:{name:"猪八戒",hp:140,skill:["钉耙重击","九齿旋风","贪吃回血"],ult:"天蓬降世",atk:18},
shaseng:{name:"沙僧",hp:125,skill:["降妖杖劈砍","流沙护盾","流沙陷阱"],ult:"黄沙万里",atk:20},
tangseng:{name:"唐僧",hp:85,skill:["紧箍咒","佛光普照","诵经禁锢"],ult:"如来法旨",atk:16}
};
const levels = [
{name:"火焰山",boss:"牛魔王",bossHp:300,monsterCount:6},
{name:"大闹天庭",boss:"二郎神",bossHp:380,monsterCount:8},
{name:"黑暗天庭",boss:"玉帝",bossHp:450,monsterCount:10}
];

let game = {
player:null,char:null,level:0,sp:0,
monsters:[],boss:null,stage:"monster",
keys:{left:false,right:false,jump:false},
gravity:0.6,floorY:280
}

class Player{
    constructor(cfg){
        this.x=80;this.y=200;this.w=40;this.h=50;
        this.vx=0;this.vy=0;this.hp=cfg.hp;this.maxHp=cfg.hp;
        this.atk=cfg.atk;this.onGround=false;
        this.facing=1;
    }
    update(){
        if(game.keys.left){this.vx=-4;this.facing=-1}
        else if(game.keys.right){this.vx=4;this.facing=1}
        else this.vx=0;
        if(game.keys.jump && this.onGround){
            this.vy=-12; game.keys.jump=false;
        }
        this.vy += game.gravity;
        this.x += this.vx;
        this.y += this.vy;
        if(this.x<0)this.x=0;
        if(this.x>canvas.width-this.w)this.x=canvas.width-this.w;
        if(this.y >= game.floorY){
            this.y=game.floorY; this.vy=0; this.onGround=true;
        }else this.onGround=false;
    }
    draw(){
        ctx.fillStyle="#4cf";
        ctx.fillRect(this.x,this.y,this.w,this.h);
    }
}
class Monster{
    constructor(x){
        this.x=x;this.y=game.floorY;this.w=36;this.h=44;
        this.hp=40;this.vx=2;this.dir=1;
    }
    update(){
        this.x += this.vx*this.dir;
        if(this.x<30 || this.x>canvas.width-60) this.dir*=-1;
        if(rectHit(this,game.player)){
            game.player.hp -= 0.3;
        }
    }
    draw(){
        ctx.fillStyle="#f66";
        ctx.fillRect(this.x,this.y,this.w,this.h);
    }
}
class Boss{
    constructor(lv){
        this.x=500;this.y=game.floorY-20;this.w=70;this.h=80;
        this.hp=lv.bossHp;this.maxHp=lv.bossHp;
        this.timer=0;
    }
    update(){
        this.timer++;
        if(this.timer>80){
            this.timer=0;
            if(rectHit(this,game.player)) game.player.hp -=12;
        }
    }
    draw(){
        ctx.fillStyle="#fa0";
        ctx.fillRect(this.x,this.y,this.w,this.h);
        ctx.fillStyle="#333";
        ctx.fillRect(this.x,this.y-12,this.w,6);
        ctx.fillStyle="#f22";
        ctx.fillRect(this.x,this.y-12,this.w*(this.hp/this.maxHp),6);
    }
}
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 spawnMonsters(){
    game.monsters=[];
    const lv = levels[game.level];
    for(let i=0;i<lv.monsterCount;i++){
        game.monsters.push(new Monster(120+i*70));
    }
}
function pick(id){
    game.char = chars[id];
    document.getElementById('selectBox').style.display="none";
    startLevel();
}
function startLevel(){
    const lv = levels[game.level];
    game.player = new Player(game.char);
    game.sp=0;
    game.stage="monster";
    spawnMonsters();
    document.getElementById('lvName').innerText=lv.name;
    loop();
}
function playerAtk(){
    const p = game.player;
    const atkBox = {x:p.facing>0?p.x+p.w:p.x-45,y:p.y,w:45,h:p.h};
    if(game.stage==="monster"){
        for(let m of game.monsters){
            if(rectHit(atkBox,m)){
                m.hp -= p.atk;
                if(m.hp<=0){
                    game.sp++;
                    if(game.sp>4)game.sp=4;
                }
            }
        }
        game.monsters = game.monsters.filter(m=>m.hp>0);
        if(game.monsters.length<=0){
            game.stage="boss";
            game.boss = new Boss(levels[game.level]);
        }
    }else if(game.stage==="boss"){
        if(rectHit(atkBox,game.boss)) game.boss.hp -= p.atk;
        if(game.boss.hp<=0){
            if(game.level>=levels.length-1){
                alert("恭喜通关全部三章!");
                return;
            }
            alert("BOSS击败!进入下一关!");
            game.level++;
            startLevel();
        }
    }
    updateUI();
}
function useSkill(id){
    if(game.sp>=4) return;
    game.sp++;
    if(game.char.name=="猪八戒"&&id===3){
        game.player.hp +=22;
        if(game.player.hp>game.player.maxHp) game.player.hp=game.player.maxHp;
    }else if(game.char.name=="唐僧"&&id===2){
        game.player.hp +=28;
        if(game.player.hp>game.player.maxHp) game.player.hp=game.player.maxHp;
    }else{
        playerAtk();
        playerAtk();
    }
    updateUI();
}
function useUlt(){
    if(game.sp<4)return;
    game.sp=0;
    if(game.stage==="monster"){
        game.monsters.forEach(m=>m.hp-=70);
        game.monsters = game.monsters.filter(m=>m.hp>0);
        if(game.monsters.length<=0){
            game.stage="boss";
            game.boss = new Boss(levels[game.level]);
        }
    }else if(game.stage==="boss"){
        game.boss.hp -=110;
        if(game.boss.hp<=0){
            if(game.level>=levels.length-1){
                alert("恭喜通关全部三章!");
                return;
            }
            alert("BOSS击败!进入下一关!");
            game.level++;
            startLevel();
        }
    }
    updateUI();
}
function updateUI(){
    document.getElementById('hp').innerText=Math.max(0,Math.floor(game.player.hp));
    document.getElementById('sp').innerText=game.sp;
    document.getElementById('ult').disabled = game.sp<4;
    if(game.boss) document.getElementById('bossHp').innerText=Math.max(0,Math.floor(game.boss.hp));
    else document.getElementById('bossHp').innerText="无";
    if(game.player.hp<=0){
        alert("战败!重新从第一关开始");
        game.level=0;
        startLevel();
    }
}
function bindTouch(id,keyName,isSkill=false,isUlt=false,isAtk=false){
    const btn=document.getElementById(id);
    btn.addEventListener('touchstart',e=>{
        e.preventDefault();
        if(isAtk) playerAtk();
        else if(isSkill) useSkill(Number(id.slice(1)));
        else if(isUlt) useUlt();
        else game.keys[keyName]=true;
    })
    btn.addEventListener('touchend',e=>{
        e.preventDefault();
        game.keys[keyName]=false;
    })
}
bindTouch("btnLeft","left");
bindTouch("btnRight","right");
bindTouch("btnJump","jump");
bindTouch("btnAtk","",false,false,true);
bindTouch("s1","",true);
bindTouch("s2","",true);
bindTouch("s3","",true);
bindTouch("ult","",false,true);

function loop(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.fillStyle="#332211";
    ctx.fillRect(0,game.floorY+50,canvas.width,80);
    game.player.update();
    game.player.draw();
    if(game.stage==="monster"){
        game.monsters.forEach(m=>{m.update();m.draw();})
    }else if(game.stage==="boss"){
        game.boss.update();
        game.boss.draw();
    }
    updateUI();
    requestAnimationFrame(loop);
}
</script>
</body>
</html>

Game Source: 西游横版闯关(造梦风格)

Creator: MegaComet18

Libraries: none

Complexity: complex (297 lines, 9.7 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: game-megacomet18-mw7hpcqf" to link back to the original. Then publish at arcadelab.ai/publish.