植物大战僵尸 plus
有学号
点击查看代码
import pygame
import random
import sys
import os
import math
from pygame.locals import *
# 初始化pygame
pygame.init()
# 游戏常量
SCREEN_WIDTH = 900
SCREEN_HEIGHT = 600
FPS = 60
GRID_SIZE = 80
GRID_WIDTH = 9
GRID_HEIGHT = 5
LAWN_LEFT = 150
LAWN_TOP = 100
# 颜色定义
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
BROWN = (139, 69, 19)
YELLOW = (255, 255, 0)
BLUE = (0, 0, 255)
RED = (255, 0, 0)
GRAY = (100, 100, 100)
PURPLE = (128, 0, 128)
# 创建游戏窗口
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("植物大战僵尸 - 优化版")
clock = pygame.time.Clock()
# 创建更复杂的图像
def create_complex_sun():
surf = pygame.Surface((40, 40), pygame.SRCALPHA)
pygame.draw.circle(surf, YELLOW, (20, 20), 20)
for i in range(8):
angle = i * 45
end_x = 20 + 25 * pygame.math.Vector2(1, 0).rotate(angle).x
end_y = 20 + 25 * pygame.math.Vector2(1, 0).rotate(angle).y
pygame.draw.line(surf, YELLOW, (20, 20), (end_x, end_y), 3)
pygame.draw.circle(surf, (255, 165, 0), (20, 20), 15)
return surf
def create_complex_sunflower():
surf = pygame.Surface((60, 60), pygame.SRCALPHA)
# 茎
pygame.draw.rect(surf, GREEN, (28, 30, 4, 30))
# 花盆
pygame.draw.rect(surf, BROWN, (20, 50, 20, 10))
# 花心
pygame.draw.circle(surf, (139, 69, 19), (30, 25), 15)
# 花瓣
for i in range(8):
angle = i * 45
end_x = 30 + 25 * pygame.math.Vector2(1, 0).rotate(angle).x
end_y = 25 + 25 * pygame.math.Vector2(1, 0).rotate(angle).y
pygame.draw.circle(surf, (255, 215, 0), (end_x, end_y), 10)
# 笑脸
pygame.draw.circle(surf, BLACK, (25, 20), 2)
pygame.draw.circle(surf, BLACK, (35, 20), 2)
pygame.draw.arc(surf, BLACK, (22, 25, 16, 10), 0.2, 2.9, 2)
return surf
def create_complex_zombie():
surf = pygame.Surface((50, 80), pygame.SRCALPHA)
# 身体
pygame.draw.ellipse(surf, (150, 150, 150), (10, 20, 30, 50))
# 头
pygame.draw.circle(surf, (200, 200, 200), (25, 20), 15)
# 眼睛
pygame.draw.circle(surf, (50, 50, 50), (20, 18), 3)
pygame.draw.circle(surf, (50, 50, 50), (30, 18), 3)
# 嘴巴
pygame.draw.rect(surf, (100, 0, 0), (20, 25, 10, 3))
# 衣服
pygame.draw.rect(surf, (70, 70, 70), (10, 35, 30, 15))
pygame.draw.line(surf, (100, 100, 100), (10, 42), (40, 42), 2)
# 手臂
pygame.draw.line(surf, (200, 200, 200), (10, 40), (0, 50), 5)
pygame.draw.line(surf, (200, 200, 200), (40, 40), (50, 50), 5)
# 腿
pygame.draw.line(surf, (70, 70, 70), (20, 70), (20, 80), 5)
pygame.draw.line(surf, (70, 70, 70), (30, 70), (30, 80), 5)
# 僵尸标识
font = pygame.font.SysFont('Arial', 12, bold=True)
text = font.render("Z", True, RED)
surf.blit(text, (40, 10))
return surf
def create_complex_peashooter():
surf = pygame.Surface((60, 60), pygame.SRCALPHA)
# 茎
pygame.draw.rect(surf, GREEN, (28, 30, 4, 30))
# 花盆
pygame.draw.rect(surf, BROWN, (20, 50, 20, 10))
# 头部
pygame.draw.circle(surf, GREEN, (30, 25), 15)
# 嘴巴
pygame.draw.circle(surf, (0, 100, 0), (35, 25), 8)
# 眼睛
pygame.draw.circle(surf, BLACK, (25, 20), 3)
# 豌豆标识
font = pygame.font.SysFont('Arial', 12, bold=True)
text = font.render("P", True, (0, 100, 0))
surf.blit(text, (20, 15))
return surf
def create_complex_wallnut():
surf = pygame.Surface((60, 60), pygame.SRCALPHA)
# 坚果
pygame.draw.ellipse(surf, BROWN, (10, 10, 40, 50))
# 纹理
pygame.draw.line(surf, (101, 67, 33), (20, 20), (40, 25), 2)
pygame.draw.line(surf, (101, 67, 33), (25, 30), (35, 40), 2)
pygame.draw.line(surf, (101, 67, 33), (30, 45), (20, 50), 2)
# 表情
pygame.draw.circle(surf, BLACK, (25, 30), 2)
pygame.draw.circle(surf, BLACK, (35, 30), 2)
pygame.draw.arc(surf, BLACK, (22, 35, 16, 10), 0.2, 2.9, 2)
# 坚果标识
font = pygame.font.SysFont('Arial', 12, bold=True)
text = font.render("W", True, (101, 67, 33))
surf.blit(text, (40, 20))
return surf
def create_complex_pea():
surf = pygame.Surface((20, 10), pygame.SRCALPHA)
pygame.draw.ellipse(surf, (50, 200, 50), (0, 0, 20, 10))
pygame.draw.ellipse(surf, (0, 100, 0), (5, 2, 10, 6))
return surf
def create_complex_shovel():
surf = pygame.Surface((40, 40), pygame.SRCALPHA)
# 手柄
pygame.draw.rect(surf, BROWN, (15, 5, 10, 20))
# 铲头
points = [(10, 25), (30, 25), (25, 40), (15, 40)]
pygame.draw.polygon(surf, (180, 180, 180), points)
return surf
def create_complex_pepper():
surf = pygame.Surface((60, 60), pygame.SRCALPHA)
# 茎
pygame.draw.rect(surf, GREEN, (28, 30, 4, 30))
# 花盆
pygame.draw.rect(surf, BROWN, (20, 50, 20, 10))
# 辣椒
pygame.draw.ellipse(surf, RED, (15, 15, 30, 40))
# 火焰效果
for i in range(5):
angle = random.randint(0, 360)
length = random.randint(5, 15)
end_x = 30 + length * math.cos(math.radians(angle))
end_y = 30 + length * math.sin(math.radians(angle))
pygame.draw.line(surf, (255, 165, 0), (30, 30), (end_x, end_y), 3)
# 标识
font = pygame.font.SysFont('Arial', 12, bold=True)
text = font.render("F", True, WHITE)
surf.blit(text, (45, 20))
return surf
def create_lawnmower():
surf = pygame.Surface((60, 40), pygame.SRCALPHA)
# 车身
pygame.draw.rect(surf, (50, 200, 50), (0, 10, 50, 20))
# 轮子
pygame.draw.circle(surf, BLACK, (10, 30), 8)
pygame.draw.circle(surf, BLACK, (40, 30), 8)
# 刀片
pygame.draw.rect(surf, (200, 200, 200), (50, 15, 10, 10))
return surf
# 创建更复杂的图像资源
sun_img = create_complex_sun()
sunflower_img = create_complex_sunflower()
peashooter_img = create_complex_peashooter()
wallnut_img = create_complex_wallnut()
zombie_img = create_complex_zombie()
pea_img = create_complex_pea()
shovel_img = create_complex_shovel()
pepper_img = create_complex_pepper()
lawnmower_img = create_lawnmower()
# 游戏字体
font = pygame.font.SysFont('Arial', 24)
big_font = pygame.font.SysFont('Arial', 36, bold=True)
title_font = pygame.font.SysFont('Arial', 48, bold=True)
class Sun:
def __init__(self, x, y, is_falling=True):
self.x = x
self.y = y
self.image = sun_img
self.rect = self.image.get_rect(center=(x, y))
self.value = 25
self.speed = 2
self.is_falling = is_falling
self.collected = False
self.fall_target_y = y + random.randint(100, 200)
self.lifetime = 0
self.max_lifetime = 10 * FPS # 10秒
def update(self):
self.lifetime += 1
if self.lifetime >= self.max_lifetime:
self.collected = True
if self.is_falling and self.y < self.fall_target_y:
self.y += self.speed
self.rect.center = (self.x, self.y)
def draw(self, surface):
surface.blit(self.image, self.rect)
def is_clicked(self, pos):
return self.rect.collidepoint(pos) and not self.collected
class Plant:
def __init__(self, x, y, plant_type):
self.x = x
self.y = y
self.plant_type = plant_type
self.health = 100
self.attack_cooldown = 0
self.eaten = False # 是否正在被吃
if plant_type == "peashooter":
self.image = peashooter_img
self.cost = 100
self.attack_speed = 2 * FPS # 每2秒攻击一次
elif plant_type == "sunflower":
self.image = sunflower_img
self.cost = 50
self.sun_production_speed = 10 * FPS # 每10秒产生阳光
self.sun_production_timer = 0
elif plant_type == "wallnut":
self.image = wallnut_img
self.cost = 50
self.health = 300 # 坚果墙有更多生命值
elif plant_type == "pepper":
self.image = pepper_img
self.cost = 150
self.exploded = False
self.rect = self.image.get_rect(center=(x, y))
def update(self):
if self.plant_type == "peashooter":
if self.attack_cooldown > 0:
self.attack_cooldown -= 1
elif self.plant_type == "sunflower":
self.sun_production_timer += 1
if self.sun_production_timer >= self.sun_production_speed:
self.sun_production_timer = 0
return True # 表示应该产生阳光
elif self.plant_type == "pepper":
# 辣椒种植后立即爆炸
if not self.exploded:
self.exploded = True
return "explode"
return False
def draw(self, surface):
surface.blit(self.image, self.rect)
# 绘制生命条(辣椒不需要)
if self.plant_type != "pepper":
health_bar_width = 40
health_ratio = self.health / (300 if self.plant_type == "wallnut" else 100)
pygame.draw.rect(surface, RED, (self.x - health_bar_width//2, self.y - 40, health_bar_width, 5))
pygame.draw.rect(surface, GREEN, (self.x - health_bar_width//2, self.y - 40, int(health_bar_width * health_ratio), 5))
student_id_text = font.render("学号:03", True, BLACK)
surface.blit(student_id_text, (10, 10))
def can_attack(self):
return self.plant_type == "peashooter" and self.attack_cooldown <= 0
def attack(self):
if self.plant_type == "peashooter":
self.attack_cooldown = self.attack_speed
return Pea(self.x + 30, self.y)
return None
class Pea:
def __init__(self, x, y):
self.x = x
self.y = y
self.image = pea_img
self.rect = self.image.get_rect(center=(x, y))
self.speed = 5
self.damage = 20
def update(self):
self.x += self.speed
self.rect.center = (self.x, self.y)
return self.x > SCREEN_WIDTH # 如果豌豆飞出屏幕则返回True
def draw(self, surface):
surface.blit(self.image, self.rect)
class Zombie:
def __init__(self, row, level):
self.x = SCREEN_WIDTH
self.y = LAWN_TOP + row * GRID_SIZE + GRID_SIZE // 2
self.image = zombie_img
self.rect = self.image.get_rect(midright=(self.x, self.y))
# 僵尸速度随关卡增加而增加(初始速度降低)
self.speed = 0.15 + level * 0.05
self.health = 100 + level * 20 # 僵尸生命值随关卡增加
self.damage = 0.5 + level * 0.1 # 僵尸伤害随关卡增加
self.attack_cooldown = 0
self.row = row
self.walk_animation = 0
self.walk_speed = 0.1
self.eating = False # 是否正在吃植物
self.eat_timer = 0 # 吃植物计时器
self.current_plant = None # 当前正在吃的植物
def update(self):
# 如果正在吃植物,则停止移动
if not self.eating:
self.walk_animation += self.walk_speed
if self.walk_animation >= 2:
self.walk_animation = 0
# 轻微左右摆动模拟走路
swing_offset = 2 * math.sin(self.walk_animation * math.pi)
self.x -= self.speed
self.rect.midright = (self.x, self.y + swing_offset)
# 吃植物计时
if self.eating:
self.eat_timer += 1
# 坚果需要更长时间吃完
eat_time = 3 * FPS if self.current_plant and self.current_plant.plant_type == "wallnut" else 1 * FPS
if self.eat_timer >= eat_time:
self.eating = False
self.eat_timer = 0
self.current_plant = None
if self.attack_cooldown > 0:
self.attack_cooldown -= 1
def start_eating(self, plant):
"""开始吃植物"""
self.eating = True
self.eat_timer = 0
self.current_plant = plant
def draw(self, surface):
surface.blit(self.image, self.rect)
# 绘制生命条
health_bar_width = 40
health_ratio = self.health / (100 + self.health * 0.2)
pygame.draw.rect(surface, RED, (self.x - health_bar_width//2, self.y - 50, health_bar_width, 5))
pygame.draw.rect(surface, GREEN, (self.x - health_bar_width//2, self.y - 50, int(health_bar_width * health_ratio), 5))
# 如果正在吃植物,绘制吃植物状态
if self.eating:
eat_text = font.render("吃...", True, RED)
surface.blit(eat_text, (self.x - 20, self.y - 70))
def can_attack(self):
return self.attack_cooldown <= 0
def attack(self, plant):
self.attack_cooldown = FPS # 1秒攻击一次
plant.health -= self.damage
return plant.health <= 0 # 返回植物是否被摧毁
class Lawnmower:
def __init__(self, row):
self.row = row
self.x = LAWN_LEFT - 30
self.y = LAWN_TOP + row * GRID_SIZE + GRID_SIZE // 2
self.image = lawnmower_img
self.rect = self.image.get_rect(center=(self.x, self.y))
self.active = False
self.speed = 10
self.used = False # 是否已使用
def activate(self):
if not self.used:
self.active = True
self.used = True
def update(self):
if self.active:
self.x += self.speed
self.rect.center = (self.x, self.y)
return self.x > SCREEN_WIDTH # 如果小推车移出屏幕则返回True
return False
def draw(self, surface):
if not self.active:
surface.blit(self.image, self.rect)
class Game:
def __init__(self):
self.plants = []
self.zombies = []
self.suns = []
self.peas = []
self.sun_count = 150
self.selected_plant = None
self.game_over = False
self.wave_timer = 0
self.zombie_spawn_timer = 0
self.sun_spawn_timer = 0
self.sun_spawn_interval = 10 * FPS # 10秒自动生成一个阳光
self.score = 0
self.level = 1
self.max_level = 5
self.unlocked_level = 1
self.state = "menu" # menu, level_select, playing, game_over
self.shovel_selected = False
self.lawnmowers = [Lawnmower(i) for i in range(GRID_HEIGHT)]
self.zombies_killed_this_wave = 0
self.zombies_to_spawn = 0
self.wave_completed = False
# 初始化关卡数据(减少第一关僵尸数量)
self.level_data = [
{"waves": 1, "zombies_per_wave": 3, "description": "新手训练营"}, # 第一关只有3个僵尸
{"waves": 2, "zombies_per_wave": 8, "description": "僵尸入侵开始"},
{"waves": 2, "zombies_per_wave": 12, "description": "僵尸大军来袭"},
{"waves": 3, "zombies_per_wave": 15, "description": "僵尸狂潮"},
{"waves": 3, "zombies_per_wave": 20, "description": "最终决战"}
]
# 植物卡片
self.plant_cards = [
{"type": "sunflower", "cost": 50, "rect": pygame.Rect(10, 10, 60, 80)},
{"type": "peashooter", "cost": 100, "rect": pygame.Rect(10, 100, 60, 80)},
]
# 铲子卡片
self.shovel_card = {"type": "shovel", "cost": 0, "rect": pygame.Rect(10, 280, 60, 80)}
def start_level(self, level):
self.level = level
self.plants = []
self.zombies = []
self.suns = []
self.peas = []
self.sun_count = 150
self.selected_plant = None
self.game_over = False
self.wave_timer = 0
self.zombie_spawn_timer = 0
self.sun_spawn_timer = 0
self.score = 0
self.shovel_selected = False
self.lawnmowers = [Lawnmower(i) for i in range(GRID_HEIGHT)]
self.zombies_killed_this_wave = 0
self.current_wave = 1
self.zombies_to_spawn = self.level_data[level-1]["zombies_per_wave"]
self.wave_completed = False
# 根据关卡解锁植物
self.plant_cards = [
{"type": "sunflower", "cost": 50, "rect": pygame.Rect(10, 10, 60, 80)},
{"type": "peashooter", "cost": 100, "rect": pygame.Rect(10, 100, 60, 80)},
]
if level >= 2: # 第二关解锁坚果
self.plant_cards.append({"type": "wallnut", "cost": 50, "rect": pygame.Rect(10, 190, 60, 80)})
if level >= 4: # 第四关解锁辣椒
self.plant_cards.append({"type": "pepper", "cost": 150, "rect": pygame.Rect(10, 280, 60, 80)})
self.state = "playing"
def spawn_sun(self, x=None, y=None, is_falling=True):
if x is None:
x = random.randint(LAWN_LEFT, LAWN_LEFT + GRID_WIDTH * GRID_SIZE)
if y is None and is_falling:
y = 0
self.suns.append(Sun(x, y, is_falling))
def spawn_zombie(self):
row = random.randint(0, GRID_HEIGHT - 1)
self.zombies.append(Zombie(row, self.level))
self.zombies_to_spawn -= 1
def update(self):
if self.game_over or self.state != "playing":
return
# 更新阳光
for sun in self.suns[:]:
sun.update()
if sun.collected:
self.suns.remove(sun)
# 更新植物
for plant in self.plants[:]:
result = plant.update()
if result == True: # 向日葵产生阳光
self.spawn_sun(plant.x, plant.y + 30, False)
elif result == "explode": # 辣椒爆炸
# 消灭附近僵尸
for zombie in self.zombies[:]:
if abs(zombie.y - plant.y) < 100 and abs(zombie.x - plant.x) < 200:
self.zombies.remove(zombie)
self.score += 10
self.zombies_killed_this_wave += 1
# 移除辣椒
self.plants.remove(plant)
# 检查植物是否需要攻击
if plant.can_attack():
# 检查该行是否有僵尸
plant_row = (plant.y - LAWN_TOP) // GRID_SIZE
zombies_in_row = [z for z in self.zombies if z.row == plant_row and z.x > plant.x]
if zombies_in_row:
pea = plant.attack()
if pea:
self.peas.append(pea)
# 更新豌豆
for pea in self.peas[:]:
if pea.update(): # 如果豌豆飞出屏幕
self.peas.remove(pea)
continue
# 检查豌豆是否击中僵尸
for zombie in self.zombies[:]:
if pea.rect.colliderect(zombie.rect):
zombie.health -= pea.damage
if zombie.health <= 0:
self.zombies.remove(zombie)
self.score += 10
self.zombies_killed_this_wave += 1
self.peas.remove(pea)
break
# 更新僵尸
for zombie in self.zombies[:]:
# 如果僵尸没有在吃植物,检查是否碰到植物
if not zombie.eating:
plant_collision = None
for plant in self.plants:
if zombie.rect.colliderect(plant.rect):
plant_collision = plant
break
if plant_collision:
# 开始吃植物
zombie.start_eating(plant_collision)
zombie.update()
# 如果僵尸在吃植物,进行攻击
if zombie.eating and zombie.current_plant:
if zombie.can_attack():
plant_destroyed = zombie.attack(zombie.current_plant)
if plant_destroyed:
self.plants.remove(zombie.current_plant)
zombie.eating = False
zombie.current_plant = None
else:
# 检查僵尸是否到达小推车位置
if zombie.x < LAWN_LEFT:
# 激活小推车
self.lawnmowers[zombie.row].activate()
# 更新小推车
for lawnmower in self.lawnmowers[:]:
if lawnmower.update(): # 如果小推车移出屏幕
self.lawnmowers.remove(lawnmower)
else:
# 检查小推车是否消灭僵尸
if lawnmower.active:
for zombie in self.zombies[:]:
if zombie.row == lawnmower.row and abs(zombie.x - lawnmower.x) < 50:
self.zombies.remove(zombie)
self.score += 10
self.zombies_killed_this_wave += 1
# 检查小推车是否失效
for i, lawnmower in enumerate(self.lawnmowers):
if lawnmower.used and not lawnmower.active:
# 检查是否有僵尸越过失效的小推车
for zombie in self.zombies:
if zombie.row == i and zombie.x < LAWN_LEFT:
self.game_over = True
# 自动生成阳光
self.sun_spawn_timer += 1
if self.sun_spawn_timer >= self.sun_spawn_interval:
self.sun_spawn_timer = 0
self.spawn_sun()
# 生成僵尸
if self.zombies_to_spawn > 0:
self.zombie_spawn_timer += 1
spawn_interval = max(1, 60 - self.level * 5) # 随关卡增加生成速度
if self.zombie_spawn_timer >= spawn_interval:
self.zombie_spawn_timer = 0
self.spawn_zombie()
# 检查波次是否完成
if self.zombies_to_spawn == 0 and len(self.zombies) == 0:
if self.current_wave < self.level_data[self.level-1]["waves"]:
self.current_wave += 1
self.zombies_to_spawn = self.level_data[self.level-1]["zombies_per_wave"]
self.zombies_killed_this_wave = 0
self.wave_completed = True
self.wave_timer = 0
else:
# 完成所有波次
self.game_over = True
if self.level == self.unlocked_level and self.unlocked_level < self.max_level:
self.unlocked_level += 1
def draw(self, surface):
# 绘制背景
surface.fill((135, 206, 235)) # 天空蓝
if self.state == "menu":
# 绘制主菜单
title = title_font.render("植物大战僵尸", True, GREEN)
surface.blit(title, (SCREEN_WIDTH//2 - title.get_width()//2, 100))
start_btn = pygame.Rect(SCREEN_WIDTH//2 - 100, 200, 200, 60)
pygame.draw.rect(surface, (100, 200, 100), start_btn)
pygame.draw.rect(surface, BLACK, start_btn, 3)
start_text = big_font.render("开始游戏", True, WHITE)
surface.blit(start_text, (SCREEN_WIDTH//2 - start_text.get_width()//2, 215))
level_btn = pygame.Rect(SCREEN_WIDTH//2 - 100, 280, 200, 60)
pygame.draw.rect(surface, (100, 150, 200), level_btn)
pygame.draw.rect(surface, BLACK, level_btn, 3)
level_text = big_font.render("选择关卡", True, WHITE)
surface.blit(level_text, (SCREEN_WIDTH//2 - level_text.get_width()//2, 295))
quit_btn = pygame.Rect(SCREEN_WIDTH//2 - 100, 360, 200, 60)
pygame.draw.rect(surface, (200, 100, 100), quit_btn)
pygame.draw.rect(surface, BLACK, quit_btn, 3)
quit_text = big_font.render("退出游戏", True, WHITE)
surface.blit(quit_text, (SCREEN_WIDTH//2 - quit_text.get_width()//2, 375))
# 绘制最高解锁关卡
level_text = font.render(f"已解锁关卡: {self.unlocked_level}/{self.max_level}", True, BLACK)
surface.blit(level_text, (SCREEN_WIDTH//2 - level_text.get_width()//2, 450))
elif self.state == "level_select":
# 绘制关卡选择界面
title = title_font.render("选择关卡", True, GREEN)
surface.blit(title, (SCREEN_WIDTH//2 - title.get_width()//2, 50))
for i in range(self.max_level):
unlocked = i < self.unlocked_level
level_btn = pygame.Rect(150 + (i % 3) * 200, 150 + (i // 3) * 120, 150, 80)
if unlocked:
pygame.draw.rect(surface, (100, 200, 100), level_btn)
else:
pygame.draw.rect(surface, (150, 150, 150), level_btn)
pygame.draw.rect(surface, BLACK, level_btn, 3)
level_text = big_font.render(f"关卡 {i+1}", True, WHITE if unlocked else BLACK)
surface.blit(level_text, (level_btn.x + 75 - level_text.get_width()//2, level_btn.y + 25))
desc_text = font.render(self.level_data[i]["description"], True, BLACK)
surface.blit(desc_text, (level_btn.x + 75 - desc_text.get_width()//2, level_btn.y + 55))
back_btn = pygame.Rect(SCREEN_WIDTH//2 - 100, 450, 200, 60)
pygame.draw.rect(surface, (200, 150, 100), back_btn)
pygame.draw.rect(surface, BLACK, back_btn, 3)
back_text = big_font.render("返回主菜单", True, WHITE)
surface.blit(back_text, (SCREEN_WIDTH//2 - back_text.get_width()//2, 465))
elif self.state == "playing":
# 绘制草坪网格
for row in range(GRID_HEIGHT):
for col in range(GRID_WIDTH):
rect = pygame.Rect(
LAWN_LEFT + col * GRID_SIZE,
LAWN_TOP + row * GRID_SIZE,
GRID_SIZE,
GRID_SIZE
)
pygame.draw.rect(surface, (124, 252, 0), rect)
pygame.draw.rect(surface, BLACK, rect, 1)
# 绘制小推车
for lawnmower in self.lawnmowers:
lawnmower.draw(surface)
# 绘制植物卡片
for card in self.plant_cards:
color = (200, 200, 200) if self.sun_count >= card["cost"] else (100, 100, 100)
pygame.draw.rect(surface, color, card["rect"])
pygame.draw.rect(surface, BLACK, card["rect"], 2)
if card["type"] == "sunflower":
plant_img = sunflower_img
elif card["type"] == "peashooter":
plant_img = peashooter_img
elif card["type"] == "wallnut":
plant_img = wallnut_img
elif card["type"] == "pepper":
plant_img = pepper_img
surface.blit(plant_img, (card["rect"].x + 10, card["rect"].y + 10))
# 绘制成本
cost_text = font.render(str(card["cost"]), True, BLACK)
surface.blit(cost_text, (card["rect"].x + 20, card["rect"].y + 60))
# 绘制铲子卡片
shovel_color = (200, 150, 150) if self.shovel_selected else (200, 200, 200)
pygame.draw.rect(surface, shovel_color, self.shovel_card["rect"])
pygame.draw.rect(surface, BLACK, self.shovel_card["rect"], 2)
surface.blit(shovel_img, (self.shovel_card["rect"].x + 10, self.shovel_card["rect"].y + 10))
shovel_text = font.render("铲子", True, BLACK)
surface.blit(shovel_text, (self.shovel_card["rect"].x + 10, self.shovel_card["rect"].y + 60))
# 绘制选中的植物(跟随鼠标)
if self.selected_plant:
mouse_x, mouse_y = pygame.mouse.get_pos()
if self.selected_plant == "sunflower":
plant_img = sunflower_img
elif self.selected_plant == "peashooter":
plant_img = peashooter_img
elif self.selected_plant == "wallnut":
plant_img = wallnut_img
elif self.selected_plant == "pepper":
plant_img = pepper_img
surface.blit(plant_img, (mouse_x - 30, mouse_y - 30))
# 绘制植物
for plant in self.plants:
plant.draw(surface)
# 绘制僵尸
for zombie in self.zombies:
zombie.draw(surface)
# 绘制豌豆
for pea in self.peas:
pea.draw(surface)
# 绘制阳光
for sun in self.suns:
sun.draw(surface)
# 绘制游戏信息
pygame.draw.rect(surface, (200, 200, 200, 180), (LAWN_LEFT + GRID_WIDTH * GRID_SIZE + 10, 10, 180, 120))
pygame.draw.rect(surface, BLACK, (LAWN_LEFT + GRID_WIDTH * GRID_SIZE + 10, 10, 180, 120), 2)
# 绘制阳光计数
sun_text = font.render(f"阳光: {self.sun_count}", True, BLACK)
surface.blit(sun_text, (LAWN_LEFT + GRID_WIDTH * GRID_SIZE + 20, 20))
# 绘制分数
score_text = font.render(f"分数: {self.score}", True, BLACK)
surface.blit(score_text, (LAWN_LEFT + GRID_WIDTH * GRID_SIZE + 20, 50))
# 绘制关卡和波次信息
level_text = font.render(f"关卡: {self.level}", True, BLACK)
surface.blit(level_text, (LAWN_LEFT + GRID_WIDTH * GRID_SIZE + 20, 80))
wave_text = font.render(f"波次: {self.current_wave}/{self.level_data[self.level-1]['waves']}", True, BLACK)
surface.blit(wave_text, (LAWN_LEFT + GRID_WIDTH * GRID_SIZE + 20, 110))
# 绘制返回按钮
back_btn = pygame.Rect(LAWN_LEFT + GRID_WIDTH * GRID_SIZE + 20, 140, 140, 30)
pygame.draw.rect(surface, (200, 100, 100), back_btn)
pygame.draw.rect(surface, BLACK, back_btn, 2)
back_text = font.render("返回主菜单", True, WHITE)
surface.blit(back_text, (back_btn.x + 10, back_btn.y + 5))
# 波次完成提示
if self.wave_completed:
self.wave_timer += 1
if self.wave_timer < 3 * FPS: # 显示3秒
wave_surf = pygame.Surface((300, 60), pygame.SRCALPHA)
wave_surf.fill((0, 0, 0, 150))
surface.blit(wave_surf, (SCREEN_WIDTH//2 - 150, SCREEN_HEIGHT//2 - 30))
wave_text = big_font.render(f"波次 {self.current_wave-1} 完成!", True, YELLOW)
surface.blit(wave_text, (SCREEN_WIDTH//2 - wave_text.get_width()//2, SCREEN_HEIGHT//2 - 20))
else:
self.wave_completed = False
# 游戏结束画面
if self.game_over:
game_over_surf = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
game_over_surf.fill((0, 0, 0, 180))
surface.blit(game_over_surf, (0, 0))
if len(self.zombies) > 0:
game_over_text = big_font.render("游戏失败!", True, RED)
else:
game_over_text = big_font.render("关卡完成!", True, GREEN)
text_rect = game_over_text.get_rect(center=(SCREEN_WIDTH//2, SCREEN_HEIGHT//2 - 80))
surface.blit(game_over_text, text_rect)
score_text = big_font.render(f"最终分数: {self.score}", True, WHITE)
score_rect = score_text.get_rect(center=(SCREEN_WIDTH//2, SCREEN_HEIGHT//2 - 30))
surface.blit(score_text, score_rect)
restart_text = font.render("按Enter键重新开始本关", True, WHITE)
restart_rect = restart_text.get_rect(center=(SCREEN_WIDTH//2, SCREEN_HEIGHT//2 + 20))
surface.blit(restart_text, restart_rect)
menu_text = font.render("按ESC键返回主菜单", True, WHITE)
menu_rect = menu_text.get_rect(center=(SCREEN_WIDTH//2, SCREEN_HEIGHT//2 + 60))
surface.blit(menu_text, menu_rect)
def handle_click(self, pos):
if self.state == "menu":
# 开始游戏按钮
start_btn = pygame.Rect(SCREEN_WIDTH//2 - 100, 200, 200, 60)
if start_btn.collidepoint(pos):
self.start_level(1)
# 选择关卡按钮
level_btn = pygame.Rect(SCREEN_WIDTH//2 - 100, 280, 200, 60)
if level_btn.collidepoint(pos):
self.state = "level_select"
# 退出游戏按钮
quit_btn = pygame.Rect(SCREEN_WIDTH//2 - 100, 360, 200, 60)
if quit_btn.collidepoint(pos):
pygame.quit()
sys.exit()
elif self.state == "level_select":
# 关卡按钮
for i in range(self.max_level):
if i < self.unlocked_level: # 只处理已解锁关卡
level_btn = pygame.Rect(150 + (i % 3) * 200, 150 + (i // 3) * 120, 150, 80)
if level_btn.collidepoint(pos):
self.start_level(i+1)
# 返回主菜单按钮
back_btn = pygame.Rect(SCREEN_WIDTH//2 - 100, 450, 200, 60)
if back_btn.collidepoint(pos):
self.state = "menu"
elif self.state == "playing" and not self.game_over:
# 返回主菜单按钮
back_btn = pygame.Rect(LAWN_LEFT + GRID_WIDTH * GRID_SIZE + 20, 140, 140, 30)
if back_btn.collidepoint(pos):
self.state = "menu"
return
# 检查是否点击了铲子卡片
if self.shovel_card["rect"].collidepoint(pos):
self.shovel_selected = not self.shovel_selected
if self.shovel_selected:
self.selected_plant = None
return
# 铲子功能
if self.shovel_selected:
for plant in self.plants[:]:
if plant.rect.collidepoint(pos):
self.plants.remove(plant)
self.shovel_selected = False
return
self.shovel_selected = False
return
# 检查是否点击了阳光
for sun in self.suns[:]:
if sun.is_clicked(pos):
self.suns.remove(sun)
self.sun_count += sun.value
return
# 检查是否点击了植物卡片
for card in self.plant_cards:
if card["rect"].collidepoint(pos) and self.sun_count >= card["cost"]:
self.selected_plant = card["type"]
self.shovel_selected = False
return
# 检查是否在草坪上种植植物
if self.selected_plant:
x, y = pos
# 检查点击位置是否在草坪网格内
if (LAWN_LEFT <= x < LAWN_LEFT + GRID_WIDTH * GRID_SIZE and
LAWN_TOP <= y < LAWN_TOP + GRID_HEIGHT * GRID_SIZE):
# 计算网格位置
col = (x - LAWN_LEFT) // GRID_SIZE
row = (y - LAWN_TOP) // GRID_SIZE
plant_x = LAWN_LEFT + col * GRID_SIZE + GRID_SIZE // 2
plant_y = LAWN_TOP + row * GRID_SIZE + GRID_SIZE // 2
# 检查该位置是否已有植物
plant_rect = pygame.Rect(plant_x - 30, plant_y - 30, 60, 60)
occupied = False
for plant in self.plants:
if plant_rect.colliderect(plant.rect):
occupied = True
break
if not occupied:
# 找到选中的植物类型和成本
for card in self.plant_cards:
if card["type"] == self.selected_plant:
if self.sun_count >= card["cost"]:
self.plants.append(Plant(plant_x, plant_y, self.selected_plant))
self.sun_count -= card["cost"]
break
self.selected_plant = None
def main():
game = Game()
running = True
while running:
# 处理事件
for event in pygame.event.get():
if event.type == QUIT:
running = False
elif event.type == MOUSEBUTTONDOWN:
if event.button == 1: # 左键点击
game.handle_click(event.pos)
elif event.type == KEYDOWN:
if event.key == K_RETURN and game.state == "playing" and game.game_over:
game.start_level(game.level)
elif event.key == K_ESCAPE:
if game.state == "playing" and game.game_over:
game.state = "menu"
elif game.state == "playing":
game.state = "menu"
elif game.state == "level_select":
game.state = "menu"
elif event.key == K_q: # 按Q键退出游戏
running = False
# 更新游戏状态
if game.state == "playing":
game.update()
# 绘制
game.draw(screen)
pygame.display.flip()
# 控制帧率
clock.tick(FPS)
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()
点击查看代码
import pygame
import random
import sys
import os
import math
from pygame.locals import *
# 初始化pygame
pygame.init()
# 游戏常量
SCREEN_WIDTH = 900
SCREEN_HEIGHT = 600
FPS = 60
GRID_SIZE = 80
GRID_WIDTH = 9
GRID_HEIGHT = 5
LAWN_LEFT = 150
LAWN_TOP = 100
# 颜色定义
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
BROWN = (139, 69, 19)
YELLOW = (255, 255, 0)
BLUE = (0, 0, 255)
RED = (255, 0, 0)
GRAY = (100, 100, 100)
PURPLE = (128, 0, 128)
# 创建游戏窗口
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("植物大战僵尸 - 优化版")
clock = pygame.time.Clock()
# 创建更复杂的图像
def create_complex_sun():
surf = pygame.Surface((40, 40), pygame.SRCALPHA)
pygame.draw.circle(surf, YELLOW, (20, 20), 20)
for i in range(8):
angle = i * 45
end_x = 20 + 25 * pygame.math.Vector2(1, 0).rotate(angle).x
end_y = 20 + 25 * pygame.math.Vector2(1, 0).rotate(angle).y
pygame.draw.line(surf, YELLOW, (20, 20), (end_x, end_y), 3)
pygame.draw.circle(surf, (255, 165, 0), (20, 20), 15)
return surf
def create_complex_sunflower():
surf = pygame.Surface((60, 60), pygame.SRCALPHA)
# 茎
pygame.draw.rect(surf, GREEN, (28, 30, 4, 30))
# 花盆
pygame.draw.rect(surf, BROWN, (20, 50, 20, 10))
# 花心
pygame.draw.circle(surf, (139, 69, 19), (30, 25), 15)
# 花瓣
for i in range(8):
angle = i * 45
end_x = 30 + 25 * pygame.math.Vector2(1, 0).rotate(angle).x
end_y = 25 + 25 * pygame.math.Vector2(1, 0).rotate(angle).y
pygame.draw.circle(surf, (255, 215, 0), (end_x, end_y), 10)
# 笑脸
pygame.draw.circle(surf, BLACK, (25, 20), 2)
pygame.draw.circle(surf, BLACK, (35, 20), 2)
pygame.draw.arc(surf, BLACK, (22, 25, 16, 10), 0.2, 2.9, 2)
return surf
def create_complex_zombie():
surf = pygame.Surface((50, 80), pygame.SRCALPHA)
# 身体
pygame.draw.ellipse(surf, (150, 150, 150), (10, 20, 30, 50))
# 头
pygame.draw.circle(surf, (200, 200, 200), (25, 20), 15)
# 眼睛
pygame.draw.circle(surf, (50, 50, 50), (20, 18), 3)
pygame.draw.circle(surf, (50, 50, 50), (30, 18), 3)
# 嘴巴
pygame.draw.rect(surf, (100, 0, 0), (20, 25, 10, 3))
# 衣服
pygame.draw.rect(surf, (70, 70, 70), (10, 35, 30, 15))
pygame.draw.line(surf, (100, 100, 100), (10, 42), (40, 42), 2)
# 手臂
pygame.draw.line(surf, (200, 200, 200), (10, 40), (0, 50), 5)
pygame.draw.line(surf, (200, 200, 200), (40, 40), (50, 50), 5)
# 腿
pygame.draw.line(surf, (70, 70, 70), (20, 70), (20, 80), 5)
pygame.draw.line(surf, (70, 70, 70), (30, 70), (30, 80), 5)
# 僵尸标识
font = pygame.font.SysFont('Arial', 12, bold=True)
text = font.render("Z", True, RED)
surf.blit(text, (40, 10))
return surf
def create_complex_peashooter():
surf = pygame.Surface((60, 60), pygame.SRCALPHA)
# 茎
pygame.draw.rect(surf, GREEN, (28, 30, 4, 30))
# 花盆
pygame.draw.rect(surf, BROWN, (20, 50, 20, 10))
# 头部
pygame.draw.circle(surf, GREEN, (30, 25), 15)
# 嘴巴
pygame.draw.circle(surf, (0, 100, 0), (35, 25), 8)
# 眼睛
pygame.draw.circle(surf, BLACK, (25, 20), 3)
# 豌豆标识
font = pygame.font.SysFont('Arial', 12, bold=True)
text = font.render("P", True, (0, 100, 0))
surf.blit(text, (20, 15))
return surf
def create_complex_wallnut():
surf = pygame.Surface((60, 60), pygame.SRCALPHA)
# 坚果
pygame.draw.ellipse(surf, BROWN, (10, 10, 40, 50))
# 纹理
pygame.draw.line(surf, (101, 67, 33), (20, 20), (40, 25), 2)
pygame.draw.line(surf, (101, 67, 33), (25, 30), (35, 40), 2)
pygame.draw.line(surf, (101, 67, 33), (30, 45), (20, 50), 2)
# 表情
pygame.draw.circle(surf, BLACK, (25, 30), 2)
pygame.draw.circle(surf, BLACK, (35, 30), 2)
pygame.draw.arc(surf, BLACK, (22, 35, 16, 10), 0.2, 2.9, 2)
# 坚果标识
font = pygame.font.SysFont('Arial', 12, bold=True)
text = font.render("W", True, (101, 67, 33))
surf.blit(text, (40, 20))
return surf
def create_complex_pea():
surf = pygame.Surface((20, 10), pygame.SRCALPHA)
pygame.draw.ellipse(surf, (50, 200, 50), (0, 0, 20, 10))
pygame.draw.ellipse(surf, (0, 100, 0), (5, 2, 10, 6))
return surf
def create_complex_shovel():
surf = pygame.Surface((40, 40), pygame.SRCALPHA)
# 手柄
pygame.draw.rect(surf, BROWN, (15, 5, 10, 20))
# 铲头
points = [(10, 25), (30, 25), (25, 40), (15, 40)]
pygame.draw.polygon(surf, (180, 180, 180), points)
return surf
def create_complex_pepper():
surf = pygame.Surface((60, 60), pygame.SRCALPHA)
# 茎
pygame.draw.rect(surf, GREEN, (28, 30, 4, 30))
# 花盆
pygame.draw.rect(surf, BROWN, (20, 50, 20, 10))
# 辣椒
pygame.draw.ellipse(surf, RED, (15, 15, 30, 40))
# 火焰效果
for i in range(5):
angle = random.randint(0, 360)
length = random.randint(5, 15)
end_x = 30 + length * math.cos(math.radians(angle))
end_y = 30 + length * math.sin(math.radians(angle))
pygame.draw.line(surf, (255, 165, 0), (30, 30), (end_x, end_y), 3)
# 标识
font = pygame.font.SysFont('Arial', 12, bold=True)
text = font.render("F", True, WHITE)
surf.blit(text, (45, 20))
return surf
def create_lawnmower():
surf = pygame.Surface((60, 40), pygame.SRCALPHA)
# 车身
pygame.draw.rect(surf, (50, 200, 50), (0, 10, 50, 20))
# 轮子
pygame.draw.circle(surf, BLACK, (10, 30), 8)
pygame.draw.circle(surf, BLACK, (40, 30), 8)
# 刀片
pygame.draw.rect(surf, (200, 200, 200), (50, 15, 10, 10))
return surf
# 创建更复杂的图像资源
sun_img = create_complex_sun()
sunflower_img = create_complex_sunflower()
peashooter_img = create_complex_peashooter()
wallnut_img = create_complex_wallnut()
zombie_img = create_complex_zombie()
pea_img = create_complex_pea()
shovel_img = create_complex_shovel()
pepper_img = create_complex_pepper()
lawnmower_img = create_lawnmower()
# 游戏字体
font = pygame.font.SysFont('Arial', 24)
big_font = pygame.font.SysFont('Arial', 36, bold=True)
title_font = pygame.font.SysFont('Arial', 48, bold=True)
class Sun:
def __init__(self, x, y, is_falling=True):
self.x = x
self.y = y
self.image = sun_img
self.rect = self.image.get_rect(center=(x, y))
self.value = 25
self.speed = 2
self.is_falling = is_falling
self.collected = False
self.fall_target_y = y + random.randint(100, 200)
self.lifetime = 0
self.max_lifetime = 10 * FPS # 10秒
def update(self):
self.lifetime += 1
if self.lifetime >= self.max_lifetime:
self.collected = True
if self.is_falling and self.y < self.fall_target_y:
self.y += self.speed
self.rect.center = (self.x, self.y)
def draw(self, surface):
surface.blit(self.image, self.rect)
def is_clicked(self, pos):
return self.rect.collidepoint(pos) and not self.collected
class Plant:
def __init__(self, x, y, plant_type):
self.x = x
self.y = y
self.plant_type = plant_type
self.health = 100
self.attack_cooldown = 0
self.eaten = False # 是否正在被吃
if plant_type == "peashooter":
self.image = peashooter_img
self.cost = 100
self.attack_speed = 2 * FPS # 每2秒攻击一次
elif plant_type == "sunflower":
self.image = sunflower_img
self.cost = 50
self.sun_production_speed = 10 * FPS # 每10秒产生阳光
self.sun_production_timer = 0
elif plant_type == "wallnut":
self.image = wallnut_img
self.cost = 50
self.health = 300 # 坚果墙有更多生命值
elif plant_type == "pepper":
self.image = pepper_img
self.cost = 150
self.exploded = False
self.rect = self.image.get_rect(center=(x, y))
def update(self):
if self.plant_type == "peashooter":
if self.attack_cooldown > 0:
self.attack_cooldown -= 1
elif self.plant_type == "sunflower":
self.sun_production_timer += 1
if self.sun_production_timer >= self.sun_production_speed:
self.sun_production_timer = 0
return True # 表示应该产生阳光
elif self.plant_type == "pepper":
# 辣椒种植后立即爆炸
if not self.exploded:
self.exploded = True
return "explode"
return False
def draw(self, surface):
surface.blit(self.image, self.rect)
# 绘制生命条(辣椒不需要)
if self.plant_type != "pepper":
health_bar_width = 40
health_ratio = self.health / (300 if self.plant_type == "wallnut" else 100)
pygame.draw.rect(surface, RED, (self.x - health_bar_width//2, self.y - 40, health_bar_width, 5))
pygame.draw.rect(surface, GREEN, (self.x - health_bar_width//2, self.y - 40, int(health_bar_width * health_ratio), 5))
def can_attack(self):
return self.plant_type == "peashooter" and self.attack_cooldown <= 0
def attack(self):
if self.plant_type == "peashooter":
self.attack_cooldown = self.attack_speed
return Pea(self.x + 30, self.y)
return None
class Pea:
def __init__(self, x, y):
self.x = x
self.y = y
self.image = pea_img
self.rect = self.image.get_rect(center=(x, y))
self.speed = 5
self.damage = 20
def update(self):
self.x += self.speed
self.rect.center = (self.x, self.y)
return self.x > SCREEN_WIDTH # 如果豌豆飞出屏幕则返回True
def draw(self, surface):
surface.blit(self.image, self.rect)
class Zombie:
def __init__(self, row, level):
self.x = SCREEN_WIDTH
self.y = LAWN_TOP + row * GRID_SIZE + GRID_SIZE // 2
self.image = zombie_img
self.rect = self.image.get_rect(midright=(self.x, self.y))
# 僵尸速度随关卡增加而增加(初始速度降低)
self.speed = 0.15 + level * 0.05
self.health = 100 + level * 20 # 僵尸生命值随关卡增加
self.damage = 0.5 + level * 0.1 # 僵尸伤害随关卡增加
self.attack_cooldown = 0
self.row = row
self.walk_animation = 0
self.walk_speed = 0.1
self.eating = False # 是否正在吃植物
self.eat_timer = 0 # 吃植物计时器
self.current_plant = None # 当前正在吃的植物
def update(self):
# 如果正在吃植物,则停止移动
if not self.eating:
self.walk_animation += self.walk_speed
if self.walk_animation >= 2:
self.walk_animation = 0
# 轻微左右摆动模拟走路
swing_offset = 2 * math.sin(self.walk_animation * math.pi)
self.x -= self.speed
self.rect.midright = (self.x, self.y + swing_offset)
# 吃植物计时
if self.eating:
self.eat_timer += 1
# 坚果需要更长时间吃完
eat_time = 3 * FPS if self.current_plant and self.current_plant.plant_type == "wallnut" else 1 * FPS
if self.eat_timer >= eat_time:
self.eating = False
self.eat_timer = 0
self.current_plant = None
if self.attack_cooldown > 0:
self.attack_cooldown -= 1
def start_eating(self, plant):
"""开始吃植物"""
self.eating = True
self.eat_timer = 0
self.current_plant = plant
def draw(self, surface):
surface.blit(self.image, self.rect)
# 绘制生命条
health_bar_width = 40
health_ratio = self.health / (100 + self.health * 0.2)
pygame.draw.rect(surface, RED, (self.x - health_bar_width//2, self.y - 50, health_bar_width, 5))
pygame.draw.rect(surface, GREEN, (self.x - health_bar_width//2, self.y - 50, int(health_bar_width * health_ratio), 5))
# 如果正在吃植物,绘制吃植物状态
if self.eating:
eat_text = font.render("吃...", True, RED)
surface.blit(eat_text, (self.x - 20, self.y - 70))
def can_attack(self):
return self.attack_cooldown <= 0
def attack(self, plant):
self.attack_cooldown = FPS # 1秒攻击一次
plant.health -= self.damage
return plant.health <= 0 # 返回植物是否被摧毁
class Lawnmower:
def __init__(self, row):
self.row = row
self.x = LAWN_LEFT - 30
self.y = LAWN_TOP + row * GRID_SIZE + GRID_SIZE // 2
self.image = lawnmower_img
self.rect = self.image.get_rect(center=(self.x, self.y))
self.active = False
self.speed = 10
self.used = False # 是否已使用
def activate(self):
if not self.used:
self.active = True
self.used = True
def update(self):
if self.active:
self.x += self.speed
self.rect.center = (self.x, self.y)
return self.x > SCREEN_WIDTH # 如果小推车移出屏幕则返回True
return False
def draw(self, surface):
if not self.active:
surface.blit(self.image, self.rect)
class Game:
def __init__(self):
self.plants = []
self.zombies = []
self.suns = []
self.peas = []
self.sun_count = 150
self.selected_plant = None
self.game_over = False
self.wave_timer = 0
self.zombie_spawn_timer = 0
self.sun_spawn_timer = 0
self.sun_spawn_interval = 10 * FPS # 10秒自动生成一个阳光
self.score = 0
self.level = 1
self.max_level = 5
self.unlocked_level = 1
self.state = "menu" # menu, level_select, playing, game_over
self.shovel_selected = False
self.lawnmowers = [Lawnmower(i) for i in range(GRID_HEIGHT)]
self.zombies_killed_this_wave = 0
self.zombies_to_spawn = 0
self.wave_completed = False
# 初始化关卡数据(减少第一关僵尸数量)
self.level_data = [
{"waves": 1, "zombies_per_wave": 3, "description": "新手训练营"}, # 第一关只有3个僵尸
{"waves": 2, "zombies_per_wave": 8, "description": "僵尸入侵开始"},
{"waves": 2, "zombies_per_wave": 12, "description": "僵尸大军来袭"},
{"waves": 3, "zombies_per_wave": 15, "description": "僵尸狂潮"},
{"waves": 3, "zombies_per_wave": 20, "description": "最终决战"}
]
# 植物卡片
self.plant_cards = [
{"type": "sunflower", "cost": 50, "rect": pygame.Rect(10, 10, 60, 80)},
{"type": "peashooter", "cost": 100, "rect": pygame.Rect(10, 100, 60, 80)},
]
# 铲子卡片
self.shovel_card = {"type": "shovel", "cost": 0, "rect": pygame.Rect(10, 280, 60, 80)}
def start_level(self, level):
self.level = level
self.plants = []
self.zombies = []
self.suns = []
self.peas = []
self.sun_count = 150
self.selected_plant = None
self.game_over = False
self.wave_timer = 0
self.zombie_spawn_timer = 0
self.sun_spawn_timer = 0
self.score = 0
self.shovel_selected = False
self.lawnmowers = [Lawnmower(i) for i in range(GRID_HEIGHT)]
self.zombies_killed_this_wave = 0
self.current_wave = 1
self.zombies_to_spawn = self.level_data[level-1]["zombies_per_wave"]
self.wave_completed = False
# 根据关卡解锁植物
self.plant_cards = [
{"type": "sunflower", "cost": 50, "rect": pygame.Rect(10, 10, 60, 80)},
{"type": "peashooter", "cost": 100, "rect": pygame.Rect(10, 100, 60, 80)},
]
if level >= 2: # 第二关解锁坚果
self.plant_cards.append({"type": "wallnut", "cost": 50, "rect": pygame.Rect(10, 190, 60, 80)})
if level >= 4: # 第四关解锁辣椒
self.plant_cards.append({"type": "pepper", "cost": 150, "rect": pygame.Rect(10, 280, 60, 80)})
self.state = "playing"
def spawn_sun(self, x=None, y=None, is_falling=True):
if x is None:
x = random.randint(LAWN_LEFT, LAWN_LEFT + GRID_WIDTH * GRID_SIZE)
if y is None and is_falling:
y = 0
self.suns.append(Sun(x, y, is_falling))
def spawn_zombie(self):
row = random.randint(0, GRID_HEIGHT - 1)
self.zombies.append(Zombie(row, self.level))
self.zombies_to_spawn -= 1
def update(self):
if self.game_over or self.state != "playing":
return
# 更新阳光
for sun in self.suns[:]:
sun.update()
if sun.collected:
self.suns.remove(sun)
# 更新植物
for plant in self.plants[:]:
result = plant.update()
if result == True: # 向日葵产生阳光
self.spawn_sun(plant.x, plant.y + 30, False)
elif result == "explode": # 辣椒爆炸
# 消灭附近僵尸
for zombie in self.zombies[:]:
if abs(zombie.y - plant.y) < 100 and abs(zombie.x - plant.x) < 200:
self.zombies.remove(zombie)
self.score += 10
self.zombies_killed_this_wave += 1
# 移除辣椒
self.plants.remove(plant)
# 检查植物是否需要攻击
if plant.can_attack():
# 检查该行是否有僵尸
plant_row = (plant.y - LAWN_TOP) // GRID_SIZE
zombies_in_row = [z for z in self.zombies if z.row == plant_row and z.x > plant.x]
if zombies_in_row:
pea = plant.attack()
if pea:
self.peas.append(pea)
# 更新豌豆
for pea in self.peas[:]:
if pea.update(): # 如果豌豆飞出屏幕
self.peas.remove(pea)
continue
# 检查豌豆是否击中僵尸
for zombie in self.zombies[:]:
if pea.rect.colliderect(zombie.rect):
zombie.health -= pea.damage
if zombie.health <= 0:
self.zombies.remove(zombie)
self.score += 10
self.zombies_killed_this_wave += 1
self.peas.remove(pea)
break
# 更新僵尸
for zombie in self.zombies[:]:
# 如果僵尸没有在吃植物,检查是否碰到植物
if not zombie.eating:
plant_collision = None
for plant in self.plants:
if zombie.rect.colliderect(plant.rect):
plant_collision = plant
break
if plant_collision:
# 开始吃植物
zombie.start_eating(plant_collision)
zombie.update()
# 如果僵尸在吃植物,进行攻击
if zombie.eating and zombie.current_plant:
if zombie.can_attack():
plant_destroyed = zombie.attack(zombie.current_plant)
if plant_destroyed:
self.plants.remove(zombie.current_plant)
zombie.eating = False
zombie.current_plant = None
else:
# 检查僵尸是否到达小推车位置
if zombie.x < LAWN_LEFT:
# 激活小推车
self.lawnmowers[zombie.row].activate()
# 更新小推车
for lawnmower in self.lawnmowers[:]:
if lawnmower.update(): # 如果小推车移出屏幕
self.lawnmowers.remove(lawnmower)
else:
# 检查小推车是否消灭僵尸
if lawnmower.active:
for zombie in self.zombies[:]:
if zombie.row == lawnmower.row and abs(zombie.x - lawnmower.x) < 50:
self.zombies.remove(zombie)
self.score += 10
self.zombies_killed_this_wave += 1
# 检查小推车是否失效
for i, lawnmower in enumerate(self.lawnmowers):
if lawnmower.used and not lawnmower.active:
# 检查是否有僵尸越过失效的小推车
for zombie in self.zombies:
if zombie.row == i and zombie.x < LAWN_LEFT:
self.game_over = True
# 自动生成阳光
self.sun_spawn_timer += 1
if self.sun_spawn_timer >= self.sun_spawn_interval:
self.sun_spawn_timer = 0
self.spawn_sun()
# 生成僵尸
if self.zombies_to_spawn > 0:
self.zombie_spawn_timer += 1
spawn_interval = max(1, 60 - self.level * 5) # 随关卡增加生成速度
if self.zombie_spawn_timer >= spawn_interval:
self.zombie_spawn_timer = 0
self.spawn_zombie()
# 检查波次是否完成
if self.zombies_to_spawn == 0 and len(self.zombies) == 0:
if self.current_wave < self.level_data[self.level-1]["waves"]:
self.current_wave += 1
self.zombies_to_spawn = self.level_data[self.level-1]["zombies_per_wave"]
self.zombies_killed_this_wave = 0
self.wave_completed = True
self.wave_timer = 0
else:
# 完成所有波次
self.game_over = True
if self.level == self.unlocked_level and self.unlocked_level < self.max_level:
self.unlocked_level += 1
def draw(self, surface):
# 绘制背景
surface.fill((135, 206, 235)) # 天空蓝
if self.state == "menu":
# 绘制主菜单
title = title_font.render("植物大战僵尸", True, GREEN)
surface.blit(title, (SCREEN_WIDTH//2 - title.get_width()//2, 100))
start_btn = pygame.Rect(SCREEN_WIDTH//2 - 100, 200, 200, 60)
pygame.draw.rect(surface, (100, 200, 100), start_btn)
pygame.draw.rect(surface, BLACK, start_btn, 3)
start_text = big_font.render("开始游戏", True, WHITE)
surface.blit(start_text, (SCREEN_WIDTH//2 - start_text.get_width()//2, 215))
level_btn = pygame.Rect(SCREEN_WIDTH//2 - 100, 280, 200, 60)
pygame.draw.rect(surface, (100, 150, 200), level_btn)
pygame.draw.rect(surface, BLACK, level_btn, 3)
level_text = big_font.render("选择关卡", True, WHITE)
surface.blit(level_text, (SCREEN_WIDTH//2 - level_text.get_width()//2, 295))
quit_btn = pygame.Rect(SCREEN_WIDTH//2 - 100, 360, 200, 60)
pygame.draw.rect(surface, (200, 100, 100), quit_btn)
pygame.draw.rect(surface, BLACK, quit_btn, 3)
quit_text = big_font.render("退出游戏", True, WHITE)
surface.blit(quit_text, (SCREEN_WIDTH//2 - quit_text.get_width()//2, 375))
# 绘制最高解锁关卡
level_text = font.render(f"已解锁关卡: {self.unlocked_level}/{self.max_level}", True, BLACK)
surface.blit(level_text, (SCREEN_WIDTH//2 - level_text.get_width()//2, 450))
elif self.state == "level_select":
# 绘制关卡选择界面
title = title_font.render("选择关卡", True, GREEN)
surface.blit(title, (SCREEN_WIDTH//2 - title.get_width()//2, 50))
for i in range(self.max_level):
unlocked = i < self.unlocked_level
level_btn = pygame.Rect(150 + (i % 3) * 200, 150 + (i // 3) * 120, 150, 80)
if unlocked:
pygame.draw.rect(surface, (100, 200, 100), level_btn)
else:
pygame.draw.rect(surface, (150, 150, 150), level_btn)
pygame.draw.rect(surface, BLACK, level_btn, 3)
level_text = big_font.render(f"关卡 {i+1}", True, WHITE if unlocked else BLACK)
surface.blit(level_text, (level_btn.x + 75 - level_text.get_width()//2, level_btn.y + 25))
desc_text = font.render(self.level_data[i]["description"], True, BLACK)
surface.blit(desc_text, (level_btn.x + 75 - desc_text.get_width()//2, level_btn.y + 55))
back_btn = pygame.Rect(SCREEN_WIDTH//2 - 100, 450, 200, 60)
pygame.draw.rect(surface, (200, 150, 100), back_btn)
pygame.draw.rect(surface, BLACK, back_btn, 3)
back_text = big_font.render("返回主菜单", True, WHITE)
surface.blit(back_text, (SCREEN_WIDTH//2 - back_text.get_width()//2, 465))
elif self.state == "playing":
# 绘制草坪网格
for row in range(GRID_HEIGHT):
for col in range(GRID_WIDTH):
rect = pygame.Rect(
LAWN_LEFT + col * GRID_SIZE,
LAWN_TOP + row * GRID_SIZE,
GRID_SIZE,
GRID_SIZE
)
pygame.draw.rect(surface, (124, 252, 0), rect)
pygame.draw.rect(surface, BLACK, rect, 1)
# 绘制小推车
for lawnmower in self.lawnmowers:
lawnmower.draw(surface)
# 绘制植物卡片
for card in self.plant_cards:
color = (200, 200, 200) if self.sun_count >= card["cost"] else (100, 100, 100)
pygame.draw.rect(surface, color, card["rect"])
pygame.draw.rect(surface, BLACK, card["rect"], 2)
if card["type"] == "sunflower":
plant_img = sunflower_img
elif card["type"] == "peashooter":
plant_img = peashooter_img
elif card["type"] == "wallnut":
plant_img = wallnut_img
elif card["type"] == "pepper":
plant_img = pepper_img
surface.blit(plant_img, (card["rect"].x + 10, card["rect"].y + 10))
# 绘制成本
cost_text = font.render(str(card["cost"]), True, BLACK)
surface.blit(cost_text, (card["rect"].x + 20, card["rect"].y + 60))
# 绘制铲子卡片
shovel_color = (200, 150, 150) if self.shovel_selected else (200, 200, 200)
pygame.draw.rect(surface, shovel_color, self.shovel_card["rect"])
pygame.draw.rect(surface, BLACK, self.shovel_card["rect"], 2)
surface.blit(shovel_img, (self.shovel_card["rect"].x + 10, self.shovel_card["rect"].y + 10))
shovel_text = font.render("铲子", True, BLACK)
surface.blit(shovel_text, (self.shovel_card["rect"].x + 10, self.shovel_card["rect"].y + 60))
# 绘制选中的植物(跟随鼠标)
if self.selected_plant:
mouse_x, mouse_y = pygame.mouse.get_pos()
if self.selected_plant == "sunflower":
plant_img = sunflower_img
elif self.selected_plant == "peashooter":
plant_img = peashooter_img
elif self.selected_plant == "wallnut":
plant_img = wallnut_img
elif self.selected_plant == "pepper":
plant_img = pepper_img
surface.blit(plant_img, (mouse_x - 30, mouse_y - 30))
# 绘制植物
for plant in self.plants:
plant.draw(surface)
# 绘制僵尸
for zombie in self.zombies:
zombie.draw(surface)
# 绘制豌豆
for pea in self.peas:
pea.draw(surface)
# 绘制阳光
for sun in self.suns:
sun.draw(surface)
# 绘制游戏信息
pygame.draw.rect(surface, (200, 200, 200, 180), (LAWN_LEFT + GRID_WIDTH * GRID_SIZE + 10, 10, 180, 120))
pygame.draw.rect(surface, BLACK, (LAWN_LEFT + GRID_WIDTH * GRID_SIZE + 10, 10, 180, 120), 2)
# 绘制阳光计数
sun_text = font.render(f"阳光: {self.sun_count}", True, BLACK)
surface.blit(sun_text, (LAWN_LEFT + GRID_WIDTH * GRID_SIZE + 20, 20))
# 绘制分数
score_text = font.render(f"分数: {self.score}", True, BLACK)
surface.blit(score_text, (LAWN_LEFT + GRID_WIDTH * GRID_SIZE + 20, 50))
# 绘制关卡和波次信息
level_text = font.render(f"关卡: {self.level}", True, BLACK)
surface.blit(level_text, (LAWN_LEFT + GRID_WIDTH * GRID_SIZE + 20, 80))
wave_text = font.render(f"波次: {self.current_wave}/{self.level_data[self.level-1]['waves']}", True, BLACK)
surface.blit(wave_text, (LAWN_LEFT + GRID_WIDTH * GRID_SIZE + 20, 110))
# 绘制返回按钮
back_btn = pygame.Rect(LAWN_LEFT + GRID_WIDTH * GRID_SIZE + 20, 140, 140, 30)
pygame.draw.rect(surface, (200, 100, 100), back_btn)
pygame.draw.rect(surface, BLACK, back_btn, 2)
back_text = font.render("返回主菜单", True, WHITE)
surface.blit(back_text, (back_btn.x + 10, back_btn.y + 5))
# 波次完成提示
if self.wave_completed:
self.wave_timer += 1
if self.wave_timer < 3 * FPS: # 显示3秒
wave_surf = pygame.Surface((300, 60), pygame.SRCALPHA)
wave_surf.fill((0, 0, 0, 150))
surface.blit(wave_surf, (SCREEN_WIDTH//2 - 150, SCREEN_HEIGHT//2 - 30))
wave_text = big_font.render(f"波次 {self.current_wave-1} 完成!", True, YELLOW)
surface.blit(wave_text, (SCREEN_WIDTH//2 - wave_text.get_width()//2, SCREEN_HEIGHT//2 - 20))
else:
self.wave_completed = False
# 游戏结束画面
if self.game_over:
game_over_surf = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
game_over_surf.fill((0, 0, 0, 180))
surface.blit(game_over_surf, (0, 0))
if len(self.zombies) > 0:
game_over_text = big_font.render("游戏失败!", True, RED)
else:
game_over_text = big_font.render("关卡完成!", True, GREEN)
text_rect = game_over_text.get_rect(center=(SCREEN_WIDTH//2, SCREEN_HEIGHT//2 - 80))
surface.blit(game_over_text, text_rect)
score_text = big_font.render(f"最终分数: {self.score}", True, WHITE)
score_rect = score_text.get_rect(center=(SCREEN_WIDTH//2, SCREEN_HEIGHT//2 - 30))
surface.blit(score_text, score_rect)
restart_text = font.render("按Enter键重新开始本关", True, WHITE)
restart_rect = restart_text.get_rect(center=(SCREEN_WIDTH//2, SCREEN_HEIGHT//2 + 20))
surface.blit(restart_text, restart_rect)
menu_text = font.render("按ESC键返回主菜单", True, WHITE)
menu_rect = menu_text.get_rect(center=(SCREEN_WIDTH//2, SCREEN_HEIGHT//2 + 60))
surface.blit(menu_text, menu_rect)
def handle_click(self, pos):
if self.state == "menu":
# 开始游戏按钮
start_btn = pygame.Rect(SCREEN_WIDTH//2 - 100, 200, 200, 60)
if start_btn.collidepoint(pos):
self.start_level(1)
# 选择关卡按钮
level_btn = pygame.Rect(SCREEN_WIDTH//2 - 100, 280, 200, 60)
if level_btn.collidepoint(pos):
self.state = "level_select"
# 退出游戏按钮
quit_btn = pygame.Rect(SCREEN_WIDTH//2 - 100, 360, 200, 60)
if quit_btn.collidepoint(pos):
pygame.quit()
sys.exit()
elif self.state == "level_select":
# 关卡按钮
for i in range(self.max_level):
if i < self.unlocked_level: # 只处理已解锁关卡
level_btn = pygame.Rect(150 + (i % 3) * 200, 150 + (i // 3) * 120, 150, 80)
if level_btn.collidepoint(pos):
self.start_level(i+1)
# 返回主菜单按钮
back_btn = pygame.Rect(SCREEN_WIDTH//2 - 100, 450, 200, 60)
if back_btn.collidepoint(pos):
self.state = "menu"
elif self.state == "playing" and not self.game_over:
# 返回主菜单按钮
back_btn = pygame.Rect(LAWN_LEFT + GRID_WIDTH * GRID_SIZE + 20, 140, 140, 30)
if back_btn.collidepoint(pos):
self.state = "menu"
return
# 检查是否点击了铲子卡片
if self.shovel_card["rect"].collidepoint(pos):
self.shovel_selected = not self.shovel_selected
if self.shovel_selected:
self.selected_plant = None
return
# 铲子功能
if self.shovel_selected:
for plant in self.plants[:]:
if plant.rect.collidepoint(pos):
self.plants.remove(plant)
self.shovel_selected = False
return
self.shovel_selected = False
return
# 检查是否点击了阳光
for sun in self.suns[:]:
if sun.is_clicked(pos):
self.suns.remove(sun)
self.sun_count += sun.value
return
# 检查是否点击了植物卡片
for card in self.plant_cards:
if card["rect"].collidepoint(pos) and self.sun_count >= card["cost"]:
self.selected_plant = card["type"]
self.shovel_selected = False
return
# 检查是否在草坪上种植植物
if self.selected_plant:
x, y = pos
# 检查点击位置是否在草坪网格内
if (LAWN_LEFT <= x < LAWN_LEFT + GRID_WIDTH * GRID_SIZE and
LAWN_TOP <= y < LAWN_TOP + GRID_HEIGHT * GRID_SIZE):
# 计算网格位置
col = (x - LAWN_LEFT) // GRID_SIZE
row = (y - LAWN_TOP) // GRID_SIZE
plant_x = LAWN_LEFT + col * GRID_SIZE + GRID_SIZE // 2
plant_y = LAWN_TOP + row * GRID_SIZE + GRID_SIZE // 2
# 检查该位置是否已有植物
plant_rect = pygame.Rect(plant_x - 30, plant_y - 30, 60, 60)
occupied = False
for plant in self.plants:
if plant_rect.colliderect(plant.rect):
occupied = True
break
if not occupied:
# 找到选中的植物类型和成本
for card in self.plant_cards:
if card["type"] == self.selected_plant:
if self.sun_count >= card["cost"]:
self.plants.append(Plant(plant_x, plant_y, self.selected_plant))
self.sun_count -= card["cost"]
break
self.selected_plant = None
def main():
game = Game()
running = True
while running:
# 处理事件
for event in pygame.event.get():
if event.type == QUIT:
running = False
elif event.type == MOUSEBUTTONDOWN:
if event.button == 1: # 左键点击
game.handle_click(event.pos)
elif event.type == KEYDOWN:
if event.key == K_RETURN and game.state == "playing" and game.game_over:
game.start_level(game.level)
elif event.key == K_ESCAPE:
if game.state == "playing" and game.game_over:
game.state = "menu"
elif game.state == "playing":
game.state = "menu"
elif game.state == "level_select":
game.state = "menu"
elif event.key == K_q: # 按Q键退出游戏
running = False
# 更新游戏状态
if game.state == "playing":
game.update()
# 绘制
game.draw(screen)
pygame.display.flip()
# 控制帧率
clock.tick(FPS)
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()
浙公网安备 33010602011771号