🎮ArcadeLab

深空舰队指挥官 - 最终实机

by EpicCoder88
533 lines24.5 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; font-family: monospace; touch-action: none; }
  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); z-index: 10; transition: opacity 0.3s; }
  .panel { background: rgba(15, 25, 45, 0.95); border: 1px solid #3a5a8a; padding: 20px; border-radius: 8px; color: #fff; width: 90%; max-width: 500px; box-shadow: 0 0 30px rgba(58, 90, 138, 0.3); }
  h1 { text-align: center; font-size: 20px; color: #66ccff; margin: 0 0 15px 0; letter-spacing: 2px;}
  .fleet-config { display: flex; justify-content: space-between; margin-bottom: 20px; }
  .side { width: 48%; }
  .side h3 { font-size: 14px; margin: 0 0 10px 0; color: #a0b0d0; border-bottom: 1px solid #3a5a8a; padding-bottom: 5px; }
  .row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; font-size: 12px; }
  .btn-group button { width: 24px; height: 24px; background: #2a4a7a; border: none; color: #fff; border-radius: 4px; font-weight: bold; cursor: pointer; }
  .val { width: 20px; text-align: center; font-weight: bold; color: #66ccff;}
  #start-btn { width: 100%; padding: 12px; background: #0055ff; color: #fff; border: none; border-radius: 4px; font-size: 16px; font-weight: bold; cursor: pointer; }
  #combat-ui { position: absolute; top: 10px; left: 10px; color: #fff; pointer-events: none; display: none; }
  .log { font-size: 11px; color: #aaa; max-width: 300px; line-height: 1.2; text-shadow: 1px 1px 2px #000; }
</style>
</head>
<body>

<div id="ui-layer">
  <div class="panel">
    <h1>深空舰队配置</h1>
    <div class="fleet-config">
      <div class="side">
        <h3 style="color:#66ccff">蓝方 (任务舰队)</h3>
        <div class="row"><span>激光舰</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>突击舰</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>导弹舰</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>补给舰</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>

<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();

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; }
function logMsg(msg) { document.getElementById('log').innerHTML = `> ${msg}<br>` + document.getElementById('log').innerHTML.slice(0, 200); }

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];
}

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

// ================= 建模渲染引擎 =================
const MOD_W = 45, MOD_H = 26;

function drawTurret(ctx, x, y, angle, type) {
  ctx.save(); ctx.translate(x, y); ctx.rotate(angle);
  if (type === 'railgun') {
    ctx.fillStyle = '#2a3b4a'; ctx.beginPath(); ctx.arc(0, 0, 8, 0, Math.PI*2); ctx.fill();
    ctx.strokeStyle = '#5c738a'; ctx.lineWidth = 1.5; ctx.stroke();
    ctx.fillStyle = '#111'; ctx.fillRect(4, -5, 22, 4); ctx.fillRect(4, 1, 22, 4);
    ctx.fillStyle = '#00ffcc'; ctx.fillRect(20, -5, 6, 4); ctx.fillRect(20, 1, 6, 4);
  } else if (type === 'pd') {
    ctx.fillStyle = '#3b3b3b'; ctx.beginPath(); ctx.arc(0, 0, 5, 0, Math.PI*2); ctx.fill();
    ctx.fillStyle = '#888'; ctx.fillRect(3, -3, 10, 6);
  } else if (type === 'laser_intercept') {
    ctx.fillStyle = '#1e2b38'; ctx.beginPath(); ctx.arc(0, 0, 5, 0, Math.PI*2); ctx.fill();
    ctx.strokeStyle = '#66ccff'; ctx.lineWidth = 1; ctx.stroke();
    ctx.fillStyle = '#111'; ctx.fillRect(3, -1.5, 10, 3);
    ctx.fillStyle = '#66ccff'; ctx.beginPath(); ctx.arc(13, 0, 2, 0, Math.PI*2); ctx.fill();
  }
  ctx.restore();
}

function drawFixedMainCannon(ctx, x, y, isOverheated) {
  ctx.save(); ctx.translate(x, y);
  ctx.fillStyle = isOverheated ? '#552222' : '#1e2b38';
  ctx.beginPath(); ctx.moveTo(-10, -12); ctx.lineTo(15, -8); ctx.lineTo(15, 8); ctx.lineTo(-10, 12); ctx.closePath(); ctx.fill();
  ctx.strokeStyle = '#4a6b8c'; ctx.lineWidth = 1.5; ctx.stroke();
  
  ctx.fillStyle = '#0a0a0a'; ctx.fillRect(15, -6, 35, 4); ctx.fillRect(15, 2, 35, 4);
  ctx.fillStyle = isOverheated ? '#ff0000' : '#66ccff'; 
  if (!isOverheated) {
    ctx.fillRect(15, -3, 35, 2);
    ctx.beginPath(); ctx.arc(50, -4, 3, 0, Math.PI*2); ctx.fill();
    ctx.beginPath(); ctx.arc(50, 4, 3, 0, Math.PI*2); ctx.fill();
  }
  ctx.restore();
}

function drawModule(ctx, type, x, y, isFirst, isLast, isOverheated) {
  ctx.save(); ctx.translate(x, y);
  
  let baseColor = '#2c3e50', highlight = '#4a6b8c', shadow = '#1a252f';
  if (type === 'engine') { baseColor = '#3d2b2b'; highlight = '#6b4c4c'; }
  if (type === 'weapon_laser') { baseColor = '#1a2b3b'; highlight = '#2e4a6b'; } 

  if (type !== 'solar' && type !== 'radiator') {
    const chamfer = 4;
    ctx.beginPath(); ctx.moveTo(chamfer, 0); ctx.lineTo(MOD_W - chamfer, 0); ctx.lineTo(MOD_W, chamfer);
    ctx.lineTo(MOD_W, MOD_H - chamfer); ctx.lineTo(MOD_W - chamfer, MOD_H); ctx.lineTo(chamfer, MOD_H);
    ctx.lineTo(0, MOD_H - chamfer); ctx.lineTo(0, chamfer); ctx.closePath();
    const grad = ctx.createLinearGradient(0, 0, 0, MOD_H);
    grad.addColorStop(0, highlight); grad.addColorStop(0.5, baseColor); grad.addColorStop(1, shadow);
    ctx.fillStyle = grad; ctx.fill();
    ctx.strokeStyle = '#050505'; ctx.lineWidth = 2;
    ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(0, MOD_H); ctx.stroke();
    if (isLast) { ctx.beginPath(); ctx.moveTo(MOD_W, 0); ctx.lineTo(MOD_W, MOD_H); ctx.stroke(); }
  }

  if (type === 'engine') {
    const flameLen = 15 + Math.random() * 10;
    const g = ctx.createLinearGradient(0, MOD_H/2, -flameLen, MOD_H/2);
    g.addColorStop(0, 'rgba(102, 204, 255, 0.9)'); g.addColorStop(1, 'rgba(0, 100, 255, 0)');
    ctx.fillStyle = g; ctx.beginPath(); ctx.moveTo(0, 6); ctx.lineTo(-flameLen, MOD_H/2); ctx.lineTo(0, MOD_H-6); ctx.fill();
  }
  else if (type === 'solar') {
    ctx.fillStyle = '#1a2b4c'; ctx.strokeStyle = '#4a7aab'; ctx.lineWidth = 1;
    ctx.fillRect(6, -28, MOD_W-12, 28); ctx.strokeRect(6, -28, MOD_W-12, 28);
    ctx.fillRect(6, MOD_H, MOD_W-12, 28); ctx.strokeRect(6, MOD_H, MOD_W-12, 28);
    ctx.strokeStyle = 'rgba(102, 204, 255, 0.3)';
    for(let i=6; i<MOD_W-6; i+=8) {
      ctx.beginPath(); ctx.moveTo(i, -28); ctx.lineTo(i, 0); ctx.stroke(); ctx.beginPath(); ctx.moveTo(i, MOD_H); ctx.lineTo(i, MOD_H+28); ctx.stroke();
    }
  }
  else if (type === 'radiator') {
    ctx.fillStyle = '#331111'; ctx.strokeStyle = '#8a2a2a'; ctx.lineWidth = 1;
    ctx.fillRect(8, -22, MOD_W-16, 22); ctx.strokeRect(8, -22, MOD_W-16, 22);
    ctx.fillRect(8, MOD_H, MOD_W-16, 22); ctx.strokeRect(8, MOD_H, MOD_W-16, 22);
    ctx.fillStyle = `rgba(255, 60, 60, 0.7)`;
    for(let i=10; i<MOD_W-10; i+=5) { ctx.fillRect(i, -20, 2, 18); ctx.fillRect(i, MOD_H+2, 2, 18); }
  }
  else if (type === 'weapon_laser') {
    drawFixedMainCannon(ctx, MOD_W - 5, MOD_H/2, isOverheated);
  }
  else if (type === 'supply') {
    ctx.fillStyle = '#888'; ctx.fillRect(MOD_W/2 - 6, -10, 12, 10); ctx.fillRect(MOD_W/2 - 6, MOD_H, 12, 10); ctx.fillRect(MOD_W, MOD_H/2 - 6, 10, 12);
    ctx.fillStyle = '#0f0'; ctx.fillRect(MOD_W/2 - 4, -10, 8, 2); ctx.fillRect(MOD_W/2 - 4, MOD_H+8, 8, 2); ctx.fillRect(MOD_W+8, MOD_H/2 - 4, 2, 8);
  }
  ctx.restore();
}

// ================= 实体与战斗逻辑 =================
class Ship {
  constructor(x, y, isEnemy, color) {
    this.x = x; this.y = y; this.isEnemy = isEnemy; this.color = color;
    this.vx = 0; this.vy = 0; this.angle = isEnemy ? Math.PI : 0;
    this.maxSpeed = 2.0; this.turnSpeed = 0.03; this.thrustPower = 0.05;
    this.radius = 30; this.dead = false; this.hp = 100; this.maxHp = 100;
    this.target = null;
    this.turretAngle = 0; // 炮塔相对舰体的偏转角度
  }

  update() {
    if (this.dead) return;
    this.vx *= 0.98; this.vy *= 0.98; this.x += this.vx; this.y += this.vy;
    if (this.x < 50 || this.x > W - 50) this.vx *= -1;
    if (this.y < 50 || this.y > H - 50) this.vy *= -1;
    if (this.hp <= 0) { this.dead = true; this.explode(30); }
  }

  moveToTarget(targetAngle, keepDistance = false) {
    const diff = normalizeAngle(targetAngle - this.angle);
    this.angle += Math.sign(diff) * Math.min(Math.abs(diff), this.turnSpeed);
    if (Math.abs(diff) < 0.3) {
      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; }
    }
  }

  // 核心武器逻辑:计算炮塔转动角度
  aimTurret(target) {
    if (!target) {
      // 无目标时,炮塔缓慢归零(指向正前方)
      this.turretAngle *= 0.95;
      return;
    }
    const ta = angleTo(this, target);
    const relativeAngle = normalizeAngle(ta - this.angle);
    // 炮塔转动速度限制
    const diff = normalizeAngle(relativeAngle - this.turretAngle);
    this.turretAngle += Math.sign(diff) * Math.min(Math.abs(diff), 0.1);
  }

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

  drawHealthBar() {
    ctx.fillStyle = 'rgba(0,0,0,0.6)'; ctx.fillRect(-20, 25, 40, 4);
    ctx.fillStyle = this.hp > 50 ? '#00ff88' : '#ff0044'; ctx.fillRect(-20, 25, 40 * (this.hp / this.maxHp), 4);
  }
}

// 1. 激光舰
class LaserShip extends Ship {
  constructor(x, y, isEnemy) {
    super(x, y, isEnemy, '#66ccff');
    this.modules = ['engine', 'supply', 'core', 'hab', 'weapon_laser'];
    this.extras = [{ index: 1, type: 'solar' }, { index: 3, type: 'radiator' }];
    this.heat = 0; this.maxHeat = 100; this.isOverheated = false;
    this.maxSpeed = 2.2; this.turnSpeed = 0.04;
  }
  update() {
    super.update(); if (this.dead) return;
    if (this.heat > 0) this.heat -= 0.3;
    if (this.isOverheated && this.heat <= 0) { this.isOverheated = false; logMsg("激光舰散热完毕"); }

    // 寻找最近敌人
    let target = null, minDist = 2000;
    entities.forEach(e => { if(e.isEnemy !== this.isEnemy && !e.dead) { let d = dist(this, e); if(d < minDist) { minDist = d; target = e; } } });
    this.target = target;

    if (target) {
      this.aimTurret(target);
      const ta = angleTo(this, target);
      
      // 舰体保持距离
      if (minDist < 300) this.moveToTarget(ta + Math.PI); else this.moveToTarget(ta);

      // 固定主炮开火 (必须在舰体对准且未过热)
      if (!this.isOverheated && Math.abs(normalizeAngle(ta - this.angle)) < 0.1) {
        bullets.push(new Bullet(this.x, this.y, this.angle, this.isEnemy, 'laser_main', this));
        this.heat += 0.8; // 持续开火发热
        if (this.heat >= this.maxHeat) { this.isOverheated = true; logMsg("激光舰主炮过热停火!"); }
      }
      // 拦截炮塔开火 (只要炮塔对准敌方导弹即可)
      bullets.forEach(b => {
        if (b.isEnemy !== this.isEnemy && b.type.includes('missile')) {
          let bAngle = angleTo(this, b);
          if (dist(this, b) < 150 && Math.abs(normalizeAngle(bAngle - (this.angle + this.turretAngle))) < 0.4) {
            bullets.push(new Bullet(this.x + Math.cos(this.angle+this.turretAngle)*20, this.y + Math.sin(this.angle+this.turretAngle)*20, this.angle+this.turretAngle, this.isEnemy, 'laser_pd'));
          }
        }
      });
    }
  }
  draw() {
    ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle);
    
    // 画外挂
    this.extras.forEach(ext => drawModule(ctx, ext.type, ext.index * MOD_W - (this.modules.length*MOD_W)/2, -MOD_H/2, false, false));
    // 画舱体
    this.modules.forEach((mod, i) => drawModule(ctx, mod, i * MOD_W - (this.modules.length*MOD_W)/2, -MOD_H/2, i===0, i===this.modules.length-1, this.isOverheated));
    
    // 实时渲染旋转的副炮塔 (4门)
    if (this.target) {
      drawTurret(ctx, -20, -MOD_H/2+2, this.turretAngle, 'laser_intercept');
      drawTurret(ctx, -20, MOD_H/2-2, this.turretAngle, 'laser_intercept');
      drawTurret(ctx, 5, -MOD_H/2+2, this.turretAngle, 'laser_intercept');
      drawTurret(ctx, 5, MOD_H/2-2, this.turretAngle, 'laser_intercept');
    }
    ctx.restore();
    this.drawHealthBar();
  }
}

// 2. 突击舰
class AssaultShip extends Ship {
  constructor(x, y, isEnemy) {
    super(x, y, isEnemy, '#00ffcc');
    this.modules = ['engine', 'supply', 'core', 'hab', 'missile', 'radar', 'railgun'];
    this.extras = [{ index: 1, type: 'solar' }, { index: 2, type: 'solar' }, { index: 4, type: 'radiator' }];
    this.railgunAmmo = 24; this.railgunCd = 0;
    this.maxSpeed = 3.5; this.turnSpeed = 0.06;
  }
  update() {
    super.update(); if (this.dead) return;
    if (this.railgunCd > 0) this.railgunCd--;
    
    let target = null, minDist = 4000; // 电子舱范围
    entities.forEach(e => { if(e.isEnemy !== this.isEnemy && !e.dead) { let d = dist(this, e); if(d < minDist) { minDist = d; target = e; } } });
    this.target = target;

    if (target) {
      this.aimTurret(target);
      this.moveToTarget(angleTo(this, target)); // 突击舰直接冲锋

      // 轨道炮开火
      if (this.railgunAmmo > 0 && this.railgunCd <= 0 && Math.abs(this.turretAngle) < 0.1) {
        bullets.push(new Bullet(this.x, this.y, this.angle + this.turretAngle, this.isEnemy, 'railgun'));
        this.railgunAmmo--; this.railgunCd = 120; // 2秒装填
      }
      // 近防火炮
      bullets.forEach(b => {
        if (b.isEnemy !== this.isEnemy && b.type.includes('missile')) {
          if (dist(this, b) < 100 && Math.abs(normalizeAngle(angleTo(this, b) - this.angle)) < 0.5) {
            b.dead = true; this.explode(3);
          }
        }
      });
    }
  }
  draw() {
    ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle);
    this.extras.forEach(ext => drawModule(ctx, ext.type, ext.index * MOD_W - (this.modules.length*MOD_W)/2, -MOD_H/2, false, false));
    this.modules.forEach((mod, i) => drawModule(ctx, mod, i * MOD_W - (this.modules.length*MOD_W)/2, -MOD_H/2, i===0, i===this.modules.length-1, false));
    
    // 渲染可旋转主炮 (轨道炮)
    if (this.target) {
      drawTurret(ctx, 60, -MOD_H/2, this.turretAngle, 'railgun');
      drawTurret(ctx, 60, MOD_H/2, this.turretAngle, 'railgun');
    }
    ctx.restore();
    this.drawHealthBar();
  }
}

// 3. 导弹舰
class MissileShip extends Ship {
  constructor(x, y, isEnemy) {
    super(x, y, isEnemy, '#ff3366');
    this.modules = ['engine', 'hab', 'missile', 'solar', 'missile', 'missile', 'missile', 'solar', 'missile', 'missile', 'missile', 'supply'];
    this.maxAmmo = 80; this.ammo = 80; this.cd = 0;
    this.maxSpeed = 1.5;
  }
  update() {
    super.update(); if (this.dead) return;
    if (this.cd > 0) this.cd--;
    
    if (this.ammo <= 0) { this.moveToTarget(this.angle + Math.PI); return; } // 没弹跑路

    let target = null, minDist = 2000;
    entities.forEach(e => { if(e.isEnemy !== this.isEnemy && !e.dead) { let d = dist(this, e); if(d < minDist) { minDist = d; target = e; } } });
    this.target = target;

    if (target) {
      this.moveToTarget(angleTo(this, target));
      if (this.cd <= 0 && Math.abs(normalizeAngle(angleTo(this, target) - this.angle)) < 0.3) {
        bullets.push(new Bullet(this.x, this.y, this.angle, this.isEnemy, 'missile', this));
        this.ammo--; this.cd = 80;
      }
    }
  }
  draw() {
    ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle);
    this.modules.forEach((mod, i) => drawModule(ctx, mod, i * MOD_W - (this.modules.length*MOD_W)/2, -MOD_H/2, i===0, i===this.modules.length-1, false));
    ctx.restore();
    this.drawHealthBar();
    ctx.fillStyle = '#fff'; ctx.font = '10px monospace'; ctx.textAlign = 'center';
    ctx.fillText(`弹:${this.ammo}`, this.x, this.y + 40);
  }
}

// 4. 补给舰
class SupplyShip extends Ship {
  constructor(x, y, isEnemy) {
    super(x, y, isEnemy, '#ffaa00');
    this.modules = ['engine', 'core', 'supply'];
    this.state = 'search'; this.friend = null;
    this.maxSpeed = 3.0; this.turnSpeed = 0.08;
  }
  update() {
    super.update(); if (this.dead) return;
    
    if (this.state === 'search') {
      this.friend = entities.find(e => e.isEnemy === this.isEnemy && !e.dead && e.ammo !== undefined && e.ammo < 20);
      if (this.friend) {
        const ta = angleTo(this, this.friend);
        this.moveToTarget(ta);
        if (dist(this, this.friend) < 50) { this.state = 'docking'; logMsg("补给舰:对接成功,开始补给"); }
      }
    } else if (this.state === 'docking') {
      this.vx = 0; this.vy = 0;
      if (!this.friend || this.friend.dead) { this.state = 'search'; return; }
      this.angle = angleTo(this, this.friend);
      
      // 手稿数据:补充速度
      if (this.friend.ammo < this.friend.maxAmmo) {
        this.friend.ammo += 0.5; // 每秒补半发
        if (Math.random() < 0.1) particles.push({x: (this.x+this.friend.x)/2, y: (this.y+this.friend.y)/2, vx:0, vy:0, life: 15, maxLife:15, color: '#00ff00', size: 2});
      } else {
        this.state = 'search'; logMsg("补给舰:弹药补充完毕,脱离");
      }
    }
  }
  draw() {
    ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle);
    this.modules.forEach((mod, i) => drawModule(ctx, mod, i * MOD_W - (this.modules.length*MOD_W)/2, -MOD_H/2, i===0, i===this.modules.length-1, false));
    
    // 状态灯
    ctx.fillStyle = this.state === 'docking' ? '#00ff00' : '#ff0000';
    ctx.beginPath(); ctx.arc(0, 0, 4, 0, Math.PI*2); ctx.fill();
    ctx.restore();
    this.drawHealthBar();
  }
}

// ================= 子弹类 =================
class Bullet {
  constructor(x, y, angle, isEnemy, type, owner = null) {
    this.x = x; this.y = y; this.isEnemy = isEnemy; this.type = type; this.angle = angle;
    this.dead = false; this.life = 200;
    
    if (type === 'missile') { this.speed = 4; this.damage = 25; this.turnSpeed = 0.04; this.life = 400; }
    else if (type === 'railgun') { this.speed = 25; this.damage = 80; this.life = 100; }
    else if (type === 'laser_main') { this.speed = 15; this.damage = 2; this.life = 10; } // 持续伤害
    else if (type === 'laser_pd') { this.speed = 20; this.damage = 0; this.life = 15; } // 纯拦截
    
    // 如果是激光,需要继承舰船的速度
    if (type.includes('laser') && owner) { this.vx = owner.vx + Math.cos(angle)*this.speed; this.vy = owner.vy + Math.sin(angle)*this.speed; }
    else { 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);
        const currentAngle = Math.atan2(this.vy, this.vx);
        const diff = normalizeAngle(ta - currentAngle);
        const newAngle = currentAngle + Math.sign(diff) * this.turnSpeed;
        this.vx = Math.cos(newAngle) * this.speed; this.vy = Math.sin(newAngle) * this.speed;
        this.angle = newAngle;
      }
    }
    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 (this.type === 'laser_pd' && e.type === 'missile') {
           if (dist(this, e) < 20) { this.dead = true; e.dead = true; particles.push({x:e.x, y:e.y, vx:0, vy:0, life:15, maxLife:15, color:'#fff', size:3}); }
        }
        // 命中舰船
        if (dist(this, e) < e.radius) {
          e.hp -= this.damage; this.dead = true;
          particles.push({x: this.x, y: this.y, vx: 0, vy: 0, life: 10, maxLife: 10, color: '#fff', size: 4});
        }
      }
    }
  }
  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 = '#ff0000'; ctx.beginPath(); ctx.moveTo(-6, -2); ctx.lineTo(-12, 0); ctx.lineTo(-6, 2); ctx.fill();
    } else if (this.type === 'railgun') {
      ctx.fillStyle = '#00ffcc'; ctx.fillRect(-15, -2, 30, 4);
    } else if (this.type === 'laser_main') {
      ctx.fillStyle = 'rgba(102, 204, 255, 0.8)'; ctx.fillRect(-15, -1.5, 30, 3);
    } else if (this.type === 'laser_pd') {
      ctx.fillStyle = '#fff'; ctx.fillRect(-8, -1, 16, 2);
    }
    ctx.restore();
  }
}

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

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

function loop() {
  if (gameState !== 'playing') return;
  time++;
  ctx.fillStyle = 'rgba(3, 5, 10, 0.4)'; ctx.fillRect(0, 0, W, H);
  
  // 绘制星轨
  ctx.strokeStyle = 'rgba(50, 100, 150, 0.1)';
  for(let i=0; i<W; i+=100) { ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, H); ctx.stroke(); }
  for(let i=0; i<H; i+=100) { ctx.beginPath(); ctx.moveTo(0, i); ctx.lineTo(W, i); ctx.stroke(); }

  for (let e of entities) { e.update(); if (!e.dead) e.draw(); }
  for (let i = bullets.length - 1; i >= 0; i--) {
    bullets[i].update();
    if (!bullets[i].dead) bullets[i].draw(); else 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 blueAlive = entities.some(e => !e.isEnemy && !e.dead);
  let redAlive = entities.some(e => e.isEnemy && !e.dead);
  if (!blueAlive || !redAlive) {
    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(blueAlive ? '蓝方舰队 胜利' : '红方舰队 胜利', W/2, H/2);
    ctx.fillText('刷新页面重新配置', W/2, H/2 + 40);
    return;
  }
  requestAnimationFrame(loop);
}
</script>
</body>
</html>

Game Source: 深空舰队指挥官 - 最终实机

Creator: EpicCoder88

Libraries: none

Complexity: complex (533 lines, 24.5 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-mvn8upvl" to link back to the original. Then publish at arcadelab.ai/publish.