代码改变世界

植物大战僵尸

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()