植物大战僵尸python代码
import pygame
import sys
import random
import math
from enum import Enum
from typing import List, Tuple, Optional, Dict, Any
--- 1. 初始化和常量定义 ---
pygame.init()
游戏常量
SCREEN_WIDTH = 1200
SCREEN_HEIGHT = 800
GRID_SIZE = 85
GRID_ROWS = 5
GRID_COLS = 9
LAWN_TOP = 120
LAWN_LEFT = 180
FPS = 60
UI_TOP_PANEL_HEIGHT = 100
颜色定义
C_BLACK = (0, 0, 0)
C_WHITE = (255, 255, 255)
C_RED = (255, 0, 0)
C_GREEN = (0, 200, 0)
C_BLUE = (0, 0, 255)
C_SKY_BLUE = (135, 206, 250)
C_GRASS_LIGHT = (0, 180, 0)
C_GRASS_DARK = (0, 160, 0)
C_WATER_LIGHT = (50, 150, 255)
C_WATER_DARK = (40, 140, 245)
C_BROWN = (139, 69, 19)
C_DARK_BROWN = (101, 67, 33)
C_SUN_YELLOW = (255, 255, 0)
C_SUN_ORANGE = (255, 165, 0)
C_PEA_GREEN = (124, 252, 0)
C_PEA_DARK_GREEN = (0, 100, 0)
C_SNOW_PEA_BLUE = (0, 191, 255)
C_ZOMBIE_SKIN = (202, 224, 192)
C_ZOMBIE_CLOTHES = (100, 80, 50)
C_CONE_ORANGE = (255, 127, 80)
C_BUCKET_GRAY = (192, 192, 192)
C_PUMPKIN_ORANGE = (244, 164, 96)
C_GOLD = (255, 215, 0)
C_YELLOW = (255, 255, 0)
字体初始化
try:
FONT_PATH = "C:/Windows/Fonts/msyh.ttc"
FONT = pygame.font.Font(FONT_PATH, 36)
SMALL_FONT = pygame.font.Font(FONT_PATH, 24)
TINY_FONT = pygame.font.Font(FONT_PATH, 18)
TITLE_FONT = pygame.font.Font(FONT_PATH, 72)
except:
FONT = pygame.font.Font(None, 48)
SMALL_FONT = pygame.font.Font(None, 32)
TINY_FONT = pygame.font.Font(None, 24)
TITLE_FONT = pygame.font.Font(None, 90)
--- 2. 辅助类和枚举 ---
class GameState(Enum):
MENU = "menu"
PLAYING = "playing"
PAUSED = "paused"
GAME_OVER = "game_over"
VICTORY = "victory"
MODE_SELECT = "mode_select" # 新增模式选择状态
class TileType(Enum):
GRASS = "grass"
# 移除 WATER 类型
# WATER = "water"
class PlantType(Enum):
SUNFLOWER = "sunflower"
PEASHOOTER = "peashooter"
WALL_NUT = "wall_nut"
SNOW_PEA = "snow_pea"
PUMPKIN = "pumpkin"
CORN_CANNON = "corn_cannon"
class ZombieType(Enum):
NORMAL = "normal"
CONEHEAD = "conehead"
BUCKETHEAD = "buckethead"
class SpriteFactory:
"""通过代码生成所有游戏元素的像素图像"""
@staticmethod
def create_surface(size, key_color=None):
surf = pygame.Surface(size)
if key_color:
surf.set_colorkey(key_color)
else:
surf = surf.convert_alpha()
surf.fill((0, 0, 0, 0))
return surf
@staticmethod
def create_sunflower():
surf = SpriteFactory.create_surface((70, 70))
# 茎
pygame.draw.rect(surf, C_PEA_GREEN, (32, 45, 6, 25))
# 花心
pygame.draw.circle(surf, C_DARK_BROWN, (35, 35), 15)
# 花瓣
for i in range(8):
angle = i * (math.pi / 4)
x = 35 + math.cos(angle) * 25
y = 35 + math.sin(angle) * 25
pygame.draw.circle(surf, C_SUN_YELLOW, (x, y), 10)
return surf
@staticmethod
def create_peashooter():
surf = SpriteFactory.create_surface((70, 70))
# 茎和叶子
pygame.draw.rect(surf, C_PEA_GREEN, (32, 40, 6, 30))
pygame.draw.ellipse(surf, C_PEA_GREEN, (15, 55, 40, 15))
# 头部和嘴巴
pygame.draw.circle(surf, C_PEA_GREEN, (35, 30), 20)
pygame.draw.circle(surf, C_PEA_DARK_GREEN, (55, 30), 10)
return surf
@staticmethod
def create_snow_pea():
surf = SpriteFactory.create_surface((70, 70))
# 茎和叶子
pygame.draw.rect(surf, C_SNOW_PEA_BLUE, (32, 40, 6, 30))
pygame.draw.ellipse(surf, C_SNOW_PEA_BLUE, (15, 55, 40, 15))
# 头部和嘴巴
pygame.draw.circle(surf, C_SNOW_PEA_BLUE, (35, 30), 20)
pygame.draw.circle(surf, C_BLUE, (55, 30), 10)
return surf
@staticmethod
def create_wall_nut():
surf = SpriteFactory.create_surface((70, 70))
pygame.draw.ellipse(surf, C_BROWN, (5, 5, 60, 65))
pygame.draw.ellipse(surf, C_BLACK, (5, 5, 60, 65), 3)
# 眼睛
pygame.draw.circle(surf, C_WHITE, (25, 35), 8)
pygame.draw.circle(surf, C_WHITE, (45, 35), 8)
pygame.draw.circle(surf, C_BLACK, (25, 35), 4)
pygame.draw.circle(surf, C_BLACK, (45, 35), 4)
return surf
@staticmethod
def create_pumpkin():
surf = SpriteFactory.create_surface((80, 80))
pygame.draw.ellipse(surf, C_PUMPKIN_ORANGE, (5, 5, 70, 70))
pygame.draw.ellipse(surf, C_BLACK, (5, 5, 70, 70), 3)
# 眼睛和嘴
pygame.draw.polygon(surf, C_BLACK, [(25, 25), (35, 40), (15, 40)])
pygame.draw.polygon(surf, C_BLACK, [(55, 25), (65, 40), (45, 40)])
pygame.draw.rect(surf, C_BLACK, (25, 55, 30, 8))
return surf
@staticmethod
def create_corn_cannon():
surf = SpriteFactory.create_surface((70, 70))
# 绘制玉米加农炮
pygame.draw.rect(surf, C_BROWN, (30, 40, 10, 30)) # 炮管
pygame.draw.circle(surf, C_YELLOW, (35, 30), 20) # 炮身
return surf
@staticmethod
def create_zombie(zombie_type: ZombieType):
surf = SpriteFactory.create_surface((70, 90))
# 身体和头部
pygame.draw.rect(surf, C_ZOMBIE_CLOTHES, (20, 40, 30, 50))
pygame.draw.circle(surf, C_ZOMBIE_SKIN, (35, 25), 20)
# 眼睛
pygame.draw.circle(surf, C_WHITE, (30, 25), 5)
pygame.draw.circle(surf, C_WHITE, (45, 25), 5)
pygame.draw.circle(surf, C_BLACK, (30, 25), 2)
pygame.draw.circle(surf, C_BLACK, (45, 25), 2)
# 附加物
if zombie_type == ZombieType.CONEHEAD:
pygame.draw.polygon(surf, C_CONE_ORANGE, [(35, -10), (15, 30), (55, 30)])
elif zombie_type == ZombieType.BUCKETHEAD:
pygame.draw.rect(surf, C_BUCKET_GRAY, (10, 0, 50, 35))
pygame.draw.rect(surf, C_BLACK, (10, 0, 50, 35), 2)
return surf
@staticmethod
def create_sun():
surf = SpriteFactory.create_surface((50, 50))
pygame.draw.circle(surf, C_SUN_ORANGE, (25, 25), 18)
pygame.draw.circle(surf, C_SUN_YELLOW, (25, 25), 15)
return surf
@staticmethod
def create_bullet(bullet_type: str):
size = 15
surf = SpriteFactory.create_surface((size, size))
if bullet_type == "normal":
color = C_PEA_GREEN
elif bullet_type == "snow":
color = C_SNOW_PEA_BLUE
elif bullet_type == "corn":
color = C_YELLOW
pygame.draw.circle(surf, color, (size // 2, size // 2), size // 2)
return surf
@staticmethod
def create_shovel():
surf = SpriteFactory.create_surface((60, 60))
pygame.draw.rect(surf, C_BROWN, (27, 20, 6, 40)) # Handle
pygame.draw.rect(surf, C_BUCKET_GRAY, (15, 0, 30, 20)) # Blade
return surf
@staticmethod
def create_lawnmower():
surf = SpriteFactory.create_surface((70, 60))
pygame.draw.rect(surf, C_RED, (0, 0, 60, 50), border_radius=10)
pygame.draw.rect(surf, C_BLACK, (50, 5, 15, 40)) # Engine
pygame.draw.circle(surf, C_WHITE, (15, 25), 10) # Logo
return surf
--- 3. 游戏对象类 ---
class GameObject:
"""所有游戏对象的基类"""
def __init__(self, row: int, col: int, health: int):
self.row = row
self.col = col
self.x = LAWN_LEFT + col * GRID_SIZE + GRID_SIZE // 2
self.y = LAWN_TOP + row * GRID_SIZE + GRID_SIZE // 2
self.health = health
self.max_health = health
self.alive = True
self.animation_tick = random.randint(0, 50)
def draw_health_bar(self, screen: pygame.Surface, y_offset: int = -40):
if self.health < self.max_health:
bar_width = 50
bar_height = 5
bar_x = self.x - bar_width // 2
bar_y = self.y + y_offset
health_ratio = self.health / self.max_health
pygame.draw.rect(screen, C_RED, (bar_x, bar_y, bar_width, bar_height))
pygame.draw.rect(screen, C_GREEN, (bar_x, bar_y, int(bar_width * health_ratio), bar_height))
pygame.draw.rect(screen, C_BLACK, (bar_x, bar_y, bar_width, bar_height), 1)
def take_damage(self, amount: int):
self.health -= amount
if self.health <= 0:
self.alive = False
class Plant(GameObject):
"""植物类"""
plant_data = {
PlantType.SUNFLOWER: {"cost": 50, "health": 80},
PlantType.PEASHOOTER: {"cost": 100, "health": 100},
PlantType.WALL_NUT: {"cost": 50, "health": 1000},
PlantType.SNOW_PEA: {"cost": 175, "health": 100},
PlantType.PUMPKIN: {"cost": 125, "health": 800},
PlantType.CORN_CANNON: {"cost": 300, "health": 150, "attack_interval": 5000}
}
def __init__(self, plant_type: PlantType, row: int, col: int, sprite: pygame.Surface):
data = self.plant_data[plant_type]
super().__init__(row, col, data["health"])
self.plant_type = plant_type
self.cost = data["cost"]
self.image = sprite
self.last_action_time = pygame.time.get_ticks()
self.pumpkin_shield = None
def update(self, game: 'Game'):
current_time = pygame.time.get_ticks()
self.animation_tick += 1
if self.plant_type == PlantType.SUNFLOWER:
if current_time - self.last_action_time > 8000:
game.add_sun(self.x, self.y, 50) # 向日葵生成阳光值从 25 增加到 50
self.last_action_time = current_time
elif self.plant_type in [PlantType.PEASHOOTER, PlantType.SNOW_PEA]:
if current_time - self.last_action_time > 1500:
has_zombie_in_row = any(z.row == self.row and z.x > self.x for z in game.zombies)
if has_zombie_in_row:
bullet_type = "snow" if self.plant_type == PlantType.SNOW_PEA else "normal"
game.add_bullet(self.row, self.x + 20, bullet_type)
self.last_action_time = current_time
elif self.plant_type == PlantType.CORN_CANNON:
if current_time - self.last_action_time > self.plant_data[PlantType.CORN_CANNON]["attack_interval"]:
has_zombie_in_row = any(z.row == self.row and z.x > self.x for z in game.zombies)
if has_zombie_in_row:
game.add_bullet(self.row, self.x + 20, "corn")
self.last_action_time = current_time
def draw(self, screen: pygame.Surface):
if self.pumpkin_shield:
self.pumpkin_shield.draw_as_shield(screen, self.x, self.y)
img_rect = self.image.get_rect(center=(self.x, self.y))
offset = int(math.sin(self.animation_tick * 0.1) * 3)
img_rect.y += offset
screen.blit(self.image, img_rect)
self.draw_health_bar(screen)
class Pumpkin(Plant):
"""南瓜头类"""
def __init__(self, row: int, col: int, sprite: pygame.Surface):
super().__init__(PlantType.PUMPKIN, row, col, sprite)
def draw_as_shield(self, screen: pygame.Surface, x: int, y: int):
img_rect = self.image.get_rect(center=(x, y))
screen.blit(self.image, img_rect)
self.x, self.y = x, y
self.draw_health_bar(screen, y_offset=-45)
class Zombie(GameObject):
"""僵尸类"""
zombie_data = {
ZombieType.NORMAL: {"health": 100, "speed": 0.6, "damage": 20},
ZombieType.CONEHEAD: {"health": 250, "speed": 0.6, "damage": 20},
ZombieType.BUCKETHEAD: {"health": 500, "speed": 0.6, "damage": 20},
}
def __init__(self, zombie_type: ZombieType, row: int, sprite: pygame.Surface):
data = self.zombie_data[zombie_type]
super().__init__(row, 9, data["health"])
self.x = SCREEN_WIDTH - 20
self.y = LAWN_TOP + row * GRID_SIZE + GRID_SIZE // 2
self.zombie_type = zombie_type
self.speed = data["speed"]
self.damage = data["damage"]
self.image = sprite
self.is_attacking = False
self.slow_timer = 0
self.last_attack_time = 0
def update(self, game: 'Game'):
self.animation_tick += 1
current_speed = self.speed
if self.slow_timer > 0:
current_speed *= 0.5
self.slow_timer -= 1
self.is_attacking = False
target_plant = None
for plant in game.plants:
if plant.row == self.row:
plant_x = LAWN_LEFT + plant.col * GRID_SIZE + GRID_SIZE // 2
if abs(self.x - plant_x) < 30:
target_plant = plant
break
if target_plant:
self.is_attacking = True
current_time = pygame.time.get_ticks()
if current_time - self.last_attack_time > 1000:
if target_plant.pumpkin_shield:
target_plant.pumpkin_shield.take_damage(self.damage)
else:
target_plant.take_damage(self.damage)
self.last_attack_time = current_time
if not self.is_attacking:
self.x -= current_speed
if self.x < LAWN_LEFT - 20:
game.state = GameState.GAME_OVER
def draw(self, screen: pygame.Surface):
img_rect = self.image.get_rect(center=(int(self.x), self.y))
if self.is_attacking:
offset = int(math.sin(self.animation_tick * 0.5) * 5)
img_rect.x += offset
screen.blit(self.image, img_rect)
if self.slow_timer > 0:
ice_overlay = pygame.Surface(img_rect.size, pygame.SRCALPHA)
ice_overlay.fill((100, 100, 255, 100))
screen.blit(ice_overlay, img_rect.topleft)
self.draw_health_bar(screen)
class Bullet:
"""子弹类"""
def __init__(self, row: int, x: int, bullet_type: str, sprite: pygame.Surface):
self.row = row
self.x = x
self.y = LAWN_TOP + row * GRID_SIZE + GRID_SIZE // 2
self.speed = 8
self.bullet_type = bullet_type
self.damage = 20 if bullet_type != "corn" else 50
self.active = True
self.image = sprite
def update(self, game: 'Game'):
self.x += self.speed
if self.x > SCREEN_WIDTH:
self.active = False
for zombie in game.zombies[:]:
if zombie.row == self.row and self.active and abs(self.x - zombie.x) < 20:
zombie.take_damage(self.damage)
if self.bullet_type == "snow":
zombie.slow_timer = 180
game.add_particles(self.x, self.y, 5, C_WHITE, (1, 3), ((-2, 2), (-2, 2)))
def draw(self, screen: pygame.Surface):
img_rect = self.image.get_rect(center=(int(self.x), self.y))
screen.blit(self.image, img_rect)
class Sun:
"""阳光类"""
def __init__(self, x: int, y: int, value: int, sprite: pygame.Surface, is_natural: bool = False):
self.x = x
self.y = y
self.value = value
self.image = sprite
self.rect = self.image.get_rect(center=(x, y))
self.collected = False
self.spawn_time = pygame.time.get_ticks()
self.life_span = 8000
self.is_natural = is_natural
if is_natural:
self.target_y = random.randint(LAWN_TOP, SCREEN_HEIGHT - 50)
self.fall_speed = 1.5
else:
self.target_y = y
self.fall_speed = 0
def update(self, game): # 修改此处,接受 game 参数
if self.is_natural and self.y < self.target_y:
self.y += self.fall_speed
self.rect.centery = int(self.y)
if pygame.time.get_ticks() - self.spawn_time > self.life_span:
self.collected = True
def draw(self, screen: pygame.Surface):
screen.blit(self.image, self.rect)
def check_click(self, pos: Tuple[int, int]) -> bool:
if self.rect.collidepoint(pos):
self.collected = True
return True
return False
class LawnMower:
"""草地剪草车类"""
def __init__(self, row: int, sprite: pygame.Surface):
self.row = row
self.image = sprite
self.x = LAWN_LEFT - GRID_SIZE // 2
self.y = LAWN_TOP + row * GRID_SIZE + GRID_SIZE // 2
self.rect = self.image.get_rect(center=(self.x, self.y))
self.is_active = False
self.speed = 10
self.triggered = False # 新增属性,标记割草机是否已触发
def update(self, game: 'Game'):
if not self.is_active and not self.triggered:
for z in game.zombies:
if z.row == self.row and z.x < self.x + 20:
self.is_active = True
self.triggered = True # 标记割草机已触发
break
if self.is_active:
self.x += self.speed
self.rect.centerx = int(self.x)
for z in game.zombies[:]:
if z.row == self.row and self.rect.colliderect(z.image.get_rect(center=(z.x, z.y))):
z.alive = False
if self.x > SCREEN_WIDTH + 50:
game.lawnmowers.remove(self)
def draw(self, screen: pygame.Surface):
screen.blit(self.image, self.rect)
class Particle:
"""粒子类,用于处理粒子效果"""
def init(self, x, y, size, color, velocity):
self.x = x
self.y = y
self.size = size
self.color = color
self.velocity = velocity
self.lifetime = 30 # 粒子存活帧数
def update(self):
self.x += self.velocity[0]
self.y += self.velocity[1]
self.lifetime -= 1
if self.lifetime <= 0:
return False
return True
def draw(self, screen):
pygame.draw.circle(screen, self.color, (int(self.x), int(self.y)), self.size)
--- 4. UI 和管理器类 ---
class Button:
"""通用按钮类"""
def __init__(self, x, y, width, height, text, font, action):
self.rect = pygame.Rect(x, y, width, height)
self.text = text
self.font = font
self.action = action
self.color = C_BROWN
self.hover_color = C_GREEN
self.is_hovered = False
def handle_event(self, event: pygame.event.Event):
if event.type == pygame.MOUSEMOTION:
self.is_hovered = self.rect.collidepoint(event.pos)
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1 and self.is_hovered:
self.action()
def draw(self, screen: pygame.Surface):
color = self.hover_color if self.is_hovered else self.color
pygame.draw.rect(screen, color, self.rect, border_radius=10)
pygame.draw.rect(screen, C_BLACK, self.rect, 2, border_radius=10)
text_surf = self.font.render(self.text, True, C_WHITE)
text_rect = text_surf.get_rect(center=self.rect.center)
screen.blit(text_surf, text_rect)
class PlantCard:
"""植物选择卡片类"""
def __init__(self, x: int, y: int, plant_type: PlantType, sprite: pygame.Surface):
self.rect = pygame.Rect(x, y, 70, 85)
self.plant_type = plant_type
self.data = Plant.plant_data[plant_type]
self.cost = self.data["cost"]
self.image = sprite
def draw(self, screen: pygame.Surface, sun_count: int, selected_plant_type: Optional[PlantType]):
pygame.draw.rect(screen, C_DARK_BROWN, self.rect, border_radius=5)
img_rect = self.image.get_rect(center=(self.rect.centerx, self.rect.centery - 10))
screen.blit(self.image, img_rect)
cost_text = TINY_FONT.render(str(self.cost), True, C_SUN_YELLOW)
screen.blit(cost_text, (self.rect.x + 5, self.rect.y + 65))
if sun_count < self.cost:
s = pygame.Surface(self.rect.size, pygame.SRCALPHA)
s.fill((100, 100, 100, 150))
screen.blit(s, self.rect.topleft)
if selected_plant_type == self.plant_type:
pygame.draw.rect(screen, C_GOLD, self.rect, 3, border_radius=5)
class UIManager:
"""UI管理器"""
def __init__(self, game: 'Game'):
self.game = game
self.sprites = game.sprites
self.plant_cards = []
card_x = 20
plant_types_in_bar = [pt for pt in PlantType if pt != PlantType.PUMPKIN]
for pt in plant_types_in_bar:
self.plant_cards.append(PlantCard(card_x, 10, pt, self.sprites[pt.value]))
card_x += 80
self.shovel_sprite = self.sprites["shovel"]
self.shovel_rect = self.shovel_sprite.get_rect(topleft=(card_x + 20, 15))
def draw_top_panel(self):
panel_rect = pygame.Rect(0, 0, SCREEN_WIDTH, UI_TOP_PANEL_HEIGHT)
pygame.draw.rect(self.game.screen, C_BROWN, panel_rect)
pygame.draw.line(self.game.screen, C_BLACK, (0, UI_TOP_PANEL_HEIGHT), (SCREEN_WIDTH, UI_TOP_PANEL_HEIGHT), 3)
sun_icon = self.sprites["sun"]
self.game.screen.blit(sun_icon, (SCREEN_WIDTH - 250, 25))
sun_text = FONT.render(f"{self.game.sun_count}", True, C_BLACK)
self.game.screen.blit(sun_text, (SCREEN_WIDTH - 190, 30))
for card in self.plant_cards:
card.draw(self.game.screen, self.game.sun_count, self.game.selected_plant_type)
self.game.screen.blit(self.shovel_sprite, self.shovel_rect)
if self.game.shovel_selected:
pygame.draw.rect(self.game.screen, C_GOLD, self.shovel_rect, 3)
def draw_game_messages(self):
wave_text = FONT.render(f"WAVE: {self.game.wave}", True, C_RED)
self.game.screen.blit(wave_text, (SCREEN_WIDTH - 250, 750))
def check_click(self, pos: Tuple[int, int]):
for card in self.plant_cards:
if card.rect.collidepoint(pos):
if self.game.sun_count >= card.cost:
self.game.selected_plant_type = card.plant_type
self.game.shovel_selected = False
return
if self.shovel_rect.collidepoint(pos):
self.game.shovel_selected = True
self.game.selected_plant_type = None
--- 5. 主游戏类 ---
class Game:
"""主游戏类"""
def __init__(self):
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("植物大战僵尸 - 纯代码像素版")
self.clock = pygame.time.Clock()
self.state = GameState.MODE_SELECT # 初始状态设为模式选择
self.sprites = self.create_all_sprites()
self.plants: List[Plant] = []
self.zombies: List[Zombie] = []
self.bullets: List[Bullet] = []
self.suns: List[Sun] = []
self.lawnmowers: List[LawnMower] = []
self.particles = [] # 初始化粒子列表
self.sun_count = 500 # 初始阳光从 150 增加到 500
self.wave = 0
self.zombie_spawn_timer = 0
self.natural_sun_timer = 0
self.selected_plant_type: Optional[PlantType] = None
self.shovel_selected = False
self.ui_manager = UIManager(self)
self.menu_buttons = self._create_menu_buttons()
self.mode_select_buttons = self._create_mode_select_buttons()
self.grid_tile_types = self._create_level_map()
def create_all_sprites(self) -> Dict[str, pygame.Surface]:
sprites = {}
for pt in PlantType:
sprites[pt.value] = getattr(SpriteFactory, f"create_{pt.value}")()
for zt in ZombieType:
sprites[zt.value] = SpriteFactory.create_zombie(zt)
sprites["sun"] = SpriteFactory.create_sun()
sprites["bullet_normal"] = SpriteFactory.create_bullet("normal")
sprites["bullet_snow"] = SpriteFactory.create_bullet("snow")
sprites["bullet_corn"] = SpriteFactory.create_bullet("corn")
sprites["shovel"] = SpriteFactory.create_shovel()
sprites["lawnmower"] = SpriteFactory.create_lawnmower()
return sprites
def _create_menu_buttons(self) -> List[Button]:
start_button = Button(SCREEN_WIDTH // 2 - 150, 300, 300, 80, "开始游戏", FONT, self.start_game)
quit_button = Button(SCREEN_WIDTH // 2 - 150, 400, 300, 80, "退出游戏", FONT, self.quit_game)
return [start_button, quit_button]
def _create_mode_select_buttons(self) -> List[Button]:
"""创建模式选择按钮"""
easy_mode_button = Button(SCREEN_WIDTH // 2 - 150, 300, 300, 80, "简单模式", FONT, lambda: self.start_game(mode="easy"))
hard_mode_button = Button(SCREEN_WIDTH // 2 - 150, 400, 300, 80, "困难模式", FONT, lambda: self.start_game(mode="hard"))
quit_button = Button(SCREEN_WIDTH // 2 - 150, 500, 300, 80, "退出游戏", FONT, self.quit_game)
return [easy_mode_button, hard_mode_button, quit_button]
def start_game(self, mode="easy"):
"""根据模式开始游戏"""
self.reset_game()
self.state = GameState.PLAYING
# 可根据不同模式调整游戏参数
if mode == "hard":
self.zombie_spawn_timer = 5000 # 示例参数调整
def _create_level_map(self) -> List[List[TileType]]:
# 全部设为草地
grid = [[TileType.GRASS for _ in range(GRID_COLS)] for _ in range(GRID_ROWS)]
return grid
def reset_game(self):
self.plants.clear()
self.zombies.clear()
self.bullets.clear()
self.suns.clear()
self.lawnmowers = [LawnMower(i, self.sprites["lawnmower"]) for i in range(GRID_ROWS)]
self.sun_count = 500 # 初始阳光从 150 增加到 500
self.wave = 0
self.selected_plant_type = None
self.shovel_selected = False
self._next_wave()
def quit_game(self):
pygame.quit()
sys.exit()
def _next_wave(self):
self.wave += 1
self.zombie_spawn_timer = pygame.time.get_ticks()
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.quit_game()
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
if self.state == GameState.PLAYING:
self.state = GameState.PAUSED
elif self.state == GameState.PAUSED:
self.state = GameState.PLAYING
if self.state == GameState.MODE_SELECT:
for btn in self.mode_select_buttons:
btn.handle_event(event)
elif self.state == GameState.MENU:
for btn in self.menu_buttons:
btn.handle_event(event)
elif self.state == GameState.PLAYING:
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
self.handle_game_click(event.pos)
def handle_game_click(self, pos: Tuple[int, int]):
self.ui_manager.check_click(pos)
for sun in self.suns[:]:
if sun.check_click(pos):
self.sun_count += sun.value
self.suns.remove(sun)
return
if LAWN_LEFT < pos[0] < LAWN_LEFT + GRID_COLS * GRID_SIZE and \
LAWN_TOP < pos[1] < LAWN_TOP + GRID_ROWS * GRID_SIZE:
col = (pos[0] - LAWN_LEFT) // GRID_SIZE
row = (pos[1] - LAWN_TOP) // GRID_SIZE
self.handle_grid_click(row, col)
def handle_grid_click(self, row: int, col: int):
if self.shovel_selected:
for plant in self.plants[:]:
if plant.row == row and plant.col == col:
self.sun_count += plant.cost // 3
self.plants.remove(plant)
if plant.pumpkin_shield:
self.plants.remove(plant.pumpkin_shield)
self.shovel_selected = False
return
if self.selected_plant_type:
plant_type = self.selected_plant_type
plant_cost = Plant.plant_data[plant_type]['cost']
if self.sun_count < plant_cost:
return
existing_plant = next((p for p in self.plants if p.row == row and p.col == col), None)
if existing_plant:
if plant_type == PlantType.PUMPKIN and not existing_plant.pumpkin_shield:
new_pumpkin = Pumpkin(row, col, self.sprites[plant_type.value])
existing_plant.pumpkin_shield = new_pumpkin
self.plants.append(new_pumpkin)
self.sun_count -= plant_cost
self.selected_plant_type = None
return
# 因为全是草地,简化逻辑
new_plant = Plant(plant_type, row, col, self.sprites[plant_type.value])
self.plants.append(new_plant)
self.sun_count -= plant_cost
self.selected_plant_type = None
def update(self):
for obj_list in [self.plants, self.zombies, self.bullets, self.suns, self.lawnmowers, self.particles]:
for obj in obj_list[:]:
if hasattr(obj, 'update'):
if isinstance(obj, Particle):
if not obj.update():
obj_list.remove(obj)
else:
obj.update(self)
if hasattr(obj, 'alive') and not obj.alive:
obj_list.remove(obj)
elif hasattr(obj, 'collected') and obj.collected:
obj_list.remove(obj)
for p in self.plants[:]:
if p.pumpkin_shield and not p.pumpkin_shield.alive:
self.plants.remove(p.pumpkin_shield)
p.pumpkin_shield = None
if not p.alive:
self.plants.remove(p)
if pygame.time.get_ticks() - self.natural_sun_timer > 7000: # 自然阳光生成间隔从 10000 缩短到 7000 毫秒
x = random.randint(LAWN_LEFT, SCREEN_WIDTH - 50)
self.add_sun(x, 50, 75, is_natural=True) # 自然阳光值从 25 增加到 75
self.natural_sun_timer = pygame.time.get_ticks()
if self.wave < 10 and pygame.time.get_ticks() - self.zombie_spawn_timer > (15000 - self.wave * 1000):
# 波数越多,每波生成的僵尸数量越多
num_zombies = self.wave + 1
for _ in range(num_zombies):
self.spawn_zombie()
self._next_wave()
elif self.wave >= 10 and not self.zombies:
self.state = GameState.VICTORY
def draw(self):
self.screen.fill(C_SKY_BLUE)
self.draw_grid()
all_objects = sorted(self.plants + self.zombies, key=lambda obj: obj.y)
for obj in all_objects:
obj.draw(self.screen)
for mower in self.lawnmowers:
mower.draw(self.screen)
for bullet in self.bullets:
bullet.draw(self.screen)
for sun in self.suns:
sun.draw(self.screen)
for particle in self.particles:
particle.draw(self.screen)
self.ui_manager.draw_top_panel()
self.ui_manager.draw_game_messages()
self.draw_cursor()
def draw_grid(self):
for r in range(GRID_ROWS):
for c in range(GRID_COLS):
rect = pygame.Rect(LAWN_LEFT + c * GRID_SIZE, LAWN_TOP + r * GRID_SIZE, GRID_SIZE, GRID_SIZE)
# 全是草地
tile_type = TileType.GRASS
color = (C_GRASS_LIGHT, C_GRASS_DARK)
pygame.draw.rect(self.screen, color[(r + c) % 2], rect)
pygame.draw.rect(self.screen, C_BLACK, rect, 1)
def draw_cursor(self):
pos = pygame.mouse.get_pos()
if self.selected_plant_type:
sprite = self.sprites[self.selected_plant_type.value]
self.screen.blit(sprite, sprite.get_rect(center=pos))
elif self.shovel_selected:
sprite = self.sprites["shovel"]
self.screen.blit(sprite, sprite.get_rect(center=pos))
def run(self):
while True:
self.handle_events()
if self.state == GameState.MODE_SELECT:
self.screen.fill(C_SKY_BLUE)
title_text = TITLE_FONT.render("选择游戏模式", True, C_WHITE)
self.screen.blit(title_text, (SCREEN_WIDTH // 2 - title_text.get_width() // 2, 100))
# 绘制 04 数字
number_text = TITLE_FONT.render("04", True, C_RED) # 使用红色使其更显眼
number_x = SCREEN_WIDTH // 2 - number_text.get_width() // 2
number_y = 200 # 调整垂直位置
self.screen.blit(number_text, (number_x, number_y))
for btn in self.mode_select_buttons:
btn.draw(self.screen)
elif self.state == GameState.MENU:
self.screen.fill(C_SKY_BLUE)
title_text = TITLE_FONT.render("像素植物大战僵尸", True, C_WHITE)
self.screen.blit(title_text, (SCREEN_WIDTH // 2 - title_text.get_width() // 2, 100))
# 绘制 04 数字
number_text = TITLE_FONT.render("04", True, C_RED) # 使用红色使其更显眼
number_x = SCREEN_WIDTH // 2 - number_text.get_width() // 2
number_y = 200 # 调整垂直位置
self.screen.blit(number_text, (number_x, number_y))
for btn in self.menu_buttons:
btn.draw(self.screen)
elif self.state == GameState.PLAYING:
self.update()
self.draw()
elif self.state == GameState.PAUSED:
pause_text = TITLE_FONT.render("已暂停", True, C_WHITE)
self.screen.blit(pause_text, (SCREEN_WIDTH // 2 - pause_text.get_width() // 2, SCREEN_HEIGHT // 2 - 50))
elif self.state == GameState.GAME_OVER:
go_text = TITLE_FONT.render("游戏结束", True, C_RED)
self.screen.blit(go_text, (SCREEN_WIDTH // 2 - go_text.get_width() // 2, SCREEN_HEIGHT // 2 - 100))
back_button = Button(SCREEN_WIDTH // 2 - 150, SCREEN_HEIGHT // 2 + 50, 300, 80, "返回模式选择", FONT, self.return_to_mode_select)
back_button.handle_event(pygame.event.poll())
back_button.draw(self.screen)
elif self.state == GameState.VICTORY:
victory_text = TITLE_FONT.render("游戏胜利", True, C_GREEN)
self.screen.blit(victory_text, (SCREEN_WIDTH // 2 - victory_text.get_width() // 2, SCREEN_HEIGHT // 2 - 50))
pygame.display.flip()
self.clock.tick(FPS)
def return_to_mode_select(self):
"""返回模式选择界面"""
self.state = GameState.MODE_SELECT
self.reset_game()
def spawn_zombie(self):
row = random.randint(0, GRID_ROWS - 1)
zombie_type = random.choice(list(ZombieType))
self.zombies.append(Zombie(zombie_type, row, self.sprites[zombie_type.value]))
def add_particles(self, x, y, count, color, size_range, velocity_range):
"""添加粒子效果"""
for _ in range(count):
size = random.randint(*size_range)
vx = random.randint(*velocity_range[0])
vy = random.randint(*velocity_range[1])
particle = Particle(x, y, size, color, (vx, vy))
self.particles.append(particle)
def add_sun(self, x, y, value, is_natural=False):
self.suns.append(Sun(x, y, value, self.sprites["sun"], is_natural))
def add_bullet(self, row, x, bullet_type):
self.bullets.append(Bullet(row, x, bullet_type, self.sprites[f"bullet_{bullet_type}"]))
if name == "main":
game = Game()
game.run()




浙公网安备 33010602011771号