🎮ArcadeLab

飞机大战 - BOSS激光优化版

by MegaFlare60
503 lines16.0 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>飞机大战 - BOSS激光优化版</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;}
body{overflow:hidden;background:#000;}
#gameCanvas{display:block;}
#touchArea{position:fixed;top:0;left:0;width:100%;height:100%;z-index:3;}
.btn-group{position:fixed;bottom:20;left:0;width:100%;display:flex;justify-content:center;gap:20px;z-index:10;}
button{padding:10px 20px;font-size:16px;border:none;border-radius:8px;background:#444;color:#ccc;}
button.unlocked{background:#28a;color:#fff;}
#bossWarning{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);font-size:32px;color:red;display:none;z-index:20;}
#talentPopup,#gameOverPopup{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);display:none;z-index:30;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:20px;}
.talent{padding:12px 24px;background:#2288dd;color:#fff;border-radius:8px;}
.info{position:fixed;top:10;left:10;color:#fff;font-size:18px;z-index:15;}
</style>
</head>
<body>
<div class="info">分数:<span id="score">0</span>|生命:<span id="lives">3</span></div>
<div id="bossWarning">⚠️ BOSS 来袭 ⚠️</div>
<div id="talentPopup">
    <h2 style="color:#fff">选择天赋</h2>
    <div id="talentContainer" style="display:flex;gap:15px;"></div>
</div>
<div id="gameOverPopup">
    <h2 style="color:red">游戏结束</h2>
    <p style="color:#fff">最终分数:<span id="finalScore">0</span></p>
    <button onclick="restartGame()">重新开始</button>
</div>
<div class="btn-group">
    <button id="dodgeBtn">闪避 (未解锁)</button>
    <button id="nukeBtn">核弹 (未解锁)</button>
</div>
<canvas id="gameCanvas"></canvas>
<div id="touchArea"></div>

<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const touchArea = document.getElementById('touchArea');
const dodgeBtn = document.getElementById('dodgeBtn');
const nukeBtn = document.getElementById('nukeBtn');

let width, height;
let isTouching = false;
let lasers = [];
let laserWarnings = [];

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

let game = {
    player: { 
        x: 0, y: 0, w: 50, h: 50, 
        speed: 5,
        bulletSpeed: 10,
        fireRate: 300,
        isDodging: false
    },
    bullets: [],
    enemyBullets: [],
    enemies: [],
    boss: null,
    score: 0,
    lives: 3,
    lastScore: 0,
    skills: {
        dodge: { unlocked: false, cd: 0, cooldown: 5000 },
        nuke: { unlocked: false, cd: 0, cooldown: 15000 }
    },
    isGameOver: false,
    lastEnemySpawn: 0,
    spawnRate: 1000,
    bossSpawned: false,
    lastShot: 0,
    laserDamageActive: false,
    laserHitCount: 0,
    maxLaserHits: 1
};

const talents = [
    { type: 'speed', desc: '移动速度+2', value: 2 },
    { type: 'bulletSpeed', desc: '子弹速度+3', value: 3 },
    { type: 'fireRate', desc: '射速提升10%', value: 0.1 },
    { type: 'unlockDodge', desc: '解锁闪避技能', value: true },
    { type: 'unlockNuke', desc: '解锁核弹技能', value: true },
    { type: 'dodgeCD', desc: '闪避冷却-1秒', value: -1000 },
    { type: 'nukeCD', desc: '核弹冷却-3秒', value: -3000 },
    { type: 'lives', desc: '生命+1', value: 1 }
];

function checkOverlap(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 initPlayer() {
    game.player.x = width / 2 - game.player.w / 2;
    game.player.y = height - game.player.h - 50;
    game.lastShot = Date.now();
}

function drawPlayer() {
    ctx.save();
    if (game.player.isDodging) ctx.globalAlpha = 0.5;
    ctx.fillStyle = '#4af';
    ctx.beginPath();
    ctx.moveTo(game.player.x + game.player.w / 2, game.player.y);
    ctx.lineTo(game.player.x, game.player.y + game.player.h);
    ctx.lineTo(game.player.x + game.player.w, game.player.y + game.player.h);
    ctx.closePath();
    ctx.fill();
    ctx.restore();
}

touchArea.addEventListener('touchstart', e=>{
    e.preventDefault();
    isTouching = true;
    updatePlayerPosition(e.touches[0]);
});
touchArea.addEventListener('touchmove', e=>{
    e.preventDefault();
    if(isTouching) updatePlayerPosition(e.touches[0]);
});
touchArea.addEventListener('touchend', ()=>isTouching = false);

function updatePlayerPosition(touch) {
    const rect = canvas.getBoundingClientRect();
    let tx = touch.clientX - rect.left;
    let ty = touch.clientY - rect.top;
    tx = Math.max(game.player.w/2, Math.min(width - game.player.w/2, tx));
    ty = Math.max(game.player.h/2, Math.min(height - game.player.h/2, ty));
    game.player.x = tx - game.player.w / 2;
    game.player.y = ty - game.player.h / 2;
}

function shoot() {
    if(Date.now() - game.lastShot > game.player.fireRate){
        game.bullets.push({
            x: game.player.x + game.player.w/2 - 2,
            y: game.player.y,
            w:4,h:15,
            speed:game.player.bulletSpeed
        });
        game.lastShot = Date.now();
    }
}

function drawBullets() {
    ctx.fillStyle = '#fff';
    game.bullets.forEach((b,i)=>{
        b.y -= b.speed;
        ctx.fillRect(b.x,b.y,b.w,b.h);
        if(b.y<0) game.bullets.splice(i,1);
    });
}

function spawnEnemy() {
    if(Date.now()-game.lastEnemySpawn < game.spawnRate || game.boss) return;
    game.lastEnemySpawn = Date.now();
    const canShoot = Math.random() < 0.2;
    game.enemies.push({
        x:Math.random()*(width-40),
        y:-50,w:40,h:40,
        speed:2+Math.random()*2,
        hp:1,canShoot,lastShot:Date.now(),
        shotInterval:3000+Math.random()*2000
    });
    if(game.score>=1500 && !game.bossSpawned){
        game.bossSpawned = true;
        document.getElementById('bossWarning').style.display='flex';
        setTimeout(()=>{
            document.getElementById('bossWarning').style.display='none';
            spawnBoss();
        },1500);
    }
}

function spawnBoss() {
    game.boss = {
        x:width/2-80,y:50,w:160,h:120,
        speed:2,hp:60,lastShot:0,
        bulletInterval:800,laserInterval:5000,
        lastLaser:0,direction:1
    };
}

function enemyShoot(enemy) {
    if(Date.now()-enemy.lastShot < enemy.shotInterval) return;
    enemy.lastShot = Date.now();
    const angle = (Math.random()-0.5)*0.8;
    game.enemyBullets.push({
        x:enemy.x+enemy.w/2-3,
        y:enemy.y+enemy.h,
        w:6,h:12,speed:4,
        vx:Math.sin(angle)*3,
        vy:Math.cos(angle)*4
    });
}

function createLaserWarning(x,y,w,h){
    let d = document.createElement('div');
    d.style.cssText = `position:absolute;left:${x}px;top:${y}px;width:${w}px;height:${h}px;background:rgba(255,0,0,0.3);z-index:4;`;
    document.body.appendChild(d);
    laserWarnings.push(d);
    return d;
}

function clearLaserWarnings(){
    laserWarnings.forEach(d=>d.parentNode&&d.remove());
    laserWarnings = [];
}

function bossAttack() {
    if(!game.boss) return;
    if(Date.now()-game.boss.lastShot > game.boss.bulletInterval){
        game.boss.lastShot = Date.now();
        for(let i=0;i<7;i++){
            let ang = (i-3)*0.15;
            game.enemyBullets.push({
                x:game.boss.x+game.boss.w/2-3,
                y:game.boss.y+game.boss.h,
                w:6,h:12,speed:5,
                vx:Math.sin(ang)*4,
                vy:Math.cos(ang)*5
            });
        }
    }

    if(Date.now()-game.boss.lastLaser > game.boss.laserInterval){
        game.boss.lastLaser = Date.now();
        game.laserHitCount = 0;
        let lc = 3,lw=4,lh=height-game.boss.y;
        for(let i=0;i<lc;i++){
            let lx = game.boss.x + (i*game.boss.w)/(lc-1);
            createLaserWarning(lx - lw/2, game.boss.y, lw, lh);
        }
        setTimeout(()=>{
            clearLaserWarnings();
            for(let i=0;i<lc;i++){
                let lx = game.boss.x + (i*game.boss.w)/(lc-1);
                let ld = document.createElement('div');
                ld.style.cssText = `position:absolute;left:${lx-lw/2}px;top:${game.boss.y}px;width:${lw}px;height:${lh}px;background:rgba(255,0,0,0.7);z-index:5;`;
                document.body.appendChild(ld);
                lasers.push(ld);
            }
            game.laserDamageActive = true;
            setTimeout(()=>{
                lasers.forEach(d=>d.parentNode&&d.remove());
                lasers = [];
                game.laserDamageActive = false;
                game.laserHitCount = 0;
            },1000);
        },1000);
    }
}

function drawEnemies() {
    game.enemies.forEach((e,i)=>{
        e.y += e.speed;
        ctx.fillStyle='#f33';
        ctx.fillRect(e.x,e.y,e.w,e.h);
        if(e.canShoot) enemyShoot(e);
        if(e.y>height){
            game.enemies.splice(i,1);
            game.lives--;
            updateStats();
        }
    });

    ctx.fillStyle='#ff9900';
    game.enemyBullets.forEach((b,i)=>{
        b.x += b.vx;
        b.y += b.vy;
        ctx.fillRect(b.x,b.y,b.w,b.h);
        if(b.y>height||b.x<0||b.x>width) game.enemyBullets.splice(i,1);
    });

    if(game.boss){
        game.boss.x += game.boss.speed * game.boss.direction;
        if(game.boss.x<0||game.boss.x>width-game.boss.w) game.boss.direction*=-1;
        bossAttack();
        ctx.fillStyle='#f99';
        ctx.fillRect(game.boss.x,game.boss.y,game.boss.w,game.boss.h);
        ctx.fillStyle='#f33';
        ctx.fillRect(game.boss.x,game.boss.y-10,game.boss.w*(game.boss.hp/60),8);
    }
}

function checkCollisions() {
    game.bullets.forEach((b,bi)=>{
        game.enemies.forEach((e,ei)=>{
            if(checkOverlap(b,e)){
                game.bullets.splice(bi,1);
                e.hp--;
                if(e.hp<=0){
                    game.enemies.splice(ei,1);
                    game.score+=100;
                    updateStats();checkTalent();
                }
            }
        });
        if(game.boss&&checkOverlap(b,game.boss)){
            game.bullets.splice(bi,1);
            game.boss.hp--;
            if(game.boss.hp<=0){
                game.boss=null;
                game.score+=1000;
                updateStats();checkTalent();
            }
        }
    });

    game.enemyBullets.forEach((b,i)=>{
        if(!game.player.isDodging&&checkOverlap(b,game.player)){
            game.enemyBullets.splice(i,1);
            game.lives--;
            updateStats();
            if(game.lives<=0)gameOver();
        }
    });

    if(game.laserDamageActive && game.laserHitCount < game.maxLaserHits){
        lasers.forEach(laser=>{
            let lr = {
                x:parseInt(laser.style.left),
                y:parseInt(laser.style.top),
                w:parseInt(laser.style.width),
                h:parseInt(laser.style.height)
            };
            let pr = {x:game.player.x,y:game.player.y,w:game.player.w,h:game.player.h};
            if(!game.player.isDodging && checkOverlap(pr,lr)){
                game.laserHitCount++;
                game.lives--;
                updateStats();
                if(game.lives<=0)gameOver();
            }
        });
    }

    game.enemies.forEach((e,i)=>{
        if(!game.player.isDodging&&checkOverlap(e,game.player)){
            game.enemies.splice(i,1);
            game.lives--;
            updateStats();
            if(game.lives<=0)gameOver();
        }
    });
}

function checkTalent() {
    if(Math.floor(game.score/500) > Math.floor(game.lastScore/500)){
        game.lastScore = game.score;
        showTalentPopup();
    }
}

function showTalentPopup() {
    let c = document.getElementById('talentContainer');
    c.innerHTML = '';
    let arr = [];
    while(arr.length<3){
        let t = talents[Math.floor(Math.random()*talents.length)];
        if(!arr.includes(t)&&!(t.type==='unlockDodge'&&game.skills.dodge.unlocked)&&!(t.type==='unlockNuke'&&game.skills.nuke.unlocked)){
            arr.push(t);
        }
    }
    arr.forEach(t=>{
        let d = document.createElement('div');
        d.className='talent';
        d.textContent = t.desc;
        d.onclick = ()=>{
            applyTalent(t);
            document.getElementById('talentPopup').style.display='none';
        };
        c.appendChild(d);
    });
    document.getElementById('talentPopup').style.display='flex';
}

function applyTalent(t) {
    switch(t.type){
        case 'speed':game.player.speed+=t.value;break;
        case 'bulletSpeed':game.player.bulletSpeed+=t.value;break;
        case 'fireRate':game.player.fireRate*=(1-t.value);break;
        case 'unlockDodge':
            game.skills.dodge.unlocked=true;
            dodgeBtn.classList.add('unlocked');
            dodgeBtn.innerHTML='闪避 (冷却: 5秒)';
        break;
        case 'unlockNuke':
            game.skills.nuke.unlocked=true;
            nukeBtn.classList.add('unlocked');
            nukeBtn.innerHTML='核弹 (冷却: 15秒)';
        break;
        case 'dodgeCD':game.skills.dodge.cooldown+=t.value;break;
        case 'nukeCD':game.skills.nuke.cooldown+=t.value;break;
        case 'lives':game.lives+=t.value;updateStats();break;
    }
}

function updateStats() {
    document.getElementById('score').textContent = game.score;
    document.getElementById('lives').textContent = game.lives;
    if(game.skills.dodge.unlocked){
        if(game.skills.dodge.cd>0){
            dodgeBtn.disabled=true;
            dodgeBtn.innerHTML=`闪避 (${Math.ceil(game.skills.dodge.cd/1000)}秒)`;
        }else{
            dodgeBtn.disabled=false;
            dodgeBtn.innerHTML=`闪避 (冷却: ${game.skills.dodge.cooldown/1000}秒)`;
        }
    }
    if(game.skills.nuke.unlocked){
        if(game.skills.nuke.cd>0){
            nukeBtn.disabled=true;
            nukeBtn.innerHTML=`核弹 (${Math.ceil(game.skills.nuke.cd/1000)}秒)`;
        }else{
            nukeBtn.disabled=false;
            nukeBtn.innerHTML=`核弹 (冷却: ${game.skills.nuke.cooldown/1000}秒)`;
        }
    }
}

dodgeBtn.onclick = ()=>{
    if(game.skills.dodge.unlocked && game.skills.dodge.cd<=0){
        game.player.isDodging=true;
        game.skills.dodge.cd = game.skills.dodge.cooldown;
        setTimeout(()=>game.player.isDodging=false,1000);
    }
};

nukeBtn.onclick = ()=>{
    if(game.skills.nuke.unlocked && game.skills.nuke.cd<=0){
        game.enemies = [];
        game.enemyBullets = [];
        lasers.forEach(d=>d.remove());lasers=[];
        laserWarnings.forEach(d=>d.remove());laserWarnings=[];
        game.skills.nuke.cd = game.skills.nuke.cooldown;
        game.score += 500;
        updateStats();
    }
};

function gameOver() {
    game.isGameOver = true;
    document.getElementById('finalScore').textContent = game.score;
    document.getElementById('gameOverPopup').style.display='flex';
}

function restartGame() {
    document.getElementById('gameOverPopup').style.display='none';
    game = {
        player: { 
            x: 0, y: 0, w: 50, h: 50, 
            speed: 5,bulletSpeed:10,fireRate:300,isDodging:false
        },
        bullets:[],enemyBullets:[],enemies:[],boss:null,
        score:0,lives:3,lastScore:0,
        skills:{
            dodge:{unlocked:false,cd:0,cooldown:5000},
            nuke:{unlocked:false,cd:0,cooldown:15000}
        },
        isGameOver:false,lastEnemySpawn:0,spawnRate:1000,
        bossSpawned:false,lastShot:0,
        laserDamageActive:false,laserHitCount:0,maxLaserHits:1
    };
    dodgeBtn.classList.remove('unlocked');
    dodgeBtn.innerHTML='闪避 (未解锁)';
    nukeBtn.classList.remove('unlocked');
    nukeBtn.innerHTML='核弹 (未解锁)';
    lasers.forEach(d=>d.remove());lasers=[];
    laserWarnings.forEach(d=>d.remove());laserWarnings=[];
    initPlayer();updateStats();gameLoop();
}

function gameLoop() {
    if(game.isGameOver) return;
    ctx.fillStyle='#111';
    ctx.fillRect(0,0,width,height);
    if(game.skills.dodge.cd>0) game.skills.dodge.cd-=16;
    if(game.skills.nuke.cd>0) game.skills.nuke.cd-=16;
    shoot();spawnEnemy();drawPlayer();drawBullets();drawEnemies();checkCollisions();
    requestAnimationFrame(gameLoop);
}

initPlayer();
updateStats();
gameLoop();
</script>
</body>
</html>

Game Source: 飞机大战 - BOSS激光优化版

Creator: MegaFlare60

Libraries: none

Complexity: complex (503 lines, 16.0 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: boss-megaflare60" to link back to the original. Then publish at arcadelab.ai/publish.