深空舰队模拟器 - 完整版
by EpicCoder88593 lines20.6 KB
<!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: #02040a; font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif; touch-action: none; }
canvas { display: block; width: 100vw; height: 100vh; }
/* 战前配置UI */
#ui-layer {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(2, 4, 10, 0.9); color: #fff; display: flex; flex-direction: column;
align-items: center; justify-content: center; z-index: 10;
}
.panel { display: flex; gap: 20px; margin-bottom: 30px; }
.faction { background: rgba(255,255,255,0.05); padding: 20px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.1); min-width: 250px; }
.blue h2 { color: #66ccff; } .red h2 { color: #ff3366; }
.ship-row { display: flex; justify-content: space-between; align-items: center; margin: 10px 0; font-size: 14px; }
.btn-ctrl { background: #223; border: 1px solid #445; color: #fff; width: 30px; height: 30px; border-radius: 6px; font-size: 18px; cursor: pointer; }
.btn-ctrl:active { background: #445; }
.count { width: 40px; text-align: center; font-weight: bold; }
#start-btn {
padding: 15px 60px; font-size: 20px; font-weight: bold; color: #000;
background: linear-gradient(90deg, #66ccff, #00ffcc); border: none; border-radius: 30px;
box-shadow: 0 0 20px rgba(102, 204, 255, 0.5); cursor: pointer;
}
#start-btn:active { transform: scale(0.95); }
/* 战斗UI */
#hud { position: absolute; top: 10px; left: 10px; right: 10px; display: flex; justify-content: space-between; pointer-events: none; font-size: 12px; color: #a0b0d0; text-shadow: 1px 1px 2px #000;}
</style>
</head>
<body>
<div id="hud" style="display:none;">
<div id="hud-left"></div>
<div id="hud-right"></div>
</div>
<div id="ui-layer">
<h1 style="letter-spacing: 2px;">深空舰队交战模拟</h1>
<p style="color:#889; font-size:14px; margin-bottom:20px;">2060年 · 太阳系资源争夺战</p>
<div class="panel">
<div class="faction blue">
<h2>蓝方舰队</h2>
<div class="ship-row"><span>激光舰(线型)</span> <button class="btn-ctrl" onclick="changeCount('b_laser',-1)">-</button> <span class="count" id="b_laser">2</span> <button class="btn-ctrl" onclick="changeCount('b_laser',1)">+</button></div>
<div class="ship-row"><span>突击舰(十字型)</span> <button class="btn-ctrl" onclick="changeCount('b_assault',-1)">-</button> <span class="count" id="b_assault">1</span> <button class="btn-ctrl" onclick="changeCount('b_assault',1)">+</button></div>
</div>
<div class="faction red">
<h2>红方舰队</h2>
<div class="ship-row"><span>导弹舰(多支型)</span> <button class="btn-ctrl" onclick="changeCount('r_missile',-1)">-</button> <span class="count" id="r_missile">2</span> <button class="btn-ctrl" onclick="changeCount('r_missile',1)">+</button></div>
<div class="ship-row"><span>补给舰(接口型)</span> <button class="btn-ctrl" onclick="changeCount('r_supply',-1)">-</button> <span class="count" id="r_supply">1</span> <button class="btn-ctrl" onclick="changeCount('r_supply',1)">+</button></div>
</div>
</div>
<button id="start-btn" onclick="startGame()">开 战</button>
</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();
// ======= 核心数据与配置 =======
let entities = [], bullets = [], particles = [];
let gameState = 'setup'; // setup, playing, ended
let fleetConfig = { b_laser: 2, b_assault: 1, r_missile: 2, r_supply: 1 };
function changeCount(id, delta) {
let val = parseInt(document.getElementById(id).innerText);
val = Math.max(0, Math.min(8, val + delta));
document.getElementById(id).innerText = val;
fleetConfig[id] = val;
}
// 基础物理计算
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;
}
// ======= 实体基类 =======
class Ship {
constructor(x, y, color, isEnemy, config) {
this.x = x; this.y = y;
this.vx = 0; this.vy = 0;
this.angle = isEnemy ? Math.PI : 0;
this.color = color; this.isEnemy = isEnemy;
this.dead = false;
// 读取配置 (手稿中的设定)
this.maxHp = 500; this.hp = this.maxHp;
this.radius = 18;
this.crew = config.crew;
this.maxSpeed = config.speed / 200; // 适配屏幕比例
this.turnSpeed = config.turnSpeed / 60; // 转换到每帧
this.detectRange = config.detectRange / 3; // 适配屏幕
this.fireCooldown = 0;
this.pdCooldown = 0; // 近防炮冷却
this.target = null;
// 状态机
this.state = 'patrol'; // patrol, combat, evade, dock
this.wanderAngle = Math.random() * Math.PI * 2;
}
update() {
if (this.dead) return;
this.vx *= 0.96; this.vy *= 0.96; // 太空阻尼模拟
this.x += this.vx; this.y += this.vy;
// 边界反弹
if (this.x < 50 || this.x > W - 50) this.vx *= -1.05;
if (this.y < 50 || this.y > H - 50) this.vy *= -1.05;
if (this.fireCooldown > 0) this.fireCooldown--;
if (this.pdCooldown > 0) this.pdCooldown--;
if (this.hp <= 0 && !this.dead) {
this.dead = true;
createExplosion(this.x, this.y, this.color, 30);
}
}
// 驾驶逻辑
driveToAngle(targetAngle, throttle = 1.0) {
const diff = normalizeAngle(targetAngle - this.angle);
// 转向
this.angle += Math.sign(diff) * Math.min(Math.abs(diff), this.turnSpeed);
// 推进 (对准一定程度才能加速)
if (Math.abs(diff) < 0.8) {
this.vx += Math.cos(this.angle) * 0.08 * throttle;
this.vy += Math.sin(this.angle) * 0.08 * throttle;
const sp = Math.hypot(this.vx, this.vy);
if (sp > this.maxSpeed) {
this.vx = (this.vx / sp) * this.maxSpeed;
this.vy = (this.vy / sp) * this.maxSpeed;
}
}
}
drawHealthBar() {
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.fillRect(-22, 28, 44, 6);
ctx.fillStyle = this.hp > this.maxHp*0.4 ? '#00ff88' : '#ff0044';
ctx.fillRect(-22, 28, 44 * (this.hp / this.maxHp), 6);
// 显示乘员数
ctx.fillStyle = '#aaa'; ctx.font = '9px Arial'; ctx.textAlign = 'center';
ctx.fillText(`👤${this.crew}`, 0, -25);
}
}
// ======= 1. 激光舰 (线型) =======
class LaserShip extends Ship {
constructor(x, y, isEnemy) {
super(x, y, '#66ccff', isEnemy, { crew: 10, speed: 200, turnSpeed: 2, detectRange: 2000 });
this.type = 'laser';
this.laserTimer = 0; // 30秒主炮计时
}
update() {
super.update();
if (this.dead) return;
// AI 索敌
this.target = entities.find(e => !e.isEnemy && !e.dead && dist(this, e) < this.detectRange);
if (this.target) {
const d = dist(this, this.target);
const angle = angleTo(this, this.target);
// 保持距离
if (d < 300) this.driveToAngle(angle + Math.PI, 1);
else this.driveToAngle(angle, 0.5);
// 舰艏固定大功率激光炮 (需要对准,连续开火)
if (Math.abs(normalizeAngle(angle - this.angle)) < 0.1 && this.fireCooldown <= 0) {
bullets.push(new Bullet(this.x, this.y, this.angle, this.isEnemy, 'laser_main'));
this.fireCooldown = 5;
}
// 4门点射拦截 (简化为近防概率拦截)
this.interceptMissiles();
} else {
this.patrol();
}
}
patrol() {
if (this.wanderAngle === undefined || Math.random() < 0.02) this.wanderAngle = Math.random() * Math.PI * 2;
this.driveToAngle(this.wanderAngle, 0.5);
}
interceptMissiles() {
if (this.pdCooldown > 0) return;
for (let b of bullets) {
if (b.isEnemy !== this.isEnemy && b.type === 'missile' && dist(this, b) < 150) {
if (Math.random() < 0.15) { // 4门拦截概率
b.dead = true;
createExplosion(b.x, b.y, '#66ccff', 5);
this.pdCooldown = 5;
break;
}
}
}
}
draw() {
ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle);
// 线型结构: 动力舱-资源舱-核心舱-生活舱-武器舱
ctx.fillStyle = this.color;
ctx.fillRect(-25, -6, 50, 12);
// 太阳能板
ctx.fillStyle = '#225588';
ctx.fillRect(-10, -22, 20, 16); ctx.fillRect(-10, 6, 20, 16);
// 舰艏激光炮
ctx.fillStyle = '#fff'; ctx.fillRect(25, -2, 12, 4);
ctx.restore();
this.drawHealthBar();
}
}
// ======= 2. 突击舰 (十字型) =======
class AssaultShip extends Ship {
constructor(x, y, isEnemy) {
super(x, y, '#00ffcc', isEnemy, { crew: 8, speed: 400, turnSpeed: 3, detectRange: 4000 });
this.type = 'assault';
this.railAmmo = 24;
this.missileAmmo = 2;
}
update() {
super.update();
if (this.dead) return;
// 没有弹药了就去规避
if (this.railAmmo <= 0) {
this.state = 'evade';
this.driveToAngle(this.angle + Math.PI/2, 1.5); // 侧向机动
return;
}
this.target = entities.find(e => !e.isEnemy && !e.dead && dist(this, e) < this.detectRange);
if (this.target) {
const angle = angleTo(this, this.target);
this.driveToAngle(angle, 1);
// 电磁炮
if (this.fireCooldown <= 0 && Math.abs(normalizeAngle(angle - this.angle)) < 0.2) {
bullets.push(new Bullet(this.x, this.y, this.angle, this.isEnemy, 'railgun'));
this.railAmmo--;
this.fireCooldown = 120; // 2秒装填
}
} else {
if (this.wanderAngle === undefined || Math.random() < 0.02) this.wanderAngle = Math.random() * Math.PI * 2;
this.driveToAngle(this.wanderAngle, 0.8);
}
}
draw() {
ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle);
ctx.fillStyle = this.color;
// 十字型
ctx.fillRect(-20, -6, 40, 12);
ctx.fillRect(-6, -20, 12, 40);
// 太阳能板x4
ctx.fillStyle = '#226655';
ctx.fillRect(-15, -25, 10, 10); ctx.fillRect(5, -25, 10, 10);
ctx.fillRect(-15, 15, 10, 10); ctx.fillRect(5, 15, 10, 10);
// 轨道炮管
ctx.fillStyle = '#fff';
ctx.fillRect(20, -8, 15, 3); ctx.fillRect(20, 5, 15, 3);
ctx.restore();
this.drawHealthBar();
ctx.fillStyle = '#fff'; ctx.font = '9px Arial'; ctx.textAlign = 'center';
ctx.fillText(`电磁:${this.railAmmo}`, 0, 40);
}
}
// ======= 3. 导弹舰 (多支型) =======
class MissileShip extends Ship {
constructor(x, y, isEnemy) {
super(x, y, '#ff3366', isEnemy, { crew: 8, speed: 1000, turnSpeed: 1, detectRange: 2008 });
this.type = 'missile';
this.missileAmmo = 80; // 8单元x10发
this.maxAmmo = 80;
this.needSupply = false;
}
update() {
super.update();
if (this.dead) return;
if (this.missileAmmo <= 0) {
this.needSupply = true;
this.state = 'evade';
this.driveToAngle(this.angle + Math.PI/2, 1); // 没弹药了规避
return;
}
this.target = entities.find(e => !e.isEnemy && !e.dead && dist(this, e) < this.detectRange);
if (this.target) {
const angle = angleTo(this, this.target);
const d = dist(this, this.target);
if (d < 400) this.driveToAngle(angle + Math.PI, 0.8); // 拉开距离
else this.driveToAngle(angle, 0.5);
// 垂发导弹 (需要大致对准)
if (this.fireCooldown <= 0 && Math.abs(normalizeAngle(angle - this.angle)) < 0.4) {
bullets.push(new Bullet(this.x, this.y, this.angle, this.isEnemy, 'missile'));
this.missileAmmo--;
this.fireCooldown = 45; // 发射间隔
}
// 近防炮拦截
if (this.pdCooldown <= 0) {
for (let b of bullets) {
if (b.isEnemy !== this.isEnemy && b.type === 'missile' && dist(this, b) < 120) {
if (Math.random() < 0.1) {
b.dead = true; createExplosion(b.x, b.y, '#ff3366', 3); this.pdCooldown = 8; break;
}
}
}
}
} else {
if (this.wanderAngle === undefined || Math.random() < 0.02) this.wanderAngle = Math.random() * Math.PI * 2;
this.driveToAngle(this.wanderAngle, 0.6);
}
}
draw() {
ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle);
ctx.fillStyle = this.color;
// 多支型
ctx.fillRect(-18, -10, 36, 20);
ctx.fillRect(-6, -18, 12, 36);
// 垂发单元
ctx.fillStyle = '#440000';
for(let i=0; i<3; i++) { ctx.fillRect(5, -12 + i*10, 6, 6); }
ctx.restore();
this.drawHealthBar();
ctx.fillStyle = this.needSupply ? '#ff0000' : '#fff';
ctx.font = '9px Arial'; ctx.textAlign = 'center';
ctx.fillText(`导弹:${this.missileAmmo}`, 0, 40);
}
}
// ======= 4. 补给舰 (接口型) =======
class SupplyShip extends Ship {
constructor(x, y, isEnemy) {
super(x, y, '#ffaa00', isEnemy, { crew: 4, speed: 400, turnSpeed: 4, detectRange: 2000 });
this.type = 'supply';
this.state = 'seek';
this.dockTarget = null;
}
update() {
super.update();
if (this.dead) return;
// 寻找需要补给的友军
if (this.state === 'seek') {
this.dockTarget = entities.find(e => e.isEnemy === this.isEnemy && !e.dead && e.needSupply && dist(this, e) < this.detectRange);
if (this.dockTarget) {
this.state = 'approach';
} else {
// 无目标巡逻
if (this.wanderAngle === undefined || Math.random() < 0.02) this.wanderAngle = Math.random() * Math.PI * 2;
this.driveToAngle(this.wanderAngle, 0.5);
}
}
// 靠近目标
if (this.state === 'approach') {
if (!this.dockTarget || this.dockTarget.dead) {
this.state = 'seek'; return;
}
this.driveToAngle(angleTo(this, this.dockTarget), 1.2);
// 距离足够近,开始对接
if (dist(this, this.dockTarget) < 45) {
this.state = 'docking';
this.vx = 0; this.vy = 0;
}
}
// 对接补给中
if (this.state === 'docking') {
if (!this.dockTarget || this.dockTarget.dead) {
this.state = 'seek'; return;
}
this.vx = 0; this.vy = 0;
// 调整姿态,对着友舰
this.angle = angleTo(this, this.dockTarget);
// 按照手稿速率:2发/秒 (每帧约 2/60)
this.dockTarget.missileAmmo += 2 / 60;
// 补给特效
if (Math.random() < 0.3) createExplosion(this.x, this.y, '#00ff00', 1);
if (this.dockTarget.missileAmmo >= this.dockTarget.maxAmmo) {
this.dockTarget.missileAmmo = this.dockTarget.maxAmmo;
this.dockTarget.needSupply = false;
this.state = 'seek'; // 补给完毕,分离
}
}
}
draw() {
ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle);
ctx.fillStyle = this.color;
// 粗壮补给舱
ctx.beginPath(); ctx.arc(0, 0, 16, 0, Math.PI*2); ctx.fill();
// 接口
ctx.fillStyle = '#fff'; ctx.fillRect(16, -6, 10, 12);
// 状态灯
ctx.fillStyle = this.state === 'docking' ? '#00ff00' : '#ff0000';
ctx.beginPath(); ctx.arc(0, 0, 6, 0, Math.PI*2); ctx.fill();
ctx.restore();
this.drawHealthBar();
if (this.state === 'docking') {
ctx.strokeStyle = '#00ff00'; ctx.lineWidth = 2; ctx.setLineDash([5,5]);
ctx.beginPath(); ctx.moveTo(this.x, this.y); ctx.lineTo(this.dockTarget.x, this.dockTarget.y); ctx.stroke();
ctx.setLineDash([]);
}
}
}
// ======= 武器与子弹系统 =======
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 = 150;
if (type === 'missile') { this.speed = 3.5; this.turnSpeed = 0.04; this.damage = 80; }
else if (type === 'laser_main') { this.speed = 15; this.damage = 8; }
else if (type === 'railgun') { this.speed = 25; this.damage = 150; }
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) {
let targetAngle = angleTo(this, target);
let currentAngle = Math.atan2(this.vy, this.vx);
let diff = normalizeAngle(targetAngle - currentAngle);
currentAngle += Math.sign(diff) * this.turnSpeed;
this.vx = Math.cos(currentAngle) * this.speed;
this.vy = Math.sin(currentAngle) * this.speed;
this.angle = currentAngle;
}
}
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 && dist(this, e) < e.radius) {
e.hp -= this.damage;
this.dead = true;
createExplosion(this.x, this.y, this.type==='missile'?'#ffaa00':'#fff', 5);
if (e.hp <= 0) e.dead = true;
}
}
}
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); // 导弹
} else if (this.type === 'laser_main') {
ctx.fillStyle = '#66ccff'; ctx.fillRect(-15, -1, 30, 2); // 激光
} else {
ctx.fillStyle = '#00ffff'; ctx.fillRect(-15, -1.5, 30, 3); // 轨道炮
}
ctx.restore();
}
}
// ======= 特效系统 =======
function createExplosion(x, y, color, count) {
for (let i = 0; i < count; i++) {
let a = Math.random() * Math.PI * 2;
let s = Math.random() * 4;
particles.push({
x, y, vx: Math.cos(a)*s, vy: Math.sin(a)*s,
life: 20 + Math.random()*20, maxLife: 40, color, size: Math.random()*2.5 + 1
});
}
}
// ======= 游戏流程 =======
function startGame() {
document.getElementById('ui-layer').style.display = 'none';
document.getElementById('hud').style.display = 'flex';
gameState = 'playing';
entities = []; bullets = []; particles = [];
// 生成蓝方 (左侧)
let bY = H / 2;
for(let i=0; i<fleetConfig.b_laser; i++) entities.push(new LaserShip(100, bY - 100 + i*100, false));
for(let i=0; i<fleetConfig.b_assault; i++) entities.push(new AssaultShip(200, bY - 50 + i*100, false));
// 生成红方 (右侧)
let rY = H / 2;
for(let i=0; i<fleetConfig.r_missile; i++) entities.push(new MissileShip(W - 100, rY - 100 + i*100, true));
for(let i=0; i<fleetConfig.r_supply; i++) entities.push(new SupplyShip(W - 200, rY + (i-1)*80, true));
loop();
}
function checkWin() {
let blueAlive = entities.some(e => !e.isEnemy && !e.dead);
let redAlive = entities.some(e => e.isEnemy && !e.dead);
if (!blueAlive || !redAlive) {
gameState = 'ended';
setTimeout(() => {
ctx.fillStyle = 'rgba(0,0,0,0.8)'; ctx.fillRect(0,0,W,H);
ctx.fillStyle = '#fff'; ctx.font = 'bold 30px sans-serif'; ctx.textAlign = 'center';
ctx.fillText(blueAlive ? '蓝方胜利' : (redAlive ? '红方胜利' : '同归于尽'), W/2, H/2);
ctx.font = '16px sans-serif'; ctx.fillStyle = '#aaa';
ctx.fillText('刷新页面重新开始', W/2, H/2 + 40);
}, 1000);
return true;
}
return false;
}
// 主循环
function loop() {
if (gameState !== 'playing') return;
ctx.fillStyle = 'rgba(2, 4, 10, 0.4)'; // 拖影
ctx.fillRect(0, 0, W, H);
// 画星空
ctx.fillStyle = '#fff';
for(let i=0; i<30; i++) { ctx.globalAlpha = Math.random()*0.6; ctx.fillRect(Math.random()*W, Math.random()*H, 1.5, 1.5); }
ctx.globalAlpha = 1;
// 更新实体
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;
// UI 更新
document.getElementById('hud-left').innerHTML = `蓝方存活: ${entities.filter(e => !e.isEnemy && !e.dead).length}`;
document.getElementById('hud-right').innerHTML = `红方存活: ${entities.filter(e => e.isEnemy && !e.dead).length}`;
if (!checkWin()) {
requestAnimationFrame(loop);
}
}
</script>
</body>
</html>Game Source: 深空舰队模拟器 - 完整版
Creator: EpicCoder88
Libraries: none
Complexity: complex (593 lines, 20.6 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-mvlwqwoi" to link back to the original. Then publish at arcadelab.ai/publish.