🎮ArcadeLab

深空舰队指挥官 - 最终模块化版

by EpicCoder88
529 lines23.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, maximum-scale=1.0, user-scalable=no">
<title>深空舰队指挥官 - 最终模块化版</title>
<style>
  body { margin: 0; overflow: hidden; background: #03050a; touch-action: none; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
  canvas { display: block; width: 100vw; height: 100vh; }
  
  /* 战前配置界面 */
  #ui-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; background: rgba(3, 5, 10, 0.9); backdrop-filter: blur(5px); z-index: 10; transition: opacity 0.4s; }
  .panel { background: rgba(15, 25, 45, 0.95); border: 1px solid #3a5a8a; padding: 25px; border-radius: 12px; color: #fff; box-shadow: 0 0 40px rgba(58, 90, 138, 0.3); width: 90%; max-width: 650px; font-family: monospace; }
  h1 { text-align: center; font-size: 24px; color: #66ccff; margin: 0 0 20px 0; letter-spacing: 3px; text-shadow: 0 0 10px #66ccff;}
  .fleet-config { display: flex; justify-content: space-between; margin-bottom: 25px; gap: 20px;}
  .side { width: 48%; background: rgba(0,0,0,0.3); padding: 15px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.05);}
  .side h3 { font-size: 14px; margin: 0 0 15px 0; color: #a0b0d0; border-bottom: 1px dashed #3a5a8a; padding-bottom: 8px; text-align: center;}
  .row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; font-size: 13px; }
  .row span { color: #ccc; }
  .btn-group { display: flex; align-items: center; gap: 10px; }
  .btn-group button { width: 28px; height: 28px; background: #2a4a7a; border: 1px solid #4a7aab; color: #fff; border-radius: 4px; font-weight: bold; cursor: pointer; transition: 0.1s;}
  .btn-group button:active { background: #66ccff; color: #000; }
  .val { width: 24px; text-align: center; font-weight: bold; color: #66ccff; font-size: 16px;}
  #start-btn { width: 100%; padding: 15px; background: linear-gradient(90deg, #0055ff, #00aaff); color: #fff; border: none; border-radius: 6px; font-size: 18px; font-weight: bold; cursor: pointer; letter-spacing: 2px; font-family: monospace; transition: 0.2s; text-shadow: 1px 1px 2px rgba(0,0,0,0.5);}
  #start-btn:hover { box-shadow: 0 0 20px rgba(0, 170, 255, 0.6); transform: scale(1.02); }
  
  /* 战斗UI */
  #combat-ui { position: absolute; top: 15px; left: 15px; color: #fff; pointer-events: none; display: none; font-family: monospace; }
  .log { font-size: 12px; color: #88aaff; max-width: 350px; line-height: 1.4; text-shadow: 1px 1px 2px #000; }
  #stats { position: absolute; top: 15px; right: 15px; color: #66ccff; font-size: 14px; text-align: right; text-shadow: 1px 1px 2px #000; font-family: monospace; display: none; }
</style>
</head>
<body>

<div id="ui-layer">
  <div class="panel">
    <h1>舰队战术配置</h1>
    <div class="fleet-config">
      <div class="side">
        <h3>蓝方 (第7联合舰队)</h3>
        <div class="row"><span>激光舰 [5舱]</span> <div class="btn-group"><button onclick="updateCount('bLaser',-1)">-</button><span class="val" id="val-bLaser">2</span><button onclick="updateCount('bLaser',1)">+</button></div></div>
        <div class="row"><span>突击舰 [7舱]</span> <div class="btn-group"><button onclick="updateCount('bAssault',-1)">-</button><span class="val" id="val-bAssault">1</span><button onclick="updateCount('bAssault',1)">+</button></div></div>
      </div>
      <div class="side">
        <h3 style="color:#ff3366">红方 (深空海盗)</h3>
        <div class="row"><span>导弹舰 [11舱]</span> <div class="btn-group"><button onclick="updateCount('rMissile',-1)">-</button><span class="val" id="val-rMissile">2</span><button onclick="updateCount('rMissile',1)">+</button></div></div>
        <div class="row"><span>补给舰 [3舱]</span> <div class="btn-group"><button onclick="updateCount('rSupply',-1)">-</button><span class="val" id="val-rSupply">1</span><button onclick="updateCount('rSupply',1)">+</button></div></div>
      </div>
    </div>
    <button id="start-btn" onclick="startBattle()">开 战</button>
  </div>
</div>

<div id="combat-ui"><div class="log" id="log"></div></div>
<div id="stats"></div>

<canvas id="gameCanvas"></canvas>

<script>
// ================= 基础设置 =================
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let W, H;
function resize() { W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; }
window.addEventListener('resize', resize); resize();

// ================= 模块化参数 =================
const MODULE_W = 16; // 单个舱段长度 (为了适应战场缩放,调小一点)
const MODULE_H = 12; // 舱段厚度

const config = { bLaser: 2, bAssault: 1, rMissile: 2, rSupply: 1 };
function updateCount(type, delta) {
  config[type] = Math.max(0, config[type] + delta);
  document.getElementById('val-' + type).innerText = config[type];
}

function logMsg(msg) {
  const el = document.getElementById('log');
  el.innerHTML = `> ${msg}<br>` + el.innerHTML.slice(0, 250);
}

// 数学辅助
function dist(a, b) { return Math.hypot(a.x - b.x, a.y - b.y); }
function angleTo(a, b) { return Math.atan2(b.y - a.y, b.x - a.x); }
function normalizeAngle(a) { while (a > Math.PI) a -= Math.PI*2; while (a < -Math.PI) a += Math.PI*2; return a; }

let entities = [];
let bullets = [];
let particles = [];
let gameState = 'config';
let totalTime = 0;

// ================= 模块化绘制引擎 =================
// 绘制单个模块
function drawModule(ctx, type, x, y, time, isFirst, isLast) {
  const pulse = Math.sin(time * 0.005) * 0.5 + 0.5;
  ctx.save();
  ctx.translate(x, y);

  // 基础颜色
  let baseColor = '#2c3e50', highlight = '#4a6b8c', shadow = '#1a252f';
  if (type === 'engine') { baseColor = '#3b2f2f'; highlight = '#5c4848'; shadow = '#1f1818'; }
  if (type === 'weapon') { baseColor = '#2b3b3b'; highlight = '#3d5c5c'; shadow = '#162121'; }
  if (type === 'missile') { baseColor = '#3b2b2b'; highlight = '#5c3d3d'; shadow = '#211616'; }
  if (type === 'radar') { baseColor = '#1e3d3d'; highlight = '#336666'; shadow = '#0d1f1f'; }
  if (type === 'supply') { baseColor = '#3b352b'; highlight = '#5c5348'; shadow = '#211d16'; }

  // 舱体
  ctx.fillStyle = baseColor;
  ctx.fillRect(0, 0, MODULE_W, MODULE_H);
  ctx.fillStyle = highlight;
  ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(MODULE_W, 0); ctx.lineTo(MODULE_W - 3, 3); ctx.lineTo(3, 3); ctx.fill();
  ctx.fillStyle = shadow;
  ctx.beginPath(); ctx.moveTo(0, MODULE_H); ctx.lineTo(MODULE_W, MODULE_H); ctx.lineTo(MODULE_W - 3, MODULE_H - 3); ctx.lineTo(3, MODULE_H - 3); ctx.fill();

  // 接缝
  ctx.strokeStyle = '#000'; ctx.lineWidth = 1.5;
  ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(0, MODULE_H); ctx.stroke();
  if (isLast) { ctx.beginPath(); ctx.moveTo(MODULE_W, 0); ctx.lineTo(MODULE_W, MODULE_H); ctx.stroke(); }

  // 特殊结构
  if (type === 'engine') {
    const flameLen = 15 + pulse * 12;
    const g = ctx.createLinearGradient(0, MODULE_H/2, -flameLen, MODULE_H/2);
    g.addColorStop(0, 'rgba(102, 204, 255, 0.8)'); g.addColorStop(1, 'rgba(0, 50, 255, 0)');
    ctx.fillStyle = g;
    ctx.beginPath(); ctx.moveTo(0, 2); ctx.lineTo(-flameLen, MODULE_H/2); ctx.lineTo(0, MODULE_H-2); ctx.fill();
  } else if (type === 'solar') {
    ctx.fillStyle = '#1a2b4c'; ctx.strokeStyle = '#3a5a8a'; ctx.lineWidth = 1;
    ctx.fillRect(3, -16, MODULE_W - 6, 16); ctx.strokeRect(3, -16, MODULE_W - 6, 16);
    ctx.fillRect(3, MODULE_H, MODULE_W - 6, 16); ctx.strokeRect(3, MODULE_H, MODULE_W - 6, 16);
    ctx.strokeStyle = 'rgba(102, 204, 255, 0.3)';
    for(let i=3; i<MODULE_W-3; i+=4) { ctx.beginPath(); ctx.moveTo(i, -16); ctx.lineTo(i, 0); ctx.stroke(); ctx.beginPath(); ctx.moveTo(i, MODULE_H); ctx.lineTo(i, MODULE_H+16); ctx.stroke(); }
  } else if (type === 'core') {
    ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; ctx.fillRect(3, 4, 4, 4);
    ctx.fillStyle = `rgba(102, 204, 255, ${0.3 + pulse*0.4})`; ctx.fillRect(MODULE_W/2 - 1, 0, 2, MODULE_H);
  } else if (type === 'hab') {
    ctx.fillStyle = '#66ccff'; ctx.beginPath(); ctx.arc(MODULE_W/2, MODULE_H/2, 2.5, 0, Math.PI*2); ctx.fill();
  } else if (type === 'weapon') {
    ctx.fillStyle = '#111'; ctx.fillRect(MODULE_W-2, MODULE_H/2 - 2, 15, 4);
    ctx.fillStyle = `rgba(0, 255, 204, ${0.5 + pulse*0.5})`; ctx.beginPath(); ctx.arc(MODULE_W+12, MODULE_H/2, 2.5, 0, Math.PI*2); ctx.fill();
    ctx.fillStyle = '#888'; ctx.fillRect(3, 1, 4, 2); ctx.fillRect(3, MODULE_H-3, 4, 2);
  } else if (type === 'missile') {
    ctx.fillStyle = '#111'; ctx.fillRect(3, 3, MODULE_W-6, 4); ctx.fillRect(3, MODULE_H-7, MODULE_W-6, 4);
    ctx.fillStyle = `rgba(255, 50, 50, ${0.3 + pulse*0.5})`; ctx.beginPath(); ctx.arc(MODULE_W-5, 5, 1.2, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.arc(MODULE_W-5, MODULE_H-5, 1.2, 0, Math.PI*2); ctx.fill();
  } else if (type === 'radar') {
    ctx.fillStyle = '#00ffcc'; ctx.fillRect(MODULE_W/2 - 1, -6, 2, 6);
    ctx.save(); ctx.translate(MODULE_W/2, -6); ctx.rotate(time * 0.003);
    ctx.strokeStyle = '#fff'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(6, 0); ctx.stroke(); ctx.restore();
  } else if (type === 'supply') {
    ctx.fillStyle = '#999'; ctx.fillRect(MODULE_W-2, MODULE_H/2 - 3, 6, 6); ctx.fillRect(MODULE_W/2 - 3, -4, 6, 4); ctx.fillRect(MODULE_W/2 - 3, MODULE_H, 6, 4);
    ctx.fillStyle = `rgba(0, 255, 0, ${0.5 + pulse*0.5})`; ctx.beginPath(); ctx.arc(MODULE_W/2, MODULE_H/2, 1.5, 0, Math.PI*2); ctx.fill();
  }
  ctx.restore();
}

// ================= 舰船基类 =================
class Ship {
  constructor(x, y, isEnemy, modules) {
    this.x = x; this.y = y;
    this.vx = 0; this.vy = 0;
    this.angle = isEnemy ? Math.PI : 0;
    this.isEnemy = isEnemy;
    this.dead = false;
    this.modules = modules;
    
    // 核心长度与物理属性
    this.length = modules.length * MODULE_W;
    this.radius = this.length * 0.5; // 简化的碰撞半径
    this.maxSpeed = 2.0; 
    this.turnSpeed = 0.03; 
    this.thrustPower = 0.05; 
    
    this.hp = 100; this.maxHp = 100;
    this.fireCooldown = 0;
    this.engineParticleTimer = 0;
  }

  update() {
    if (this.dead) return;
    
    // 单向推进物理
    this.vx *= 0.985; this.vy *= 0.985;
    this.x += this.vx; this.y += this.vy;
    
    // 边界反弹 (留出空间)
    const m = this.length * 0.5;
    if (this.x < m && this.vx < 0) this.vx *= -0.5;
    if (this.x > W - m && this.vx > 0) this.vx *= -0.5;
    if (this.y < m && this.vy < 0) this.vy *= -0.5;
    if (this.y > H - m && this.vy > 0) this.vy *= -0.5;

    if (this.fireCooldown > 0) this.fireCooldown--;
    if (this.engineParticleTimer > 0) this.engineParticleTimer--;
    
    if (this.hp <= 0) {
      this.dead = true;
      this.explode(this.length * 1.5);
    }
  }

  moveToTarget(targetAngle) {
    const diff = normalizeAngle(targetAngle - this.angle);
    this.angle += Math.sign(diff) * Math.min(Math.abs(diff), this.turnSpeed);
    
    if (Math.abs(diff) < 0.4) {
      this.vx += Math.cos(this.angle) * this.thrustPower;
      this.vy += Math.sin(this.angle) * this.thrustPower;
      
      const speed = Math.hypot(this.vx, this.vy);
      if (speed > this.maxSpeed) { this.vx = (this.vx / speed) * this.maxSpeed; this.vy = (this.vy / speed) * this.maxSpeed; }
      this.engineParticleTimer = 2;
    }
  }

  evade(targetAngle) {
    this.moveToTarget(targetAngle + Math.PI * 0.7);
  }

  explode(count) {
    for (let i = 0; i < count; i++) {
      const a = Math.random() * Math.PI * 2, s = Math.random() * 4;
      particles.push({ x: this.x, y: this.y, vx: Math.cos(a)*s, vy: Math.sin(a)*s, life: 30+Math.random()*20, maxLife: 50, color: Math.random()>0.5?'#ffaa00':'#ff3300', size: Math.random()*3 + 1 });
    }
  }

  // 统一渲染方法
  draw(time) {
    if (this.dead) return;
    
    ctx.save();
    ctx.translate(this.x, this.y);
    ctx.rotate(this.angle);
    // 将原点移到整个拼接模块的中心左侧
    ctx.translate(-this.length / 2, -MODULE_H / 2);
    
    // 绘制所有模块
    this.modules.forEach((modType, i) => {
      drawModule(ctx, modType, i * MODULE_W, 0, time, i === 0, i === this.modules.length - 1);
    });
    ctx.restore();
    
    // 绘制血条 (在飞船正上方)
    ctx.fillStyle = 'rgba(0,0,0,0.6)';
    ctx.fillRect(this.x - 25, this.y - this.length*0.5 - 15, 50, 4);
    ctx.fillStyle = this.hp > 50 ? '#00ff88' : '#ff0044';
    ctx.fillRect(this.x - 25, this.y - this.length*0.5 - 15, 50 * (this.hp / this.maxHp), 4);
  }
}

// ================= 1. 激光舰 (5舱) =================
class LaserShip extends Ship {
  constructor(x, y, isEnemy) {
    super(x, y, isEnemy, ['engine', 'solar', 'core', 'hab', 'weapon']);
    this.color = '#66ccff'; this.maxSpeed = 2.5; this.turnSpeed = 0.04;
    this.heat = 0; this.isOverheated = false;
  }
  update(time) {
    super.update();
    if (this.dead) return;
    
    if (this.heat > 0) this.heat -= 0.5;
    if (this.isOverheated && this.heat <= 0) this.isOverheated = false;

    let target = entities.find(e => e.isEnemy !== this.isEnemy && !e.dead);
    if (target) {
      const d = dist(this, target), ta = angleTo(this, target);
      // 交战距离拉长
      if (d < 250) this.evade(ta);
      else if (d > 600) this.moveToTarget(ta);
      else this.moveToTarget(ta + Math.PI/2);

      // 主炮 (原点偏移到船头)
      if (!this.isOverheated && this.fireCooldown <= 0 && Math.abs(normalizeAngle(ta - this.angle)) < 0.15) {
        let bx = this.x + Math.cos(this.angle) * this.length/2;
        let by = this.y + Math.sin(this.angle) * this.length/2;
        bullets.push(new Bullet(bx, by, this.angle, this.isEnemy, 'laser'));
        this.heat += 6; this.fireCooldown = 5;
        if (this.heat >= 100) { this.isOverheated = true; logMsg("激光舰:主炮过热!"); }
      }
      
      // 近防拦截
      bullets.forEach(b => {
        if (b.isEnemy !== this.isEnemy && b.type === 'missile' && dist(this, b) < 200) {
          if (Math.random() < 0.1) { b.dead = true; particles.push({x: b.x, y: b.y, vx: 0, vy: 0, life: 10, maxLife: 10, color: '#66ccff', size: 2}); }
        }
      });
    }
  }
}

// ================= 2. 突击舰 (7舱) =================
class AssaultShip extends Ship {
  constructor(x, y, isEnemy) {
    super(x, y, isEnemy, ['engine', 'supply', 'core', 'hab', 'missile', 'radar', 'weapon']);
    this.color = '#00ffcc'; this.maxSpeed = 4.0; this.turnSpeed = 0.06;
  }
  update(time) {
    super.update();
    if (this.dead) return;
    
    let target = entities.find(e => e.isEnemy !== this.isEnemy && !e.dead);
    if (target) {
      const ta = angleTo(this, target);
      this.moveToTarget(ta);
      
      if (this.fireCooldown <= 0 && Math.abs(normalizeAngle(ta - this.angle)) < 0.1) {
        let bx = this.x + Math.cos(this.angle) * this.length/2;
        let by = this.y + Math.sin(this.angle) * this.length/2;
        bullets.push(new Bullet(bx, by, this.angle, this.isEnemy, 'railgun'));
        this.fireCooldown = 120;
      }
      if (Math.random() < 0.01) {
        let bx = this.x + Math.cos(this.angle) * this.length/2;
        let by = this.y + Math.sin(this.angle) * this.length/2;
        bullets.push(new Bullet(bx, by, this.angle, this.isEnemy, 'missile'));
      }

      bullets.forEach(b => {
        if (b.isEnemy !== this.isEnemy && b.type === 'missile' && dist(this, b) < 150) {
          if (Math.random() < 0.15) { b.dead = true; particles.push({x: b.x, y: b.y, vx: 0, vy: 0, life: 10, maxLife: 10, color: '#00ffcc', size: 2}); }
        }
      });
    }
  }
}

// ================= 3. 导弹舰 (11舱) =================
class MissileShip extends Ship {
  constructor(x, y, isEnemy) {
    super(x, y, isEnemy, ['engine', 'hab', 'missile', 'solar', 'missile', 'missile', 'missile', 'solar', 'missile', 'missile', 'missile', 'supply']);
    this.color = '#ff3366'; this.maxSpeed = 1.5; this.turnSpeed = 0.02;
    this.maxAmmo = 80; this.ammo = 80;
  }
  update(time) {
    super.update();
    if (this.dead) return;
    
    if (this.ammo <= 0) { this.moveToTarget(this.angle + Math.PI); return; }

    let target = entities.find(e => e.isEnemy !== this.isEnemy && !e.dead);
    if (target) {
      const d = dist(this, target), ta = angleTo(this, target);
      if (d < 400) this.evade(ta); else this.moveToTarget(ta);

      if (this.fireCooldown <= 0 && Math.abs(normalizeAngle(ta - this.angle)) < 0.3) {
        let bx = this.x + Math.cos(this.angle) * this.length/2;
        let by = this.y + Math.sin(this.angle) * this.length/2;
        bullets.push(new Bullet(bx, by, this.angle - 0.1, this.isEnemy, 'missile'));
        bullets.push(new Bullet(bx, by, this.angle + 0.1, this.isEnemy, 'missile'));
        this.ammo -= 2; this.fireCooldown = 80;
      }
      
      bullets.forEach(b => {
        if (b.isEnemy !== this.isEnemy && b.type !== 'missile' && dist(this, b) < 150) {
          if (Math.random() < 0.2) b.dead = true;
        }
      });
    }
  }
  draw(time) {
    super.draw(time);
    if (this.dead) return;
    ctx.fillStyle = '#fff'; ctx.font = '10px monospace'; ctx.textAlign = 'center';
    ctx.fillText(`弹:${Math.floor(this.ammo)}`, this.x, this.y + this.length*0.5 + 20);
  }
}

// ================= 4. 补给舰 (3舱) =================
class SupplyShip extends Ship {
  constructor(x, y, isEnemy) {
    super(x, y, isEnemy, ['engine', 'core', 'supply']);
    this.color = '#ffaa00'; this.maxSpeed = 3.0; this.turnSpeed = 0.08;
    this.state = 'search'; this.targetFriend = null;
  }
  update() {
    super.update();
    if (this.dead) return;

    if (this.state === 'search') {
      this.targetFriend = entities.find(e => e.isEnemy === this.isEnemy && !e.dead && e !== this && e.ammo !== undefined && e.ammo < 20);
      
      if (this.targetFriend) {
        const ta = angleTo(this, this.targetFriend);
        // 根据长度判断靠近
        const safeDist = (this.length + this.targetFriend.length) / 2 + 10;
        if (dist(this, this.targetFriend) < safeDist + 50) this.thrustPower = 0.01;
        else this.thrustPower = 0.08;
        this.moveToTarget(ta);
        
        if (dist(this, this.targetFriend) < safeDist && Math.abs(normalizeAngle(ta - this.angle)) < 0.2) {
          this.state = 'docking'; this.vx = 0; this.vy = 0;
          logMsg("补给舰:接口锁定,开始对接!");
        }
      } else {
        this.moveToTarget(this.angle + (Math.random() - 0.5) * 0.1);
      }
    } else if (this.state === 'docking') {
      this.vx = 0; this.vy = 0;
      if (!this.targetFriend || this.targetFriend.dead) { this.state = 'search'; return; }
      
      this.angle = angleTo(this, this.targetFriend);
      
      if (this.targetFriend.ammo < this.targetFriend.maxAmmo) {
        this.targetFriend.ammo += 1;
        if (Math.random() < 0.2) particles.push({x: (this.x+this.targetFriend.x)/2, y: (this.y+this.targetFriend.y)/2, vx: 0, vy: 0, life: 15, maxLife: 15, color: '#00ff00', size: 2});
      } else {
        this.state = 'search'; logMsg("补给舰:弹药补满,解除对接。"); this.targetFriend = null;
      }
    }
  }
}

// ================= 子弹类 =================
class Bullet {
  constructor(x, y, angle, isEnemy, type) {
    this.x = x; this.y = y; this.angle = angle; this.isEnemy = isEnemy; this.type = type;
    this.dead = false; this.life = 200;
    
    if (type === 'missile') { this.speed = 3; this.turnSpeed = 0.03; this.damage = 25; this.life = 300; }
    else if (type === 'laser') { this.speed = 15; this.damage = 8; }
    else if (type === 'railgun') { this.speed = 25; this.damage = 50; }
    this.vx = Math.cos(angle) * this.speed; this.vy = Math.sin(angle) * this.speed;
  }
  update() {
    if (this.dead) return;
    if (this.type === 'missile') {
      let target = entities.find(e => e.isEnemy !== this.isEnemy && !e.dead);
      if (target) {
        const ta = angleTo(this, target); let ca = Math.atan2(this.vy, this.vx);
        const diff = normalizeAngle(ta - ca); ca += Math.sign(diff) * this.turnSpeed;
        this.vx = Math.cos(ca) * this.speed; this.vy = Math.sin(ca) * this.speed; this.angle = ca;
      }
    }
    this.x += this.vx; this.y += this.vy; this.life--;
    if (this.life <= 0 || this.x < 0 || this.x > W || this.y < 0 || this.y > H) this.dead = true;

    for (let e of entities) {
      if (!e.dead && e.isEnemy !== this.isEnemy) {
        // 飞船变长,子弹碰撞检测利用偏转计算,稍微放宽判定
        if (dist(this, e) < e.radius + 10) {
          e.hp -= this.damage; this.dead = true;
          for(let i=0; i<3; i++) particles.push({x: this.x, y: this.y, vx: (Math.random()-0.5)*2, vy: (Math.random()-0.5)*2, life: 10, maxLife: 10, color: this.type === 'laser' ? '#66ccff' : '#fff', size: 2});
        }
      }
    }
  }
  draw() {
    if (this.dead) return;
    ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle);
    if (this.type === 'missile') {
      ctx.fillStyle = '#ffaa00'; ctx.fillRect(-6, -2, 12, 4);
      ctx.fillStyle = '#ff3300'; ctx.beginPath(); ctx.moveTo(-6, -2); ctx.lineTo(-12, 0); ctx.lineTo(-6, 2); ctx.fill();
    } else if (this.type === 'laser') {
      ctx.fillStyle = '#66ccff'; ctx.fillRect(-10, -1, 20, 2);
    } else {
      ctx.fillStyle = '#00ffff'; ctx.fillRect(-15, -2, 30, 4);
    }
    ctx.restore();
  }
}

// ================= 游戏控制 =================
function startBattle() {
  document.getElementById('ui-layer').style.opacity = '0';
  setTimeout(() => document.getElementById('ui-layer').style.display = 'none', 400);
  document.getElementById('combat-ui').style.display = 'block';
  document.getElementById('stats').style.display = 'block';
  
  gameState = 'playing'; totalTime = 0;
  entities = []; bullets = []; particles = [];
  document.getElementById('log').innerHTML = "舰队跃迁完成,进入交战距离...";

  // 根据配置部署舰船 (给一点初始位置偏移防止重叠)
  for(let i=0; i<config.bLaser; i++) entities.push(new LaserShip(150, H * (i+1)/(config.bLaser+1), false));
  for(let i=0; i<config.bAssault; i++) entities.push(new AssaultShip(250, H * (i+1)/(config.bAssault+1), false));

  for(let i=0; i<config.rMissile; i++) entities.push(new MissileShip(W - 200, H * (i+1)/(config.rMissile+1), true));
  for(let i=0; i<config.rSupply; i++) entities.push(new SupplyShip(W - 100, H * (i+1)/(config.rSupply+1), true));
  
  requestAnimationFrame(loop);
}

function loop(time) {
  if (gameState !== 'playing') return;
  totalTime += 1/60;

  ctx.fillStyle = 'rgba(5, 9, 20, 0.35)'; ctx.fillRect(0, 0, W, H);
  
  // 背景星图网格
  ctx.strokeStyle = 'rgba(50, 100, 150, 0.05)'; ctx.lineWidth = 1; ctx.beginPath();
  for(let i=0; i<W; i+=100) { ctx.moveTo(i, 0); ctx.lineTo(i, H); }
  for(let i=0; i<H; i+=100) { ctx.moveTo(0, i); ctx.lineTo(W, i); }
  ctx.stroke();

  // 实体更新
  for (let e of entities) { e.update(time); e.draw(time); }
  
  // 子弹更新
  for (let i = bullets.length - 1; i >= 0; i--) { bullets[i].update(); bullets[i].draw(); if(bullets[i].dead) bullets.splice(i, 1); }

  // 粒子更新
  for (let i = particles.length - 1; i >= 0; i--) {
    let p = particles[i]; p.x += p.vx; p.y += p.vy; p.life--;
    ctx.globalAlpha = Math.max(0, p.life / p.maxLife); ctx.fillStyle = p.color;
    ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI*2); ctx.fill();
    if (p.life <= 0) particles.splice(i, 1);
  }
  ctx.globalAlpha = 1;

  // 侧边状态栏
  let bAlive = entities.filter(e => !e.isEnemy && !e.dead).length;
  let rAlive = entities.filter(e => e.isEnemy && !e.dead).length;
  document.getElementById('stats').innerHTML = `时间: ${totalTime.toFixed(1)}s<br>蓝方: ${bAlive}<br>红方: ${rAlive}`;

  if (bAlive === 0 || rAlive === 0) {
    gameState = 'over';
    ctx.fillStyle = 'rgba(0,0,0,0.8)'; ctx.fillRect(0, 0, W, H);
    ctx.fillStyle = '#fff'; ctx.font = 'bold 30px monospace'; ctx.textAlign = 'center';
    ctx.fillText(bAlive > 0 ? '蓝方舰队 胜利' : (rAlive > 0 ? '红方舰队 胜利' : '同归于尽'), W/2, H/2);
    ctx.font = '16px monospace'; ctx.fillText('刷新页面重新配置舰队', W/2, H/2 + 40);
    return;
  }

  requestAnimationFrame(loop);
}
</script>
</body>
</html>

Game Source: 深空舰队指挥官 - 最终模块化版

Creator: EpicCoder88

Libraries: none

Complexity: complex (529 lines, 23.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: game-epiccoder88-mvn7x6pm" to link back to the original. Then publish at arcadelab.ai/publish.