我来为你实现一个完整的坦克大战游戏案例,使用HTML5 Canvas和JavaScript。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">坦克大战</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #1a1a1a;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: 'Arial', sans-serif;
}
#gameContainer {
background-color: #333;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
}
canvas {
border: 2px solid #444;
background: #000;
display: block;
cursor: crosshair;
}
#gameInfo {
color: white;
margin-top: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
#score {
font-size: 18px;
background: #444;
padding: 5px 15px;
border-radius: 20px;
}
.btn {
background: #e74c3c;
color: white;
border: none;
padding: 8px 20px;
border-radius: 20px;
cursor: pointer;
font-size: 16px;
transition: background 0.3s;
}
.btn:hover {
background: #c0392b;
}
.btn:active {
transform: scale(0.95);
}
#controls {
color: #888;
font-size: 12px;
margin-top: 10px;
}
#gameOver {
color: #e74c3c;
font-weight: bold;
font-size: 14px;
}
</style>
</head>
<body>
<div id="gameContainer">
<canvas id="gameCanvas"></canvas>
<div id="gameInfo">
<span id="score">得分: 0</span>
<button id="restartBtn" class="btn">重新开始</button>
<span id="gameOver"></span>
</div>
<div id="controls">
<p>WASD / 方向键: 移动 | 鼠标: 瞄准 | 左键: 射击 | P: 暂停</p>
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
const restartBtn = document.getElementById('restartBtn');
const gameOverElement = document.getElementById('gameOver');
// 游戏配置
const GRID_SIZE = 30;
canvas.width = 900;
canvas.height = 600;
// 游戏状态
let score = 0;
let gameRunning = true;
let paused = false;
let lastTime = 0;
// 玩家坦克
const player = {
x: canvas.width / 2,
y: canvas.height - 50,
width: 40,
height: 40,
speed: 200,
angle: -Math.PI / 2,
cooldown: 0,
maxCooldown: 0.2
};
// 敌人数组
let enemies = [];
// 子弹数组
let bullets = [];
// 障碍物数组
const obstacles = [];
// 粒子效果
let particles = [];
// 生成障碍物
function generateObstacles() {
obstacles.length = 0;
// 第一排
for (let i = 1; i < 5; i++) {
obstacles.push({
x: i * 150,
y: 150,
width: GRID_SIZE,
height: GRID_SIZE
});
}
// 第二排
for (let i = 1; i < 5; i++) {
obstacles.push({
x: i * 150,
y: 150,
width: GRID_SIZE,
height: GRID_SIZE
});
}
// 中间障碍
obstacles.push({ x: 420, y: 250, width: 60, height: 60 });
obstacles.push({ x: 420, y: 350, width: 60, height: 60 });
// 侧面障碍
for (let i = 0; i < 3; i++) {
obstacles.push({
x: 150,
y: 200 + i * 80,
width: GRID_SIZE,
height: GRID_SIZE
});
obstacles.push({
x: canvas.width - 180,
y: 200 + i * 80,
width: GRID_SIZE,
height: GRID_SIZE
});
}
}
// 生成敌人
function spawnEnemy() {
const enemy = {
x: Math.random() * (canvas.width - 40) + 20,
y: 0,
width: 36,
height: 36,
speed: 60 + Math.random() * 80,
angle: Math.PI / 2,
pattern: Math.floor(Math.random() * 2),
changeTimer: 0,
cooldown: 1 + Math.random() * 2
};
// 避免生成在障碍物中
const maxAttempts = 10;
for (let i = 0; i < maxAttempts; i++) {
let collision = false;
for (const obstacle of obstacles) {
if (checkCollision(enemy, obstacle)) {
collision = true;
break;
}
}
if (!collision) break;
enemy.x = Math.random() * (canvas.width - 40) + 20;
enemy.y = 0;
}
enemies.push(enemy);
}
// 碰撞检测
function checkCollision(rect1, rect2) {
return rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height &&
rect1.y + rect1.height > rect2.y;
}
// 键盘控制
const keys = {};
document.addEventListener('keydown', (e) => {
keys[e.key.toLowerCase()] = true;
if (e.key === 'p' || e.key === 'P') {
paused = !paused;
if (!paused) lastTime = performance.now();
}
});
document.addEventListener('keyup', (e) => {
keys[e.key.toLowerCase()] = false;
});
// 鼠标控制
let mouseX = player.x;
let mouseY = player.y - 100;
let mouseDown = false;
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
mouseX = e.clientX - rect.left;
mouseY = e.clientY - rect.top;
});
canvas.addEventListener('mousedown', (e) => {
if (e.button === 0) {
mouseDown = true;
shoot();
}
});
canvas.addEventListener('mouseup', (e) => {
if (e.button === 0) {
mouseDown = false;
}
});
// 射击
function shoot() {
if (player.cooldown <= 0 && gameRunning && !paused) {
const speed = 400;
bullets.push({
x: player.x + player.width / 2,
y: player.y + player.height / 2,
vx: Math.cos(player.angle) * speed,
vy: Math.sin(player.angle) * speed,
isPlayer: true,
damage: 1
});
player.cooldown = player.maxCooldown;
// 粒子效果
for (let i = 0; i < 5; i++) {
particles.push({
x: player.x + player.width / 2,
y: player.y + player.height / 2,
vx: Math.cos(player.angle) * 100 + (Math.random() - 0.5) * 100,
vy: Math.sin(player.angle) * 100 + (Math.random() - 0.5) * 100,
life: 0.2,
color: '#FFD700'
});
}
}
}
// 敌人射击
function enemyShoot(enemy) {
if (enemy.cooldown <= 0) {
const dx = player.x + player.width / 2 - (enemy.x + enemy.width / 2);
const dy = player.y + player.height / 2 - (enemy.y + enemy.height / 2);
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 300) {
const speed = 200;
const angle = Math.atan2(dy, dx);
bullets.push({
x: enemy.x + enemy.width / 2,
y: enemy.y + enemy.height / 2,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
isPlayer: false,
damage: 1
});
enemy.cooldown = 1 + Math.random() * 2;
// 粒子效果
for (let i = 0; i < 3; i++) {
particles.push({
x: enemy.x + enemy.width / 2,
y: enemy.y + enemy.height / 2,
vx: Math.cos(angle) * 80 + (Math.random() - 0.5) * 50,
vy: Math.sin(angle) * 80 + (Math.random() - 0.5) * 50,
life: 0.3,
color: '#FF4444'
});
}
}
}
}
// 更新游戏
function update(deltaTime) {
if (!gameRunning || paused) return;
// 玩家移动
const prevX = player.x;
const prevY = player.y;
let dx = 0, dy = 0;
if (keys['w'] || keys['arrowup']) dy = -1;
if (keys['s'] || keys['arrowdown']) dy = 1;
if (keys['a'] || keys['arrowleft']) dx = -1;
if (keys['d'] || keys['arrowright']) dx = 1;
// 归一化
const mag = Math.sqrt(dx * dx + dy * dy);
if (mag > 0) {
dx /= mag;
dy /= mag;
player.x += dx * player.speed * deltaTime;
player.y += dy * player.speed * deltaTime;
}
// 更新角度
player.angle = Math.atan2(mouseY - (player.y + player.height / 2), mouseX - (player.x + player.width / 2));
// 边界检测
player.x = Math.max(0, Math.min(canvas.width - player.width, player.x));
player.y = Math.max(0, Math.min(canvas.height - player.height, player.y));
// 障碍物碰撞
for (const obstacle of obstacles) {
if (checkCollision(player, obstacle)) {
player.x = prevX;
player.y = prevY;
break;
}
}
// 冷却时间
if (player.cooldown > 0) {
player.cooldown -= deltaTime;
}
// 连续射击
if (mouseDown) {
shoot();
}
// 更新子弹
for (let i = bullets.length - 1; i >= 0; i--) {
const bullet = bullets[i];
bullet.x += bullet.vx * deltaTime;
bullet.y += bullet.vy * deltaTime;
// 移除超出边界的子弹
if (bullet.x < 0 || bullet.x > canvas.width ||
bullet.y < 0 || bullet.y > canvas.height) {
bullets.splice(i, 1);
continue;
}
// 子弹与障碍物碰撞
let hitObstacle = false;
for (const obstacle of obstacles) {
if (checkCollision({
x: bullet.x - 2,
y: bullet.y - 2,
width: 4,
height: 4
}, obstacle)) {
hitObstacle = true;
break;
}
}
if (hitObstacle) {
// 粒子效果
for (let j = 0; j < 4; j++) {
particles.push({
x: bullet.x,
y: bullet.y,
vx: (Math.random() - 0.5) * 100,
vy: (Math.random() - 0.5) * 100,
life: 0.3,
color: '#888'
});
}
bullets.splice(i, 1);
continue;
}
// 子弹与敌人碰撞
if (bullet.isPlayer) {
for (let j = enemies.length - 1; j >= 0; j--) {
const enemy = enemies[j];
if (checkCollision(bullet, enemy)) {
enemies.splice(j, 1);
bullets.splice(i, 1);
score += 100;
scoreElement.textContent = `得分: ${score}`;
// 粒子效果
for (let k = 0; k < 10; k++) {
particles.push({
x: bullet.x,
y: bullet.y,
vx: (Math.random() - 0.5) * 200,
vy: (Math.random() - 0.5) * 200,
life: 0.5,
color: '#FFD700'
});
}
break;
}
}
}
// 子弹与玩家碰撞
if (!bullet.isPlayer) {
if (checkCollision(bullet, player)) {
gameOver();
return;
}
}
}
// 更新敌人
for (let i = enemies.length - 1; i >= 0; i--) {
const enemy = enemies[i];
enemy.changeTimer -= deltaTime;
// 敌人AI
if (enemy.changeTimer <= 0) {
enemy.changeTimer = 1 + Math.random();
if (enemy.pattern === 0) {
// 直线移动
enemy.angle = Math.PI / 2 + (Math.random() - 0.5) * 0.5;
} else {
// 追踪玩家
const dx = player.x - enemy.x;
const dy = player.y - enemy.y;
enemy.angle = Math.atan2(dy, dx);
}
}
enemy.x += Math.cos(enemy.angle) * enemy.speed * deltaTime;
enemy.y += Math.sin(enemy.angle) * enemy.speed * deltaTime;
// 边界限制
enemy.x = Math.max(0, Math.min(canvas.width - enemy.width, enemy.x));
enemy.y = Math.max(0, Math.min(canvas.height - enemy.height, enemy.y));
// 障碍物碰撞
for (const obstacle of obstacles) {
if (checkCollision(enemy, obstacle)) {
enemy.x -= Math.cos(enemy.angle) * enemy.speed * deltaTime;
enemy.y -= Math.sin(enemy.angle) * enemy.speed * deltaTime;
enemy.angle += Math.PI * (Math.random() > 0.5 ? 1 : -1);
enemy.changeTimer = 0;
break;
}
}
// 更新敌人冷却时间
if (enemy.cooldown > 0) {
enemy.cooldown -= deltaTime;
}
// 敌人射击
enemyShoot(enemy);
// 检查与玩家的碰撞
if (checkCollision(enemy, player)) {
gameOver();
return;
}
// 移除超出屏幕的敌人
if (enemy.y > canvas.height + 50) {
enemies.splice(i, 1);
}
}
// 更新粒子
for (let i = particles.length - 1; i >= 0; i--) {
const particle = particles[i];
particle.x += particle.vx * deltaTime;
particle.y += particle.vy * deltaTime;
particle.life -= deltaTime;
if (particle.life <= 0) {
particles.splice(i, 1);
}
}
// 生成敌人
if (Math.random() < 0.3 * deltaTime && enemies.length < 10) {
spawnEnemy();
}
}
// 绘制坦克
function drawTank(x, y, angle, isPlayer) {
const size = 20;
ctx.save();
// 坦克身体
const centerX = x + 20;
const centerY = y + 20;
ctx.translate(centerX, centerY);
ctx.rotate(angle);
// 身体
ctx.fillStyle = isPlayer ? '#2ECC40' : '#FF4136';
ctx.fillRect(-15, -12, 30, 24);
ctx.strokeStyle = '#111';
ctx.strokeRect(-15, -12, 30, 24);
// 履带
ctx.fillStyle = '#555';
ctx.fillRect(-18, -14, 8, 28);
ctx.fillRect(10, -14, 8, 28);
// 炮管
ctx.fillStyle = '#333';
ctx.fillRect(-5, -18, 10, 36);
ctx.strokeStyle = '#111';
ctx.strokeRect(-5, -18, 10, 36);
// 履带纹理
ctx.fillStyle = '#666';
for (let i = -12; i <= 12; i += 8) {
ctx.fillRect(-17, i, 6, 3);
ctx.fillRect(11, i, 6, 3);
}
// 舱盖
ctx.fillStyle = isPlayer ? '#27AE60' : '#C0392B';
ctx.beginPath();
ctx.arc(-8, 0, 8, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
ctx.restore();
}
// 绘制
function draw() {
// 清屏
ctx.fillStyle = '#1a1a1a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 迷你地图格线
ctx.strokeStyle = 'rgba(255, 255, 255, 0.03)';
ctx.lineWidth = 1;
for (let i = 0; i < canvas.width; i += GRID_SIZE) {
ctx.beginPath();
ctx.moveTo(i, 0);
ctx.lineTo(i, canvas.height);
ctx.stroke();
}
for (let i = 0; i < canvas.height; i += GRID_SIZE) {
ctx.beginPath();
ctx.moveTo(0, i);
ctx.lineTo(canvas.width, i);
ctx.stroke();
}
// 绘制障碍物
for (const obstacle of obstacles) {
ctx.fillStyle = '#7F8C8D';
ctx.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
ctx.strokeStyle = '#5D6D7E';
ctx.strokeRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
// 障碍物纹理
ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';
ctx.beginPath();
ctx.moveTo(obstacle.x, obstacle.y);
ctx.lineTo(obstacle.x + obstacle.width, obstacle.y + obstacle.height);
ctx.moveTo(obstacle.x + obstacle.width, obstacle.y);
ctx.lineTo(obstacle.x, obstacle.y + obstacle.height);
ctx.stroke();
}
// 绘制子弹
for (const bullet of bullets) {
ctx.save();
ctx.beginPath();
ctx.arc(bullet.x, bullet.y, 3, 0, Math.PI * 2);
ctx.fillStyle = bullet.isPlayer ? '#FFD700' : '#FF4444';
ctx.fill();
ctx.strokeStyle = '#111';
ctx.stroke();
ctx.restore();
}
// 绘制敌人
for (const enemy of enemies) {
drawTank(enemy.x, enemy.y, enemy.angle, false);
}
// 绘制玩家
drawTank(player.x, player.y, player.angle, true);
// 绘制粒子
for (const particle of particles) {
const alpha = particle.life / 0.5;
ctx.save();
ctx.globalAlpha = Math.max(0, alpha);
ctx.fillStyle = particle.color;
ctx.fillRect(particle.x - 3, particle.y - 3, 6, 6);
ctx.restore();
}
// 绘制鼠标瞄准点
if (gameRunning) {
ctx.save();
ctx.strokeStyle = '#FFD700';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(mouseX, mouseY, 15, 0, Math.PI * 2);
ctx.stroke();
// 十字线
ctx.beginPath();
ctx.moveTo(mouseX - 20, mouseY);
ctx.lineTo(mouseX + 20, mouseY);
ctx.moveTo(mouseX, mouseY - 20);
ctx.lineTo(mouseX, mouseY + 20);
ctx.stroke();
ctx.restore();
}
// 暂停提示
if (paused && gameRunning) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.font = '48px Arial';
ctx.textAlign = 'center';
ctx.fillText('⏸ 已暂停', canvas.width / 2, canvas.height / 2);
}
// 游戏结束提示
if (!gameRunning) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#FF4444';
ctx.font = '48px Arial';
ctx.textAlign = 'center';
ctx.fillText('游戏结束', canvas.width / 2, canvas.height / 2 - 20);
ctx.fillStyle = 'white';
ctx.font = '24px Arial';
ctx.fillText(`得分: ${score}`, canvas.width / 2, canvas.height / 2 + 30);
}
}
// 游戏结束
function gameOver() {
gameRunning = false;
gameOverElement.textContent = '✖';
}
// 重新开始游戏
function restartGame() {
score = 0;
scoreElement.textContent = '得分: 0';
enemies.length = 0;
bullets.length = 0;
particles.length = 0;
gameRunning = true;
paused = false;
gameOverElement.textContent = '';
player.x = canvas.width / 2;
player.y = canvas.height - 50;
player.cooldown = 0;
generateObstacles();
// 初始敌人
for (let i = 0; i < 3; i++) {
spawnEnemy();
}
}
// 游戏循环
function gameLoop(timestamp) {
const deltaTime = Math.min((timestamp - lastTime) / 1000, 0.1);
lastTime = timestamp;
if (!paused && gameRunning) {
update(deltaTime);
}
draw();
requestAnimationFrame(gameLoop);
}
// 初始化
restartBtn.addEventListener('click', restartGame);
generateObstacles();
// 初始敌人
for (let i = 0; i < 3; i++) {
spawnEnemy();
}
lastTime = performance.now();
requestAnimationFrame(gameLoop);
</script>
</body>
</html>
游戏功能说明:
🎮 控制方式
- WASD / 方向键:移动坦克
- 鼠标移动:瞄准
- 鼠标左键:射击(可长按连射)
- P 键:暂停/继续游戏
🎯 游戏特色
-
敌人AI系统:
- 敌人会追踪玩家
- 敌人会射击
- 不同的移动模式
-
障碍物系统:
- 固定障碍物会阻挡坦克和子弹
- 坦克不能穿越障碍物
-
视觉效果:
- 发射和爆炸粒子效果
- 坦克转向动画
- 瞄准准星
-
游戏机制:
- 实时的得分系统
- 游戏结束检测
- 重新开始功能
🕹️ 游戏技巧
- 利用障碍物躲避敌人子弹
- 不断移动避免被敌人瞄准
- 优先消灭靠近的敌人
- 适时使用连射模式
这是一个完整的坦克大战游戏,你可以将它保存为 HTML 文件并在浏览器中运行,游戏包含了现代坦克游戏的核心机制,并且代码结构清晰,容易理解和扩展。