動態

詳情 返回 返回

自制植物大戰殭屍 - 動態 詳情

介紹

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>

user avatar hlinleanring 頭像 oeasy 頭像 yejianfeixue 頭像 codexiaosheng 頭像 xboxyan 頭像 hyfhao 頭像 wanmuc 頭像
點贊 7 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.