植物大战僵尸HTML5游戏完整实现教程

一、游戏概述与技术背景

1.1 植物大战僵尸游戏简介

《植物大战僵尸》(Plants vs. Zombies)是由PopCap Games开发的一款塔防类游戏,自2009年发布以来风靡全球。玩家通过种植各种植物来抵御不同类型的僵尸入侵,保护自己的家园。游戏的核心机制包括:

  • 资源管理:通过收集阳光种植植物
  • 策略布局:在不同位置放置适合的植物应对特定僵尸
  • 关卡挑战:不同难度和特殊条件的关卡设计
  • 多样化元素:丰富的植物和僵尸类型

1.2 为什么选择HTML5实现游戏

HTML5为游戏开发提供了强大且标准化的技术栈:

  • 跨平台兼容:可在PC、移动设备和各种浏览器上运行
  • 无需插件:基于标准Web技术,无需Flash等插件
  • Canvas API:提供高性能2D图形渲染能力
  • Web Audio API:支持高质量音效处理
  • 本地存储:可保存游戏进度和设置

根据知识库资料,HTML5游戏开发已成为现代网页游戏的主流选择,特别是Canvas技术为2D游戏提供了强大支持。

1.3 本教程的独特价值

本教程提供的是完全可运行的独立HTML文件,包含:

  • 专业级游戏架构设计
  • 完整的游戏核心机制实现
  • 详细的代码注释和解析
  • 游戏性能优化技巧
  • 可扩展的模块化设计
  • 自定义植物/僵尸图标功能
  • 响应式布局适配不同设备

与市面上常见的简单示例不同,本实现包含了真正的游戏循环、碰撞检测、状态管理等专业游戏开发要素。


二、完整可运行的HTML5植物大战僵尸游戏代码

以下是一个可以直接复制到.html文件中并在浏览器中运行的完整游戏实现:

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HTML5植物大战僵尸</title>
    <style>
        /* 全局重置和基础样式 */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            user-select: none;
        }
        
        body {
            font-family: 'Arial', sans-serif;
            background-color: #2c3e50;
            color: #ecf0f1;
            overflow: hidden;
            height: 100vh;
            display: flex;
            flex-direction: column;
        }
        
        /* 游戏标题区域 */
        .game-header {
            text-align: center;
            padding: 10px 0;
            background: linear-gradient(to bottom, #27ae60, #219653);
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
            position: relative;
        }
        
        .game-title {
            font-size: 2.5rem;
            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
            color: #f1c40f;
            letter-spacing: 2px;
        }
        
        .sun-value {
            position: absolute;
            top: 10px;
            left: 20px;
            background-color: #f39c12;
            color: #fff;
            padding: 5px 15px;
            border-radius: 20px;
            font-weight: bold;
            font-size: 1.2rem;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
        }
        
        /* 游戏主区域 */
        .game-container {
            display: flex;
            flex: 1;
            overflow: hidden;
            position: relative;
        }
        
        /* 植物选择栏 */
        .plant-selector {
            width: 120px;
            background-color: #8e44ad;
            padding: 15px 5px;
            overflow-y: auto;
            display: flex;
            flex-direction: column;
            gap: 10px;
            box-shadow: -2px 0 10px rgba(0, 0, 0, 0.3);
        }
        
        .plant-card {
            width: 100px;
            height: 100px;
            background-color: #9b59b6;
            border-radius: 10px;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            position: relative;
            transition: transform 0.2s;
            box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3);
            overflow: hidden;
        }
        
        .plant-card:hover {
            transform: scale(1.05);
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.4);
        }
        
        .plant-card img {
            width: 60px;
            height: 60px;
            object-fit: contain;
        }
        
        .plant-name {
            font-size: 0.8rem;
            margin-top: 5px;
            text-align: center;
        }
        
        .plant-cost {
            position: absolute;
            top: 5px;
            right: 5px;
            background-color: #e74c3c;
            color: white;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-weight: bold;
            font-size: 0.7rem;
        }
        
        .plant-cooldown {
            position: absolute;
            bottom: 0;
            left: 0;
            right: 0;
            height: 5px;
            background-color: rgba(0, 0, 0, 0.5);
        }
        
        .plant-cooldown-fill {
            height: 100%;
            background-color: #27ae60;
            width: 0%;
            transition: width 0.1s linear;
        }
        
        /* 游戏场景 */
        .game-scene {
            flex: 1;
            position: relative;
            background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="%239cbb73"/><path d="M0 50 L100 50 M0 70 L100 70 M0 90 L100 90 M0 30 L100 30 M0 10 L100 10" stroke="%237ba05b" stroke-width="1"/></svg>');
            background-size: 100% 100%;
            overflow: hidden;
        }
        
        .grid-lines {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
        }
        
        .grid-line-horizontal {
            position: absolute;
            left: 0;
            right: 0;
            height: 1px;
            background-color: rgba(0, 0, 0, 0.3);
        }
        
        .grid-line-vertical {
            position: absolute;
            top: 0;
            bottom: 0;
            width: 1px;
            background-color: rgba(0, 0, 0, 0.3);
        }
        
        /* 游戏元素通用样式 */
        .game-object {
            position: absolute;
            transition: transform 0.2s;
        }
        
        .plant {
            width: 80px;
            height: 80px;
            z-index: 10;
        }
        
        .zombie {
            width: 80px;
            height: 100px;
            z-index: 5;
            transition: left 0.05s linear;
        }
        
        .sun {
            width: 40px;
            height: 40px;
            border-radius: 50%;
            background-color: #f1c40f;
            display: flex;
            align-items: center;
            justify-content: center;
            font-weight: bold;
            color: #d35400;
            box-shadow: 0 0 10px #f1c40f;
            z-index: 20;
            cursor: pointer;
            animation: float 3s ease-in-out infinite;
        }
        
        @keyframes float {
            0%, 100% { transform: translateY(0); }
            50% { transform: translateY(-10px); }
        }
        
        .projectile {
            width: 30px;
            height: 30px;
            position: absolute;
            z-index: 15;
        }
        
        /* 游戏界面 */
        .game-interface {
            position: absolute;
            bottom: 10px;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 15px;
            z-index: 100;
        }
        
        .game-button {
            background-color: #3498db;
            color: white;
            border: none;
            padding: 8px 15px;
            border-radius: 5px;
            cursor: pointer;
            font-weight: bold;
            transition: background-color 0.2s;
        }
        
        .game-button:hover {
            background-color: #2980b9;
        }
        
        .game-button:active {
            transform: scale(0.95);
        }
        
        /* 暂停菜单 */
        .pause-menu {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.7);
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            z-index: 200;
            display: none;
        }
        
        .pause-title {
            font-size: 2.5rem;
            color: #f1c40f;
            margin-bottom: 20px;
        }
        
        .pause-buttons {
            display: flex;
            gap: 20px;
        }
        
        /* 游戏结束界面 */
        .game-over {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.7);
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            z-index: 200;
            display: none;
        }
        
        .game-over-title {
            font-size: 3rem;
            color: #e74c3c;
            margin-bottom: 20px;
        }
        
        .game-over-stats {
            font-size: 1.5rem;
            margin-bottom: 30px;
            text-align: center;
        }
        
        /* 自定义设置面板 */
        .custom-panel {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: #34495e;
            padding: 20px;
            border-radius: 10px;
            width: 400px;
            max-width: 90%;
            z-index: 300;
            display: none;
            box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
        }
        
        .panel-title {
            font-size: 1.5rem;
            margin-bottom: 15px;
            color: #f1c40f;
            text-align: center;
        }
        
        .custom-form {
            display: flex;
            flex-direction: column;
            gap: 15px;
        }
        
        .form-group {
            display: flex;
            flex-direction: column;
            gap: 5px;
        }
        
        .form-group label {
            font-weight: bold;
        }
        
        .form-group input {
            padding: 8px;
            border-radius: 5px;
            border: 1px solid #7f8c8d;
            background-color: #2c3e50;
            color: #ecf0f1;
        }
        
        .form-actions {
            display: flex;
            justify-content: space-between;
            margin-top: 10px;
        }
        
        .close-panel {
            background-color: #e74c3c;
        }
        
        /* 提示信息 */
        .notification {
            position: fixed;
            top: 20px;
            right: 20px;
            background-color: #27ae60;
            color: white;
            padding: 10px 20px;
            border-radius: 5px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
            z-index: 1000;
            transform: translateX(150%);
            transition: transform 0.3s ease-out;
        }
        
        .notification.show {
            transform: translateX(0);
        }
        
        /* 僵尸健康条 */
        .health-bar {
            position: absolute;
            top: -10px;
            left: 0;
            width: 100%;
            height: 5px;
            background-color: #e74c3c;
            border-radius: 2px;
            overflow: hidden;
        }
        
        .health-fill {
            height: 100%;
            background-color: #27ae60;
            width: 100%;
        }
        
        /* 植物健康条 */
        .plant-health-bar {
            position: absolute;
            bottom: -10px;
            left: 0;
            width: 100%;
            height: 5px;
            background-color: #e74c3c;
            border-radius: 2px;
            overflow: hidden;
        }
        
        .plant-health-fill {
            height: 100%;
            background-color: #27ae60;
            width: 100%;
        }
        
        /* 移动端适配 */
        @media (max-width: 768px) {
            .game-title {
                font-size: 1.8rem;
            }
            
            .plant-selector {
                width: 80px;
            }
            
            .plant-card {
                width: 70px;
                height: 70px;
            }
            
            .plant-card img {
                width: 45px;
                height: 45px;
            }
            
            .sun-value {
                font-size: 1rem;
                padding: 3px 10px;
            }
        }
    </style>
</head>
<body>
    <div class="game-header">
        <div class="sun-value">☀ <span id="sunCount">50</span></div>
        <h1 class="game-title">植物大战僵尸</h1>
    </div>
    
    <div class="game-container">
        <div class="plant-selector" id="plantSelector">
            <!-- 植物选择卡将通过JS动态生成 -->
        </div>
        
        <div class="game-scene" id="gameScene">
            <div class="grid-lines" id="gridLines"></div>
            <!-- 游戏元素将通过JS动态添加 -->
        </div>
    </div>
    
    <div class="game-interface">
        <button class="game-button" id="pauseButton">暂停</button>
        <button class="game-button" id="customButton">自定义</button>
        <button class="game-button" id="restartButton">重新开始</button>
    </div>
    
    <div class="pause-menu" id="pauseMenu">
        <h2 class="pause-title">游戏暂停</h2>
        <div class="pause-buttons">
            <button class="game-button" id="resumeButton">继续</button>
            <button class="game-button" id="menuButton">主菜单</button>
        </div>
    </div>
    
    <div class="game-over" id="gameOver">
        <h2 class="game-over-title" id="gameResult">游戏结束</h2>
        <div class="game-over-stats">
            <p>获得阳光: <span id="finalSun">0</span></p>
            <p>存活波数: <span id="finalWave">0</span></p>
        </div>
        <div class="pause-buttons">
            <button class="game-button" id="retryButton">再试一次</button>
            <button class="game-button" id="backToMenuButton">主菜单</button>
        </div>
    </div>
    
    <div class="custom-panel" id="customPanel">
        <h2 class="panel-title">自定义游戏元素</h2>
        <div class="custom-form">
            <div class="form-group">
                <label for="sunflowerImg">向日葵图片URL:</label>
                <input type="text" id="sunflowerImg" value="https://i.imgur.com/6XwV0Pm.png">
            </div>
            <div class="form-group">
                <label for="peashooterImg">豌豆射手图片URL:</label>
                <input type="text" id="peashooterImg" value="https://i.imgur.com/4cYcBkX.png">
            </div>
            <div class="form-group">
                <label for="zombieImg">普通僵尸图片URL:</label>
                <input type="text" id="zombieImg" value="https://i.imgur.com/3kQ0c9z.png">
            </div>
            <div class="form-group">
                <label for="coneheadZombieImg">路障僵尸图片URL:</label>
                <input type="text" id="coneheadZombieImg" value="https://i.imgur.com/1GkT9Dk.png">
            </div>
            <div class="form-actions">
                <button class="game-button" id="applyCustom">应用</button>
                <button class="game-button close-panel" id="closeCustom">关闭</button>
            </div>
        </div>
    </div>
    
    <div class="notification" id="notification">通知消息</div>

    <script>
        // ======================
        // 游戏核心引擎
        // ======================
        
        /**
         * 游戏主类 - 管理整个游戏的生命周期和状态
         */
        class PVZGame {
            constructor() {
                // 游戏状态
                this.gameState = 'playing'; // playing, paused, gameOver
                this.sunCount = 50; // 初始阳光数量
                this.currentWave = 1; // 当前波数
                this.zombiesInWave = 5; // 当前波僵尸数量
                this.zombiesRemaining = 5; // 剩余僵尸数量
                this.gridRows = 5; // 网格行数
                this.gridCols = 9; // 网格列数
                this.plants = []; // 植物数组
                this.zombies = []; // 僵尸数组
                this.suns = []; // 阳光数组
                this.projectiles = []; // 投射物数组
                this.selectedPlant = null; // 当前选中的植物
                this.gameLoop = null; // 游戏循环ID
                this.lastSunDropTime = 0; // 上次掉落阳光时间
                this.lastZombieSpawnTime = 0; // 上次生成僵尸时间
                this.zombieSpawnInterval = 3000; // 僵尸生成间隔(ms)
                this.sunDropInterval = 15000; // 阳光掉落间隔(ms)
                this.gridCellWidth = 0; // 网格单元宽度
                this.gridCellHeight = 0; // 网格单元高度
                this.plantCooldowns = {}; // 植物冷却计时
                
                // DOM元素引用
                this.gameScene = document.getElementById('gameScene');
                this.plantSelector = document.getElementById('plantSelector');
                this.sunCountElement = document.getElementById('sunCount');
                this.gridLines = document.getElementById('gridLines');
                this.pauseMenu = document.getElementById('pauseMenu');
                this.gameOver = document.getElementById('gameOver');
                this.gameResult = document.getElementById('gameResult');
                this.finalSun = document.getElementById('finalSun');
                this.finalWave = document.getElementById('finalWave');
                this.notification = document.getElementById('notification');
                this.customPanel = document.getElementById('customPanel');
                
                // 游戏元素配置
                this.plantTypes = [
                    {
                        id: 'sunflower',
                        name: '向日葵',
                        cost: 50,
                        cooldown: 5000,
                        img: 'https://i.imgur.com/6XwV0Pm.png',
                        description: '每24秒生产50阳光'
                    },
                    {
                        id: 'peashooter',
                        name: '豌豆射手',
                        cost: 100,
                        cooldown: 7500,
                        img: 'https://i.imgur.com/4cYcBkX.png',
                        description: '发射豌豆攻击僵尸'
                    },
                    {
                        id: 'wallnut',
                        name: '坚果墙',
                        cost: 50,
                        cooldown: 30000,
                        img: 'https://i.imgur.com/0nT5qQv.png',
                        description: '阻挡僵尸前进'
                    }
                ];
                
                this.zombieTypes = [
                    {
                        id: 'normal',
                        name: '普通僵尸',
                        speed: 0.5,
                        health: 100,
                        damage: 10,
                        img: 'https://i.imgur.com/3kQ0c9z.png'
                    },
                    {
                        id: 'conehead',
                        name: '路障僵尸',
                        speed: 0.4,
                        health: 200,
                        damage: 15,
                        img: 'https://i.imgur.com/1GkT9Dk.png'
                    }
                ];
                
                this.projectileTypes = {
                    'peashooter': {
                        speed: 2,
                        damage: 20,
                        img: 'https://i.imgur.com/8qVxV0P.png'
                    }
                };
                
                // 初始化游戏
                this.init();
            }
            
            /**
             * 初始化游戏
             */
            init() {
                // 创建网格线
                this.createGridLines();
                
                // 创建植物选择卡
                this.createPlantSelector();
                
                // 添加事件监听器
                this.setupEventListeners();
                
                // 启动游戏循环
                this.startGameLoop();
                
                // 显示初始通知
                this.showNotification('游戏开始!种植向日葵获取阳光,种植豌豆射手抵御僵尸!');
            }
            
            /**
             * 创建网格线
             */
            createGridLines() {
                // 移除现有网格线
                while (this.gridLines.firstChild) {
                    this.gridLines.removeChild(this.gridLines.firstChild);
                }
                
                // 计算网格尺寸
                this.gridCellWidth = this.gameScene.clientWidth / this.gridCols;
                this.gridCellHeight = this.gameScene.clientHeight / this.gridRows;
                
                // 创建水平网格线
                for (let i = 1; i < this.gridRows; i++) {
                    const line = document.createElement('div');
                    line.className = 'grid-line-horizontal';
                    line.style.top = (i * this.gridCellHeight) + 'px';
                    this.gridLines.appendChild(line);
                }
                
                // 创建垂直网格线
                for (let i = 1; i < this.gridCols; i++) {
                    const line = document.createElement('div');
                    line.className = 'grid-line-vertical';
                    line.style.left = (i * this.gridCellWidth) + 'px';
                    this.gridLines.appendChild(line);
                }
            }
            
            /**
             * 创建植物选择卡
             */
            createPlantSelector() {
                this.plantSelector.innerHTML = '';
                
                this.plantTypes.forEach(plant => {
                    const plantCard = document.createElement('div');
                    plantCard.className = 'plant-card';
                    plantCard.dataset.plantId = plant.id;
                    
                    plantCard.innerHTML = `
                        <img src="${plant.img}" alt="${plant.name}">
                        <div class="plant-name">${plant.name}</div>
                        <div class="plant-cost">${plant.cost}</div>
                        <div class="plant-cooldown">
                            <div class="plant-cooldown-fill"></div>
                        </div>
                    `;
                    
                    this.plantSelector.appendChild(plantCard);
                    
                    // 设置初始冷却状态
                    this.plantCooldowns[plant.id] = {
                        lastUsed: 0,
                        isCooling: false
                    };
                });
            }
            
            /**
             * 设置事件监听器
             */
            setupEventListeners() {
                // 植物选择卡点击事件
                document.querySelectorAll('.plant-card').forEach(card => {
                    card.addEventListener('click', (e) => {
                        const plantId = e.currentTarget.dataset.plantId;
                        this.selectPlant(plantId);
                    });
                });
                
                // 游戏场景点击事件(放置植物)
                this.gameScene.addEventListener('click', (e) => {
                    if (this.gameState !== 'playing' || !this.selectedPlant) return;
                    
                    // 计算网格位置
                    const rect = this.gameScene.getBoundingClientRect();
                    const x = e.clientX - rect.left;
                    const y = e.clientY - rect.top;
                    
                    const col = Math.floor(x / this.gridCellWidth);
                    const row = Math.floor(y / this.gridCellHeight);
                    
                    // 检查是否在有效网格内
                    if (col >= 0 && col < this.gridCols && row >= 0 && row < this.gridRows) {
                        this.placePlant(row, col);
                    }
                });
                
                // 暂停按钮
                document.getElementById('pauseButton').addEventListener('click', () => {
                    this.togglePause();
                });
                
                // 恢复按钮
                document.getElementById('resumeButton').addEventListener('click', () => {
                    this.togglePause();
                });
                
                // 菜单按钮
                document.getElementById('menuButton').addEventListener('click', () => {
                    this.gameOverScreen('游戏已暂停');
                });
                
                // 重新开始按钮
                document.getElementById('restartButton').addEventListener('click', () => {
                    this.restartGame();
                });
                
                // 自定义按钮
                document.getElementById('customButton').addEventListener('click', () => {
                    this.showCustomPanel();
                });
                
                // 应用自定义设置
                document.getElementById('applyCustom').addEventListener('click', () => {
                    this.applyCustomSettings();
                });
                
                // 关闭自定义面板
                document.getElementById('closeCustom').addEventListener('click', () => {
                    this.hideCustomPanel();
                });
                
                // 重试按钮
                document.getElementById('retryButton').addEventListener('click', () => {
                    this.restartGame();
                });
                
                // 返回主菜单按钮
                document.getElementById('backToMenuButton').addEventListener('click', () => {
                    this.gameOverScreen('游戏已暂停');
                });
                
                // 窗口大小变化事件
                window.addEventListener('resize', () => {
                    this.createGridLines();
                });
                
                // 键盘事件(作弊码支持)
                window.addEventListener('keydown', (e) => {
                    this.handleCheats(e);
                });
            }
            
            /**
             * 处理作弊码
             */
            handleCheats(e) {
                // 暂存输入的字符
                if (!this.cheatBuffer) {
                    this.cheatBuffer = '';
                    this.cheatBufferTimer = setTimeout(() => {
                        this.cheatBuffer = '';
                    }, 2000);
                }
                
                this.cheatBuffer += e.key.toLowerCase();
                
                // 检查作弊码
                if (this.cheatBuffer.includes('sun')) {
                    this.addSun(250);
                    this.showNotification('阳光作弊码激活!+250阳光');
                    this.cheatBuffer = '';
                }
                
                if (this.cheatBuffer.includes('kill')) {
                    this.killAllZombies();
                    this.showNotification('全屏秒杀激活!');
                    this.cheatBuffer = '';
                }
                
                if (this.cheatBuffer.includes('win')) {
                    this.currentWave += 5;
                    this.zombiesInWave += 10;
                    this.showNotification(`波数增加!当前第${this.currentWave}波`);
                    this.cheatBuffer = '';
                }
                
                if (this.cheatBuffer.includes('dance')) {
                    this.makeZombiesDance();
                    this.showNotification('僵尸开始跳舞!');
                    this.cheatBuffer = '';
                }
                
                if (this.cheatBuffer.includes('future')) {
                    this.changeZombieAppearance('future');
                    this.showNotification('僵尸戴上时尚太阳眼镜!');
                    this.cheatBuffer = '';
                }
                
                if (this.cheatBuffer.includes('mustache')) {
                    this.changeZombieAppearance('mustache');
                    this.showNotification('僵尸戴上两撇胡子!');
                    this.cheatBuffer = '';
                }
            }
            
            /**
             * 切换游戏暂停状态
             */
            togglePause() {
                if (this.gameState === 'paused') {
                    this.resumeGame();
                } else {
                    this.pauseGame();
                }
            }
            
            /**
             * 暂停游戏
             */
            pauseGame() {
                this.gameState = 'paused';
                this.pauseMenu.style.display = 'flex';
                if (this.gameLoop) {
                    clearInterval(this.gameLoop);
                    this.gameLoop = null;
                }
            }
            
            /**
             * 恢复游戏
             */
            resumeGame() {
                this.gameState = 'playing';
                this.pauseMenu.style.display = 'none';
                this.startGameLoop();
            }
            
            /**
             * 选择植物
             */
            selectPlant(plantId) {
                if (this.gameState !== 'playing') return;
                
                const plant = this.plantTypes.find(p => p.id === plantId);
                if (!plant) return;
                
                // 检查阳光是否足够
                if (this.sunCount < plant.cost) {
                    this.showNotification(`阳光不足!需要 ${plant.cost} 阳光`);
                    return;
                }
                
                // 检查冷却时间
                const now = Date.now();
                const cooldown = this.plantCooldowns[plantId];
                
                if (cooldown.isCooling && (now - cooldown.lastUsed) < plant.cooldown) {
                    this.showNotification(`${plant.name} 正在冷却中`);
                    return;
                }
                
                this.selectedPlant = plantId;
                document.querySelectorAll('.plant-card').forEach(card => {
                    if (card.dataset.plantId === plantId) {
                        card.style.boxShadow = '0 0 15px #f1c40f';
                    } else {
                        card.style.boxShadow = '';
                    }
                });
                
                this.showNotification(`已选择 ${plant.name},点击草地种植`);
            }
            
            /**
             * 放置植物
             */
            placePlant(row, col) {
                const plant = this.plantTypes.find(p => p.id === this.selectedPlant);
                if (!plant) return;
                
                // 检查该位置是否已有植物
                const existingPlant = this.plants.find(p => p.row === row && p.col === col);
                if (existingPlant) {
                    this.showNotification('该位置已有植物!');
                    return;
                }
                
                // 检查阳光是否足够
                if (this.sunCount < plant.cost) {
                    this.showNotification(`阳光不足!需要 ${plant.cost} 阳光`);
                    return;
                }
                
                // 扣除阳光
                this.sunCount -= plant.cost;
                this.updateSunCount();
                
                // 创建植物元素
                const plantElement = document.createElement('div');
                plantElement.className = 'game-object plant';
                plantElement.style.left = (col * this.gridCellWidth) + 'px';
                plantElement.style.top = (row * this.gridCellHeight) + 'px';
                plantElement.innerHTML = `
                    <img src="${plant.img}" alt="${plant.name}" style="width:100%;height:100%;">
                    <div class="plant-health-bar">
                        <div class="plant-health-fill" style="width:100%"></div>
                    </div>
                `;
                
                this.gameScene.appendChild(plantElement);
                
                // 添加植物到数组
                this.plants.push({
                    id: Date.now(),
                    type: this.selectedPlant,
                    row: row,
                    col: col,
                    element: plantElement,
                    health: 100,
                    lastAction: Date.now()
                });
                
                // 重置选择状态
                this.selectedPlant = null;
                document.querySelectorAll('.plant-card').forEach(card => {
                    card.style.boxShadow = '';
                });
                
                // 开始冷却
                this.startPlantCooldown(plant.id, plant.cooldown);
                
                // 显示通知
                this.showNotification(`成功种植 ${plant.name}!`);
                
                // 特殊植物逻辑
                if (plant.id === 'sunflower') {
                    this.startSunflowerProduction(this.plants[this.plants.length - 1]);
                }
            }
            
            /**
             * 开始植物冷却
             */
            startPlantCooldown(plantId, cooldownTime) {
                const plantCard = document.querySelector(`.plant-card[data-plant-id="${plantId}"]`);
                const cooldownFill = plantCard.querySelector('.plant-cooldown-fill');
                
                this.plantCooldowns[plantId] = {
                    lastUsed: Date.now(),
                    isCooling: true
                };
                
                // 动画冷却效果
                let startTime = Date.now();
                const animate = () => {
                    const elapsed = Date.now() - startTime;
                    const progress = Math.min(elapsed / cooldownTime, 1);
                    cooldownFill.style.width = (progress * 100) + '%';
                    
                    if (progress < 1) {
                        requestAnimationFrame(animate);
                    } else {
                        this.plantCooldowns[plantId].isCooling = false;
                    }
                };
                
                requestAnimationFrame(animate);
            }
            
            /**
             * 开始向日葵生产阳光
             */
            startSunflowerProduction(plant) {
                // 向日葵每24秒生产50阳光
                plant.sunInterval = setInterval(() => {
                    if (this.gameState !== 'playing') return;
                    
                    // 创建阳光元素
                    const sun = document.createElement('div');
                    sun.className = 'game-object sun';
                    sun.textContent = '☀';
                    sun.style.fontSize = '24px';
                    sun.style.left = (plant.col * this.gridCellWidth + 20) + 'px';
                    sun.style.top = (plant.row * this.gridCellHeight + 20) + 'px';
                    
                    this.gameScene.appendChild(sun);
                    this.suns.push({
                        id: Date.now(),
                        element: sun,
                        x: plant.col * this.gridCellWidth + 20,
                        y: plant.row * this.gridCellHeight + 20,
                        targetX: null,
                        targetY: null,
                        collected: false
                    });
                    
                    // 动画:阳光升起
                    let startY = plant.row * this.gridCellHeight + 20;
                    let currentY = startY;
                    const riseHeight = 100;
                    const riseDuration = 1000;
                    const riseStartTime = Date.now();
                    
                    const riseAnimation = () => {
                        const elapsed = Date.now() - riseStartTime;
                        const progress = Math.min(elapsed / rise
相关推荐
无光末阳4 小时前
vue 环境下多个定时器的创建与暂停的统一封装
前端·vue.js
Hilaku4 小时前
技术Leader的“第一性原理”:我是如何做技术决策的?
前端·javascript·面试
liyf4 小时前
发布-订阅(Publish–Subscribe) vs 观察者模式(Observer Pattern)
前端
云中雾丽4 小时前
Flutter 里的 Riverpod 用法解析
前端
前端snow4 小时前
记录:非常典型的一个redux问题
前端
慧一居士4 小时前
src/App.vue 和 public/index.html 关系和区别
前端·vue.js
九十一5 小时前
websocket的连接原理
前端·javascript
念你那丝微笑5 小时前
vue实现批量导出二维码到PDF(支持分页生成 PDF)
前端·vue.js·pdf
Renounce5 小时前
《Android Handler:线程间通信的核心实现》
前端