import pygame
import random
import sys
初始化pygame
pygame.init()
游戏常量
SCREEN_WIDTH, SCREEN_HEIGHT = 800, 600
GRID_SIZE = 80
LANES = 5
COLUMNS = 9
FPS = 60
颜色定义
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)
GRAY = (128, 128, 128)
创建游戏窗口
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("植物大战僵尸")
clock = pygame.time.Clock()
加载资源
def load_image(name, size=None):
try:
image = pygame.image.load(f"assets/{name}.png").convert_alpha()
if size:
image = pygame.transform.scale(image, size)
return image
except:
# 如果找不到图片,创建一个彩色方块作为替代
surf = pygame.Surface((size or (50, 50)))
colors = {
'sunflower': YELLOW,
'peashooter': GREEN,
'peashot': (150, 255, 150),
'zombie': RED,
'sun': YELLOW,
'background': (200, 220, 200),
'card': (180, 255, 180),
'button': (255, 200, 100),
'shovel': (150, 150, 150)
}
color = colors.get(name.split('.')[0], (100, 100, 100))
surf.fill(color)
return surf
游戏资源
resources = {
'background': load_image('background', (SCREEN_WIDTH, SCREEN_HEIGHT)),
'sunflower': load_image('sunflower', (60, 60)),
'peashooter': load_image('peashooter', (60, 60)),
'peashot': load_image('peashot', (20, 20)),
'zombie': load_image('zombie', (60, 80)),
'sun': load_image('sun', (40, 40)),
'card_sunflower': load_image('card', (80, 100)),
'card_peashooter': load_image('card', (80, 100)),
'shovel': load_image('shovel', (40, 40)),
'button': load_image('button', (120, 40))
}
游戏字体
pygame.font.init()
font = pygame.font.SysFont("SimHei", 24)
large_font = pygame.font.SysFont("SimHei", 36)
游戏类
class GameObject:
def init(self, x, y, image):
self.x = x
self.y = y
self.image = image
self.width = image.get_width()
self.height = image.get_height()
self.rect = pygame.Rect(x, y, self.width, self.height)
def update_rect(self):
self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
def draw(self, surface):
surface.blit(self.image, (self.x, self.y))
class Plant(GameObject):
def init(self, x, y, image, health, cost):
super().init(x, y, image)
self.health = health
self.cost = cost
self.attack_cooldown = 0
self.sun_production_cooldown = 0
def update(self):
if self.attack_cooldown > 0:
self.attack_cooldown -= 1
if self.sun_production_cooldown > 0:
self.sun_production_cooldown -= 1
class Sunflower(Plant):
def init(self, x, y):
super().init(x, y, resources['sunflower'], 100, 50)
self.sun_production_cooldown = 300
def update(self):
super().update()
if self.sun_production_cooldown == 0:
self.sun_production_cooldown = 300
return Sun(self.x, self.y)
return None
class Peashooter(Plant):
def init(self, x, y):
super().init(x, y, resources['peashooter'], 100, 100)
self.attack_cooldown = 60
def update(self):
super().update()
if self.attack_cooldown == 0:
self.attack_cooldown = 60
return Pea(self.x + 40, self.y + 20)
return None
class Sun(GameObject):
def init(self, x, y):
super().init(x, y, resources['sun'])
self.value = 25
self.target_y = y + random.randint(20, 80)
self.fall_speed = 1
self.collected = False
self.collect_time = 0
def update(self):
if self.y < self.target_y and not self.collected:
self.y += self.fall_speed
self.update_rect()
def collect(self):
self.collected = True
self.collect_time = 30
def is_collected(self):
if self.collected:
self.collect_time -= 1
if self.collect_time <= 0:
return True
return False
class Pea(GameObject):
def init(self, x, y):
super().init(x, y, resources['peashot'])
self.damage = 20
self.speed = 5
def update(self):
self.x += self.speed
self.update_rect()
return self.x > SCREEN_WIDTH
class Zombie(GameObject):
def init(self, lane):
x = SCREEN_WIDTH
y = 100 + lane * GRID_SIZE
super().init(x, y, resources['zombie'])
self.health = 200
self.speed = 0.5
self.lane = lane
self.eating = False
def update(self, plants):
self.eating = False
for plant in plants:
if plant.y // GRID_SIZE == self.lane and self.x < plant.x + plant.width and self.x + self.width > plant.x:
self.eating = True
plant.health -= 1
return
if not self.eating:
self.x -= self.speed
self.update_rect()
def is_dead(self):
return self.health <= 0
class Card(GameObject):
def init(self, x, y, plant_type, cost, recharge_time):
super().init(x, y, resources[f'card_{plant_type}'])
self.plant_type = plant_type
self.cost = cost
self.recharge_time = recharge_time
self.current_recharge = 0
self.available = True
def update(self):
if self.current_recharge > 0:
self.current_recharge -= 1
if self.current_recharge == 0:
self.available = True
def use(self):
if self.available:
self.available = False
self.current_recharge = self.recharge_time
return True
return False
def draw(self, surface):
super().draw(surface)
# 绘制卡片上的文字
cost_text = font.render(f"{self.cost}", True, BLACK)
surface.blit(cost_text, (self.x + 10, self.y + 10))
# 如果卡片在冷却中,绘制冷却覆盖
if not self.available:
recharge_percent = 1 - (self.current_recharge / self.recharge_time)
recharge_height = int(self.height * recharge_percent)
pygame.draw.rect(surface, (100, 100, 100, 150),
(self.x, self.y + self.height - recharge_height,
self.width, recharge_height))
class Shovel(GameObject):
def init(self, x, y):
super().init(x, y, resources['shovel'])
self.selected = False
def draw(self, surface):
super().draw(surface)
if self.selected:
pygame.draw.rect(surface, GREEN, (self.x, self.y, self.width, self.height), 2)
游戏状态类
class GameState:
def init(self):
self.sun = 150
self.plants = []
self.zombies = []
self.sun_objects = []
self.peas = []
self.cards = [
Card(50, 20, 'sunflower', 50, 100),
Card(150, 20, 'peashooter', 100, 150)
]
self.shovel = Shovel(250, 30)
self.selected_card = None
self.selected_shovel = False
self.score = 0
self.wave = 1
self.zombie_timer = 100
self.game_over = False
self.victory = False
self.plant_grid = [[None for _ in range(COLUMNS)] for _ in range(LANES)]
def update(self):
if self.game_over:
return
# 生成阳光
if random.randint(1, 300) == 1:
lane = random.randint(0, LANES - 1)
x = random.randint(100, SCREEN_WIDTH - 100)
self.sun_objects.append(Sun(x, 50))
# 更新植物
new_plants = []
for plant in self.plants:
plant.update()
# 处理植物产生阳光或豌豆
if isinstance(plant, Sunflower):
sun = plant.update()
if sun:
self.sun_objects.append(sun)
elif isinstance(plant, Peashooter):
pea = plant.update()
if pea:
self.peas.append(pea)
# 检查植物是否死亡
if plant.health > 0:
new_plants.append(plant)
else:
# 植物死亡,从网格中移除
grid_x = plant.x // GRID_SIZE
grid_y = plant.y // GRID_SIZE - 1 # 减去顶部栏
if 0 <= grid_y < LANES and 0 <= grid_x < COLUMNS:
self.plant_grid[grid_y][grid_x] = None
self.plants = new_plants
# 更新阳光
for sun in self.sun_objects:
sun.update()
self.sun_objects = [sun for sun in self.sun_objects if not sun.is_collected()]
# 更新豌豆
new_peas = []
for pea in self.peas:
if not pea.update():
new_peas.append(pea)
self.peas = new_peas
# 生成僵尸
self.zombie_timer -= 1
if self.zombie_timer <= 0:
lane = random.randint(0, LANES - 1)
self.zombies.append(Zombie(lane))
self.zombie_timer = random.randint(80, 150)
# 更新僵尸
new_zombies = []
for zombie in self.zombies:
zombie.update(self.plants)
# 检查僵尸是否到达最左边
if zombie.x < 50:
self.game_over = True
# 检查僵尸是否死亡
if not zombie.is_dead():
new_zombies.append(zombie)
else:
self.score += 100
self.zombies = new_zombies
# 检查胜利条件
if self.score >= 1000 and not self.zombies:
self.victory = True
# 更新卡片
for card in self.cards:
card.update()
# 检查豌豆与僵尸的碰撞
for pea in self.peas[:]:
hit = False
for zombie in self.zombies:
if pea.rect.colliderect(zombie.rect):
zombie.health -= pea.damage
self.peas.remove(pea)
hit = True
break
if hit:
break
def draw(self, surface):
# 绘制背景
surface.blit(resources['background'], (0, 0))
# 绘制网格线
for i in range(1, LANES):
pygame.draw.line(surface, GRAY, (0, 100 + i * GRID_SIZE), (SCREEN_WIDTH, 100 + i * GRID_SIZE), 2)
for i in range(1, COLUMNS):
pygame.draw.line(surface, GRAY, (i * GRID_SIZE, 100), (i * GRID_SIZE, SCREEN_HEIGHT), 2)
# 绘制阳光
for sun in self.sun_objects:
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)
# 绘制卡片
for card in self.cards:
card.draw(surface)
# 绘制铲子
self.shovel.draw(surface)
# 绘制阳光数量
sun_text = font.render(f"阳光: {self.sun}", True, BLACK)
surface.blit(sun_text, (400, 30))
# 绘制分数
score_text = font.render(f"分数: {self.score}", True, BLACK)
surface.blit(score_text, (550, 30))
# 绘制波次
wave_text = font.render(f"波次: {self.wave}", True, BLACK)
surface.blit(wave_text, (700, 30))
# 如果选择了卡片,绘制预览
if self.selected_card is not None:
mouse_x, mouse_y = pygame.mouse.get_pos()
grid_x = (mouse_x // GRID_SIZE) * GRID_SIZE
grid_y = ((mouse_y - 100) // GRID_SIZE) * GRID_SIZE + 100
if 100 <= grid_y < 100 + LANES * GRID_SIZE:
plant_type = self.selected_card.plant_type
if plant_type == 'sunflower':
preview = resources['sunflower']
elif plant_type == 'peashooter':
preview = resources['peashooter']
# 半透明预览
preview.set_alpha(150)
surface.blit(preview, (grid_x + 10, grid_y + 10))
# 如果选择了铲子,绘制预览
if self.selected_shovel:
mouse_x, mouse_y = pygame.mouse.get_pos()
surface.blit(resources['shovel'], (mouse_x - 20, mouse_y - 20))
# 游戏结束画面
if self.game_over:
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
overlay.fill(BLACK)
overlay.set_alpha(150)
surface.blit(overlay, (0, 0))
game_over_text = large_font.render("游戏结束", True, WHITE)
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, SCREEN_HEIGHT//2 - 50))
surface.blit(final_score_text, (SCREEN_WIDTH//2 - final_score_text.get_width()//2, SCREEN_HEIGHT//2))
surface.blit(restart_text, (SCREEN_WIDTH//2 - restart_text.get_width()//2, SCREEN_HEIGHT//2 + 50))
# 胜利画面
if self.victory:
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
overlay.fill(YELLOW)
overlay.set_alpha(100)
surface.blit(overlay, (0, 0))
victory_text = large_font.render("胜利!", True, GREEN)
final_score_text = font.render(f"最终分数: {self.score}", True, BLACK)
next_wave_text = font.render("按 N 键进入下一波", True, BLACK)
surface.blit(victory_text, (SCREEN_WIDTH//2 - victory_text.get_width()//2, SCREEN_HEIGHT//2 - 50))
surface.blit(final_score_text, (SCREEN_WIDTH//2 - final_score_text.get_width()//2, SCREEN_HEIGHT//2))
surface.blit(next_wave_text, (SCREEN_WIDTH//2 - next_wave_text.get_width()//2, SCREEN_HEIGHT//2 + 50))
def handle_click(self, pos):
if self.game_over or self.victory:
return
x, y = pos
# 检查是否点击了卡片
for card in self.cards:
if card.rect.collidepoint(pos) and card.available and self.sun >= card.cost:
self.selected_card = card
self.selected_shovel = False
return
# 检查是否点击了铲子
if self.shovel.rect.collidepoint(pos):
self.selected_shovel = not self.selected_shovel
self.selected_card = None
return
# 检查是否点击了阳光
for sun in self.sun_objects[:]:
if sun.rect.collidepoint(pos) and not sun.collected:
sun.collect()
self.sun += sun.value
return
# 如果选择了卡片,尝试种植植物
if self.selected_card is not None:
grid_x = x // GRID_SIZE
grid_y = (y - 100) // GRID_SIZE
if 0 <= grid_y < LANES and 0 <= grid_x < COLUMNS and self.plant_grid[grid_y][grid_x] is None:
plant_type = self.selected_card.plant_type
plant_x = grid_x * GRID_SIZE + 10
plant_y = grid_y * GRID_SIZE + 110
if plant_type == 'sunflower' and self.sun >= 50:
self.plants.append(Sunflower(plant_x, plant_y))
self.plant_grid[grid_y][grid_x] = 'sunflower'
self.sun -= 50
self.selected_card.use()
elif plant_type == 'peashooter' and self.sun >= 100:
self.plants.append(Peashooter(plant_x, plant_y))
self.plant_grid[grid_y][grid_x] = 'peashooter'
self.sun -= 100
self.selected_card.use()
self.selected_card = None
# 如果选择了铲子,尝试移除植物
elif self.selected_shovel:
grid_x = x // GRID_SIZE
grid_y = (y - 100) // GRID_SIZE
if 0 <= grid_y < LANES and 0 <= grid_x < COLUMNS and self.plant_grid[grid_y][grid_x] is not None:
# 找到要移除的植物
for i, plant in enumerate(self.plants):
if plant.x // GRID_SIZE == grid_x and (plant.y - 100) // GRID_SIZE == grid_y:
self.plants.pop(i)
self.plant_grid[grid_y][grid_x] = None
break
self.selected_shovel = False
def restart(self):
self.__init__()
主游戏循环
def main():
game_state = GameState()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # 左键点击
game_state.handle_click(pygame.mouse.get_pos())
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r and game_state.game_over:
game_state.restart()
if event.key == pygame.K_n and game_state.victory:
game_state.victory = False
game_state.wave += 1
game_state.zombie_timer = 50
game_state.update()
game_state.draw(screen)
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
sys.exit()
if name == "main":
main()#3002

浙公网安备 33010602011771号