乐于分享
好东西不私藏

经典坦克大战游戏(有源码,可复制)

经典坦克大战游戏(有源码,可复制)

本游戏是解压小游戏,操作简单,好玩解压,还可以作为案例进行开发讲解。后面我会把源码贴出来,希望点赞转发加关注。

游戏操作指南
控制方式
键盘控制 :
  • – ↑ / W – 向上移动
  • – ↓ / S – 向下移动
  • – ← / A – 向左移动
  • – → / D – 向右移动
  • – 空格键 – 发射子弹
  • – R键 – 游戏结束后重新开始
用电脑浏览器打开这个文件:
tank_game.html
<!DOCTYPE html><htmllang="zh-CN"><head>    <metacharset="UTF-8">    <metaname="viewport"content="width=device-width, initial-scale=1.0">    <title>坦克大战小游戏</title>    <linkrel="stylesheet"href="tank_game.css"></head><body>    <divclass="game-container">        <h1>坦克大战小游戏</h1>        <divclass="game-info">            <divclass="score">得分: <spanid="score">0</span></div>            <divclass="controls">方向键移动 | 空格键射击</div>        </div>        <divclass="canvas-container">            <canvasid="gameCanvas"width="800"height="600"></canvas>            <divclass="game-over"id="gameOver">                <h2>Game Over</h2>                <p>最终得分: <spanid="finalScore">0</span></p>                <buttonclass="restart-btn"onclick="restartGame()">重新开始</button>            </div>        </div>    </div>    <scriptsrc="tank_game.js"></script></body></html>
tank_game.css
* {    margin0;    padding0;    box-sizing: border-box;}body {    font-family: Arial, sans-serif;    background-color#f0f0f0;    display: flex;    flex-direction: column;    align-items: center;    justify-content: center;    min-height100vh;    padding20px;}.game-container {    text-align: center;}h1 {    color#333;    margin-bottom20px;    font-size2.5rem;    text-shadow2px 2px 4px rgba(0000.1);}.game-info {    display: flex;    justify-content: space-between;    align-items: center;    padding15px 30px;    background-color#fff;    border-radius10px;    margin-bottom20px;    box-shadow0 2px 10px rgba(0000.1);    width800px;}.score {    font-size1.5rem;    color#4CAF50;    font-weight: bold;}.controls {    font-size1rem;    color#666;}#gameCanvas {    background-color#e0e0e0;    border3px solid #333;    border-radius10px;    display: block;    box-shadow0 4px 20px rgba(0000.2);}.game-over {    position: absolute;    top50%;    left50%;    transformtranslate(-50%, -50%);    background-colorrgba(0000.8);    color: white;    padding40px 60px;    border-radius15px;    text-align: center;    display: none;    z-index10;}.game-over h2 {    font-size3rem;    margin-bottom20px;    color#ff4444;}.game-over p {    font-size1.5rem;    margin-bottom30px;}.restart-btn {    background-color#4CAF50;    color: white;    border: none;    padding15px 40px;    font-size1.5rem;    border-radius10px;    cursor: pointer;    transition: all 0.3s ease;}.restart-btn:hover {    background-color#45a049;    transformscale(1.05);}.restart-btn:active {    transformscale(0.95);}.canvas-container {    position: relative;}
tank_game.js
const canvas = document.getElementById('gameCanvas');const ctx = canvas.getContext('2d');const scoreElement = document.getElementById('score');const finalScoreElement = document.getElementById('finalScore');const gameOverElement = document.getElementById('gameOver');const GAME_WIDTH = 800;const GAME_HEIGHT = 600;const TANK_SIZE = 40;const BULLET_SIZE = 6;const BULLET_SPEED = 8;const TANK_SPEED = 4;const ENEMY_SPEED = 2;let score = 0;let gameRunning = true;let animationId;const keys = {};class Tank {    constructor(x, y, color, isPlayer = false) {        this.x = x;        this.y = y;        this.width = TANK_SIZE;        this.height = TANK_SIZE;        this.color = color;        this.direction = 'up';        this.isPlayer = isPlayer;        this.speed = isPlayer ? TANK_SPEED : ENEMY_SPEED;        this.bullets = [];        this.shootCooldown = 0;        this.opacity = 1.0;        this.scale = 1.0;        this.isExploding = false;    }    draw() {        ctx.save();        ctx.globalAlpha = this.opacity;        ctx.translate(this.x + this.width / 2this.y + this.height / 2);        ctx.scale(this.scale, this.scale);        switch(this.direction) {            case 'up':                ctx.rotate(0);                break;            case 'right':                ctx.rotate(Math.PI / 2);                break;            case 'down':                ctx.rotate(Math.PI);                break;            case 'left':                ctx.rotate(-Math.PI / 2);                break;        }        ctx.fillStyle = this.color;        ctx.fillRect(-this.width / 2, -this.height / 2this.width, this.height);        ctx.fillStyle = this.isPlayer ? '#2E7D32' : '#B71C1C';        ctx.fillRect(-this.width / 2 + 5, -this.height / 2 + 5this.width - 10this.height - 10);        ctx.fillStyle = this.isPlayer ? '#1B5E20' : '#8B0000';        ctx.fillRect(-4, -this.height / 2 - 8812);        ctx.restore();    }    move(dx, dy, obstacles) {        const newX = this.x + dx;        const newY = this.y + dy;        // 边界检查        if (newX < 0 || newX + this.width > GAME_WIDTH ||            newY < 0 || newY + this.height > GAME_HEIGHT) {            return;        }        // 障碍物碰撞检查        if (this.checkCollision(newX, newY, obstacles)) {            // 尝试分离移动:先尝试X轴移动,再尝试Y轴移动            if (dx !== 0 && !this.checkCollision(newX, this.y, obstacles)) {                // 只有X轴移动是安全的                this.x = newX;            } else if (dy !== 0 && !this.checkCollision(this.x, newY, obstacles)) {                // 只有Y轴移动是安全的                this.y = newY;            }            // 如果两个方向都不安全,则不移动            return;        }        // 如果没有碰撞,正常移动        this.x = newX;        this.y = newY;    }    checkCollision(x, y, obstacles) {        // 添加一个小的缓冲区,防止坦克与障碍物边缘过于接近        const buffer = this.isPlayer ? 2 : 1;        for (let obstacle of obstacles) {            if (x + buffer < obstacle.x + obstacle.width &&                x + this.width - buffer > obstacle.x &&                y + buffer < obstacle.y + obstacle.height &&                y + this.height - buffer > obstacle.y) {                return true;            }        }        return false;    }    shoot() {        if (this.shootCooldown <= 0) {            let bulletX, bulletY, bulletDx, bulletDy;            switch(this.direction) {                case 'up':                    bulletX = this.x + this.width / 2 - BULLET_SIZE / 2;                    bulletY = this.y - BULLET_SIZE;                    bulletDx = 0;                    bulletDy = -BULLET_SPEED;                    break;                case 'down':                    bulletX = this.x + this.width / 2 - BULLET_SIZE / 2;                    bulletY = this.y + this.height;                    bulletDx = 0;                    bulletDy = BULLET_SPEED;                    break;                case 'left':                    bulletX = this.x - BULLET_SIZE;                    bulletY = this.y + this.height / 2 - BULLET_SIZE / 2;                    bulletDx = -BULLET_SPEED;                    bulletDy = 0;                    break;                case 'right':                    bulletX = this.x + this.width;                    bulletY = this.y + this.height / 2 - BULLET_SIZE / 2;                    bulletDx = BULLET_SPEED;                    bulletDy = 0;                    break;            }            this.bullets.push(new Bullet(bulletX, bulletY, bulletDx, bulletDy, this.isPlayer));            this.shootCooldown = this.isPlayer ? 15 : 60;            audioManager.playShootSound();        }    }    updateBullets(obstacles) {        for (let i = this.bullets.length - 1; i >= 0; i--) {            this.bullets[i].update();            if (this.bullets[i].checkCollision(obstacles) ||                this.bullets[i].isOutOfBounds()) {                this.bullets.splice(i, 1);            }        }    }    drawBullets() {        this.bullets.forEach(bullet => bullet.draw());    }}class Bullet {    constructor(x, y, dx, dy, isPlayerBullet) {        this.x = x;        this.y = y;        this.width = BULLET_SIZE;        this.height = BULLET_SIZE;        this.dx = dx;        this.dy = dy;        this.isPlayerBullet = isPlayerBullet;    }    update() {        this.x += this.dx;        this.y += this.dy;    }    draw() {        ctx.fillStyle = this.isPlayerBullet ? '#FFD700' : '#FF4444';        ctx.beginPath();        ctx.arc(this.x + this.width / 2this.y + this.height / 2this.width / 20, Math.PI * 2);        ctx.fill();    }    checkCollision(obstacles) {        for (let obstacle of obstacles) {            if (this.x < obstacle.x + obstacle.width &&                this.x + this.width > obstacle.x &&                this.y < obstacle.y + obstacle.height &&                this.y + this.height > obstacle.y) {                return true;            }        }        return false;    }    isOutOfBounds() {        return this.x < 0 || this.x > GAME_WIDTH ||               this.y < 0 || this.y > GAME_HEIGHT;    }}class Obstacle {    constructor(x, y, width, height) {        this.x = x;        this.y = y;        this.width = width;        this.height = height;    }    draw() {        ctx.fillStyle = '#666';        ctx.fillRect(this.x, this.y, this.width, this.height);        ctx.strokeStyle = '#444';        ctx.lineWidth = 2;        ctx.strokeRect(this.x, this.y, this.width, this.height);    }}class Particle {    constructor(x, y, color, type) {        this.x = x;        this.y = y;        this.color = color;        this.type = type;        this.size = Math.random() * 8 + 4;        this.speedX = (Math.random() - 0.5) * 10;        this.speedY = (Math.random() - 0.5) * 10;        this.life = 1.0;        this.decay = Math.random() * 0.02 + 0.01;        this.gravity = 0.1;    }    update() {        this.x += this.speedX;        this.y += this.speedY;        this.speedY += this.gravity;        this.life -= this.decay;        this.size *= 0.98;    }    draw() {        ctx.save();        ctx.globalAlpha = this.life;        ctx.fillStyle = this.color;        if (this.type === 'spark') {            ctx.beginPath();            ctx.arc(this.x, this.y, this.size / 20, Math.PI * 2);            ctx.fill();        } else if (this.type === 'smoke') {            ctx.beginPath();            ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);            ctx.fill();        } else if (this.type === 'debris') {            ctx.fillRect(this.x - this.size / 2this.y - this.size / 2this.size, this.size);        }        ctx.restore();    }    isDead() {        return this.life <= 0;    }}class Explosion {    constructor(x, y, color) {        this.x = x;        this.y = y;        this.color = color;        this.particles = [];        this.phase = 0;        this.phaseTimer = 0;        this.duration = 0;        this.flashOpacity = 1.0;        this.screenShake = 10;        this.tankOpacity = 1.0;        this.tankScale = 1.0;        this.initParticles();    }    initParticles() {        const particleCount = 50;        for (let i = 0; i < particleCount; i++) {            const angle = (Math.PI * 2 * i) / particleCount;            const speed = Math.random() * 8 + 4;            const particleType = Math.random() < 0.3 ? 'smoke' : (Math.random() < 0.6 ? 'spark' : 'debris');            const particleColor = particleType === 'smoke' ?                 `rgba(100100100, ${Math.random() * 0.5 + 0.3})` :                 (particleType === 'spark' ?                     `rgba(255, ${Math.floor(Math.random() * 200)}, 01)` :                     this.color);            const particle = new Particle(this.x, this.y, particleColor, particleType);            particle.speedX = Math.cos(angle) * speed;            particle.speedY = Math.sin(angle) * speed;            this.particles.push(particle);        }    }    update() {        this.duration++;        this.phaseTimer++;        if (this.phaseTimer < 10) {            this.phase = 0;            this.flashOpacity = 1.0 - (this.phaseTimer / 10);        } else if (this.phaseTimer < 40) {            this.phase = 1;            this.flashOpacity = 0;            this.screenShake = Math.max(0this.screenShake - 0.5);        } else {            this.phase = 2;            this.flashOpacity = 0;            this.screenShake = 0;        }        if (this.phaseTimer < 30) {            this.tankOpacity = 1.0 - (this.phaseTimer / 30);            this.tankScale = 1.0 + (this.phaseTimer / 30) * 0.3;        } else {            this.tankOpacity = 0;        }        this.particles.forEach(particle => particle.update());        this.particles = this.particles.filter(particle => !particle.isDead());    }    draw() {        if (this.phase === 0) {            ctx.save();            ctx.globalAlpha = this.flashOpacity;            ctx.fillStyle = '#FFFFFF';            ctx.beginPath();            ctx.arc(this.x, this.y, 600, Math.PI * 2);            ctx.fill();            ctx.restore();        }        this.particles.forEach(particle => particle.draw());    }    isComplete() {        return this.duration >= 120;    }    getScreenShake() {        return this.screenShake;    }}class AudioManager {    constructor() {        this.audioContext = null;        this.initialized = false;    }    init() {        if (!this.initialized) {            this.audioContext = new (window.AudioContext || window.webkitAudioContext)();            this.initialized = true;        }    }    playExplosionSound() {        if (!this.initialized) return;        const oscillator = this.audioContext.createOscillator();        const gainNode = this.audioContext.createGain();        const filterNode = this.audioContext.createBiquadFilter();        oscillator.type = 'sawtooth';        oscillator.frequency.setValueAtTime(150this.audioContext.currentTime);        oscillator.frequency.exponentialRampToValueAtTime(50this.audioContext.currentTime + 0.5);        filterNode.type = 'lowpass';        filterNode.frequency.setValueAtTime(1000this.audioContext.currentTime);        filterNode.frequency.exponentialRampToValueAtTime(100this.audioContext.currentTime + 0.5);        gainNode.gain.setValueAtTime(0.3this.audioContext.currentTime);        gainNode.gain.exponentialRampToValueAtTime(0.01this.audioContext.currentTime + 0.5);        oscillator.connect(filterNode);        filterNode.connect(gainNode);        gainNode.connect(this.audioContext.destination);        oscillator.start();        oscillator.stop(this.audioContext.currentTime + 0.5);    }    playShootSound() {        if (!this.initialized) return;        const oscillator = this.audioContext.createOscillator();        const gainNode = this.audioContext.createGain();        oscillator.type = 'square';        oscillator.frequency.setValueAtTime(800this.audioContext.currentTime);        oscillator.frequency.exponentialRampToValueAtTime(200this.audioContext.currentTime + 0.1);        gainNode.gain.setValueAtTime(0.1this.audioContext.currentTime);        gainNode.gain.exponentialRampToValueAtTime(0.01this.audioContext.currentTime + 0.1);        oscillator.connect(gainNode);        gainNode.connect(this.audioContext.destination);        oscillator.start();        oscillator.stop(this.audioContext.currentTime + 0.1);    }}let player;let enemies = [];let obstacles = [];let explosions = [];let audioManager = new AudioManager();let screenShakeX = 0;let screenShakeY = 0;let lastObstacleChangeScore = 0;let obstacleChangeNotification = null;function initGame() {    score = 0;    lastObstacleChangeScore = 0;    gameRunning = true;    scoreElement.textContent = score;    gameOverElement.style.display = 'none';    audioManager.init();    player = new Tank(GAME_WIDTH / 2 - TANK_SIZE / 2, GAME_HEIGHT - TANK_SIZE - 10'#4CAF50'true);    enemies = [];    obstacles = [];    explosions = [];    createObstacles();    ensurePlayerNotInObstacle(); // 确保玩家坦克不与障碍物重叠    spawnEnemies(3);}function createObstacles() {    obstacles = [];    // 创建随机障碍物布局    const obstacleCount = 8 + Math.floor(score / 10);    for (let i = 0; i < obstacleCount; i++) {        let validPosition = false;        let attempts = 0;        while (!validPosition && attempts < 100) {            attempts++;            const width = Math.random() * 60 + 40;            const height = Math.random() * 60 + 40;            const x = Math.random() * (GAME_WIDTH - width);            const y = Math.random() * (GAME_HEIGHT - height - 100) + 50// 避免底部区域            const newObstacle = new Obstacle(x, y, width, height);            // 检查是否与现有障碍物重叠            let overlapping = false;            for (let obstacle of obstacles) {                if (x < obstacle.x + obstacle.width &&                    x + width > obstacle.x &&                    y < obstacle.y + obstacle.height &&                    y + height > obstacle.y) {                    overlapping = true;                    break;                }            }            // 检查是否与玩家初始位置重叠            if (!overlapping) {                const playerX = GAME_WIDTH / 2 - TANK_SIZE / 2;                const playerY = GAME_HEIGHT - TANK_SIZE - 10;                if (!(x < playerX + TANK_SIZE &&                      x + width > playerX &&                      y < playerY + TANK_SIZE &&                      y + height > playerY)) {                    obstacles.push(newObstacle);                    validPosition = true;                }            }        }    }}function ensurePlayerNotInObstacle() {    // 确保玩家坦克不与任何障碍物重叠    let overlapping = true;    let attempts = 0;    while (overlapping && attempts < 100) {        overlapping = false;        attempts++;        for (let obstacle of obstacles) {            if (player.x < obstacle.x + obstacle.width &&                player.x + player.width > obstacle.x &&                player.y < obstacle.y + obstacle.height &&                player.y + player.height > obstacle.y) {                overlapping = true;                // 尝试将玩家移动到安全位置                player.x = Math.random() * (GAME_WIDTH - TANK_SIZE);                player.y = Math.random() * (GAME_HEIGHT - TANK_SIZE - 100) + 50;                break;            }        }    }}function spawnEnemies(count) {    for (let i = 0; i < count; i++) {        spawnEnemy();    }}function spawnEnemy() {    let validPosition = false;    let attempts = 0;    while (!validPosition && attempts < 100) {        attempts++;        const x = Math.random() * (GAME_WIDTH - TANK_SIZE);        const y = Math.random() * (GAME_HEIGHT / 2 - TANK_SIZE); // 只在上半部分生成        const enemy = new Tank(x, y, '#FF4444');        // 检查是否与障碍物重叠        let overlapping = false;        for (let obstacle of obstacles) {            if (enemy.checkCollision(enemy.x, enemy.y, [obstacle])) {                overlapping = true;                break;            }        }        // 检查是否与其他敌人重叠        for (let otherEnemy of enemies) {            if (enemy.x < otherEnemy.x + otherEnemy.width &&                enemy.x + enemy.width > otherEnemy.x &&                enemy.y < otherEnemy.y + otherEnemy.height &&                enemy.y + enemy.height > otherEnemy.y) {                overlapping = true;                break;            }        }        if (!overlapping) {            enemies.push(enemy);            validPosition = true;        }    }}function showObstacleChangeNotification() {    obstacleChangeNotification = {        text: '障碍物已更新!',        opacity: 1.0,        y: GAME_HEIGHT / 2 - 50    };}function updateObstacleChangeNotification() {    if (obstacleChangeNotification) {        obstacleChangeNotification.opacity -= 0.02;        obstacleChangeNotification.y -= 1;        if (obstacleChangeNotification.opacity <= 0) {            obstacleChangeNotification = null;        }    }}function drawObstacleChangeNotification() {    if (obstacleChangeNotification) {        ctx.save();        ctx.globalAlpha = obstacleChangeNotification.opacity;        ctx.fillStyle = '#FFFFFF';        ctx.font = 'bold 24px Arial';        ctx.textAlign = 'center';        ctx.fillText(obstacleChangeNotification.text, GAME_WIDTH / 2, obstacleChangeNotification.y);        ctx.restore();    }}function handleInput() {    let dx = 0, dy = 0;    if (keys['ArrowUp'] || keys['w'] || keys['W']) {        dy = -player.speed;        player.direction = 'up';    }    if (keys['ArrowDown'] || keys['s'] || keys['S']) {        dy = player.speed;        player.direction = 'down';    }    if (keys['ArrowLeft'] || keys['a'] || keys['A']) {        dx = -player.speed;        player.direction = 'left';    }    if (keys['ArrowRight'] || keys['d'] || keys['D']) {        dx = player.speed;        player.direction = 'right';    }    if (dx !== 0 || dy !== 0) {        player.move(dx, dy, obstacles);    }    if (keys[' ']) {        player.shoot();    }}function updateEnemies() {    enemies.forEach(enemy => {        if (enemy.shootCooldown > 0) {            enemy.shootCooldown--;        }        if (Math.random() < 0.02) {            enemy.shoot();        }        // 只有在随机概率下或者刚碰到障碍物时才改变方向        if (Math.random() < 0.01 || enemy.shouldChangeDirection) {            const directions = ['up''down''left''right'];            const randomDirection = directions[Math.floor(Math.random() * directions.length)];            enemy.direction = randomDirection;            enemy.shouldChangeDirection = false;        }        let dx = 0, dy = 0;        switch(enemy.direction) {            case 'up': dy = -enemy.speed; break;            case 'down': dy = enemy.speed; break;            case 'left': dx = -enemy.speed; break;            case 'right': dx = enemy.speed; break;        }        // 检查移动是否会碰到障碍物或边界        const newX = enemy.x + dx;        const newY = enemy.y + dy;        if (newX < 0 || newX + enemy.width > GAME_WIDTH ||             newY < 0 || newY + enemy.height > GAME_HEIGHT ||            enemy.checkCollision(newX, newY, obstacles)) {            // 如果会碰到障碍物或边界,反向移动            enemy.direction = getOppositeDirection(enemy.direction);            enemy.shouldChangeDirection = true;            // 重新计算反向移动            dx = 0; dy = 0;            switch(enemy.direction) {                case 'up': dy = -enemy.speed; break;                case 'down': dy = enemy.speed; break;                case 'left': dx = -enemy.speed; break;                case 'right': dx = enemy.speed; break;            }        }        enemy.move(dx, dy, obstacles);        enemy.updateBullets(obstacles);    });}function getOppositeDirection(direction) {    switch(direction) {        case 'up'return 'down';        case 'down'return 'up';        case 'left'return 'right';        case 'right'return 'left';        default: return 'up';    }}function checkBulletCollisions() {    for (let i = enemies.length - 1; i >= 0; i--) {        const enemy = enemies[i];        for (let j = enemy.bullets.length - 1; j >= 0; j--) {            const bullet = enemy.bullets[j];            if (bullet.x < player.x + player.width &&                bullet.x + bullet.width > player.x &&                bullet.y < player.y + player.height &&                bullet.y + bullet.height > player.y) {                gameOver();                return;            }        }        for (let j = player.bullets.length - 1; j >= 0; j--) {            const bullet = player.bullets[j];            if (bullet.x < enemy.x + enemy.width &&                bullet.x + bullet.width > enemy.x &&                bullet.y < enemy.y + enemy.height &&                bullet.y + bullet.height > enemy.y) {                player.bullets.splice(j, 1);                const explosion = new Explosion(                    enemy.x + enemy.width / 2,                    enemy.y + enemy.height / 2,                    enemy.color                );                explosions.push(explosion);                audioManager.playExplosionSound();                enemy.isExploding = true;                enemy.opacity = 0;                setTimeout(() => {                    if (gameRunning) {                        const enemyIndex = enemies.indexOf(enemy);                        if (enemyIndex > -1) {                            enemies.splice(enemyIndex, 1);                            score++;                            scoreElement.textContent = score;                            spawnEnemy();                        }                    }                }, 1000);                break;            }        }    }}function gameOver() {    const playerExplosion = new Explosion(        player.x + player.width / 2,        player.y + player.height / 2,        player.color    );    explosions.push(playerExplosion);    audioManager.playExplosionSound();    let explosionAnimation = 0;    const explosionDuration = 60;    function animateExplosion() {        explosionAnimation++;        ctx.save();        let shakeX = 0;        let shakeY = 0;        explosions.forEach(explosion => {            explosion.update();            const shakeAmount = explosion.getScreenShake();            if (shakeAmount > 0) {                shakeX = (Math.random() - 0.5) * shakeAmount;                shakeY = (Math.random() - 0.5) * shakeAmount;            }        });        ctx.translate(shakeX, shakeY);        ctx.clearRect(-shakeX, -shakeY, GAME_WIDTH, GAME_HEIGHT);        obstacles.forEach(obstacle => obstacle.draw());        enemies.forEach(enemy => {            enemy.draw();            enemy.drawBullets();        });        explosions.forEach(explosion => explosion.draw());        ctx.restore();        if (explosionAnimation < explosionDuration) {            requestAnimationFrame(animateExplosion);        } else {            gameRunning = false;            finalScoreElement.textContent = score;            gameOverElement.style.display = 'block';            explosions = [];        }    }    animateExplosion();}function restartGame() {    initGame();    gameLoop();}function draw() {    let shakeX = 0;    let shakeY = 0;    explosions.forEach(explosion => {        const shakeAmount = explosion.getScreenShake();        if (shakeAmount > 0) {            shakeX = (Math.random() - 0.5) * shakeAmount;            shakeY = (Math.random() - 0.5) * shakeAmount;        }    });    ctx.save();    ctx.translate(shakeX, shakeY);    ctx.clearRect(-shakeX, -shakeY, GAME_WIDTH, GAME_HEIGHT);    obstacles.forEach(obstacle => obstacle.draw());    player.draw();    player.drawBullets();    enemies.forEach(enemy => {        enemy.draw();        enemy.drawBullets();    });    explosions.forEach(explosion => explosion.draw());    ctx.restore();    drawObstacleChangeNotification();}function gameLoop() {    if (!gameRunning) return;    if (player.shootCooldown > 0) {        player.shootCooldown--;    }    handleInput();    player.updateBullets(obstacles);    updateEnemies();    checkBulletCollisions();    // 检查是否需要变换障碍物    checkObstacleChange();    explosions.forEach(explosion => explosion.update());    explosions = explosions.filter(explosion => !explosion.isComplete());    updateObstacleChangeNotification();    draw();    animationId = requestAnimationFrame(gameLoop);}function checkObstacleChange() {    // 每增加10分变换一次障碍物    if (score - lastObstacleChangeScore >= 10) {        createObstacles();        lastObstacleChangeScore = score;        showObstacleChangeNotification();        ensurePlayerNotInObstacle();    }}// 事件监听器document.addEventListener('keydown', (e) => {    keys[e.key] = true;    // 处理R键重新开始游戏    if (e.key === 'r' || e.key === 'R') {        if (!gameRunning) {            restartGame();        }        e.preventDefault();    }    if (['ArrowUp''ArrowDown''ArrowLeft''ArrowRight'' '].includes(e.key)) {        e.preventDefault();    }});document.addEventListener('keyup', (e) => {    keys[e.key] = false;});// 初始化游戏initGame();gameLoop();
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 经典坦克大战游戏(有源码,可复制)

评论 抢沙发

7 + 8 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮