每日不定时更新,涵盖软件、教程、素材等资源下载。找免费资源就来酷玩资源网。

网页版魂斗罗游戏,梦回童年!

酸奶丿果冻 HTML源码 0

想起小时候玩的一款游戏,那时候还是游戏机玩的,魂斗罗,真的那时候玩的超级有意思,每天放学自己玩。
试着写了一版本地运行的纯HTML页面,玩了一下挺好。
操作方式
按键 功能
← → / A D 移动
Space / ↑ / W 跳跃
Z 射击
X 炸弹
↓ / S 趴下 / 空中向下瞄准
Enter 开始 / 重新开始

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>魂斗罗-单机版</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; display: flex; align-items: center; justify-content: center; }
canvas { display: block; image-rendering: pixelated; image-rendering: crisp-edges; background: #000; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
// ==================== CONTRA GAME ====================
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');

// --- Responsive Canvas ---
const GAME_W = 800, GAME_H = 480;
canvas.width = GAME_W; canvas.height = GAME_H;
function resize() {
  const s = Math.min(window.innerWidth / GAME_W, window.innerHeight / GAME_H);
  canvas.style.width = (GAME_W * s) + 'px';
  canvas.style.height = (GAME_H * s) + 'px';
}
window.addEventListener('resize', resize); resize();

// --- Audio Engine ---
const AudioCtx = window.AudioContext || window.webkitAudioContext;
let audioCtx;
function ensureAudio() { if (!audioCtx) audioCtx = new AudioCtx(); }
function playSound(type) {
  ensureAudio();
  const o = audioCtx.createOscillator();
  const g = audioCtx.createGain();
  o.connect(g); g.connect(audioCtx.destination);
  const t = audioCtx.currentTime;
  switch(type) {
    case 'shoot':
      o.type='square'; o.frequency.setValueAtTime(800,t); o.frequency.exponentialRampToValueAtTime(200,t+0.08);
      g.gain.setValueAtTime(0.15,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.08);
      o.start(t); o.stop(t+0.08); break;
    case 'spread':
      o.type='sawtooth'; o.frequency.setValueAtTime(600,t); o.frequency.exponentialRampToValueAtTime(100,t+0.12);
      g.gain.setValueAtTime(0.12,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.12);
      o.start(t); o.stop(t+0.12); break;
    case 'explosion':
      o.type='sawtooth'; o.frequency.setValueAtTime(200,t); o.frequency.exponentialRampToValueAtTime(30,t+0.3);
      g.gain.setValueAtTime(0.2,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.3);
      o.start(t); o.stop(t+0.3); break;
    case 'powerup':
      o.type='sine'; o.frequency.setValueAtTime(400,t); o.frequency.exponentialRampToValueAtTime(1200,t+0.2);
      g.gain.setValueAtTime(0.15,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.25);
      o.start(t); o.stop(t+0.25); break;
    case 'jump':
      o.type='sine'; o.frequency.setValueAtTime(300,t); o.frequency.exponentialRampToValueAtTime(600,t+0.1);
      g.gain.setValueAtTime(0.1,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.1);
      o.start(t); o.stop(t+0.1); break;
    case 'hit':
      o.type='square'; o.frequency.setValueAtTime(150,t); o.frequency.exponentialRampToValueAtTime(50,t+0.2);
      g.gain.setValueAtTime(0.2,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.2);
      o.start(t); o.stop(t+0.2); break;
    case 'boss':
      o.type='sawtooth'; o.frequency.setValueAtTime(100,t); o.frequency.exponentialRampToValueAtTime(40,t+0.5);
      g.gain.setValueAtTime(0.25,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.5);
      o.start(t); o.stop(t+0.5); break;
  }
}

// --- Input ---
const keys = {};
window.addEventListener('keydown', e => { keys[e.code] = true; e.preventDefault(); });
window.addEventListener('keyup', e => { keys[e.code] = false; e.preventDefault(); });

// --- Game State ---
let gameState = 'title'; // title, playing, gameover, win
let score = 0, lives = 3, hiScore = parseInt(localStorage.getItem('contraHi') || '0');
let cameraX = 0;
const LEVEL_W = 6400;
const GRAVITY = 0.5;
let frameCount = 0;
let screenShake = 0;
let bossDefeated = false;

// --- Platforms ---
const platforms = [];
function generateLevel() {
  platforms.length = 0;
  // Ground
  for (let x = 0; x < LEVEL_W; x += 200) {
    if (x > 400 && x < 600) continue; // gap
    if (x > 1800 && x < 2000) continue; // gap
    if (x > 3200 && x < 3400) continue; // gap
    platforms.push({ x, y: GAME_H - 40, w: 200, h: 40, type: 'ground' });
  }
  // Floating platforms
  const floats = [
    {x:300,y:320,w:120},{x:500,y:280,w:100},{x:700,y:240,w:140},
    {x:950,y:300,w:100},{x:1100,y:220,w:120},{x:1300,y:280,w:100},
    {x:1500,y:200,w:140},{x:1700,y:320,w:100},{x:1900,y:260,w:120},
    {x:2100,y:180,w:100},{x:2300,y:300,w:140},{x:2500,y:240,w:100},
    {x:2700,y:320,w:120},{x:2900,y:200,w:100},{x:3100,y:280,w:140},
    {x:3300,y:160,w:100},{x:3500,y:300,w:120},{x:3700,y:240,w:100},
    {x:3900,y:320,w:140},{x:4100,y:200,w:100},{x:4300,y:280,w:120},
    {x:4500,y:160,w:100},{x:4700,y:300,w:140},{x:4900,y:240,w:100},
    {x:5100,y:320,w:120},{x:5300,y:200,w:100},{x:5500,y:280,w:140},
    {x:5700,y:160,w:120},{x:5900,y:300,w:100}
  ];
  floats.forEach(f => platforms.push({ x:f.x, y:f.y, w:f.w, h:16, type:'platform' }));
  // Walls/barriers
  [800,1600,2400,3600,4800].forEach(x => {
    platforms.push({ x, y:GAME_H-120, w:30, h:80, type:'wall' });
  });
}

// --- Player ---
const player = {
  x: 100, y: 300, w: 28, h: 40, vx: 0, vy: 0,
  dir: 1, onGround: false, jumping: false,
  shooting: false, shootTimer: 0, shootCooldown: 8,
  weapon: 'normal', // normal, spread, rapid, laser
  weaponTimer: 0, invincible: 0,
  prone: false, animFrame: 0, animTimer: 0,
  alive: true, respawnTimer: 0
};

// --- Bullets ---
let playerBullets = [];
let enemyBullets = [];

// --- Enemies ---
let enemies = [];
let boss = null;

// --- Powerups ---
let powerups = [];

// --- Explosions ---
let explosions = [];

// --- Particles ---
let particles = [];

// --- Spawn enemies ---
function spawnEnemies() {
  enemies.length = 0;
  // Soldiers
  for (let i = 0; i < 40; i++) {
    const ex = 600 + i * 140 + Math.random() * 60;
    enemies.push({
      type: 'soldier', x: ex, y: GAME_H - 80, w: 24, h: 36,
      hp: 1, dir: -1, vx: 0, vy: 0, onGround: false,
      shootTimer: Math.random() * 120, alive: true,
      animFrame: 0, patrol: ex - 60, patrolMax: ex + 60,
      state: 'patrol' // patrol, chase, shoot
    });
  }
  // Turrets
  [1000, 1800, 2600, 3400, 4200, 5000, 5600].forEach(tx => {
    enemies.push({
      type: 'turret', x: tx, y: GAME_H - 72, w: 32, h: 32,
      hp: 3, dir: -1, shootTimer: 60, alive: true,
      angle: 0
    });
  });
  // Runners (fast enemies)
  for (let i = 0; i < 15; i++) {
    const ex = 1200 + i * 350 + Math.random() * 100;
    enemies.push({
      type: 'runner', x: ex, y: GAME_H - 76, w: 22, h: 34,
      hp: 1, dir: -1, vx: -3, vy: 0, onGround: false,
      alive: true, animFrame: 0
    });
  }
  // Powerup drops
  [700, 1400, 2200, 3000, 3800, 4600, 5400].forEach((px, i) => {
    const types = ['spread','rapid','laser','life','shield'];
    powerups.push({
      x: px, y: 180 + Math.random() * 80, w: 24, h: 24,
      type: types[i % types.length], alive: true, bobTimer: Math.random() * 100
    });
  });
}

// --- Boss ---
function spawnBoss() {
  boss = {
    x: LEVEL_W - 300, y: GAME_H - 200, w: 120, h: 160,
    hp: 50, maxHp: 50, phase: 0,
    shootTimer: 0, moveTimer: 0, alive: true,
    dir: -1, vy: 0
  };
}

// --- Collision ---
function rectCollide(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 resolveCollision(entity) {
  entity.onGround = false;
  for (const p of platforms) {
    if (rectCollide(entity, p)) {
      const overlapX = Math.min(entity.x + entity.w - p.x, p.x + p.w - entity.x);
      const overlapY = Math.min(entity.y + entity.h - p.y, p.y + p.h - entity.y);
      if (overlapY < overlapX) {
        if (entity.y + entity.h/2 < p.y + p.h/2) {
          entity.y = p.y - entity.h;
          entity.vy = 0;
          entity.onGround = true;
        } else {
          entity.y = p.y + p.h;
          entity.vy = 0;
        }
      } else {
        if (entity.x + entity.w/2 < p.x + p.w/2) {
          entity.x = p.x - entity.w;
        } else {
          entity.x = p.x + p.w;
        }
        entity.vx = 0;
      }
    }
  }
}

// --- Draw Helpers ---
function drawPixelChar(x, y, dir, frame, prone, shooting) {
  const sx = x - cameraX;
  if (sx < -50 || sx > GAME_W + 50) return;
  ctx.save();
  ctx.translate(sx + 14, y);
  ctx.scale(dir, 1);
  if (prone) {
    // Prone position
    ctx.fillStyle = '#1e88e5';
    ctx.fillRect(-14, 24, 28, 12); // body horizontal
    ctx.fillStyle = '#ffcc80';
    ctx.fillRect(10, 22, 10, 10); // head
    ctx.fillStyle = '#1565c0';
    ctx.fillRect(-14, 28, 28, 8); // legs
    // Gun
    ctx.fillStyle = '#757575';
    ctx.fillRect(18, 26, 16, 4);
  } else {
    // Head
    ctx.fillStyle = '#ffcc80';
    ctx.fillRect(-6, 0, 12, 12);
    // Hair/bandana
    ctx.fillStyle = '#e53935';
    ctx.fillRect(-7, 0, 14, 4);
    ctx.fillRect(-9, 2, 4, 3); // bandana tail
    // Eyes
    ctx.fillStyle = '#000';
    ctx.fillRect(2, 5, 3, 3);
    // Body
    ctx.fillStyle = '#1e88e5';
    ctx.fillRect(-8, 12, 16, 14);
    // Belt
    ctx.fillStyle = '#795548';
    ctx.fillRect(-8, 24, 16, 3);
    // Legs
    const legOff = Math.sin(frame * 0.3) * 4;
    ctx.fillStyle = '#1565c0';
    ctx.fillRect(-7, 27, 6, 13 + (frame % 2 === 0 ? legOff : -legOff));
    ctx.fillRect(1, 27, 6, 13 + (frame % 2 === 0 ? -legOff : legOff));
    // Boots
    ctx.fillStyle = '#4e342e';
    ctx.fillRect(-8, 37, 7, 3);
    ctx.fillRect(1, 37, 7, 3);
    // Gun arm
    ctx.fillStyle = '#757575';
    if (shooting) {
      ctx.fillRect(8, 14, 18, 4);
      // Muzzle flash
      ctx.fillStyle = '#ffeb3b';
      ctx.fillRect(26, 12, 6, 8);
    } else {
      ctx.fillRect(8, 16, 14, 3);
    }
    // Arm
    ctx.fillStyle = '#ffcc80';
    ctx.fillRect(6, 14, 4, 6);
  }
  ctx.restore();
}

function drawSoldier(e) {
  const sx = e.x - cameraX;
  if (sx < -50 || sx > GAME_W + 50) return;
  ctx.save();
  ctx.translate(sx + 12, e.y);
  ctx.scale(e.dir, 1);
  // Head
  ctx.fillStyle = '#4caf50';
  ctx.fillRect(-5, 0, 10, 10);
  // Helmet
  ctx.fillStyle = '#2e7d32';
  ctx.fillRect(-6, -2, 12, 6);
  // Eyes
  ctx.fillStyle = '#f00';
  ctx.fillRect(1, 4, 3, 2);
  // Body
  ctx.fillStyle = '#388e3c';
  ctx.fillRect(-7, 10, 14, 14);
  // Legs
  const lo = Math.sin(e.animFrame * 0.2) * 3;
  ctx.fillStyle = '#1b5e20';
  ctx.fillRect(-6, 24, 5, 12 + lo);
  ctx.fillRect(1, 24, 5, 12 - lo);
  // Gun
  ctx.fillStyle = '#616161';
  ctx.fillRect(7, 14, 12, 3);
  ctx.restore();
}

function drawTurret(e) {
  const sx = e.x - cameraX;
  if (sx < -50 || sx > GAME_W + 50) return;
  // Base
  ctx.fillStyle = '#616161';
  ctx.fillRect(sx, e.y + 16, 32, 16);
  // Dome
  ctx.fillStyle = '#757575';
  ctx.beginPath();
  ctx.arc(sx + 16, e.y + 16, 14, Math.PI, 0);
  ctx.fill();
  // Barrel
  ctx.save();
  ctx.translate(sx + 16, e.y + 14);
  ctx.rotate(e.angle);
  ctx.fillStyle = '#424242';
  ctx.fillRect(0, -3, 24, 6);
  ctx.restore();
  // Light
  ctx.fillStyle = frameCount % 30 < 15 ? '#f44336' : '#ff8a80';
  ctx.fillRect(sx + 13, e.y + 6, 6, 4);
}

function drawRunner(e) {
  const sx = e.x - cameraX;
  if (sx < -50 || sx > GAME_W + 50) return;
  ctx.save();
  ctx.translate(sx + 11, e.y);
  ctx.scale(e.dir, 1);
  // Head
  ctx.fillStyle = '#ff6f00';
  ctx.fillRect(-5, 0, 10, 10);
  // Eyes
  ctx.fillStyle = '#fff';
  ctx.fillRect(1, 3, 4, 4);
  ctx.fillStyle = '#f00';
  ctx.fillRect(2, 4, 2, 2);
  // Body
  ctx.fillStyle = '#e65100';
  ctx.fillRect(-6, 10, 12, 12);
  // Legs (running fast)
  const lo = Math.sin(e.animFrame * 0.5) * 6;
  ctx.fillStyle = '#bf360c';
  ctx.fillRect(-5, 22, 4, 12 + lo);
  ctx.fillRect(1, 22, 4, 12 - lo);
  // Claws
  ctx.fillStyle = '#ff3d00';
  ctx.fillRect(6, 12, 8, 3);
  ctx.restore();
}

function drawBoss(b) {
  if (!b || !b.alive) return;
  const sx = b.x - cameraX;
  ctx.save();
  // Main body
  ctx.fillStyle = '#455a64';
  ctx.fillRect(sx, b.y, b.w, b.h);
  // Armor plates
  ctx.fillStyle = '#37474f';
  ctx.fillRect(sx + 10, b.y + 10, b.w - 20, 30);
  ctx.fillRect(sx + 10, b.y + 60, b.w - 20, 30);
  ctx.fillRect(sx + 10, b.y + 110, b.w - 20, 30);
  // Core (weak point)
  const coreGlow = Math.sin(frameCount * 0.1) * 0.3 + 0.7;
  ctx.fillStyle = `rgba(244, 67, 54, ${coreGlow})`;
  ctx.beginPath();
  ctx.arc(sx + b.w/2, b.y + b.h/2, 20, 0, Math.PI * 2);
  ctx.fill();
  ctx.fillStyle = '#ff8a80';
  ctx.beginPath();
  ctx.arc(sx + b.w/2, b.y + b.h/2, 10, 0, Math.PI * 2);
  ctx.fill();
  // Cannons
  ctx.fillStyle = '#263238';
  ctx.fillRect(sx - 20, b.y + 20, 30, 12);
  ctx.fillRect(sx - 20, b.y + b.h - 40, 30, 12);
  ctx.fillRect(sx + b.w - 10, b.y + 40, 30, 12);
  ctx.fillRect(sx + b.w - 10, b.y + b.h - 60, 30, 12);
  // Eyes
  ctx.fillStyle = '#f44336';
  ctx.fillRect(sx + 25, b.y + 15, 12, 8);
  ctx.fillRect(sx + b.w - 37, b.y + 15, 12, 8);
  // HP bar
  ctx.fillStyle = '#333';
  ctx.fillRect(sx, b.y - 20, b.w, 10);
  ctx.fillStyle = b.hp > b.maxHp * 0.3 ? '#f44336' : '#ff1744';
  ctx.fillRect(sx + 1, b.y - 19, (b.w - 2) * (b.hp / b.maxHp), 8);
  // Name
  ctx.fillStyle = '#fff';
  ctx.font = '10px monospace';
  ctx.fillText('BOSS - ALIEN CORE', sx + 10, b.y - 24);
  ctx.restore();
}

// --- Background ---
function drawBackground() {
  // Sky gradient
  const grad = ctx.createLinearGradient(0, 0, 0, GAME_H);
  grad.addColorStop(0, '#0d1b2a');
  grad.addColorStop(0.4, '#1b2838');
  grad.addColorStop(0.7, '#2d4a3e');
  grad.addColorStop(1, '#1a3a2a');
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, GAME_W, GAME_H);

  // Stars
  ctx.fillStyle = '#fff';
  for (let i = 0; i < 60; i++) {
    const sx = ((i * 137 + 50) % GAME_W + GAME_W - cameraX * 0.05 % GAME_W) % GAME_W;
    const sy = (i * 89 + 20) % (GAME_H * 0.5);
    const size = (i % 3 === 0) ? 2 : 1;
    ctx.globalAlpha = 0.3 + (Math.sin(frameCount * 0.02 + i) * 0.3);
    ctx.fillRect(sx, sy, size, size);
  }
  ctx.globalAlpha = 1;

  // Mountains (parallax)
  ctx.fillStyle = '#1a3328';
  for (let i = 0; i < 12; i++) {
    const mx = i * 200 - (cameraX * 0.15) % 2400;
    const mh = 80 + (i * 47) % 60;
    ctx.beginPath();
    ctx.moveTo(mx, GAME_H - 40);
    ctx.lineTo(mx + 100, GAME_H - 40 - mh);
    ctx.lineTo(mx + 200, GAME_H - 40);
    ctx.fill();
  }

  // Trees (parallax)
  ctx.fillStyle = '#0d2818';
  for (let i = 0; i < 20; i++) {
    const tx = i * 150 - (cameraX * 0.3) % 3000;
    const th = 40 + (i * 31) % 30;
    ctx.fillRect(tx + 15, GAME_H - 40 - th, 10, th);
    ctx.beginPath();
    ctx.moveTo(tx, GAME_H - 40 - th + 10);
    ctx.lineTo(tx + 20, GAME_H - 40 - th - 25);
    ctx.lineTo(tx + 40, GAME_H - 40 - th + 10);
    ctx.fill();
  }
}

function drawPlatforms() {
  for (const p of platforms) {
    const sx = p.x - cameraX;
    if (sx > GAME_W + 10 || sx + p.w < -10) continue;
    if (p.type === 'ground') {
      ctx.fillStyle = '#3e2723';
      ctx.fillRect(sx, p.y, p.w, p.h);
      ctx.fillStyle = '#4caf50';
      ctx.fillRect(sx, p.y, p.w, 6);
      ctx.fillStyle = '#2e7d32';
      ctx.fillRect(sx, p.y + 6, p.w, 4);
      // Dirt texture
      ctx.fillStyle = '#5d4037';
      for (let dx = 0; dx < p.w; dx += 20) {
        ctx.fillRect(sx + dx + 5, p.y + 15, 8, 4);
        ctx.fillRect(sx + dx + 2, p.y + 25, 6, 3);
      }
    } else if (p.type === 'platform') {
      ctx.fillStyle = '#78909c';
      ctx.fillRect(sx, p.y, p.w, p.h);
      ctx.fillStyle = '#90a4ae';
      ctx.fillRect(sx, p.y, p.w, 4);
      ctx.fillStyle = '#546e7a';
      ctx.fillRect(sx + 2, p.y + 6, p.w - 4, 4);
      // Rivets
      ctx.fillStyle = '#b0bec5';
      ctx.fillRect(sx + 4, p.y + 2, 3, 3);
      ctx.fillRect(sx + p.w - 7, p.y + 2, 3, 3);
    } else if (p.type === 'wall') {
      ctx.fillStyle = '#5d4037';
      ctx.fillRect(sx, p.y, p.w, p.h);
      ctx.fillStyle = '#6d4c41';
      for (let dy = 0; dy < p.h; dy += 12) {
        ctx.fillRect(sx, p.y + dy, p.w, 1);
        ctx.fillRect(sx + p.w/2, p.y + dy, 1, 12);
      }
    }
  }
}

function drawPowerups() {
  for (const p of powerups) {
    if (!p.alive) continue;
    const sx = p.x - cameraX;
    if (sx < -30 || sx > GAME_W + 30) continue;
    const bob = Math.sin((frameCount + p.bobTimer) * 0.05) * 4;
    const py = p.y + bob;
    // Wing container
    ctx.fillStyle = '#e53935';
    ctx.fillRect(sx - 2, py - 2, 28, 28);
    ctx.fillStyle = '#c62828';
    ctx.fillRect(sx, py, 24, 24);
    // Letter
    ctx.fillStyle = '#fff';
    ctx.font = 'bold 14px monospace';
    const letters = { spread: 'S', rapid: 'R', laser: 'L', life: '1UP', shield: '&#128737;' };
    ctx.fillText(letters[p.type] || '?', sx + 4, py + 17);
    // Glow
    ctx.globalAlpha = 0.3 + Math.sin(frameCount * 0.1) * 0.2;
    ctx.fillStyle = '#ffeb3b';
    ctx.fillRect(sx - 4, py - 4, 32, 32);
    ctx.globalAlpha = 1;
  }
}

function drawBullets() {
  // Player bullets
  for (const b of playerBullets) {
    const sx = b.x - cameraX;
    if (sx < -10 || sx > GAME_W + 10) continue;
    if (b.type === 'laser') {
      ctx.fillStyle = '#00e5ff';
      ctx.fillRect(sx, b.y - 1, b.w, 3);
      ctx.fillStyle = '#b2ebf2';
      ctx.fillRect(sx, b.y, b.w, 1);
    } else if (b.type === 'spread') {
      ctx.fillStyle = '#ff9800';
      ctx.beginPath();
      ctx.arc(sx + b.w/2, b.y + b.h/2, 3, 0, Math.PI * 2);
      ctx.fill();
    } else {
      ctx.fillStyle = '#ffeb3b';
      ctx.fillRect(sx, b.y, b.w, b.h);
      ctx.fillStyle = '#fff';
      ctx.fillRect(sx + 1, b.y + 1, b.w - 2, b.h - 2);
    }
  }
  // Enemy bullets
  for (const b of enemyBullets) {
    const sx = b.x - cameraX;
    if (sx < -10 || sx > GAME_W + 10) continue;
    ctx.fillStyle = '#f44336';
    ctx.beginPath();
    ctx.arc(sx + 3, b.y + 3, 4, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#ff8a80';
    ctx.beginPath();
    ctx.arc(sx + 3, b.y + 3, 2, 0, Math.PI * 2);
    ctx.fill();
  }
}

function drawExplosions() {
  for (let i = explosions.length - 1; i >= 0; i--) {
    const e = explosions[i];
    const sx = e.x - cameraX;
    const progress = e.timer / e.maxTimer;
    const r = e.radius * (1 - progress * 0.5);
    ctx.globalAlpha = progress;
    ctx.fillStyle = '#ff6f00';
    ctx.beginPath(); ctx.arc(sx, e.y, r, 0, Math.PI * 2); ctx.fill();
    ctx.fillStyle = '#ffeb3b';
    ctx.beginPath(); ctx.arc(sx, e.y, r * 0.6, 0, Math.PI * 2); ctx.fill();
    ctx.fillStyle = '#fff';
    ctx.beginPath(); ctx.arc(sx, e.y, r * 0.2, 0, Math.PI * 2); ctx.fill();
    ctx.globalAlpha = 1;
    e.timer--;
    if (e.timer <= 0) explosions.splice(i, 1);
  }
}

function drawParticles() {
  for (let i = particles.length - 1; i >= 0; i--) {
    const p = particles[i];
    const sx = p.x - cameraX;
    ctx.globalAlpha = p.life / p.maxLife;
    ctx.fillStyle = p.color;
    ctx.fillRect(sx, p.y, p.size, p.size);
    p.x += p.vx; p.y += p.vy; p.vy += 0.1; p.life--;
    if (p.life <= 0) particles.splice(i, 1);
  }
  ctx.globalAlpha = 1;
}

function drawHUD() {
  // Score
  ctx.fillStyle = 'rgba(0,0,0,0.5)';
  ctx.fillRect(0, 0, GAME_W, 36);
  ctx.fillStyle = '#fff';
  ctx.font = 'bold 16px monospace';
  ctx.fillText(`SCORE: ${score.toString().padStart(8, '0')}`, 10, 24);
  ctx.fillText(`HI: ${hiScore.toString().padStart(8, '0')}`, 220, 24);
  // Lives
  ctx.fillStyle = '#e53935';
  for (let i = 0; i < lives; i++) {
    ctx.fillRect(460 + i * 22, 10, 14, 16);
    ctx.fillStyle = '#ffcc80';
    ctx.fillRect(463 + i * 22, 6, 8, 8);
    ctx.fillStyle = '#e53935';
  }
  // Weapon
  ctx.fillStyle = '#ffeb3b';
  ctx.font = '12px monospace';
  const wNames = { normal: 'NORMAL', spread: 'SPREAD', rapid: 'RAPID', laser: 'LASER' };
  ctx.fillText(`WEAPON: ${wNames[player.weapon]}`, 580, 24);
  // Weapon timer
  if (player.weaponTimer > 0 && player.weapon !== 'normal') {
    ctx.fillStyle = '#333';
    ctx.fillRect(580, 28, 80, 4);
    ctx.fillStyle = '#ffeb3b';
    ctx.fillRect(580, 28, 80 * (player.weaponTimer / 600), 4);
  }
  // Progress bar
  ctx.fillStyle = '#333';
  ctx.fillRect(GAME_W - 160, 14, 150, 8);
  ctx.fillStyle = '#4caf50';
  ctx.fillRect(GAME_W - 160, 14, 150 * Math.min(1, cameraX / (LEVEL_W - GAME_W)), 8);
  ctx.fillStyle = '#fff';
  ctx.font = '8px monospace';
  ctx.fillText('PROGRESS', GAME_W - 145, 12);
}

// --- Title Screen ---
function drawTitle() {
  // Background
  ctx.fillStyle = '#0a0a0a';
  ctx.fillRect(0, 0, GAME_W, GAME_H);

  // Animated background lines
  ctx.strokeStyle = 'rgba(229, 57, 53, 0.1)';
  ctx.lineWidth = 1;
  for (let i = 0; i < 20; i++) {
    const y = (i * 30 + frameCount * 0.5) % GAME_H;
    ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(GAME_W, y); ctx.stroke();
  }

  // Title
  ctx.save();
  const titleY = 100 + Math.sin(frameCount * 0.03) * 8;
  ctx.fillStyle = '#e53935';
  ctx.font = 'bold 72px monospace';
  ctx.textAlign = 'center';
  ctx.fillText('CONTRA', GAME_W / 2, titleY);
  // Title shadow
  ctx.fillStyle = '#b71c1c';
  ctx.fillText('CONTRA', GAME_W / 2 + 3, titleY + 3);
  // Subtitle
  ctx.fillStyle = '#ffeb3b';
  ctx.font = 'bold 20px monospace';
  ctx.fillText('魂 斗 罗', GAME_W / 2, titleY + 40);

  // Blinking text
  if (frameCount % 60 < 40) {
    ctx.fillStyle = '#fff';
    ctx.font = '18px monospace';
    ctx.fillText('PRESS ENTER TO START', GAME_W / 2, 280);
  }

  // Controls
  ctx.fillStyle = '#90a4ae';
  ctx.font = '13px monospace';
  ctx.fillText('← → : MOVE    ↑ : AIM UP    ↓ : PRONE', GAME_W / 2, 340);
  ctx.fillText('SPACE : JUMP    Z : SHOOT    X : BOMB', GAME_W / 2, 365);

  // Hi-Score
  ctx.fillStyle = '#ffeb3b';
  ctx.font = '14px monospace';
  ctx.fillText(`HI-SCORE: ${hiScore.toString().padStart(8, '0')}`, GAME_W / 2, 420);

  ctx.restore();
}

function drawGameOver() {
  ctx.fillStyle = 'rgba(0,0,0,0.7)';
  ctx.fillRect(0, 0, GAME_W, GAME_H);
  ctx.save();
  ctx.textAlign = 'center';
  ctx.fillStyle = '#e53935';
  ctx.font = 'bold 56px monospace';
  ctx.fillText('GAME OVER', GAME_W / 2, GAME_H / 2 - 30);
  ctx.fillStyle = '#fff';
  ctx.font = '20px monospace';
  ctx.fillText(`FINAL SCORE: ${score.toString().padStart(8, '0')}`, GAME_W / 2, GAME_H / 2 + 20);
  if (frameCount % 60 < 40) {
    ctx.fillStyle = '#ffeb3b';
    ctx.font = '16px monospace';
    ctx.fillText('PRESS ENTER TO CONTINUE', GAME_W / 2, GAME_H / 2 + 70);
  }
  ctx.restore();
}

function drawWin() {
  ctx.fillStyle = 'rgba(0,0,0,0.7)';
  ctx.fillRect(0, 0, GAME_W, GAME_H);
  ctx.save();
  ctx.textAlign = 'center';
  ctx.fillStyle = '#4caf50';
  ctx.font = 'bold 48px monospace';
  ctx.fillText('MISSION COMPLETE!', GAME_W / 2, GAME_H / 2 - 40);
  ctx.fillStyle = '#ffeb3b';
  ctx.font = 'bold 24px monospace';
  ctx.fillText(`SCORE: ${score.toString().padStart(8, '0')}`, GAME_W / 2, GAME_H / 2 + 10);
  ctx.fillStyle = '#fff';
  ctx.font = '16px monospace';
  ctx.fillText('THE ALIEN CORE HAS BEEN DESTROYED!', GAME_W / 2, GAME_H / 2 + 50);
  if (frameCount % 60 < 40) {
    ctx.fillStyle = '#90a4ae';
    ctx.font = '14px monospace';
    ctx.fillText('PRESS ENTER TO PLAY AGAIN', GAME_W / 2, GAME_H / 2 + 90);
  }
  ctx.restore();
}

// --- Update Functions ---
function updatePlayer() {
  if (!player.alive) {
    player.respawnTimer--;
    if (player.respawnTimer <= 0) {
      if (lives > 0) {
        player.alive = true;
        player.x = cameraX + 100;
        player.y = 200;
        player.vx = 0; player.vy = 0;
        player.invincible = 120;
        player.weapon = 'normal';
      } else {
        gameState = 'gameover';
        if (score > hiScore) { hiScore = score; localStorage.setItem('contraHi', hiScore); }
      }
    }
    return;
  }

  // Movement
  const speed = 3;
  if (keys['ArrowLeft'] || keys['KeyA']) { player.vx = -speed; player.dir = -1; }
  else if (keys['ArrowRight'] || keys['KeyD']) { player.vx = speed; player.dir = 1; }
  else { player.vx = 0; }

  // Prone
  player.prone = (keys['ArrowDown'] || keys['KeyS']) && player.onGround;
  if (player.prone) { player.h = 20; player.vx *= 0.5; }
  else { player.h = 40; }

  // Jump
  if ((keys['Space'] || keys['ArrowUp'] || keys['KeyW']) && player.onGround) {
    player.vy = -10;
    player.jumping = true;
    player.onGround = false;
    playSound('jump');
  }

  // Gravity
  player.vy += GRAVITY;
  player.x += player.vx;
  player.y += player.vy;

  // Resolve collisions
  resolveCollision(player);

  // Bounds
  if (player.x < cameraX) player.x = cameraX;
  if (player.y > GAME_H + 50) {
    playerDie();
    return;
  }

  // Shooting
  if (player.shootTimer > 0) player.shootTimer--;
  const cooldown = player.weapon === 'rapid' ? 4 : player.shootCooldown;
  if (keys['KeyZ'] && player.shootTimer <= 0) {
    shoot();
    player.shootTimer = cooldown;
  }

  // Bomb (X key)
  if (keys['KeyX'] && !player._bombUsed) {
    player._bombUsed = true;
    // Screen clear bomb
    for (const e of enemies) {
      if (e.alive && Math.abs(e.x - player.x) < 400) {
        e.alive = false;
        score += 100;
        explosions.push({ x: e.x, y: e.y, radius: 30, timer: 20, maxTimer: 20 });
      }
    }
    screenShake = 15;
    playSound('explosion');
  }
  if (!keys['KeyX']) player._bombUsed = false;

  // Invincibility
  if (player.invincible > 0) player.invincible--;

  // Weapon timer
  if (player.weaponTimer > 0) {
    player.weaponTimer--;
    if (player.weaponTimer <= 0) player.weapon = 'normal';
  }

  // Animation
  if (Math.abs(player.vx) > 0) {
    player.animTimer++;
    if (player.animTimer > 6) { player.animFrame++; player.animTimer = 0; }
  }

  // Camera
  const targetCam = player.x - GAME_W * 0.35;
  cameraX += (targetCam - cameraX) * 0.08;
  cameraX = Math.max(0, Math.min(LEVEL_W - GAME_W, cameraX));

  // Boss trigger
  if (cameraX > LEVEL_W - GAME_W - 100 && !boss && !bossDefeated) {
    spawnBoss();
    playSound('boss');
  }
}

function shoot() {
  const bx = player.x + (player.dir > 0 ? player.w : -8);
  const by = player.y + (player.prone ? 26 : 14);
  let bvx = player.dir * 8;
  let bvy = 0;

  // Aim up
  if (keys['ArrowUp'] && !player.onGround) {
    bvy = -8;
    bvx = player.dir * 4;
  } else if (keys['ArrowUp'] && player.onGround && !player.prone) {
    bvy = -8;
    bvx = 0;
  }
  // Aim down (in air)
  if (keys['ArrowDown'] && !player.onGround) {
    bvy = 8;
    bvx = player.dir * 4;
  }

  if (player.weapon === 'spread') {
    playSound('spread');
    for (let a = -0.3; a <= 0.3; a += 0.15) {
      playerBullets.push({
        x: bx, y: by, w: 6, h: 6,
        vx: bvx * Math.cos(a) - bvy * Math.sin(a),
        vy: bvx * Math.sin(a) + bvy * Math.cos(a),
        type: 'spread', life: 60
      });
    }
  } else if (player.weapon === 'laser') {
    playSound('spread');
    playerBullets.push({
      x: bx, y: by, w: 30, h: 3,
      vx: bvx * 1.5, vy: bvy * 1.5,
      type: 'laser', life: 40
    });
  } else {
    playSound('shoot');
    playerBullets.push({
      x: bx, y: by, w: 8, h: 3,
      vx: bvx, vy: bvy,
      type: 'normal', life: 80
    });
  }
  player.shooting = true;
  setTimeout(() => player.shooting = false, 80);
}

function playerDie() {
  if (player.invincible > 0) return;
  player.alive = false;
  lives--;
  player.respawnTimer = 90;
  screenShake = 20;
  playSound('hit');
  explosions.push({ x: player.x, y: player.y + 20, radius: 25, timer: 25, maxTimer: 25 });
  for (let i = 0; i < 15; i++) {
    particles.push({
      x: player.x + 14, y: player.y + 20,
      vx: (Math.random() - 0.5) * 6, vy: (Math.random() - 0.5) * 6 - 3,
      size: 2 + Math.random() * 3, color: ['#e53935','#ffeb3b','#ff9800'][Math.floor(Math.random()*3)],
      life: 30 + Math.random() * 20, maxLife: 50
    });
  }
}

function updateBullets() {
  // Player bullets
  for (let i = playerBullets.length - 1; i >= 0; i--) {
    const b = playerBullets[i];
    b.x += b.vx; b.y += b.vy; b.life--;
    if (b.life <= 0 || b.x < cameraX - 50 || b.x > cameraX + GAME_W + 50 || b.y < -20 || b.y > GAME_H + 20) {
      playerBullets.splice(i, 1); continue;
    }
    // Hit enemies
    for (const e of enemies) {
      if (!e.alive) continue;
      if (rectCollide(b, e)) {
        e.hp--;
        if (e.hp <= 0) {
          e.alive = false;
          score += e.type === 'turret' ? 300 : e.type === 'runner' ? 200 : 100;
          explosions.push({ x: e.x + e.w/2, y: e.y + e.h/2, radius: 20, timer: 18, maxTimer: 18 });
          playSound('explosion');
          for (let j = 0; j < 8; j++) {
            particles.push({
              x: e.x + e.w/2, y: e.y + e.h/2,
              vx: (Math.random()-0.5)*5, vy: (Math.random()-0.5)*5-2,
              size: 2+Math.random()*2, color: ['#ff6f00','#ffeb3b','#fff'][Math.floor(Math.random()*3)],
              life: 20+Math.random()*15, maxLife: 35
            });
          }
        }
        playerBullets.splice(i, 1); break;
      }
    }
    // Hit boss
    if (boss && boss.alive && rectCollide(b, boss)) {
      boss.hp--;
      if (boss.hp <= 0) {
        boss.alive = false;
        bossDefeated = true;
        score += 5000;
        screenShake = 30;
        playSound('explosion');
        for (let k = 0; k < 30; k++) {
          explosions.push({
            x: boss.x + Math.random() * boss.w,
            y: boss.y + Math.random() * boss.h,
            radius: 15 + Math.random() * 20,
            timer: 20 + Math.random() * 20, maxTimer: 40
          });
        }
        setTimeout(() => { gameState = 'win'; if (score > hiScore) { hiScore = score; localStorage.setItem('contraHi', hiScore); } }, 2000);
      }
      playerBullets.splice(i, 1);
      playSound('hit');
    }
    // Hit platforms (walls only)
    for (const p of platforms) {
      if (p.type === 'wall' && rectCollide(b, p)) {
        playerBullets.splice(i, 1); break;
      }
    }
  }

  // Enemy bullets
  for (let i = enemyBullets.length - 1; i >= 0; i--) {
    const b = enemyBullets[i];
    b.x += b.vx; b.y += b.vy; b.life--;
    if (b.life <= 0 || b.x < cameraX - 100 || b.x > cameraX + GAME_W + 100) {
      enemyBullets.splice(i, 1); continue;
    }
    // Hit player
    if (player.alive && rectCollide({ x: b.x, y: b.y, w: 6, h: 6 }, player)) {
      playerDie();
      enemyBullets.splice(i, 1);
    }
  }
}

function updateEnemies() {
  for (const e of enemies) {
    if (!e.alive) continue;
    const dx = player.x - e.x;
    const dist = Math.abs(dx);

    if (e.type === 'soldier') {
      // AI
      if (dist < 350 && player.alive) {
        e.dir = dx > 0 ? 1 : -1;
        e.state = dist < 250 ? 'shoot' : 'chase';
      } else {
        e.state = 'patrol';
      }

      if (e.state === 'patrol') {
        e.vx = e.dir * 0.8;
        if (e.x <= e.patrol) e.dir = 1;
        if (e.x >= e.patrolMax) e.dir = -1;
      } else if (e.state === 'chase') {
        e.vx = e.dir * 1.5;
      } else {
        e.vx = 0;
        e.shootTimer--;
        if (e.shootTimer <= 0 && dist < 400) {
          e.shootTimer = 60 + Math.random() * 40;
          const angle = Math.atan2(player.y - e.y, player.x - e.x);
          enemyBullets.push({
            x: e.x + 12, y: e.y + 14,
            vx: Math.cos(angle) * 3, vy: Math.sin(angle) * 3,
            life: 120
          });
        }
      }

      e.vy += GRAVITY;
      e.x += e.vx;
      e.y += e.vy;
      resolveCollision(e);
      e.animFrame++;

      // Touch damage
      if (player.alive && rectCollide(player, e)) playerDie();

    } else if (e.type === 'turret') {
      if (player.alive) {
        e.angle = Math.atan2(player.y - e.y, player.x - e.x);
        e.dir = dx > 0 ? 1 : -1;
      }
      e.shootTimer--;
      if (e.shootTimer <= 0 && dist < 500) {
        e.shootTimer = 80;
        enemyBullets.push({
          x: e.x + 16 + Math.cos(e.angle) * 24,
          y: e.y + 14 + Math.sin(e.angle) * 24,
          vx: Math.cos(e.angle) * 4, vy: Math.sin(e.angle) * 4,
          life: 100
        });
      }
    } else if (e.type === 'runner') {
      if (dist < 500 && player.alive) {
        e.dir = dx > 0 ? 1 : -1;
        e.vx = e.dir * 3.5;
      }
      e.vy += GRAVITY;
      e.x += e.vx;
      e.y += e.vy;
      resolveCollision(e);
      e.animFrame++;

      if (player.alive && rectCollide(player, e)) playerDie();
    }
  }
}

function updateBoss() {
  if (!boss || !boss.alive) return;
  boss.shootTimer--;
  boss.moveTimer++;

  // Movement
  boss.y = GAME_H - 200 + Math.sin(boss.moveTimer * 0.02) * 40;

  // Attack patterns
  if (boss.shootTimer <= 0) {
    boss.phase = (boss.phase + 1) % 3;
    if (boss.phase === 0) {
      // Spread shot
      for (let a = -0.6; a <= 0.6; a += 0.2) {
        enemyBullets.push({
          x: boss.x + 20, y: boss.y + boss.h / 2,
          vx: Math.cos(a + Math.PI) * 4, vy: Math.sin(a + Math.PI) * 4,
          life: 150
        });
      }
      boss.shootTimer = 40;
    } else if (boss.phase === 1) {
      // Aimed shot
      const angle = Math.atan2(player.y - boss.y - boss.h/2, player.x - boss.x);
      for (let i = 0; i < 3; i++) {
        setTimeout(() => {
          if (boss && boss.alive) {
            enemyBullets.push({
              x: boss.x + 20, y: boss.y + boss.h / 2,
              vx: Math.cos(angle) * 5, vy: Math.sin(angle) * 5,
              life: 120
            });
          }
        }, i * 100);
      }
      boss.shootTimer = 60;
    } else {
      // Rain of bullets
      for (let i = 0; i < 5; i++) {
        enemyBullets.push({
          x: boss.x + Math.random() * boss.w,
          y: boss.y + boss.h,
          vx: (Math.random() - 0.5) * 3,
          vy: 2 + Math.random() * 3,
          life: 120
        });
      }
      boss.shootTimer = 50;
    }
    playSound('shoot');
  }

  // Touch damage
  if (player.alive && rectCollide(player, boss)) playerDie();
}

function updatePowerups() {
  for (const p of powerups) {
    if (!p.alive) continue;
    if (player.alive && rectCollide(player, p)) {
      p.alive = false;
      playSound('powerup');
      score += 500;
      switch (p.type) {
        case 'spread': player.weapon = 'spread'; player.weaponTimer = 600; break;
        case 'rapid': player.weapon = 'rapid'; player.weaponTimer = 600; break;
        case 'laser': player.weapon = 'laser'; player.weaponTimer = 600; break;
        case 'life': lives = Math.min(lives + 1, 9); break;
        case 'shield': player.invincible = 300; break;
      }
      for (let i = 0; i < 10; i++) {
        particles.push({
          x: p.x + 12, y: p.y + 12,
          vx: (Math.random()-0.5)*4, vy: (Math.random()-0.5)*4,
          size: 2+Math.random()*2, color: '#ffeb3b',
          life: 20+Math.random()*10, maxLife: 30
        });
      }
    }
  }
}

// --- Game Init ---
function initGame() {
  score = 0; lives = 3; cameraX = 0;
  bossDefeated = false; boss = null;
  playerBullets = []; enemyBullets = [];
  explosions = []; particles = [];
  player.x = 100; player.y = 300;
  player.vx = 0; player.vy = 0;
  player.alive = true; player.invincible = 60;
  player.weapon = 'normal'; player.weaponTimer = 0;
  player.dir = 1; player.prone = false;
  generateLevel();
  spawnEnemies();
}

// --- Main Loop ---
function gameLoop() {
  frameCount++;

  if (gameState === 'title') {
    drawTitle();
    if (keys['Enter'] || keys['Space']) {
      gameState = 'playing';
      initGame();
      ensureAudio();
    }
  } else if (gameState === 'playing') {
    // Screen shake
    ctx.save();
    if (screenShake > 0) {
      ctx.translate(
        (Math.random() - 0.5) * screenShake,
        (Math.random() - 0.5) * screenShake
      );
      screenShake--;
    }

    drawBackground();
    drawPlatforms();
    drawPowerups();
    updatePlayer();
    updateEnemies();
    updateBoss();
    updateBullets();
    updatePowerups();

    // Draw enemies
    for (const e of enemies) {
      if (!e.alive) continue;
      const sx = e.x - cameraX;
      if (sx < -50 || sx > GAME_W + 50) continue;
      if (e.type === 'soldier') drawSoldier(e);
      else if (e.type === 'turret') drawTurret(e);
      else if (e.type === 'runner') drawRunner(e);
    }

    drawBoss(boss);
    drawBullets();
    drawExplosions();
    drawParticles();

    // Draw player
    if (player.alive) {
      if (player.invincible > 0 && frameCount % 4 < 2) {
        // Flash when invincible
      } else {
        drawPixelChar(player.x, player.y, player.dir, player.animFrame, player.prone, player.shooting);
      }
    }

    drawHUD();
    ctx.restore();
  } else if (gameState === 'gameover') {
    drawBackground();
    drawPlatforms();
    drawGameOver();
    if (keys['Enter']) { gameState = 'title'; keys['Enter'] = false; }
  } else if (gameState === 'win') {
    drawBackground();
    drawPlatforms();
    drawWin();
    if (keys['Enter']) { gameState = 'title'; keys['Enter'] = false; }
  }

  requestAnimationFrame(gameLoop);
}

// --- Start ---
gameLoop();
</script>
</body>
</html>

免责声明:

本站提供的资源,都来自网络,版权争议与本站无关,所有内容及软件的文章仅限用于学习和研究目的。不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负,我们不保证内容的长久可用性,通过使用本站内容随之而来的风险与本站无关,您必须在下载后的24个小时之内,从您的电脑/手机中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。侵删请致信E-mail: kuwanw@qq.com

同类推荐
评论列表
签到