坦克大战:海陆双战
by QuantumComet967487 lines363.1 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' width='32' height='32'%3E%3Cdefs%3E%3ClinearGradient id='tankBodyGradient' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%234CAF50'/%3E%3Cstop offset='100%25' stop-color='%232E7D32'/%3E%3C/linearGradient%3E%3ClinearGradient id='turretGradient' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%23388E3C'/%3E%3Cstop offset='100%25' stop-color='%231B5E20'/%3E%3C/linearGradient%3E%3ClinearGradient id='cannonGradient' x1='0%25' y1='0%25' x2='0%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%23616161'/%3E%3Cstop offset='100%25' stop-color='%23212121'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect x='6' y='14' width='20' height='10' rx='2' fill='url(%23tankBodyGradient)'/%3E%3Crect x='4' y='14' width='2' height='10' fill='%23212121'/%3E%3Crect x='26' y='14' width='2' height='10' fill='%23212121'/%3E%3Crect x='4' y='14' width='2' height='2' fill='%23424242'/%3E%3Crect x='4' y='18' width='2' height='2' fill='%23424242'/%3E%3Crect x='4' y='22' width='2' height='2' fill='%23424242'/%3E%3Crect x='26' y='14' width='2' height='2' fill='%23424242'/%3E%3Crect x='26' y='18' width='2' height='2' fill='%23424242'/%3E%3Crect x='26' y='22' width='2' height='2' fill='%23424242'/%3E%3Ccircle cx='16' cy='16' r='6' fill='url(%23turretGradient)'/%3E%3Ccircle cx='16' cy='16' r='2' fill='%231B5E20'/%3E%3Crect x='15' y='4' width='2' height='12' fill='url(%23cannonGradient)'/%3E%3Crect x='13' y='2' width='6' height='2' fill='%23212121'/%3E%3Crect x='14' y='3' width='4' height='1' fill='%23616161'/%3E%3C/svg%3E"
type="image/svg+xml">
<title>坦克大战:海陆双战</title>
<style>
.menu-button {
padding: 8px 15px;
font-size: 16px;
background-color: #2196F3;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
margin: 5px;
transition: background-color 0.3s;
}
.menu-button:hover {
background-color: #1976D2;
}
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #333;
font-family: Arial, sans-serif;
}
#game-container {
position: relative;
}
#gameCanvas {
border: 2px solid #fff;
background-color: #000;
}
#start-screen,
#game-over-screen,
#win-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
color: white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
button {
padding: 10px 20px;
font-size: 18px;
margin-top: 20px;
cursor: pointer;
background-color: #2196F3;
color: white;
border: none;
border-radius: 5px;
position: relative;
z-index: 1;
}
button:hover {
background-color: #1976D2;
}
h1 {
margin-bottom: 40px;
margin-top: -50px;
position: relative;
z-index: 1;
}
.start-screen-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0.5;
z-index: 0;
}
</style>
</head>
<body>
<div id="game-container">
<!-- BOSS血条 -->
<div id="boss-health-bar-container"
style="display: none; position: absolute; top: 10px; left: 50%; transform: translateX(-50%); z-index: 10; width: 400px;">
<div id="boss-label"
style="text-align: center; color: #ff0000; font-size: 18px; font-weight: bold; margin-bottom: 5px;"
data-translate="boss">BOSS</div>
<div id="boss-health-bar"
style="width: 100%; height: 20px; background-color: #333; border-radius: 10px; overflow: hidden; border: 2px solid #ff0000;">
<div id="boss-health-fill"
style="width: 100%; height: 100%; background: linear-gradient(90deg, #ff0000, #ff6600); transition: width 0.3s ease;">
</div>
</div>
<div id="boss-health-text" style="text-align: center; color: white; font-size: 14px; margin-top: 5px;">15/15
</div>
</div>
<canvas id="gameCanvas" width="950" height="650"></canvas>
<div id="start-screen">
<!-- 语言切换按钮 -->
<button id="language-btn"
style="position: absolute; top: 10px; right: 10px; padding: 5px 10px; font-size: 14px; background-color: #2196F3; color: white; border: none; border-radius: 5px; cursor: pointer; z-index: 2;">语言/Language</button>
<!-- 语言切换弹窗 -->
<div id="language-modal"
style="display: none; position: absolute; top: 45px; right: 10px; background-color: #333; padding: 10px; border-radius: 5px; z-index: 2;">
<button id="lang-zh"
style="display: block; width: 100%; padding: 8px 16px; margin-bottom: 5px; background-color: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer;">中文</button>
<button id="lang-en"
style="display: block; width: 100%; padding: 8px 16px; background-color: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer;">English</button>
</div>
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='950' height='650' viewBox='0 0 950 650'%3E%3Crect width='950' height='650' fill='%23333333'/%3E%3Cg%3E%3Cpath d='M0 50 H950 M0 100 H950 M0 150 H950 M0 200 H950 M0 250 H950 M0 300 H950 M0 350 H950 M0 400 H950 M0 450 H950 M0 500 H950 M0 550 H950 M0 600 H950' stroke='%23666' stroke-width='1'/%3E%3Cpath d='M50 0 V650 M100 0 V650 M150 0 V650 M200 0 V650 M250 0 V650 M300 0 V650 M350 0 V650 M400 0 V650 M450 0 V650 M500 0 V650 M550 0 V650 M600 0 V650 M650 0 V650 M700 0 V650 M750 0 V650 M800 0 V650 M850 0 V650 M900 0 V650' stroke='%23666' stroke-width='1'/%3E%3Ccircle cx='130' cy='80' r='2' fill='%23999'/%3E%3Ccircle cx='250' cy='150' r='1' fill='%23999'/%3E%3Ccircle cx='370' cy='230' r='2' fill='%23999'/%3E%3Ccircle cx='475' cy='320' r='1' fill='%23999'/%3E%3Ccircle cx='590' cy='390' r='2' fill='%23999'/%3E%3Ccircle cx='710' cy='470' r='1' fill='%23999'/%3E%3Ccircle cx='830' cy='530' r='2' fill='%23999'/%3E%3Ccircle cx='190' cy='590' r='1' fill='%23999'/%3E%3Ccircle cx='310' cy='520' r='2' fill='%23999'/%3E%3Ccircle cx='420' cy='450' r='1' fill='%23999'/%3E%3Ccircle cx='540' cy='370' r='2' fill='%23999'/%3E%3Ccircle cx='660' cy='290' r='1' fill='%23999'/%3E%3Ccircle cx='780' cy='210' r='2' fill='%23999'/%3E%3Ccircle cx='85' cy='140' r='1' fill='%23999'/%3E%3C/g%3E%3Cg transform='translate(475, 325) scale(2)'%3E%3Crect x='-30' y='-15' width='60' height='30' fill='%23000'/%3E%3Crect x='-40' y='-10' width='10' height='20' fill='%23000'/%3E%3Crect x='30' y='-10' width='10' height='20' fill='%23000'/%3E%3Crect x='-5' y='-25' width='10' height='10' fill='%23000'/%3E%3Cline x1='0' y1='-25' x2='0' y2='-45' stroke='%23000' stroke-width='6'/%3E%3C/g%3E%3Cg%3E%3Cpath d='M100 100 L120 100 L120 120 L100 120 Z M140 100 L160 100 L160 120 L140 120 Z M100 140 L120 140 L120 160 L100 160 Z M140 140 L160 140 L160 160 L140 160 Z' fill='%23666'/%3E%3Cpath d='M790 100 L810 100 L810 120 L790 120 Z M830 100 L850 100 L850 120 L830 120 Z M790 140 L810 140 L810 160 L790 160 Z M830 140 L850 140 L850 160 L830 160 Z' fill='%23666'/%3E%3Cpath d='M100 490 L120 490 L120 510 L100 510 Z M140 490 L160 490 L160 510 L140 510 Z M100 530 L120 530 L120 550 L100 550 Z M140 530 L160 530 L160 550 L140 550 Z' fill='%23666'/%3E%3Cpath d='M790 490 L810 490 L810 510 L790 510 Z M830 490 L850 490 L850 510 L830 510 Z M790 530 L810 530 L810 550 L790 550 Z M830 530 L850 530 L850 550 L830 550 Z' fill='%23666'/%3E%3C/g%3E%3C/svg%3E"
alt="坦克大战游戏背景" class="start-screen-image">
<h1 id="game-title" data-translate="game-title">坦克大战:海陆双战</h1>
· <div style="display: flex; align-items: center; gap: 10px;">
· <div id="total-score"
style="font-size: 24px; color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.5); opacity: 0.9;"><span
data-translate="total-score">金币:</span><span id="total-score-value">0</span></div>
</div>
<div id="dialog-overlay"
style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.7); z-index: 999;">
</div>
<!-- 敌人介绍弹窗 -->
<div id="enemy-info-dialog"
style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #333; padding: 20px; border-radius: 10px; z-index: 1000; max-width: 800px; max-height: 80vh; overflow-y: auto;">
<h2 style="color: white; text-align: center; margin-bottom: 20px;" data-translate="enemy-info-title">
敌人介绍</h2>
<div style="color: white; margin-bottom: 20px;">
<h3 data-translate="normal-enemy">普通敌人</h3>
<p data-translate="normal-enemy-desc">速度中等,生命值较低,武器伤害一般。是游戏中最常见的敌人类型。</p>
<svg width="60" height="60" viewBox="0 0 60 60">
<!-- 车身 -->
<rect x="16" y="16" width="28" height="28" fill="#E53935" />
<rect x="18" y="18" width="24" height="24" fill="#C62828" stroke="#B71C1C" stroke-width="1" />
<!-- 炮塔 -->
<circle cx="30" cy="30" r="9" fill="#D32F2F" />
<circle cx="30" cy="30" r="5" fill="#B71C1C" />
<!-- 炮管 -->
<rect x="28" y="0" width="4" height="28" fill="#444" />
<rect x="29" y="-8" width="2" height="8" fill="#333" />
<!-- 履带 -->
<rect x="0" y="14" width="12" height="32" fill="#444" />
<rect x="48" y="14" width="12" height="32" fill="#444" />
<!-- 履带细节 -->
<rect x="1" y="16" width="10" height="6" fill="#555" />
<rect x="1" y="30" width="10" height="6" fill="#555" />
<rect x="1" y="44" width="10" height="6" fill="#555" />
<rect x="49" y="16" width="10" height="6" fill="#555" />
<rect x="49" y="30" width="10" height="6" fill="#555" />
<rect x="49" y="44" width="10" height="6" fill="#555" />
</svg>
</div>
<div style="color: white; margin-bottom: 20px;">
<h3 data-translate="medium-enemy">中型敌人</h3>
<p data-translate="medium-enemy-desc">速度较慢,但生命值比普通敌人高。需要多次攻击才能消灭。</p>
<svg width="70" height="70" viewBox="0 0 70 70">
<!-- 车身 -->
<rect x="18" y="18" width="34" height="34" fill="#FFA726" />
<rect x="20" y="20" width="30" height="30" fill="#FB8C00" stroke="#EF6C00" stroke-width="1" />
<!-- 炮塔 -->
<circle cx="35" cy="35" r="11" fill="#F57C00" />
<circle cx="35" cy="35" r="6" fill="#E65100" />
<!-- 炮管 -->
<rect x="33" y="0" width="4" height="32" fill="#444" />
<rect x="34" y="-10" width="2" height="10" fill="#333" />
<!-- 履带 -->
<rect x="0" y="16" width="14" height="38" fill="#444" />
<rect x="56" y="16" width="14" height="38" fill="#444" />
<!-- 履带细节 -->
<rect x="1" y="18" width="12" height="7" fill="#555" />
<rect x="1" y="35" width="12" height="7" fill="#555" />
<rect x="1" y="52" width="12" height="7" fill="#555" />
<rect x="57" y="18" width="12" height="7" fill="#555" />
<rect x="57" y="35" width="12" height="7" fill="#555" />
<rect x="57" y="52" width="12" height="7" fill="#555" />
</svg>
</div>
<div style="color: white; margin-bottom: 20px;">
<h3 data-translate="boss-enemy">毁灭者</h3>
<p data-translate="boss-enemy-desc">
陆地关卡的终极BOSS,代号"毁灭者"的巨型坦克。体型庞大,装甲厚重,火力惊人。战斗分为三个阶段:第一阶段为普通状态,第二阶段加速并切换为双发射击,第三阶段进入狂暴状态使用三发射击模式。击败它即可进入海域关卡。
</p>
<svg width="100" height="100" viewBox="0 0 100 100">
<!-- 车身 -->
<rect x="26" y="26" width="48" height="48" fill="#AB47BC" />
<rect x="28" y="28" width="44" height="44" fill="#9C27B0" stroke="#7B1FA2" stroke-width="1" />
<rect x="30" y="30" width="40" height="40" fill="#8E24AA" stroke="#6A1B9A" stroke-width="1" />
<!-- 炮塔 -->
<circle cx="50" cy="50" r="14" fill="#8E24AA" />
<circle cx="50" cy="50" r="8" fill="#7B1FA2" />
<circle cx="50" cy="50" r="4" fill="#6A1B9A" />
<!-- 炮管 -->
<rect x="47" y="0" width="6" height="47" fill="#444" />
<rect x="48" y="-12" width="4" height="12" fill="#333" />
<!-- 履带 -->
<rect x="0" y="24" width="20" height="52" fill="#444" />
<rect x="80" y="24" width="20" height="52" fill="#444" />
<!-- 履带细节 -->
<rect x="1" y="26" width="18" height="10" fill="#555" />
<rect x="1" y="46" width="18" height="10" fill="#555" />
<rect x="1" y="66" width="18" height="10" fill="#555" />
<rect x="81" y="26" width="18" height="10" fill="#555" />
<rect x="81" y="46" width="18" height="10" fill="#555" />
<rect x="81" y="66" width="18" height="10" fill="#555" />
</svg>
</div>
<div style="color: white; margin-bottom: 20px;">
<h3 data-translate="turret-enemy">炮台</h3>
<p data-translate="turret-enemy-desc">固定在墙上的防御武器,会从任意方向(上、下、左、右)发射子弹,对玩家造成1点伤害。</p>
<svg width="60" height="60" viewBox="0 0 60 60">
<!-- 底座发光 -->
<circle cx="30" cy="30" r="20" fill="url(#turretGlow)"/>
<!-- 六边形底座 -->
<polygon points="30,10 47,20 47,40 30,50 13,40 13,20" fill="#78909C"/>
<!-- 底座内圈 -->
<circle cx="30" cy="30" r="10" fill="#546E7A"/>
<!-- 中心发光 -->
<circle cx="30" cy="30" r="8" fill="url(#centerGlow)"/>
<!-- 炮管装甲底座 -->
<circle cx="30" cy="23" r="5" fill="#455A64"/>
<!-- 炮管 -->
<rect x="27" y="3" width="6" height="22" fill="#37474F"/>
<!-- 炮管加强环 -->
<rect x="26" y="11" width="8" height="5" fill="#263238"/>
<!-- 炮口 -->
<circle cx="30" cy="3" r="4" fill="#FF5722"/>
<circle cx="30" cy="3" r="3" fill="url(#muzzleGlow)"/>
<circle cx="30" cy="3" r="2" fill="#1A1A1A"/>
<!-- 铆钉装饰 -->
<circle cx="30" cy="20" r="2" fill="#90A4AE"/>
<circle cx="42" cy="27" r="2" fill="#90A4AE"/>
<circle cx="42" cy="33" r="2" fill="#90A4AE"/>
<circle cx="30" cy="40" r="2" fill="#90A4AE"/>
<circle cx="18" cy="33" r="2" fill="#90A4AE"/>
<circle cx="18" cy="27" r="2" fill="#90A4AE"/>
<!-- 渐变定义 -->
<defs>
<radialGradient id="turretGlow" cx="30" cy="30" r="20">
<stop offset="0%" stop-color="rgba(255,100,0,0.3)"/>
<stop offset="50%" stop-color="rgba(255,50,0,0.15)"/>
<stop offset="100%" stop-color="rgba(255,0,0,0)"/>
</radialGradient>
<radialGradient id="centerGlow" cx="30" cy="30" r="8">
<stop offset="0%" stop-color="rgba(255,150,0,0.8)"/>
<stop offset="50%" stop-color="rgba(255,100,0,0.4)"/>
<stop offset="100%" stop-color="rgba(255,50,0,0)"/>
</radialGradient>
<radialGradient id="muzzleGlow" cx="30" cy="3" r="3">
<stop offset="0%" stop-color="rgba(255,200,0,1)"/>
<stop offset="50%" stop-color="rgba(255,100,0,0.6)"/>
<stop offset="100%" stop-color="rgba(255,50,0,0)"/>
</radialGradient>
</defs>
</svg>
</div>
<div style="color: white; margin-bottom: 20px;">
<h3 data-translate="battleship-enemy">无畏舰</h3>
<p data-translate="battleship-enemy-desc">
海域关卡的终极BOSS,体型庞大的军舰。普通攻击与普通敌人相同,但拥有多种特殊技能:1.朝四周顺序开炮(北→东北→东→东南→南→西南→西→西北);2.朝任意方向发射3行子弹;3.在周围放置地雷;4.召唤小弟,期间自己不发射子弹。
</p>
<svg width="100" height="70" viewBox="0 0 100 70">
<!-- 军舰主体 -->
<ellipse cx="50" cy="45" rx="45" ry="18" fill="#1E3A5F"/>
<ellipse cx="50" cy="42" rx="40" ry="14" fill="#2C5282"/>
<!-- 甲板 -->
<rect x="8" y="35" width="84" height="8" fill="#3182CE"/>
<!-- 船头 -->
<polygon points="5,50 12,40 12,55" fill="#1A365D"/>
<!-- 船尾 -->
<polygon points="95,48 88,40 88,55" fill="#1A365D"/>
<!-- 主炮塔 -->
<rect x="35" y="25" width="30" height="12" fill="#4A5568"/>
<rect x="40" y="15" width="20" height="12" fill="#2D3748"/>
<!-- 炮塔炮管 -->
<rect x="47" y="0" width="6" height="18" fill="#4A5568"/>
<circle cx="50" cy="0" r="5" fill="#718096"/>
<!-- 副炮塔 -->
<rect x="18" y="32" width="8" height="6" fill="#4A5568"/>
<rect x="74" y="32" width="8" height="6" fill="#4A5568"/>
<!-- 烟囱 -->
<rect x="55" y="20" width="6" height="15" fill="#4A5568"/>
<rect x="56" y="18" width="4" height="5" fill="#2D3748"/>
<!-- 桅杆 -->
<rect x="42" y="10" width="2" height="25" fill="#4A5568"/>
<!-- 雷达 -->
<circle cx="43" cy="8" r="4" fill="#2D3748"/>
<!-- 装饰线条 -->
<line x1="15" y1="48" x2="85" y2="48" stroke="#3182CE" stroke-width="1"/>
<line x1="20" y1="50" x2="80" y2="50" stroke="#2C5282" stroke-width="1"/>
</svg>
</div>
<button id="close-enemy-info"
style="padding: 5px 15px; background: #2196F3; color: white; border: none; border-radius: 5px; cursor: pointer; display: block; margin: 0 auto;"
data-translate="close-enemy-info">关闭</button>
</div>
<!-- 按键介绍弹窗 -->
<div id="control-info-dialog"
style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #333; padding: 20px; border-radius: 10px; z-index: 1000; max-width: 600px;">
<h2 style="color: white; text-align: center; margin-bottom: 20px;" data-translate="control-info-title">
按键介绍</h2>
<div style="color: white; margin-bottom: 15px;">
<h3 data-translate="movement-control">移动控制</h3>
<p><strong data-translate="w-key">W键</strong>: <span data-translate="w-desc">向上移动</span></p>
<p><strong data-translate="s-key">S键</strong>: <span data-translate="s-desc">向下移动</span></p>
<p><strong data-translate="a-key">A键</strong>: <span data-translate="a-desc">向左移动</span></p>
<p><strong data-translate="d-key">D键</strong>: <span data-translate="d-desc">向右移动</span></p>
</div>
<div style="color: white; margin-bottom: 15px;">
<h3 data-translate="shoot-control">射击控制</h3>
<p><strong data-translate="space-key">空格键</strong>: <span data-translate="space-desc">发射子弹</span>
</p>
</div>
<button id="close-control-info"
style="padding: 5px 15px; background: #2196F3; color: white; border: none; border-radius: 5px; cursor: pointer; display: block; margin: 0 auto;"
data-translate="close-control-info">关闭</button>
</div>
<!-- 道具和机关介绍弹窗 -->
<div id="prop-trap-info-dialog"
style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #333; padding: 20px; border-radius: 10px; z-index: 1000; max-width: 800px; max-height: 80vh; overflow-y: auto;">
<h2 style="color: white; text-align: center; margin-bottom: 20px;"
data-translate="prop-trap-info-title">道具和机关介绍</h2>
<div style="color: white; margin-bottom: 20px;">
<h3 data-translate="props">道具</h3>
<div style="margin-bottom: 15px;">
<h4 data-translate="extra-life">额外生命</h4>
<p data-translate="extra-life-desc">拾取后增加一条生命。</p>
<svg width="40" height="40" viewBox="0 0 40 40">
<!-- 渐变圆形 -->
<defs>
<radialGradient id="lifeGradient" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#81C784" />
<stop offset="100%" stop-color="#4CAF50" />
</radialGradient>
</defs>
<circle cx="20" cy="20" r="18" fill="url(#lifeGradient)" stroke="#388E3C"
stroke-width="2" />
<!-- 白色三角形图标 -->
<path d="M20 8 L26 24 L14 24 Z" fill="#fff" />
</svg>
</div>
<div style="margin-bottom: 15px;">
<h4 data-translate="speed-boost">速度提升</h4>
<p data-translate="speed-boost-desc">拾取后5秒内提升坦克移动速度。</p>
<svg width="40" height="40" viewBox="0 0 40 40">
<defs>
<radialGradient id="speedGradient" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#FFEB3B" />
<stop offset="100%" stop-color="#FFC107" />
</radialGradient>
</defs>
<circle cx="20" cy="20" r="18" fill="url(#speedGradient)" stroke="#FF9800"
stroke-width="2" />
<!-- 速度图标(向右箭头) -->
<path d="M14 20 L26 14 L26 26 Z" fill="#fff" />
</svg>
</div>
<div style="margin-bottom: 15px;">
<h4 data-translate="shield">护盾</h4>
<p data-translate="shield-desc">拾取后5秒内获得护盾保护,免疫一次伤害。</p>
<svg width="40" height="40" viewBox="0 0 40 40">
<defs>
<radialGradient id="shieldGradient" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#64B5F6" />
<stop offset="100%" stop-color="#2196F3" />
</radialGradient>
</defs>
<circle cx="20" cy="20" r="18" fill="url(#shieldGradient)" stroke="#1976D2"
stroke-width="2" />
<!-- 护盾图标(同心圆) -->
<circle cx="20" cy="20" r="8" fill="#fff" />
<circle cx="20" cy="20" r="12" fill="none" stroke="#fff" stroke-width="1" />
</svg>
</div>
<div style="margin-bottom: 15px;">
<h4 data-translate="time-freeze">时间冻结</h4>
<p data-translate="time-freeze-desc">拾取后5秒内冻结所有敌人。</p>
<svg width="40" height="40" viewBox="0 0 40 40">
<defs>
<radialGradient id="freezeGradient" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#84FFFF" />
<stop offset="100%" stop-color="#00BCD4" />
</radialGradient>
</defs>
<circle cx="20" cy="20" r="18" fill="url(#freezeGradient)" stroke="#0097A7"
stroke-width="2" />
<!-- 雪花图标 -->
<path
d="M20 8 L22 16 L24 8 L20 14 L16 8 L18 16 L16 20 L24 20 L22 16 L24 28 L20 22 L16 28 L18 20 Z"
fill="#fff" />
</svg>
</div>
<div style="margin-bottom: 15px;">
<h4 data-translate="time-slow">时间减速</h4>
<p data-translate="time-slow-desc">拾取后8秒内降低所有敌人移动和发射速度的50%。</p>
<svg width="40" height="40" viewBox="0 0 40 40">
<defs>
<radialGradient id="slowGradient" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#CE93D8" />
<stop offset="100%" stop-color="#9C27B0" />
</radialGradient>
</defs>
<circle cx="20" cy="20" r="18" fill="url(#slowGradient)" stroke="#7B1FA2"
stroke-width="2" />
<!-- 时钟图标 -->
<circle cx="20" cy="20" r="8" fill="#fff" />
<line x1="20" y1="20" x2="20" y2="14" stroke="#9C27B0" stroke-width="2" />
<line x1="20" y1="20" x2="22" y2="18" stroke="#9C27B0" stroke-width="2" />
</svg>
</div>
</div>
<div style="color: white;">
<h3 data-translate="traps">机关</h3>
<div style="margin-bottom: 15px;">
<h4 data-translate="mine">地雷</h4>
<p data-translate="mine-desc">触碰后爆炸,对坦克造成伤害。</p>
<svg width="30" height="30" viewBox="0 0 30 30">
<defs>
<radialGradient id="mineGradient" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#555" />
<stop offset="100%" stop-color="#333" />
</radialGradient>
</defs>
<circle cx="15" cy="15" r="14" fill="url(#mineGradient)" stroke="#ff0000"
stroke-width="2" />
<!-- 骷髅标志 -->
<path d="M15 7 L18 13 L12 13 Z" fill="#ff0000" />
<!-- 金属纹理 -->
<line x1="15" y1="1" x2="15" y2="14" stroke="#555" stroke-width="1" />
<line x1="1" y1="15" x2="14" y2="15" stroke="#555" stroke-width="1" />
<line x1="15" y1="16" x2="15" y2="29" stroke="#555" stroke-width="1" />
<line x1="16" y1="15" x2="29" y2="15" stroke="#555" stroke-width="1" />
<line x1="5" y1="5" x2="10" y2="10" stroke="#555" stroke-width="1" />
<line x1="25" y1="5" x2="20" y2="10" stroke="#555" stroke-width="1" />
<line x1="5" y1="25" x2="10" y2="20" stroke="#555" stroke-width="1" />
<line x1="25" y1="25" x2="20" y2="20" stroke="#555" stroke-width="1" />
</svg>
</div>
<div style="margin-bottom: 15px;">
<h4 data-translate="oil-barrel">油桶</h4>
<p data-translate="oil-barrel-desc">射击后爆炸,对附近敌人造成伤害。</p>
<svg width="30" height="40" viewBox="0 0 30 40">
<!-- 油桶主体 -->
<rect x="3" y="5" width="24" height="30" fill="#8B4513" rx="3" />
<!-- 金属边缘 -->
<rect x="3" y="5" width="24" height="4" fill="#5D3A1A" />
<rect x="3" y="31" width="24" height="4" fill="#5D3A1A" />
<!-- 高光 -->
<rect x="6" y="10" width="4" height="20" fill="#A0522D" opacity="0.5" />
<!-- 桶盖 -->
<rect x="10" y="2" width="10" height="4" fill="#444" />
<rect x="11" y="1" width="8" height="2" fill="#666" />
</svg>
</div>
<div style="margin-bottom: 15px;">
<h4 data-translate="portal">传送门</h4>
<p data-translate="portal-desc">进入一个传送门后会从对应的另一个传送门出来。</p>
<svg width="40" height="40" viewBox="0 0 40 40">
<defs>
<radialGradient id="portalOuter" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="rgba(255,255,255,0.8)" />
<stop offset="100%" stop-color="rgba(0,0,0,0)" />
</radialGradient>
<radialGradient id="portalInner" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="rgba(255,255,255,0.5)" />
<stop offset="50%" stop-color="#9c27b0" />
<stop offset="100%" stop-color="#6a1b9a" />
</radialGradient>
</defs>
<!-- 外光环 -->
<circle cx="20" cy="20" r="18" fill="url(#portalOuter)" />
<!-- 主体 -->
<circle cx="20" cy="20" r="14" fill="url(#portalInner)" />
<!-- 内圈 -->
<circle cx="20" cy="20" r="10" fill="rgba(255,255,255,0.5)" />
<!-- 中心 -->
<circle cx="20" cy="20" r="5" fill="#fff" opacity="0.8" />
</svg>
</div>
<div style="margin-bottom: 15px;">
<h4 data-translate="laser-net">激光网</h4>
<p data-translate="laser-net-desc">海域关卡特有机关,红色网状结构,四角有黑色边框。玩家踩上后无法移动3秒,激光网会变形包裹玩家。</p>
<svg width="40" height="40" viewBox="0 0 40 40">
<!-- 四角黑色边框 -->
<rect x="2" y="2" width="8" height="8" fill="#000" />
<rect x="30" y="2" width="8" height="8" fill="#000" />
<rect x="2" y="30" width="8" height="8" fill="#000" />
<rect x="30" y="30" width="8" height="8" fill="#000" />
<!-- 红色网状线 -->
<line x1="10" y1="20" x2="30" y2="20" stroke="#ff0000" stroke-width="2" />
<line x1="20" y1="10" x2="20" y2="30" stroke="#ff0000" stroke-width="2" />
<line x1="10" y1="10" x2="30" y2="30" stroke="#ff0000" stroke-width="2" />
<line x1="30" y1="10" x2="10" y2="30" stroke="#ff0000" stroke-width="2" />
<!-- 交叉点 -->
<circle cx="20" cy="20" r="3" fill="#ff0000" />
<circle cx="10" cy="10" r="2" fill="#ff0000" />
<circle cx="30" cy="10" r="2" fill="#ff0000" />
<circle cx="10" cy="30" r="2" fill="#ff0000" />
<circle cx="30" cy="30" r="2" fill="#ff0000" />
</svg>
</div>
</div>
<button id="close-prop-trap-info"
style="padding: 5px 15px; background: #2196F3; color: white; border: none; border-radius: 5px; cursor: pointer; display: block; margin: 0 auto; margin-top: 20px;"
data-translate="close-prop-trap-info">关闭</button>
</div>
<div
style="display: flex; flex-direction: column; gap: 15px; margin-bottom: 20px; position: relative; z-index: 1;">
<div>
<label for="player-name" style="display: block; margin-bottom: 5px;"
data-translate="player-name-label">玩家名称:</label>
<input type="text" id="player-name" placeholder="输入你的名字"
style="padding: 8px; width: 200px; border-radius: 5px; border: none;">
</div>
<div>
<label style="display: block; margin-bottom: 5px;" data-translate="tank-color-label">坦克颜色:</label>
<div style="display: flex; align-items: center; gap: 10px;">
<input type="color" id="tank-color-picker" value="#2196F3"
style="width: 40px; height: 40px; border: none; cursor: pointer;">
<canvas id="tank-preview" width="60" height="60"
style="border: 1px solid #fff; background-color: #333;"></canvas>
</div>
</div>
</div>
<div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin-bottom: 20px;">
<button id="difficulty-btn" class="menu-button" data-translate="difficulty-btn">难度</button>
<div id="difficulty-modal"
style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #333; padding: 20px; border-radius: 10px; z-index: 1000;">
<h2 style="color: white; text-align: center;">选择难度</h2>
<div style="display: flex; gap: 10px; justify-content: center;">
<button class="difficulty-option" data-level="1"
style="padding: 10px 20px; background-color: #2196F3; color: white; border: none; border-radius: 5px; cursor: pointer;">初级</button>
<button class="difficulty-option" data-level="2"
style="padding: 10px 20px; background-color: #FF9800; color: white; border: none; border-radius: 5px; cursor: pointer;">中级</button>
<button class="difficulty-option" data-level="3"
style="padding: 10px 20px; background-color: #F44336; color: white; border: none; border-radius: 5px; cursor: pointer;">高级</button>
</div>
</div>
<div id="modal-overlay"
style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.7); z-index: 999;">
</div>
<button id="start-button" class="menu-button" data-translate="start-button">开始游戏</button>
<button id="level-select-button" class="menu-button" data-translate="level-select-button">关卡选择</button>
<button id="toggle-timer-button" class="menu-button" data-translate="toggle-timer-button">模式:正常</button>
<button id="info-button" class="menu-button" data-translate="info-button">游戏介绍</button>
</div>
<!-- 商店按钮 -->
<button id="shop-button" class="menu-button" style="position: absolute; bottom: 20px; right: 20px;" data-translate="shop-button">商店</button>
<!-- 商店弹窗 -->
<div id="shop-dialog"
style="display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: rgba(30, 30, 30, 0.95); padding: 30px; border-radius: 15px; z-index: 100; width: 550px; min-height: 500px; border: 3px solid #FFD700;">
<h2 style="color: #FFD700; text-align: center; margin-bottom: 25px; margin-top: 0; font-size: 32px;"
data-translate="shop-title">商店</h2>
<!-- 金币显示 -->
<div style="position: absolute; bottom: 25px; left: 25px; display: flex; align-items: center; gap: 8px;">
<svg width="32" height="32" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" fill="#FFD700" />
<circle cx="8" cy="10" r="1.5" fill="#FFA000" />
<path d="M12 7 L12 11 M10 9 L14 9" stroke="#FFA000" stroke-width="2" stroke-linecap="round" />
</svg>
<span id="shop-gold" style="color: #FFD700; font-size: 24px; font-weight: bold;">0</span>
</div>
<!-- 商品区域 -->
<div id="shop-items" style="min-height: 400px; color: white; padding-top: 50px;">
<!-- 道具分区 -->
<div style="margin-bottom: 25px;">
<h3 style="color: #4CAF50; font-size: 20px; margin: 0 0 15px 0; padding-bottom: 8px; border-bottom: 2px solid #4CAF50; display: flex; align-items: center; gap: 10px;">
<svg width="24" height="24" viewBox="0 0 24 24">
<rect x="3" y="3" width="18" height="18" rx="2" fill="#4CAF50" />
<path d="M6 9 L12 15 L18 9" stroke="white" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" />
</svg>
道具
</h3>
<!-- 砖块商品 -->
<div style="display: flex; align-items: center; justify-content: space-between; padding: 15px; background-color: rgba(50, 50, 50, 0.8); border-radius: 10px; margin-bottom: 10px;">
<div style="display: flex; align-items: center; gap: 15px;">
<!-- 砖块图标 -->
<svg width="50" height="50" viewBox="0 0 50 50">
<rect x="5" y="5" width="40" height="40" fill="#8B4513" stroke="#654321" stroke-width="2" />
<rect x="10" y="10" width="15" height="15" fill="#A0522D" />
<rect x="25" y="10" width="15" height="15" fill="#A0522D" />
<rect x="10" y="25" width="15" height="15" fill="#A0522D" />
<rect x="25" y="25" width="15" height="15" fill="#A0522D" />
<line x1="25" y1="10" x2="25" y2="35" stroke="#654321" stroke-width="1" />
<line x1="10" y1="25" x2="40" y2="25" stroke="#654321" stroke-width="1" />
</svg>
<div>
<h4 style="margin: 0; color: #DEB887;">砖块</h4>
<p style="margin: 5px 0 0 0; font-size: 14px; color: #aaa;">按E在前方放置墙壁</p>
</div>
</div>
<div style="display: flex; align-items: center; gap: 10px;">
<span style="color: #FFD700; font-size: 18px; font-weight: bold;">100</span>
<button id="buy-brick-button"
style="padding: 8px 20px; font-size: 16px; background-color: #4CAF50; color: white; border: none; border-radius: 8px; cursor: pointer;">购买</button>
</div>
</div>
</div>
<!-- 升级分区 -->
<div>
<h3 style="color: #2196F3; font-size: 20px; margin: 0 0 15px 0; padding-bottom: 8px; border-bottom: 2px solid #2196F3; display: flex; align-items: center; gap: 10px;">
<svg width="24" height="24" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" fill="#2196F3" />
<path d="M12 8 L12 16 M8 12 L16 12" stroke="white" stroke-width="2" stroke-linecap="round" />
<circle cx="12" cy="12" r="3" fill="white" />
</svg>
升级
</h3>
<!-- 激光炮商品 -->
<div style="display: flex; align-items: center; justify-content: space-between; padding: 15px; background-color: rgba(50, 50, 50, 0.8); border-radius: 10px; margin-bottom: 10px;">
<div style="display: flex; align-items: center; gap: 15px;">
<svg width="50" height="50" viewBox="0 0 50 50">
<rect x="5" y="20" width="40" height="10" fill="#4169E1" rx="2" />
<rect x="8" y="22" width="34" height="6" fill="#6495ED" />
<circle cx="40" cy="25" r="8" fill="#FF0000" stroke="#FF4500" stroke-width="2" />
<circle cx="40" cy="25" r="4" fill="#FFA500" />
</svg>
<div>
<h4 style="margin: 0; color: #DEB887;">激光炮</h4>
<p id="laser-status-text" style="margin: 5px 0 0 0; font-size: 14px; color: #aaa;">等级: 0 (20%几率)</p>
</div>
</div>
<div style="display: flex; align-items: center; gap: 10px;">
<span style="color: #FFD700; font-size: 18px; font-weight: bold;">500</span>
<button id="buy-laser-button"
style="padding: 8px 20px; font-size: 16px; background-color: #2196F3; color: white; border: none; border-radius: 8px; cursor: pointer;">购买</button>
</div>
</div>
<!-- 连发商品 -->
<div style="display: flex; align-items: center; justify-content: space-between; padding: 15px; background-color: rgba(50, 50, 50, 0.8); border-radius: 10px; margin-bottom: 10px;">
<div style="display: flex; align-items: center; gap: 15px;">
<svg width="50" height="50" viewBox="0 0 50 50">
<rect x="10" y="22" width="30" height="8" fill="#FF6347" rx="2" />
<circle cx="18" cy="26" r="6" fill="#FF4500" stroke="#DC143C" stroke-width="2" />
<circle cx="28" cy="26" r="6" fill="#FF4500" stroke="#DC143C" stroke-width="2" />
<circle cx="38" cy="26" r="6" fill="#FF4500" stroke="#DC143C" stroke-width="2" />
<path d="M20 10 L20 18 M30 10 L30 18" stroke="#FF6347" stroke-width="3" stroke-linecap="round" />
</svg>
<div>
<h4 style="margin: 0; color: #DEB887;">连发</h4>
<p id="rapid-fire-status-text" style="margin: 5px 0 0 0; font-size: 14px; color: #aaa;">等级: 0 (+30%频率)</p>
</div>
</div>
<div style="display: flex; align-items: center; gap: 10px;">
<span style="color: #FFD700; font-size: 18px; font-weight: bold;">500</span>
<button id="buy-rapid-fire-button"
style="padding: 8px 20px; font-size: 16px; background-color: #2196F3; color: white; border: none; border-radius: 8px; cursor: pointer;">购买</button>
</div>
</div>
</div>
</div>
<button id="close-shop-button"
style="position: absolute; top: 15px; right: 15px; padding: 8px 15px; font-size: 16px; background-color: #f44336; color: white; border: none; border-radius: 8px;"
data-translate="close-shop-button">关闭</button>
</div>
<!-- 介绍按钮弹窗 -->
<div id="info-dialog"
style="display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: rgba(0, 0, 0, 0.8); padding: 20px; border-radius: 10px; z-index: 100; width: 250px;">
<h2 style="color: white; text-align: center; margin-bottom: 20px; margin-top: 0;"
data-translate="info-dialog-title">游戏介绍</h2>
<div style="display: flex; flex-direction: column; gap: 10px;">
<button id="enemy-info-button"
style="padding: 8px 16px; font-size: 16px; background-color: #2196F3; color: white; border: none; border-radius: 5px;"
data-translate="enemy-info-button">敌人介绍</button>
<button id="control-info-button"
style="padding: 8px 16px; font-size: 16px; background-color: #2196F3; color: white; border: none; border-radius: 5px;"
data-translate="control-info-button">按键介绍</button>
<button id="prop-trap-info-button"
style="padding: 8px 16px; font-size: 16px; background-color: #2196F3; color: white; border: none; border-radius: 5px;"
data-translate="prop-trap-info-button">道具和机关介绍</button>
<button id="close-info-button"
style="padding: 8px 16px; font-size: 16px; margin-top: 15px; background-color: #f44336; color: white; border: none; border-radius: 5px;"
data-translate="close-info-button">关闭</button>
</div>
</div>
</div>
<div id="game-over-screen" style="display: none;">
<h1 data-translate="game-over">游戏结束</h1>
<button id="restart-button" data-translate="restart-button">再来一次</button>
</div>
<!-- 关卡选择弹窗 -->
<div id="level-select-modal"
style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #333; padding: 20px; border-radius: 10px; z-index: 1000;">
<h2 style="color: white; text-align: center;" data-translate="level-select-title">选择关卡</h2>
<div id="level-grid"
style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; margin-top: 20px; max-height: 400px; overflow-y: auto;">
<!-- 关卡按钮将通过JS动态生成 -->
</div>
<button id="close-level-select"
style="margin-top: 20px; padding: 8px 16px; background-color: #f44336; color: white; border: none; border-radius: 5px;"
data-translate="close-level-select">关闭</button>
</div>
<div id="win-screen" style="display: none;">
<h1 id="win-message" data-translate="win">胜利!</h1>
<button id="next-level-button" data-translate="next-level-button">下一关</button>
</div>
</div>
<script>
let gameDifficulty = 1; // 默认初级难度
// 难度选择模态框控制
const difficultyBtn = document.getElementById('difficulty-btn');
const difficultyModal = document.getElementById('difficulty-modal');
const modalOverlay = document.getElementById('modal-overlay');
const difficultyOptions = document.querySelectorAll('.difficulty-option');
difficultyBtn.addEventListener('click', () => {
difficultyModal.style.display = 'block';
modalOverlay.style.display = 'block';
});
modalOverlay.addEventListener('click', () => {
difficultyModal.style.display = 'none';
modalOverlay.style.display = 'none';
});
difficultyOptions.forEach(option => {
option.addEventListener('click', () => {
gameDifficulty = parseInt(option.dataset.level);
difficultyModal.style.display = 'none';
modalOverlay.style.display = 'none';
alert(`已选择${option.textContent}难度`);
});
});
// 多语言支持
let currentLanguage = 'zh'; // 默认中文
const translations = {
zh: {
'game-title': '坦克大战:海陆双战',
'total-score': '金币:',
'reset-confirm-message': '确定重置金币?',
'difficulty-btn': '难度',
'difficulty-title': '选择难度',
'easy': '初级',
'medium': '中级',
'hard': '高级',
'start-button': '开始游戏',
'level-select-button': '关卡选择',
'toggle-timer-button': '模式:正常',
'toggle-timer-button-timed': '模式:限时',
'info-button': '游戏介绍',
'player-name-label': '玩家名称:',
'player-name-placeholder': '输入你的名字',
'tank-color-label': '坦克颜色:',
'restart-button': '再来一次',
'next-level-button': '下一关',
'game-over': '游戏结束',
'win': '胜利!',
'level-select-title': '选择关卡',
'close-level-select': '关闭',
'info-dialog-title': '游戏介绍',
'enemy-info-button': '敌人介绍',
'control-info-button': '按键介绍',
'prop-trap-info-button': '道具和机关介绍',
'close-info-button': '关闭',
'close-enemy-info': '关闭',
'close-control-info': '关闭',
'close-prop-trap-info': '关闭',
'enemy-info-title': '敌人介绍',
'normal-enemy': '普通敌人',
'normal-enemy-desc': '速度中等,生命值较低,武器伤害一般。是游戏中最常见的敌人类型。',
'medium-enemy': '中型敌人',
'medium-enemy-desc': '速度较慢,但生命值比普通敌人高。需要多次攻击才能消灭。',
'boss-enemy': '毁灭者',
'boss-enemy-desc': '陆地关卡的终极BOSS,代号"毁灭者"的巨型坦克。体型庞大,装甲厚重,火力惊人。战斗分为三个阶段:第一阶段为普通状态,第二阶段加速并切换为双发射击,第三阶段进入狂暴状态使用三发射击模式。击败它即可进入海域关卡。',
'turret-enemy': '炮台',
'turret-enemy-desc': '固定在墙上的防御武器,会从斜向(斜上、斜下、斜左、斜右)发射子弹,对玩家造成1点伤害。',
'battleship-enemy': '无畏舰',
'battleship-enemy-desc': '海域关卡的终极BOSS,体型庞大的军舰。普通攻击与普通敌人相同,但拥有多种特殊技能:1.朝四周顺序开炮(北→东北→东→东南→南→西南→西→西北);2.朝任意方向发射3行子弹;3.在周围放置地雷;4.召唤小弟,期间自己不发射子弹。',
'control-info-title': '按键介绍',
'movement-control': '移动控制',
'shoot-control': '射击控制',
'w-key': 'W键',
'w-desc': '向上移动',
's-key': 'S键',
's-desc': '向下移动',
'a-key': 'A键',
'a-desc': '向左移动',
'd-key': 'D键',
'd-desc': '向右移动',
'space-key': '空格键',
'space-desc': '发射子弹',
'prop-trap-info-title': '道具和机关介绍',
'props': '道具',
'traps': '机关',
'extra-life': '额外生命',
'extra-life-desc': '拾取后增加一条生命。',
'speed-boost': '速度提升',
'speed-boost-desc': '拾取后5秒内提升坦克移动速度。',
'shield': '护盾',
'shield-desc': '拾取后5秒内获得护盾保护,免疫一次伤害。',
'time-freeze': '时间冻结',
'time-freeze-desc': '拾取后5秒内冻结所有敌人。',
'time-slow': '时间减速',
'time-slow-desc': '拾取后8秒内降低所有敌人移动和发射速度的50%。',
'mine': '地雷',
'mine-desc': '触碰后爆炸,对坦克造成伤害。',
'oil-barrel': '油桶',
'oil-barrel-desc': '射击后爆炸,对附近敌人造成伤害。',
'portal': '传送门',
'portal-desc': '进入一个传送门后会从对应的另一个传送门出来。',
'laser-net': '激光网',
'laser-net-desc': '海域关卡特有机关,红色网状结构,四角有黑色边框。玩家踩上后无法移动3秒,激光网会变形包裹玩家。',
'language-btn': '中文',
'lang-zh': '中文',
'lang-en': 'English',
'level': '关卡',
'boss': 'BOSS',
'time': '时间',
'score': '金币',
'story-title': '坦克大战:海陆双战',
'story-p1': '战火席卷全球,敌军在陆地和海洋同时发起进攻。你作为联军指挥官,必须带领部队击退敌人。',
'story-p2': '穿越两大战场:陆地关卡(1-10)和海域关卡(11-20)。击败陆地BOSS"毁灭者"后开启海域战场,最终挑战海域BOSS"无畏舰"。',
'story-p3': '收集护盾、加速等道具增强战斗力,躲避地雷、激光网等机关陷阱。选择不同模式:正常、限时或极限模式。',
'skip-story': '跳过剧情',
'ocean-world': '第2大关:海域',
'land-world': '第1大关:陆地',
'shop-button': '商店',
'shop-title': '商店',
'shop-empty': '商品即将上架...',
'close-shop-button': '关闭'
},
en: {
'game-title': 'Tank Battle: Steel Defense',
'total-score': 'Gold: ',
'reset-confirm-message': 'Are you sure to reset gold?',
'difficulty-btn': 'Difficulty',
'difficulty-title': 'Select Difficulty',
'easy': 'Easy',
'medium': 'Medium',
'hard': 'Hard',
'start-button': 'Start Game',
'level-select-button': 'Level Select',
'toggle-timer-button': 'Mode: Normal',
'toggle-timer-button-timed': 'Mode: Timed',
'info-button': 'Game Info',
'player-name-label': 'Player Name:',
'player-name-placeholder': 'Enter your name',
'tank-color-label': 'Tank Color:',
'restart-button': 'Play Again',
'next-level-button': 'Next Level',
'game-over': 'Game Over',
'win': 'Victory!',
'level-select-title': 'Select Level',
'close-level-select': 'Close',
'info-dialog-title': 'Game Information',
'enemy-info-button': 'Enemy Info',
'control-info-button': 'Controls',
'prop-trap-info-button': 'Power-ups & Traps',
'close-info-button': 'Close',
'close-enemy-info': 'Close',
'close-control-info': 'Close',
'close-prop-trap-info': 'Close',
'enemy-info-title': 'Enemy Information',
'normal-enemy': 'Normal Enemy',
'normal-enemy-desc': 'Medium speed, low health, average damage. The most common enemy type in the game.',
'medium-enemy': 'Medium Enemy',
'medium-enemy-desc': 'Slower speed, but higher health than normal enemies. Requires multiple attacks to destroy.',
'boss-enemy': 'Destroyer',
'boss-enemy-desc': 'The ultimate BOSS of land levels, a massive tank codenamed "Destroyer". Huge size, heavy armor, and devastating firepower. Battle has three phases: Phase 1 is normal state, Phase 2 accelerates with double-shot mode, Phase 3 enters berserk mode with triple-shot mode. Defeat it to enter ocean levels.',
'turret-enemy': 'Turret',
'turret-enemy-desc': 'A defensive weapon fixed to walls. It fires bullets in diagonal directions and deals 1 damage to the player. Destroyed when shot.',
'battleship-enemy': 'Dreadnought',
'battleship-enemy-desc': 'The ultimate BOSS of ocean levels, a massive warship. Normal attacks are the same as regular enemies, but it has multiple special abilities: 1. Fire in all 8 directions sequentially (N→NE→E→SE→S→SW→W→NW); 2. Fire 3 rows of bullets in random direction; 3. Place mines around itself; 4. Summon minions while not firing.',
'control-info-title': 'Controls',
'movement-control': 'Movement',
'shoot-control': 'Shooting',
'w-key': 'W Key',
'w-desc': 'Move Up',
's-key': 'S Key',
's-desc': 'Move Down',
'a-key': 'A Key',
'a-desc': 'Move Left',
'd-key': 'D Key',
'd-desc': 'Move Right',
'space-key': 'Spacebar',
'space-desc': 'Fire Bullet',
'prop-trap-info-title': 'Power-ups & Traps',
'props': 'Power-ups',
'traps': 'Traps',
'extra-life': 'Extra Life',
'extra-life-desc': 'Gain one additional life.',
'speed-boost': 'Speed Boost',
'speed-boost-desc': 'Increase tank movement speed for 5 seconds.',
'shield': 'Shield',
'shield-desc': 'Gain shield protection for 5 seconds, immune to one damage.',
'time-freeze': 'Time Freeze',
'time-freeze-desc': 'Freeze all enemies for 5 seconds.',
'time-slow': 'Time Slow',
'time-slow-desc': 'Reduce enemy movement and firing speed by 50% for 8 seconds.',
'mine': 'Mine',
'mine-desc': 'Explodes on contact, damaging tanks.',
'oil-barrel': 'Oil Barrel',
'oil-barrel-desc': 'Explodes when shot, damaging nearby enemies.',
'portal': 'Portal',
'portal-desc': 'Enter one portal and exit from the corresponding portal.',
'laser-net': 'Laser Net',
'laser-net-desc': 'Ocean level exclusive trap. Red mesh structure with black borders at four corners. Player cannot move for 3 seconds when stepped on, and the laser net will transform to wrap around the player.',
'language-btn': 'English',
'lang-zh': '中文',
'lang-en': 'English',
'level': 'Level',
'boss': 'BOSS',
'time': 'Time',
'score': 'Gold',
'story-title': 'Tank Battle: Land & Sea',
'story-p1': 'War engulfs the globe. Enemy forces attack on both land and sea. As the coalition commander, you must lead your forces to repel the invaders.',
'story-p2': 'Conquer two battlefields: Land levels (1-10) and Ocean levels (11-20). Defeat the land BOSS "Destroyer" to unlock the ocean battlefield, and finally challenge the ocean BOSS "Dreadnought".',
'story-p3': 'Collect power-ups like shield and speed boost to enhance your combat ability. Avoid traps like mines and laser nets. Choose different modes: Normal, Timed, or Extreme mode.',
'skip-story': 'Skip Story',
'ocean-world': 'World 2: Ocean',
'land-world': 'World 1: Land',
'shop-button': 'Shop',
'shop-title': 'Shop',
'shop-empty': 'Items coming soon...',
'close-shop-button': 'Close'
}
};
// 语言切换按钮控制
const languageBtn = document.getElementById('language-btn');
const languageModal = document.getElementById('language-modal');
const langZhBtn = document.getElementById('lang-zh');
const langEnBtn = document.getElementById('lang-en');
languageBtn.addEventListener('click', (e) => {
e.stopPropagation();
languageModal.style.display = languageModal.style.display === 'block' ? 'none' : 'block';
});
langZhBtn.addEventListener('click', () => {
setLanguage('zh');
});
langEnBtn.addEventListener('click', () => {
setLanguage('en');
});
// 点击其他地方关闭语言弹窗
document.addEventListener('click', (e) => {
if (e.target !== languageBtn && !languageModal.contains(e.target)) {
languageModal.style.display = 'none';
}
});
// 设置语言函数
function setLanguage(lang) {
currentLanguage = lang;
languageBtn.textContent = translations[lang]['language-btn'];
languageModal.style.display = 'none';
// 更新所有需要翻译的元素
updateTranslations();
}
// 更新翻译函数
function updateTranslations() {
const elements = document.querySelectorAll('[data-translate]');
elements.forEach(el => {
const key = el.dataset.translate;
if (translations[currentLanguage][key]) {
el.textContent = translations[currentLanguage][key];
}
});
// 更新placeholder
const playerNameInput = document.getElementById('player-name');
if (playerNameInput) {
playerNameInput.placeholder = translations[currentLanguage]['player-name-placeholder'];
}
// 更新弹窗内容
document.getElementById('reset-confirm-dialog').querySelector('p').textContent = translations[currentLanguage]['reset-confirm-message'];
// 更新难度弹窗
const difficultyTitle = document.querySelector('#difficulty-modal h2');
if (difficultyTitle) {
difficultyTitle.textContent = translations[currentLanguage]['difficulty-title'];
}
const difficultyOptions = document.querySelectorAll('.difficulty-option');
difficultyOptions[0].textContent = translations[currentLanguage]['easy'];
difficultyOptions[1].textContent = translations[currentLanguage]['medium'];
difficultyOptions[2].textContent = translations[currentLanguage]['hard'];
// 更新关卡选择弹窗
const levelSelectTitle = document.querySelector('#level-select-modal h2');
if (levelSelectTitle) {
levelSelectTitle.textContent = translations[currentLanguage]['level-select-title'];
}
// 更新关卡按钮文本
const levelButtons = document.querySelectorAll('#level-grid button');
const unlockedLevels = getUnlockedLevels();
levelButtons.forEach((btn, index) => {
const level = index + 1;
if (level <= unlockedLevels) {
if (level === 10) {
btn.textContent = translations[currentLanguage]['level'] + ' ' + level + ' (' + translations[currentLanguage]['boss-enemy'] + ')';
} else if (level === 20) {
btn.textContent = translations[currentLanguage]['level'] + ' ' + level + ' (' + translations[currentLanguage]['battleship-enemy'] + ')';
} else if (level <= 10) {
btn.textContent = translations[currentLanguage]['level'] + ' ' + level + ' (' + translations[currentLanguage]['land-world'].replace('World 1: ', '').replace('第1大关:', '') + ')';
} else {
btn.textContent = translations[currentLanguage]['level'] + ' ' + level + ' (' + translations[currentLanguage]['ocean-world'].replace('World 2: ', '').replace('第2大关:', '') + ')';
}
}
});
// 更新游戏结束和胜利屏幕
document.querySelector('#game-over-screen h1').textContent = translations[currentLanguage]['game-over'];
document.getElementById('win-message').textContent = translations[currentLanguage]['win'];
// 更新模式按钮
const toggleTimerBtn = document.getElementById('toggle-timer-button');
if (toggleTimerBtn) {
toggleTimerBtn.textContent = gameState.timeLimitEnabled ? translations[currentLanguage]['toggle-timer-button-timed'] : translations[currentLanguage]['toggle-timer-button'];
}
}
// 音频播放辅助函数
// Game constants
const CANVAS_WIDTH = 950;
const CANVAS_HEIGHT = 650;
const TANK_SIZE = 30;
const BULLET_SIZE = 5;
const BULLET_SPEED = 5;
const TANK_SPEED = 2;
const ENEMY_SPEED = 1;
const WALL_SIZE = 30;
const PROP_SIZE = 20;
const TRAP_SIZE = 20;
const PROP_TYPES = {
EXTRA_LIFE: 'extraLife',
SPEED_BOOST: 'speedBoost',
SHIELD: 'shield',
TIME_FREEZE: 'timeFreeze',
TIME_SLOW: 'timeSlow'
};
const TRAP_TYPES = {
MINE: 'mine',
PORTAL: 'portal',
OIL_BARREL: 'oilBarrel',
LASER_NET: 'laserNet'
};
// 炮台类
class Turret {
constructor(x, y) {
this.x = x;
this.y = y;
this.width = 25;
this.height = 25;
this.color = '#757575'; // 灰色
this.shootCooldown = 60; // 射击冷却
this.maxShootCooldown = 60; // 最大冷却
this.direction = 'up-left'; // 初始方向(斜上左)
this.active = true;
}
draw() {
if (!this.active) return;
ctx.save();
ctx.translate(this.x + this.width / 2, this.y + this.height / 2);
// 根据方向旋转
switch (this.direction) {
case 'up-left':
ctx.rotate(-Math.PI / 4);
break;
case 'up-right':
ctx.rotate(Math.PI / 4);
break;
case 'down-left':
ctx.rotate(-3 * Math.PI / 4);
break;
case 'down-right':
ctx.rotate(3 * Math.PI / 4);
break;
}
// 绘制底座发光效果
const glowGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, this.width / 2 + 5);
glowGradient.addColorStop(0, 'rgba(255, 100, 0, 0.3)');
glowGradient.addColorStop(0.5, 'rgba(255, 50, 0, 0.15)');
glowGradient.addColorStop(1, 'rgba(255, 0, 0, 0)');
ctx.fillStyle = glowGradient;
ctx.beginPath();
ctx.arc(0, 0, this.width / 2 + 8, 0, Math.PI * 2);
ctx.fill();
// 绘制六边形底座
ctx.fillStyle = '#78909C'; // 蓝灰色底座
ctx.beginPath();
for (let i = 0; i < 6; i++) {
const angle = (i * Math.PI / 3) - Math.PI / 6;
const x = Math.cos(angle) * (this.width / 2);
const y = Math.sin(angle) * (this.height / 2);
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.closePath();
ctx.fill();
// 绘制底座内圈
ctx.fillStyle = '#546E7A';
ctx.beginPath();
ctx.arc(0, 0, this.width / 3, 0, Math.PI * 2);
ctx.fill();
// 绘制底座中心发光
const centerGlow = ctx.createRadialGradient(0, 0, 0, 0, 0, this.width / 4);
centerGlow.addColorStop(0, 'rgba(255, 150, 0, 0.8)');
centerGlow.addColorStop(0.5, 'rgba(255, 100, 0, 0.4)');
centerGlow.addColorStop(1, 'rgba(255, 50, 0, 0)');
ctx.fillStyle = centerGlow;
ctx.beginPath();
ctx.arc(0, 0, this.width / 4, 0, Math.PI * 2);
ctx.fill();
// 绘制炮管装甲底座
ctx.fillStyle = '#455A64';
ctx.beginPath();
ctx.arc(0, -3, 8, 0, Math.PI * 2);
ctx.fill();
// 绘制炮管主体(朝向正上方,通过旋转实现斜向)
ctx.fillStyle = '#37474F';
ctx.fillRect(-4, -this.height / 2 - 15, 8, 20);
// 绘制炮管中间加强环
ctx.fillStyle = '#263238';
ctx.fillRect(-5, -this.height / 2 - 8, 10, 6);
// 绘制炮管尖端(炮口)
ctx.fillStyle = '#FF5722'; // 橙色炮口
ctx.beginPath();
ctx.arc(0, -this.height / 2 - 18, 6, 0, Math.PI * 2);
ctx.fill();
// 炮口内圈发光
const muzzleGlow = ctx.createRadialGradient(0, -this.height / 2 - 18, 0, 0, -this.height / 2 - 18, 5);
muzzleGlow.addColorStop(0, 'rgba(255, 200, 0, 1)');
muzzleGlow.addColorStop(0.5, 'rgba(255, 100, 0, 0.6)');
muzzleGlow.addColorStop(1, 'rgba(255, 50, 0, 0)');
ctx.fillStyle = muzzleGlow;
ctx.beginPath();
ctx.arc(0, -this.height / 2 - 18, 5, 0, Math.PI * 2);
ctx.fill();
// 炮口内圈深色
ctx.fillStyle = '#1A1A1A';
ctx.beginPath();
ctx.arc(0, -this.height / 2 - 18, 3, 0, Math.PI * 2);
ctx.fill();
// 绘制底座装饰铆钉
ctx.fillStyle = '#90A4AE';
for (let i = 0; i < 6; i++) {
const angle = (i * Math.PI / 3) + Math.PI / 6;
const x = Math.cos(angle) * (this.width / 3);
const y = Math.sin(angle) * (this.height / 3);
ctx.beginPath();
ctx.arc(x, y, 2.5, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
update() {
if (!this.active) return;
// 射击冷却
if (this.shootCooldown > 0) {
this.shootCooldown--;
return;
}
// 随机选择45度方向射击(斜向)
const directions = ['up-left', 'up-right', 'down-left', 'down-right'];
this.direction = directions[Math.floor(Math.random() * directions.length)];
this.shoot();
this.shootCooldown = this.maxShootCooldown;
}
shoot() {
const bulletX = this.x + this.width / 2 - 3;
const bulletY = this.y + this.height / 2 - 3;
gameState.bullets.push(new Bullet(
bulletX,
bulletY,
this.direction,
false, // 不是玩家子弹
'#9E9E9E' // 灰色子弹
));
}
}
// 烟雾粒子类
class Smoke {
constructor(x, y, direction) {
this.x = x;
this.y = y;
this.size = Math.random() * 2 + 1; // 进一步调小烟雾初始大小至1-3
this.speedX = 0;
this.speedY = 0;
this.opacity = 1;
this.decay = Math.random() * 0.04 + 0.02;
// 随机黑色和灰色烟雾,确保与地面颜色(#333333)不同
const grayValue = Math.floor(Math.random() * 50) + 30; // 30-80 之间的灰色值
this.color = `rgba(${grayValue}, ${grayValue}, ${grayValue}, 0.8)`;
this.angle = 0;
this.rotationSpeed = (Math.random() - 0.5) * 0.1;
// 根据坦克方向设置烟雾初始速度
switch (direction) {
case 'up':
this.speedX = (Math.random() - 0.5) * 0.35;
this.speedY = (Math.random() * 1 + 0.5) * 0.7;
break;
case 'down':
this.speedX = (Math.random() - 0.5) * 0.35;
this.speedY = -(Math.random() * 1 + 0.5) * 0.7;
break;
case 'left':
this.speedX = (Math.random() * 1 + 0.5) * 0.7;
this.speedY = (Math.random() - 0.5) * 0.35;
break;
case 'right':
this.speedX = -(Math.random() * 1 + 0.5) * 0.7;
this.speedY = (Math.random() - 0.5) * 0.35;
break;
}
}
update() {
this.x += this.speedX;
this.y += this.speedY;
this.opacity -= this.decay;
this.size *= 0.95;
this.angle += this.rotationSpeed;
}
draw() {
ctx.save();
ctx.globalAlpha = this.opacity;
ctx.fillStyle = this.color;
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
ctx.beginPath();
ctx.ellipse(0, 0, this.size, this.size * 0.7, 0, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
}
// Game state
const gameState = {
isPlaying: false,
score: 0,
level: 1,
lives: 3,
player: null,
playerName: '玩家',
playerColor: '#2196F3', // 默认蓝色
enemies: [],
bullets: [],
walls: [],
props: [],
traps: [],
airDrops: [],
lastAirDropTime: 0,
airDropInterval: 10000, // 每10秒生成一个空投
explosions: [],
smokes: [], // 烟雾粒子数组
playerPropParticles: [], // 玩家获得道具后的粒子效果
playerSpeedParticles: [], // 加速状态下的粒子效果
enemySlowParticles: [], // 敌人减速状态下的粒子效果
turrets: [], // 炮台数组
isSpeedBoosted: false, // 是否处于加速状态
activeProps: {}, // 当前激活的道具及其剩余时间
gameMode: 'normal', // normal, timed, extreme
timeLimitEnabled: false,
timeRemaining: 120,
timerInterval: null,
bossDeathAnimation: null, // BOSS死亡动画状态: 'exploding', 'cracking', 'transition', null
bossDeathTimer: 0, // 动画计时器
crackLines: [], // 地图裂缝线条
landFragments: null, // 陆地碎片(用于过渡动画)
scoreTexts: [], // 得分字幕数组
bricks: parseInt(localStorage.getItem('tankBattleBricks') || '0'), // 砖块数量
laserLevel: parseInt(localStorage.getItem('tankBattleLaserLevel') || '0'), // 激光炮等级(0-8)
laserEnabled: localStorage.getItem('tankBattleLaserEnabled') !== 'false', // 激光炮开关
rapidFireLevel: parseInt(localStorage.getItem('tankBattleRapidFireLevel') || '0'), // 连发等级(0-7)
ultimateReady: true, // 大招是否准备好了(每关一次)
ultimateCooldown: 0, // 大招冷却计时器(150秒 = 2分半)
ultimateState: 'idle', // 大招状态: 'idle', 'extending', 'firing', 'retracting'
ultimateTimer: 0, // 大招动画计时器
ultimateParticles: [], // 大招粒子特效
waves: [], // 海浪数组
waveEventCooldown: 0, // 海浪事件冷却(30秒 = 1800帧)
waveEventActive: false // 海浪事件是否正在发生
};
// 得分字幕类
class ScoreText {
constructor(x, y, score) {
this.x = x;
this.y = y;
this.score = score;
this.opacity = 1;
this.offsetY = 0;
this.fontSize = 24;
}
update() {
this.opacity -= 0.015;
this.offsetY -= 1;
this.fontSize *= 0.99;
}
draw() {
ctx.save();
ctx.globalAlpha = this.opacity;
ctx.font = `bold ${this.fontSize}px Arial`;
ctx.textAlign = 'center';
// 金色渐变效果
const gradient = ctx.createLinearGradient(this.x, this.y + this.offsetY - 20, this.x, this.y + this.offsetY);
gradient.addColorStop(0, '#FFD700');
gradient.addColorStop(0.5, '#FFA500');
gradient.addColorStop(1, '#FF8C00');
ctx.fillStyle = gradient;
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.strokeText(`+${this.score}`, this.x, this.y + this.offsetY);
ctx.fillText(`+${this.score}`, this.x, this.y + this.offsetY);
ctx.restore();
}
}
// 海浪类
class Wave {
constructor(x, y, speed, width, height) {
this.x = x;
this.y = y;
this.speed = speed;
this.width = width;
this.height = height;
this.opacity = 0.8;
this.phase = Math.random() * Math.PI * 2; // 随机相位
this.amplitude = 3; // 波浪振幅
this.frequency = 0.05; // 波浪频率
}
update() {
this.x += this.speed;
this.phase += this.frequency;
}
draw() {
ctx.save();
ctx.globalAlpha = this.opacity;
// 创建波浪渐变
const gradient = ctx.createLinearGradient(this.x, this.y, this.x + this.width, this.y);
gradient.addColorStop(0, 'rgba(100, 180, 255, 0.3)');
gradient.addColorStop(0.5, 'rgba(50, 150, 255, 0.6)');
gradient.addColorStop(1, 'rgba(100, 180, 255, 0.3)');
ctx.fillStyle = gradient;
ctx.beginPath();
// 绘制波浪形状
const startY = this.y;
const segments = 20;
const segmentWidth = this.width / segments;
for (let i = 0; i <= segments; i++) {
const x = this.x + i * segmentWidth;
const waveOffset = Math.sin(this.phase + i * 0.5) * this.amplitude;
const y = startY + waveOffset;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
// 完成路径
ctx.lineTo(this.x + this.width, startY + this.height);
ctx.lineTo(this.x, startY + this.height);
ctx.closePath();
ctx.fill();
// 绘制波浪线
ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
ctx.lineWidth = 2;
ctx.beginPath();
for (let i = 0; i <= segments; i++) {
const x = this.x + i * segmentWidth;
const waveOffset = Math.sin(this.phase + i * 0.5) * this.amplitude;
const y = startY + waveOffset + 3;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
ctx.restore();
}
// 检测与坦克的碰撞
checkCollision(tank) {
const tankCenterX = tank.x + tank.width / 2;
const tankCenterY = tank.y + tank.height / 2;
const waveCenterX = this.x + this.width / 2;
const waveCenterY = this.y + this.height / 2;
return Math.abs(tankCenterX - waveCenterX) < (this.width / 2 + tank.width / 2) * 0.8 &&
Math.abs(tankCenterY - waveCenterY) < (this.height / 2 + tank.height / 2) * 0.8;
}
}
// 触发海浪事件
function triggerWaveEvent() {
// 生成3-5个海浪
const waveCount = 3 + Math.floor(Math.random() * 3);
for (let i = 0; i < waveCount; i++) {
const width = 100 + Math.random() * 80;
const height = 30 + Math.random() * 20;
const y = 50 + Math.random() * (CANVAS_HEIGHT - 150);
const speed = 1.5 + Math.random() * 2; // 速度各不相同
gameState.waves.push(new Wave(-width, y, speed, width, height));
}
gameState.waveEventActive = true;
gameState.waveEventCooldown = 1800; // 30秒冷却
}
// DOM elements
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// BOSS死亡动画函数
function startBossDeathAnimation() {
gameState.bossDeathAnimation = 'exploding';
gameState.bossDeathTimer = 0;
gameState.crackLines = [];
}
function updateBossDeathAnimation() {
if (!gameState.bossDeathAnimation) return;
gameState.bossDeathTimer++;
// 阶段1: 全屏随机爆炸特效 (约4秒)
if (gameState.bossDeathAnimation === 'exploding') {
if (gameState.bossDeathTimer < 120) { // 4秒 @ 30fps
// 随机生成爆炸(更密集)
if (Math.random() < 0.5) {
const x = Math.random() * CANVAS_WIDTH;
const y = Math.random() * CANVAS_HEIGHT;
gameState.explosions.push(new Explosion(x, y, false, 30 + Math.random() * 40));
}
// 额外的大爆炸
if (gameState.bossDeathTimer % 30 === 0) {
const x = Math.random() * CANVAS_WIDTH;
const y = Math.random() * CANVAS_HEIGHT;
gameState.explosions.push(new Explosion(x, y, false, 60 + Math.random() * 40));
}
} else {
// 进入阶段2: 地图裂开
gameState.bossDeathAnimation = 'cracking';
gameState.bossDeathTimer = 0;
// 生成初始裂缝
generateCrackLines();
}
}
// 阶段2: 地图裂开 (约4秒)
else if (gameState.bossDeathAnimation === 'cracking') {
if (gameState.bossDeathTimer < 120) { // 4秒 @ 30fps
// 继续生成裂缝(更频繁)
if (Math.random() < 0.2) {
generateCrackLines();
}
// 持续增加裂缝长度(逐渐裂开效果)
for (let line of gameState.crackLines) {
if (line.length < 200) { // 最大长度
line.length += 3 + Math.random() * 3;
}
// 随机生成分支
if (line.length > 50 && Math.random() < 0.02 && !line.hasBranch) {
line.hasBranch = true;
gameState.crackLines.push({
x: line.x + Math.cos(line.angle) * line.length * 0.5,
y: line.y + Math.sin(line.angle) * line.length * 0.5,
angle: line.angle + (Math.random() - 0.5) * Math.PI,
length: 30 + Math.random() * 50,
hasBranch: false
});
}
}
// 持续生成爆炸
if (Math.random() < 0.2) {
const x = Math.random() * CANVAS_WIDTH;
const y = Math.random() * CANVAS_HEIGHT;
gameState.explosions.push(new Explosion(x, y, false, 20 + Math.random() * 30));
}
} else {
// 进入阶段3: 最终爆炸和地图转换
gameState.bossDeathAnimation = 'transition';
gameState.bossDeathTimer = 0;
// 生成更多最终大爆炸
for (let i = 0; i < 20; i++) {
const x = Math.random() * CANVAS_WIDTH;
const y = Math.random() * CANVAS_HEIGHT;
gameState.explosions.push(new Explosion(x, y, false, 40 + Math.random() * 40));
}
}
}
// 阶段3: 海域地图过渡 (约4秒)
else if (gameState.bossDeathAnimation === 'transition') {
// 持续生成小爆炸
if (gameState.bossDeathTimer < 90 && Math.random() < 0.3) {
const x = Math.random() * CANVAS_WIDTH;
const y = Math.random() * CANVAS_HEIGHT;
gameState.explosions.push(new Explosion(x, y, false, 15 + Math.random() * 20));
}
if (gameState.bossDeathTimer >= 120) { // 4秒后完成
gameState.bossDeathAnimation = null;
gameState.bossDeathTimer = 0;
gameState.crackLines = [];
gameState.landFragments = null;
// 直接结束关卡,不管还有没有敌人
endGame(true);
}
}
}
function generateCrackLines() {
const startX = Math.random() * CANVAS_WIDTH;
const startY = Math.random() * CANVAS_HEIGHT;
const angle = Math.random() * Math.PI * 2;
gameState.crackLines.push({
x: startX,
y: startY,
angle: angle,
length: 20 + Math.random() * 40
});
}
function drawBossDeathAnimation() {
if (!gameState.bossDeathAnimation) return;
// 绘制裂缝(黑色,带渐变效果)
if (gameState.bossDeathAnimation === 'cracking' || gameState.bossDeathAnimation === 'transition') {
// 裂缝阴影效果
ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';
ctx.lineWidth = 8;
ctx.lineCap = 'round';
for (let line of gameState.crackLines) {
ctx.beginPath();
ctx.moveTo(line.x + 2, line.y + 2);
ctx.lineTo(
line.x + Math.cos(line.angle) * line.length + 2,
line.y + Math.sin(line.angle) * line.length + 2
);
ctx.stroke();
}
// 主裂缝(黑色,更粗)
ctx.strokeStyle = '#000000';
ctx.lineWidth = 5;
ctx.lineCap = 'round';
for (let line of gameState.crackLines) {
ctx.beginPath();
ctx.moveTo(line.x, line.y);
ctx.lineTo(
line.x + Math.cos(line.angle) * line.length,
line.y + Math.sin(line.angle) * line.length
);
ctx.stroke();
// 裂缝内部发光效果
ctx.strokeStyle = 'rgba(50, 50, 50, 0.8)';
ctx.lineWidth = 2;
ctx.stroke();
}
}
// 阶段3: 陆地碎片散开并过渡到海域地图
if (gameState.bossDeathAnimation === 'transition') {
// 初始化碎片(只在第一帧)
if (gameState.bossDeathTimer === 0) {
gameState.landFragments = [];
const fragmentSize = 60;
for (let x = 0; x < CANVAS_WIDTH; x += fragmentSize) {
for (let y = 0; y < CANVAS_HEIGHT; y += fragmentSize) {
// 根据裂缝位置决定碎片是否分离
let willBreak = false;
for (let crack of gameState.crackLines) {
const dist = Math.sqrt(
Math.pow((x + fragmentSize/2) - crack.x, 2) +
Math.pow((y + fragmentSize/2) - crack.y, 2)
);
if (dist < crack.length + 50) {
willBreak = true;
break;
}
}
if (willBreak || Math.random() < 0.3) {
const angle = Math.atan2(
(y + fragmentSize/2) - CANVAS_HEIGHT/2,
(x + fragmentSize/2) - CANVAS_WIDTH/2
);
gameState.landFragments.push({
x: x,
y: y,
width: fragmentSize,
height: fragmentSize,
targetX: x + Math.cos(angle) * (200 + Math.random() * 200),
targetY: y + Math.sin(angle) * (200 + Math.random() * 200),
rotation: (Math.random() - 0.5) * Math.PI * 2,
rotationSpeed: (Math.random() - 0.5) * 0.1,
opacity: 1,
currentRotation: 0
});
}
}
}
}
const progress = gameState.bossDeathTimer / 120;
// 绘制海洋背景(逐渐显现)
const oceanAlpha = Math.min(progress * 1.2, 1);
const oceanGradient = ctx.createLinearGradient(0, 0, 0, CANVAS_HEIGHT);
oceanGradient.addColorStop(0, `rgba(33, 150, 243, ${oceanAlpha})`);
oceanGradient.addColorStop(0.3, `rgba(66, 165, 245, ${oceanAlpha})`);
oceanGradient.addColorStop(0.7, `rgba(0, 188, 212, ${oceanAlpha})`);
oceanGradient.addColorStop(1, `rgba(0, 131, 143, ${oceanAlpha})`);
ctx.fillStyle = oceanGradient;
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// 绘制波浪效果
if (progress > 0.3) {
ctx.strokeStyle = `rgba(255, 255, 255, ${oceanAlpha * 0.4})`;
ctx.lineWidth = 2;
for (let i = 0; i < 6; i++) {
ctx.beginPath();
for (let x = 0; x <= CANVAS_WIDTH; x += 15) {
const y = 30 + i * 40 + Math.sin((x + gameState.bossDeathTimer * 2 + i * 100) * 0.02) * 10;
if (x === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
}
}
// 绘制散开的陆地碎片
if (gameState.landFragments) {
for (let fragment of gameState.landFragments) {
// 计算碎片位置(使用缓动函数)
const easeProgress = 1 - Math.pow(1 - progress, 3);
const currentX = fragment.x + (fragment.targetX - fragment.x) * easeProgress;
const currentY = fragment.y + (fragment.targetY - fragment.y) * easeProgress;
fragment.currentRotation += fragment.rotationSpeed;
const currentOpacity = Math.max(0, 1 - easeProgress * 1.2);
// 保存当前状态
ctx.save();
ctx.translate(currentX + fragment.width/2, currentY + fragment.height/2);
ctx.rotate(fragment.currentRotation);
ctx.globalAlpha = currentOpacity;
// 绘制黑色地图碎片
const landGradient = ctx.createLinearGradient(-fragment.width/2, -fragment.height/2, fragment.width/2, fragment.height/2);
landGradient.addColorStop(0, '#1a1a1a');
landGradient.addColorStop(0.5, '#2d2d2d');
landGradient.addColorStop(1, '#1a1a1a');
ctx.fillStyle = landGradient;
ctx.fillRect(-fragment.width/2, -fragment.height/2, fragment.width, fragment.height);
// 添加裂纹纹理
ctx.strokeStyle = 'rgba(50, 50, 50, 0.5)';
ctx.lineWidth = 1;
for (let i = 0; i < 2; i++) {
ctx.beginPath();
ctx.moveTo(
-fragment.width/2 + Math.random() * fragment.width,
-fragment.height/2 + Math.random() * fragment.height
);
ctx.lineTo(
-fragment.width/2 + Math.random() * fragment.width,
-fragment.height/2 + Math.random() * fragment.height
);
ctx.stroke();
}
// 绘制碎片边框
ctx.strokeStyle = '#333333';
ctx.lineWidth = 2;
ctx.strokeRect(-fragment.width/2, -fragment.height/2, fragment.width, fragment.height);
// 恢复状态
ctx.restore();
}
}
}
}
const startScreen = document.getElementById('start-screen');
// 初始化总分数显示
if (!localStorage.getItem('tankBattleTotalScore')) {
localStorage.setItem('tankBattleTotalScore', '0');
}
updateTotalScoreDisplay();
const gameOverScreen = document.getElementById('game-over-screen');
const winScreen = document.getElementById('win-screen');
const startButton = document.getElementById('start-button');
const levelSelectButton = document.getElementById('level-select-button');
const levelSelectModal = document.getElementById('level-select-modal');
const closeLevelSelect = document.getElementById('close-level-select');
const levelGrid = document.getElementById('level-grid');
const restartButton = document.getElementById('restart-button');
// 获取已解锁的关卡(从localStorage读取)
function getUnlockedLevels() {
const saved = localStorage.getItem('tankBattleUnlockedLevels');
if (saved) {
return parseInt(saved);
}
return 1; // 默认只解锁第一关
}
// 保存已解锁的关卡
function saveUnlockedLevel(level) {
const currentUnlocked = getUnlockedLevels();
if (level > currentUnlocked) {
localStorage.setItem('tankBattleUnlockedLevels', level.toString());
updateLevelButtons();
}
}
// 更新关卡按钮状态
function updateLevelButtons() {
const unlockedLevels = getUnlockedLevels();
const buttons = levelGrid.querySelectorAll('button');
buttons.forEach((button, index) => {
const level = index + 1;
if (level <= unlockedLevels) {
// 陆地关卡(1-10)蓝色,海域关卡(11-20)青色
if (level <= 10) {
button.style.backgroundColor = '#2196F3';
} else {
button.style.backgroundColor = '#00BCD4';
}
button.style.color = 'white';
button.style.cursor = 'pointer';
button.disabled = false;
// 添加关卡类型提示
if (level === 10) {
button.textContent = '关卡 ' + level + ' (毁灭者)';
} else if (level === 20) {
button.textContent = '关卡 ' + level + ' (无畏舰)';
} else if (level <= 10) {
button.textContent = '关卡 ' + level + ' (陆地)';
} else {
button.textContent = '关卡 ' + level + ' (海域)';
}
} else {
button.style.backgroundColor = '#555';
button.style.color = '#999';
button.style.cursor = 'not-allowed';
button.disabled = true;
button.textContent = '🔒 ' + level;
}
});
}
// 生成关卡按钮(1-20关)
for (let i = 1; i <= 20; i++) {
const levelButton = document.createElement('button');
levelButton.textContent = '关卡 ' + i;
levelButton.style.padding = '10px';
levelButton.style.border = 'none';
levelButton.style.borderRadius = '5px';
levelButton.style.fontSize = '14px';
levelButton.style.minHeight = '50px';
levelButton.addEventListener('click', function () {
if (!this.disabled) {
gameState.level = i;
levelSelectModal.style.display = 'none';
startGame();
}
});
levelGrid.appendChild(levelButton);
}
// 初始化关卡按钮状态
updateLevelButtons();
// 关卡选择弹窗逻辑
levelSelectButton.addEventListener('click', function () {
updateLevelButtons(); // 更新按钮状态
levelSelectModal.style.display = 'block';
modalOverlay.style.display = 'block';
});
closeLevelSelect.addEventListener('click', function () {
levelSelectModal.style.display = 'none';
modalOverlay.style.display = 'none';
});
modalOverlay.addEventListener('click', function (event) {
if (event.target === modalOverlay) {
levelSelectModal.style.display = 'none';
modalOverlay.style.display = 'none';
}
});
const nextLevelButton = document.getElementById('next-level-button');
const infoButton = document.getElementById('info-button');
const infoDialog = document.getElementById('info-dialog');
const closeInfoButton = document.getElementById('close-info-button');
const enemyInfoButton = document.getElementById('enemy-info-button');
const controlInfoButton = document.getElementById('control-info-button');
const propTrapInfoButton = document.getElementById('prop-trap-info-button');
const enemyInfoDialog = document.getElementById('enemy-info-dialog');
const controlInfoDialog = document.getElementById('control-info-dialog');
const propTrapInfoDialog = document.getElementById('prop-trap-info-dialog');
const closeEnemyInfoButton = document.getElementById('close-enemy-info');
const closeControlInfoButton = document.getElementById('close-control-info');
const closePropTrapInfoButton = document.getElementById('close-prop-trap-info');
const dialogOverlay = document.getElementById('dialog-overlay');
// Event listeners
startButton.addEventListener('click', startGame);
// 游戏介绍按钮事件
infoButton.addEventListener('click', function () {
infoDialog.style.display = 'block';
});
// 商店按钮事件
const shopButton = document.getElementById('shop-button');
const shopDialog = document.getElementById('shop-dialog');
const closeShopButton = document.getElementById('close-shop-button');
const shopGold = document.getElementById('shop-gold');
shopButton.addEventListener('click', function () {
// 更新金币显示(使用总分数作为金币)
const totalScore = parseInt(localStorage.getItem('tankBattleTotalScore') || '0');
shopGold.textContent = totalScore;
// 更新升级状态显示
updateLaserStatus();
updateRapidFireStatus();
shopDialog.style.display = 'block';
});
closeShopButton.addEventListener('click', function () {
shopDialog.style.display = 'none';
});
// 购买砖块按钮事件
const buyBrickButton = document.getElementById('buy-brick-button');
buyBrickButton.addEventListener('click', function () {
const totalScore = parseInt(localStorage.getItem('tankBattleTotalScore') || '0');
const BRICK_PRICE = 100;
if (totalScore >= BRICK_PRICE) {
// 扣除金币
localStorage.setItem('tankBattleTotalScore', (totalScore - BRICK_PRICE).toString());
// 增加砖块数量
const currentBricks = parseInt(localStorage.getItem('tankBattleBricks') || '0');
localStorage.setItem('tankBattleBricks', (currentBricks + 1).toString());
gameState.bricks = currentBricks + 1;
// 更新显示
shopGold.textContent = totalScore - BRICK_PRICE;
}
});
// 购买激光炮按钮事件
const buyLaserButton = document.getElementById('buy-laser-button');
buyLaserButton.addEventListener('click', function () {
const totalScore = parseInt(localStorage.getItem('tankBattleTotalScore') || '0');
const UPGRADE_PRICE = 500;
const currentLevel = gameState.laserLevel;
if (totalScore >= UPGRADE_PRICE && currentLevel < 8) {
// 扣除金币
localStorage.setItem('tankBattleTotalScore', (totalScore - UPGRADE_PRICE).toString());
// 增加等级
localStorage.setItem('tankBattleLaserLevel', (currentLevel + 1).toString());
gameState.laserLevel = currentLevel + 1;
// 更新显示
shopGold.textContent = totalScore - UPGRADE_PRICE;
updateLaserStatus();
}
});
// 购买连发按钮事件
const buyRapidFireButton = document.getElementById('buy-rapid-fire-button');
buyRapidFireButton.addEventListener('click', function () {
const totalScore = parseInt(localStorage.getItem('tankBattleTotalScore') || '0');
const UPGRADE_PRICE = 500;
const currentLevel = gameState.rapidFireLevel;
if (totalScore >= UPGRADE_PRICE && currentLevel < 7) {
// 扣除金币
localStorage.setItem('tankBattleTotalScore', (totalScore - UPGRADE_PRICE).toString());
// 增加等级
localStorage.setItem('tankBattleRapidFireLevel', (currentLevel + 1).toString());
gameState.rapidFireLevel = currentLevel + 1;
// 更新显示
shopGold.textContent = totalScore - UPGRADE_PRICE;
updateRapidFireStatus();
}
});
// 更新激光炮状态显示
function updateLaserStatus() {
const laserStatusText = document.getElementById('laser-status-text');
const buyLaserBtn = document.getElementById('buy-laser-button');
const currentLevel = gameState.laserLevel;
const chance = currentLevel * 10 + 20;
if (currentLevel >= 8) {
laserStatusText.textContent = '等级: 8 (已满级)';
buyLaserBtn.disabled = true;
buyLaserBtn.textContent = '已满级';
buyLaserBtn.style.opacity = '0.5';
buyLaserBtn.style.cursor = 'not-allowed';
} else {
laserStatusText.textContent = `等级: ${currentLevel} (${chance}%几率)`;
buyLaserBtn.disabled = false;
buyLaserBtn.textContent = '购买';
buyLaserBtn.style.opacity = '1';
buyLaserBtn.style.cursor = 'pointer';
}
}
// 更新连发状态显示
function updateRapidFireStatus() {
const rapidFireStatusText = document.getElementById('rapid-fire-status-text');
const buyRapidFireBtn = document.getElementById('buy-rapid-fire-button');
const currentLevel = gameState.rapidFireLevel;
const bonus = currentLevel * 5 + 15;
if (currentLevel >= 7) {
rapidFireStatusText.textContent = '等级: 7 (已满级)';
buyRapidFireBtn.disabled = true;
buyRapidFireBtn.textContent = '已满级';
buyRapidFireBtn.style.opacity = '0.5';
buyRapidFireBtn.style.cursor = 'not-allowed';
} else {
rapidFireStatusText.textContent = `等级: ${currentLevel} (+${bonus}%频率)`;
buyRapidFireBtn.disabled = false;
buyRapidFireBtn.textContent = '购买';
buyRapidFireBtn.style.opacity = '1';
buyRapidFireBtn.style.cursor = 'pointer';
}
}
// 使用散射大招
function useUltimate() {
if (!gameState.player || gameState.ultimateState !== 'idle') return;
if (!gameState.ultimateReady && gameState.ultimateCooldown > 0) return;
// 开始大招
gameState.ultimateState = 'extending';
gameState.ultimateTimer = 0;
gameState.ultimateReady = false;
gameState.ultimateParticles = [];
// 添加初始特效粒子
const px = gameState.player.x + gameState.player.width / 2;
const py = gameState.player.y + gameState.player.height / 2;
for (let i = 0; i < 30; i++) {
gameState.ultimateParticles.push({
x: px + (Math.random() - 0.5) * 40,
y: py + (Math.random() - 0.5) * 40,
vx: (Math.random() - 0.5) * 4,
vy: (Math.random() - 0.5) * 4,
life: 60,
maxLife: 60,
color: Math.random() > 0.5 ? '#FFD700' : '#FF6347',
size: 4 + Math.random() * 4
});
}
}
// 更新大招状态
function updateUltimate() {
if (gameState.ultimateState === 'idle') {
if (gameState.ultimateCooldown > 0) {
gameState.ultimateCooldown--;
if (gameState.ultimateCooldown <= 0) {
gameState.ultimateReady = true;
}
}
return;
}
// 更新粒子
gameState.ultimateParticles = gameState.ultimateParticles.filter(p => {
p.x += p.vx;
p.y += p.vy;
p.life--;
return p.life > 0;
});
gameState.ultimateTimer++;
if (gameState.ultimateState === 'extending') {
// 小炮筒伸出阶段(0.3秒)
if (gameState.ultimateTimer < 18) {
// 添加能量粒子效果
if (gameState.ultimateTimer % 2 === 0) {
const px = gameState.player.x + gameState.player.width / 2;
const py = gameState.player.y + gameState.player.height / 2;
for (let i = 0; i < 3; i++) {
gameState.ultimateParticles.push({
x: px + (Math.random() - 0.5) * 40,
y: py + (Math.random() - 0.5) * 40,
vx: (Math.random() - 0.5) * 1.5,
vy: (Math.random() - 0.5) * 1.5,
life: 25,
maxLife: 25,
color: Math.random() > 0.5 ? '#00FFFF' : '#4169E1',
size: 2 + Math.random() * 3
});
}
}
} else {
gameState.ultimateState = 'firing';
gameState.ultimateTimer = 0;
}
} else if (gameState.ultimateState === 'firing') {
// 发射阶段(1秒)
if (gameState.ultimateTimer < 60) {
// 每6帧发射一次子弹和激光炮
if (gameState.ultimateTimer % 6 === 0) {
fireUltimateShots();
}
// 持续添加炫酷粒子
const px = gameState.player.x + gameState.player.width / 2;
const py = gameState.player.y + gameState.player.height / 2;
if (gameState.ultimateTimer % 2 === 0) {
gameState.ultimateParticles.push({
x: px + (Math.random() - 0.5) * 60,
y: py + (Math.random() - 0.5) * 60,
vx: (Math.random() - 0.5) * 4,
vy: (Math.random() - 0.5) * 4,
life: 15,
maxLife: 15,
color: Math.random() > 0.5 ? '#FF6B6B' : '#FFD93D',
size: 3 + Math.random() * 5
});
}
} else {
gameState.ultimateState = 'retracting';
gameState.ultimateTimer = 0;
}
} else if (gameState.ultimateState === 'retracting') {
// 小炮筒缩回阶段(0.3秒)
if (gameState.ultimateTimer >= 18) {
gameState.ultimateState = 'idle';
gameState.ultimateCooldown = 9000; // 150秒 * 60帧 = 9000帧
gameState.ultimateTimer = 0;
}
}
}
// 发射大招弹幕
function fireUltimateShots() {
if (!gameState.player) return;
const player = gameState.player;
const px = player.x + player.width / 2;
const py = player.y + player.height / 2;
// 根据玩家朝向确定基础角度
let baseAngle;
switch (player.direction) {
case 'up':
baseAngle = -Math.PI / 2; // 90度向上
break;
case 'down':
baseAngle = Math.PI / 2; // 90度向下
break;
case 'left':
baseAngle = Math.PI; // 180度向左
break;
case 'right':
baseAngle = 0; // 0度向右
break;
default:
baseAngle = -Math.PI / 2;
}
// 三个炮筒的位置:左、中、右
const cannonOffsets = [-18, 0, 18];
const spreadAngle = 22 * Math.PI / 180; // 22度转换为弧度
for (let i = 0; i < cannonOffsets.length; i++) {
const cannonOffset = cannonOffsets[i];
let bulletX, bulletY;
// 根据朝向计算炮口位置
switch (player.direction) {
case 'up':
bulletX = px + cannonOffset - BULLET_SIZE / 2;
bulletY = py - player.height / 2 - 30;
break;
case 'down':
bulletX = px + cannonOffset - BULLET_SIZE / 2;
bulletY = py + player.height / 2 + 30;
break;
case 'left':
bulletX = px - player.width / 2 - 30;
bulletY = py + cannonOffset - BULLET_SIZE / 2;
break;
case 'right':
bulletX = px + player.width / 2 + 30;
bulletY = py + cannonOffset - BULLET_SIZE / 2;
break;
}
// 每个炮筒发射5发子弹,形成22度扇形
for (let j = -2; j <= 2; j++) {
const bulletAngle = baseAngle + j * (spreadAngle / 2);
gameState.bullets.push(new Bullet(
bulletX,
bulletY,
player.direction,
true,
gameState.playerColor,
bulletAngle
));
}
// 每个炮筒有30%几率发射激光炮
if (Math.random() < 0.3) {
let laserX, laserY;
switch (player.direction) {
case 'up':
laserX = px + cannonOffset - 3;
laserY = py - player.height / 2 - 40;
break;
case 'down':
laserX = px + cannonOffset - 3;
laserY = py + player.height / 2 + 40;
break;
case 'left':
laserX = px - player.width / 2 - 40;
laserY = py + cannonOffset - 15;
break;
case 'right':
laserX = px + player.width / 2 + 40;
laserY = py + cannonOffset - 15;
break;
}
// 激光炮随机选一个角度
const laserAngle = baseAngle + (Math.random() - 0.5) * spreadAngle;
gameState.bullets.push(new Laser(laserX, laserY, player.direction, true, laserAngle));
}
}
}
// 关闭游戏介绍按钮事件
closeInfoButton.addEventListener('click', function () {
infoDialog.style.display = 'none';
});
// 敌人介绍按钮事件
enemyInfoButton.addEventListener('click', function () {
infoDialog.style.display = 'none';
enemyInfoDialog.style.display = 'block';
});
// 按键介绍按钮事件
controlInfoButton.addEventListener('click', function () {
infoDialog.style.display = 'none';
controlInfoDialog.style.display = 'block';
});
// 道具和机关介绍按钮事件
propTrapInfoButton.addEventListener('click', function () {
infoDialog.style.display = 'none';
propTrapInfoDialog.style.display = 'block';
});
// 关闭敌人介绍按钮事件
closeEnemyInfoButton.addEventListener('click', function () {
enemyInfoDialog.style.display = 'none';
infoDialog.style.display = 'block';
});
// 关闭按键介绍按钮事件
closeControlInfoButton.addEventListener('click', function () {
controlInfoDialog.style.display = 'none';
infoDialog.style.display = 'block';
});
// 关闭道具和机关介绍按钮事件
closePropTrapInfoButton.addEventListener('click', function () {
propTrapInfoDialog.style.display = 'none';
infoDialog.style.display = 'block';
});
// 点击遮罩层关闭弹窗
dialogOverlay.addEventListener('click', function () {
enemyInfoDialog.style.display = 'none';
controlInfoDialog.style.display = 'none';
propTrapInfoDialog.style.display = 'none';
document.getElementById('reset-confirm-dialog').style.display = 'none';
dialogOverlay.style.display = 'none';
});
// 阻止弹窗内部点击事件冒泡
enemyInfoDialog.addEventListener('click', function (e) {
e.stopPropagation();
});
controlInfoDialog.addEventListener('click', function (e) {
e.stopPropagation();
});
propTrapInfoDialog.addEventListener('click', function (e) {
e.stopPropagation();
});
restartButton.addEventListener('click', restartGame);
nextLevelButton.addEventListener('click', nextLevel);
const toggleTimerButton = document.getElementById('toggle-timer-button');
toggleTimerButton.addEventListener('click', () => {
// 循环切换模式:正常 → 限时 → 极限
const modes = ['normal', 'timed', 'extreme'];
const currentIndex = modes.indexOf(gameState.gameMode);
const nextIndex = (currentIndex + 1) % modes.length;
gameState.gameMode = modes[nextIndex];
// 更新按钮显示
const modeNames = {
'normal': '正常',
'timed': '限时',
'extreme': '极限'
};
toggleTimerButton.textContent = `模式:${modeNames[gameState.gameMode]}`;
// 更新限时状态
gameState.timeLimitEnabled = gameState.gameMode === 'timed' || gameState.gameMode === 'extreme';
});
// Keyboard controls
const keys = {
KeyW: false,
KeyA: false,
KeyS: false,
KeyD: false,
Space: false,
KeyE: false,
KeyR: false,
KeyX: false
};
let keyXReleased = true; // 防止X键被连续触发
document.addEventListener('keydown', (e) => {
if (e.code in keys) {
keys[e.code] = true;
}
// 处理X键(大招)
if (e.code === 'KeyX' && keyXReleased && gameState.isPlaying) {
keyXReleased = false;
useUltimate();
}
});
document.addEventListener('keyup', (e) => {
if (e.code in keys) {
keys[e.code] = false;
}
if (e.code === 'KeyX') {
keyXReleased = true;
}
});
// Game objects
class Tank {
constructor(x, y, isPlayer = false) {
this.x = x;
this.y = y;
this.width = TANK_SIZE;
this.height = TANK_SIZE;
this.direction = 'up';
this.speed = isPlayer ? TANK_SPEED : ENEMY_SPEED;
this.isPlayer = isPlayer;
this.shootCooldown = 0;
this.maxShootCooldown = 30;
this.health = 1;
this.shield = false;
this.color = isPlayer ? gameState.playerColor : '#F44336'; // 玩家使用选择的颜色,敌人默认红色
this.turretColor = isPlayer ? this.darkenColor(this.color, 20) : '#D32F2F';
this.isFrozen = false; // 新增:冻结状态
this.speedMultiplier = 1; // 新增:速度乘数
this.freezeTimeout = null; // 新增:冻结定时器
this.slowTimeout = null; // 新增:减速定时器
this.isSlowed = false; // 新增:是否处于减速状态
this.phase = 1; // 新增:BOSS阶段,默认为1
this.isBoss = false; // 新增:是否为BOSS
this.shootPattern = 'single'; // 新增:射击模式
this.stunned = false; // 新增:眩晕状态(无法移动)
this.stunEndTime = 0; // 新增:眩晕结束时间
}
darkenColor(color, percent) {
// 将颜色从十六进制转换为RGB
let r = parseInt(color.substring(1, 3), 16);
let g = parseInt(color.substring(3, 5), 16);
let b = parseInt(color.substring(5, 7), 16);
// 调暗颜色
r = Math.max(0, Math.floor(r * (1 - percent / 100)));
g = Math.max(0, Math.floor(g * (1 - percent / 100)));
b = Math.max(0, Math.floor(b * (1 - percent / 100)));
// 转换回十六进制
const toHex = (c) => {
const hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return '#' + toHex(r) + toHex(g) + toHex(b);
}
lightenColor(color, percent) {
// 将颜色从十六进制转换为RGB
let r = parseInt(color.substring(1, 3), 16);
let g = parseInt(color.substring(3, 5), 16);
let b = parseInt(color.substring(5, 7), 16);
// 调亮颜色
r = Math.min(255, Math.floor(r * (1 + percent / 100)));
g = Math.min(255, Math.floor(g * (1 + percent / 100)));
b = Math.min(255, Math.floor(b * (1 + percent / 100)));
// 转换回十六进制
const toHex = (c) => {
const hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return '#' + toHex(r) + toHex(g) + toHex(b);
}
draw() {
// 绘制玩家名称(如果是玩家坦克)
if (this.isPlayer) {
ctx.save();
ctx.fillStyle = '#fff';
ctx.font = '16px Arial';
ctx.textAlign = 'center';
ctx.fillText(gameState.playerName, this.x + this.width / 2, this.y - 20);
ctx.restore();
}
// 绘制中型坦克/船只的血量条
if (this.isMedium && this.health > 1) {
ctx.save();
const barWidth = this.width * 0.8;
const barHeight = 6;
const barX = this.x + this.width / 2 - barWidth / 2;
const barY = this.y - 15;
// 血量条背景
ctx.fillStyle = '#333';
ctx.fillRect(barX, barY, barWidth, barHeight);
// 当前血量
const healthPercent = this.health / 3; // 中型坦克最大血量为3
ctx.fillStyle = '#4CAF50'; // 绿色
ctx.fillRect(barX, barY, barWidth * healthPercent, barHeight);
// 血量条边框
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1;
ctx.strokeRect(barX, barY, barWidth, barHeight);
ctx.restore();
}
ctx.save();
ctx.translate(this.x + this.width / 2, this.y + this.height / 2);
// Rotate based on direction
switch (this.direction) {
case 'right':
ctx.rotate(Math.PI / 2);
break;
case 'down':
ctx.rotate(Math.PI);
break;
case 'left':
ctx.rotate(3 * Math.PI / 2);
break;
}
// 判断是否为海域关卡
const isOceanLevel = gameState.level >= 11 && gameState.level <= 20;
if (isOceanLevel) {
// 海域关卡:绘制船只皮肤
this.drawShip();
} else {
// 陆地关卡:绘制坦克皮肤
this.drawTank();
}
ctx.restore();
}
drawTank() {
// 判断是否正在使用大招
const isUltimateActive = this.isPlayer && gameState.ultimateState !== 'idle';
// 坦克颜色定义
let bodyColor, turretColor, trackColor, trackDetailColor;
if (isUltimateActive) {
// 大招皮肤:金色坦克样式
bodyColor = '#FFD700'; // 金色车身
turretColor = '#DAA520'; // 暗金色炮塔
trackColor = '#8B4513'; // 深棕色履带
trackDetailColor = '#CD853F'; // 铜色履带细节
} else {
// 普通皮肤
bodyColor = this.color;
turretColor = this.turretColor;
trackColor = '#212121';
trackDetailColor = '#424242';
}
// 绘制履带
ctx.fillStyle = trackColor;
ctx.fillRect(-this.width / 2 - 4, -this.height / 2, 4, this.height);
ctx.fillRect(this.width / 2, -this.height / 2, 4, this.height);
// 履带细节
ctx.fillStyle = trackDetailColor;
ctx.fillRect(-this.width / 2 - 4, -this.height / 2, 4, 6);
ctx.fillRect(-this.width / 2 - 4, this.height / 2 - 6, 4, 6);
ctx.fillRect(this.width / 2, -this.height / 2, 4, 6);
ctx.fillRect(this.width / 2, this.height / 2 - 6, 4, 6);
// 创建车身渐变效果
const bodyGradient = ctx.createLinearGradient(-this.width / 2, -this.height / 2, this.width / 2, this.height / 2);
if (isUltimateActive) {
// 大招金色渐变
bodyGradient.addColorStop(0, '#FFD700');
bodyGradient.addColorStop(0.5, '#FFA500');
bodyGradient.addColorStop(1, '#DAA520');
} else {
bodyGradient.addColorStop(0, this.lightenColor(bodyColor, 15));
bodyGradient.addColorStop(1, this.darkenColor(bodyColor, 15));
}
// 绘制坦克车身
ctx.fillStyle = bodyGradient;
ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
// 大招时的外发光效果
if (isUltimateActive) {
const glowGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, this.width * 0.7);
glowGradient.addColorStop(0, 'rgba(255, 215, 0, 0.3)');
glowGradient.addColorStop(0.5, 'rgba(255, 140, 0, 0.15)');
glowGradient.addColorStop(1, 'rgba(255, 69, 0, 0)');
ctx.fillStyle = glowGradient;
ctx.beginPath();
ctx.arc(0, 0, this.width * 0.7, 0, Math.PI * 2);
ctx.fill();
}
// 绘制车身装甲细节
ctx.strokeStyle = this.darkenColor(bodyColor, 30);
ctx.lineWidth = 2;
ctx.strokeRect(-this.width / 2 + 4, -this.height / 2 + 4, this.width - 8, this.height - 8);
ctx.strokeRect(-this.width / 2 + 8, -this.height / 2 + 8, this.width - 16, this.height - 16);
// 绘制装甲板连接点
ctx.fillStyle = '#333';
ctx.beginPath();
ctx.arc(-this.width / 2 + 8, -this.height / 2 + 8, 2, 0, Math.PI * 2);
ctx.arc(this.width / 2 - 8, -this.height / 2 + 8, 2, 0, Math.PI * 2);
ctx.arc(-this.width / 2 + 8, this.height / 2 - 8, 2, 0, Math.PI * 2);
ctx.arc(this.width / 2 - 8, this.height / 2 - 8, 2, 0, Math.PI * 2);
ctx.fill();
// 绘制坦克炮塔
const turretGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, this.width / 4);
if (isUltimateActive) {
// 大招金色炮塔渐变
turretGradient.addColorStop(0, '#FFD700');
turretGradient.addColorStop(0.5, '#FFA500');
turretGradient.addColorStop(1, '#DAA520');
} else {
turretGradient.addColorStop(0, this.lightenColor(turretColor, 10));
turretGradient.addColorStop(1, this.darkenColor(turretColor, 20));
}
ctx.fillStyle = turretGradient;
ctx.beginPath();
ctx.arc(0, 0, this.width / 4, 0, Math.PI * 2);
ctx.fill();
// 绘制炮塔细节
if (isUltimateActive) {
ctx.fillStyle = '#8B4513';
} else {
ctx.fillStyle = '#444';
}
ctx.beginPath();
ctx.arc(0, 0, this.width / 8, 0, Math.PI * 2);
ctx.fill();
// 绘制瞄准镜
if (isUltimateActive) {
ctx.fillStyle = '#000';
ctx.fillRect(-3, -this.height / 2 - 16, 6, 4);
ctx.fillStyle = '#FFD700';
ctx.fillRect(-2, -this.height / 2 - 15, 4, 2);
} else {
ctx.fillStyle = '#000';
ctx.fillRect(-3, -this.height / 2 - 16, 6, 4);
ctx.fillStyle = '#0ff';
ctx.fillRect(-2, -this.height / 2 - 15, 4, 2);
}
// 绘制炮管
let turretOffset = 0;
if (this.turretExtension) {
turretOffset = this.turretExtension * 15; // 炮筒最多伸长15像素
}
if (isUltimateActive) {
// 大招金色炮管
const cannonGradient = ctx.createLinearGradient(-3, 0, 3, 0);
cannonGradient.addColorStop(0, '#CD853F');
cannonGradient.addColorStop(0.5, '#DEB887');
cannonGradient.addColorStop(1, '#CD853F');
ctx.fillStyle = cannonGradient;
ctx.fillRect(-3, -this.height / 2 - 12 - turretOffset, 6, 14 + turretOffset);
ctx.fillStyle = '#8B4513';
ctx.fillRect(-2, -this.height / 2 - 18 - turretOffset, 4, 6);
} else {
ctx.fillStyle = trackColor;
ctx.fillRect(-3, -this.height / 2 - 12 - turretOffset, 6, 14 + turretOffset);
ctx.fillStyle = this.darkenColor(trackColor, 20);
ctx.fillRect(-2, -this.height / 2 - 18 - turretOffset, 4, 6);
}
// 绘制炮管散热孔
ctx.fillStyle = '#555';
const holeStartY = -this.height / 2 - 10 - turretOffset;
ctx.fillRect(-1, holeStartY, 2, 2);
ctx.fillRect(-1, holeStartY + 4, 2, 2);
ctx.fillRect(-1, holeStartY + 8, 2, 2);
// 添加侧装甲
ctx.fillStyle = this.darkenColor(bodyColor, 10);
ctx.fillRect(-this.width / 2 - 6, -this.height / 2 + 8, 2, this.height - 16);
ctx.fillRect(this.width / 2 + 4, -this.height / 2 + 8, 2, this.height - 16);
// 添加前装甲板
ctx.fillStyle = this.darkenColor(bodyColor, 20);
ctx.beginPath();
ctx.moveTo(-this.width / 2 + 10, -this.height / 2 - 5);
ctx.lineTo(0, -this.height / 2 - 15);
ctx.lineTo(this.width / 2 - 10, -this.height / 2 - 5);
ctx.closePath();
ctx.fill();
// 添加顶部装甲细节
ctx.fillStyle = '#444';
ctx.fillRect(-this.width / 4, -this.height / 2 + 5, this.width / 2, 3);
ctx.fillRect(-this.width / 4, this.height / 2 - 8, this.width / 2, 3);
// 绘制坦克护盾效果(仅玩家坦克)
if (this.isPlayer && this.shield) {
ctx.strokeStyle = 'rgba(33, 150, 243, 0.6)';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(0, 0, this.width / 2 + 8, 0, Math.PI * 2);
ctx.stroke();
ctx.fillStyle = 'rgba(33, 150, 243, 0.15)';
ctx.beginPath();
ctx.arc(0, 0, this.width / 2 + 6, 0, Math.PI * 2);
ctx.fill();
}
// 绘制眩晕效果(仅玩家坦克)
if (this.isPlayer && this.stunned && Date.now() < this.stunEndTime) {
const remaining = Math.max(0, Math.ceil((this.stunEndTime - Date.now()) / 1000));
// 旋转的星星表示眩晕
const time = Date.now() / 100;
for (let i = 0; i < 3; i++) {
const angle = time + i * Math.PI * 2 / 3;
const radius = this.width / 2 + 15;
const starX = Math.cos(angle) * radius;
const starY = Math.sin(angle) * radius;
ctx.fillStyle = '#FFEB3B';
ctx.beginPath();
for (let j = 0; j < 5; j++) {
const starAngle = j * Math.PI * 2 / 5 - Math.PI / 2;
const starRadius = j % 2 === 0 ? 6 : 3;
const sx = starX + Math.cos(starAngle) * starRadius;
const sy = starY + Math.sin(starAngle) * starRadius;
if (j === 0) {
ctx.moveTo(sx, sy);
} else {
ctx.lineTo(sx, sy);
}
}
ctx.closePath();
ctx.fill();
}
// 显示眩晕时间
ctx.fillStyle = '#FF5722';
ctx.font = 'bold 14px Arial';
ctx.textAlign = 'center';
ctx.fillText(`眩晕 ${remaining}秒`, 0, this.height / 2 + 25);
}
// 绘制大招小炮筒(仅玩家坦克)
if (this.isPlayer && gameState.ultimateState !== 'idle') {
let cannonExtension = 0;
if (gameState.ultimateState === 'extending') {
cannonExtension = gameState.ultimateTimer / 18;
} else if (gameState.ultimateState === 'firing') {
cannonExtension = 1;
} else if (gameState.ultimateState === 'retracting') {
cannonExtension = 1 - gameState.ultimateTimer / 18;
}
const cannonLength = cannonExtension * 35;
const cannonWidth = 10;
const sideOffset = 18;
// 绘制单个炮筒的辅助函数
const drawCannon = (offsetX, offsetY, rotAngle) => {
ctx.save();
ctx.translate(offsetX, offsetY);
ctx.rotate(rotAngle);
// 炮筒外发光
if (gameState.ultimateState === 'firing') {
const glowGradient = ctx.createRadialGradient(0, -cannonLength / 2, 0, 0, -cannonLength / 2, cannonWidth + 10);
glowGradient.addColorStop(0, 'rgba(255, 100, 0, 0.3)');
glowGradient.addColorStop(0.5, 'rgba(255, 50, 0, 0.15)');
glowGradient.addColorStop(1, 'rgba(255, 0, 0, 0)');
ctx.fillStyle = glowGradient;
ctx.beginPath();
ctx.arc(0, -cannonLength / 2, cannonWidth + 10, 0, Math.PI * 2);
ctx.fill();
}
// 炮筒金属渐变
const cannonGradient = ctx.createLinearGradient(-cannonWidth / 2, -cannonLength, cannonWidth / 2, -cannonLength);
cannonGradient.addColorStop(0, '#444');
cannonGradient.addColorStop(0.3, '#777');
cannonGradient.addColorStop(0.5, '#999');
cannonGradient.addColorStop(0.7, '#777');
cannonGradient.addColorStop(1, '#444');
// 炮筒主体(有层次感的炮筒)
ctx.fillStyle = cannonGradient;
ctx.beginPath();
ctx.roundRect(-cannonWidth / 2 - 1, -cannonLength, cannonWidth + 2, cannonLength, 3);
ctx.fill();
// 炮筒核心
ctx.fillStyle = '#555';
ctx.beginPath();
ctx.roundRect(-cannonWidth / 2 + 1, -cannonLength + 2, cannonWidth - 2, cannonLength - 4, 2);
ctx.fill();
// 炮筒高光
ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
ctx.beginPath();
ctx.roundRect(-cannonWidth / 2 + 2, -cannonLength + 3, 2, cannonLength - 6, 1);
ctx.fill();
// 炮筒装饰环
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
for (let i = 1; i <= 3; i++) {
ctx.beginPath();
ctx.moveTo(-cannonWidth / 2, -cannonLength + i * 8);
ctx.lineTo(cannonWidth / 2, -cannonLength + i * 8);
ctx.stroke();
}
// 炮口
const muzzleGradient = ctx.createRadialGradient(0, -cannonLength, 0, 0, -cannonLength, cannonWidth / 2 + 2);
muzzleGradient.addColorStop(0, '#222');
muzzleGradient.addColorStop(0.6, '#333');
muzzleGradient.addColorStop(1, '#111');
ctx.fillStyle = muzzleGradient;
ctx.beginPath();
ctx.arc(0, -cannonLength, cannonWidth / 2 + 2, 0, Math.PI * 2);
ctx.fill();
// 炮口内圈
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(0, -cannonLength, cannonWidth / 2 - 1, 0, Math.PI * 2);
ctx.fill();
// 炮口金属圈
ctx.strokeStyle = '#888';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(0, -cannonLength, cannonWidth / 2 + 1, 0, Math.PI * 2);
ctx.stroke();
// 发射阶段的炮口火焰
if (gameState.ultimateState === 'firing') {
const flameSize = 10 + Math.random() * 8;
const flameGradient = ctx.createRadialGradient(0, -cannonLength - flameSize / 2, 0, 0, -cannonLength - flameSize / 2, flameSize + 5);
flameGradient.addColorStop(0, 'rgba(255, 255, 200, 0.95)');
flameGradient.addColorStop(0.2, 'rgba(255, 200, 50, 0.9)');
flameGradient.addColorStop(0.5, 'rgba(255, 100, 0, 0.7)');
flameGradient.addColorStop(0.8, 'rgba(255, 50, 0, 0.4)');
flameGradient.addColorStop(1, 'rgba(255, 0, 0, 0)');
ctx.fillStyle = flameGradient;
ctx.beginPath();
ctx.arc(0, -cannonLength - flameSize / 2, flameSize + 5, 0, Math.PI * 2);
ctx.fill();
// 火焰中心
const innerFlame = ctx.createRadialGradient(0, -cannonLength - 3, 0, 0, -cannonLength - 3, 6);
innerFlame.addColorStop(0, 'rgba(255, 255, 255, 1)');
innerFlame.addColorStop(0.5, 'rgba(255, 220, 100, 0.8)');
innerFlame.addColorStop(1, 'rgba(255, 150, 0, 0)');
ctx.fillStyle = innerFlame;
ctx.beginPath();
ctx.arc(0, -cannonLength - 3, 6, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
};
// 根据朝向旋转炮筒
let cannonAngle = 0;
let cannonX1 = -sideOffset, cannonY1 = 0;
let cannonX2 = sideOffset, cannonY2 = 0;
switch (this.direction) {
case 'up':
cannonAngle = 0;
cannonX1 = -sideOffset; cannonY1 = -this.height / 2 - 5;
cannonX2 = sideOffset; cannonY2 = -this.height / 2 - 5;
break;
case 'down':
cannonAngle = Math.PI;
cannonX1 = -sideOffset; cannonY1 = this.height / 2 + 5;
cannonX2 = sideOffset; cannonY2 = this.height / 2 + 5;
break;
case 'left':
cannonAngle = -Math.PI / 2;
cannonX1 = -this.width / 2 - 5; cannonY1 = -sideOffset;
cannonX2 = -this.width / 2 - 5; cannonY2 = sideOffset;
break;
case 'right':
cannonAngle = Math.PI / 2;
cannonX1 = this.width / 2 + 5; cannonY1 = -sideOffset;
cannonX2 = this.width / 2 + 5; cannonY2 = sideOffset;
break;
}
// 绘制两个侧炮筒
drawCannon(cannonX1, cannonY1, cannonAngle);
drawCannon(cannonX2, cannonY2, cannonAngle);
}
// 绘制冻结效果(仅敌人坦克)
if (!this.isPlayer && this.isFrozen) {
ctx.fillStyle = 'rgba(0, 188, 212, 0.4)';
ctx.fillRect(-this.width / 2 - 6, -this.height / 2 - 6, this.width + 12, this.height + 12);
ctx.strokeStyle = 'rgba(0, 188, 212, 0.8)';
ctx.lineWidth = 2;
ctx.strokeRect(-this.width / 2 - 6, -this.height / 2 - 6, this.width + 12, this.height + 12);
}
}
drawShip() {
// 判断是否正在使用大招
const isUltimateActive = this.isPlayer && gameState.ultimateState !== 'idle';
// 帆船样式 - 恢复颜色区分
let hullColor, hullShadow, deckColor, mastColor, sailColor, cannonColor;
if (isUltimateActive) {
// 大招皮肤:金色战舰样式
hullColor = '#FFD700'; // 金色船体
hullShadow = '#B8860B'; // 金色阴影
deckColor = '#DAA520'; // 金色甲板
mastColor = '#8B4513'; // 深棕色桅杆
sailColor = '#FFEC8B'; // 亮金色船帆
cannonColor = '#CD853F'; // 铜色炮管
} else {
// 普通皮肤
hullColor = this.isPlayer ? '#1565C0' : '#C62828';
hullShadow = this.isPlayer ? '#0D47A1' : '#B71C1C';
// 如果是中型船只或无畏舰,使用自定义颜色
if ((this.isMedium || this.isBattleship) && this.color) {
hullColor = this.color;
hullShadow = this.darkenColor(this.color, 20);
}
deckColor = '#8B7355'; // 甲板棕色
mastColor = '#5D4037'; // 桅杆棕色
sailColor = this.isPlayer ? '#FAFAFA' : '#EFEBE9'; // 船帆颜色
cannonColor = ((this.isMedium || this.isBattleship) && this.turretColor) ? this.turretColor : '#424242';
}
// 绘制船体(细长的V形,类似图片中的帆船)
ctx.beginPath();
ctx.moveTo(0, -this.height / 2 - 20); // 尖尖的船头
// 右船身曲线
ctx.quadraticCurveTo(this.width * 0.45, -this.height / 2 + 8, this.width * 0.5, this.height / 2);
// 船尾右侧
ctx.lineTo(this.width * 0.38, this.height / 2 + 8);
// 船尾底部(稍微突出)
ctx.lineTo(-this.width * 0.38, this.height / 2 + 8);
// 船尾左侧
ctx.lineTo(-this.width * 0.5, this.height / 2);
// 左船身曲线
ctx.quadraticCurveTo(-this.width * 0.45, -this.height / 2 + 8, 0, -this.height / 2 - 20);
ctx.closePath();
// 船体渐变
const hullGradient = ctx.createLinearGradient(0, -this.height / 2, 0, this.height / 2);
if (isUltimateActive) {
// 大招金色渐变
hullGradient.addColorStop(0, '#FFD700'); // 亮金色
hullGradient.addColorStop(0.3, '#FFA500'); // 橙色
hullGradient.addColorStop(0.7, '#DAA520'); // 暗金色
hullGradient.addColorStop(1, hullShadow);
} else if (this.isBattleship) {
// 无畏舰使用灰色船体
hullGradient.addColorStop(0, '#9E9E9E'); // 浅灰色
hullGradient.addColorStop(0.5, '#757575'); // 中灰色
hullGradient.addColorStop(1, hullShadow); // 深蓝色阴影
} else {
// 普通船只使用白色船体
hullGradient.addColorStop(0, '#FFFFFF');
hullGradient.addColorStop(0.8, '#FAFAFA');
hullGradient.addColorStop(1, hullShadow);
}
ctx.fillStyle = hullGradient;
ctx.fill();
// 大招时的外发光效果
if (isUltimateActive) {
const glowGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, this.width * 0.6);
glowGradient.addColorStop(0, 'rgba(255, 215, 0, 0.3)');
glowGradient.addColorStop(0.5, 'rgba(255, 140, 0, 0.15)');
glowGradient.addColorStop(1, 'rgba(255, 69, 0, 0)');
ctx.fillStyle = glowGradient;
ctx.beginPath();
ctx.arc(0, 0, this.width * 0.6, 0, Math.PI * 2);
ctx.fill();
}
// 船体边框
if (isUltimateActive) {
ctx.strokeStyle = '#FFD700'; // 金色边框
ctx.lineWidth = 3;
} else {
ctx.strokeStyle = this.isBattleship ? '#616161' : '#BDBDBD';
ctx.lineWidth = 1.5;
}
ctx.stroke();
// 绘制甲板(上层平台)
ctx.fillStyle = this.isBattleship ? '#616161' : deckColor;
ctx.beginPath();
ctx.moveTo(-this.width * 0.38, -this.height / 2 + 10);
ctx.lineTo(this.width * 0.38, -this.height / 2 + 10);
ctx.lineTo(this.width * 0.32, this.height / 2 - 8);
ctx.lineTo(-this.width * 0.32, this.height / 2 - 8);
ctx.closePath();
ctx.fill();
// 甲板边缘线
ctx.strokeStyle = this.isBattleship ? '#424242' : this.darkenColor(deckColor, 30);
ctx.lineWidth = 1;
ctx.stroke();
// 绘制船舱(在甲板中央)
ctx.fillStyle = this.isBattleship ? '#546E7A' : '#F5F5F5';
ctx.fillRect(-this.width * 0.15, -this.height / 2 + 14, this.width * 0.3, this.height / 2 - 18);
// 船舱边框
ctx.strokeStyle = this.isBattleship ? '#455A64' : '#BDBDBD';
ctx.lineWidth = 1;
ctx.strokeRect(-this.width * 0.15, -this.height / 2 + 14, this.width * 0.3, this.height / 2 - 18);
// 船舱窗户
ctx.fillStyle = this.isBattleship ? '#29B6F6' : '#1E88E5';
const windowPositions = [
[-this.width * 0.07, -this.height / 2 + 18],
[0, -this.height / 2 + 18],
[this.width * 0.07, -this.height / 2 + 18],
[-this.width * 0.07, -this.height / 2 + 32],
[0, -this.height / 2 + 32],
[this.width * 0.07, -this.height / 2 + 32]
];
windowPositions.forEach(pos => {
ctx.fillRect(pos[0] - 3, pos[1], 6, 6);
});
// 绘制主桅杆(从船舱顶部伸出,贯穿船体)
ctx.fillStyle = mastColor;
ctx.fillRect(-1.5, -this.height / 2 - 25, 3, this.height / 2 + 15);
// 横杆(桅杆顶部)
ctx.fillRect(-this.width * 0.22, -this.height / 2 - 25, this.width * 0.44, 2);
// 绘制主帆(大三角形帆)
if (isUltimateActive) {
// 大招时的金色船帆
const sailGradient = ctx.createLinearGradient(0, -this.height / 2 - 28, 0, -this.height / 2 + 12);
sailGradient.addColorStop(0, '#FFD700');
sailGradient.addColorStop(0.5, '#FFEC8B');
sailGradient.addColorStop(1, '#DAA520');
ctx.fillStyle = sailGradient;
} else {
ctx.fillStyle = 'rgba(250, 250, 250, 0.95)';
}
ctx.beginPath();
ctx.moveTo(0, -this.height / 2 - 28); // 帆顶
ctx.lineTo(this.width * 0.38, -this.height / 2 + 12); // 帆右下角
ctx.lineTo(0, -this.height / 2 + 12); // 帆左下角
ctx.closePath();
ctx.fill();
// 帆的边框和中线
if (isUltimateActive) {
ctx.strokeStyle = '#8B4513';
} else {
ctx.strokeStyle = '#8D6E63';
}
ctx.lineWidth = 1;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, -this.height / 2 - 28);
ctx.lineTo(0, -this.height / 2 + 12);
ctx.stroke();
// 绘制前帆(较小的三角形帆,在船头方向)
if (isUltimateActive) {
// 大招时的金色前帆
const frontSailGradient = ctx.createLinearGradient(0, -this.height / 2 - 20, 0, -this.height / 2 + 10);
frontSailGradient.addColorStop(0, '#FFD700');
frontSailGradient.addColorStop(0.5, '#FFEC8B');
frontSailGradient.addColorStop(1, '#DAA520');
ctx.fillStyle = frontSailGradient;
} else {
ctx.fillStyle = 'rgba(245, 245, 245, 0.9)';
}
ctx.beginPath();
ctx.moveTo(0, -this.height / 2 - 20); // 前帆顶部
ctx.lineTo(-this.width * 0.28, -this.height / 2 + 10); // 前帆左下角
ctx.lineTo(0, -this.height / 2 + 10); // 前帆右下角
ctx.closePath();
ctx.fill();
if (isUltimateActive) {
ctx.strokeStyle = '#8B4513';
} else {
ctx.strokeStyle = '#8D6E63';
}
ctx.lineWidth = 1;
ctx.stroke();
// 绘制船炮(隐藏在船头下方,与船头重合)
ctx.fillStyle = cannonColor;
// 炮管底座
ctx.fillRect(-2.5, -this.height / 2 - 5, 5, 4);
// 炮管(从船头伸出)
ctx.fillRect(-2, -this.height / 2 - 18, 4, 13);
// 炮口
ctx.fillStyle = '#212121';
ctx.beginPath();
ctx.arc(0, -this.height / 2 - 22, 2.5, 0, Math.PI * 2);
ctx.fill();
// 绘制船头装饰线条
ctx.strokeStyle = '#FFD700';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, -this.height / 2 - 20);
ctx.lineTo(-5, -this.height / 2 - 5);
ctx.moveTo(0, -this.height / 2 - 20);
ctx.lineTo(5, -this.height / 2 - 5);
ctx.stroke();
// 绘制船尾细节
ctx.fillStyle = '#424242';
ctx.fillRect(-this.width * 0.06, this.height / 2 + 4, this.width * 0.12, 4);
// 绘制船只护盾效果(仅玩家船只)
if (this.isPlayer && this.shield) {
ctx.strokeStyle = 'rgba(33, 150, 243, 0.6)';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(0, 0, this.width * 0.55 + 10, 0, Math.PI * 2);
ctx.stroke();
ctx.fillStyle = 'rgba(33, 150, 243, 0.15)';
ctx.beginPath();
ctx.arc(0, 0, this.width * 0.55 + 8, 0, Math.PI * 2);
ctx.fill();
}
// 绘制冻结效果(仅敌人船只)
if (!this.isPlayer && this.isFrozen) {
ctx.fillStyle = 'rgba(0, 188, 212, 0.4)';
ctx.fillRect(-this.width * 0.55 - 6, -this.height / 2 - 30, this.width * 1.1 + 12, this.height + 50);
ctx.strokeStyle = 'rgba(0, 188, 212, 0.8)';
ctx.lineWidth = 2;
ctx.strokeRect(-this.width * 0.55 - 6, -this.height / 2 - 30, this.width * 1.1 + 12, this.height + 50);
}
}
update() {
// Handle player movement
if (this.isPlayer) {
this.handlePlayerMovement();
this.handleShooting();
this.handlePlaceBrick();
this.handleLaserToggle();
} else {
this.handleEnemyAI();
}
// Update shoot cooldown
if (this.shootCooldown > 0) {
this.shootCooldown--;
}
}
handlePlayerMovement() {
// 检查眩晕状态
if (this.stunned && Date.now() < this.stunEndTime) {
// 更新眩晕状态
if (Date.now() >= this.stunEndTime) {
this.stunned = false;
}
return; // 眩晕时无法移动
}
let moved = false;
let newX = this.x;
let newY = this.y;
if (keys.KeyW) {
newY -= this.speed;
this.direction = 'up';
moved = true;
} else if (keys.KeyS) {
newY += this.speed;
this.direction = 'down';
moved = true;
} else if (keys.KeyA) {
newX -= this.speed;
this.direction = 'left';
moved = true;
} else if (keys.KeyD) {
newX += this.speed;
this.direction = 'right';
moved = true;
}
// Check collision with walls
if (moved && !this.checkCollision(newX, newY)) {
// 检测与敌人的碰撞
this.checkEnemyCollision();
this.x = newX;
this.y = newY;
// 玩家坦克移动时生成烟雾
this.createSmoke();
// 如果处于加速状态,生成黄色粒子
if (gameState.isSpeedBoosted) {
this.createSpeedParticles();
}
}
}
// 检测玩家坦克与敌人的碰撞
checkEnemyCollision() {
for (let i = gameState.enemies.length - 1; i >= 0; i--) {
const enemy = gameState.enemies[i];
if (
this.x < enemy.x + enemy.width &&
this.x + this.width > enemy.x &&
this.y < enemy.y + enemy.height &&
this.y + this.height > enemy.y
) {
// 5%的几率造成伤害
if (Math.random() < 0.05) {
const damage = enemy.isBoss ? 5 : 1;
enemy.health -= damage;
// 创建碰撞效果
gameState.explosions.push(new Explosion(
enemy.x + enemy.width / 2,
enemy.y + enemy.height / 2
));
if (enemy.health <= 0) {
const score = enemy.isBoss ? 500 : 100;
// 创建得分字幕
gameState.scoreTexts.push(new ScoreText(
enemy.x + enemy.width / 2,
enemy.y + enemy.height / 2,
score
));
gameState.enemies.splice(i, 1);
gameState.score += score;
}
}
break; // 只处理第一个碰撞的敌人
}
}
}
createSpeedParticles() {
// 在坦克后方生成粒子
let particleX, particleY;
const centerX = this.x + this.width / 2;
const centerY = this.y + this.height / 2;
switch (this.direction) {
case 'up':
particleX = centerX + (Math.random() - 0.5) * this.width * 0.5;
particleY = this.y + this.height + 5;
break;
case 'down':
particleX = centerX + (Math.random() - 0.5) * this.width * 0.5;
particleY = this.y - 5;
break;
case 'left':
particleX = this.x + this.width + 5;
particleY = centerY + (Math.random() - 0.5) * this.height * 0.5;
break;
case 'right':
particleX = this.x - 5;
particleY = centerY + (Math.random() - 0.5) * this.height * 0.5;
break;
}
// 每次移动生成1-2个粒子
if (Math.random() > 0.3) {
gameState.playerSpeedParticles.push(new PlayerSpeedParticle(particleX, particleY, this.direction));
}
if (Math.random() > 0.6) {
gameState.playerSpeedParticles.push(new PlayerSpeedParticle(particleX, particleY, this.direction));
}
}
handleEnemyAI() {
// Move in current direction
let newX = this.x;
let newY = this.y;
let moved = false;
if (!this.isFrozen) {
if (this.isBoss) {
if (this.isBattleship) {
// 无畏舰AI - 海域BOSS
this.updateBattleshipAI();
} else {
// 毁灭者AI - 陆地BOSS
this.updateDestroyerAI();
}
} else {
// Regular enemy AI - move randomly
if (Math.random() < 0.02) {
const directions = ['up', 'down', 'left', 'right'];
this.direction = directions[Math.floor(Math.random() * directions.length)];
}
// Random shooting
if (Math.random() < 0.01 && this.shootCooldown === 0) {
this.shoot();
}
}
const effectiveSpeed = this.speed * this.speedMultiplier;
switch (this.direction) {
case 'up':
newY -= effectiveSpeed;
moved = true;
break;
case 'down':
newY += effectiveSpeed;
moved = true;
break;
case 'left':
newX -= effectiveSpeed;
moved = true;
break;
case 'right':
newX += effectiveSpeed;
moved = true;
break;
}
}
// Check collision with walls
if (moved && !this.checkCollision(newX, newY)) {
this.x = newX;
this.y = newY;
// 敌人坦克移动时生成烟雾
this.createSmoke();
// 如果处于减速状态,生成紫色粒子
if (this.isSlowed && Math.random() > 0.95) {
createEnemySlowParticles(this);
}
} else if (moved) {
// Change direction if collision
const directions = ['up', 'down', 'left', 'right'];
if (this.isBoss) {
// BOSS has smarter collision response - try to go around
// Prefer direction towards player if possible
const player = gameState.player;
if (player) {
const dx = player.x - this.x;
const dy = player.y - this.y;
const preferredDirections = [];
if (dx > 0) preferredDirections.push('right');
if (dx < 0) preferredDirections.push('left');
if (dy > 0) preferredDirections.push('down');
if (dy < 0) preferredDirections.push('up');
// Add random directions as fallback
preferredDirections.push(...directions.filter(d => !preferredDirections.includes(d)));
// Try preferred directions first
for (const dir of preferredDirections) {
this.direction = dir;
let testX = this.x;
let testY = this.y;
switch (dir) {
case 'up': testY -= this.speed * this.speedMultiplier; break;
case 'down': testY += this.speed * this.speedMultiplier; break;
case 'left': testX -= this.speed * this.speedMultiplier; break;
case 'right': testX += this.speed * this.speedMultiplier; break;
}
if (!this.checkCollision(testX, testY)) {
newX = testX;
newY = testY;
moved = true;
break;
}
}
}
} else {
this.direction = directions[Math.floor(Math.random() * directions.length)];
}
}
}
handleShooting() {
if (this.isPlayer) {
// 计算连发后的冷却时间
let effectiveCooldown = this.maxShootCooldown;
if (gameState.rapidFireLevel > 0) {
const bonusPercent = gameState.rapidFireLevel * 5 + 15;
effectiveCooldown = Math.max(5, Math.floor(this.maxShootCooldown * (1 - bonusPercent / 100)));
}
if (keys.Space && this.shootCooldown === 0) {
// 检查是否发射激光炮
if (gameState.laserLevel > 0 && gameState.laserEnabled) {
const chance = gameState.laserLevel * 10 + 20;
if (Math.random() * 100 < chance) {
this.shootLaser();
} else {
this.shoot();
}
} else {
this.shoot();
}
this.shootCooldown = effectiveCooldown;
}
} else {
// 敌人的射击逻辑保持不变
if (keys.Space && this.shootCooldown === 0) {
this.shoot();
this.shootCooldown = this.maxShootCooldown;
}
}
}
shootLaser() {
// 计算激光起始位置
let laserX = this.x + this.width / 2;
let laserY = this.y + this.height / 2;
let dx = 0, dy = 0;
switch (this.direction) {
case 'up': dy = -1; break;
case 'down': dy = 1; break;
case 'left': dx = -1; break;
case 'right': dx = 1; break;
}
// 创建激光
const laser = new Laser(laserX, laserY, dx, dy, this.isPlayer);
gameState.bullets.push(laser);
}
handlePlaceBrick() {
// 检查是否是陆地关卡
if (gameState.level < 11 || gameState.level > 20) {
// 陆地关卡
if (keys.KeyE && gameState.bricks > 0) {
// 计算墙壁放置位置(玩家前方一格)
let wallX = this.x;
let wallY = this.y;
const wallSize = WALL_SIZE;
switch (this.direction) {
case 'up':
wallY -= wallSize;
break;
case 'down':
wallY += this.height;
break;
case 'left':
wallX -= wallSize;
break;
case 'right':
wallX += this.width;
break;
}
// 检查位置是否有效(在画布内)
if (wallX >= 0 && wallX + wallSize <= CANVAS_WIDTH &&
wallY >= 0 && wallY + wallSize <= CANVAS_HEIGHT) {
// 检查是否与其他墙壁重叠
let canPlace = true;
for (const wall of gameState.walls) {
if (wallX < wall.x + wall.width &&
wallX + wallSize > wall.x &&
wallY < wall.y + wall.height &&
wallY + wallSize > wall.y) {
canPlace = false;
break;
}
}
// 检查是否与玩家重叠
if (canPlace &&
wallX < this.x + this.width &&
wallX + wallSize > this.x &&
wallY < this.y + this.height &&
wallY + wallSize > this.y) {
canPlace = false;
}
// 检查是否与敌人重叠
if (canPlace) {
for (const enemy of gameState.enemies) {
if (wallX < enemy.x + enemy.width &&
wallX + wallSize > enemy.x &&
wallY < enemy.y + enemy.height &&
wallY + wallSize > enemy.y) {
canPlace = false;
break;
}
}
}
if (canPlace) {
// 放置墙壁
gameState.walls.push(new Wall(wallX, wallY, false));
gameState.bricks--;
localStorage.setItem('tankBattleBricks', gameState.bricks.toString());
}
}
// 重置按键状态(防止连续放置)
keys.KeyE = false;
}
}
}
handleLaserToggle() {
// 处理R键切换激光炮
if (keys.KeyR && gameState.laserLevel > 0) {
gameState.laserEnabled = !gameState.laserEnabled;
localStorage.setItem('tankBattleLaserEnabled', gameState.laserEnabled);
keys.KeyR = false; // 防止连续切换
}
}
shoot() {
let bulletX = this.x + this.width / 2 - BULLET_SIZE / 2;
let bulletY = this.y + this.height / 2 - BULLET_SIZE / 2;
// Adjust bullet position based on direction
switch (this.direction) {
case 'up':
bulletY -= this.height / 2;
break;
case 'down':
bulletY += this.height / 2;
break;
case 'left':
bulletX -= this.width / 2;
break;
case 'right':
bulletX += this.width / 2;
break;
}
// 根据射击模式发射子弹
if (this.shootPattern === 'single' || !this.isBoss) {
// 单发射击
gameState.bullets.push(new Bullet(
bulletX,
bulletY,
this.direction,
this.isPlayer,
this.color
));
} else if (this.shootPattern === 'double') {
// 双发射击,稍微分散
let angle1 = -15 * Math.PI / 180;
let angle2 = 15 * Math.PI / 180;
let spread = 10;
// 计算分散角度的子弹位置
let dx1 = Math.sin(angle1) * spread;
let dy1 = Math.cos(angle1) * spread;
let dx2 = Math.sin(angle2) * spread;
let dy2 = Math.cos(angle2) * spread;
// 根据方向调整分散位置
let bulletX1, bulletY1, bulletX2, bulletY2;
switch (this.direction) {
case 'up':
bulletX1 = bulletX + dx1;
bulletY1 = bulletY - dy1;
bulletX2 = bulletX + dx2;
bulletY2 = bulletY - dy2;
break;
case 'down':
bulletX1 = bulletX + dx1;
bulletY1 = bulletY + dy1;
bulletX2 = bulletX + dx2;
bulletY2 = bulletY + dy2;
break;
case 'left':
bulletX1 = bulletX - dy1;
bulletY1 = bulletY + dx1;
bulletX2 = bulletX - dy2;
bulletY2 = bulletY + dx2;
break;
case 'right':
bulletX1 = bulletX + dy1;
bulletY1 = bulletY + dx1;
bulletX2 = bulletX + dy2;
bulletY2 = bulletY + dx2;
break;
}
gameState.bullets.push(new Bullet(
bulletX1,
bulletY1,
this.direction,
this.isPlayer,
this.color
));
gameState.bullets.push(new Bullet(
bulletX2,
bulletY2,
this.direction,
this.isPlayer,
this.color
));
} else if (this.shootPattern === 'triple') {
// 三发射击,形成扇形
let angle1 = -30 * Math.PI / 180;
let angle2 = 0;
let angle3 = 30 * Math.PI / 180;
let spread = 15;
// 计算分散角度的子弹位置
let dx1 = Math.sin(angle1) * spread;
let dy1 = Math.cos(angle1) * spread;
let dx2 = Math.sin(angle2) * spread;
let dy2 = Math.cos(angle2) * spread;
let dx3 = Math.sin(angle3) * spread;
let dy3 = Math.cos(angle3) * spread;
// 根据方向调整分散位置
let bulletX1, bulletY1, bulletX2, bulletY2, bulletX3, bulletY3;
switch (this.direction) {
case 'up':
bulletX1 = bulletX + dx1;
bulletY1 = bulletY - dy1;
bulletX2 = bulletX + dx2;
bulletY2 = bulletY - dy2;
bulletX3 = bulletX + dx3;
bulletY3 = bulletY - dy3;
break;
case 'down':
bulletX1 = bulletX + dx1;
bulletY1 = bulletY + dy1;
bulletX2 = bulletX + dx2;
bulletY2 = bulletY + dy2;
bulletX3 = bulletX + dx3;
bulletY3 = bulletY + dy3;
break;
case 'left':
bulletX1 = bulletX - dy1;
bulletY1 = bulletY + dx1;
bulletX2 = bulletX - dy2;
bulletY2 = bulletY + dx2;
bulletX3 = bulletX - dy3;
bulletY3 = bulletY + dx3;
break;
case 'right':
bulletX1 = bulletX + dy1;
bulletY1 = bulletY + dx1;
bulletX2 = bulletX + dy2;
bulletY2 = bulletY + dx2;
bulletX3 = bulletX + dy3;
bulletY3 = bulletY + dx3;
break;
}
gameState.bullets.push(new Bullet(
bulletX1,
bulletY1,
this.direction,
this.isPlayer,
this.color
));
gameState.bullets.push(new Bullet(
bulletX2,
bulletY2,
this.direction,
this.isPlayer,
this.color
));
gameState.bullets.push(new Bullet(
bulletX3,
bulletY3,
this.direction,
this.isPlayer,
this.color
));
}
}
// 无畏舰AI更新
updateBattleshipAI() {
const player = gameState.player;
// 更新技能冷却
if (this.skillCooldown > 0) {
this.skillCooldown--;
}
// 追踪玩家(更积极)
if (player && Math.random() < 0.95) {
const dx = player.x - this.x;
const dy = player.y - this.y;
if (Math.abs(dx) > Math.abs(dy)) {
this.direction = dx > 0 ? 'right' : 'left';
} else {
this.direction = dy > 0 ? 'down' : 'up';
}
} else {
if (Math.random() < 0.15) {
const directions = ['up', 'down', 'left', 'right'];
this.direction = directions[Math.floor(Math.random() * directions.length)];
}
}
// 技能选择(更频繁)
if (this.skillCooldown === 0 && !this.isSummoning) {
const rand = Math.random();
if (rand < 0.35) {
this.useSkill1(); // 朝四周顺序开炮
} else if (rand < 0.6) {
this.useSkill2(); // 朝任意方向发射3行子弹
} else if (rand < 0.8) {
this.useSkill3(); // 在周围放置地雷
} else if (rand < 0.95) {
this.useSkill4(); // 召唤小弟
} else if (Math.random() < 0.08 && this.shootCooldown === 0) {
this.shoot(); // 普通射击
}
}
// 召唤期间不发射普通子弹(更频繁)
if (!this.isSummoning && Math.random() < 0.035 && this.shootCooldown === 0) {
this.shoot();
}
}
// 技能1:朝四周顺序开炮(北→东北→东→东南→南→西南→西→西北)
useSkill1() {
this.currentSkill = 'skill1';
this.skillCooldown = 90; // 1.5秒冷却
const directions8 = ['up', 'up-right', 'right', 'down-right', 'down', 'down-left', 'left', 'up-left'];
const centerX = this.x + this.width / 2;
const centerY = this.y + this.height / 2;
// 发射3轮,每轮有轻微延迟
for (let wave = 0; wave < 3; wave++) {
setTimeout(() => {
for (let i = 0; i < 8; i++) {
const dir = directions8[i];
let bulletX = centerX - BULLET_SIZE / 2;
let bulletY = centerY - BULLET_SIZE / 2;
// 根据8个方向设置子弹位置
switch (dir) {
case 'up':
bulletY -= this.height / 2;
break;
case 'up-right':
bulletX += this.width / 4;
bulletY -= this.height / 2;
break;
case 'right':
bulletX += this.width / 2;
break;
case 'down-right':
bulletX += this.width / 4;
bulletY += this.height / 2;
break;
case 'down':
bulletY += this.height / 2;
break;
case 'down-left':
bulletX -= this.width / 4;
bulletY += this.height / 2;
break;
case 'left':
bulletX -= this.width / 2;
break;
case 'up-left':
bulletX -= this.width / 4;
bulletY -= this.height / 2;
break;
}
gameState.bullets.push(new Bullet(bulletX, bulletY, dir, false, '#FF4444'));
}
}, wave * 100);
}
this.currentSkill = null;
}
// 技能2:朝任意方向发射3行子弹
useSkill2() {
this.currentSkill = 'skill2';
this.skillCooldown = 140; // 2.3秒冷却
const randomDirs = ['up', 'down', 'left', 'right'];
const targetDir = randomDirs[Math.floor(Math.random() * randomDirs.length)];
this.direction = targetDir;
const centerX = this.x + this.width / 2;
const centerY = this.y + this.height / 2;
// 发射5行子弹,每行7发
for (let row = 0; row < 5; row++) {
for (let col = 0; col < 7; col++) {
let bulletX = centerX - BULLET_SIZE / 2;
let bulletY = centerY - BULLET_SIZE / 2;
let bulletDir = targetDir;
// 根据方向计算偏移
const rowOffset = (row - 2) * 15;
const colDelay = col * 6;
switch (targetDir) {
case 'up':
bulletX += rowOffset;
bulletY -= this.height / 2 + colDelay;
break;
case 'down':
bulletX += rowOffset;
bulletY += this.height / 2 + colDelay;
break;
case 'left':
bulletX -= this.width / 2 + colDelay;
bulletY += rowOffset;
break;
case 'right':
bulletX += this.width / 2 + colDelay;
bulletY += rowOffset;
break;
}
gameState.bullets.push(new Bullet(bulletX, bulletY, bulletDir, false, '#FF6600'));
}
}
this.currentSkill = null;
}
// 技能3:在周围放置地雷
useSkill3() {
this.currentSkill = 'skill3';
this.skillCooldown = 180; // 3秒冷却
const centerX = this.x + this.width / 2;
const centerY = this.y + this.height / 2;
const trapPositions = [
{ x: centerX - 60, y: centerY - 60 },
{ x: centerX + 60, y: centerY - 60 },
{ x: centerX - 60, y: centerY + 60 },
{ x: centerX + 60, y: centerY + 60 },
{ x: centerX, y: centerY - 80 },
{ x: centerX, y: centerY + 80 },
{ x: centerX - 80, y: centerY },
{ x: centerX + 80, y: centerY },
{ x: centerX - 120, y: centerY - 120 },
{ x: centerX + 120, y: centerY - 120 },
{ x: centerX - 120, y: centerY + 120 },
{ x: centerX + 120, y: centerY + 120 }
];
for (const pos of trapPositions) {
// 检查位置是否有效
let valid = true;
for (const wall of gameState.walls) {
if (pos.x > wall.x && pos.x < wall.x + wall.width &&
pos.y > wall.y && pos.y < wall.y + wall.height) {
valid = false;
break;
}
}
if (valid) {
const trap = new Trap(pos.x - TRAP_SIZE / 2, pos.y - TRAP_SIZE / 2, 'mine');
gameState.traps.push(trap);
}
}
this.currentSkill = null;
}
// 技能4:召唤小弟
useSkill4() {
this.currentSkill = 'skill4';
this.skillCooldown = 240; // 4秒冷却
this.isSummoning = true;
const summonPositions = [
{ x: this.x - TANK_SIZE, y: this.y },
{ x: this.x + this.width, y: this.y },
{ x: this.x + this.width / 2 - TANK_SIZE / 2, y: this.y - TANK_SIZE },
{ x: this.x - TANK_SIZE * 1.5, y: this.y - TANK_SIZE },
{ x: this.x + this.width + TANK_SIZE * 0.5, y: this.y - TANK_SIZE }
];
let summonedCount = 0;
for (const pos of summonPositions) {
if (summonedCount >= 4) break; // 最多召唤4个
// 检查位置是否有效
let valid = true;
if (pos.x < 0 || pos.x + TANK_SIZE > CANVAS_WIDTH ||
pos.y < 0 || pos.y + TANK_SIZE > CANVAS_HEIGHT) {
continue;
}
for (const wall of gameState.walls) {
if (pos.x < wall.x + wall.width &&
pos.x + TANK_SIZE > wall.x &&
pos.y < wall.y + wall.height &&
pos.y + TANK_SIZE > wall.y) {
valid = false;
break;
}
}
if (valid) {
const minion = new Tank(pos.x, pos.y);
minion.isMedium = true;
minion.health = 2;
minion.width = TANK_SIZE * 1.1;
minion.height = TANK_SIZE * 1.1;
minion.speed = ENEMY_SPEED * 1.1;
minion.color = '#8B4513'; // 棕色小弟
minion.turretColor = '#654321';
gameState.enemies.push(minion);
summonedCount++;
}
}
// 召唤动画持续时间
setTimeout(() => {
if (this) {
this.isSummoning = false;
this.currentSkill = null;
}
}, 1000);
}
// 毁灭者AI更新
updateDestroyerAI() {
// 更新技能冷却
if (this.skillCooldown > 0) {
this.skillCooldown--;
}
// 如果正在使用导弹技能,处理导弹技能逻辑
if (this.isUsingMissileSkill) {
this.missileSkillTimer++;
// 第一阶段:炮筒上升(0.5秒)
if (this.missileSkillTimer < 15) {
this.direction = 'up';
this.turretExtension = this.missileSkillTimer / 15;
return;
}
// 第二阶段:炮筒缩回(0.5秒)
else if (this.missileSkillTimer < 30) {
this.direction = 'up';
this.turretExtension = 1 - (this.missileSkillTimer - 15) / 15;
return;
}
// 第三阶段:发射导弹阶段(持续4秒)
else if (this.missileSkillTimer < 150) {
this.direction = 'up';
// 炮筒循环伸缩动画
const cycleTime = (this.missileSkillTimer - 30) % 20;
if (cycleTime < 10) {
this.turretExtension = cycleTime / 10;
} else {
this.turretExtension = 1 - (cycleTime - 10) / 10;
}
// 每7帧发射1-2枚导弹
if (this.missileSkillTimer % 7 === 0) {
const missileCount = 1 + Math.floor(Math.random() * 2);
for (let i = 0; i < missileCount; i++) {
this.launchMissile();
}
}
return;
}
// 技能结束
else {
this.isUsingMissileSkill = false;
this.missileSkillTimer = 0;
this.turretExtension = 0;
return;
}
}
// 追踪玩家
const player = gameState.player;
if (player && Math.random() < 0.7) {
const dx = player.x - this.x;
const dy = player.y - this.y;
if (Math.abs(dx) > Math.abs(dy)) {
this.direction = dx > 0 ? 'right' : 'left';
} else {
this.direction = dy > 0 ? 'down' : 'up';
}
} else {
if (Math.random() < 0.2) {
const directions = ['up', 'down', 'left', 'right'];
this.direction = directions[Math.floor(Math.random() * directions.length)];
}
}
// 技能选择 - 2%几率使用导弹技能
if (this.skillCooldown === 0 && Math.random() < 0.02) {
this.useMissileSkill();
}
// 普通射击
if (Math.random() < 0.03 && this.shootCooldown === 0) {
this.shoot();
}
}
// 使用导弹技能
useMissileSkill() {
this.currentSkill = 'missile';
this.skillCooldown = 360; // 6秒冷却
this.isUsingMissileSkill = true;
this.missileSkillTimer = 0;
this.turretExtension = 0;
}
// 发射一枚导弹
launchMissile() {
// 随机位置生成导弹,随机高度爆炸
const targetX = Math.random() * (CANVAS_WIDTH - 40) + 20;
const explodeY = 100 + Math.random() * (CANVAS_HEIGHT - 200);
const missile = new Missile(targetX, -30, targetX, explodeY);
gameState.bullets.push(missile);
}
// 创建烟雾效果
createSmoke() {
// 海域关卡(船只)不产生烟雾
const isOceanLevel = gameState.level >= 11 && gameState.level <= 20;
if (isOceanLevel) {
return;
}
const smokeCount = 2; // 恢复坦克两边各一个烟雾
const tankWidth = this.width;
const tankHeight = this.height;
let smokeX, smokeY;
// 根据坦克方向确定烟雾位置(坦克尾部两边)
switch (this.direction) {
case 'up':
// 坦克向上时,烟雾从底部两侧冒出
smokeX = this.x + tankWidth / 2;
smokeY = this.y + tankHeight;
break;
case 'down':
// 坦克向下时,烟雾从顶部两侧冒出
smokeX = this.x + tankWidth / 2;
smokeY = this.y;
break;
case 'left':
// 坦克向左时,烟雾从右侧两侧冒出
smokeX = this.x + tankWidth;
smokeY = this.y + tankHeight / 2;
break;
case 'right':
// 坦克向右时,烟雾从左侧两侧冒出
smokeX = this.x;
smokeY = this.y + tankHeight / 2;
break;
}
// 在坦克尾部两侧生成烟雾
for (let i = 0; i < smokeCount; i++) {
let offsetX = 0;
if (this.direction === 'up' || this.direction === 'down') {
// 上下方向时,烟雾在左右两侧
offsetX = (i === 0 ? -tankWidth / 4 : tankWidth / 4);
} else {
// 左右方向时,烟雾在上下两侧
smokeY = (i === 0 ? this.y + tankHeight / 4 : this.y + tankHeight * 3 / 4);
}
gameState.smokes.push(new Smoke(
smokeX + offsetX + (Math.random() - 0.5) * 5,
smokeY + (Math.random() - 0.5) * 5,
this.direction
));
}
}
checkCollision(newX, newY) {
// Check collision with walls
for (let i = gameState.walls.length - 1; i >= 0; i--) {
const wall = gameState.walls[i];
if (
newX < wall.x + wall.width &&
newX + this.width > wall.x &&
newY < wall.y + wall.height &&
newY + this.height > wall.y
) {
// 如果是玩家坦克,则摧毁墙壁
if (this.isPlayer) {
// 创建爆炸效果
gameState.explosions.push(new Explosion(wall.x + wall.width / 2, wall.y + wall.height / 2));
// 播放击中墙壁音效
// playSound('hit-wall-sound');
// 移除墙壁
gameState.walls.splice(i, 1);
return false; // 允许移动
}
return true; // 阻止敌人坦克移动
}
}
// Check collision with canvas boundaries
if (
newX < 0 ||
newX + this.width > CANVAS_WIDTH ||
newY < 0 ||
newY + this.height > CANVAS_HEIGHT
) {
return true;
}
// Check collision with traps (oil barrels)
for (let i = gameState.traps.length - 1; i >= 0; i--) {
const trap = gameState.traps[i];
if (
trap.type === TRAP_TYPES.OIL_BARREL &&
trap.active &&
this.x < trap.x + trap.width &&
this.x + this.width > trap.x &&
this.y < trap.y + trap.height &&
this.y + this.height > trap.y
) {
trap.trigger(null); // 触发油桶爆炸,传入null因为不是坦克触发的
return true; // 子弹被消耗
}
}
return false;
}
}
// 玩家道具粒子类
class PlayerPropParticle {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.color = color;
this.size = Math.random() * 4 + 2;
this.speedX = (Math.random() - 0.5) * 4;
this.speedY = (Math.random() - 0.5) * 4;
this.opacity = 1;
this.decay = Math.random() * 0.03 + 0.02;
this.lifeTime = 1500; // 1.5秒
this.startTime = Date.now();
}
update() {
const elapsed = Date.now() - this.startTime;
if (elapsed >= this.lifeTime) {
return true; // 粒子生命周期结束
}
this.x += this.speedX;
this.y += this.speedY;
this.opacity = 1 - (elapsed / this.lifeTime);
this.size *= 0.98; // 逐渐缩小
return false;
}
draw() {
ctx.save();
ctx.globalAlpha = this.opacity;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
}
// 玩家加速粒子类
class PlayerSpeedParticle {
constructor(x, y, direction) {
this.x = x;
this.y = y;
this.direction = direction;
this.size = Math.random() * 6 + 3;
this.speed = Math.random() * 3 + 2;
this.opacity = 1;
this.decay = Math.random() * 0.05 + 0.03;
this.color = '#FFC107'; // 黄色
}
update() {
// 根据方向移动粒子(从坦克前方向后移动)
switch (this.direction) {
case 'up':
this.y += this.speed;
break;
case 'down':
this.y -= this.speed;
break;
case 'left':
this.x += this.speed;
break;
case 'right':
this.x -= this.speed;
break;
}
this.opacity -= this.decay;
this.size *= 0.95;
this.speed *= 0.98;
return this.opacity <= 0 || this.size <= 1;
}
draw() {
ctx.save();
ctx.globalAlpha = this.opacity;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
// 添加发光效果
ctx.shadowColor = this.color;
ctx.shadowBlur = 10;
ctx.fill();
ctx.restore();
}
}
// 敌人减速粒子类
class EnemySlowParticle {
constructor(x, y) {
this.x = x;
this.y = y;
this.size = Math.random() * 5 + 2;
this.speedX = (Math.random() - 0.5) * 2;
this.speedY = (Math.random() - 0.5) * 2;
this.opacity = 1;
this.decay = Math.random() * 0.02 + 0.01;
this.color = '#9C27B0'; // 紫色
this.lifeTime = 8000; // 8秒
this.startTime = Date.now();
}
update() {
const elapsed = Date.now() - this.startTime;
if (elapsed >= this.lifeTime) {
return true; // 粒子生命周期结束
}
this.x += this.speedX;
this.y += this.speedY;
this.opacity = 1 - (elapsed / this.lifeTime);
this.size *= 0.99;
return false;
}
draw() {
ctx.save();
ctx.globalAlpha = this.opacity;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
// 添加发光效果
ctx.shadowColor = this.color;
ctx.shadowBlur = 8;
ctx.fill();
ctx.restore();
}
}
// 创建敌人减速粒子效果
function createEnemySlowParticles(enemy) {
const centerX = enemy.x + enemy.width / 2;
const centerY = enemy.y + enemy.height / 2;
// 每次创建1个粒子
const offsetX = (Math.random() - 0.5) * enemy.width * 0.5;
const offsetY = (Math.random() - 0.5) * enemy.height * 0.5;
gameState.enemySlowParticles.push(new EnemySlowParticle(centerX + offsetX, centerY + offsetY));
}
// 创建玩家获得道具时的粒子效果
function createPropParticles(player, propType) {
const propColors = {
[PROP_TYPES.EXTRA_LIFE]: '#4CAF50', // 绿色
[PROP_TYPES.SPEED_BOOST]: '#FFC107', // 黄色
[PROP_TYPES.SHIELD]: '#2196F3', // 蓝色
[PROP_TYPES.TIME_FREEZE]: '#00BCD4', // 青色
[PROP_TYPES.TIME_SLOW]: '#9C27B0' // 紫色
};
const color = propColors[propType] || '#ffffff';
const centerX = player.x + player.width / 2;
const centerY = player.y + player.height / 2;
// 创建多个粒子
for (let i = 0; i < 15; i++) {
gameState.playerPropParticles.push(new PlayerPropParticle(centerX, centerY, color));
}
}
// 粒子类
// 优化:限制最大粒子数量
const MAX_PARTICLES = 100;
let particleCount = 0;
class Particle {
constructor(x, y, color, isPortal, isOcean = false) {
// 如果粒子数量已达上限,则不创建新粒子
if (particleCount >= MAX_PARTICLES) return;
particleCount++;
this.x = x;
this.y = y;
this.size = isOcean ? Math.random() * 3 + 2 : Math.random() * 2 + 1; // 水花粒子更大
this.opacity = 1;
this.decay = isOcean ? Math.random() * 0.03 + 0.02 : Math.random() * 0.04 + 0.02;
if (isOcean) {
// 水花粒子:向上飞溅然后下落
this.speedX = (Math.random() - 0.5) * (Math.random() * 4 + 2);
this.speedY = -Math.random() * 5 - 2; // 向上飞
this.gravity = 0.15; // 重力效果
// 水花颜色(白色、浅蓝色)
const waterColor = Math.random();
if (waterColor < 0.3) {
this.color = `rgba(255, 255, 255, ${Math.random() * 0.8 + 0.5})`;
} else if (waterColor < 0.6) {
this.color = `rgba(173, 216, 230, ${Math.random() * 0.8 + 0.5})`;
} else {
this.color = `rgba(135, 206, 235, ${Math.random() * 0.8 + 0.5})`;
}
} else {
this.speedX = (Math.random() - 0.5) * (Math.random() * 3 + 1);
this.speedY = (Math.random() - 0.5) * (Math.random() * 3 + 1);
this.color = isPortal ?
`rgba(33, 150, 243, ${Math.random() * 0.5 + 0.5})` :
`rgba(33, 150, 243, ${Math.random() * 0.5 + 0.5})`;
}
}
update() {
if (!this.x && !this.y) return true; // 如果粒子被限制创建,直接返回true
this.x += this.speedX;
this.y += this.speedY;
// 水花粒子受重力影响
if (this.gravity) {
this.speedY += this.gravity;
}
this.opacity -= this.decay;
this.size *= 0.97;
if (this.opacity <= 0 || this.size <= 0.1) {
particleCount--; // 粒子消失时减少计数
return true;
}
return false;
}
draw() {
ctx.save();
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
}
// 爆炸效果类
class Explosion {
constructor(x, y, isPortalExplosion = false, radius = 30) {
this.x = x;
this.y = y;
this.radius = 5;
this.maxRadius = radius;
this.growthSpeed = 1.5;
this.opacity = 1;
this.decaySpeed = 0.02;
this.isPortalExplosion = isPortalExplosion;
this.particles = [];
// 判断是否为海域关卡
this.isOceanLevel = gameState && gameState.level >= 11 && gameState.level <= 20;
// 创建粒子(海域关卡使用水花粒子)
const particleCount = isPortalExplosion ? 30 : (this.isOceanLevel ? 40 : 50);
for (let i = 0; i < particleCount; i++) {
this.particles.push(new Particle(x, y, null, isPortalExplosion, this.isOceanLevel));
}
}
draw() {
ctx.save();
// 绘制粒子
this.particles.forEach(particle => particle.draw());
if (this.isPortalExplosion) {
// 绘制传送门爆炸外圈 (深紫色)
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = `rgba(76, 17, 149, ${this.opacity * 0.7})`;
ctx.fill();
// 绘制传送门爆炸中层 (紫色)
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius * 0.7, 0, Math.PI * 2);
ctx.fillStyle = `rgba(123, 31, 162, ${this.opacity * 0.8})`;
ctx.fill();
// 绘制传送门爆炸中心 (亮紫色)
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius * 0.4, 0, Math.PI * 2);
ctx.fillStyle = `rgba(191, 90, 242, ${this.opacity})`;
ctx.fill();
} else if (this.isOceanLevel) {
// 海域关卡:绘制水花效果
const waveRadius = this.radius * 1.1; // 水花尺寸
// 绘制水花外圈波纹
ctx.beginPath();
ctx.arc(this.x, this.y, waveRadius, 0, Math.PI * 2);
ctx.strokeStyle = `rgba(255, 255, 255, ${this.opacity * 0.5})`;
ctx.lineWidth = 2;
ctx.stroke();
// 绘制中层波纹
ctx.beginPath();
ctx.arc(this.x, this.y, waveRadius * 0.7, 0, Math.PI * 2);
ctx.strokeStyle = `rgba(135, 206, 235, ${this.opacity * 0.6})`;
ctx.lineWidth = 1.5;
ctx.stroke();
// 绘制内层水花
ctx.beginPath();
ctx.arc(this.x, this.y, waveRadius * 0.4, 0, Math.PI * 2);
ctx.fillStyle = `rgba(173, 216, 230, ${this.opacity * 0.6})`;
ctx.fill();
// 绘制水花中心泡沫
ctx.beginPath();
ctx.arc(this.x, this.y, waveRadius * 0.2, 0, Math.PI * 2);
ctx.fillStyle = `rgba(255, 255, 255, ${this.opacity * 0.8})`;
ctx.fill();
} else {
// 绘制普通爆炸外圈 (深蓝色)
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = `rgba(13, 71, 161, ${this.opacity * 0.7})`;
ctx.fill();
// 绘制普通爆炸中层 (蓝色)
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius * 0.7, 0, Math.PI * 2);
ctx.fillStyle = `rgba(33, 150, 243, ${this.opacity * 0.8})`;
ctx.fill();
// 绘制普通爆炸中心 (亮蓝色)
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius * 0.4, 0, Math.PI * 2);
ctx.fillStyle = `rgba(187, 222, 251, ${this.opacity})`;
ctx.fill();
}
ctx.restore();
}
update() {
// 海域关卡水花扩散更快
this.radius += this.isOceanLevel ? this.growthSpeed * 1.5 : this.growthSpeed;
this.opacity -= this.decaySpeed;
// 更新粒子
this.particles = this.particles.filter(particle => !particle.update());
// 如果爆炸效果已经消失且粒子全部消失,则返回true表示需要移除
return this.opacity <= 0 || (this.radius > this.maxRadius * (this.isOceanLevel ? 1.5 : 1) && this.particles.length === 0);
}
}
class Laser {
constructor(x, y, dx, dy, isPlayer, angle = null) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.isPlayer = isPlayer;
this.speed = 8;
this.width = 6;
this.height = 30;
this.hitEnemies = []; // 已经击中的敌人,实现穿透效果
this.angle = angle; // 角度模式
}
draw() {
ctx.save();
ctx.translate(this.x, this.y);
// 根据角度或方向旋转
if (this.angle !== null) {
ctx.rotate(this.angle + Math.PI / 2); // 激光默认是垂直的,需要旋转90度
} else {
// 根据方向旋转
if (this.dx !== 0) {
ctx.rotate(this.dx > 0 ? Math.PI / 2 : -Math.PI / 2);
} else if (this.dy > 0) {
ctx.rotate(Math.PI);
}
}
// 绘制激光渐变
const gradient = ctx.createLinearGradient(0, -this.height / 2, 0, this.height / 2);
gradient.addColorStop(0, 'rgba(0, 255, 255, 0)');
gradient.addColorStop(0.3, 'rgba(0, 255, 255, 0.8)');
gradient.addColorStop(0.5, 'rgba(255, 255, 255, 1)');
gradient.addColorStop(0.7, 'rgba(0, 255, 255, 0.8)');
gradient.addColorStop(1, 'rgba(0, 255, 255, 0)');
ctx.fillStyle = gradient;
ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
// 绘制激光核心
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(-1, -this.height / 2, 2, this.height);
ctx.restore();
}
update() {
if (this.angle !== null) {
// 角度模式
this.x += Math.cos(this.angle) * this.speed;
this.y += Math.sin(this.angle) * this.speed;
} else {
// 方向模式
this.x += this.dx * this.speed;
this.y += this.dy * this.speed;
}
// 检查是否出界
if (this.x < -50 || this.x > CANVAS_WIDTH + 50 ||
this.y < -50 || this.y > CANVAS_HEIGHT + 50) {
return true; // 需要移除
}
// 碰撞检测(穿透效果)
if (this.isPlayer) {
// 检测普通敌人
for (let i = gameState.enemies.length - 1; i >= 0; i--) {
const enemy = gameState.enemies[i];
if (!this.hitEnemies.includes(enemy)) {
if (this.x - this.width / 2 < enemy.x + enemy.width &&
this.x + this.width / 2 > enemy.x &&
this.y - this.height / 2 < enemy.y + enemy.height &&
this.y + this.height / 2 > enemy.y) {
// 击中敌人
this.hitEnemies.push(enemy);
const damage = enemy.isBoss ? 3 : 2;
enemy.health -= damage;
if (enemy.health <= 0) {
const score = enemy.isBoss ? 500 : 100;
gameState.scoreTexts.push(new ScoreText(
enemy.x + enemy.width / 2,
enemy.y + enemy.height / 2,
score
));
gameState.enemies.splice(i, 1);
gameState.score += score;
}
}
}
}
// 检测炮台
if (gameState.turrets) {
for (let i = gameState.turrets.length - 1; i >= 0; i--) {
const turret = gameState.turrets[i];
if (!this.hitEnemies.includes(turret) && turret.active) {
if (this.x - this.width / 2 < turret.x + turret.width &&
this.x + this.width / 2 > turret.x &&
this.y - this.height / 2 < turret.y + turret.height &&
this.y + this.height / 2 > turret.y) {
// 击中炮台
this.hitEnemies.push(turret);
turret.active = false; // 摧毁炮台
gameState.score += 50; // 炮台分数
gameState.scoreTexts.push(new ScoreText(
turret.x + turret.width / 2,
turret.y + turret.height / 2,
50
));
}
}
}
}
} else {
// 敌人的激光攻击玩家
const player = gameState.player;
if (player && !player.shield) {
if (this.x - this.width / 2 < player.x + player.width &&
this.x + this.width / 2 > player.x &&
this.y - this.height / 2 < player.y + player.height &&
this.y + this.height / 2 > player.y) {
// 击中玩家
gameState.lives--;
if (gameState.lives <= 0) {
gameOver();
}
return true; // 移除激光
}
}
}
return false;
}
}
class Bullet {
constructor(x, y, direction, isPlayerBullet = false, tankColor = null, angle = null) {
this.x = x;
this.y = y;
this.width = BULLET_SIZE;
this.height = BULLET_SIZE;
this.direction = direction;
this.speed = BULLET_SPEED;
this.isPlayerBullet = isPlayerBullet;
this.life = 100; // 子弹生命周期
this.tankColor = tankColor || (isPlayerBullet ? '#2196F3' : '#F44336');
this.damage = this.isPlayerBullet ? (gameState.playerBulletDamage || 1) : 1;
this.angle = angle; // 角度模式
}
draw() {
ctx.save();
ctx.translate(this.x + this.width / 2, this.y + this.height / 2);
// 如果是角度模式,直接用角度旋转(需要加上90度偏移,因为子弹初始绘制是朝右的)
if (this.angle !== null) {
ctx.rotate(this.angle);
} else {
// 根据方向旋转子弹,确保与坦克方向一致
switch (this.direction) {
case 'right':
ctx.rotate(0); // 向右发射,不旋转
break;
case 'down':
ctx.rotate(Math.PI / 2); // 向下发射,旋转90度
break;
case 'left':
ctx.rotate(Math.PI); // 向左发射,旋转180度
break;
case 'up':
ctx.rotate(3 * Math.PI / 2); // 向上发射,旋转270度
break;
case 'up-right':
ctx.rotate(-Math.PI / 4); // 斜上右,旋转-45度
break;
case 'down-right':
ctx.rotate(Math.PI / 4); // 斜下右,旋转45度
break;
case 'down-left':
ctx.rotate(3 * Math.PI / 4); // 斜下左,旋转135度
break;
case 'up-left':
ctx.rotate(-3 * Math.PI / 4); // 斜上左,旋转-135度
break;
}
}
// 创建子弹渐变效果
const gradient = ctx.createLinearGradient(-this.width / 2, -this.height / 2, this.width * 2, 0);
gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
gradient.addColorStop(0.3, this.lightenColor(this.tankColor, 50));
gradient.addColorStop(1, this.darkenColor(this.tankColor, 20));
// 绘制子弹主体 (统一水平向右的形状,通过旋转适应不同方向)
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.moveTo(-this.width / 2, -this.height / 3);
ctx.lineTo(this.width * 1.5, 0);
ctx.lineTo(-this.width / 2, this.height / 3);
ctx.closePath();
ctx.fill();
// 绘制发光效果
ctx.shadowColor = this.lightenColor(this.tankColor, 100);
ctx.shadowBlur = 8;
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.beginPath();
ctx.moveTo(-this.width / 4, -this.height / 4);
ctx.lineTo(this.width, 0);
ctx.lineTo(-this.width / 4, this.height / 4);
ctx.closePath();
ctx.fill();
// 绘制子弹尖端
ctx.shadowBlur = 0;
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(this.width * 1.5, 0, this.height / 4, 0, Math.PI * 2);
ctx.fill();
// 添加尾部拖尾效果
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.beginPath();
ctx.moveTo(-this.width / 2 - this.width / 4, 0);
ctx.lineTo(-this.width / 2, -this.height / 6);
ctx.lineTo(-this.width / 2, this.height / 6);
ctx.closePath();
ctx.fill();
ctx.restore();
}
// 调暗颜色方法
darkenColor(color, percent) {
// 将颜色从十六进制转换为RGB
let r = parseInt(color.substring(1, 3), 16);
let g = parseInt(color.substring(3, 5), 16);
let b = parseInt(color.substring(5, 7), 16);
// 调暗颜色
r = Math.max(0, Math.floor(r * (1 - percent / 100)));
g = Math.max(0, Math.floor(g * (1 - percent / 100)));
b = Math.max(0, Math.floor(b * (1 - percent / 100)));
// 转换回十六进制
const toHex = (c) => {
const hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return '#' + toHex(r) + toHex(g) + toHex(b);
}
// 调亮颜色方法
lightenColor(color, percent) {
// 将颜色从十六进制转换为RGB
let r = parseInt(color.substring(1, 3), 16);
let g = parseInt(color.substring(3, 5), 16);
let b = parseInt(color.substring(5, 7), 16);
// 调亮颜色
r = Math.min(255, Math.floor(r * (1 + percent / 100)));
g = Math.min(255, Math.floor(g * (1 + percent / 100)));
b = Math.min(255, Math.floor(b * (1 + percent / 100)));
// 转换回十六进制
const toHex = (c) => {
const hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return '#' + toHex(r) + toHex(g) + toHex(b);
}
update() {
if (this.angle !== null) {
// 角度模式:按角度移动
this.x += Math.cos(this.angle) * this.speed;
this.y += Math.sin(this.angle) * this.speed;
} else {
// 方向模式
const diagSpeed = this.speed * Math.SQRT1_2; // 斜向速度
switch (this.direction) {
case 'up':
this.y -= this.speed;
break;
case 'down':
this.y += this.speed;
break;
case 'left':
this.x -= this.speed;
break;
case 'right':
this.x += this.speed;
break;
case 'up-left':
this.x -= diagSpeed;
this.y -= diagSpeed;
break;
case 'up-right':
this.x += diagSpeed;
this.y -= diagSpeed;
break;
case 'down-left':
this.x -= diagSpeed;
this.y += diagSpeed;
break;
case 'down-right':
this.x += diagSpeed;
this.y += diagSpeed;
break;
}
}
// Remove bullet if it goes off screen
if (
this.x < 0 ||
this.x > CANVAS_WIDTH ||
this.y < 0 ||
this.y > CANVAS_HEIGHT
) {
return true;
}
return false;
}
checkCollision() {
// Check collision with walls
for (let i = gameState.walls.length - 1; i >= 0; i--) {
const wall = gameState.walls[i];
if (
this.x < wall.x + wall.width &&
this.x + this.width > wall.x &&
this.y < wall.y + wall.height &&
this.y + this.height > wall.y
) {
// 创建爆炸效果
gameState.explosions.push(new Explosion(wall.x + wall.width / 2, wall.y + wall.height / 2));
// 播放击中墙壁音效
// playSound('hit-wall-sound');
gameState.walls.splice(i, 1);
return true;
}
}
// Check collision with tanks
if (this.isPlayerBullet) {
for (let i = gameState.enemies.length - 1; i >= 0; i--) {
const enemy = gameState.enemies[i];
if (
this.x < enemy.x + enemy.width &&
this.x + this.width > enemy.x &&
this.y < enemy.y + enemy.height &&
this.y + this.height > enemy.y
) {
// 创建爆炸效果
gameState.explosions.push(new Explosion(enemy.x + enemy.width / 2, enemy.y + enemy.height / 2));
// 播放击中敌人音效
// playSound('hit-enemy-sound');
if (enemy.isBoss) {
enemy.health--;
// 更新BOSS血条
updateBossHealthBar(enemy);
// 播放得分音效
// playSound('score-sound');
gameState.score += 50;
// 阶段转换逻辑
if (enemy.health <= 10 && enemy.health > 5 && enemy.phase === 1) {
// 进入第二阶段
enemy.phase = 2;
enemy.shootPattern = 'double';
enemy.speed = ENEMY_SPEED * 0.8;
enemy.color = '#F44336'; // 红色
enemy.turretColor = '#D32F2F';
// 创建阶段转换特效
gameState.explosions.push(new Explosion(enemy.x + enemy.width / 2, enemy.y + enemy.height / 2, false, 40));
// playSound('level-up-sound');
// 杀死所有中型坦克
for (let j = gameState.enemies.length - 1; j >= 0; j--) {
const e = gameState.enemies[j];
if (e.isMedium) {
gameState.explosions.push(new Explosion(e.x + e.width / 2, e.y + e.height / 2));
gameState.enemies.splice(j, 1);
}
}
// 设置玩家子弹伤害为2
gameState.playerBulletDamage = 2;
} else if (enemy.health <= 5 && enemy.phase === 2) {
// 进入第三阶段
enemy.phase = 3;
enemy.shootPattern = 'triple';
enemy.speed = ENEMY_SPEED * 0.9;
enemy.color = '#FFC107'; // 黄色
enemy.turretColor = '#FFA000';
// 创建阶段转换特效
gameState.explosions.push(new Explosion(enemy.x + enemy.width / 2, enemy.y + enemy.height / 2, false, 40));
// playSound('level-up-sound');
// 设置玩家子弹伤害为3
gameState.playerBulletDamage = 3;
}
if (enemy.health <= 0) {
// 隐藏BOSS血条
hideBossHealthBar();
// 清除保存的BOSS血量(BOSS已被击败)
gameState.savedBossHealth = null;
gameState.savedBossMaxHealth = null;
// 播放得分音效
// playSound('score-sound');
gameState.enemies.splice(i, 1);
gameState.score += 500; // BOSS分数更高
// 创建大型爆炸效果
gameState.explosions.push(new Explosion(enemy.x + enemy.width / 2, enemy.y + enemy.height / 2, false, 60));
// 触发BOSS死亡动画(所有BOSS)
startBossDeathAnimation();
}
} else if (enemy.isMedium) {
enemy.health--;
// 播放得分音效
// playSound('score-sound');
gameState.score += 30;
if (enemy.health <= 0) {
// 播放得分音效
// playSound('score-sound');
// 创建得分字幕
gameState.scoreTexts.push(new ScoreText(
enemy.x + enemy.width / 2,
enemy.y + enemy.height / 2,
330
));
gameState.enemies.splice(i, 1);
gameState.score += 300; // 中型坦克分数
// 创建爆炸效果
gameState.explosions.push(new Explosion(enemy.x + enemy.width / 2, enemy.y + enemy.height / 2, false, 40));
}
} else {
// 播放得分音效
// playSound('score-sound');
// 创建得分字幕
gameState.scoreTexts.push(new ScoreText(
enemy.x + enemy.width / 2,
enemy.y + enemy.height / 2,
100
));
gameState.enemies.splice(i, 1);
gameState.score += 100;
}
return true;
}
}
// Check collision with turrets
for (let i = gameState.turrets.length - 1; i >= 0; i--) {
const turret = gameState.turrets[i];
if (
this.x < turret.x + turret.width &&
this.x + this.width > turret.x &&
this.y < turret.y + turret.height &&
this.y + this.height > turret.y
) {
// 创建爆炸效果
gameState.explosions.push(new Explosion(turret.x + turret.width / 2, turret.y + turret.height / 2));
// 播放击中炮台音效
// playSound('hit-turret-sound');
gameState.turrets.splice(i, 1);
gameState.score += 50; // 炮台分数
return true;
}
}
} else {
if (
this.x < gameState.player.x + gameState.player.width &&
this.x + this.width > gameState.player.x &&
this.y < gameState.player.y + gameState.player.height &&
this.y + this.height > gameState.player.y
) {
if (gameState.player.shield) {
gameState.player.shield = false;
return true;
}
// 创建爆炸效果
gameState.explosions.push(new Explosion(gameState.player.x + gameState.player.width / 2, gameState.player.y + gameState.player.height / 2));
// 播放玩家死亡音效
// playSound('player-death-sound');
// 保存BOSS血量(如果存在BOSS)
const boss = gameState.enemies.find(e => e.isBoss);
if (boss) {
gameState.savedBossHealth = boss.health;
gameState.savedBossMaxHealth = boss.maxHealth;
}
gameState.lives--;
if (gameState.lives <= 0) {
endGame(false);
} else {
spawnPlayer();
}
return true;
}
}
// Check collision with traps (especially oil barrels)
for (let i = gameState.traps.length - 1; i >= 0; i--) {
const trap = gameState.traps[i];
if (
trap.active &&
trap.type === TRAP_TYPES.OIL_BARREL &&
this.x < trap.x + trap.width &&
this.x + this.width > trap.x &&
this.y < trap.y + trap.height &&
this.y + this.height > trap.y
) {
// Bullet hits oil barrel, trigger explosion
trap.trigger(null); // Pass null since no tank is triggering it
return true;
}
}
return false;
}
}
// 导弹类
class Missile {
constructor(startX, startY, targetX, explodeY) {
this.x = startX;
this.y = startY;
this.targetX = targetX;
this.explodeY = explodeY;
this.width = 12;
this.height = 30;
this.speed = 4; // 稍微加快导弹速度
this.isMissile = true;
this.damage = 1;
this.hasExploded = false;
}
draw() {
ctx.save();
ctx.translate(this.x + this.width / 2, this.y + this.height / 2);
// 绘制导弹主体(竖直)
const gradient = ctx.createLinearGradient(0, -this.height / 2, 0, this.height / 2);
gradient.addColorStop(0, '#FF5722');
gradient.addColorStop(0.5, '#FF9800');
gradient.addColorStop(1, '#F44336');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.moveTo(-this.width / 2, this.height / 2);
ctx.lineTo(0, -this.height / 2);
ctx.lineTo(this.width / 2, this.height / 2);
ctx.closePath();
ctx.fill();
// 绘制导弹发光效果
ctx.shadowColor = '#FF5722';
ctx.shadowBlur = 10;
ctx.strokeStyle = '#FF9800';
ctx.lineWidth = 2;
ctx.stroke();
// 绘制尾部火焰
if (Math.random() > 0.3) {
ctx.shadowBlur = 0;
ctx.fillStyle = 'rgba(255, 235, 59, 0.8)';
ctx.beginPath();
ctx.moveTo(-3, this.height / 2);
ctx.lineTo(0, this.height / 2 + 8 + Math.random() * 5);
ctx.lineTo(3, this.height / 2);
ctx.closePath();
ctx.fill();
}
ctx.restore();
}
update() {
// 向下移动
this.y += this.speed;
// 检查是否到达爆炸位置或底部
if (this.y >= this.explodeY || this.y > CANVAS_HEIGHT - 50) {
this.explode();
return true;
}
return false;
}
explode() {
if (this.hasExploded) return;
this.hasExploded = true;
// 创建爆炸效果
gameState.explosions.push(new Explosion(this.x + this.width / 2, this.y + this.height / 2, false, 40));
// 检查爆炸是否击中玩家(只要在爆炸范围内就受到伤害)
const player = gameState.player;
const dist = Math.sqrt(
Math.pow((this.x + this.width / 2) - (player.x + player.width / 2), 2) +
Math.pow((this.y + this.height / 2) - (player.y + player.height / 2), 2)
);
if (dist < 80) {
// 玩家受到伤害并眩晕
if (!gameState.player.shield) {
gameState.lives--;
if (gameState.lives <= 0) {
endGame(false);
} else {
// 眩晕玩家5秒
gameState.player.stunned = true;
gameState.player.stunEndTime = Date.now() + 5000;
spawnPlayer();
}
} else {
gameState.player.shield = false;
}
}
}
}
class Wall {
constructor(x, y) {
this.x = x;
this.y = y;
this.width = WALL_SIZE;
this.height = WALL_SIZE;
this.color = '#607D8B'; // 石灰色作为基础色
// 预先决定是否长树(固定不闪烁)
this.hasTree = Math.random() > 0.85;
}
draw() {
ctx.save();
ctx.translate(this.x, this.y);
// 检查是否是海域关卡
const isOceanLevel = gameState.level >= 11 && gameState.level <= 20;
if (isOceanLevel) {
// 沙滩风格墙壁
// 浅色沙色渐变,更自然
const sandGradient = ctx.createLinearGradient(0, 0, 0, this.height);
sandGradient.addColorStop(0, '#FAF0E6'); // 非常浅的沙色
sandGradient.addColorStop(0.5, '#F5DEB3'); // 浅沙色
sandGradient.addColorStop(1, '#DEB887'); // 沙棕色
// 绘制沙滩块主体
ctx.fillStyle = sandGradient;
ctx.fillRect(0, 0, this.width, this.height);
// 添加一些小贝壳装饰(随机)
if (Math.random() > 0.7) {
ctx.fillStyle = '#FFF8DC';
ctx.beginPath();
ctx.ellipse(
this.width * 0.3 + Math.random() * this.width * 0.4,
this.height * 0.4 + Math.random() * this.height * 0.3,
2, 1.5,
Math.random() * Math.PI, 0, Math.PI * 2
);
ctx.fill();
}
// 添加小树(固定不闪烁)
if (this.hasTree) {
// 树干
ctx.fillStyle = '#8B4513';
ctx.fillRect(this.width / 2 - 2, this.height * 0.4, 4, this.height * 0.5);
// 树冠(绿色圆形)
ctx.fillStyle = '#228B22';
ctx.beginPath();
ctx.arc(this.width / 2, this.height * 0.35, this.width * 0.25, 0, Math.PI * 2);
ctx.fill();
// 树冠高光
ctx.fillStyle = '#32CD32';
ctx.beginPath();
ctx.arc(this.width / 2 - 3, this.height * 0.3, 4, 0, Math.PI * 2);
ctx.fill();
}
} else {
// 普通陆地墙壁
// 创建墙壁渐变效果
const wallGradient = ctx.createLinearGradient(0, 0, this.width, this.height);
wallGradient.addColorStop(0, this.lightenColor(this.color, 10));
wallGradient.addColorStop(1, this.darkenColor(this.color, 30));
// 绘制墙壁主体
ctx.fillStyle = wallGradient;
ctx.fillRect(0, 0, this.width, this.height);
// 绘制墙壁边缘
ctx.strokeStyle = this.darkenColor(this.color, 40);
ctx.lineWidth = 2;
ctx.strokeRect(0, 0, this.width, this.height);
// 绘制砖石纹理
const brickWidth = this.width / 3;
const brickHeight = this.height / 3;
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
// 砖块位置偏移,创建交错效果
const offsetX = (j % 2 === 0) ? 0 : brickWidth / 2;
const x = i * brickWidth + offsetX;
const y = j * brickHeight;
// 确保砖块不超出墙壁边界
if (x + brickWidth <= this.width) {
ctx.strokeStyle = this.darkenColor(this.color, 20);
ctx.lineWidth = 1;
ctx.strokeRect(x, y, brickWidth, brickHeight);
// 砖块内部细节
if (Math.random() > 0.5) {
ctx.fillStyle = this.darkenColor(this.color, 10);
ctx.fillRect(x + 2, y + 2, brickWidth - 4, brickHeight - 4);
}
}
}
}
// 添加金属加固条
ctx.fillStyle = '#424242';
ctx.fillRect(0, this.height - 5, this.width, 5);
ctx.fillRect(this.width - 5, 0, 5, this.height);
}
ctx.restore();
}
// 调暗颜色方法
darkenColor(color, percent) {
// 将颜色从十六进制转换为RGB
let r = parseInt(color.substring(1, 3), 16);
let g = parseInt(color.substring(3, 5), 16);
let b = parseInt(color.substring(5, 7), 16);
// 调暗颜色
r = Math.max(0, Math.floor(r * (1 - percent / 100)));
g = Math.max(0, Math.floor(g * (1 - percent / 100)));
b = Math.max(0, Math.floor(b * (1 - percent / 100)));
// 转换回十六进制
const toHex = (c) => {
const hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return '#' + toHex(r) + toHex(g) + toHex(b);
}
// 调亮颜色方法
lightenColor(color, percent) {
// 将颜色从十六进制转换为RGB
let r = parseInt(color.substring(1, 3), 16);
let g = parseInt(color.substring(3, 5), 16);
let b = parseInt(color.substring(5, 7), 16);
// 调亮颜色
r = Math.min(255, Math.floor(r * (1 + percent / 100)));
g = Math.min(255, Math.floor(g * (1 + percent / 100)));
b = Math.min(255, Math.floor(b * (1 + percent / 100)));
// 转换回十六进制
const toHex = (c) => {
const hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return '#' + toHex(r) + toHex(g) + toHex(b);
}
}
// 空投箱类
class AirDrop {
constructor(x, y) {
this.x = x;
this.y = y;
this.width = 30;
this.height = 30;
this.speed = 0.3 + Math.random() * 2; // 增加速度范围: 0.3-2.3
this.isOpen = false;
this.dropTime = Date.now();
this.openDelay = 2000 + Math.random() * 2000; // 随机延迟: 2-4秒
this.glowIntensity = 1;
this.glowDirection = -0.02;
}
update() {
// 下落逻辑
if (!this.isOpen) {
this.y += this.speed;
// 移除落地检测,经过延迟后自动打开
if (Date.now() - this.dropTime > this.openDelay) {
this.open();
}
}
// 闪烁效果
this.glowIntensity += this.glowDirection;
if (this.glowIntensity <= 0.5 || this.glowIntensity >= 1) {
this.glowDirection *= -1;
}
return false; // 不删除空投箱
}
open() {
this.isOpen = true;
// 生成随机道具(极限模式下不生成)
if (gameState.gameMode !== 'extreme') {
const propTypes = Object.values(PROP_TYPES);
const randomType = propTypes[Math.floor(Math.random() * propTypes.length)];
gameState.props.push(new Prop(this.x + 5, this.y + 5, randomType));
}
// 创建打开特效
gameState.explosions.push(new Explosion(this.x + this.width / 2, this.y + this.height / 2, false, 30));
// playSound('powerup-sound');
}
draw() {
ctx.save();
ctx.translate(this.x + this.width / 2, this.y + this.height / 2);
if (!this.isOpen) {
// 绘制空投箱主体
ctx.fillStyle = '#607D8B';
ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
// 绘制箱子细节
ctx.fillStyle = '#455A64';
ctx.fillRect(-this.width / 2 + 5, -this.height / 2, 5, this.height);
ctx.fillRect(this.width / 2 - 10, -this.height / 2, 5, this.height);
ctx.fillRect(-this.width / 2, -this.height / 2 + 5, this.width, 5);
ctx.fillRect(-this.width / 2, this.height / 2 - 10, this.width, 5);
// 绘制发光效果
ctx.strokeStyle = `rgba(179, 255, 255, ${this.glowIntensity})`;
ctx.lineWidth = 2;
ctx.strokeRect(-this.width / 2 - 2, -this.height / 2 - 2, this.width + 4, this.height + 4);
}
ctx.restore();
}
}
class Prop {
constructor(x, y, type) {
this.x = x;
this.y = y;
this.width = PROP_SIZE;
this.height = PROP_SIZE;
this.type = type;
this.spawnTime = Date.now();
this.lifespan = 10000; // 10 seconds
this.rotation = 0;
this.rotationSpeed = 0.05;
this.colors = {
[PROP_TYPES.EXTRA_LIFE]: '#4CAF50', // 绿色
[PROP_TYPES.SPEED_BOOST]: '#FFC107', // 黄色
[PROP_TYPES.SHIELD]: '#2196F3', // 蓝色
[PROP_TYPES.TIME_FREEZE]: '#00BCD4', // 青色
[PROP_TYPES.TIME_SLOW]: '#9C27B0' // 紫色
};
}
draw() {
ctx.save();
ctx.translate(this.x + this.width / 2, this.y + this.height / 2);
ctx.rotate(this.rotation);
const color = this.colors[this.type];
// 创建道具渐变效果
const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, this.width / 2);
gradient.addColorStop(0, this.lightenColor(color, 30));
gradient.addColorStop(1, color);
// 绘制道具主体
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(0, 0, this.width / 2, 0, Math.PI * 2);
ctx.fill();
// 绘制道具边框
ctx.strokeStyle = this.darkenColor(color, 30);
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(0, 0, this.width / 2, 0, Math.PI * 2);
ctx.stroke();
// 绘制道具图标
ctx.fillStyle = '#fff';
switch (this.type) {
case PROP_TYPES.EXTRA_LIFE:
ctx.beginPath();
ctx.moveTo(0, -this.height / 4);
ctx.lineTo(this.width / 4, this.height / 4);
ctx.lineTo(-this.width / 4, this.height / 4);
ctx.closePath();
ctx.fill();
break;
case PROP_TYPES.SPEED_BOOST:
ctx.beginPath();
ctx.moveTo(-this.width / 4, 0);
ctx.lineTo(this.width / 4, -this.height / 4);
ctx.lineTo(this.width / 4, this.height / 4);
ctx.closePath();
ctx.fill();
break;
case PROP_TYPES.SHIELD:
ctx.beginPath();
ctx.arc(0, 0, this.width / 6, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.arc(0, 0, this.width / 4, 0, Math.PI * 2);
ctx.stroke();
break;
case PROP_TYPES.TIME_SLOW:
// 绘制时间冻结图标(雪花形状)
ctx.beginPath();
ctx.moveTo(0, -this.height / 4);
ctx.lineTo(this.width / 6, this.height / 6);
ctx.lineTo(this.width / 4, 0);
ctx.lineTo(this.width / 6, -this.height / 6);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(0, this.height / 4);
ctx.lineTo(-this.width / 6, -this.height / 6);
ctx.lineTo(-this.width / 4, 0);
ctx.lineTo(-this.width / 6, this.height / 6);
ctx.closePath();
ctx.fill();
break;
case PROP_TYPES.TIME_FREEZE:
// 绘制时间减速图标(时钟形状)
ctx.beginPath();
ctx.arc(0, 0, this.width / 6, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, -this.height / 6);
ctx.lineTo(this.width / 12, -this.height / 12);
ctx.closePath();
ctx.fill();
break;
}
ctx.restore();
}
update() {
this.rotation += this.rotationSpeed;
// Remove prop if lifespan expired
if (Date.now() - this.spawnTime > this.lifespan) {
return true;
}
return false;
}
// 调暗颜色方法
darkenColor(color, percent) {
// 将颜色从十六进制转换为RGB
let r = parseInt(color.substring(1, 3), 16);
let g = parseInt(color.substring(3, 5), 16);
let b = parseInt(color.substring(5, 7), 16);
// 调暗颜色
r = Math.max(0, Math.floor(r * (1 - percent / 100)));
g = Math.max(0, Math.floor(g * (1 - percent / 100)));
b = Math.max(0, Math.floor(b * (1 - percent / 100)));
// 转换回十六进制
const toHex = (c) => {
const hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return '#' + toHex(r) + toHex(g) + toHex(b);
}
// 调亮颜色方法
lightenColor(color, percent) {
// 将颜色从十六进制转换为RGB
let r = parseInt(color.substring(1, 3), 16);
let g = parseInt(color.substring(3, 5), 16);
let b = parseInt(color.substring(5, 7), 16);
// 调亮颜色
r = Math.min(255, Math.floor(r * (1 + percent / 100)));
g = Math.min(255, Math.floor(g * (1 + percent / 100)));
b = Math.min(255, Math.floor(b * (1 + percent / 100)));
// 转换回十六进制
const toHex = (c) => {
const hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return '#' + toHex(r) + toHex(g) + toHex(b);
}
update() {
// Remove prop if lifespan expired
if (Date.now() - this.spawnTime > this.lifespan) {
return true;
}
return false;
}
}
// 机关类
class Trap {
constructor(x, y, type) {
this.x = x;
this.y = y;
this.width = TRAP_SIZE;
this.height = TRAP_SIZE;
this.type = type;
this.active = true;
this.spawnTime = Date.now();
this.lifespan = type === TRAP_TYPES.PORTAL ? 45000 : type === TRAP_TYPES.LASER_NET ? 60000 : 30000; // 传送门45秒,激光网60秒,其他30秒
this.linkedPortal = null; // 用于传送门,链接到另一个传送门
this.explosionRadius = 80; // 油桶爆炸半径,比地雷更大
this.lastTeleportTime = 0; // 用于传送门冷却
this.rotation = 0;
this.rotationSpeed = type === TRAP_TYPES.MINE ? 0.02 : 0;
this.uses = 0; // 传送门使用次数
this.colors = {
[TRAP_TYPES.MINE]: '#333333',
[TRAP_TYPES.PORTAL]: '#0066ff',
[TRAP_TYPES.OIL_BARREL]: '#8B4513',
[TRAP_TYPES.LASER_NET]: '#ff0000'
};
// 激光网特有属性
this.isActivated = false; // 是否被触发
this.activatedTime = 0; // 触发时间
this.stunDuration = 3000; // 眩晕持续时间3秒
}
draw() {
if (!this.active) return;
ctx.save();
ctx.translate(this.x + this.width / 2, this.y + this.height / 2);
ctx.rotate(this.rotation);
switch (this.type) {
case TRAP_TYPES.MINE:
if (gameState.level >= 11) {
// 海域关卡:漩涡样式 - 与地图颜色相近
const time = Date.now() / 1000;
const whirlpoolSpeed = 1.2;
const maxRadius = this.width * 1;
// 外光晕 - 与地图背景融合的渐变
const outerGlow = ctx.createRadialGradient(0, 0, 0, 0, 0, maxRadius * 1.2);
outerGlow.addColorStop(0, 'rgba(4, 120, 180, 0.4)'); // 深蓝
outerGlow.addColorStop(0.4, 'rgba(3, 169, 244, 0.2)'); // 中等蓝
outerGlow.addColorStop(0.7, 'rgba(41, 182, 246, 0.1)'); // 明亮蓝
outerGlow.addColorStop(1, 'rgba(79, 195, 247, 0)'); // 浅天蓝(透明)
ctx.fillStyle = outerGlow;
ctx.beginPath();
ctx.arc(0, 0, maxRadius * 1.2, 0, Math.PI * 2);
ctx.fill();
// 多层螺旋漩涡 - 颜色从深蓝渐变到浅蓝
const layers = [
{ color: 'rgba(2, 88, 209, 0.85)', width: 3, speed: whirlpoolSpeed, radius: maxRadius * 0.95 },
{ color: 'rgba(3, 169, 244, 0.8)', width: 2.5, speed: -whirlpoolSpeed * 1.3, radius: maxRadius * 0.8 },
{ color: 'rgba(41, 182, 246, 0.75)', width: 2, speed: whirlpoolSpeed * 1.6, radius: maxRadius * 0.65 },
{ color: 'rgba(79, 195, 247, 0.7)', width: 1.5, speed: -whirlpoolSpeed * 1.9, radius: maxRadius * 0.5 },
{ color: 'rgba(128, 214, 254, 0.65)', width: 1, speed: whirlpoolSpeed * 2.2, radius: maxRadius * 0.35 },
];
layers.forEach((layer, layerIndex) => {
ctx.strokeStyle = layer.color;
ctx.lineWidth = layer.width;
ctx.beginPath();
// 绘制螺旋线
const turns = 2.5;
const points = 100;
for (let i = 0; i <= points; i++) {
const progress = i / points;
const angle = progress * Math.PI * 2 * turns + time * layer.speed;
const radius = layer.radius * (1 - progress * 0.7);
const x = Math.cos(angle) * radius;
const y = Math.sin(angle) * radius;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
});
// 漩涡中心渐变核心
const centerGlow = ctx.createRadialGradient(0, 0, 0, 0, 0, this.width / 2.5);
centerGlow.addColorStop(0, 'rgba(128, 214, 254, 0.9)'); // 最浅
centerGlow.addColorStop(0.3, 'rgba(79, 195, 247, 0.7)'); // 浅蓝
centerGlow.addColorStop(0.6, 'rgba(3, 169, 244, 0.4)'); // 中等蓝
centerGlow.addColorStop(1, 'rgba(2, 88, 209, 0)'); // 深蓝(透明)
ctx.fillStyle = centerGlow;
ctx.beginPath();
ctx.arc(0, 0, this.width / 2.5, 0, Math.PI * 2);
ctx.fill();
// 中心亮点 - 更柔和
const centerPoint = ctx.createRadialGradient(0, 0, 0, 0, 0, 5);
centerPoint.addColorStop(0, 'rgba(255, 255, 255, 0.8)');
centerPoint.addColorStop(1, 'rgba(128, 214, 254, 0)');
ctx.fillStyle = centerPoint;
ctx.beginPath();
ctx.arc(0, 0, 4 + Math.sin(time * 4) * 2, 0, Math.PI * 2);
ctx.fill();
// 流动的水纹效果
for (let i = 0; i < 4; i++) {
const rippleTime = time * 1 + i * 0.5;
const rippleAlpha = 0.15 + Math.sin(time + i) * 0.05;
const rippleRadius = this.width * (0.3 + (rippleTime % 1) * 0.5);
const rippleGradient = ctx.createRadialGradient(0, 0, rippleRadius * 0.8, 0, 0, rippleRadius);
rippleGradient.addColorStop(0, `rgba(128, 214, 254, 0)`);
rippleGradient.addColorStop(0.8, `rgba(79, 195, 247, ${rippleAlpha})`);
rippleGradient.addColorStop(1, `rgba(41, 182, 246, ${rippleAlpha * 0.5})`);
ctx.strokeStyle = rippleGradient;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.arc(0, 0, rippleRadius, 0, Math.PI * 2);
ctx.stroke();
}
} else {
// 陆地关卡:普通地雷样式
const mineGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, this.width / 2);
mineGradient.addColorStop(0, '#555555');
mineGradient.addColorStop(1, this.colors[this.type]);
ctx.fillStyle = mineGradient;
ctx.beginPath();
ctx.arc(0, 0, this.width / 2, 0, Math.PI * 2);
ctx.fill();
// 绘制地雷边缘
ctx.strokeStyle = '#ff0000';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(0, 0, this.width / 2, 0, Math.PI * 2);
ctx.stroke();
// 绘制地雷标志
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.moveTo(0, -this.height / 4);
ctx.lineTo(this.width / 4, this.height / 4);
ctx.lineTo(-this.width / 4, this.height / 4);
ctx.closePath();
ctx.fill();
// 绘制金属纹理
ctx.strokeStyle = '#555555';
ctx.lineWidth = 1;
for (let i = 0; i < 6; i++) {
const angle = (i * Math.PI) / 3;
const x1 = Math.cos(angle) * this.width / 4;
const y1 = Math.sin(angle) * this.width / 4;
const x2 = Math.cos(angle) * this.width / 2;
const y2 = Math.sin(angle) * this.width / 2;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
}
break;
case TRAP_TYPES.PORTAL:
// 绘制传送门 (蓝色和紫色交替)
const portalColor1 = Math.sin(Date.now() / 500) > 0 ? '#0066ff' : '#9900ff';
const portalColor2 = Math.sin(Date.now() / 500) > 0 ? '#9900ff' : '#0066ff';
// 外光环
ctx.beginPath();
ctx.arc(0, 0, this.width / 2, 0, Math.PI * 2);
const outerGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, this.width / 2);
outerGradient.addColorStop(0, 'rgba(255, 255, 255, 0.8)');
outerGradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
ctx.fillStyle = outerGradient;
ctx.fill();
// 传送门主体
ctx.beginPath();
ctx.arc(0, 0, this.width / 2 * 0.8, 0, Math.PI * 2);
const portalGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, this.width / 2 * 0.8);
portalGradient.addColorStop(0, 'rgba(255, 255, 255, 0.5)');
portalGradient.addColorStop(0.5, '#9c27b0'); // 紫色
portalGradient.addColorStop(1, '#6a1b9a'); // 深紫色
ctx.fillStyle = portalGradient;
ctx.fill();
// 内部光环
ctx.beginPath();
ctx.arc(0, 0, this.width / 3, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.fill();
// 能量流动效果
const time = Date.now() / 1000;
for (let i = 0; i < 8; i++) {
const angle = (i * Math.PI / 4) + time;
const distance = this.width / 4 + Math.sin(time * 2 + i) * this.width / 8;
const x = Math.cos(angle) * distance;
const y = Math.sin(angle) * distance;
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
ctx.beginPath();
ctx.arc(x, y, 3, 0, Math.PI * 2);
ctx.fill();
}
break;
case TRAP_TYPES.OIL_BARREL:
// 绘制油桶主体
const barrelGradient = ctx.createLinearGradient(-this.width / 2, 0, this.width / 2, 0);
barrelGradient.addColorStop(0, this.darkenColor(this.colors[this.type], 20));
barrelGradient.addColorStop(1, this.lightenColor(this.colors[this.type], 10));
ctx.fillStyle = barrelGradient;
ctx.fillRect(-this.width / 2, -this.height / 4, this.width, this.height * 3 / 4);
// 油桶顶部
ctx.fillStyle = this.darkenColor(this.colors[this.type], 10);
ctx.fillRect(-this.width / 2 - 2, -this.height / 2, this.width + 4, this.height / 4);
// 油桶盖子
ctx.fillStyle = '#696969';
ctx.fillRect(-this.width / 4, -this.height / 2, this.width / 2, this.height / 8);
// 金属条纹
ctx.fillStyle = '#444444';
ctx.fillRect(-this.width / 2, -this.height / 8, this.width, 2);
ctx.fillRect(-this.width / 2, this.height / 8, this.width, 2);
// 警告标志
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.moveTo(-this.width / 6, -this.height / 16);
ctx.lineTo(this.width / 6, -this.height / 16);
ctx.lineTo(0, this.height / 16);
ctx.closePath();
ctx.fill();
break;
case TRAP_TYPES.LASER_NET: {
const time = Date.now() / 1000;
const halfW = this.width / 2;
const halfH = this.height / 2;
if (this.isActivated) {
// 触发后:包住的样子(椭圆形牢笼)
const pulseScale = 1 + Math.sin(time * 8) * 0.05;
const alpha = 0.8 + Math.sin(time * 6) * 0.2;
// 外边框(黑色)
ctx.strokeStyle = '#000000';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.ellipse(0, 0, halfW * pulseScale, halfH * pulseScale, 0, 0, Math.PI * 2);
ctx.stroke();
// 激光网内部(红色渐变)
const laserGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, halfW * pulseScale);
laserGradient.addColorStop(0, `rgba(255, 0, 0, ${alpha * 0.3})`);
laserGradient.addColorStop(0.5, `rgba(255, 0, 0, ${alpha * 0.5})`);
laserGradient.addColorStop(1, `rgba(255, 0, 0, ${alpha * 0.7})`);
ctx.fillStyle = laserGradient;
ctx.beginPath();
ctx.ellipse(0, 0, halfW * pulseScale, halfH * pulseScale, 0, 0, Math.PI * 2);
ctx.fill();
// 十字激光线
ctx.strokeStyle = `rgba(255, 0, 0, ${alpha})`;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(-halfW * pulseScale, 0);
ctx.lineTo(halfW * pulseScale, 0);
ctx.moveTo(0, -halfH * pulseScale);
ctx.lineTo(0, halfH * pulseScale);
ctx.stroke();
// 四个角的黑色边框加强
ctx.fillStyle = '#000000';
const cornerSize = 6;
ctx.fillRect(-halfW * pulseScale - 3, -halfH * pulseScale - 3, cornerSize, cornerSize);
ctx.fillRect(halfW * pulseScale - 3, -halfH * pulseScale - 3, cornerSize, cornerSize);
ctx.fillRect(-halfW * pulseScale - 3, halfH * pulseScale - 3, cornerSize, cornerSize);
ctx.fillRect(halfW * pulseScale - 3, halfH * pulseScale - 3, cornerSize, cornerSize);
} else {
// 未触发:网状结构
const gridSize = 8;
const flicker = 0.7 + Math.sin(time * 10) * 0.3;
// 绘制网格线
ctx.strokeStyle = `rgba(255, 0, 0, ${flicker})`;
ctx.lineWidth = 1.5;
// 横线
for (let y = -halfH + gridSize; y < halfH; y += gridSize) {
ctx.beginPath();
ctx.moveTo(-halfW, y);
ctx.lineTo(halfW, y);
ctx.stroke();
}
// 竖线
for (let x = -halfW + gridSize; x < halfW; x += gridSize) {
ctx.beginPath();
ctx.moveTo(x, -halfH);
ctx.lineTo(x, halfH);
ctx.stroke();
}
// 四个角的黑色边框
ctx.fillStyle = '#000000';
const cornerSize = 8;
ctx.fillRect(-halfW - 2, -halfH - 2, cornerSize, cornerSize);
ctx.fillRect(halfW - cornerSize + 2, -halfH - 2, cornerSize, cornerSize);
ctx.fillRect(-halfW - 2, halfH - cornerSize + 2, cornerSize, cornerSize);
ctx.fillRect(halfW - cornerSize + 2, halfH - cornerSize + 2, cornerSize, cornerSize);
// 边框线条(连接四个角)
ctx.strokeStyle = '#000000';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(-halfW - 2, -halfH);
ctx.lineTo(halfW + 2, -halfH);
ctx.moveTo(-halfW - 2, halfH);
ctx.lineTo(halfW + 2, halfH);
ctx.moveTo(-halfW, -halfH - 2);
ctx.lineTo(-halfW, halfH + 2);
ctx.moveTo(halfW, -halfH - 2);
ctx.lineTo(halfW, halfH + 2);
ctx.stroke();
}
break;
}
}
ctx.restore();
}
update() {
this.rotation += this.rotationSpeed;
// 检查是否过期
if (Date.now() - this.spawnTime > this.lifespan) {
return true;
}
return false;
}
// 调暗颜色方法
darkenColor(color, percent) {
// 将颜色从十六进制转换为RGB
let r = parseInt(color.substring(1, 3), 16);
let g = parseInt(color.substring(3, 5), 16);
let b = parseInt(color.substring(5, 7), 16);
// 调暗颜色
r = Math.max(0, Math.floor(r * (1 - percent / 100)));
g = Math.max(0, Math.floor(g * (1 - percent / 100)));
b = Math.max(0, Math.floor(b * (1 - percent / 100)));
// 转换回十六进制
const toHex = (c) => {
const hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return '#' + toHex(r) + toHex(g) + toHex(b);
}
// 调亮颜色方法
lightenColor(color, percent) {
// 将颜色从十六进制转换为RGB
let r = parseInt(color.substring(1, 3), 16);
let g = parseInt(color.substring(3, 5), 16);
let b = parseInt(color.substring(5, 7), 16);
// 调亮颜色
r = Math.min(255, Math.floor(r * (1 + percent / 100)));
g = Math.min(255, Math.floor(g * (1 + percent / 100)));
b = Math.min(255, Math.floor(b * (1 + percent / 100)));
// 转换回十六进制
const toHex = (c) => {
const hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return '#' + toHex(r) + toHex(g) + toHex(b);
}
update() {
// 检查是否过期
if (Date.now() - this.spawnTime > this.lifespan) {
return true;
}
return false;
}
trigger(tank) {
if (!this.active) return false;
switch (this.type) {
case TRAP_TYPES.MINE:
this.active = false;
// 创建爆炸效果
gameState.explosions.push(new Explosion(this.x + this.width / 2, this.y + this.height / 2));
// 播放爆炸音效
// playSound('hit-enemy-sound');
// 对坦克造成伤害
if (tank.isPlayer) {
if (tank.shield) {
tank.shield = false;
} else {
gameState.lives--;
if (gameState.lives <= 0) {
endGame(false);
} else {
spawnPlayer();
}
}
} else {
// 对敌人造成伤害
for (let i = gameState.enemies.length - 1; i >= 0; i--) {
if (gameState.enemies[i] === tank) {
if (tank.isBoss) {
// 地雷对BOSS只有1点伤害
tank.health -= 1;
if (tank.health <= 0) {
gameState.enemies.splice(i, 1);
gameState.score += 500;
}
} else {
gameState.enemies.splice(i, 1);
gameState.score += 100;
}
break;
}
}
}
break;
case TRAP_TYPES.PORTAL:
// 传送逻辑 - 添加冷却时间防止连续传送
const currentTime = Date.now();
if (this.linkedPortal && this.linkedPortal.active && currentTime - this.lastTeleportTime > 1000) {
// 创建传送效果 (蓝色爆炸)
gameState.explosions.push(new Explosion(this.x + this.width / 2, this.y + this.height / 2, true));
gameState.explosions.push(new Explosion(this.linkedPortal.x + this.linkedPortal.width / 2, this.linkedPortal.y + this.linkedPortal.height / 2, true));
// 播放传送音效
// playSound('hit-wall-sound');
// 传送坦克
const newX = this.linkedPortal.x + (this.linkedPortal.width - tank.width) / 2;
const newY = this.linkedPortal.y + (this.linkedPortal.height - tank.height) / 2;
tank.x = newX;
tank.y = newY;
// 增加使用次数
this.uses++;
this.linkedPortal.uses++;
// 设置冷却时间
this.lastTeleportTime = currentTime;
this.linkedPortal.lastTeleportTime = currentTime;
// 检查是否达到使用次数上限
if (this.uses >= 10) {
this.active = false;
gameState.explosions.push(new Explosion(this.x + this.width / 2, this.y + this.height / 2, true));
}
if (this.linkedPortal.uses >= 10) {
this.linkedPortal.active = false;
gameState.explosions.push(new Explosion(this.linkedPortal.x + this.linkedPortal.width / 2, this.linkedPortal.y + this.linkedPortal.height / 2, true));
}
}
if (this.uses >= 10) {
this.active = false;
gameState.explosions.push(new Explosion(this.x + this.width / 2, this.y + this.height / 2, true));
}
if (this.linkedPortal.uses >= 10) {
this.linkedPortal.active = false;
gameState.explosions.push(new Explosion(this.linkedPortal.x + this.linkedPortal.width / 2, this.linkedPortal.y + this.linkedPortal.height / 2, true));
}
break;
case TRAP_TYPES.OIL_BARREL:
this.active = false;
// 创建更大的爆炸效果
gameState.explosions.push(new Explosion(this.x + this.width / 2, this.y + this.height / 2, false, this.explosionRadius));
// 播放爆炸音效
// playSound('hit-enemy-sound');
// 对范围内的坦克造成伤害
this.damageNearbyTanks();
break;
case TRAP_TYPES.LASER_NET:
if (!this.isActivated && tank.isPlayer) {
this.isActivated = true;
this.activatedTime = Date.now();
// 使玩家坦克无法移动3秒
tank.stunned = true;
tank.stunEndTime = Date.now() + this.stunDuration;
// 3秒后激光网消失
setTimeout(() => {
this.active = false;
}, this.stunDuration);
}
break;
}
return true;
}
damageNearbyTanks() {
const centerX = this.x + this.width / 2;
const centerY = this.y + this.height / 2;
// 伤害玩家
const player = gameState.player;
if (player) {
const dx = player.x + player.width / 2 - centerX;
const dy = player.y + player.height / 2 - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < this.explosionRadius) {
if (player.shield) {
player.shield = false;
} else {
gameState.lives--;
if (gameState.lives <= 0) {
endGame(false);
} else {
spawnPlayer();
}
}
}
}
// 伤害敌人
for (let i = gameState.enemies.length - 1; i >= 0; i--) {
const enemy = gameState.enemies[i];
const dx = enemy.x + enemy.width / 2 - centerX;
const dy = enemy.y + enemy.height / 2 - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < this.explosionRadius) {
gameState.enemies.splice(i, 1);
gameState.score += 100;
}
}
}
linkPortal(portal) {
this.linkedPortal = portal;
portal.linkedPortal = this;
}
}
// Game functions
// 添加暂停状态变量
gameState.isPaused = false;
// 添加颜色选择器和坦克预览
const colorPicker = document.getElementById('tank-color-picker');
const previewCanvas = document.getElementById('tank-preview');
const previewCtx = previewCanvas.getContext('2d');
// 绘制预览坦克的函数
function drawPreviewTank(color) {
previewCtx.clearRect(0, 0, previewCanvas.width, previewCanvas.height);
const tankSize = 25;
const x = previewCanvas.width / 2;
const y = previewCanvas.height / 2;
const turretColor = darkenColor(color, 20);
const trackColor = '#212121';
const trackDetailColor = '#424242';
previewCtx.save();
previewCtx.translate(x, y);
// 绘制履带
previewCtx.fillStyle = trackColor;
previewCtx.fillRect(-tankSize / 2 - 4, -tankSize / 2, 4, tankSize);
previewCtx.fillRect(tankSize / 2, -tankSize / 2, 4, tankSize);
// 履带细节
previewCtx.fillStyle = trackDetailColor;
previewCtx.fillRect(-tankSize / 2 - 4, -tankSize / 2, 4, 6);
previewCtx.fillRect(-tankSize / 2 - 4, tankSize / 2 - 6, 4, 6);
previewCtx.fillRect(tankSize / 2, -tankSize / 2, 4, 6);
previewCtx.fillRect(tankSize / 2, tankSize / 2 - 6, 4, 6);
// 创建车身渐变效果
const bodyGradient = previewCtx.createLinearGradient(-tankSize / 2, -tankSize / 2, tankSize / 2, tankSize / 2);
bodyGradient.addColorStop(0, lightenColor(color, 15));
bodyGradient.addColorStop(1, darkenColor(color, 15));
// 绘制坦克车身
previewCtx.fillStyle = bodyGradient;
previewCtx.fillRect(-tankSize / 2, -tankSize / 2, tankSize, tankSize);
// 绘制车身装甲细节
previewCtx.strokeStyle = darkenColor(color, 30);
previewCtx.lineWidth = 2;
previewCtx.strokeRect(-tankSize / 2 + 4, -tankSize / 2 + 4, tankSize - 8, tankSize - 8);
previewCtx.strokeRect(-tankSize / 2 + 8, -tankSize / 2 + 8, tankSize - 16, tankSize - 16);
// 绘制装甲板连接点
previewCtx.fillStyle = '#333';
previewCtx.beginPath();
previewCtx.arc(-tankSize / 2 + 8, -tankSize / 2 + 8, 2, 0, Math.PI * 2);
previewCtx.arc(tankSize / 2 - 8, -tankSize / 2 + 8, 2, 0, Math.PI * 2);
previewCtx.arc(-tankSize / 2 + 8, tankSize / 2 - 8, 2, 0, Math.PI * 2);
previewCtx.arc(tankSize / 2 - 8, tankSize / 2 - 8, 2, 0, Math.PI * 2);
previewCtx.fill();
// 绘制坦克炮塔
const turretGradient = previewCtx.createRadialGradient(0, 0, 0, 0, 0, tankSize / 4);
turretGradient.addColorStop(0, lightenColor(turretColor, 10));
turretGradient.addColorStop(1, darkenColor(turretColor, 20));
previewCtx.fillStyle = turretGradient;
previewCtx.beginPath();
previewCtx.arc(0, 0, tankSize / 4, 0, Math.PI * 2);
previewCtx.fill();
// 绘制炮塔细节
previewCtx.fillStyle = '#444';
previewCtx.beginPath();
previewCtx.arc(0, 0, tankSize / 8, 0, Math.PI * 2);
previewCtx.fill();
// 绘制瞄准镜
previewCtx.fillStyle = '#000';
previewCtx.fillRect(-3, -tankSize / 2 - 16, 6, 4);
previewCtx.fillStyle = '#0ff';
previewCtx.fillRect(-2, -tankSize / 2 - 15, 4, 2);
// 绘制炮管
previewCtx.fillStyle = trackColor;
previewCtx.fillRect(-3, -tankSize / 2 - 12, 6, 14);
previewCtx.fillStyle = darkenColor(trackColor, 20);
previewCtx.fillRect(-2, -tankSize / 2 - 18, 4, 6);
// 绘制炮管散热孔
previewCtx.fillStyle = '#555';
previewCtx.fillRect(-1, -tankSize / 2 - 10, 2, 2);
previewCtx.fillRect(-1, -tankSize / 2 - 6, 2, 2);
previewCtx.fillRect(-1, -tankSize / 2 - 2, 2, 2);
// 添加侧装甲
previewCtx.fillStyle = darkenColor(color, 10);
previewCtx.fillRect(-tankSize / 2 - 6, -tankSize / 2 + 8, 2, tankSize - 16);
previewCtx.fillRect(tankSize / 2 + 4, -tankSize / 2 + 8, 2, tankSize - 16);
previewCtx.restore();
}
// 颜色明暗处理函数
function darkenColor(color, percent) {
let r = parseInt(color.substring(1, 3), 16);
let g = parseInt(color.substring(3, 5), 16);
let b = parseInt(color.substring(5, 7), 16);
r = Math.max(0, Math.floor(r * (1 - percent / 100)));
g = Math.max(0, Math.floor(g * (1 - percent / 100)));
b = Math.max(0, Math.floor(b * (1 - percent / 100)));
const toHex = (c) => {
const hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return '#' + toHex(r) + toHex(g) + toHex(b);
}
function lightenColor(color, percent) {
let r = parseInt(color.substring(1, 3), 16);
let g = parseInt(color.substring(3, 5), 16);
let b = parseInt(color.substring(5, 7), 16);
r = Math.min(255, Math.floor(r * (1 + percent / 100)));
g = Math.min(255, Math.floor(g * (1 + percent / 100)));
b = Math.min(255, Math.floor(b * (1 + percent / 100)));
const toHex = (c) => {
const hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return '#' + toHex(r) + toHex(g) + toHex(b);
}
// 初始化预览坦克
drawPreviewTank(colorPicker.value);
// 颜色选择器事件监听
colorPicker.addEventListener('input', function () {
drawPreviewTank(this.value);
});
function startGame() {
// 根据难度调整敌人数量
const baseEnemies = 5;
enemyCount = baseEnemies * gameDifficulty;
// 获取玩家名字
const playerNameInput = document.getElementById('player-name');
gameState.playerName = playerNameInput.value.trim() || '玩家';
// 获取坦克颜色
gameState.playerColor = colorPicker.value;
gameState.isPlaying = true;
gameState.isPaused = false;
gameState.score = 0;
// 保持当前关卡重新开始
// gameState.level = 1;
gameState.playerBulletDamage = 1; // 玩家子弹初始伤害
// 根据游戏模式设置初始生命数
gameState.lives = gameState.gameMode === 'extreme' ? 1 : 3;
startScreen.style.display = 'none';
gameOverScreen.style.display = 'none';
winScreen.style.display = 'none';
// 创建剧情显示元素
const storyElement = document.createElement('div');
storyElement.id = 'game-story';
storyElement.style.position = 'fixed';
storyElement.style.top = '50%';
storyElement.style.left = '50%';
storyElement.style.transform = 'translate(-50%, -50%)';
storyElement.style.color = 'white';
storyElement.style.fontSize = '24px';
storyElement.style.textAlign = 'center';
storyElement.style.padding = '20px';
storyElement.style.maxWidth = '600px';
storyElement.style.backgroundColor = 'rgba(0,0,0,0.7)';
storyElement.style.borderRadius = '10px';
storyElement.style.zIndex = '1000';
storyElement.style.opacity = '1';
storyElement.style.transition = 'opacity 1s linear';
storyElement.innerHTML = `
<h2>${translations[currentLanguage]['story-title']}</h2>
<p>${translations[currentLanguage]['story-p1']}</p>
<p>${translations[currentLanguage]['story-p2']}</p>
<p>${translations[currentLanguage]['story-p3']}</p>
<button id="skip-story" style="margin-top: 20px; padding: 10px 20px; font-size: 18px; background-color: #ff4500; color: white; border: none; border-radius: 5px; cursor: pointer;">${translations[currentLanguage]['skip-story']}</button>
`;
document.body.appendChild(storyElement);
// 添加跳过按钮点击事件
const skipButton = document.getElementById('skip-story');
skipButton.addEventListener('click', function () {
storyElement.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(storyElement);
initLevel();
gameLoop();
}, 1000); // 等待透明度过渡完成
});
// 10秒后隐藏剧情并开始游戏
setTimeout(() => {
storyElement.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(storyElement);
initLevel();
gameLoop();
}, 1000); // 等待透明度过渡完成
}, 10000);
}
function initLevel() {
console.log('初始化关卡: ' + gameState.level);
gameState.enemies = [];
gameState.bullets = [];
gameState.walls = [];
spawnPlayer();
spawnEnemies();
generateMap();
// 初始化限时模式
if (gameState.timeLimitEnabled) {
gameState.timeRemaining = 120; // 2分钟
// 清除可能存在的旧定时器
if (gameState.timerInterval) {
clearInterval(gameState.timerInterval);
}
// 设置新定时器
gameState.timerInterval = setInterval(() => {
gameState.timeRemaining--;
if (gameState.timeRemaining <= 0) {
clearInterval(gameState.timerInterval);
endGame(false); // 时间到,游戏结束
}
}, 1000);
}
}
function spawnPlayer() {
gameState.player = new Tank(
CANVAS_WIDTH / 2 - TANK_SIZE / 2,
CANVAS_HEIGHT - TANK_SIZE * 2,
true
);
}
function spawnEnemies() {
// Spawn enemies based on level
// 调整敌人数量增长速度,避免过多敌人导致生成失败
if (gameState.level === 10) {
// 第十关生成陆地BOSS
const bossX = CANVAS_WIDTH / 2 - TANK_SIZE;
const bossY = 50;
const bossTank = new Tank(bossX, bossY);
// BOSS属性
bossTank.isBoss = true;
// 使用保存的BOSS血量(如果存在),否则使用默认值
bossTank.maxHealth = gameState.savedBossMaxHealth || 150;
bossTank.health = gameState.savedBossHealth || 150;
bossTank.width = TANK_SIZE * 1.5;
bossTank.height = TANK_SIZE * 1.5;
bossTank.speed = ENEMY_SPEED * 0.7;
bossTank.color = '#9C27B0'; // 紫色BOSS
bossTank.turretColor = '#7B1FA2';
bossTank.phase = 1; // 初始阶段
bossTank.shootPattern = 'single'; // 初始射击模式
bossTank.currentSkill = null; // 当前技能
bossTank.skillCooldown = 0; // 技能冷却
bossTank.isUsingMissileSkill = false; // 是否正在使用导弹技能
bossTank.missileSkillTimer = 0; // 导弹技能计时器
bossTank.turretExtension = 0; // 炮筒伸缩程度(0-1)
gameState.enemies.push(bossTank);
// 显示BOSS血条
showBossHealthBar(bossTank);
// 添加四个中型坦克
for (let i = 0; i < 4; i++) {
let x, y, validPosition;
do {
validPosition = true;
x = Math.random() * (CANVAS_WIDTH - TANK_SIZE);
y = Math.random() * (CANVAS_HEIGHT / 3 - TANK_SIZE) + 100;
// 检查与墙壁的碰撞
for (const wall of gameState.walls) {
if (x < wall.x + wall.width &&
x + TANK_SIZE > wall.x &&
y < wall.y + wall.height &&
y + TANK_SIZE > wall.y) {
validPosition = false;
break;
}
}
// 检查与其他敌人的碰撞
for (const enemy of gameState.enemies) {
if (x < enemy.x + enemy.width &&
x + TANK_SIZE > enemy.x &&
y < enemy.y + enemy.height &&
y + TANK_SIZE > enemy.y) {
validPosition = false;
break;
}
}
} while (!validPosition);
const mediumTank = new Tank(x, y);
mediumTank.isMedium = true;
mediumTank.health = 3; // 普通坦克的三倍
mediumTank.width = TANK_SIZE * 1.2;
mediumTank.height = TANK_SIZE * 1.2;
mediumTank.speed = ENEMY_SPEED * 0.9;
mediumTank.color = '#FF9800'; // 橙色中型坦克
mediumTank.turretColor = '#E65100';
gameState.enemies.push(mediumTank);
}
} else if (gameState.level === 20) {
// 第20关生成海域BOSS - 无畏舰
const battleshipX = CANVAS_WIDTH / 2 - TANK_SIZE * 1.2;
const battleshipY = 50;
const battleship = new Tank(battleshipX, battleshipY);
// 无畏舰属性
battleship.isBoss = true;
battleship.isBattleship = true; // 标记为海域BOSS
// 使用保存的BOSS血量(如果存在),否则使用默认值
battleship.maxHealth = gameState.savedBossMaxHealth || 300;
battleship.health = gameState.savedBossHealth || 300;
battleship.width = TANK_SIZE * 2.2;
battleship.height = TANK_SIZE * 1.4;
battleship.speed = ENEMY_SPEED * 0.75;
battleship.color = '#2C5282'; // 海军蓝军舰(与介绍图标一致)
battleship.turretColor = '#4A5568'; // 炮塔颜色(与介绍图标一致)
battleship.currentSkill = null; // 当前技能
battleship.skillCooldown = 0; // 技能冷却
battleship.currentDirectionIndex = 0; // 用于技能1的方向索引
battleship.isSummoning = false; // 是否正在召唤
gameState.enemies.push(battleship);
// 显示BOSS血条
showBossHealthBar(battleship);
// 添加五艘中型船只
for (let i = 0; i < 5; i++) {
let x, y, validPosition;
do {
validPosition = true;
x = Math.random() * (CANVAS_WIDTH - TANK_SIZE);
y = Math.random() * (CANVAS_HEIGHT / 3 - TANK_SIZE) + 100;
// 检查与墙壁的碰撞
for (const wall of gameState.walls) {
if (x < wall.x + wall.width &&
x + TANK_SIZE > wall.x &&
y < wall.y + wall.height &&
y + TANK_SIZE > wall.y) {
validPosition = false;
break;
}
}
// 检查与其他敌人的碰撞
for (const enemy of gameState.enemies) {
if (x < enemy.x + enemy.width &&
x + TANK_SIZE > enemy.x &&
y < enemy.y + enemy.height &&
y + TANK_SIZE > enemy.y) {
validPosition = false;
break;
}
}
} while (!validPosition);
const mediumTank = new Tank(x, y);
mediumTank.isMedium = true;
mediumTank.health = 3;
mediumTank.width = TANK_SIZE * 1.2;
mediumTank.height = TANK_SIZE * 1.2;
mediumTank.speed = ENEMY_SPEED * 0.9;
mediumTank.color = '#FFD700'; // 金色/黄色中型船只
mediumTank.turretColor = '#FFA500';
gameState.enemies.push(mediumTank);
}
} else {
// 判断是否为海域关卡
const isOceanLevel = gameState.level >= 11 && gameState.level <= 20;
// 根据难度调整敌人数量
let baseEnemyCount;
if (isOceanLevel) {
// 海域关卡:重新计算敌人数量,从第11关开始相当于陆地第1关+3个敌人
const oceanSubLevel = gameState.level - 10; // 海域子关卡 1-10
baseEnemyCount = 5 + Math.floor(oceanSubLevel * 1.8); // 更高的基础数量和增长速度
} else {
// 陆地关卡:原有公式
baseEnemyCount = 3 + Math.floor((gameState.level - 1) * 1.5);
}
const enemyCount = Math.min(Math.floor(baseEnemyCount * gameDifficulty), 15 * gameDifficulty);
for (let i = 0; i < enemyCount; i++) {
let x, y;
let validPosition = false;
// Find valid position (not overlapping with walls or other enemies)
// 优化:减少最大尝试次数并添加保底位置
let attempts = 0;
while (!validPosition && attempts < 50) {
attempts++;
x = Math.random() * (CANVAS_WIDTH - TANK_SIZE);
y = Math.random() * (CANVAS_HEIGHT / 2 - TANK_SIZE);
validPosition = true;
// 如果尝试次数过多,强制使用一个保底位置
if (attempts > 40) {
x = 50 + (i % 5) * 100;
y = 50 + Math.floor(i / 5) * 100;
validPosition = true;
break;
}
// 优化:只检查附近的墙壁(基于空间分区思想)
const wallGridSize = 100; // 网格大小
const xGrid = Math.floor(x / wallGridSize);
const yGrid = Math.floor(y / wallGridSize);
// 只检查当前网格和相邻网格的墙壁
for (const wall of gameState.walls) {
const wallXGrid = Math.floor(wall.x / wallGridSize);
const wallYGrid = Math.floor(wall.y / wallGridSize);
if (Math.abs(wallXGrid - xGrid) <= 1 && Math.abs(wallYGrid - yGrid) <= 1) {
if (
x < wall.x + wall.width &&
x + TANK_SIZE > wall.x &&
y < wall.y + wall.height &&
y + TANK_SIZE > wall.y
) {
validPosition = false;
break;
}
}
}
// Check against other enemies
if (validPosition) {
for (const enemy of gameState.enemies) {
if (
x < enemy.x + enemy.width &&
x + TANK_SIZE > enemy.x &&
y < enemy.y + enemy.height &&
y + TANK_SIZE > enemy.y
) {
validPosition = false;
break;
}
}
}
}
// 第7关及以后,前2辆敌人坦克改为中型坦克
if (gameState.level >= 7 && i < 2) {
const mediumTank = new Tank(x, y);
mediumTank.isMedium = true;
mediumTank.health = 3;
mediumTank.width = TANK_SIZE * 1.2;
mediumTank.height = TANK_SIZE * 1.2;
mediumTank.speed = ENEMY_SPEED * 0.9;
// 海域关卡中型船只使用黄色
if (gameState.level >= 11 && gameState.level <= 20) {
mediumTank.color = '#FFD700'; // 金色/黄色
mediumTank.turretColor = '#FFA500'; // 橙色炮塔
} else {
mediumTank.color = '#FF9800'; // 橙色中型坦克
mediumTank.turretColor = '#E65100';
}
gameState.enemies.push(mediumTank);
} else {
gameState.enemies.push(new Tank(x, y));
}
}
}
// 确保至少有一个敌人
if (gameState.enemies.length === 0) {
gameState.enemies.push(new Tank(50, 50));
}
}
function generateMap() {
// 清空地图元素
gameState.walls = [];
gameState.grasses = [];
gameState.props = [];
gameState.traps = [];
// Generate random walls
// 限制最大墙壁数量,防止敌人无法生成
const wallCount = Math.min(50 + gameState.level * 3, 80);
for (let i = 0; i < wallCount; i++) {
let x, y;
let validPosition = false;
// Find valid position (not overlapping with player or base area)
// 限制最大尝试次数,防止无限循环
let attempts = 0;
while (!validPosition && attempts < 100) {
attempts++;
x = Math.floor(Math.random() * (CANVAS_WIDTH / WALL_SIZE)) * WALL_SIZE;
y = Math.floor(Math.random() * (CANVAS_HEIGHT / WALL_SIZE)) * WALL_SIZE;
validPosition = true;
// Avoid player spawn area
if (
x > CANVAS_WIDTH / 2 - TANK_SIZE * 2 &&
x < CANVAS_WIDTH / 2 + TANK_SIZE * 2 &&
y > CANVAS_HEIGHT - TANK_SIZE * 4 &&
y < CANVAS_HEIGHT
) {
validPosition = false;
}
// Avoid base spawn area
if (
x > CANVAS_WIDTH / 2 - WALL_SIZE * 2 &&
x < CANVAS_WIDTH / 2 + WALL_SIZE * 2 &&
y > CANVAS_HEIGHT / 2 - WALL_SIZE * 2 &&
y < CANVAS_HEIGHT / 2 + WALL_SIZE * 2
) {
validPosition = false;
}
}
gameState.walls.push(new Wall(x, y));
// Generate random props (10% chance)
// 极限模式下不生成道具
if (gameState.gameMode !== 'extreme' && Math.random() < 0.1) {
const propTypes = Object.values(PROP_TYPES);
const randomType = propTypes[Math.floor(Math.random() * propTypes.length)];
gameState.props.push(new Prop(x, y, randomType));
}
// 生成随机机关
const rand = Math.random();
if (rand < 0.03) {
// 3% 的几率生成地雷
gameState.traps.push(new Trap(x, y, TRAP_TYPES.MINE));
} else if (rand < 0.05 && gameState.level < 11) {
// 2% 的几率生成油桶 - 海域关卡不生成
gameState.traps.push(new Trap(x, y, TRAP_TYPES.OIL_BARREL));
} else if (rand < 0.06 && gameState.level >= 11 && gameState.level <= 20) {
// 1% 的几率生成激光网 - 仅在海域关卡生成
gameState.traps.push(new Trap(x, y, TRAP_TYPES.LASER_NET));
}
}
// 单独生成传送门,确保成对出现 - 海域关卡不生成
if (gameState.level < 11) {
const portalCount = Math.floor(Math.random() * 3) + 1; // 1-3对传送门
const unpairedPortals = [];
for (let i = 0; i < portalCount * 2; i++) {
let x, y;
let validPosition = false;
let attempts = 0;
while (!validPosition && attempts < 100) {
attempts++;
x = Math.floor(Math.random() * (CANVAS_WIDTH / WALL_SIZE)) * WALL_SIZE;
y = Math.floor(Math.random() * (CANVAS_HEIGHT / WALL_SIZE)) * WALL_SIZE;
validPosition = true;
// 避免玩家出生区域
if (
x > CANVAS_WIDTH / 2 - TANK_SIZE * 2 &&
x < CANVAS_WIDTH / 2 + TANK_SIZE * 2 &&
y > CANVAS_HEIGHT - TANK_SIZE * 4 &&
y < CANVAS_HEIGHT
) {
validPosition = false;
}
// 避免中央核心区域
if (
x > CANVAS_WIDTH / 2 - WALL_SIZE * 2 &&
x < CANVAS_WIDTH / 2 + WALL_SIZE * 2 &&
y > CANVAS_HEIGHT / 2 - WALL_SIZE * 2 &&
y < CANVAS_HEIGHT / 2 + WALL_SIZE * 2
) {
validPosition = false;
}
// 避免与墙壁重叠
for (const wall of gameState.walls) {
if (
x < wall.x + wall.width &&
x + TRAP_SIZE > wall.x &&
y < wall.y + wall.height &&
y + TRAP_SIZE > wall.y
) {
validPosition = false;
break;
}
}
}
const portal = new Trap(x, y, TRAP_TYPES.PORTAL);
gameState.traps.push(portal);
unpairedPortals.push(portal);
}
// 配对传送门
for (let i = 0; i < unpairedPortals.length; i += 2) {
unpairedPortals[i].linkPortal(unpairedPortals[i + 1]);
}
}
// 生成炮台 - 海域关卡不生成炮台
gameState.turrets = [];
const isOceanLevel = gameState.level >= 11 && gameState.level <= 20;
if (!isOceanLevel) {
const turretCount = 2; // 陆地关卡2个炮台
for (let i = 0; i < turretCount; i++) {
let x, y;
let validPosition = false;
let attempts = 0;
while (!validPosition && attempts < 100) {
attempts++;
// 在墙上随机选择一个位置
if (gameState.walls.length > 0) {
const wall = gameState.walls[Math.floor(Math.random() * gameState.walls.length)];
// 在墙的周围生成炮台
const offsetX = (Math.random() - 0.5) * 40;
const offsetY = (Math.random() - 0.5) * 40;
x = wall.x + wall.width / 2 - 12.5 + offsetX;
y = wall.y + wall.height / 2 - 12.5 + offsetY;
// 确保在画布内
x = Math.max(0, Math.min(CANVAS_WIDTH - 25, x));
y = Math.max(0, Math.min(CANVAS_HEIGHT - 25, y));
validPosition = true;
} else {
// 如果没有墙,随机位置
x = Math.random() * (CANVAS_WIDTH - 50) + 25;
y = Math.random() * (CANVAS_HEIGHT - 50) + 25;
validPosition = true;
}
}
gameState.turrets.push(new Turret(x, y));
}
}
}
function restartGame() {
// 重置游戏状态
gameState.isPlaying = true;
gameState.isPaused = false;
gameState.score = 0;
// 保持当前关卡重新开始
// gameState.level = 1;
// 根据游戏模式设置初始生命数
gameState.lives = gameState.gameMode === 'extreme' ? 1 : 3;
gameState.enemies = [];
gameState.bullets = [];
gameState.walls = [];
gameState.props = [];
gameState.traps = [];
gameState.explosions = [];
gameState.smokes = []; // 清空烟雾数组
// 清除定时器
if (gameState.timerInterval) {
clearInterval(gameState.timerInterval);
gameState.timerInterval = null;
}
// 隐藏所有游戏结束相关界面
gameOverScreen.style.display = 'none';
winScreen.style.display = 'none';
// 隐藏BOSS血条
hideBossHealthBar();
// 初始化当前关卡
initLevel();
// 启动游戏循环
gameLoop();
}
// 显示BOSS血条
function showBossHealthBar(boss) {
const healthBarContainer = document.getElementById('boss-health-bar-container');
const healthFill = document.getElementById('boss-health-fill');
const healthText = document.getElementById('boss-health-text');
healthFill.style.width = '100%';
healthText.textContent = `${boss.health}/${boss.maxHealth}`;
healthBarContainer.style.display = 'block';
}
// 更新BOSS血条
function updateBossHealthBar(boss) {
const healthBarContainer = document.getElementById('boss-health-bar-container');
const healthFill = document.getElementById('boss-health-fill');
const healthText = document.getElementById('boss-health-text');
if (healthBarContainer.style.display === 'block' && boss) {
// 如果BOSS死亡,隐藏血条
if (boss.health <= 0) {
hideBossHealthBar();
return;
}
const percentage = (boss.health / boss.maxHealth) * 100;
healthFill.style.width = `${percentage}%`;
healthText.textContent = `${boss.health}/${boss.maxHealth}`;
}
}
// 隐藏BOSS血条
function hideBossHealthBar() {
const healthBarContainer = document.getElementById('boss-health-bar-container');
healthBarContainer.style.display = 'none';
}
function nextLevel() {
// 确保关卡不超过20
if (gameState.level < 20) {
gameState.level = gameState.level + 1;
console.log('进入下一关: ' + gameState.level);
initLevel();
winScreen.style.display = 'none';
gameState.isPlaying = true;
} else {
// 已通关所有关卡
winScreen.style.display = 'flex';
if (currentLanguage === 'zh') {
document.getElementById('win-message').textContent = '恭喜你通关所有20关!游戏通关!';
document.getElementById('next-level-button').textContent = '重新开始';
} else {
document.getElementById('win-message').textContent = 'Congratulations! You completed all 20 levels! Game Complete!';
document.getElementById('next-level-button').textContent = 'Restart';
}
// 播放通关音效
// playSound('game-complete-sound');
}
}
function updateTotalScoreDisplay() {
const totalScore = localStorage.getItem('tankBattleTotalScore') || '0';
document.getElementById('total-score-value').textContent = totalScore;
}
function endGame(isVictory) {
gameState.isPlaying = false;
// 累加总分数
const totalScore = parseInt(localStorage.getItem('tankBattleTotalScore') || '0');
const newTotalScore = totalScore + gameState.score;
localStorage.setItem('tankBattleTotalScore', newTotalScore.toString());
updateTotalScoreDisplay();
// 清除定时器
if (gameState.timerInterval) {
clearInterval(gameState.timerInterval);
gameState.timerInterval = null;
}
if (isVictory) {
// 解锁下一关
saveUnlockedLevel(gameState.level + 1);
// 播放关卡完成音效
// playSound('level-up-sound');
winScreen.style.display = 'flex';
if (gameState.level < 20) {
// 显示当前关卡和下一关信息
let levelName = '';
let nextLevelName = '';
if (gameState.level <= 10) {
levelName = translations[currentLanguage]['land-world'];
if (gameState.level + 1 <= 10) {
nextLevelName = translations[currentLanguage]['land-world'];
} else {
nextLevelName = translations[currentLanguage]['ocean-world'];
}
} else {
levelName = translations[currentLanguage]['ocean-world'];
nextLevelName = translations[currentLanguage]['ocean-world'];
}
// 第10关是BOSS关,特殊提示
if (gameState.level === 10) {
if (currentLanguage === 'zh') {
document.getElementById('win-message').textContent = `第 ${gameState.level} 关BOSS已击败!进入海域关卡!`;
} else {
document.getElementById('win-message').textContent = `Level ${gameState.level} BOSS Defeated! Entering Ocean Levels!`;
}
} else {
if (currentLanguage === 'zh') {
document.getElementById('win-message').textContent = `第 ${gameState.level} 关完成! (${levelName})`;
} else {
document.getElementById('win-message').textContent = `Level ${gameState.level} Complete! (${levelName})`;
}
}
if (currentLanguage === 'zh') {
document.getElementById('next-level-button').textContent = `下一关 (${gameState.level + 1} - ${nextLevelName})`;
} else {
document.getElementById('next-level-button').textContent = `Next Level (${gameState.level + 1} - ${nextLevelName})`;
}
// 添加关卡选择按钮
const levelSelectBtn = document.getElementById('level-select-button');
if (!levelSelectBtn) {
const btn = document.createElement('button');
btn.id = 'level-select-button';
btn.style.marginTop = '15px';
btn.style.padding = '10px 20px';
btn.style.fontSize = '18px';
btn.style.backgroundColor = '#2196F3';
btn.style.color = 'white';
btn.style.border = 'none';
btn.style.borderRadius = '5px';
btn.style.cursor = 'pointer';
btn.addEventListener('click', function () {
winScreen.style.display = 'none';
startScreen.style.display = 'flex';
// 显示关卡选择弹窗
document.getElementById('level-select-modal').style.display = 'flex';
});
winScreen.appendChild(btn);
}
// 更新关卡选择按钮的文本
if (levelSelectBtn) {
if (currentLanguage === 'zh') {
levelSelectBtn.textContent = '关卡选择';
} else {
levelSelectBtn.textContent = 'Level Select';
}
levelSelectBtn.style.display = 'block';
}
} else {
// 显示通关信息(第20关)
if (currentLanguage === 'zh') {
document.getElementById('win-message').textContent = '恭喜你通关所有20关!游戏通关!';
document.getElementById('next-level-button').textContent = '重新开始';
} else {
document.getElementById('win-message').textContent = 'Congratulations! You completed all 20 levels! Game Complete!';
document.getElementById('next-level-button').textContent = 'Restart';
}
// 隐藏关卡选择按钮
const levelSelectBtn = document.getElementById('level-select-button');
if (levelSelectBtn) {
levelSelectBtn.style.display = 'none';
}
// 播放通关音效
// playSound('game-complete-sound');
}
} else {
gameOverScreen.style.display = 'flex';
// 播放游戏结束音效
// playSound('player-death-sound');
}
}
function checkLevelComplete() {
if (gameState.enemies.length === 0) {
endGame(true);
}
}
function update() {
if (gameState.isPaused) return;
if (!gameState.isPlaying) return;
// 更新大招状态
updateUltimate();
// 处理海浪事件(仅在海域关卡)
const isOceanLevel = gameState.level >= 11 && gameState.level <= 20;
if (isOceanLevel) {
// 减少海浪冷却时间
if (gameState.waveEventCooldown > 0) {
gameState.waveEventCooldown--;
}
// 10%概率触发海浪事件
if (gameState.waveEventCooldown <= 0 && !gameState.waveEventActive) {
if (Math.random() < 0.0016666) { // 约10%概率(每帧0.166666%,10秒左右触发)
triggerWaveEvent();
}
}
// 更新海浪
for (let i = gameState.waves.length - 1; i >= 0; i--) {
const wave = gameState.waves[i];
wave.update();
// 检查海浪是否出界
if (wave.x > CANVAS_WIDTH) {
gameState.waves.splice(i, 1);
continue;
}
// 检查与玩家的碰撞
if (wave.checkCollision(gameState.player)) {
// 玩家受到伤害
if (!gameState.player.shield) {
gameState.lives--;
if (gameState.lives <= 0) {
gameOver();
}
}
// 移除这个海浪,避免重复伤害
gameState.waves.splice(i, 1);
// 在玩家位置创建爆炸效果
gameState.explosions.push(new Explosion(
gameState.player.x + gameState.player.width / 2,
gameState.player.y + gameState.player.height / 2,
false,
20
));
}
// 检查与敌人的碰撞
for (let j = gameState.enemies.length - 1; j >= 0; j--) {
const enemy = gameState.enemies[j];
if (wave.checkCollision(enemy)) {
// 敌人受到伤害(BOSS血量较高)
if (enemy.isBoss) {
enemy.health--;
} else {
enemy.health -= 2;
}
if (enemy.health <= 0) {
const score = enemy.isBoss ? 500 : 100;
gameState.scoreTexts.push(new ScoreText(
enemy.x + enemy.width / 2,
enemy.y + enemy.height / 2,
score
));
gameState.enemies.splice(j, 1);
gameState.score += score;
}
// 在敌人位置创建爆炸效果
gameState.explosions.push(new Explosion(
enemy.x + enemy.width / 2,
enemy.y + enemy.height / 2,
false,
20
));
}
}
}
// 检查是否所有海浪都已离开屏幕
if (gameState.waveEventActive && gameState.waves.length === 0) {
gameState.waveEventActive = false;
}
}
// 更新BOSS死亡动画
updateBossDeathAnimation();
// 如果正在播放BOSS死亡动画,阻止玩家移动
if (gameState.bossDeathAnimation) {
// 清空玩家移动状态
gameState.player.keys = {};
}
// 生成空投(极限模式下不生成道具)
if (gameState.gameMode !== 'extreme') {
const now = Date.now();
if (now - gameState.lastAirDropTime > gameState.airDropInterval) {
// 随机X坐标,确保空投箱完全在画布内
const x = Math.random() * CANVAS_WIDTH; // 允许空投在任意X位置生成
gameState.airDrops.push(new AirDrop(x, -30));
gameState.lastAirDropTime = now;
// 随机调整下次空投时间(10-20秒)
gameState.airDropInterval = 10000; // 固定每10秒生成一个空投
}
}
// 更新空投箱
for (let i = gameState.airDrops.length - 1; i >= 0; i--) {
const airDrop = gameState.airDrops[i];
if (airDrop.update()) {
gameState.airDrops.splice(i, 1);
}
}
// Update player
gameState.player.update();
// Update traps
for (let i = gameState.traps.length - 1; i >= 0; i--) {
const trap = gameState.traps[i];
if (trap.update()) {
gameState.traps.splice(i, 1);
continue;
}
// Check collision with player
if (
trap.active &&
gameState.player.x < trap.x + trap.width &&
gameState.player.x + gameState.player.width > trap.x &&
gameState.player.y < trap.y + trap.height &&
gameState.player.y + gameState.player.height > trap.y
) {
trap.trigger(gameState.player);
}
// Check collision with enemies
for (let j = gameState.enemies.length - 1; j >= 0; j--) {
const enemy = gameState.enemies[j];
if (
trap.active &&
enemy.x < trap.x + trap.width &&
enemy.x + enemy.width > trap.x &&
enemy.y < trap.y + trap.height &&
enemy.y + enemy.height > trap.y
) {
trap.trigger(enemy);
}
}
}
// Update explosions
for (let i = gameState.explosions.length - 1; i >= 0; i--) {
const explosion = gameState.explosions[i];
if (explosion.update()) {
gameState.explosions.splice(i, 1);
}
}
// Update enemy
gameState.enemies.forEach(enemy => enemy.update());
// Update bullets
for (let i = gameState.bullets.length - 1; i >= 0; i--) {
const bullet = gameState.bullets[i];
if (bullet instanceof Laser || bullet instanceof Missile) {
if (bullet.update()) {
gameState.bullets.splice(i, 1);
}
} else {
const shouldRemove = bullet.update();
if (shouldRemove || bullet.checkCollision()) {
gameState.bullets.splice(i, 1);
}
}
}
// Check level complete (不在播放BOSS死亡动画时)
if (!gameState.bossDeathAnimation) {
checkLevelComplete();
}
// Update player prop particles
for (let i = gameState.playerPropParticles.length - 1; i >= 0; i--) {
if (gameState.playerPropParticles[i].update()) {
gameState.playerPropParticles.splice(i, 1);
}
}
// Update player speed particles
for (let i = gameState.playerSpeedParticles.length - 1; i >= 0; i--) {
if (gameState.playerSpeedParticles[i].update()) {
gameState.playerSpeedParticles.splice(i, 1);
}
}
// Update enemy slow particles
for (let i = gameState.enemySlowParticles.length - 1; i >= 0; i--) {
if (gameState.enemySlowParticles[i].update()) {
gameState.enemySlowParticles.splice(i, 1);
}
}
// Update turrets
for (let i = gameState.turrets.length - 1; i >= 0; i--) {
const turret = gameState.turrets[i];
turret.update();
if (!turret.active) {
gameState.turrets.splice(i, 1);
}
}
// Update props
for (let i = gameState.props.length - 1; i >= 0; i--) {
const prop = gameState.props[i];
if (prop.update()) {
gameState.props.splice(i, 1);
continue;
}
// Check collision with player
if (
prop.x < gameState.player.x + gameState.player.width &&
prop.x + prop.width > gameState.player.x &&
prop.y < gameState.player.y + gameState.player.height &&
prop.y + prop.height > gameState.player.y
) {
// 创建道具粒子效果
createPropParticles(gameState.player, prop.type);
// Apply prop effect
switch (prop.type) {
case PROP_TYPES.EXTRA_LIFE:
gameState.lives++;
break;
case PROP_TYPES.SPEED_BOOST:
gameState.player.speed *= 1.5;
gameState.isSpeedBoosted = true;
gameState.activeProps[PROP_TYPES.SPEED_BOOST] = { startTime: Date.now(), duration: 5000 };
setTimeout(() => {
gameState.player.speed /= 1.5;
gameState.isSpeedBoosted = false;
delete gameState.activeProps[PROP_TYPES.SPEED_BOOST];
// 清除剩余的加速粒子
gameState.playerSpeedParticles = [];
}, 5000);
break;
case PROP_TYPES.SHIELD:
gameState.player.shield = true;
gameState.activeProps[PROP_TYPES.SHIELD] = { startTime: Date.now(), duration: 5000 };
setTimeout(() => {
gameState.player.shield = false;
delete gameState.activeProps[PROP_TYPES.SHIELD];
}, 5000);
break;
case PROP_TYPES.TIME_FREEZE:
// 冻结所有敌人5秒
gameState.activeProps[PROP_TYPES.TIME_FREEZE] = { startTime: Date.now(), duration: 5000 };
gameState.enemies.forEach(enemy => {
enemy.isFrozen = true;
clearTimeout(enemy.freezeTimeout);
enemy.freezeTimeout = setTimeout(() => {
enemy.isFrozen = false;
delete gameState.activeProps[PROP_TYPES.TIME_FREEZE];
}, 5000);
});
break;
case PROP_TYPES.TIME_SLOW:
// 减速所有敌人8秒
gameState.activeProps[PROP_TYPES.TIME_SLOW] = { startTime: Date.now(), duration: 8000 };
gameState.enemies.forEach(enemy => {
enemy.speedMultiplier = 0.5;
enemy.isSlowed = true;
clearTimeout(enemy.slowTimeout);
enemy.slowTimeout = setTimeout(() => {
enemy.speedMultiplier = 1;
enemy.isSlowed = false;
}, 8000);
});
break;
}
gameState.props.splice(i, 1);
}
}
// Update score texts
for (let i = gameState.scoreTexts.length - 1; i >= 0; i--) {
const scoreText = gameState.scoreTexts[i];
scoreText.update();
if (scoreText.opacity <= 0) {
gameState.scoreTexts.splice(i, 1);
}
}
}
function draw() {
if (gameState.isPaused) {
// 绘制半透明暂停遮罩,允许查看背后战况
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
ctx.fillStyle = '#fff';
ctx.font = '40px Arial';
ctx.textAlign = 'center';
ctx.fillText('游戏暂停', CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2 - 50);
return;
}
// 检查是否是海域关卡(11-20关)
const isOceanLevel = gameState.level >= 11 && gameState.level <= 20;
if (isOceanLevel) {
// 海域地图背景 - 明亮的大海蓝
const oceanGradient = ctx.createLinearGradient(0, 0, 0, CANVAS_HEIGHT);
oceanGradient.addColorStop(0, '#4FC3F7'); // 浅天蓝
oceanGradient.addColorStop(0.3, '#29B6F6'); // 明亮蓝
oceanGradient.addColorStop(0.7, '#03A9F4'); // 中等蓝
oceanGradient.addColorStop(1, '#0288D1'); // 稍深蓝
ctx.fillStyle = oceanGradient;
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// 添加轻微波浪效果
const waveTime = Date.now() / 2000;
ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
ctx.lineWidth = 1;
// 水平波浪线条
for (let i = 0; i < 5; i++) {
const baseY = 80 + i * 120;
ctx.beginPath();
for (let x = 0; x <= CANVAS_WIDTH; x += 5) {
const waveOffset = Math.sin(waveTime + x * 0.01 + i * 0.5) * 8;
const y = baseY + waveOffset;
if (x === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
}
// 水面反光小点
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
for (let i = 0; i < 30; i++) {
const sparkleX = (Math.sin(waveTime * 2 + i * 0.7) * 0.5 + 0.5) * CANVAS_WIDTH;
const sparkleY = (Math.cos(waveTime * 1.5 + i * 0.9) * 0.5 + 0.5) * CANVAS_HEIGHT;
const sparkleSize = 1 + Math.sin(waveTime * 3 + i) * 0.5;
ctx.beginPath();
ctx.arc(sparkleX, sparkleY, sparkleSize, 0, Math.PI * 2);
ctx.fill();
}
} else {
// 普通陆地地图
const groundColor = '#333333';
const textureColor = '#444444';
ctx.fillStyle = groundColor;
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// 优化:只在需要时重新生成纹理
if (!gameState.groundTextureCreated) {
// Add texture
ctx.fillStyle = textureColor;
const gridSize = 40;
for (let x = 0; x < CANVAS_WIDTH; x += gridSize) {
for (let y = 0; y < CANVAS_HEIGHT; y += gridSize) {
if (Math.random() > 0.7) {
ctx.fillRect(x, y, 2, 2);
}
}
}
gameState.groundTextureCreated = true;
}
}
if (!gameState.isPlaying) return;
// Draw walls
gameState.walls.forEach(wall => wall.draw());
// Draw traps
gameState.traps.forEach(trap => trap.draw());
// Draw player
gameState.player.draw();
// Draw enemies
gameState.enemies.forEach(enemy => enemy.draw());
// Draw turrets
gameState.turrets.forEach(turret => turret.draw());
// Draw bullets
gameState.bullets.forEach(bullet => bullet.draw());
// Draw explosions
gameState.explosions.forEach(explosion => explosion.draw());
// Draw waves
gameState.waves.forEach(wave => wave.draw());
// Draw air drops
gameState.airDrops.forEach(airDrop => airDrop.draw());
// Draw props
gameState.props.forEach(prop => prop.draw());
// Draw player prop particles
gameState.playerPropParticles.forEach(particle => particle.draw());
// Draw player speed particles
gameState.playerSpeedParticles.forEach(particle => particle.draw());
// Draw enemy slow particles
gameState.enemySlowParticles.forEach(particle => particle.draw());
// 绘制大招粒子
gameState.ultimateParticles.forEach(particle => {
ctx.save();
ctx.globalAlpha = particle.life / particle.maxLife;
ctx.fillStyle = particle.color;
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
});
// Draw HUD
ctx.fillStyle = '#fff';
ctx.font = '20px Arial';
ctx.textAlign = 'left';
ctx.fillText(`${translations[currentLanguage]['score']}: ${gameState.score}`, 10, 20);
// 显示关卡和海域提示
ctx.fillStyle = '#fff'; // 白色
let levelDisplay;
if (gameState.level >= 11 && gameState.level <= 20) {
// 海域关:2-1 格式
const subLevel = gameState.level - 10;
levelDisplay = `2-${subLevel}`;
} else {
// 陆地关:1-1 格式
levelDisplay = `1-${gameState.level}`;
}
ctx.fillText(`${translations[currentLanguage]['level']}: ${levelDisplay}`, 10, 40);
ctx.fillStyle = '#fff';
ctx.fillText(`HP: ${gameState.lives}`, 10, 60);
// 显示倒计时
if (gameState.timeLimitEnabled) {
const minutes = Math.floor(gameState.timeRemaining / 60);
const seconds = gameState.timeRemaining % 60;
const timeString = `${minutes}:${seconds.toString().padStart(2, '0')}`;
ctx.fillText(`${translations[currentLanguage]['time']}: ${timeString}`, CANVAS_WIDTH / 2 - 80, 20);
}
// 显示激活道具时间(右上角)
ctx.textAlign = 'right';
const propNames = {
[PROP_TYPES.SPEED_BOOST]: '加速',
[PROP_TYPES.SHIELD]: '护盾',
[PROP_TYPES.TIME_FREEZE]: '冻结',
[PROP_TYPES.TIME_SLOW]: '减速'
};
const propColors = {
[PROP_TYPES.SPEED_BOOST]: '#FFC107', // 黄色
[PROP_TYPES.SHIELD]: '#2196F3', // 蓝色
[PROP_TYPES.TIME_FREEZE]: '#00BCD4', // 青色
[PROP_TYPES.TIME_SLOW]: '#9C27B0' // 紫色
};
let propIndex = 0;
for (const [propType, propData] of Object.entries(gameState.activeProps)) {
const elapsed = Date.now() - propData.startTime;
const remaining = Math.max(0, Math.ceil((propData.duration - elapsed) / 1000));
if (remaining > 0) {
ctx.fillStyle = propColors[propType] || '#fff';
ctx.fillText(`${propNames[propType]}: ${remaining}s`, CANVAS_WIDTH - 10, 20 + propIndex * 25);
propIndex++;
}
}
// 显示砖块数量、激光炮、连发和大招(右下角)
ctx.textAlign = 'right';
let yOffset = 0;
// 显示大招状态
if (gameState.ultimateReady) {
ctx.fillStyle = '#FFD700';
ctx.fillText(`大招: 就绪 (X)`, CANVAS_WIDTH - 10, CANVAS_HEIGHT - 10 - yOffset);
yOffset += 20;
} else if (gameState.ultimateCooldown > 0) {
const cooldownSeconds = Math.ceil(gameState.ultimateCooldown / 60);
ctx.fillStyle = '#888';
ctx.fillText(`大招: ${cooldownSeconds}秒`, CANVAS_WIDTH - 10, CANVAS_HEIGHT - 10 - yOffset);
yOffset += 20;
} else if (gameState.ultimateState !== 'idle') {
ctx.fillStyle = '#FF69B4';
ctx.fillText(`大招: 施放中...`, CANVAS_WIDTH - 10, CANVAS_HEIGHT - 10 - yOffset);
yOffset += 20;
}
// 显示激光炮状态
if (gameState.laserLevel > 0) {
const laserChance = gameState.laserLevel * 10 + 20;
ctx.fillStyle = gameState.laserEnabled ? '#00FFFF' : '#888';
ctx.fillText(`激光炮: ${laserChance}% (R)`, CANVAS_WIDTH - 10, CANVAS_HEIGHT - 10 - yOffset);
yOffset += 20;
}
// 显示连发状态
if (gameState.rapidFireLevel > 0) {
const fireRateBonus = gameState.rapidFireLevel * 5 + 15;
ctx.fillStyle = '#FF6347';
ctx.fillText(`连发: +${fireRateBonus}%`, CANVAS_WIDTH - 10, CANVAS_HEIGHT - 10 - yOffset);
yOffset += 20;
}
// 显示砖块数量
if (gameState.level >= 11 && gameState.level <= 20) {
// 海域关:显示禁用
ctx.fillStyle = '#888';
ctx.fillText(`砖块: 禁用`, CANVAS_WIDTH - 10, CANVAS_HEIGHT - 10 - yOffset);
} else if (gameState.bricks > 0) {
// 陆地关:显示数量(只有砖块>0时显示)
ctx.fillStyle = '#DEB887';
ctx.fillText(`砖块: ${gameState.bricks} (E)`, CANVAS_WIDTH - 10, CANVAS_HEIGHT - 10 - yOffset);
}
ctx.textAlign = 'left';
// 绘制得分字幕(在所有内容之上,除了BOSS死亡动画)
gameState.scoreTexts.forEach(scoreText => scoreText.draw());
// 绘制BOSS死亡动画(在所有内容之上)
drawBossDeathAnimation();
}
function gameLoop() {
update();
draw();
// 更新并绘制烟雾 (在其他元素之后绘制)
for (let i = gameState.smokes.length - 1; i >= 0; i--) {
const smoke = gameState.smokes[i];
smoke.update();
smoke.draw();
if (smoke.opacity <= 0) {
gameState.smokes.splice(i, 1);
}
}
requestAnimationFrame(gameLoop);
}
// 暂停游戏函数
function resumeGame() {
gameState.isPaused = false;
}
// 添加ESC键暂停监听
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && gameState.isPlaying) {
gameState.isPaused = !gameState.isPaused;
if (gameState.isPaused) {
// 创建继续按钮
const resumeBtn = document.createElement('button');
resumeBtn.id = 'resume-button';
resumeBtn.textContent = '继续';
resumeBtn.style.position = 'absolute';
resumeBtn.style.left = '50%';
resumeBtn.style.top = '70%'
resumeBtn.style.transform = 'translate(-50%, -50%)';
resumeBtn.style.padding = '10px 20px';
resumeBtn.style.fontSize = '20px';
resumeBtn.onclick = () => {
resumeGame();
document.body.removeChild(resumeBtn);
};
document.body.appendChild(resumeBtn);
} else {
// 移除继续按钮
const resumeBtn = document.getElementById('resume-button');
if (resumeBtn) document.body.removeChild(resumeBtn);
}
}
});
</script>
</body>
</html>Game Source: 坦克大战:海陆双战
Creator: QuantumComet96
Libraries: none
Complexity: complex (7487 lines, 363.1 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: -quantumcomet96" to link back to the original. Then publish at arcadelab.ai/publish.