Spaces:
Running
Running
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>植物大战僵尸 Web版</title> | |
| <!-- 引入 FontAwesome 用于图标 --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary-green: #4CAF50; | |
| --dark-green: #2E7D32; | |
| --lawn-light: #81C784; | |
| --lawn-dark: #66BB6A; | |
| --sun-yellow: #FFEB3B; | |
| --text-color: #333; | |
| --card-bg: #fff; | |
| --danger-red: #e53935; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| user-select: none; | |
| -webkit-user-select: none; | |
| touch-action: manipulation; | |
| } | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background-color: #222; | |
| color: var(--text-color); | |
| height: 100vh; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| overflow: hidden; | |
| } | |
| /* 游戏主容器 */ | |
| #game-container { | |
| width: 100%; | |
| max-width: 960px; | |
| height: 100%; | |
| max-height: 600px; | |
| background-color: var(--lawn-light); | |
| position: relative; | |
| display: flex; | |
| flex-direction: column; | |
| box-shadow: 0 0 20px rgba(0,0,0,0.5); | |
| border-radius: 8px; | |
| overflow: hidden; | |
| } | |
| /* 顶部栏 */ | |
| header { | |
| background-color: rgba(0, 0, 0, 0.8); | |
| color: white; | |
| padding: 10px 20px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| z-index: 100; | |
| } | |
| .brand { | |
| font-weight: bold; | |
| font-size: 1.2rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .brand a { | |
| color: var(--sun-yellow); | |
| text-decoration: none; | |
| font-size: 0.9rem; | |
| background: rgba(255,255,255,0.1); | |
| padding: 4px 8px; | |
| border-radius: 4px; | |
| transition: background 0.3s; | |
| } | |
| .brand a:hover { | |
| background: rgba(255,255,255,0.2); | |
| } | |
| /* 游戏区域布局 */ | |
| #game-area { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| position: relative; | |
| } | |
| /* 顶部控制面板 (阳光 & 卡牌) */ | |
| #hud { | |
| background: rgba(255, 255, 255, 0.9); | |
| padding: 10px; | |
| display: flex; | |
| align-items: center; | |
| gap: 15px; | |
| border-bottom: 4px solid var(--dark-green); | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| } | |
| .sun-counter { | |
| display: flex; | |
| align-items: center; | |
| background: #fff; | |
| border: 2px solid var(--sun-yellow); | |
| border-radius: 20px; | |
| padding: 5px 15px; | |
| font-weight: bold; | |
| font-size: 1.2rem; | |
| color: #F57F17; | |
| box-shadow: inset 0 0 5px rgba(0,0,0,0.1); | |
| } | |
| .sun-icon { | |
| margin-right: 8px; | |
| color: var(--sun-yellow); | |
| text-shadow: 0 0 2px #F9A825; | |
| } | |
| .plant-cards { | |
| display: flex; | |
| gap: 10px; | |
| } | |
| .card { | |
| width: 70px; | |
| height: 90px; | |
| background: var(--card-bg); | |
| border: 2px solid #ccc; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| transition: transform 0.1s, border-color 0.2s; | |
| position: relative; | |
| } | |
| .card:hover { | |
| transform: translateY(-2px); | |
| } | |
| .card.selected { | |
| border-color: var(--primary-green); | |
| background-color: #E8F5E9; | |
| box-shadow: 0 0 8px var(--primary-green); | |
| } | |
| .card.disabled { | |
| filter: grayscale(100%); | |
| opacity: 0.6; | |
| cursor: not-allowed; | |
| } | |
| .card-emoji { | |
| font-size: 2rem; | |
| margin-bottom: 5px; | |
| } | |
| .card-cost { | |
| font-size: 0.8rem; | |
| font-weight: bold; | |
| color: #555; | |
| } | |
| /* 草坪网格 */ | |
| #lawn-grid { | |
| flex: 1; | |
| display: grid; | |
| grid-template-rows: repeat(5, 1fr); | |
| grid-template-columns: repeat(9, 1fr); | |
| position: relative; | |
| } | |
| .cell { | |
| border: 1px solid rgba(0,0,0,0.05); | |
| position: relative; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| /* 棋盘格效果 */ | |
| .cell:nth-child(odd) { | |
| background-color: var(--lawn-light); | |
| } | |
| .cell:nth-child(even) { | |
| background-color: var(--lawn-dark); | |
| } | |
| /* 每一行错位颜色调整 */ | |
| .cell:nth-child(18n+1), .cell:nth-child(18n+3), .cell:nth-child(18n+5), .cell:nth-child(18n+7), .cell:nth-child(18n+9), | |
| .cell:nth-child(18n+10), .cell:nth-child(18n+12), .cell:nth-child(18n+14), .cell:nth-child(18n+16), .cell:nth-child(18n+18) { | |
| background-color: var(--lawn-light); | |
| } | |
| .cell:nth-child(18n+2), .cell:nth-child(18n+4), .cell:nth-child(18n+6), .cell:nth-child(18n+8), | |
| .cell:nth-child(18n+11), .cell:nth-child(18n+13), .cell:nth-child(18n+15), .cell:nth-child(18n+17) { | |
| background-color: var(--lawn-dark); | |
| } | |
| .cell:hover { | |
| filter: brightness(1.1); | |
| cursor: pointer; | |
| } | |
| /* 游戏实体 (植物、僵尸、子弹) */ | |
| .entity { | |
| position: absolute; | |
| pointer-events: none; /* 让点击穿透到网格 */ | |
| z-index: 10; | |
| } | |
| .plant { | |
| font-size: 3rem; | |
| z-index: 5; | |
| transition: transform 0.1s; | |
| } | |
| .plant.hit { | |
| transform: scale(0.9); | |
| filter: brightness(0.8); | |
| } | |
| .zombie { | |
| font-size: 3.5rem; | |
| z-index: 20; | |
| transition: left 0.1s linear; /* 平滑移动 */ | |
| } | |
| .zombie.eating { | |
| animation: shake 0.5s infinite; | |
| } | |
| .projectile { | |
| width: 20px; | |
| height: 20px; | |
| background-color: #66BB6A; | |
| border-radius: 50%; | |
| border: 2px solid #2E7D32; | |
| z-index: 15; | |
| box-shadow: 0 0 5px #fff; | |
| } | |
| .sun-token { | |
| position: absolute; | |
| width: 50px; | |
| height: 50px; | |
| font-size: 2.5rem; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| cursor: pointer; | |
| z-index: 100; | |
| filter: drop-shadow(0 0 5px orange); | |
| animation: sun-glow 2s infinite alternate; | |
| pointer-events: auto; /* 允许点击 */ | |
| transition: top 5s linear, opacity 0.3s; | |
| } | |
| /* 模态框 */ | |
| .modal { | |
| 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: 200; | |
| color: white; | |
| text-align: center; | |
| } | |
| .hidden { | |
| display: none ; | |
| } | |
| h1 { | |
| font-size: 3rem; | |
| color: var(--primary-green); | |
| text-shadow: 2px 2px 0 #000; | |
| margin-bottom: 10px; | |
| } | |
| .btn { | |
| padding: 15px 40px; | |
| font-size: 1.5rem; | |
| background: var(--primary-green); | |
| color: white; | |
| border: none; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| margin-top: 20px; | |
| box-shadow: 0 4px 0 var(--dark-green); | |
| transition: transform 0.1s, box-shadow 0.1s; | |
| } | |
| .btn:active { | |
| transform: translateY(4px); | |
| box-shadow: 0 0 0 var(--dark-green); | |
| } | |
| .instructions { | |
| margin-top: 20px; | |
| color: #ddd; | |
| max-width: 600px; | |
| line-height: 1.6; | |
| } | |
| /* 动画定义 */ | |
| @keyframes sun-glow { | |
| from { transform: scale(1); filter: drop-shadow(0 0 5px orange); } | |
| to { transform: scale(1.1); filter: drop-shadow(0 0 15px yellow); } | |
| } | |
| @keyframes shake { | |
| 0% { transform: rotate(0deg); } | |
| 25% { transform: rotate(5deg); } | |
| 75% { transform: rotate(-5deg); } | |
| 100% { transform: rotate(0deg); } | |
| } | |
| @media (max-width: 600px) { | |
| .card { width: 50px; height: 70px; } | |
| .card-emoji { font-size: 1.5rem; } | |
| .card-cost { font-size: 0.7rem; } | |
| .plant, .zombie { font-size: 2rem; } | |
| h1 { font-size: 2rem; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="game-container"> | |
| <header> | |
| <div class="brand"> | |
| <i class="fas fa-seedling"></i> PvZ Web版 | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">Built with anycoder</a> | |
| </div> | |
| <div id="score-display">波数: 1</div> | |
| </header> | |
| <div id="game-area"> | |
| <div id="hud"> | |
| <div class="sun-counter"> | |
| <i class="fas fa-sun sun-icon"></i> | |
| <span id="sun-amount">150</span> | |
| </div> | |
| <div class="plant-cards"> | |
| <!-- 向日葵卡片 --> | |
| <div class="card" id="card-sunflower" onclick="selectPlant('sunflower', 50)"> | |
| <div class="card-emoji">🌻</div> | |
| <div class="card-cost">50</div> | |
| </div> | |
| <!-- 豌豆射手卡片 --> | |
| <div class="card" id="card-peashooter" onclick="selectPlant('peashooter', 100)"> | |
| <div class="card-emoji">🌱</div> | |
| <div class="card-cost">100</div> | |
| </div> | |
| <!-- 坚果卡片 --> | |
| <div class="card" id="card-wallnut" onclick="selectPlant('wallnut', 50)"> | |
| <div class="card-emoji">🌰</div> | |
| <div class="card-cost">50</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="lawn-grid"> | |
| <!-- 网格由 JS 生成 --> | |
| </div> | |
| </div> | |
| <!-- 开始界面 --> | |
| <div id="start-screen" class="modal"> | |
| <h1>植物大战僵尸</h1> | |
| <div class="instructions"> | |
| <p>收集阳光,种植植物,抵御僵尸的进攻!</p> | |
| <p>🌻 向日葵: 生产阳光<br>🌱 豌豆射手: 发射子弹攻击<br>🌰 坚果墙: 阻挡僵尸</p> | |
| </div> | |
| <button class="btn" onclick="startGame()">开始游戏</button> | |
| </div> | |
| <!-- 游戏结束界面 --> | |
| <div id="game-over-screen" class="modal hidden"> | |
| <h1 style="color: var(--danger-red)">僵尸吃掉了你的脑子!</h1> | |
| <p>存活波数: <span id="final-wave">1</span></p> | |
| <button class="btn" onclick="resetGame()">重新开始</button> | |
| </div> | |
| </div> | |
| <script> | |
| // --- 游戏配置与状态 --- | |
| const config = { | |
| rows: 5, | |
| cols: 9, | |
| sunValue: 25, | |
| sunSpawnRate: 8000, // 自然阳光生成间隔 | |
| zombieSpawnRateInitial: 10000, // 初始僵尸生成间隔 | |
| bulletSpeed: 5, // 子弹每帧移动像素 | |
| zombieSpeed: 0.3, // 僵尸每帧移动像素 | |
| }; | |
| let state = { | |
| sun: 150, | |
| isPlaying: false, | |
| selectedPlant: null, | |
| plants: [], | |
| zombies: [], | |
| projectiles: [], | |
| suns: [], | |
| wave: 1, | |
| lastTime: 0, | |
| timers: { | |
| sun: 0, | |
| zombie: 0 | |
| } | |
| }; | |
| // --- DOM 元素引用 --- | |
| const gridEl = document.getElementById('lawn-grid'); | |
| const sunEl = document.getElementById('sun-amount'); | |
| const cards = { | |
| sunflower: document.getElementById('card-sunflower'), | |
| peashooter: document.getElementById('card-peashooter'), | |
| wallnut: document.getElementById('card-wallnut') | |
| }; | |
| // --- 初始化网格 --- | |
| function initGrid() { | |
| gridEl.innerHTML = ''; | |
| for (let r = 0; r < config.rows; r++) { | |
| for (let c = 0; c < config.cols; c++) { | |
| const cell = document.createElement('div'); | |
| cell.classList.add('cell'); | |
| cell.dataset.row = r; | |
| cell.dataset.col = c; | |
| cell.addEventListener('click', () => handleCellClick(r, c)); | |
| gridEl.appendChild(cell); | |
| } | |
| } | |
| } | |
| // --- 游戏流程控制 --- | |
| function startGame() { | |
| document.getElementById('start-screen').classList.add('hidden'); | |
| document.getElementById('game-over-screen').classList.add('hidden'); | |
| resetState(); | |
| state.isPlaying = true; | |
| state.lastTime = performance.now(); | |
| requestAnimationFrame(gameLoop); | |
| updateUI(); | |
| } | |
| function resetGame() { | |
| startGame(); | |
| } | |
| function resetState() { | |
| state.sun = 150; | |
| state.plants = []; | |
| state.zombies = []; | |
| state.projectiles = []; | |
| state.suns = []; | |
| state.wave = 1; | |
| state.timers.sun = 0; | |
| state.timers.zombie = 0; | |
| // 清理界面上的实体 | |
| document.querySelectorAll('.entity, .sun-token').forEach(el => el.remove()); | |
| updateUI(); | |
| } | |
| function gameOver() { | |
| state.isPlaying = false; | |
| document.getElementById('final-wave').innerText = state.wave; | |
| document.getElementById('game-over-screen').classList.remove('hidden'); | |
| } | |
| // --- 核心游戏循环 --- | |
| function gameLoop(timestamp) { | |
| if (!state.isPlaying) return; | |
| const deltaTime = timestamp - state.lastTime; | |
| state.lastTime = timestamp; | |
| updateLogic(deltaTime); | |
| render(); // 虽然主要由DOM事件驱动,但我们需要在这里处理移动逻辑的视觉更新 | |
| requestAnimationFrame(gameLoop); | |
| } | |
| function updateLogic(deltaTime) { | |
| // 1. 生成自然阳光 | |
| state.timers.sun += deltaTime; | |
| if (state.timers.sun > config.sunSpawnRate) { | |
| spawnSun(); | |
| state.timers.sun = 0; | |
| } | |
| // 2. 生成僵尸 (随着时间推移,生成速度变快) | |
| state.timers.zombie += deltaTime; | |
| let currentSpawnRate = Math.max(2000, config.zombieSpawnRateInitial - (state.wave * 500)); | |
| if (state.timers.zombie > currentSpawnRate) { | |
| spawnZombie(); | |
| state.timers.zombie = 0; | |
| // 每生成5个僵尸增加一波难度 | |
| if (state.zombies.length % 5 === 0) { | |
| state.wave++; | |
| document.getElementById('score-display').innerText = `波数: ${state.wave}`; | |
| } | |
| } | |
| // 3. 更新植物 (攻击/生产) | |
| state.plants.forEach(plant => { | |
| plant.timer += deltaTime; | |
| // 向日葵生产阳光 | |
| if (plant.type === 'sunflower' && plant.timer > 10000) { // 10秒生产一次 | |
| spawnSun(plant.row, plant.col, true); | |
| plant.timer = 0; | |
| } | |
| // 豌豆射手攻击 | |
| if (plant.type === 'peashooter' && plant.timer > 1500) { // 1.5秒射击一次 | |
| // 检测该行是否有僵尸 | |
| const hasZombie = state.zombies.some(z => z.row === plant.row && z.x > plant.col * (100/config.cols)); | |
| if (hasZombie) { | |
| spawnProjectile(plant.row, plant.col); | |
| plant.timer = 0; | |
| } | |
| } | |
| }); | |
| // 4. 更新子弹位置与碰撞 | |
| for (let i = state.projectiles.length - 1; i >= 0; i--) { | |
| let p = state.projectiles[i]; | |
| p.x += config.bulletSpeed * (deltaTime / 16); // 归一化速度 | |
| // 移除超出屏幕的子弹 | |
| if (p.x > gridEl.clientWidth) { | |
| p.el.remove(); | |
| state.projectiles.splice(i, 1); | |
| continue; | |
| } | |
| // 子弹碰撞检测 | |
| let hit = false; | |
| for (let j = 0; j < state.zombies.length; j++) { | |
| let z = state.zombies[j]; | |
| if (p.row === z.row && Math.abs(p.x - z.x) < 30 && z.health > 0) { | |
| // 击中僵尸 | |
| z.health -= 20; | |
| z.el.style.filter = "brightness(2) sepia(1) hue-rotate(-50deg) saturate(5)"; // 受伤闪红 | |
| setTimeout(() => { if(z.el) z.el.style.filter = ""; }, 100); | |
| if (z.health <= 0) { | |
| killZombie(j); | |
| } | |
| // 移除子弹 | |
| p.el.remove(); | |
| state.projectiles.splice(i, 1); | |
| hit = true; | |
| break; | |
| } | |
| } | |
| if (!hit) { | |
| p.el.style.left = p.x + 'px'; | |
| } | |
| } | |
| // 5. 更新僵尸位置与啃食 | |
| for (let i = state.zombies.length - 1; i >= 0; i--) { | |
| let z = state.zombies[i]; | |
| // 检测是否在啃食植物 | |
| let eating = false; | |
| const cellWidth = gridEl.clientWidth / config.cols; | |
| // 简单的网格碰撞:僵尸中心点所在的列 | |
| let currentCol = Math.floor((z.x + 20) / cellWidth); | |
| // 查找该位置是否有植物 | |
| let plant = state.plants.find(p => p.row === z.row && p.col === currentCol); | |
| if (plant) { | |
| eating = true; | |
| z.el.classList.add('eating'); | |
| plant.health -= 0.5; // 啃食伤害 | |
| plant.el.classList.add('hit'); | |
| if (plant.health <= 0) { | |
| // 植物死亡 | |
| plant.el.remove(); | |
| state.plants = state.plants.filter(p => p !== plant); | |
| z.el.classList.remove('eating'); | |
| } | |
| } else { | |
| z.el.classList.remove('eating'); | |
| } | |
| if (!eating) { | |
| z.x -= config.zombieSpeed * (deltaTime / 16); | |
| z.el.style.left = z.x + 'px'; | |
| } | |
| // 游戏结束判定 | |
| if (z.x < -20) { | |
| gameOver(); | |
| } | |
| } | |
| } | |
| function render() { | |
| // 大部分渲染在逻辑中直接操作 DOM 样式了,这里主要用于清理无效引用 | |
| updateUI(); | |
| } | |
| // --- 实体操作 --- | |
| function selectPlant(type, cost) { | |
| if (state.sun >= cost) { | |
| state.selectedPlant = { type, cost }; | |
| // 高亮选中卡片 | |
| Object.values(cards).forEach(c => c.classList.remove('selected')); | |
| cards[type].classList.add('selected'); | |
| } | |
| } | |
| function handleCellClick(row, col) { | |
| if (!state.isPlaying || !state.selectedPlant) return; | |
| // 检查该格子是否已有植物 | |
| if (state.plants.some(p => p.row === row && p.col === col)) return; | |
| // 扣除阳光 | |
| state.sun -= state.selectedPlant.cost; | |
| // 创建植物 | |
| createPlant(row, col, state.selectedPlant.type); | |
| // 重置选择 | |
| state.selectedPlant = null; | |
| Object.values(cards).forEach(c => c.classList.remove('selected')); | |
| updateUI(); | |
| } | |
| function createPlant(row, col, type) { | |
| const cell = document.querySelector(`.cell[data-row='${row}'][data-col='${col}']`); | |
| const el = document.createElement('div'); | |
| el.classList.add('entity', 'plant'); | |
| let emoji = ''; | |
| let health = 100; | |
| if (type === 'sunflower') { | |
| emoji = '🌻'; | |
| health = 80; | |
| } else if (type === 'peashooter') { | |
| emoji = '🌱'; | |
| health = 100; | |
| } else if (type === 'wallnut') { | |
| emoji = '🌰'; | |
| health = 400; // 高血量 | |
| } | |
| el.innerText = emoji; | |
| // 居中显示在格子里 | |
| el.style.top = '50%'; | |
| el.style.left = '50%'; | |
| el.style.transform = 'translate(-50%, -50%)'; | |
| cell.appendChild(el); | |
| state.plants.push({ | |
| row, col, type, health, el, timer: 0 | |
| }); | |
| } | |
| function spawnZombie() { | |
| const row = Math.floor(Math.random() * config.rows); | |
| const el = document.createElement('div'); | |
| el.classList.add('entity', 'zombie'); | |
| el.innerText = '🧟'; | |
| const startX = gridEl.clientWidth; | |
| const rowHeight = gridEl.clientHeight / config.rows; | |
| el.style.top = (row * rowHeight + rowHeight/2 - 20) + 'px'; // 垂直居中微调 | |
| el.style.left = startX + 'px'; | |
| gridEl.appendChild(el); | |
| state.zombies.push({ | |
| row, x: startX, health: 100, el | |
| }); | |
| } | |
| function killZombie(index) { | |
| const z = state.zombies[index]; | |
| z.el.innerText = '💥'; // 死亡特效 | |
| setTimeout(() => { | |
| if(z.el) z.el.remove(); | |
| }, 500); | |
| state.zombies.splice(index, 1); | |
| } | |
| function spawnProjectile(row, col) { | |
| const el = document.createElement('div'); | |
| el.classList.add('entity', 'projectile'); | |
| const cellWidth = gridEl.clientWidth / config.cols; | |
| const rowHeight = gridEl.clientHeight / config.rows; | |
| // 初始位置 | |
| const startX = col * cellWidth + 40; | |
| const startY = row * rowHeight + rowHeight/2 - 10; | |
| el.style.left = startX + 'px'; | |
| el.style.top = startY + 'px'; | |
| gridEl.appendChild(el); | |
| state.projectiles.push({ | |
| row, x: startX, el | |
| }); | |
| } | |
| function spawnSun(row = null, col = null, fromPlant = false) { | |
| const el = document.createElement('div'); | |
| el.classList.add('sun-token'); | |
| el.innerText = '☀️'; | |
| let targetTop, targetLeft; | |
| if (fromPlant && row !== null && col !== null) { | |
| // 从植物产生 | |
| const cell = document.querySelector(`.cell[data-row='${row}'][data-col='${col}']`); | |
| const rect = cell.getBoundingClientRect(); | |
| const gridRect = gridEl.getBoundingClientRect(); | |
| // 相对于 grid 的坐标 | |
| targetLeft = (rect.left - gridRect.left) + 10; | |
| targetTop = (rect.top - gridRect.top) + 10; | |
| el.style.left = targetLeft + 'px'; | |
| el.style.top = targetTop + 'px'; | |
| } else { | |
| // 从天空掉落 | |
| targetLeft = Math.random() * (gridEl.clientWidth - 50); | |
| targetTop = gridEl.clientHeight - Math.random() * 200; // 随机高度 | |
| el.style.left = targetLeft + 'px'; | |
| el.style.top = '-50px'; | |
| // 触发下落动画 | |
| setTimeout(() => { | |
| el.style.top = targetTop + 'px'; | |
| }, 50); | |
| } | |
| el.onclick = (e) => { | |
| e.stopPropagation(); // 防止点穿 | |
| collectSun(el); | |
| }; | |
| // 8秒后自动消失 | |
| setTimeout(() => { | |
| if(el.parentElement) { | |
| el.style.opacity = 0; | |
| setTimeout(() => el.remove(), 300); | |
| } | |
| }, 8000); | |
| gridEl.appendChild(el); | |
| } | |
| function collectSun(el) { | |
| state.sun += config.sunValue; | |
| // 飞向左上角动画 | |
| const hudRect = document.getElementById('hud').getBoundingClientRect(); | |
| const gridRect = gridEl.getBoundingClientRect(); | |
| // 计算相对位置 | |
| const targetX = hudRect.left - gridRect.left; | |
| const targetY = hudRect.top - gridRect.top; | |
| el.style.transition = 'all 0.5s ease-in'; | |
| el.style.left = targetX + 'px'; | |
| el.style.top = targetY + 'px'; | |
| el.style.opacity = 0; | |
| setTimeout(() => { | |
| el.remove(); | |
| updateUI(); | |
| }, 500); | |
| } | |
| function updateUI() { | |
| sunEl.innerText = state.sun; | |
| // 更新卡片状态 (变灰如果阳光不足) | |
| if (state.sun < 50) { | |
| cards.sunflower.classList.add('disabled'); | |
| cards.wallnut.classList.add('disabled'); | |
| } else { | |
| cards.sunflower.classList.remove('disabled'); | |
| cards.wallnut.classList.remove('disabled'); | |
| } | |
| if (state.sun < 100) { | |
| cards.peashooter.classList.add('disabled'); | |
| } else { | |
| cards.peashooter.classList.remove('disabled'); | |
| } | |
| } | |
| // --- 启动 --- | |
| initGrid(); | |
| </script> | |
| </body> | |
| </html> |