介紹
1,基本講解
“UTF—8” 防止中文亂碼
"<title>" 這是網頁標題
“<!DOCTYPE>”**的作用是聲明這是“HTML5”,防止不同瀏覽器出現報錯信息
**
如果有不理解的歡迎私信
源碼
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>植物大戰殭屍青春版</title>
<style>
body {
margin: 0;
padding: 0;
background: linear-gradient(135deg, #2c3e50 0%, #4a5568 100%);
font-family: 'Microsoft YaHei', Arial, sans-serif;
overflow: hidden;
}
#game-container {
position: relative;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
#game-header {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
color: white;
text-align: center;
z-index: 10;
}
h1 {
font-size: 3rem;
margin: 0;
text-shadow: 3px 3px 0px rgba(0, 0, 0, 0.3);
letter-spacing: 2px;
background: linear-gradient(to right, #ffeb3b, #ff9800);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
#sun-counter {
position: absolute;
top: 100px;
right: 50px;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px 20px;
border-radius: 10px;
font-size: 1.5rem;
font-weight: bold;
border: 2px solid #ffeb3b;
box-shadow: 0 4px 15px rgba(255, 235, 59, 0.3);
}
#health-container {
position: absolute;
top: 100px;
left: 50px;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px 20px;
border-radius: 10px;
font-size: 1.2rem;
font-weight: bold;
border: 2px solid #ff4444;
box-shadow: 0 4px 15px rgba(255, 68, 68, 0.3);
}
#health-bar {
width: 200px;
height: 20px;
background: rgba(255, 255, 255, 0.2);
border-radius: 10px;
overflow: hidden;
margin-top: 5px;
border: 1px solid #333;
}
#health-fill {
height: 100%;
background: linear-gradient(90deg, #ff4444, #ff8800);
border-radius: 10px;
transition: width 0.3s ease-in-out;
box-shadow: 0 0 10px rgba(255, 68, 68, 0.5);
}
canvas {
border: 3px solid #4caf50;
box-shadow: 0 0 30px rgba(76, 175, 80, 0.3);
background-color: #008000;
border-radius: 10px;
}
#plant-selector {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 15px;
background: rgba(0, 0, 0, 0.8);
padding: 15px 25px;
border-radius: 15px;
border: 2px solid #4caf50;
box-shadow: 0 4px 20px rgba(76, 175, 80, 0.3);
}
.plant-item {
width: 80px;
height: 80px;
background: linear-gradient(135deg, #66bb6a, #43a047);
border-radius: 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
position: relative;
overflow: hidden;
}
.plant-item:hover:not(.disabled) {
transform: translateY(-5px) scale(1.05);
box-shadow: 0 6px 20px rgba(102, 187, 106, 0.4);
border-color: #ffeb3b;
}
.plant-item.selected {
border-color: #ffeb3b;
background: linear-gradient(135deg, #81c784, #66bb6a);
box-shadow: 0 0 20px rgba(255, 235, 59, 0.5);
}
.plant-item.disabled {
opacity: 0.5;
cursor: not-allowed;
background: linear-gradient(135deg, #757575, #616161);
}
.plant-icon {
font-size: 2rem;
color: white;
margin-bottom: 5px;
}
.plant-cost {
background: rgba(0, 0, 0, 0.5);
color: #ffeb3b;
font-weight: bold;
padding: 2px 8px;
border-radius: 10px;
font-size: 0.9rem;
}
.game-over {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.game-over-text {
color: #ff0000;
font-size: 3rem;
font-weight: bold;
text-align: center;
text-shadow: 0 0 10px #ff0000, 0 0 20px #ff0000, 0 0 30px #ff0000;
animation: shake 0.5s infinite, flash 1s infinite;
transform-origin: center;
letter-spacing: 2px;
padding: 20px;
border: 3px solid #ff0000;
border-radius: 15px;
background: rgba(255, 0, 0, 0.1);
}
@keyframes shake {
0%, 100% { transform: translateX(0) rotate(0deg); }
25% { transform: translateX(-5px) rotate(-2deg); }
75% { transform: translateX(5px) rotate(2deg); }
}
@keyframes flash {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
/* 美化草地邊框裝飾 */
canvas::after {
content: '';
position: absolute;
bottom: -10px;
left: 0;
width: 100%;
height: 10px;
background: repeating-linear-gradient(
45deg,
#4caf50,
#4caf50 10px,
#66bb6a 10px,
#66bb6a 20px
);
border-radius: 0 0 10px 10px;
}
</style>
</head>
<body>
<div id="game-container">
<div id="game-header">
<h1>植物大戰殭屍青春版</h1>
</div>
<div id="sun-counter">陽光: 150</div>
<div id="health-container">
玩家生命
<div id="health-bar">
<div id="health-fill"></div>
</div>
</div>
<canvas id="gameCanvas"></canvas>
<div id="plant-selector">
<div class="plant-item" data-plant="sunflower">
<div class="plant-icon">☀️</div>
<div class="plant-cost">50</div>
</div>
<div class="plant-item" data-plant="peashooter">
<div class="plant-icon">🌱</div>
<div class="plant-cost">100</div>
</div>
<div class="plant-item" data-plant="wallnut">
<div class="plant-icon">🌰</div>
<div class="plant-cost">50</div>
</div>
<div class="plant-item" data-plant="snowpea">
<div class="plant-icon">❄️</div>
<div class="plant-cost">175</div>
</div>
<div class="plant-item" data-plant="cherrybomb">
<div class="plant-icon">💣</div>
<div class="plant-cost">150</div>
</div>
</div>
</div>
<script>
// 獲取Canvas元素
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const sunCounter = document.getElementById('sun-counter');
const healthFill = document.getElementById('health-fill');
const plantItems = document.querySelectorAll('.plant-item');
// 設置Canvas尺寸
canvas.width = 720;
canvas.height = 500;
// 遊戲狀態變量
let suns = 150;
let selectedPlant = null;
let plants = [];
let zombies = [];
let projectiles = [];
let sunsFalling = [];
let gameTime = 0;
let lastZombieSpawn = 0;
let zombieSpawnRate = 8000; // 8秒
const gridSize = 80;
const rows = 6;
const cols = 9;
let gameOver = false;
let zombiesGotThrough = 0;
const maxZombiesThrough = 3;
// 添加預覽相關變量
let mousePos = { x: 0, y: 0 };
let showPreview = false;
// 更新陽光計數器顯示
function updateSunCounter() {
sunCounter.textContent = `陽光: ${suns}`;
}
// 更新植物選擇器狀態
function updatePlantSelection() {
plantItems.forEach(item => {
const cost = parseInt(item.querySelector('.plant-cost').textContent);
if (suns >= cost) {
item.classList.remove('disabled');
} else {
item.classList.add('disabled');
if (selectedPlant === item.dataset.plant) {
selectedPlant = null;
item.classList.remove('selected');
}
}
});
}
// 更新玩家血量顯示
function updateHealthBar() {
const healthPercentage = Math.max(0, 100 - (zombiesGotThrough / maxZombiesThrough) * 100);
healthFill.style.width = `${healthPercentage}%`;
// 添加血量變化的動畫效果
healthFill.classList.add('health-change');
setTimeout(() => {
healthFill.classList.remove('health-change');
}, 300);
}
// 檢查遊戲是否結束
function checkGameOver() {
if (zombiesGotThrough >= maxZombiesThrough && !gameOver) {
gameOver = true;
// 創建遊戲結束覆蓋層
const gameOverOverlay = document.createElement('div');
gameOverOverlay.className = 'game-over';
const gameOverText = document.createElement('div');
gameOverText.className = 'game-over-text';
gameOverText.textContent = '看什麼看,殭屍來抓你了!!!!!';
gameOverOverlay.appendChild(gameOverText);
document.body.appendChild(gameOverOverlay);
}
}
// 植物類
class Plant {
constructor(x, y, type) {
this.x = x;
this.y = y;
this.type = type;
this.width = 60;
this.height = 60;
this.lastActionTime = 0;
this.actionCooldown = type === 'sunflower' ? 15000 : type === 'peashooter' || type === 'snowpea' ? 1500 : 0;
// 加強植物血量 - 向日葵和射手血量從150增加到300,堅果從500增加到800
switch (type) {
case 'sunflower':
this.health = 300;
this.color = '#ffcc00';
break;
case 'peashooter':
this.health = 300;
this.color = '#00cc00';
break;
case 'wallnut':
this.health = 800;
this.color = '#996633';
break;
case 'snowpea':
this.health = 300;
this.color = '#33ccff';
break;
case 'cherrybomb':
this.health = 100;
this.color = '#ff0000';
this.fuseTime = 3000; // 3秒引爆時間
this.exploded = false;
break;
}
this.maxHealth = this.health;
}
update() {
const now = Date.now();
// 向日葵生產陽光
if (this.type === 'sunflower' && now - this.lastActionTime > this.actionCooldown) {
this.lastActionTime = now;
this.produceSun();
}
// 豌豆射手發射豌豆
if ((this.type === 'peashooter' || this.type === 'snowpea') && now - this.lastActionTime > this.actionCooldown) {
this.lastActionTime = now;
this.shootPea();
}
// 櫻桃炸彈爆炸邏輯
if (this.type === 'cherrybomb' && !this.exploded) {
// 檢查是否到了引爆時間
if (now - this.lastActionTime > this.fuseTime) {
this.explode();
}
}
}
produceSun() {
sunsFalling.push({
x: this.x + this.width / 2,
y: this.y,
radius: 15,
color: '#ffeb3b',
speed: 1,
collected: false,
spawnTime: Date.now()
});
}
shootPea() {
const isSnow = this.type === 'snowpea';
projectiles.push({
x: this.x + this.width,
y: this.y + this.height / 2,
width: 10,
height: 10,
speed: isSnow ? 4 : 5,
color: isSnow ? '#33ccff' : '#00cc00',
isSnow: isSnow
});
}
draw() {
ctx.save();
ctx.translate(this.x, this.y);
// 繪製植物基礎形狀(帶陰影)
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.width / 2, this.height / 2, this.width / 2 - 5, 0, Math.PI * 2);
ctx.fill();
// 重置陰影
ctx.shadowBlur = 0;
// 根據類型繪製不同的特徵
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
switch (this.type) {
case 'sunflower':
// 繪製花瓣
for (let i = 0; i < 8; i++) {
const angle = (i * Math.PI * 2) / 8;
ctx.beginPath();
ctx.ellipse(
this.width / 2 + Math.cos(angle) * 25,
this.height / 2 + Math.sin(angle) * 25,
10, 15, 0, 0, Math.PI * 2
);
ctx.fillStyle = '#ff9900';
ctx.fill();
ctx.strokeStyle = '#000';
ctx.stroke();
}
// 繪製中心(帶漸變)
const sunGradient = ctx.createRadialGradient(
this.width / 2 - 3, this.height / 2 - 3, 2,
this.width / 2, this.height / 2, 10
);
sunGradient.addColorStop(0, '#ffffff');
sunGradient.addColorStop(1, '#ffcc00');
ctx.beginPath();
ctx.arc(this.width / 2, this.height / 2, 10, 0, Math.PI * 2);
ctx.fillStyle = sunGradient;
ctx.fill();
break;
case 'peashooter':
// 繪製葉子
ctx.fillStyle = '#009900';
ctx.beginPath();
ctx.ellipse(this.width / 2, this.height / 3, 15, 8, 0, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(this.width / 2, this.height * 2 / 3, 15, 8, 0, 0, Math.PI * 2);
ctx.fill();
// 繪製發射管
ctx.fillStyle = '#663300';
ctx.fillRect(this.width / 2 + 10, this.height / 2 - 5, 15, 10);
break;
case 'wallnut':
// 繪製紋理
ctx.fillStyle = '#7a5230';
ctx.fillRect(0, this.height / 2 - 2, this.width, 4);
ctx.fillRect(this.width / 2 - 2, 0, 4, this.height);
// 添加裂紋效果
if (this.health < 350) {
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(this.width - 10, this.height - 10);
ctx.stroke();
}
if (this.health < 200) {
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(this.width - 10, 10);
ctx.lineTo(10, this.height - 10);
ctx.stroke();
}
break;
case 'snowpea':
// 繪製葉子(帶漸變)
const leafGradient = ctx.createLinearGradient(
this.width / 2 - 15, this.height / 3,
this.width / 2 + 15, this.height / 3
);
leafGradient.addColorStop(0, '#004444');
leafGradient.addColorStop(1, '#006666');
ctx.fillStyle = leafGradient;
ctx.beginPath();
ctx.ellipse(this.width / 2, this.height / 3, 15, 8, 0, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(this.width / 2, this.height * 2 / 3, 15, 8, 0, 0, Math.PI * 2);
ctx.fill();
// 繪製發射管
ctx.fillStyle = '#336699';
ctx.fillRect(this.width / 2 + 10, this.height / 2 - 5, 15, 10);
break;
case 'cherrybomb':
// 繪製簡化的櫻桃炸彈
ctx.fillStyle = '#ffcccc';
ctx.beginPath();
ctx.arc(this.width / 2, this.height / 2, 10, 0, Math.PI * 2);
ctx.fill();
// 繪製導火索
ctx.strokeStyle = '#996633';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(this.width / 2 + 12, this.height / 2 - 12);
ctx.lineTo(this.width / 2 + 20, this.height / 2 - 24);
ctx.stroke();
break;
}
// 繪製血條
ctx.fillStyle = '#ff0000';
ctx.fillRect(5, 5, (this.width - 10) * (this.health / this.maxHealth), 5);
ctx.strokeStyle = '#000';
ctx.strokeRect(5, 5, this.width - 10, 5);
ctx.restore();
}
explode() {
this.exploded = true;
// 爆炸效果
createExplosion(this.x + this.width / 2, this.y + this.height / 2, 100);
// 清除爆炸範圍內的殭屍
zombies.forEach(zombie => {
const dx = zombie.x + zombie.width / 2 - (this.x + this.width / 2);
const dy = zombie.y + zombie.height / 2 - (this.y + this.height / 2);
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 100) {
zombie.health = 0;
zombie.isDead = true;
}
});
// 移除櫻桃炸彈本身
const index = plants.findIndex(p => p === this);
if (index !== -1) {
plants.splice(index, 1);
}
}
}
// 殭屍類
class Zombie {
constructor(row) {
this.x = canvas.width;
this.y = row * gridSize + 20;
this.width = 60;
this.height = 80;
this.speed = 1;
this.health = 100;
this.type = 'normal';
this.color = '#8b4513';
this.isSlowed = false;
this.slowTimer = 0;
this.isDead = false;
// 隨機選擇殭屍類型
const rand = Math.random();
if (rand < 0.6) {
this.type = 'normal';
this.health = 100;
// 將普通殭屍速度從1降低到0.6
this.speed = 0.6;
this.color = '#8b4513';
} else if (rand < 0.8) {
this.type = 'bucket';
this.health = 250;
// 將鐵桶殭屍速度從0.7降低到0.5
this.speed = 0.5;
this.color = '#333333';
} else {
this.type = 'cone';
this.health = 180;
// 將路障殭屍速度從0.9降低到0.55
this.speed = 0.55;
this.color = '#ff6600';
}
}
update() {
if (this.isDead) return;
// 檢查是否有植物在前方,如果有則停止移動
let hasPlantInFront = false;
plants.forEach(plant => {
// 檢查植物是否在殭屍前方(同一行且在殭屍右側)
if (plant.y >= this.y - 20 && plant.y <= this.y + 20 &&
plant.x >= this.x && plant.x < this.x + 50) {
hasPlantInFront = true;
}
});
// 如果有植物在前方,則不移動;否則正常移動
if (!hasPlantInFront) {
// 應用減速效果
let moveSpeed = this.speed;
if (this.isSlowed) {
moveSpeed *= 0.5;
this.slowTimer -= 16.67; // 假設每幀約16.67ms
if (this.slowTimer <= 0) {
this.isSlowed = false;
}
}
this.x -= moveSpeed;
}
// 檢查是否到達遊戲結束點
if (this.x < -this.width) {
// 殭屍突破防線,減少玩家生命
zombiesGotThrough++;
updateHealthBar();
checkGameOver();
this.isDead = true;
}
}
draw() {
if (this.isDead) return;
ctx.save();
ctx.translate(this.x, this.y);
// 繪製殭屍身體
ctx.fillStyle = this.color;
ctx.fillRect(0, 30, this.width, 50);
// 繪製殭屍頭部(帶陰影)
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
ctx.shadowBlur = 8;
ctx.beginPath();
ctx.arc(this.width / 2, 20, 20, 0, Math.PI * 2);
ctx.fillStyle = '#ffcc99';
ctx.fill();
ctx.shadowBlur = 0;
// 根據類型繪製不同的特徵
if (this.type === 'bucket') {
// 繪製鐵桶(帶漸變)
const bucketGradient = ctx.createLinearGradient(10, 0, 10, 25);
bucketGradient.addColorStop(0, '#666666');
bucketGradient.addColorStop(1, '#333333');
ctx.fillStyle = bucketGradient;
ctx.fillRect(10, 0, 40, 25);
ctx.fillStyle = '#777777';
ctx.fillRect(15, 5, 30, 5);
} else if (this.type === 'cone') {
// 繪製路障(帶漸變)
const coneGradient = ctx.createLinearGradient(this.width / 2, -10, this.width / 2, 20);
coneGradient.addColorStop(0, '#ff9900');
coneGradient.addColorStop(1, '#cc5500');
ctx.fillStyle = coneGradient;
ctx.beginPath();
ctx.moveTo(this.width / 2, -10);
ctx.lineTo(5, 20);
ctx.lineTo(55, 20);
ctx.closePath();
ctx.fill();
}
// 繪製眼睛(帶反光)
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(this.width / 2 - 5, 18, 4, 0, Math.PI * 2);
ctx.arc(this.width / 2 + 5, 18, 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(this.width / 2 - 6, 18, 2, 0, Math.PI * 2);
ctx.arc(this.width / 2 + 4, 18, 2, 0, Math.PI * 2);
ctx.fill();
// 眼睛反光
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(this.width / 2 - 4, 17, 1, 0, Math.PI * 2);
ctx.arc(this.width / 2 + 6, 17, 1, 0, Math.PI * 2);
ctx.fill();
// 繪製嘴巴
ctx.strokeStyle = '#000000';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(this.width / 2, 25, 8, 0, Math.PI, false);
ctx.stroke();
// 繪製牙齒
if (this.type === 'normal') {
ctx.fillStyle = '#ffffff';
ctx.fillRect(this.width / 2 - 6, 25, 3, 3);
ctx.fillRect(this.width / 2, 25, 3, 3);
ctx.fillRect(this.width / 2 + 6, 25, 3, 3);
}
// 繪製血條
ctx.fillStyle = '#ff0000';
ctx.fillRect(0, 0, this.width * (this.health / (this.type === 'normal' ? 100 : this.type === 'bucket' ? 250 : 180)), 5);
ctx.strokeStyle = '#000';
ctx.strokeRect(0, 0, this.width, 5);
// 如果被減速,繪製減速效果
if (this.isSlowed) {
ctx.fillStyle = 'rgba(51, 204, 255, 0.3)';
ctx.beginPath();
ctx.arc(this.width / 2, 40, 30, 0, Math.PI * 2);
ctx.fill();
// 添加減速粒子效果
ctx.fillStyle = 'rgba(51, 204, 255, 0.7)';
for (let i = 0; i < 5; i++) {
const angle = Math.random() * Math.PI * 2;
const radius = 25 + Math.random() * 5;
ctx.beginPath();
ctx.arc(
this.width / 2 + Math.cos(angle) * radius,
40 + Math.sin(angle) * radius,
2, 0, Math.PI * 2
);
ctx.fill();
}
}
ctx.restore();
}
}
// 檢查矩形碰撞
function checkCollision(rect1, rect2) {
return !(rect2.y + rect2.height < rect1.y ||
rect2.y > rect1.y + rect1.height ||
rect2.x + rect2.width < rect1.x ||
rect2.x > rect1.x + rect1.width);
}
// 爆炸效果函數
function createExplosion(x, y, radius) {
// 爆炸的圓形波紋效果
ctx.save();
ctx.translate(x, y);
// 繪製爆炸的中心
const explosionGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, radius);
explosionGradient.addColorStop(0, '#ff9900');
explosionGradient.addColorStop(0.5, '#ff3300');
explosionGradient.addColorStop(1, 'rgba(255, 0, 0, 0)');
ctx.fillStyle = explosionGradient;
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
// 生成殭屍
function spawnZombie() {
if (gameOver) return;
const now = Date.now();
if (now - lastZombieSpawn > zombieSpawnRate) {
lastZombieSpawn = now;
// 隨機選擇一行
const row = Math.floor(Math.random() * rows);
zombies.push(new Zombie(row));
// 隨着遊戲時間增加,殭屍生成速度變快
gameTime += zombieSpawnRate;
if (gameTime > 45000) { // 45秒後
zombieSpawnRate = 6000;
} else if (gameTime > 90000) { // 90秒後
zombieSpawnRate = 4500;
}
}
}
// 植物種植函數
function plantAt(x, y) {
if (gameOver) return;
// 計算網格位置
const col = Math.floor(x / gridSize);
const row = Math.floor((y - 20) / gridSize);
// 檢查是否在有效區域內
if (col >= 0 && col < cols && row >= 0 && row < rows && selectedPlant) {
// 檢查該位置是否已有植物
const hasPlant = plants.some(plant =>
Math.floor(plant.x / gridSize) === col &&
Math.floor((plant.y - 20) / gridSize) === row
);
if (!hasPlant) {
// 獲取植物成本
let cost = 0;
switch (selectedPlant) {
case 'sunflower': cost = 50; break;
case 'peashooter': cost = 100; break;
case 'wallnut': cost = 50; break;
case 'snowpea': cost = 175; break;
case 'cherrybomb': cost = 150; break;
}
// 檢查陽光是否足夠
if (suns >= cost) {
suns -= cost;
updateSunCounter();
// 在網格中心創建植物
plants.push(new Plant(
col * gridSize,
row * gridSize + 20,
selectedPlant
));
// 取消選擇
selectedPlant = null;
plantItems.forEach(item => item.classList.remove('selected'));
}
}
}
}
// 繪製植物預覽
function drawPlantPreview() {
if (!selectedPlant || !showPreview || gameOver) return;
// 計算網格位置
const col = Math.floor(mousePos.x / gridSize);
const row = Math.floor((mousePos.y - 20) / gridSize);
// 檢查是否在有效區域內
if (col >= 0 && col < cols && row >= 0 && row < rows) {
const x = col * gridSize;
const y = row * gridSize + 20;
// 檢查該位置是否已有植物
const hasPlant = plants.some(plant =>
Math.floor(plant.x / gridSize) === col &&
Math.floor((plant.y - 20) / gridSize) === row
);
// 獲取植物成本
let cost = 0;
switch (selectedPlant) {
case 'sunflower': cost = 50; break;
case 'peashooter': cost = 100; break;
case 'wallnut': cost = 50; break;
case 'snowpea': cost = 175; break;
case 'cherrybomb': cost = 150; break;
}
const canPlant = !hasPlant && suns >= cost;
ctx.save();
ctx.translate(x, y);
// 設置半透明效果
ctx.globalAlpha = 0.5;
// 根據是否可種植設置顏色
if (canPlant) {
ctx.strokeStyle = '#00ff00';
} else {
ctx.strokeStyle = '#ff0000';
}
ctx.lineWidth = 3;
ctx.setLineDash([5, 5]);
// 繪製預覽邊框
ctx.strokeRect(2, 2, gridSize - 4, gridSize - 4);
// 繪製植物預覽形狀
let previewColor = '';
switch (selectedPlant) {
case 'sunflower': previewColor = '#ffcc00'; break;
case 'peashooter': previewColor = '#00cc00'; break;
case 'wallnut': previewColor = '#996633'; break;
case 'snowpea': previewColor = '#33ccff'; break;
case 'cherrybomb': previewColor = '#ff0000'; break;
}
ctx.fillStyle = previewColor;
ctx.beginPath();
ctx.arc(gridSize / 2, gridSize / 2, gridSize / 2 - 5, 0, Math.PI * 2);
ctx.fill();
// 繪製簡單的植物特徵(使預覽更明顯)
switch (selectedPlant) {
case 'sunflower':
// 繪製簡化的花瓣
for (let i = 0; i < 8; i++) {
const angle = (i * Math.PI * 2) / 8;
ctx.beginPath();
ctx.ellipse(
gridSize / 2 + Math.cos(angle) * 25,
gridSize / 2 + Math.sin(angle) * 25,
10, 15, 0, 0, Math.PI * 2
);
ctx.fillStyle = '#ff9900';
ctx.fill();
}
// 繪製中心
ctx.beginPath();
ctx.arc(gridSize / 2, gridSize / 2, 10, 0, Math.PI * 2);
ctx.fillStyle = '#ffcc00';
ctx.fill();
break;
case 'peashooter':
// 繪製簡化的葉子
ctx.fillStyle = '#009900';
ctx.beginPath();
ctx.ellipse(gridSize / 2, gridSize / 3, 15, 8, 0, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(gridSize / 2, gridSize * 2 / 3, 15, 8, 0, 0, Math.PI * 2);
ctx.fill();
// 繪製簡化的發射管
ctx.fillStyle = '#663300';
ctx.fillRect(gridSize / 2 + 10, gridSize / 2 - 5, 15, 10);
break;
case 'wallnut':
// 繪製簡化的堅果紋理
ctx.fillStyle = '#7a5230';
ctx.fillRect(0, gridSize / 2 - 2, gridSize, 4);
ctx.fillRect(gridSize / 2 - 2, 0, 4, gridSize);
break;
case 'snowpea':
// 繪製簡化的葉子
ctx.fillStyle = '#006666';
ctx.beginPath();
ctx.ellipse(gridSize / 2, gridSize / 3, 15, 8, 0, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(gridSize / 2, gridSize * 2 / 3, 15, 8, 0, 0, Math.PI * 2);
ctx.fill();
// 繪製簡化的發射管
ctx.fillStyle = '#336699';
ctx.fillRect(gridSize / 2 + 10, gridSize / 2 - 5, 15, 10);
break;
case 'cherrybomb':
// 繪製簡化的櫻桃炸彈
ctx.fillStyle = '#ffcccc';
ctx.beginPath();
ctx.arc(gridSize / 2, gridSize / 2, 10, 0, Math.PI * 2);
ctx.fill();
// 繪製導火索
ctx.strokeStyle = '#996633';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(gridSize / 2 + 12, gridSize / 2 - 12);
ctx.lineTo(gridSize / 2 + 20, gridSize / 2 - 24);
ctx.stroke();
break;
}
// 重置透明度和線條樣式
ctx.globalAlpha = 1;
ctx.setLineDash([]);
ctx.restore();
}
}
// 遊戲主循環
function gameLoop() {
if (gameOver) return;
// 清空畫布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 繪製背景漸變
const backgroundGradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
backgroundGradient.addColorStop(0, '#006400');
backgroundGradient.addColorStop(1, '#004d00');
ctx.fillStyle = backgroundGradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 繪製草地紋理
ctx.fillStyle = 'rgba(0, 128, 0, 0.3)';
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
const x = j * gridSize;
const y = i * gridSize + 20;
// 隨機的小草圖案
if (Math.random() > 0.7) {
ctx.beginPath();
ctx.moveTo(x + 40, y + 70);
ctx.lineTo(x + 35, y + 60);
ctx.lineTo(x + 40, y + 65);
ctx.lineTo(x + 45, y + 60);
ctx.closePath();
ctx.fill();
}
}
}
// 繪製遊戲網格(更微妙的網格線)
ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
ctx.lineWidth = 1;
for (let i = 0; i <= cols; i++) {
ctx.beginPath();
ctx.moveTo(i * gridSize, 20);
ctx.lineTo(i * gridSize, canvas.height);
ctx.stroke();
}
for (let i = 0; i <= rows; i++) {
ctx.beginPath();
ctx.moveTo(0, i * gridSize + 20);
ctx.lineTo(canvas.width, i * gridSize + 20);
ctx.stroke();
}
// 更新和繪製植物
plants.forEach(plant => {
plant.update();
plant.draw();
});
// 生成和更新殭屍
spawnZombie();
zombies.forEach(zombie => {
zombie.update();
zombie.draw();
});
// 移除死亡的殭屍
zombies = zombies.filter(zombie => !zombie.isDead);
// 更新和繪製子彈
projectiles.forEach((projectile, index) => {
projectile.x += projectile.speed;
// 繪製子彈(帶粒子效果)
ctx.fillStyle = projectile.color;
if (projectile.isSnow) {
// 雪豌豆子彈是圓形(帶漸變)
const snowGradient = ctx.createRadialGradient(
projectile.x - 2, projectile.y - 2, 1,
projectile.x, projectile.y, projectile.width / 2
);
snowGradient.addColorStop(0, '#ffffff');
snowGradient.addColorStop(1, projectile.color);
ctx.beginPath();
ctx.arc(projectile.x, projectile.y, projectile.width / 2, 0, Math.PI * 2);
ctx.fillStyle = snowGradient;
ctx.fill();
// 雪粒子效果
ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
for (let i = 0; i < 3; i++) {
const angle = Math.random() * Math.PI * 2;
const radius = projectile.width / 2 + 2;
ctx.beginPath();
ctx.arc(
projectile.x + Math.cos(angle) * radius,
projectile.y + Math.sin(angle) * radius,
1, 0, Math.PI * 2
);
ctx.fill();
}
} else {
// 普通豌豆子彈是矩形
ctx.fillRect(projectile.x, projectile.y - projectile.height / 2, projectile.width, projectile.height);
}
// 檢查子彈是否超出畫布
if (projectile.x > canvas.width) {
projectiles.splice(index, 1);
}
});
// 更新和繪製掉落的陽光
sunsFalling.forEach((sun, index) => {
sun.y += sun.speed;
// 繪製陽光(帶漸變和發光效果)
ctx.shadowColor = sun.color;
ctx.shadowBlur = 15;
const sunGradient = ctx.createRadialGradient(
sun.x - 5, sun.y - 5, 2,
sun.x, sun.y, sun.radius
);
sunGradient.addColorStop(0, '#ffffff');
sunGradient.addColorStop(1, sun.color);
ctx.beginPath();
ctx.arc(sun.x, sun.y, sun.radius, 0, Math.PI * 2);
ctx.fillStyle = sunGradient;
ctx.fill();
ctx.shadowBlur = 0;
// 添加陽光光芒(動態效果)
ctx.strokeStyle = sun.color;
ctx.lineWidth = 2;
const time = Date.now() / 500;
for (let i = 0; i < 8; i++) {
const angle = (i * Math.PI * 2) / 8 + time;
const length = 5 + Math.sin(time + i) * 2;
const x1 = sun.x + Math.cos(angle) * sun.radius;
const y1 = sun.y + Math.sin(angle) * sun.radius;
const x2 = sun.x + Math.cos(angle) * (sun.radius + length);
const y2 = sun.y + Math.sin(angle) * (sun.radius + length);
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
// 檢查陽光是否可以收集或消失
if (sun.y > canvas.height || Date.now() - sun.spawnTime > 15000) {
sunsFalling.splice(index, 1);
}
});
// 碰撞檢測
// 1. 子彈和殭屍碰撞
projectiles.forEach((projectile, pIndex) => {
zombies.forEach((zombie, zIndex) => {
if (!zombie.isDead && checkCollision(projectile, zombie)) {
// 移除子彈
projectiles.splice(pIndex, 1);
// 減少殭屍生命值
zombie.health -= 20;
// 添加擊中效果
for (let i = 0; i < 5; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 1 + Math.random() * 3;
// 可以在這裏添加擊中粒子效果
}
// 如果是雪豌豆,減速殭屍
if (projectile.isSnow) {
zombie.isSlowed = true;
zombie.slowTimer = 3000; // 3秒
}
// 檢查殭屍是否死亡
if (zombie.health <= 0) {
zombie.isDead = true;
// 隨機掉落陽光
if (Math.random() < 0.3) {
sunsFalling.push({
x: zombie.x + zombie.width / 2,
y: zombie.y + zombie.height / 2,
radius: 15,
color: '#ffeb3b',
speed: 1,
collected: false,
spawnTime: Date.now()
});
}
}
}
});
});
// 2. 殭屍和植物碰撞
zombies.forEach((zombie, zIndex) => {
if (zombie.isDead) return;
plants.forEach((plant, pIndex) => {
if (checkCollision(zombie, plant)) {
// 殭屍攻擊植物(添加攻擊動畫效果)
// 削弱殭屍傷害,從5點減少到2點,增加遊戲平衡性
plant.health -= 2;
// 攻擊效果動畫
ctx.save();
ctx.translate(plant.x + plant.width / 2, plant.y + plant.height / 2);
ctx.fillStyle = 'rgba(255, 0, 0, 0.3)';
ctx.beginPath();
ctx.arc(0, 0, plant.width / 2 + 10, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
// 檢查植物是否死亡
if (plant.health <= 0) {
plants.splice(pIndex, 1);
}
}
});
});
// 定期增加陽光
if (Math.random() < 0.005) { // 大約每3秒隨機掉落陽光
sunsFalling.push({
x: Math.random() * (canvas.width - 100) + 50,
y: -30,
radius: 15,
color: '#ffeb3b',
speed: 1,
collected: false,
spawnTime: Date.now()
});
}
// 更新植物選擇器狀態
updatePlantSelection();
// 繪製植物種植預覽
drawPlantPreview();
// 繼續遊戲循環
requestAnimationFrame(gameLoop);
}
// 事件監聽器
plantItems.forEach(item => {
item.addEventListener('click', () => {
if (gameOver) return;
const plantType = item.dataset.plant;
const cost = parseInt(item.querySelector('.plant-cost').textContent);
if (suns >= cost) {
// 取消之前的選擇
plantItems.forEach(i => i.classList.remove('selected'));
// 如果點擊的是當前選中的植物,則取消選擇
if (selectedPlant === plantType) {
selectedPlant = null;
} else {
// 選擇新植物
selectedPlant = plantType;
item.classList.add('selected');
// 添加選擇動畫
item.style.transform = 'scale(1.1)';
setTimeout(() => {
item.style.transform = '';
}, 200);
}
}
});
});
// 點擊畫布種植植物
canvas.addEventListener('click', (e) => {
if (gameOver) return;
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// 檢查是否點擊了陽光
let sunCollected = false;
sunsFalling.forEach((sun, index) => {
const dx = sun.x - x;
const dy = sun.y - y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= sun.radius) {
suns += 50;
updateSunCounter();
// 添加收集陽光的動畫效果
const collectEffect = document.createElement('div');
collectEffect.style.position = 'absolute';
collectEffect.style.left = (e.clientX - rect.left) + 'px';
collectEffect.style.top = (e.clientY - rect.top) + 'px';
collectEffect.style.color = '#ffeb3b';
collectEffect.style.fontSize = '20px';
collectEffect.style.fontWeight = 'bold';
collectEffect.style.pointerEvents = 'none';
collectEffect.style.zIndex = '100';
collectEffect.textContent = '+50';
document.getElementById('game-container').appendChild(collectEffect);
// 動畫
let pos = 0;
const animate = () => {
pos += 2;
collectEffect.style.transform = `translateY(-${pos}px)`;
collectEffect.style.opacity = (1 - pos / 50);
if (pos < 50) {
requestAnimationFrame(animate);
} else {
collectEffect.remove();
}
};
animate();
sunsFalling.splice(index, 1);
sunCollected = true;
}
});
// 如果沒有收集陽光,則嘗試種植植物
if (!sunCollected && selectedPlant) {
plantAt(x, y);
}
});
// 鼠標移動事件,用於植物種植預覽
canvas.addEventListener('mousemove', (e) => {
if (gameOver) return;
const rect = canvas.getBoundingClientRect();
mousePos.x = e.clientX - rect.left;
mousePos.y = e.clientY - rect.top;
showPreview = true;
});
// 鼠標離開畫布事件
canvas.addEventListener('mouseleave', () => {
showPreview = false;
});
// 添加CSS動畫樣式
const style = document.createElement('style');
style.textContent = `
.health-change {
transition: width 0.3s ease-in-out;
}
`;
document.head.appendChild(style);
// 開始遊戲
gameLoop();
</script>
</body>
</html>