不会写游戏的前端不是好博主!用《植物大战僵尸》案例打通你的JS任督二脉 一个项目学会:DOM操作、事件处理、游戏循环、状态管理等核心技能
"别人的JavaScript教程:变量、函数、数组... 我的JavaScript教程:👽向日葵生产阳光 → 🧜♂️豌豆射手攻击 → 😍僵尸进攻 → 🏠保卫家园!这个超有趣的实战项目。通过开发一个完整游戏,让你在不知不觉中掌握JS核心概念。就等你了!"
游戏状态变量
let gameState = {
sun: 150,
wave: 1,
maxWaves: 5,
zombiesAlive: 0,
health: 5,
gameActive: false,
selectedPlant: null,
plants: [],
zombies: [],
projectiles: [],
suns: new Map(), // 使用Map来管理阳光,更容易查找和删除
nextZombieId: 1,
nextProjectileId: 1,
gameLoopId: null,
sunInterval: null,
waveInterval: null
};
gameState 是整个游戏的 "记忆库" 和 "控制中心",它记住游戏发生的所有事情:
四大核心功能
1. 数据存储中心(记住一切)
用户操作 → 更新gameState → UI显示
↑ ↓
更新gameState ← 游戏事件
2. 游戏逻辑依据(决策基础)
// 判断能否种植物:
function canPlacePlant(plantType) {
// 依据1:是否有足够的阳光? → 查看 gameState.sun
// 依据2:这个格子有没有植物? → 查看 gameState.plants
// 依据3:游戏是否在进行? → 查看 gameState.gameActive
}
3. UI显示来源(显示什么)
150 ← 显示 gameState.sun
1/5 ← 显示 gameState.wave
5 ← 显示 gameState.health
4. 游戏同步协调(保持一致性)
// 当僵尸被豌豆击中时:
function zombieHit(zombieId, damage) {
// 1. 更新僵尸血量(游戏逻辑)
zombie.hp -= damage;
// 2. 更新UI显示(视觉效果)
updateZombieHpBar(zombieId);
// 3. 如果僵尸死亡,更新游戏状态
if (zombie.hp <= 0) {
gameState.zombiesAlive--; // 减少存活计数
removeZombieFromUI(zombieId); // 移除僵尸显示
}
}
DOM元素:
// DOM元素
const sunCountEl = document.getElementById('sun-count');
const waveCountEl = document.getElementById('wave-count');
const zombieCountEl = document.getElementById('zombie-count');
const healthCountEl = document.getElementById('health-count');
const lawnEl = document.getElementById('lawn');
const plantCards = document.querySelectorAll('.plant-card');
const startBtn = document.getElementById('start-btn');
const resetBtn = document.getElementById('reset-btn');
const messageEl = document.getElementById('message');
const messageTitleEl = document.getElementById('message-title');
const messageTextEl = document.getElementById('message-text');
const restartBtn = document.getElementById('restart-btn');
1. document
-
作用:表示整个HTML文档
-
含义:浏览器加载页面时会自动创建一个
document对象,代表整个网页 -
类比:就像是一本书的"目录"或"索引"
2. .getElementById('lawn')
-
作用:在文档中查找具有指定id的元素
-
分解:
-
getElement:获取元素 -
ById:通过id -
('lawn'):id值为"lawn"
-
-
意思:在文档中查找id="lawn"的元素
-
返回值:找到的元素对象,如果没找到返回
null
3. const lawnEl =
-
作用:将找到的元素赋值给变量
-
分解:
-
const:声明一个常量(值不可改变) -
lawnEl:变量名(自定义的,可改为其他名字) -
=:赋值运算符
-
-
意思:将找到的元素存储在
lawnEl变量中
总结:"在HTML文档中找到id为'lawn'的元素,并将其引用存储到名为lawnEl的常量中"现在 lawnEl 就代表那个 比喻:就像一个记分牌,记录游戏的当前情况。 对象(Object) - 描述一个事物的多个属性: 数组(Array) - 列表: 定义一个函数: 调用函数: 给按钮添加点击事件: 对应代码: 游戏不断检查: 植物是否需要生产阳光 豌豆射手是否需要攻击 抛射物是否需要移动 僵尸是否需要移动
初始化草地
// 初始化草地
function initLawn() {
lawnEl.innerHTML = '';
for (let row = 0; row < 5; row++) {
for (let col = 0; col < 9; col++) {
const cell = document.createElement('div');
cell.className = 'cell';
cell.dataset.row = row;
cell.dataset.col = col;
cell.addEventListener('click', () => placePlant(row, col));
lawnEl.appendChild(cell);
}
}
}
lawnEl.innerHTML = '':清空草坪
const cell = document.createElement('div'):创建一个"格子"(创建一个HTML的
// 游戏状态变量
let gameState = {
sun: 150,
wave: 1,
maxWaves: 5,
zombiesAlive: 0,
health: 5,
gameActive: false,
selectedPlant: null,
plants: [],
zombies: [],
projectiles: [],
suns: new Map(), // 使用Map来管理阳光,更容易查找和删除
nextZombieId: 1,
nextProjectileId: 1,
gameLoopId: null,
sunInterval: null,
waveInterval: null
};
// 植物类型数据
const plantTypes = {
sunflower: {
name: "向日葵",
cost: 50,
emoji: "👽",
color: "#ffd700",
hp: 5,
maxHp: 5,
produceSun: true,
produceSunInterval: 10000,
attack: false,
attackInterval: 0
},
peashooter: {
name: "豌豆射手",
cost: 100,
emoji: "🧜♂️🧜♂️",
color: "#7cb879",
hp: 10,
maxHp: 10,
produceSun: false,
produceSunInterval: 0,
attack: true,
attackInterval: 2000
},
wallnut: {
name: "坚果墙",
cost: 50,
emoji: "🥜",
color: "#8b4513",
hp: 25,
maxHp: 25,
produceSun: false,
produceSunInterval: 0,
attack: false,
attackInterval: 0
}
};
// 僵尸类型数据
const zombieTypes = [
{ type: "normal", emoji: "😍", hp: 10, maxHp: 10, speed: 0.08, damage: 1 },
{ type: "cone", emoji: "😉😉", hp: 20, maxHp: 20, speed: 0.06, damage: 1 },
{ type: "bucket", emoji: "🤐", hp: 30, maxHp: 30, speed: 0.04, damage: 1 }
];
// DOM元素
const sunCountEl = document.getElementById('sun-count');
const waveCountEl = document.getElementById('wave-count');
const zombieCountEl = document.getElementById('zombie-count');
const healthCountEl = document.getElementById('health-count');
const lawnEl = document.getElementById('lawn');
const plantCards = document.querySelectorAll('.plant-card');
const startBtn = document.getElementById('start-btn');
const resetBtn = document.getElementById('reset-btn');
const messageEl = document.getElementById('message');
const messageTitleEl = document.getElementById('message-title');
const messageTextEl = document.getElementById('message-text');
const restartBtn = document.getElementById('restart-btn');
// ==================== 游戏初始化 ====================
// 初始化草地
function initLawn() {
lawnEl.innerHTML = '';
for (let row = 0; row < 5; row++) {
for (let col = 0; col < 9; col++) {
const cell = document.createElement('div');
cell.className = 'cell';
cell.dataset.row = row;
cell.dataset.col = col;
cell.addEventListener('click', () => placePlant(row, col));
lawnEl.appendChild(cell);
}
}
}
// 初始化游戏
function initGame() {
gameState = {
sun: 150,
wave: 1,
maxWaves: 5,
zombiesAlive: 0,
health: 5,
gameActive: false,
selectedPlant: null,
plants: [],
zombies: [],
projectiles: [],
suns: new Map(),
nextZombieId: 1,
nextProjectileId: 1,
gameLoopId: null,
sunInterval: null,
waveInterval: null
};
updateUI();
initLawn();
clearIntervals();
hideMessage();
plantCards.forEach(card => {
card.classList.remove('selected', 'disabled');
});
startBtn.textContent = "开始游戏";
startBtn.disabled = false;
}
// ==================== 阳光系统 ====================
// 创建阳光元素
function createSun(row, col, source = 'sky') {
const sunId = `sun-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
// 创建阳光DOM元素
const sunEl = document.createElement('div');
sunEl.id = sunId;
sunEl.className = 'sun';
sunEl.textContent = '☀️';
sunEl.style.left = `${col * (100 / 9) + 5}%`;
sunEl.style.top = `${row * 20 + 10}%`;
// 设置数据属性
sunEl.dataset.sunId = sunId;
sunEl.dataset.row = row;
sunEl.dataset.col = col;
sunEl.dataset.source = source;
// 添加点击事件 - 使用事件委托
sunEl.addEventListener('click', handleSunClick);
// 添加到草地
lawnEl.appendChild(sunEl);
// 存储阳光信息
gameState.suns.set(sunId, {
id: sunId,
element: sunEl,
row: row,
col: col,
source: source,
collected: false,
timeoutId: null
});
// 设置自动消失
const timeoutId = setTimeout(() => {
removeSun(sunId);
}, 10000);
// 存储timeoutId以便清理
const sunData = gameState.suns.get(sunId);
if (sunData) {
sunData.timeoutId = timeoutId;
}
return sunId;
}
// 处理阳光点击
function handleSunClick(event) {
event.stopPropagation(); // 阻止事件冒泡
const sunEl = event.currentTarget;
const sunId = sunEl.dataset.sunId;
if (!sunId) return;
collectSun(sunId);
}
// 收集阳光
function collectSun(sunId) {
const sunData = gameState.suns.get(sunId);
if (!sunData || sunData.collected) {
return;
}
// 标记为已收集
sunData.collected = true;
// 清除自动消失的timeout
if (sunData.timeoutId) {
clearTimeout(sunData.timeoutId);
}
// 增加阳光数量
gameState.sun += 25;
// 收集动画
const sunEl = sunData.element;
sunEl.style.transform = 'scale(1.5)';
sunEl.style.opacity = '0';
sunEl.style.transition = 'all 0.3s ease-out';
// 播放音效(如果有的话)
playSunSound();
// 更新UI
updateUI();
// 延迟移除元素
setTimeout(() => {
if (sunEl && sunEl.parentNode) {
sunEl.remove();
}
// 从Map中删除
gameState.suns.delete(sunId);
}, 300);
}
// 移除阳光(超时自动消失)
function removeSun(sunId) {
const sunData = gameState.suns.get(sunId);
if (!sunData || sunData.collected) {
return;
}
const sunEl = sunData.element;
// 淡出动画
sunEl.style.opacity = '0';
sunEl.style.transition = 'opacity 0.5s';
setTimeout(() => {
if (sunEl && sunEl.parentNode) {
sunEl.remove();
}
gameState.suns.delete(sunId);
}, 500);
}
// 播放阳光收集音效(占位函数)
function playSunSound() {
// 这里可以添加音效
console.log('阳光收集音效');
}
// 创建随机阳光(天空掉落)
function createRandomSun() {
const row = Math.floor(Math.random() * 5);
const col = Math.floor(Math.random() * 9);
createSun(row, col, 'sky');
}
// 向日葵生产阳光
function sunflowerProduceSun(plant) {
const now = Date.now();
if (now - plant.lastProducedSun > plantTypes.sunflower.produceSunInterval) {
plant.lastProducedSun = now;
// 在向日葵位置创建阳光
createSun(plant.row, plant.col, 'sunflower');
}
}
// ==================== 植物系统 ====================
// 更新UI
function updateUI() {
sunCountEl.textContent = gameState.sun;
waveCountEl.textContent = `${gameState.wave}/${gameState.maxWaves}`;
zombieCountEl.textContent = gameState.zombiesAlive;
healthCountEl.textContent = gameState.health;
plantCards.forEach(card => {
const plantType = card.dataset.plant;
const cost = parseInt(card.dataset.cost);
if (gameState.sun < cost) {
card.classList.add('disabled');
} else {
card.classList.remove('disabled');
}
if (gameState.selectedPlant === plantType) {
card.classList.add('selected');
} else {
card.classList.remove('selected');
}
});
}
// 选择植物
plantCards.forEach(card => {
card.addEventListener('click', () => {
if (card.classList.contains('disabled')) return;
const plantType = card.dataset.plant;
if (gameState.selectedPlant === plantType) {
gameState.selectedPlant = null;
} else {
gameState.selectedPlant = plantType;
}
updateUI();
});
});
// 放置植物
function placePlant(row, col) {
if (!gameState.gameActive || !gameState.selectedPlant) return;
const existingPlant = gameState.plants.find(p => p.row === row && p.col === col);
if (existingPlant) return;
const plantType = plantTypes[gameState.selectedPlant];
if (gameState.sun < plantType.cost) {
alert(`阳光不足!需要${plantType.cost}阳光,你只有${gameState.sun}阳光。`);
return;
}
gameState.sun -= plantType.cost;
const plant = {
id: `plant-${Date.now()}`,
type: gameState.selectedPlant,
row: row,
col: col,
hp: plantType.hp,
maxHp: plantType.maxHp,
lastProducedSun: plantType.produceSun ? Date.now() : null,
lastAttacked: plantType.attack ? Date.now() : null
};
gameState.plants.push(plant);
const cell = document.querySelector(`.cell[data-row="${row}"][data-col="${col}"]`);
const plantEl = document.createElement('div');
plantEl.id = plant.id;
plantEl.className = 'plant';
plantEl.textContent = plantType.emoji;
plantEl.style.color = plantType.color;
cell.appendChild(plantEl);
cell.classList.add('with-plant');
const hpBar = document.createElement('div');
hpBar.className = 'hp-bar';
const hpFill = document.createElement('div');
hpFill.className = 'hp-fill';
hpFill.style.width = '100%';
hpBar.appendChild(hpFill);
plantEl.appendChild(hpBar);
gameState.selectedPlant = null;
updateUI();
}
// ==================== 僵尸系统 ====================
// 豌豆射手攻击
function peashooterAttack(plant) {
const now = Date.now();
if (now - plant.lastAttacked > plantTypes.peashooter.attackInterval) {
plant.lastAttacked = now;
const zombiesInRow = gameState.zombies.filter(z => z.row === plant.row && z.col > plant.col);
if (zombiesInRow.length === 0) return;
const projectile = {
id: `projectile-${gameState.nextProjectileId++}`,
row: plant.row,
col: plant.col + 0.5,
damage: 2,
speed: 0.15
};
gameState.projectiles.push(projectile);
const projectileEl = document.createElement('div');
projectileEl.id = projectile.id;
projectileEl.className = 'projectile';
projectileEl.style.top = `${plant.row * 20 + 10}%`;
projectileEl.style.left = `${(plant.col + 0.5) * (100 / 9)}%`;
lawnEl.appendChild(projectileEl);
}
}
// 移动抛射物
function moveProjectile(projectile) {
const projectileEl = document.getElementById(projectile.id);
if (!projectileEl) return;
projectile.col += projectile.speed * 0.05;
projectileEl.style.left = `${projectile.col * (100 / 9)}%`;
const hitZombie = gameState.zombies.find(z =>
z.row === projectile.row &&
Math.abs(z.col - projectile.col) < 0.3
);
if (hitZombie) {
hitZombie.hp -= projectile.damage;
const projectileIndex = gameState.projectiles.findIndex(p => p.id === projectile.id);
if (projectileIndex > -1) {
gameState.projectiles.splice(projectileIndex, 1);
}
if (projectileEl) {
projectileEl.style.backgroundColor = '#ff0000';
projectileEl.style.transform = 'scale(1.5)';
setTimeout(() => {
if (projectileEl) projectileEl.remove();
}, 100);
}
if (hitZombie.hp <= 0) {
removeZombie(hitZombie.id);
}
return;
}
if (projectile.col > 9) {
const projectileIndex = gameState.projectiles.findIndex(p => p.id === projectile.id);
if (projectileIndex > -1) {
gameState.projectiles.splice(projectileIndex, 1);
}
if (projectileEl) {
projectileEl.remove();
}
}
}
// 创建僵尸
function createZombie() {
const row = Math.floor(Math.random() * 5);
let zombieType;
if (gameState.wave > 4) {
zombieType = zombieTypes[2];
} else if (gameState.wave > 2) {
zombieType = zombieTypes[1];
} else {
zombieType = zombieTypes[0];
}
const zombie = {
id: `zombie-${gameState.nextZombieId++}`,
type: zombieType.type,
row: row,
col: 8.5,
hp: zombieType.hp,
maxHp: zombieType.maxHp,
speed: zombieType.speed,
damage: zombieType.damage,
isAttacking: false,
moveInterval: null
};
gameState.zombies.push(zombie);
gameState.zombiesAlive++;
const zombieEl = document.createElement('div');
zombieEl.id = zombie.id;
zombieEl.className = 'zombie';
zombieEl.textContent = zombieType.emoji;
zombieEl.style.top = `${row * 20}%`;
zombieEl.style.right = `0%`;
const hpBar = document.createElement('div');
hpBar.className = 'zombie-hp-bar';
const hpFill = document.createElement('div');
hpFill.className = 'zombie-hp-fill';
hpFill.style.width = '100%';
hpBar.appendChild(hpFill);
zombieEl.appendChild(hpBar);
lawnEl.appendChild(zombieEl);
startZombieMovement(zombie);
updateUI();
}
// 开始僵尸移动
function startZombieMovement(zombie) {
if (zombie.moveInterval) {
clearInterval(zombie.moveInterval);
}
zombie.moveInterval = setInterval(() => {
if (!gameState.gameActive || zombie.hp <= 0) {
clearInterval(zombie.moveInterval);
return;
}
moveZombieStep(zombie);
}, 100);
}
// 僵尸单步移动
function moveZombieStep(zombie) {
const zombieEl = document.getElementById(zombie.id);
if (!zombieEl) return;
const plantInFront = gameState.plants.find(p => {
return p.row === zombie.row &&
Math.abs(p.col - zombie.col) < 0.5;
});
if (plantInFront && !zombie.isAttacking) {
zombie.isAttacking = true;
clearInterval(zombie.moveInterval);
attackPlant(zombie, plantInFront);
} else if (!plantInFront) {
if (zombie.isAttacking) {
zombie.isAttacking = false;
startZombieMovement(zombie);
}
zombie.col -= zombie.speed * 0.05;
if (zombie.col < 0) {
clearInterval(zombie.moveInterval);
zombieReachedHouse(zombie);
return;
}
const rightPos = (8.5 - zombie.col) * (100 / 9);
zombieEl.style.right = `${rightPos}%`;
const plantAhead = gameState.plants.find(p => {
return p.row === zombie.row &&
Math.abs(p.col - zombie.col) < 0.5;
});
if (plantAhead && !zombie.isAttacking) {
zombie.isAttacking = true;
clearInterval(zombie.moveInterval);
attackPlant(zombie, plantAhead);
}
}
}
// 僵尸攻击植物
function attackPlant(zombie, plant) {
if (!gameState.gameActive || zombie.hp <= 0 || plant.hp <= 0) {
zombie.isAttacking = false;
startZombieMovement(zombie);
return;
}
plant.hp -= zombie.damage;
updatePlantHpDisplay(plant);
const plantEl = document.getElementById(plant.id);
if (plantEl) {
plantEl.style.transform = 'scale(1.1)';
setTimeout(() => {
if (plantEl) plantEl.style.transform = 'scale(1)';
}, 200);
}
if (plant.hp <= 0) {
removePlant(plant.id);
zombie.isAttacking = false;
setTimeout(() => {
if (gameState.gameActive && zombie.hp > 0) {
startZombieMovement(zombie);
}
}, 500);
return;
}
setTimeout(() => {
if (gameState.gameActive && zombie.hp > 0 && plant.hp > 0) {
attackPlant(zombie, plant);
} else {
zombie.isAttacking = false;
if (gameState.gameActive && zombie.hp > 0) {
startZombieMovement(zombie);
}
}
}, 1000);
}
// 更新植物血量显示
function updatePlantHpDisplay(plant) {
const plantEl = document.getElementById(plant.id);
if (!plantEl) return;
const hpBar = plantEl.querySelector('.hp-bar .hp-fill');
if (hpBar) {
const hpPercent = (plant.hp / plant.maxHp) * 100;
hpBar.style.width = `${hpPercent}%`;
if (hpPercent > 60) {
hpBar.style.backgroundColor = '#00ff00';
} else if (hpPercent > 30) {
hpBar.style.backgroundColor = '#ffff00';
} else {
hpBar.style.backgroundColor = '#ff0000';
}
}
}
// 僵尸到达房屋
function zombieReachedHouse(zombie) {
gameState.health--;
removeZombie(zombie.id);
updateUI();
if (gameState.health <= 0) {
gameOver(false);
}
}
// 移除植物
function removePlant(plantId) {
const plantIndex = gameState.plants.findIndex(p => p.id === plantId);
if (plantIndex === -1) return;
const plant = gameState.plants[plantIndex];
gameState.plants.splice(plantIndex, 1);
const plantEl = document.getElementById(plantId);
if (plantEl) {
plantEl.style.transform = 'scale(0.5)';
plantEl.style.opacity = '0.5';
setTimeout(() => {
if (plantEl) {
plantEl.remove();
const cell = document.querySelector(`.cell[data-row="${plant.row}"][data-col="${plant.col}"]`);
if (cell) {
cell.classList.remove('with-plant');
}
}
}, 300);
}
}
// 移除僵尸
function removeZombie(zombieId) {
const zombieIndex = gameState.zombies.findIndex(z => z.id === zombieId);
if (zombieIndex === -1) return;
const zombie = gameState.zombies[zombieIndex];
if (zombie.moveInterval) {
clearInterval(zombie.moveInterval);
}
gameState.zombies.splice(zombieIndex, 1);
gameState.zombiesAlive--;
const zombieEl = document.getElementById(zombieId);
if (zombieEl) {
zombieEl.style.transform = 'rotate(90deg)';
zombieEl.style.opacity = '0.5';
setTimeout(() => {
if (zombieEl) {
zombieEl.remove();
}
}, 500);
}
if (gameState.zombiesAlive === 0 && gameState.wave >= gameState.maxWaves) {
setTimeout(() => {
if (gameState.zombiesAlive === 0 && gameState.health > 0) {
gameOver(true);
}
}, 2000);
}
updateUI();
}
// ==================== 游戏控制 ====================
// 开始游戏
startBtn.addEventListener('click', () => {
if (!gameState.gameActive) {
startGame();
} else {
pauseGame();
}
});
// 开始游戏
function startGame() {
gameState.gameActive = true;
startBtn.textContent = "暂停游戏";
startSunProduction();
startWave();
gameLoop();
}
// 暂停游戏
function pauseGame() {
gameState.gameActive = false;
startBtn.textContent = "继续游戏";
clearIntervals();
if (gameState.gameLoopId) {
cancelAnimationFrame(gameState.gameLoopId);
gameState.gameLoopId = null;
}
}
// 开始产生天空阳光
function startSunProduction() {
if (gameState.sunInterval) clearInterval(gameState.sunInterval);
gameState.sunInterval = setInterval(() => {
if (!gameState.gameActive) return;
createRandomSun();
}, 8000);
}
// 开始一波僵尸
function startWave() {
if (gameState.wave > gameState.maxWaves) return;
const zombiesInWave = Math.min(2 + gameState.wave, 6);
for (let i = 0; i < zombiesInWave; i++) {
const delay = i * 3000 + Math.random() * 2000;
setTimeout(() => {
if (!gameState.gameActive) return;
createZombie();
}, delay);
}
if (gameState.wave < gameState.maxWaves) {
const nextWaveDelay = zombiesInWave * 4000 + 10000;
gameState.waveInterval = setTimeout(() => {
if (!gameState.gameActive) return;
gameState.wave++;
updateUI();
startWave();
}, nextWaveDelay);
}
}
// 游戏主循环
function gameLoop() {
if (!gameState.gameActive) return;
gameState.plants.forEach(plant => {
if (plant.type === 'sunflower') {
sunflowerProduceSun(plant);
} else if (plant.type === 'peashooter') {
peashooterAttack(plant);
}
});
gameState.projectiles.forEach(projectile => {
moveProjectile(projectile);
});
gameState.gameLoopId = requestAnimationFrame(() => gameLoop());
}
// 游戏结束
function gameOver(isWin) {
gameState.gameActive = false;
clearIntervals();
if (gameState.gameLoopId) {
cancelAnimationFrame(gameState.gameLoopId);
gameState.gameLoopId = null;
}
gameState.zombies.forEach(zombie => {
if (zombie.moveInterval) {
clearInterval(zombie.moveInterval);
}
});
if (isWin) {
messageTitleEl.textContent = "恭喜!";
messageTextEl.textContent = `你成功抵御了${gameState.maxWaves}波僵尸的攻击!`;
} else {
messageTitleEl.textContent = "游戏结束";
messageTextEl.textContent = "僵尸攻破了你的防线!";
}
showMessage();
}
// 显示消息
function showMessage() {
messageEl.style.display = 'block';
}
// 隐藏消息
function hideMessage() {
messageEl.style.display = 'none';
}
// 清除所有间隔
function clearIntervals() {
if (gameState.waveInterval) clearTimeout(gameState.waveInterval);
if (gameState.sunInterval) clearInterval(gameState.sunInterval);
// 清理所有阳光的timeout
gameState.suns.forEach(sunData => {
if (sunData.timeoutId) {
clearTimeout(sunData.timeoutId);
}
});
gameState.zombies.forEach(zombie => {
if (zombie.moveInterval) {
clearInterval(zombie.moveInterval);
}
});
}
// 重置游戏
resetBtn.addEventListener('click', initGame);
// 重新开始游戏
restartBtn.addEventListener('click', () => {
hideMessage();
initGame();
});
// 初始化游戏
initGame();逐部分解析(从简单到复杂)
1. 游戏状态变量 (
gameState)
let gameState = {
sun: 150, // 当前阳光数量
wave: 1, // 当前波数
health: 5, // 家园血量
gameActive: false, // 游戏是否进行中
// ... 其他状态
};2. 变量声明 (
let, const)let:可以改变的值(如分数、血量)const:固定不变的值(如植物类型数据)3. DOM元素(连接HTML和JavaScript)
// 获取HTML元素
const sunCountEl = document.getElementById('sun-count');
// sunCountEl 现在代表HTML中的阳光计数显示4. 基本的数据结构
const plantTypes = {
sunflower: {
name: "向日葵",
cost: 50, // 种植需要50阳光
emoji: "👽", // 显示的表情
hp: 5 // 血量
}
};
const zombieTypes = [
{ type: "normal", emoji: "😍", hp: 10 }, // 第一项
{ type: "cone", emoji: "😉😉", hp: 20 } // 第二项
];5. 函数(Function) - 可重复使用的代码块
function updateUI() {
sunCountEl.textContent = gameState.sun; // 更新显示
}
updateUI(); // 执行这个函数6. 事件监听(点击按钮、选择植物等)
startBtn.addEventListener('click', () => {
// 当按钮被点击时,执行这里的代码
if (!gameState.gameActive) {
startGame();
}
});核心游戏机制解析
1. 种植植物的流程:
点击植物卡片 → 选择植物 → 点击草地格子 → 检查阳光 → 扣除阳光 → 创建植物
function placePlant(row, col) {
// 1. 检查游戏是否进行中
if (!gameState.gameActive || !gameState.selectedPlant) return;
// 2. 检查这个格子是否有植物
const existingPlant = gameState.plants.find(p => p.row === row && p.col === col);
if (existingPlant) return;
// 3. 检查阳光是否足够
if (gameState.sun < plantType.cost) {
alert(`阳光不足!`);
return;
}
// 4. 扣除阳光,创建植物
gameState.sun -= plantType.cost;
// ... 创建植物对象和显示元素
}2. 游戏循环(核心)
function gameLoop() {
if (!gameState.gameActive) return; // 如果游戏暂停,停止循环
// 检查所有植物
gameState.plants.forEach(plant => {
if (plant.type === 'sunflower') {
sunflowerProduceSun(plant); // 向日葵生产阳光
} else if (plant.type === 'peashooter') {
peashooterAttack(plant); // 豌豆射手攻击
}
});
// 继续下一帧循环
gameState.gameLoopId = requestAnimationFrame(() => gameLoop());
}










