植物大战僵尸
2025-06-23 13:37 nm1137 阅读(34) 评论(0) 收藏 举报import pygame
import random
import sys
from typing import List, Dict, Tuple, Optional
初始化pygame
pygame.init()
pygame.mixer.init()
游戏常量
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
FPS = 60
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)
GRAY = (100, 100, 100)
植物常量
PEASHOOTER_COST = 100
SUNFLOWER_COST = 50
WALLNUT_COST = 125
PEASHOOTER_HEALTH = 300
SUNFLOWER_HEALTH = 200
WALLNUT_HEALTH = 800
僵尸常量
ZOMBIE_HEALTH = 500
ZOMBIE_DAMAGE = 10
ZOMBIE_SPEED = 1
CONEHEAD_ZOMBIE_HEALTH = 1000
BUCKETHEAD_ZOMBIE_HEALTH = 1800
创建游戏窗口
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("植物大战僵尸简化版")
clock = pygame.time.Clock()
加载资源
def load_image(name, size=None):
image = pygame.Surface((50, 50))
if name == "peashooter":
image.fill((0, 150, 0))
elif name == "sunflower":
image.fill((255, 255, 0))
elif name == "wallnut":
image.fill((139, 69, 19))
elif name == "zombie":
image.fill((0, 100, 0))
elif name == "conehead":
image.fill((139, 69, 19))
elif name == "buckethead":
image.fill((192, 192, 192))
elif name == "sun":
image = pygame.Surface((30, 30))
image.fill(YELLOW)
elif name == "pea":
image = pygame.Surface((10, 10))
image.fill(WHITE)
elif name == "background":
image = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
image.fill((100, 200, 100))
if size:
image = pygame.transform.scale(image, size)
return image
加载音效
def load_sound(name):
class DummySound:
def play(self): pass
return DummySound()
游戏资源
IMAGES = {
"peashooter": load_image("peashooter"),
"sunflower": load_image("sunflower"),
"wallnut": load_image("wallnut"),
"zombie": load_image("zombie"),
"conehead": load_image("conehead"),
"buckethead": load_image("buckethead"),
"sun": load_image("sun"),
"pea": load_image("pea"),
"background": load_image("background", (SCREEN_WIDTH, SCREEN_HEIGHT))
}
SOUNDS = {
"plant": load_sound("plant"),
"shoot": load_sound("shoot"),
"zombie_die": load_sound("zombie_die"),
"sun_collect": load_sound("sun_collect"),
"game_over": load_sound("game_over")
}
游戏字体
pygame.font.init()
FONT = pygame.font.SysFont("Arial", 24)
BIG_FONT = pygame.font.SysFont("Arial", 48)
游戏类
class GameObject:
def init(self, x, y, width, height):
self.rect = pygame.Rect(x, y, width, height)
self.health = 0
self.alive = True
def update(self):
if self.health <= 0:
self.alive = False
def draw(self, surface):
pygame.draw.rect(surface, RED, self.rect)
def collides_with(self, other):
return self.rect.colliderect(other.rect)
class Plant(GameObject):
def init(self, x, y, plant_type, cost, health):
super().init(x, y, 50, 50)
self.plant_type = plant_type
self.cost = cost
self.health = health
self.cooldown = 0
self.image = IMAGES[plant_type]
def update(self):
super().update()
if self.cooldown > 0:
self.cooldown -= 1
def draw(self, surface):
surface.blit(self.image, self.rect)
# 绘制生命值条
health_bar_width = int(self.rect.width * (self.health / max(1, self.max_health)))
health_bar = pygame.Rect(self.rect.x, self.rect.y - 10, health_bar_width, 5)
pygame.draw.rect(surface, GREEN if self.health > 0 else RED, health_bar)
pygame.draw.rect(surface, BLACK, health_bar, 1)
class Peashooter(Plant):
def init(self, x, y):
super().init(x, y, "peashooter", PEASHOOTER_COST, PEASHOOTER_HEALTH)
self.max_health = PEASHOOTER_HEALTH
self.attack_cooldown = 100
self.cooldown = self.attack_cooldown
def can_shoot(self):
return self.cooldown == 0
def shoot(self):
self.cooldown = self.attack_cooldown
return Pea(self.rect.right, self.rect.centery)
class Sunflower(Plant):
def init(self, x, y):
super().init(x, y, "sunflower", SUNFLOWER_COST, SUNFLOWER_HEALTH)
self.max_health = SUNFLOWER_HEALTH
self.sun_cooldown = 300
self.cooldown = random.randint(0, self.sun_cooldown // 2)
def can_produce_sun(self):
return self.cooldown == 0
def produce_sun(self):
self.cooldown = self.sun_cooldown
return Sun(self.rect.centerx, self.rect.y - 30)
class Wallnut(Plant):
def init(self, x, y):
super().init(x, y, "wallnut", WALLNUT_COST, WALLNUT_HEALTH)
self.max_health = WALLNUT_HEALTH
class Zombie(GameObject):
def init(self, x, y, health, zombie_type="zombie", speed=ZOMBIE_SPEED, damage=ZOMBIE_DAMAGE):
super().init(x, y, 60, 80)
self.health = health
self.zombie_type = zombie_type
self.speed = speed
self.damage = damage
self.image = IMAGES[zombie_type]
self.eating = False
self.attack_cooldown = 50
self.attack_timer = 0
def update(self):
super().update()
if self.eating:
self.attack_timer -= 1
else:
self.rect.x -= self.speed
def draw(self, surface):
surface.blit(self.image, self.rect)
# 绘制生命值条
health_bar_width = int(self.rect.width * (self.health / max(1, self.max_health)))
health_bar = pygame.Rect(self.rect.x, self.rect.y - 10, health_bar_width, 5)
pygame.draw.rect(surface, GREEN if self.health > 0 else RED, health_bar)
pygame.draw.rect(surface, BLACK, health_bar, 1)
def start_eating(self):
self.eating = True
self.attack_timer = self.attack_cooldown
def stop_eating(self):
self.eating = False
def can_attack(self):
return self.eating and self.attack_timer <= 0
def attack(self, plant):
plant.health -= self.damage
self.attack_timer = self.attack_cooldown
class NormalZombie(Zombie):
def init(self, x, y):
super().init(x, y, ZOMBIE_HEALTH, "zombie")
self.max_health = ZOMBIE_HEALTH
class ConeheadZombie(Zombie):
def init(self, x, y):
super().init(x, y, CONEHEAD_ZOMBIE_HEALTH, "conehead", speed=1.2)
self.max_health = CONEHEAD_ZOMBIE_HEALTH
class BucketheadZombie(Zombie):
def init(self, x, y):
super().init(x, y, BUCKETHEAD_ZOMBIE_HEALTH, "buckethead", speed=1.5)
self.max_health = BUCKETHEAD_ZOMBIE_HEALTH
class Pea(GameObject):
def init(self, x, y):
super().init(x, y, 10, 10)
self.damage = 20
self.speed = 5
self.image = IMAGES["pea"]
def update(self):
super().update()
self.rect.x += self.speed
if self.rect.x > SCREEN_WIDTH:
self.alive = False
def draw(self, surface):
surface.blit(self.image, self.rect)
class Sun(GameObject):
def init(self, x, y, value=25):
super().init(x, y, 30, 30)
self.value = value
self.image = IMAGES["sun"]
self.target_y = y + random.randint(30, 100)
self.falling_speed = 1
self.collected = False
self.collect_time = 0
def update(self):
super().update()
if not self.collected and self.rect.y < self.target_y:
self.rect.y += self.falling_speed
elif self.collected:
self.collect_time += 1
if self.collect_time > 30:
self.alive = False
def draw(self, surface):
surface.blit(self.image, self.rect)
class Game:
def init(self):
self.reset()
def reset(self):
self.sun = 50
self.score = 0
self.wave = 1
self.zombies_spawned = 0
self.zombies_killed = 0
self.zombies_to_spawn = 5 + self.wave * 2
self.game_over = False
self.victory = False
self.selected_plant = None
self.rows = 5
self.cols = 9
self.cell_width = 80
self.cell_height = 100
self.lane_height = SCREEN_HEIGHT // self.rows
self.plants = []
self.zombies = []
self.peas = []
self.sun_list = []
self.zombie_spawn_timer = 0
self.sun_spawn_timer = 0
self.sun_spawn_delay = 300
self.wave_timer = 0
self.wave_delay = 600
self.mouse_x = 0
self.mouse_y = 0
def update(self):
if self.game_over:
return
# 更新阳光生成计时器
self.sun_spawn_timer += 1
if self.sun_spawn_timer >= self.sun_spawn_delay:
self.spawn_sun()
self.sun_spawn_timer = 0
# 更新波次计时器
if self.zombies_spawned >= self.zombies_to_spawn and len(self.zombies) == 0:
self.wave_timer += 1
if self.wave_timer >= self.wave_delay:
self.next_wave()
# 更新阳光
for sun in self.sun_list[:]:
sun.update()
if not sun.alive:
self.sun_list.remove(sun)
# 更新植物
for plant in self.plants[:]:
plant.update()
if not plant.alive:
self.plants.remove(plant)
for zombie in self.zombies:
if zombie.eating and zombie.collides_with(plant):
zombie.stop_eating()
elif plant.plant_type == "peashooter":
peashooter = plant
if peashooter.can_shoot():
pea = peashooter.shoot()
self.peas.append(pea)
SOUNDS["shoot"].play()
elif plant.plant_type == "sunflower":
sunflower = plant
if sunflower.can_produce_sun():
sun = sunflower.produce_sun()
self.sun_list.append(sun)
# 更新豌豆
for pea in self.peas[:]:
pea.update()
if not pea.alive:
self.peas.remove(pea)
else:
# 检查豌豆是否击中僵尸
for zombie in self.zombies:
if pea.collides_with(zombie) and zombie.alive:
zombie.health -= pea.damage
self.peas.remove(pea)
if zombie.health <= 0:
zombie.alive = False
self.zombies_killed += 1
self.score += 100
SOUNDS["zombie_die"].play()
break
# 更新僵尸
for zombie in self.zombies[:]:
zombie.update()
if not zombie.alive:
self.zombies.remove(zombie)
else:
# 检查僵尸是否到达最左边
if zombie.rect.x <= 50:
self.game_over = True
SOUNDS["game_over"].play()
continue
# 检查僵尸是否碰到植物
plant_in_lane = False
for plant in self.plants:
if zombie.rect.colliderect(plant.rect) and plant.alive:
if not zombie.eating:
zombie.start_eating()
if zombie.can_attack():
zombie.attack(plant)
plant_in_lane = True
break
if not plant_in_lane and zombie.eating:
zombie.stop_eating()
# 生成僵尸
if self.zombies_spawned < self.zombies_to_spawn:
self.zombie_spawn_timer += 1
spawn_delay = 120 - self.wave * 5
spawn_delay = max(30, spawn_delay) # 最小生成延迟
if self.zombie_spawn_timer >= spawn_delay:
self.spawn_zombie()
self.zombie_spawn_timer = 0
def draw(self, surface):
# 绘制背景
surface.blit(IMAGES["background"], (0, 0))
# 绘制网格线
for i in range(1, self.rows):
pygame.draw.line(surface, GRAY, (0, i * self.lane_height), (SCREEN_WIDTH, i * self.lane_height), 2)
for i in range(1, self.cols):
pygame.draw.line(surface, GRAY, (i * self.cell_width, 0), (i * self.cell_width, SCREEN_HEIGHT), 2)
# 绘制顶部信息栏
pygame.draw.rect(surface, (50, 50, 50), (0, 0, SCREEN_WIDTH, 50))
sun_text = FONT.render(f"阳光: {self.sun}", True, YELLOW)
score_text = FONT.render(f"分数: {self.score}", True, WHITE)
wave_text = FONT.render(f"波次: {self.wave}", True, WHITE)
surface.blit(sun_text, (20, 15))
surface.blit(score_text, (180, 15))
surface.blit(wave_text, (340, 15))
# 绘制植物选择栏
plant_y = 10
plant_spacing = 70
plants = [
("向日葵", "sunflower", SUNFLOWER_COST),
("豌豆射手", "peashooter", PEASHOOTER_COST),
("坚果墙", "wallnut", WALLNUT_COST)
]
for i, (name, plant_type, cost) in enumerate(plants):
plant_x = SCREEN_WIDTH - 120 - i * plant_spacing
pygame.draw.rect(surface, (100, 100, 100), (plant_x - 5, plant_y - 5, 60, 60))
if self.selected_plant == plant_type:
pygame.draw.rect(surface, YELLOW, (plant_x - 5, plant_y - 5, 60, 60), 3)
surface.blit(IMAGES[plant_type], (plant_x, plant_y))
cost_text = FONT.render(str(cost), True, YELLOW if self.sun >= cost else RED)
surface.blit(cost_text, (plant_x + 30 - cost_text.get_width() // 2, plant_y + 55))
name_text = FONT.render(name, True, WHITE)
surface.blit(name_text, (plant_x + 30 - name_text.get_width() // 2, 60))
# 绘制阳光
for sun in self.sun_list:
sun.draw(surface)
# 绘制植物
for plant in self.plants:
plant.draw(surface)
# 绘制豌豆
for pea in self.peas:
pea.draw(surface)
# 绘制僵尸
for zombie in self.zombies:
zombie.draw(surface)
# 绘制鼠标位置的预览植物
if self.selected_plant and self.can_place_plant(self.mouse_x, self.mouse_y):
cell_x, cell_y = self.get_cell_from_pos(self.mouse_x, self.mouse_y)
if cell_x >= 0 and cell_x < self.cols and cell_y >= 0 and cell_y < self.rows:
plant_x = cell_x * self.cell_width + 15
plant_y = cell_y * self.lane_height + 15
preview_rect = pygame.Rect(plant_x, plant_y, 50, 50)
surface.blit(IMAGES[self.selected_plant], preview_rect)
# 半透明预览
s = pygame.Surface((50, 50), pygame.SRCALPHA)
s.fill((255, 255, 255, 100))
surface.blit(s, preview_rect)
# 绘制游戏结束或胜利界面
if self.game_over:
self.draw_game_over(surface)
elif self.victory:
self.draw_victory(surface)
# 绘制波次信息
if self.zombies_spawned >= self.zombies_to_spawn and len(self.zombies) == 0:
wave_progress = self.wave_timer / self.wave_delay
next_wave_text = FONT.render(f"下一波: {int((self.wave_delay - self.wave_timer) / FPS) + 1}", True, WHITE)
progress_bar_width = 200
progress_bar_height = 20
progress_bar_x = SCREEN_WIDTH // 2 - progress_bar_width // 2
progress_bar_y = 50
pygame.draw.rect(surface, GRAY, (progress_bar_x, progress_bar_y, progress_bar_width, progress_bar_height))
pygame.draw.rect(surface, GREEN, (progress_bar_x, progress_bar_y, int(progress_bar_width * wave_progress), progress_bar_height))
surface.blit(next_wave_text, (SCREEN_WIDTH // 2 - next_wave_text.get_width() // 2, progress_bar_y + 25))
def draw_game_over(self, surface):
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 150))
surface.blit(overlay, (0, 0))
game_over_text = BIG_FONT.render("游戏结束", True, RED)
final_score_text = FONT.render(f"最终分数: {self.score}", True, WHITE)
restart_text = FONT.render("按 R 键重新开始", True, WHITE)
surface.blit(game_over_text, (SCREEN_WIDTH // 2 - game_over_text.get_width() // 2, 200))
surface.blit(final_score_text, (SCREEN_WIDTH // 2 - final_score_text.get_width() // 2, 270))
surface.blit(restart_text, (SCREEN_WIDTH // 2 - restart_text.get_width() // 2, 320))
def draw_victory(self, surface):
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 150))
surface.blit(overlay, (0, 0))
victory_text = BIG_FONT.render("胜利!", True, YELLOW)
final_score_text = FONT.render(f"最终分数: {self.score}", True, WHITE)
restart_text = FONT.render("按 R 键重新开始", True, WHITE)
surface.blit(victory_text, (SCREEN_WIDTH // 2 - victory_text.get_width() // 2, 200))
surface.blit(final_score_text, (SCREEN_WIDTH // 2 - final_score_text.get_width() // 2, 270))
surface.blit(restart_text, (SCREEN_WIDTH // 2 - restart_text.get_width() // 2, 320))
def handle_event(self, event):
if event.type == pygame.QUIT:
return False
if event.type == pygame.MOUSEMOTION:
self.mouse_x, self.mouse_y = event.pos
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # 左键点击
x, y = event.pos
# 检查是否点击了植物选择栏
plant_y = 10
plant_spacing = 70
plants = [
("sunflower", SUNFLOWER_COST),
("peashooter", PEASHOOTER_COST),
("wallnut", WALLNUT_COST)
]
for i, (plant_type, cost) in enumerate(plants):
plant_x = SCREEN_WIDTH - 120 - i * plant_spacing
if plant_x <= x <= plant_x + 50 and plant_y <= y <= plant_y + 50:
if self.sun >= cost:
self.selected_plant = plant_type if self.selected_plant != plant_type else None
else:
self.selected_plant = None
break
# 检查是否点击了阳光
for sun in self.sun_list[:]:
if sun.rect.collidepoint(x, y) and not sun.collected:
sun.collected = True
self.sun += sun.value
SOUNDS["sun_collect"].play()
# 检查是否放置植物
if self.selected_plant and self.can_place_plant(x, y):
cell_x, cell_y = self.get_cell_from_pos(x, y)
plant_x = cell_x * self.cell_width + 15
plant_y = cell_y * self.lane_height + 15
if self.selected_plant == "sunflower":
plant = Sunflower(plant_x, plant_y)
elif self.selected_plant == "peashooter":
plant = Peashooter(plant_x, plant_y)
elif self.selected_plant == "wallnut":
plant = Wallnut(plant_x, plant_y)
self.plants.append(plant)
self.sun -= plant.cost
SOUNDS["plant"].play()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r and (self.game_over or self.victory):
self.reset()
return True
def can_place_plant(self, x, y):
if y < 50: # 不能在顶部信息栏放置
return False
cell_x, cell_y = self.get_cell_from_pos(x, y)
# 检查是否在有效单元格内
if cell_x < 0 or cell_x >= self.cols or cell_y < 0 or cell_y >= self.rows:
return False
# 检查单元格是否已被占用
for plant in self.plants:
plant_cell_x, plant_cell_y = self.get_cell_from_pos(plant.rect.x, plant.rect.y)
if plant_cell_x == cell_x and plant_cell_y == cell_y:
return False
return True
def get_cell_from_pos(self, x, y):
cell_x = x // self.cell_width
cell_y = y // self.lane_height
return cell_x, cell_y
def spawn_sun(self):
x = random.randint(50, SCREEN_WIDTH - 50)
y = -50
self.sun_list.append(Sun(x, y))
def spawn_zombie(self):
lane = random.randint(0, self.rows - 1)
x = SCREEN_WIDTH
y = lane * self.lane_height + 10
# 根据波次随机生成不同类型的僵尸
zombie_type = random.random()
if zombie_type < 0.7: # 70% 普通僵尸
zombie = NormalZombie(x, y)
elif zombie_type < 0.9: # 20% 铁桶僵尸
zombie = BucketheadZombie(x, y)
else: # 10% 橄榄球僵尸
zombie = ConeheadZombie(x, y)
self.zombies.append(zombie)
self.zombies_spawned += 1
def next_wave(self):
self.wave += 1
self.zombies_spawned = 0
self.zombies_to_spawn = 5 + self.wave * 2
self.wave_timer = 0
# 如果达到第10波,游戏胜利
if self.wave > 10:
self.victory = True
游戏主循环
def main():
game = Game()
running = True
while running:
for event in pygame.event.get():
if not game.handle_event(event):
running = False
game.update()
game.draw(screen)
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
sys.exit()
if name == "main":
main()
浙公网安备 33010602011771号