pvz网页版 V1.2
2026-09-06 17:38:16
发布于:上海
5阅读
0回复
0点赞
相较于1.1啥都改了
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>植物大战僵尸 H5 复刻版</title>
<style>
:root {
--bg-color: #2c3e50;
--ui-bg: #8d6e63;
--ui-border: #5d4037;
--text-color: #fff;
--accent: #f1c40f;
}
body {
margin: 0;
padding: 0;
background-color: var(--bg-color);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
overflow: hidden;
user-select: none;
}
#game-container {
position: relative;
width: 900px;
height: 600px;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
background: #4caf50; /* 草地背景 */
border: 4px solid var(--ui-border);
border-radius: 8px;
overflow: hidden;
}
canvas {
display: block;
cursor: crosshair;
}
/* UI 层 */
.ui-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
/* 顶部菜单栏 */
#top-bar {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 80px;
background: rgba(141, 110, 99, 0.9);
border-bottom: 4px solid var(--ui-border);
display: flex;
align-items: center;
padding: 0 20px;
box-sizing: border-box;
pointer-events: auto;
z-index: 10;
}
.sun-counter {
background: #fff;
color: #333;
padding: 5px 15px;
border-radius: 20px;
font-weight: bold;
font-size: 20px;
border: 2px solid #f1c40f;
margin-right: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.sun-icon {
width: 24px;
height: 24px;
background: radial-gradient(circle, #f1c40f 40%, #f39c12 100%);
border-radius: 50%;
box-shadow: 0 0 5px #f1c40f;
}
/* 植物卡片栏 */
#plant-bar {
display: flex;
gap: 10px;
flex: 1;
}
.plant-card {
width: 60px;
height: 70px;
background: #a1887f;
border: 2px solid #5d4037;
border-radius: 4px;
cursor: pointer;
position: relative;
transition: transform 0.1s;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.plant-card:hover {
transform: scale(1.05);
background: #bcaaa4;
}
.plant-card.selected {
border-color: #f1c40f;
box-shadow: 0 0 10px #f1c40f;
}
.plant-card.disabled {
opacity: 0.5;
cursor: not-allowed;
filter: grayscale(1);
}
.card-cost {
position: absolute;
bottom: 2px;
right: 2px;
font-size: 12px;
color: #fff;
background: rgba(0,0,0,0.6);
padding: 1px 4px;
border-radius: 2px;
}
.card-preview {
width: 30px;
height: 30px;
border-radius: 50%;
}
/* 工具栏 */
#tools {
display: flex;
gap: 10px;
margin-left: 20px;
}
.tool-btn {
width: 50px;
height: 50px;
background: #795548;
border: 2px solid #3e2723;
border-radius: 50%;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 20px;
transition: all 0.2s;
}
.tool-btn:hover {
background: #8d6e63;
}
.tool-btn.active {
background: #d32f2f;
border-color: #b71c1c;
}
/* 菜单覆盖层 */
#menu-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.85);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
pointer-events: auto;
}
.menu-title {
font-size: 48px;
color: #8bc34a;
text-shadow: 2px 2px 0 #33691e;
margin-bottom: 40px;
font-weight: bold;
letter-spacing: 2px;
}
.mode-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
.mode-btn {
padding: 15px 30px;
font-size: 18px;
background: #5d4037;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
transition: background 0.2s;
border: 2px solid #3e2723;
}
.mode-btn:hover {
background: #795548;
}
.hidden {
display: none !important;
}
/* 暂停面板 */
#pause-panel {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(255, 255, 255, 0.9);
padding: 30px;
border-radius: 10px;
text-align: center;
border: 4px solid #5d4037;
z-index: 50;
display: none;
}
#pause-panel h2 {
margin-top: 0;
color: #333;
}
.resume-btn {
background: #4caf50;
color: white;
border: none;
padding: 10px 20px;
font-size: 16px;
border-radius: 5px;
cursor: pointer;
}
/* 消息提示 */
#message-area {
position: absolute;
top: 100px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.7);
color: white;
padding: 10px 20px;
border-radius: 20px;
font-size: 18px;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
}
</style>
</head>
<body>
<div id="game-container">
<canvas id="gameCanvas" width="900" height="600"></canvas>
<!-- 顶部UI -->
<div id="top-bar" class="hidden">
<div class="sun-counter">
<div class="sun-icon"></div>
<span id="sun-display">50</span>
</div>
<div id="plant-bar">
<!-- 动态生成植物卡片 -->
</div>
<div id="tools">
<div class="tool-btn" id="shovel-btn" title="铲子">🥄</div>
<div class="tool-btn" id="pause-btn" title="暂停">⏸️</div>
</div>
</div>
<!-- 消息提示 -->
<div id="message-area">游戏开始!</div>
<!-- 暂停面板 -->
<div id="pause-panel">
<h2>游戏暂停</h2>
<button class="resume-btn" id="resume-btn">继续游戏</button>
</div>
<!-- 主菜单 -->
<div id="menu-overlay">
<div class="menu-title">植物大战僵尸 H5</div>
<div class="mode-grid">
<button class="mode-btn" data-mode="adventure">冒险模式 (Adventure)</button>
<button class="mode-btn" data-mode="endless">无尽模式 (Endless)</button>
<button class="mode-btn" data-mode="minigame">迷你游戏 (Minigame)</button>
<button class="mode-btn" data-mode="puzzle">益智模式 (Puzzle)</button>
</div>
<p style="color: #aaa; margin-top: 20px; font-size: 14px;">点击模式开始游戏 | 鼠标左键种植/收集阳光</p>
</div>
</div>
<script>
/**
* 游戏核心逻辑
*/
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// 游戏配置
const CONFIG = {
GRID_ROWS: 5,
GRID_COLS: 9,
CELL_WIDTH: 80,
CELL_HEIGHT: 100,
OFFSET_X: 20, // 网格起始X
OFFSET_Y: 100, // 网格起始Y
SUN_VALUE: 25,
INITIAL_SUN: 50,
FPS: 60
};
// 植物定义
const PLANTS = {
SUNFLOWER: { id: 1, name: '向日葵', cost: 50, hp: 300, color: '#f1c40f', type: 'producer', cooldown: 500 },
PEASHOOTER: { id: 2, name: '豌豆射手', cost: 100, hp: 300, color: '#4caf50', type: 'shooter', damage: 20, rate: 1500 },
WALLNUT: { id: 3, name: '坚果墙', cost: 50, hp: 2000, color: '#795548', type: 'defender' },
SNOWPEA: { id: 4, name: '寒冰射手', cost: 175, hp: 300, color: '#00bcd4', type: 'shooter', damage: 20, rate: 1500, effect: 'slow' }
};
// 僵尸定义
const ZOMBIES = {
NORMAL: { hp: 200, speed: 0.3, color: '#9e9e9e', damage: 10 },
CONEHEAD: { hp: 560, speed: 0.3, color: '#ff9800', damage: 10 }, // 路障僵尸
BUCKETHEAD: { hp: 1300, speed: 0.25, color: '#607d8b', damage: 10 } // 铁桶僵尸
};
// 游戏状态
let gameState = {
mode: null,
sun: 50,
plants: [],
zombies: [],
bullets: [],
suns: [], // 掉落的阳光
particles: [], // 特效粒子
grid: [], // 网格占用情况
lastTime: 0,
isPaused: false,
isGameOver: false,
wave: 1,
zombieSpawnTimer: 0,
sunSpawnTimer: 0,
selectedPlant: null,
isShovelActive: false,
levelConfig: null
};
// 初始化网格
function initGrid() {
gameState.grid = [];
for (let r = 0; r < CONFIG.GRID_ROWS; r++) {
let row = [];
for (let c = 0; c < CONFIG.GRID_COLS; c++) {
row.push(null);
}
gameState.grid.push(row);
}
}
// 类定义
class Plant {
constructor(r, c, typeKey) {
this.r = r;
this.c = c;
this.typeKey = typeKey;
this.config = PLANTS[typeKey];
this.hp = this.config.hp;
this.maxHp = this.config.hp;
this.x = CONFIG.OFFSET_X + c * CONFIG.CELL_WIDTH + CONFIG.CELL_WIDTH / 2;
this.y = CONFIG.OFFSET_Y + r * CONFIG.CELL_HEIGHT + CONFIG.CELL_HEIGHT / 2;
this.lastActionTime = 0;
this.animFrame = 0;
}
update(dt, now) {
this.animFrame += dt;
if (this.config.type === 'producer') {
if (now - this.lastActionTime > 10000) { // 每10秒生产阳光
createSun(this.x, this.y, false);
this.lastActionTime = now;
}
} else if (this.config.type === 'shooter') {
// 检查该行是否有僵尸
const hasZombieInRow = gameState.zombies.some(z => z.r === this.r && z.x > this.x);
if (hasZombieInRow && now - this.lastActionTime > this.config.rate) {
createBullet(this.x + 20, this.y, this.r, this.config.damage, this.config.effect);
this.lastActionTime = now;
}
}
}
draw(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
// 简单的呼吸动画
const scale = 1 + Math.sin(this.animFrame / 200) * 0.05;
ctx.scale(scale, scale);
// 绘制植物主体
ctx.fillStyle = this.config.color;
if (this.typeKey === 'SUNFLOWER') {
// 向日葵花瓣
for(let i=0; i<8; i++) {
ctx.beginPath();
ctx.rotate(Math.PI / 4);
ctx.ellipse(0, -15, 8, 15, 0, 0, Math.PI*2);
ctx.fill();
}
ctx.beginPath();
ctx.arc(0, 0, 12, 0, Math.PI*2);
ctx.fillStyle = '#5d4037'; // 花心
ctx.fill();
} else if (this.typeKey === 'PEASHOOTER' || this.typeKey === 'SNOWPEA') {
// 头部
ctx.beginPath();
ctx.arc(0, -5, 18, 0, Math.PI*2);
ctx.fill();
// 嘴巴
ctx.fillStyle = '#333';
ctx.beginPath();
ctx.arc(12, -5, 8, 0, Math.PI*2);
ctx.fill();
// 叶子
ctx.fillStyle = '#2e7d32';
ctx.beginPath();
ctx.ellipse(-5, 15, 10, 5, Math.PI/4, 0, Math.PI*2);
ctx.fill();
} else if (this.typeKey === 'WALLNUT') {
// 坚果身体
ctx.beginPath();
ctx.ellipse(0, 0, 20, 25, 0, 0, Math.PI*2);
ctx.fill();
// 裂纹表示受伤
if (this.hp < this.maxHp * 0.6) {
ctx.strokeStyle = '#3e2723';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(-5, -10);
ctx.lineTo(5, 5);
ctx.stroke();
}
}
// 血条
if (this.hp < this.maxHp) {
ctx.fillStyle = 'red';
ctx.fillRect(-15, -35, 30, 4);
ctx.fillStyle = '#0f0';
ctx.fillRect(-15, -35, 30 * (this.hp / this.maxHp), 4);
}
ctx.restore();
}
}
class Zombie {
constructor(r, typeKey) {
this.r = r;
this.typeKey = typeKey;
this.config = ZOMBIES[typeKey];
this.hp = this.config.hp;
this.maxHp = this.config.hp;
this.speed = this.config.speed;
this.x = canvas.width + 50;
this.y = CONFIG.OFFSET_Y + r * CONFIG.CELL_HEIGHT + CONFIG.CELL_HEIGHT / 2;
this.isEating = false;
this.eatTarget = null;
this.animFrame = 0;
this.slowFactor = 1;
}
update(dt) {
this.animFrame += dt;
if (this.isEating && this.eatTarget) {
if (this.eatTarget.hp > 0) {
this.eatTarget.hp -= this.config.damage * (dt / 1000);
} else {
this.isEating = false;
this.eatTarget = null;
}
} else {
this.x -= this.speed * this.slowFactor * (dt / 16); // 归一化速度
// 检查是否碰到植物
const col = Math.floor((this.x - CONFIG.OFFSET_X) / CONFIG.CELL_WIDTH);
if (col >= 0 && col < CONFIG.GRID_COLS) {
const plant = gameState.grid[this.r][col];
if (plant && Math.abs(plant.x - this.x) < 30) {
this.isEating = true;
this.eatTarget = plant;
}
}
// 减速效果恢复
if (this.slowFactor < 1) {
this.slowFactor += 0.001;
if (this.slowFactor > 1) this.slowFactor = 1;
}
}
// 游戏结束判断
if (this.x < CONFIG.OFFSET_X - 20) {
gameOver();
}
}
draw(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
// 走路摇晃
const wobble = Math.sin(this.animFrame / 100) * 2;
ctx.rotate(wobble * Math.PI / 180);
// 身体
ctx.fillStyle = this.config.color;
ctx.fillRect(-15, -20, 30, 50);
// 头
ctx.fillStyle = '#9e9e9e'; // 皮肤色
ctx.beginPath();
ctx.arc(0, -25, 15, 0, Math.PI*2);
ctx.fill();
// 眼睛
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(-5, -28, 4, 0, Math.PI*2);
ctx.arc(5, -28, 4, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(-5, -28, 1, 0, Math.PI*2);
ctx.arc(5, -28, 1, 0, Math.PI*2);
ctx.fill();
// 特殊头部装饰
if (this.typeKey === 'CONEHEAD') {
ctx.fillStyle = '#ff9800';
ctx.beginPath();
ctx.moveTo(-12, -35);
ctx.lineTo(12, -35);
ctx.lineTo(0, -60);
ctx.fill();
} else if (this.typeKey === 'BUCKETHEAD') {
ctx.fillStyle = '#607d8b';
ctx.fillRect(-14, -45, 28, 20);
}
// 手臂
ctx.fillStyle = this.config.color;
if (this.isEating) {
// 吃东西动作
ctx.fillRect(10, -10, 15, 8);
} else {
ctx.fillRect(10, -10 + wobble, 15, 8);
}
// 血条
ctx.fillStyle = 'red';
ctx.fillRect(-15, -50, 30, 4);
ctx.fillStyle = '#0f0';
ctx.fillRect(-15, -50, 30 * (this.hp / this.maxHp), 4);
ctx.restore();
}
}
class Bullet {
constructor(x, y, r, damage, effect) {
this.x = x;
this.y = y;
this.r = r;
this.damage = damage;
this.effect = effect;
this.speed = 5;
this.active = true;
}
update() {
this.x += this.speed;
if (this.x > canvas.width) {
this.active = false;
}
}
draw(ctx) {
ctx.fillStyle = this.effect === 'slow' ? '#00bcd4' : '#4caf50';
ctx.beginPath();
ctx.arc(this.x, this.y, 6, 0, Math.PI*2);
ctx.fill();
}
}
class Sun {
constructor(x, y, targetY, isNatural) {
this.x = x;
this.y = y;
this.targetY = targetY || y;
this.isNatural = isNatural;
this.active = true;
this.life = 10000; // 存在时间
this.vy = isNatural ? 1 : -2; // 自然下落,生产上升
}
update(dt) {
this.life -= dt;
if (this.life <= 0) this.active = false;
if (this.isNatural && this.y < this.targetY) {
this.y += this.vy;
} else if (!this.isNatural && this.y > this.targetY) {
this.y += this.vy;
}
}
draw(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(Date.now() / 500);
// 阳光光芒
ctx.fillStyle = '#f1c40f';
for(let i=0; i<8; i++) {
ctx.beginPath();
ctx.rotate(Math.PI / 4);
ctx.rect(-2, -15, 4, 10);
ctx.fill();
}
// 阳光核心
ctx.beginPath();
ctx.arc(0, 0, 12, 0, Math.PI*2);
ctx.fillStyle = '#ffeb3b';
ctx.fill();
ctx.strokeStyle = '#f57f17';
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
}
}
// 辅助函数
function createSun(x, y, isNatural) {
const targetY = isNatural ? CONFIG.OFFSET_Y + Math.random() * (CONFIG.GRID_ROWS * CONFIG.CELL_HEIGHT) : y - 50;
gameState.suns.push(new Sun(x, y, targetY, isNatural));
}
function createBullet(x, y, r, damage, effect) {
gameState.bullets.push(new Bullet(x, y, r, damage, effect));
}
function showMessage(msg) {
const el = document.getElementById('message-area');
el.innerText = msg;
el.style.opacity = 1;
setTimeout(() => {
el.style.opacity = 0;
}, 2000);
}
// 游戏主循环
function gameLoop(timestamp) {
if (gameState.isPaused || gameState.isGameOver) return;
const dt = timestamp - gameState.lastTime;
gameState.lastTime = timestamp;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 1. 绘制背景网格
drawBackground();
// 2. 生成逻辑
handleSpawning(dt, timestamp);
// 3. 更新与绘制实体
updateAndDrawEntities(dt);
// 4. 碰撞检测
checkCollisions();
// 5. UI 更新
updateUI();
requestAnimationFrame(gameLoop);
}
function drawBackground() {
// 草地
ctx.fillStyle = '#4caf50';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 网格线
ctx.strokeStyle = 'rgba(0,0,0,0.1)';
ctx.lineWidth = 1;
for (let r = 0; r <= CONFIG.GRID_ROWS; r++) {
ctx.beginPath();
ctx.moveTo(CONFIG.OFFSET_X, CONFIG.OFFSET_Y + r * CONFIG.CELL_HEIGHT);
ctx.lineTo(CONFIG.OFFSET_X + CONFIG.GRID_COLS * CONFIG.CELL_WIDTH, CONFIG.OFFSET_Y + r * CONFIG.CELL_HEIGHT);
ctx.stroke();
}
for (let c = 0; c <= CONFIG.GRID_COLS; c++) {
ctx.beginPath();
ctx.moveTo(CONFIG.OFFSET_X + c * CONFIG.CELL_WIDTH, CONFIG.OFFSET_Y);
ctx.lineTo(CONFIG.OFFSET_X + c * CONFIG.CELL_WIDTH, CONFIG.OFFSET_Y + CONFIG.GRID_ROWS * CONFIG.CELL_HEIGHT);
ctx.stroke();
}
// 房子区域
ctx.fillStyle = '#8d6e63';
ctx.fillRect(0, CONFIG.OFFSET_Y, CONFIG.OFFSET_X, CONFIG.GRID_ROWS * CONFIG.CELL_HEIGHT);
}
function handleSpawning(dt, now) {
// 自然阳光生成
gameState.sunSpawnTimer += dt;
if (gameState.sunSpawnTimer > 10000) { // 每10秒
createSun(CONFIG.OFFSET_X + Math.random() * (CONFIG.GRID_COLS * CONFIG.CELL_WIDTH), -50, true);
gameState.sunSpawnTimer = 0;
}
// 僵尸生成
if (gameState.mode === 'adventure' || gameState.mode === 'endless') {
gameState.zombieSpawnTimer += dt;
let spawnRate = 5000; // 默认5秒
if (gameState.wave > 5) spawnRate = 3000;
if (gameState.wave > 10) spawnRate = 1500;
if (gameState.zombieSpawnTimer > spawnRate) {
const r = Math.floor(Math.random() * CONFIG.GRID_ROWS);
let type = 'NORMAL';
const rand = Math.random();
if (gameState.wave > 2 && rand > 0.7) type = 'CONEHEAD';
if (gameState.wave > 5 && rand > 0.9) type = 'BUCKETHEAD';
gameState.zombies.push(new Zombie(r, type));
gameState.zombieSpawnTimer = 0;
// 增加波数
if (gameState.zombies.length % 5 === 0) gameState.wave++;
}
} else if (gameState.mode === 'minigame') {
// 传送带模式:直接给植物,僵尸快速生成
// 简化处理:这里仅做演示,实际应实现传送带逻辑
}
}
function updateAndDrawEntities(dt) {
// 植物
gameState.plants.forEach(p => {
p.update(dt, Date.now());
p.draw(ctx);
});
// 僵尸
gameState.zombies.forEach((z, index) => {
z.update(dt);
z.draw(ctx);
if (z.hp <= 0) {
gameState.zombies.splice(index, 1);
}
});
// 子弹
gameState.bullets.forEach((b, index) => {
b.update();
b.draw(ctx);
if (!b.active) {
gameState.bullets.splice(index, 1);
}
});
// 阳光
gameState.suns.forEach((s, index) => {
s.update(dt);
s.draw(ctx);
if (!s.active) {
gameState.suns.splice(index, 1);
}
});
}
function checkCollisions() {
// 子弹击中僵尸
gameState.bullets.forEach(b => {
if (!b.active) return;
gameState.zombies.forEach(z => {
if (z.r === b.r && Math.abs(z.x - b.x) < 20 && Math.abs(z.y - b.y) < 30) {
z.hp -= b.damage;
b.active = false;
if (b.effect === 'slow') {
z.slowFactor = 0.5;
}
// 简单击中特效
createParticle(b.x, b.y, '#fff');
}
});
});
}
function createParticle(x, y, color) {
// 简化粒子系统,仅用于视觉反馈
}
function updateUI() {
document.getElementById('sun-display').innerText = Math.floor(gameState.sun);
// 更新卡片状态
const cards = document.querySelectorAll('.plant-card');
cards.forEach(card => {
const cost = parseInt(card.dataset.cost);
if (gameState.sun < cost) {
card.classList.add('disabled');
} else {
card.classList.remove('disabled');
}
});
}
// 交互处理
canvas.addEventListener('click', (e) => {
if (gameState.isPaused || gameState.isGameOver) return;
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// 1. 检查是否点击阳光
let sunClicked = false;
gameState.suns.forEach((s, index) => {
if (Math.abs(x - s.x) < 30 && Math.abs(y - s.y) < 30) {
gameState.sun += CONFIG.SUN_VALUE;
s.active = false;
sunClicked = true;
// 播放音效逻辑占位
}
});
if (sunClicked) return;
// 2. 铲子逻辑
if (gameState.isShovelActive) {
const c = Math.floor((x - CONFIG.OFFSET_X) / CONFIG.CELL_WIDTH);
const r = Math.floor((y - CONFIG.OFFSET_Y) / CONFIG.CELL_HEIGHT);
if (r >= 0 && r < CONFIG.GRID_ROWS && c >= 0 && c < CONFIG.GRID_COLS) {
const plant = gameState.grid[r][c];
if (plant) {
gameState.grid[r][c] = null;
gameState.plants = gameState.plants.filter(p => p !== plant);
gameState.isShovelActive = false;
document.getElementById('shovel-btn').classList.remove('active');
}
}
return;
}
// 3. 种植逻辑
if (gameState.selectedPlant) {
const c = Math.floor((x - CONFIG.OFFSET_X) / CONFIG.CELL_WIDTH);
const r = Math.floor((y - CONFIG.OFFSET_Y) / CONFIG.CELL_HEIGHT);
// 边界检查
if (r >= 0 && r < CONFIG.GRID_ROWS && c >= 0 && c < CONFIG.GRID_COLS) {
// 检查是否已有植物
if (!gameState.grid[r][c]) {
const plantConfig = PLANTS[gameState.selectedPlant];
if (gameState.sun >= plantConfig.cost) {
gameState.sun -= plantConfig.cost;
const newPlant = new Plant(r, c, gameState.selectedPlant);
gameState.plants.push(newPlant);
gameState.grid[r][c] = newPlant;
// 取消选择
gameState.selectedPlant = null;
document.querySelectorAll('.plant-card').forEach(c => c.classList.remove('selected'));
} else {
showMessage("阳光不足!");
}
} else {
showMessage("这里已经有植物了!");
}
}
}
});
// UI 事件绑定
function initUI() {
const plantBar = document.getElementById('plant-bar');
Object.keys(PLANTS).forEach(key => {
const p = PLANTS[key];
const div = document.createElement('div');
div.className = 'plant-card';
div.dataset.id = key;
div.dataset.cost = p.cost;
div.innerHTML = `
<div class="card-preview" style="background:${p.color}"></div>
<div class="card-cost">${p.cost}</div>
`;
div.onclick = () => {
if (gameState.sun >= p.cost) {
gameState.selectedPlant = key;
gameState.isShovelActive = false;
document.getElementById('shovel-btn').classList.remove('active');
document.querySelectorAll('.plant-card').forEach(c => c.classList.remove('selected'));
div.classList.add('selected');
}
};
plantBar.appendChild(div);
});
// 铲子
document.getElementById('shovel-btn').onclick = function() {
gameState.isShovelActive = !gameState.isShovelActive;
gameState.selectedPlant = null;
document.querySelectorAll('.plant-card').forEach(c => c.classList.remove('selected'));
this.classList.toggle('active');
};
// 暂停
const pauseBtn = document.getElementById('pause-btn');
const pausePanel = document.getElementById('pause-panel');
const resumeBtn = document.getElementById('resume-btn');
pauseBtn.onclick = () => {
gameState.isPaused = true;
pausePanel.style.display = 'block';
};
resumeBtn.onclick = () => {
gameState.isPaused = false;
pausePanel.style.display = 'none';
gameState.lastTime = performance.now();
requestAnimationFrame(gameLoop);
};
// 菜单模式选择
document.querySelectorAll('.mode-btn').forEach(btn => {
btn.onclick = () => {
const mode = btn.dataset.mode;
startGame(mode);
};
});
}
function startGame(mode) {
gameState.mode = mode;
gameState.sun = CONFIG.INITIAL_SUN;
gameState.plants = [];
gameState.zombies = [];
gameState.bullets = [];
gameState.suns = [];
gameState.wave = 1;
gameState.isGameOver = false;
gameState.isPaused = false;
initGrid();
document.getElementById('menu-overlay').classList.add('hidden');
document.getElementById('top-bar').classList.remove('hidden');
showMessage(`开始模式: ${mode === 'adventure' ? '冒险' : mode === 'endless' ? '无尽' : mode}`);
gameState.lastTime = performance.now();
requestAnimationFrame(gameLoop);
}
function gameOver() {
gameState.isGameOver = true;
alert("僵尸吃掉了你的脑子!游戏结束。");
location.reload();
}
// 启动
initUI();
</script>
</body>
</html>
这里空空如也








有帮助,赞一个