一、游戏概述与技术背景
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