游戏核心概念
这是一个基于经典《植物大战僵尸》的Python复刻版,使用Pygame库实现。游戏保留了原版的核心玩法:玩家在草坪上种植植物抵御僵尸进攻,保护家园。
游戏架构设计
1. 游戏资源系统
网络图片加载:通过load_network_image函数从网络加载图片资源
占位符生成:当网络加载失败时,自动生成彩色占位符
资源类型:
植物:向日葵、豌豆射手、寒冰射手、坚果墙、樱桃炸弹
僵尸:普通、锥头、铁桶、橄榄球、舞王僵尸
其他:阳光、子弹、爆炸效果
2. 游戏核心类
Game类:管理游戏状态、资源、对象和逻辑
Plant类:植物基类,包含种植、攻击、生产阳光等功能
Zombie类:僵尸基类,包含移动、攻击等行为
Bullet类:子弹对象,包括豌豆和雪球
Sun类:阳光资源收集对象
Explosion类:爆炸特效
3. 游戏玩法设计
植物种植系统:
向日葵:生产阳光资源
豌豆射手:发射豌豆攻击
寒冰射手:发射冰冻豌豆减缓僵尸
坚果墙:高生命值防御植物
樱桃炸弹:范围爆炸伤害
僵尸类型系统:
普通僵尸:基础僵尸
锥头僵尸:中等生命值
铁桶僵尸:高生命值
橄榄球僵尸:快速移动,会冲刺
舞王僵尸:召唤伴舞僵尸
资源与经济:
阳光作为游戏货币
不同植物有不同成本
自动收集阳光功能
4. 游戏视觉效果
草坪网格:5行9列的种植区域
动画效果:
植物浮动动画
僵尸行走动画
子弹飞行效果
爆炸特效
状态显示:
阳光计数
分数统计
波数进度
僵尸击杀数
5. 游戏流程
玩家选择植物
在草坪网格上种植
收集阳光资源
抵御不断来袭的僵尸
完成12波僵尸攻击获胜
技术亮点
网络资源加载:
使用requests和PIL库实现网络图片加载
优雅的失败处理机制
僵尸AI系统:
不同类型僵尸有独特行为模式
权重系统控制僵尸生成概率
波次难度递增机制
特效系统:
冰冻效果可视化
僵尸死亡特效
樱桃炸弹爆炸范围
音频系统:
多种游戏音效
异常处理机制
UI设计:
清晰的植物选择栏
游戏状态显示面板
游戏结束/胜利提示
扩展可能性
新增植物:
双发射手
西瓜投手
玉米加农炮
新增僵尸:
气球僵尸
矿工僵尸
雪橇僵尸小队
游戏模式:
无尽模式
生存模式
小游戏模式
其他功能:
植物升级系统
成就系统
关卡编辑器
源代码:
import pygame
import random
import sys
import os
import math
import requests
from io import BytesIO
from pygame.locals import *
from PIL import Image
# 初始化pygame
pygame.init()
pygame.mixer.init()
# 游戏常量
SCREEN_WIDTH = 900
SCREEN_HEIGHT = 600
GRID_SIZE = 80
GRID_COLS = 9
GRID_ROWS = 5
LAWN_TOP = 120
LAWN_LEFT = 50
FPS = 60
# 颜色定义
GREEN = (34, 139, 34)
LIGHT_GREEN = (144, 238, 144)
BROWN = (139, 69, 19)
YELLOW = (255, 255, 0)
RED = (255, 0, 0)
BLUE = (30, 144, 255)
SKY_BLUE = (135, 206, 235)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
PURPLE = (128, 0, 128)
DARK_GREEN = (0, 100, 0)
# 创建游戏窗口
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption(“植物大战僵尸 – 完善僵尸模型版”)
clock = pygame.time.Clock()
# 字体
font = pygame.font.SysFont('simhei', 24)
big_font = pygame.font.SysFont('simhei', 36)
title_font = pygame.font.SysFont('simhei', 48, bold=True)
# 自动联网加载图像资源
def load_network_image(url):
try:
response = requests.get(url)
img = Image.open(BytesIO(response.content))
return pygame.image.fromstring(img.tobytes(), img.size, img.mode).convert_alpha()
except:
# 如果网络加载失败,返回一个空白表面
return pygame.Surface((GRID_SIZE, GRID_SIZE), pygame.SRCALPHA)
# 加载游戏资源
def load_resources():
resources = {}
# 植物图像
resources['sunflower'] = load_network_image(“https://i.imgur.com/3L1v2b3.png”)
resources['peashooter'] = load_network_image(“https://i.imgur.com/5a1B4lN.png”)
resources['wallnut'] = load_network_image(“https://i.imgur.com/9uT1b4g.png”)
resources['cherrybomb'] = load_network_image(“https://i.imgur.com/7J1f3D2.png”)
resources['snowpea'] = load_network_image(“https://i.imgur.com/8Hk1Y5j.png”)
# 僵尸图像 – 使用picsum提供的示例图片
resources['zombie'] = load_network_image(“https://picsum.photos/seed/zombie1/80/100”) # 普通僵尸
resources['conehead'] = load_network_image(“https://picsum.photos/seed/zombie2/80/100”) # 锥头僵尸
resources['buckethead'] = load_network_image(“https://picsum.photos/seed/zombie3/80/100”) # 铁桶僵尸
resources['football'] = load_network_image(“https://picsum.photos/seed/zombie4/80/100”) # 橄榄球僵尸
resources['dancer'] = load_network_image(“https://picsum.photos/seed/zombie5/80/100”) # 舞王僵尸
# 其他元素
resources['sun'] = load_network_image(“https://i.imgur.com/2Kb1C3d.png”)
resources['pea'] = load_network_image(“https://i.imgur.com/4J7b1C8.png”)
resources['snowball'] = load_network_image(“https://i.imgur.com/1N4b1C3.png”)
resources['explosion'] = load_network_image(“https://i.imgur.com/6K9b1C9.png”)
resources['lawn'] = load_network_image(“https://i.imgur.com/7L2b1C1.png”)
# 背景
resources['background'] = load_network_image(“https://picsum.photos/seed/background/900/600”)
# 按钮
resources['collect_btn'] = load_network_image(“https://i.imgur.com/1N4b1C3.png”)
# 如果加载失败,创建占位符
for key in resources:
if resources[key].get_width() == 1: # 检查是否空白表面
placeholder = pygame.Surface((GRID_SIZE, GRID_SIZE), pygame.SRCALPHA)
# 根据类型创建不同的占位符
if “zombie” in key:
if “conehead” in key:
# 锥头僵尸占位符
placeholder.fill((100, 100, 150, 255))
pygame.draw.rect(placeholder, (150, 150, 100), (20, 10, 40, 20)) # 锥头
elif “buckethead” in key:
# 铁桶僵尸占位符
placeholder.fill((80, 80, 120, 255))
pygame.draw.rect(placeholder, (150, 150, 150), (15, 5, 50, 30)) # 铁桶
elif “football” in key:
# 橄榄球僵尸占位符
placeholder.fill((70, 100, 120, 255))
pygame.draw.ellipse(placeholder, (200, 0, 0), (10, 15, 60, 30)) # 橄榄球头盔
elif “dancer” in key:
# 舞王僵尸占位符
placeholder.fill((120, 80, 100, 255))
pygame.draw.rect(placeholder, (200, 100, 100), (30, 5, 20, 40)) # 皇冠
else:
# 普通僵尸占位符
placeholder.fill((100, 100, 150, 255))
elif “plant” in key or key in ['sunflower', 'peashooter', 'snowpea', 'wallnut', 'cherrybomb']:
# 植物占位符
colors = {
'sunflower': (255, 200, 0),
'peashooter': (0, 150, 0),
'snowpea': (100, 200, 255),
'wallnut': (150, 75, 0),
'cherrybomb': (255, 0, 0)
}
color = colors.get(key, (50, 200, 50))
placeholder.fill((*color, 255))
else:
# 其他元素占位符
placeholder.fill((random.randint(50, 200), random.randint(50, 200), random.randint(50, 200), 255))
resources[key] = placeholder
return resources
# 游戏类
class Game:
def __init__(self, resources):
self.resources = resources
self.sun_count = 200
self.plants = []
self.zombies = []
self.bullets = []
self.sun_list = []
self.selected_plant = None
self.score = 0
self.wave = 1
self.zombie_spawn_timer = 0
self.sun_spawn_timer = 0
self.game_over = False
self.game_won = False
self.zombies_killed = 0
self.auto_collect = False
self.auto_collect_timer = 0
self.auto_collect_cost = 100
self.last_zombie_spawn = 0
self.special_zombie_chance = 0.1
self.background = pygame.transform.scale(self.resources['background'], (SCREEN_WIDTH, SCREEN_HEIGHT))
self.collect_btn = pygame.transform.scale(self.resources['collect_btn'], (80, 80))
self.collect_btn_rect = self.collect_btn.get_rect(topleft=(SCREEN_WIDTH – 100, 20))
self.sound_enabled = True
self.explosions = []
self.zombie_types = [“normal”, “conehead”, “buckethead”, “football”, “dancer”]
self.zombie_spawn_weights = [50, 20, 15, 10, 5] # 僵尸类型生成权重
# 加载音效
try:
self.plant_sound = pygame.mixer.Sound(os.path.join('sounds', 'plant.wav'))
self.sun_sound = pygame.mixer.Sound(os.path.join('sounds', 'sun.wav'))
self.shoot_sound = pygame.mixer.Sound(os.path.join('sounds', 'shoot.wav'))
self.snow_sound = pygame.mixer.Sound(os.path.join('sounds', 'snow.wav'))
self.explosion_sound = pygame.mixer.Sound(os.path.join('sounds', 'explosion.wav'))
self.zombie_sound = pygame.mixer.Sound(os.path.join('sounds', 'zombie.wav'))
self.zombie_die_sound = pygame.mixer.Sound(os.path.join('sounds', 'zombie_die.wav'))
except:
# 如果音效文件不存在,创建空的音效对象
self.plant_sound = pygame.mixer.Sound(buffer=bytearray())
self.sun_sound = pygame.mixer.Sound(buffer=bytearray())
self.shoot_sound = pygame.mixer.Sound(buffer=bytearray())
self.snow_sound = pygame.mixer.Sound(buffer=bytearray())
self.explosion_sound = pygame.mixer.Sound(buffer=bytearray())
self.zombie_sound = pygame.mixer.Sound(buffer=bytearray())
self.zombie_die_sound = pygame.mixer.Sound(buffer=bytearray())
def draw(self):
# 绘制背景
screen.blit(self.background, (0, 0))
# 绘制草坪网格
for row in range(GRID_ROWS):
for col in range(GRID_COLS):
rect = pygame.Rect(LAWN_LEFT + col * GRID_SIZE, LAWN_TOP + row * GRID_SIZE,
GRID_SIZE, GRID_SIZE)
pygame.draw.rect(screen, GREEN, rect)
pygame.draw.rect(screen, LIGHT_GREEN, rect, 1)
# 绘制草地纹理
for i in range(5):
x = rect.x + random.randint(5, GRID_SIZE-5)
y = rect.y + random.randint(5, GRID_SIZE-5)
pygame.draw.circle(screen, DARK_GREEN, (x, y), 2)
# 绘制植物
for plant in self.plants:
plant.draw(self.resources)
# 绘制僵尸
for zombie in self.zombies:
zombie.draw(self.resources)
# 绘制子弹
for bullet in self.bullets:
bullet.draw(self.resources)
# 绘制阳光
for sun in self.sun_list:
sun.draw(self.resources)
# 绘制爆炸效果
for explosion in self.explosions:
explosion.draw(self.resources)
# 绘制植物选择栏
pygame.draw.rect(screen, (0, 0, 0, 180), (0, 0, SCREEN_WIDTH, LAWN_TOP))
pygame.draw.rect(screen, BROWN, (0, 0, SCREEN_WIDTH, LAWN_TOP), 3)
plant_types = [
{“name”: “向日葵”, “cost”: 50, “type”: “sunflower”},
{“name”: “豌豆射手”, “cost”: 100, “type”: “peashooter”},
{“name”: “寒冰射手”, “cost”: 150, “type”: “snowpea”},
{“name”: “坚果墙”, “cost”: 50, “type”: “wallnut”},
{“name”: “樱桃炸弹”, “cost”: 150, “type”: “cherrybomb”},
]
for i, plant in enumerate(plant_types):
rect = pygame.Rect(20 + i * 150, 20, 120, 90)
pygame.draw.rect(screen, (50, 50, 50, 180), rect)
pygame.draw.rect(screen, (200, 200, 100), rect, 2)
# 绘制植物缩略图
plant_img = pygame.transform.scale(self.resources[plant['type']], (50, 50))
screen.blit(plant_img, (rect.centerx – 25, rect.top + 10))
cost_text = font.render(f”{plant['cost']} 阳光”, True, YELLOW)
name_text = font.render(plant[“name”], True, WHITE)
screen.blit(cost_text, (rect.centerx – cost_text.get_width()//2, rect.bottom – 30))
screen.blit(name_text, (rect.centerx – name_text.get_width()//2, rect.bottom – 50))
# 高亮显示选中的植物
if self.selected_plant == i:
pygame.draw.rect(screen, RED, rect, 4)
# 绘制阳光计数
pygame.draw.rect(screen, (0, 0, 0, 180), (SCREEN_WIDTH – 200, 10, 190, 40))
pygame.draw.rect(screen, YELLOW, (SCREEN_WIDTH – 200, 10, 190, 40), 2)
sun_text = font.render(f”阳光: {self.sun_count}”, True, YELLOW)
screen.blit(sun_text, (SCREEN_WIDTH – 180, 20))
# 绘制自动收集按钮
screen.blit(self.collect_btn, (SCREEN_WIDTH – 100, 20))
auto_text = font.render(“自动收集”, True, WHITE)
screen.blit(auto_text, (SCREEN_WIDTH – 90, 75))
# 绘制分数和波数
pygame.draw.rect(screen, (0, 0, 0, 180), (SCREEN_WIDTH – 200, 60, 190, 90))
pygame.draw.rect(screen, BLUE, (SCREEN_WIDTH – 200, 60, 190, 90), 2)
score_text = font.render(f”分数: {self.score}”, True, WHITE)
wave_text = font.render(f”波数: {self.wave}/12″, True, WHITE)
kills_text = font.render(f”击杀: {self.zombies_killed}”, True, WHITE)
screen.blit(score_text, (SCREEN_WIDTH – 180, 70))
screen.blit(wave_text, (SCREEN_WIDTH – 180, 100))
screen.blit(kills_text, (SCREEN_WIDTH – 180, 130))
# 绘制标题
title = title_font.render(“植物大战僵尸 – 完善僵尸模型版”, True, (10, 200, 10))
screen.blit(title, (SCREEN_WIDTH // 2 – title.get_width() // 2, 5))
# 自动收集状态
if self.auto_collect:
auto_status = font.render(“自动收集中…”, True, YELLOW)
screen.blit(auto_status, (SCREEN_WIDTH – 180, 160))
# 游戏结束或胜利提示
if self.game_over:
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 150))
screen.blit(overlay, (0, 0))
game_over_text = big_font.render(“游戏结束! 僵尸吃掉了你的脑子!”, True, RED)
screen.blit(game_over_text, (SCREEN_WIDTH//2 – game_over_text.get_width()//2,
SCREEN_HEIGHT//2 – 50))
restart_text = font.render(“按R键重新开始”, True, WHITE)
screen.blit(restart_text, (SCREEN_WIDTH//2 – restart_text.get_width()//2,
SCREEN_HEIGHT//2 + 10))
if self.game_won:
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 150))
screen.blit(overlay, (0, 0))
win_text = big_font.render(“恭喜! 你成功抵御了僵尸进攻!”, True, YELLOW)
screen.blit(win_text, (SCREEN_WIDTH//2 – win_text.get_width()//2,
SCREEN_HEIGHT//2 – 50))
restart_text = font.render(“按R键重新开始”, True, WHITE)
screen.blit(restart_text, (SCREEN_WIDTH//2 – restart_text.get_width()//2,
SCREEN_HEIGHT//2 + 10))
def update(self):
if self.game_over or self.game_won:
return
# 生成阳光
self.sun_spawn_timer += 1
if self.sun_spawn_timer > 200: # 每200帧生成一个阳光
self.sun_list.append(Sun())
self.sun_spawn_timer = 0
# 生成僵尸
self.zombie_spawn_timer += 1
spawn_interval = max(40, 200 – self.wave * 12) # 波数越高,僵尸生成越快
if self.zombie_spawn_timer > spawn_interval:
row = random.randint(0, GRID_ROWS – 1)
# 根据权重随机选择僵尸类型
zombie_type = random.choices(self.zombie_types, weights=self.zombie_spawn_weights, k=1)[0]
# 确保不会在早期生成太强的僵尸
if self.wave < 3 and zombie_type in [“buckethead”, “football”, “dancer”]:
zombie_type = random.choice([“normal”, “conehead”])
# 创建僵尸实例
if zombie_type == “normal”:
self.zombies.append(NormalZombie(row))
elif zombie_type == “conehead”:
self.zombies.append(ConeheadZombie(row))
elif zombie_type == “buckethead”:
self.zombies.append(BucketheadZombie(row))
elif zombie_type == “football”:
self.zombies.append(FootballZombie(row))
elif zombie_type == “dancer”:
self.zombies.append(DancerZombie(row))
self.zombie_spawn_timer = 0
self.last_zombie_spawn = pygame.time.get_ticks()
# 调整僵尸生成权重(每波降低普通僵尸权重,提高特殊僵尸权重)
if self.zombie_spawn_weights[0] > 10:
self.zombie_spawn_weights[0] -= 2
if self.zombie_spawn_weights[1] < 30:
self.zombie_spawn_weights[1] += 1
if self.zombie_spawn_weights[2] < 25:
self.zombie_spawn_weights[2] += 1
if self.zombie_spawn_weights[3] < 20 and self.wave > 5:
self.zombie_spawn_weights[3] += 1
if self.zombie_spawn_weights[4] < 15 and self.wave > 8:
self.zombie_spawn_weights[4] += 1
# 更新植物
for plant in self.plants:
plant.update(self)
# 检查植物是否被吃掉
if plant.health <= 0:
self.plants.remove(plant)
# 更新僵尸
for zombie in self.zombies:
zombie.update(self)
# 检查僵尸是否到达左边界
if zombie.x < LAWN_LEFT:
self.game_over = True
# 检查僵尸是否死亡
if zombie.health <= 0:
self.zombies.remove(zombie)
self.score += zombie.points
self.zombies_killed += 1
# 创建死亡效果
if zombie.type != “dancer”: # 舞王僵尸死亡时产生伴舞
self.explosions.append(Explosion(zombie.x, zombie.y, “zombie”))
# 播放死亡音效
if self.sound_enabled:
self.zombie_die_sound.play()
# 每杀死8个僵尸进入下一波
if self.zombies_killed % 8 == 0:
self.wave += 1
self.sun_count += 200 # 奖励阳光
# 更新子弹
for bullet in self.bullets:
bullet.update()
# 检查子弹是否击中僵尸
for zombie in self.zombies:
if (zombie.row == bullet.row and
abs(bullet.x – zombie.x) < 50 and
abs(bullet.y – zombie.y) < 50):
zombie.health -= bullet.damage
# 冰冻效果
if bullet.freeze_power > 0:
zombie.speed = max(0.1, zombie.speed * (1 – bullet.freeze_power))
zombie.frozen_timer = 180 # 3秒冰冻效果
if bullet in self.bullets:
self.bullets.remove(bullet)
break
# 移除超出屏幕的子弹
if bullet.x > SCREEN_WIDTH:
self.bullets.remove(bullet)
# 更新阳光
for sun in self.sun_list:
sun.update()
# 移除超时的阳光
if sun.lifetime <= 0:
self.sun_list.remove(sun)
# 更新爆炸效果
for explosion in self.explosions:
explosion.update()
if explosion.lifetime <= 0:
self.explosions.remove(explosion)
# 自动收集阳光
if self.auto_collect:
self.auto_collect_timer += 1
if self.auto_collect_timer > 30: # 每半秒收集一次
self.collect_all_sun()
self.auto_collect_timer = 0
# 检查胜利条件
if self.wave > 12:
self.game_won = True
def add_plant(self, row, col):
if self.selected_plant is None:
return False
# 检查位置是否已被占用
for plant in self.plants:
if plant.row == row and plant.col == col:
return False
plant_types = [“sunflower”, “peashooter”, “snowpea”, “wallnut”, “cherrybomb”]
plant_type = plant_types[self.selected_plant]
# 植物成本
costs = {
“sunflower”: 50,
“peashooter”: 100,
“snowpea”: 150,
“wallnut”: 50,
“cherrybomb”: 150
}
cost = costs.get(plant_type, 100)
if self.sun_count >= cost:
self.plants.append(Plant(row, col, plant_type))
self.sun_count -= cost
if self.sound_enabled:
self.plant_sound.play()
return True
return False
def collect_sun(self, x, y):
for sun in self.sun_list:
if sun.rect.collidepoint(x, y):
self.sun_list.remove(sun)
self.sun_count += 25
if self.sound_enabled:
self.sun_sound.play()
return True
return False
def collect_all_sun(self):
suns_collected = len(self.sun_list)
self.sun_count += suns_collected * 25
self.sun_list = []
if self.sound_enabled and suns_collected > 0:
self.sun_sound.play()
def toggle_auto_collect(self):
if self.sun_count >= self.auto_collect_cost:
self.auto_collect = not self.auto_collect
self.sun_count -= self.auto_collect_cost
self.auto_collect_cost = max(50, self.auto_collect_cost – 10) # 每次使用后成本降低
def reset(self):
# 重置所有游戏状态
self.sun_count = 200
self.plants = []
self.zombies = []
self.bullets = []
self.sun_list = []
self.selected_plant = None
self.score = 0
self.wave = 1
self.zombie_spawn_timer = 0
self.sun_spawn_timer = 0
self.game_over = False
self.game_won = False
self.zombies_killed = 0
self.auto_collect = False
self.auto_collect_timer = 0
self.zombie_spawn_weights = [50, 20, 15, 10, 5] # 重置生成权重
# 植物类
class Plant:
def __init__(self, row, col, plant_type):
self.row = row
self.col = col
self.type = plant_type
self.x = LAWN_LEFT + col * GRID_SIZE + GRID_SIZE // 2
self.y = LAWN_TOP + row * GRID_SIZE + GRID_SIZE // 2
self.shoot_timer = 0
self.sun_timer = 0
self.explode_timer = 0
self.health = 100
self.ready_to_explode = False
self.animation_timer = 0
self.animation_frame = 0
# 不同类型植物的属性
if plant_type == “sunflower”:
self.color = YELLOW
self.radius = 25
self.max_health = 100
elif plant_type == “peashooter”:
self.color = GREEN
self.radius = 20
self.max_health = 100
elif plant_type == “snowpea”:
self.color = (100, 200, 255)
self.radius = 20
self.max_health = 100
elif plant_type == “wallnut”:
self.color = BROWN
self.radius = 30
self.max_health = 400 # 坚果墙有更多生命值
elif plant_type == “cherrybomb”:
self.color = RED
self.radius = 30
self.max_health = 100
self.explode_timer = 180 # 3秒后爆炸
def draw(self, resources):
# 植物动画效果
self.animation_timer += 1
if self.animation_timer > 10:
self.animation_frame = (self.animation_frame + 1) % 4
self.animation_timer = 0
# 使用资源图片绘制植物
plant_img = resources.get(self.type, None)
if plant_img:
# 添加轻微的浮动效果
float_offset = math.sin(pygame.time.get_ticks() / 200) * 3
scaled_img = pygame.transform.scale(plant_img, (int(plant_img.get_width() * (1.0 + self.animation_frame*0.02)),
int(plant_img.get_height() * (1.0 + self.animation_frame*0.02))))
img_rect = scaled_img.get_rect(center=(self.x, self.y + float_offset))
screen.blit(scaled_img, img_rect)
else:
# 回退到绘图
pygame.draw.circle(screen, self.color, (self.x, self.y), self.radius)
pygame.draw.circle(screen, BLACK, (self.x, self.y), self.radius, 2)
# 绘制植物表情
if self.type == “sunflower”:
pygame.draw.circle(screen, (200, 150, 0), (self.x, self.y), 10)
pygame.draw.circle(screen, BLACK, (self.x, self.y), 10, 1)
elif self.type == “peashooter” or self.type == “snowpea”:
pygame.draw.circle(screen, (0, 100, 0), (self.x – 5, self.y), 8)
pygame.draw.circle(screen, (0, 100, 0), (self.x + 5, self.y), 8)
pygame.draw.circle(screen, BLACK, (self.x – 5, self.y), 3)
pygame.draw.circle(screen, BLACK, (self.x + 5, self.y), 3)
elif self.type == “wallnut”:
pygame.draw.circle(screen, (150, 75, 0), (self.x, self.y), 8)
pygame.draw.circle(screen, (150, 75, 0), (self.x – 10, self.y – 5), 8)
pygame.draw.circle(screen, (150, 75, 0), (self.x + 10, self.y – 5), 8)
elif self.type == “cherrybomb”:
pygame.draw.circle(screen, (255, 0, 0), (self.x, self.y), 15)
pygame.draw.circle(screen, (200, 0, 0), (self.x, self.y), 10)
# 绘制生命条
health_percent = self.health / self.max_health
bar_width = 40
pygame.draw.rect(screen, RED, (self.x – bar_width//2, self.y – 40, bar_width, 5))
pygame.draw.rect(screen, GREEN, (self.x – bar_width//2, self.y – 40, bar_width * health_percent, 5))
# 樱桃炸弹倒计时
if self.type == “cherrybomb” and self.explode_timer > 0:
countdown = font.render(str(self.explode_timer // 60 + 1), True, RED)
screen.blit(countdown, (self.x – countdown.get_width()//2, self.y – 60))
def update(self, game):
if self.type == “peashooter” or self.type == “snowpea”:
self.shoot_timer += 1
shoot_interval = 40 if self.type == “peashooter” else 50
if self.shoot_timer > shoot_interval:
# 检查该行是否有僵尸
for zombie in game.zombies:
if zombie.row == self.row and zombie.x > self.x:
freeze_power = 0.5 if self.type == “snowpea” else 0
game.bullets.append(Bullet(self.x, self.y, self.row, freeze_power))
self.shoot_timer = 0
if game.sound_enabled:
if self.type == “snowpea”:
game.snow_sound.play()
else:
game.shoot_sound.play()
break
elif self.type == “sunflower”:
self.sun_timer += 1
if self.sun_timer > 150: # 每2.5秒产生一个阳光
game.sun_list.append(Sun(self.x, self.y))
self.sun_timer = 0
if game.sound_enabled:
game.sun_sound.play()
elif self.type == “cherrybomb”:
self.explode_timer -= 1
if self.explode_timer <= 0 and not self.ready_to_explode:
self.ready_to_explode = True
# 创建爆炸效果
game.explosions.append(Explosion(self.x, self.y, “cherry”))
# 伤害周围僵尸
for zombie in game.zombies:
distance = math.sqrt((zombie.x – self.x)**2 + (zombie.y – self.y)**2)
if distance < 150:
zombie.health -= 500 # 巨大伤害
# 播放音效
if game.sound_enabled:
game.explosion_sound.play()
# 移除植物
self.health = 0
# 僵尸基类
class Zombie:
def __init__(self, row, zombie_type=”normal”):
self.row = row
self.x = SCREEN_WIDTH
self.y = LAWN_TOP + row * GRID_SIZE + GRID_SIZE // 2
self.speed = 0.5
self.attack_timer = 0
self.health = 100
self.max_health = 100
self.damage = 0.5
self.type = zombie_type
self.points = 100
self.animation_timer = 0
self.animation_frame = 0
self.frozen_timer = 0
self.is_eating = False
self.stun_timer = 0
# 添加僵尸状态变量
self.is_walking = True
self.attack_cooldown = 30 # 攻击冷却时间
def draw(self, resources):
# 僵尸动画
self.animation_timer += 1
if self.animation_timer > 10:
self.animation_frame = (self.animation_frame + 1) % 4
self.animation_timer = 0
# 使用资源图片绘制僵尸
zombie_img = resources.get(self.type, None)
if zombie_img:
# 根据方向翻转图像
flipped_img = pygame.transform.flip(zombie_img, True, False)
# 添加动画缩放效果
scale_factor = 1.0 + 0.05 * math.sin(self.animation_frame * 0.5)
scaled_img = pygame.transform.scale(flipped_img,
(int(flipped_img.get_width() * scale_factor),
int(flipped_img.get_height() * scale_factor)))
# 冰冻效果
if self.frozen_timer > 0:
# 创建带蓝色色调的表面
frozen_surface = pygame.Surface(scaled_img.get_size(), pygame.SRCALPHA)
frozen_surface.fill((100, 150, 255, 100))
scaled_img.blit(frozen_surface, (0, 0), special_flags=pygame.BLEND_RGBA_ADD)
# 绘制僵尸
img_rect = scaled_img.get_rect(center=(self.x, self.y))
screen.blit(scaled_img, img_rect)
else:
# 回退到绘图 – 这里使用更详细的僵尸绘制
# 绘制僵尸身体
body_height = 60
body_width = 40
pygame.draw.rect(screen, (100, 100, 150), (self.x – body_width//2, self.y – body_height//2, body_width, body_height))
pygame.draw.rect(screen, (50, 50, 100), (self.x – body_width//2, self.y – body_height//2, body_width, body_height), 2)
# 绘制僵尸头部
head_radius = 20
pygame.draw.circle(screen, (150, 150, 150), (self.x, self.y – body_height//2 – head_radius//2), head_radius)
pygame.draw.circle(screen, (100, 100, 100), (self.x, self.y – body_height//2 – head_radius//2), head_radius, 2)
# 绘制眼睛
eye_radius = 5
pygame.draw.circle(screen, RED, (self.x – 7, self.y – body_height//2 – head_radius//2 – 3), eye_radius)
pygame.draw.circle(screen, RED, (self.x + 7, self.y – body_height//2 – head_radius//2 – 3), eye_radius)
# 绘制嘴巴
mouth_width = 15
pygame.draw.arc(screen, (100, 0, 0),
(self.x – mouth_width//2, self.y – body_height//2 – 5, mouth_width, 15),
math.pi, 2*math.pi, 2)
# 绘制生命条
health_percent = self.health / self.max_health
bar_width = 50
bar_height = 6
pygame.draw.rect(screen, RED, (self.x – bar_width//2, self.y – 70, bar_width, bar_height))
pygame.draw.rect(screen, GREEN, (self.x – bar_width//2, self.y – 70, bar_width * health_percent, bar_height))
# 冰冻效果图标
if self.frozen_timer > 0:
pygame.draw.circle(screen, (100, 150, 255), (self.x – bar_width//2 – 10, self.y – 67), 8)
def update(self, game):
# 更新冰冻状态
if self.frozen_timer > 0:
self.frozen_timer -= 1
if self.frozen_timer == 0:
self.speed = min(0.8, self.speed * 2) # 解冻后恢复速度
# 更新眩晕状态
if self.stun_timer > 0:
self.stun_timer -= 1
return
# 移动僵尸
if self.is_walking:
self.x -= self.speed
# 检查是否与植物碰撞
self.is_eating = False
for plant in game.plants:
if plant.row == self.row and abs(self.x – plant.x) < 50:
self.is_eating = True
self.is_walking = False
self.attack_timer += 1
if self.attack_timer > self.attack_cooldown: # 根据冷却时间攻击
plant.health -= self.damage
self.attack_timer = 0
# 随机播放僵尸音效
if game.sound_enabled and random.random() < 0.02:
game.zombie_sound.play()
break
# 如果没有攻击植物,则继续前进
if not self.is_eating:
self.is_walking = True
self.attack_timer = 0
# 普通僵尸
class NormalZombie(Zombie):
def __init__(self, row):
super().__init__(row, “normal”)
self.health = 100
self.max_health = 100
self.speed = 0.5
self.points = 100
# 锥头僵尸
class ConeheadZombie(Zombie):
def __init__(self, row):
super().__init__(row, “conehead”)
self.health = 250
self.max_health = 250
self.speed = 0.45
self.points = 150
# 铁桶僵尸
class BucketheadZombie(Zombie):
def __init__(self, row):
super().__init__(row, “buckethead”)
self.health = 500
self.max_health = 500
self.speed = 0.4
self.points = 200
# 橄榄球僵尸
class FootballZombie(Zombie):
def __init__(self, row):
super().__init__(row, “football”)
self.health = 400
self.max_health = 400
self.speed = 0.7
self.damage = 0.7
self.points = 300
def update(self, game):
super().update(game)
# 橄榄球僵尸有概率冲撞植物
if not self.is_eating and random.random() < 0.01:
self.stun_timer = 30 # 短暂眩晕
self.x -= 20 # 向前冲刺
# 对植物造成伤害
for plant in game.plants:
if plant.row == self.row and abs(self.x – plant.x) < 70:
plant.health -= 15 # 增加冲撞伤害
# 舞王僵尸
class DancerZombie(Zombie):
def __init__(self, row):
super().__init__(row, “dancer”)
self.health = 350
self.max_health = 350
self.speed = 0.4
self.damage = 0.4
self.points = 250
self.dance_timer = 0
self.last_dance = 0
self.dance_cooldown = 180 # 跳舞冷却时间
def update(self, game):
super().update(game) # 调用父类更新方法
# 舞王僵尸跳舞召唤伴舞
self.dance_timer += 1
if self.dance_timer – self.last_dance > self.dance_cooldown: # 根据冷却时间跳舞
if random.random() < 0.5: # 50%概率跳舞
self.last_dance = self.dance_timer
# 在相邻行召唤伴舞僵尸
for i in range(-1, 2): # 上一行、当前行、下一行
new_row = self.row + i
if 0 <= new_row < GRID_ROWS:
game.zombies.append(NormalZombie(new_row))
# 伴舞僵尸从舞王位置开始
game.zombies[-1].x = self.x + 50
# 播放音效
if game.sound_enabled:
game.zombie_sound.play()
def draw(self, resources):
super().draw(resources) # 调用父类绘制方法
# 跳舞时显示特效
if self.dance_timer – self.last_dance < 30:
# 绘制音符
pygame.draw.circle(screen, (255, 255, 0), (self.x + 30, self.y – 60), 8)
pygame.draw.circle(screen, (255, 100, 100), (self.x – 30, self.y – 40), 6)
# 子弹类
class Bullet:
def __init__(self, x, y, row, freeze_power=0):
self.x = x
self.y = y
self.row = row
self.speed = 7
self.damage = 25
self.radius = 8
self.freeze_power = freeze_power # 冰冻强度 (0-1)
self.rotation = 0
def draw(self, resources):
# 使用资源图片绘制子弹
if self.freeze_power > 0:
bullet_img = resources.get('snowball', None)
else:
bullet_img = resources.get('pea', None)
if bullet_img:
scaled_img = pygame.transform.scale(bullet_img, (self.radius * 2, self.radius * 2))
img_rect = scaled_img.get_rect(center=(self.x, self.y))
screen.blit(scaled_img, img_rect)
else:
# 回退到绘图
pygame.draw.circle(screen, (255, 255, 0) if self.freeze_power > 0 else (0, 255, 0), (int(self.x), int(self.y)), self.radius)
def update(self):
self.x += self.speed
# 阳光类
class Sun:
def __init__(self, x=None, y=None):
if x is None:
self.x = random.randint(LAWN_LEFT, LAWN_LEFT + GRID_COLS * GRID_SIZE)
self.y = 0
else:
self.x = x
self.y = y
self.speed = 1
self.lifetime = 300 # 阳光持续时间
self.rect = pygame.Rect(self.x – 20, self.y – 20, 40, 40)
def draw(self, resources):
sun_img = resources.get('sun', None)
if sun_img:
scaled_img = pygame.transform.scale(sun_img, (40, 40))
img_rect = scaled_img.get_rect(center=(self.x, self.y))
screen.blit(scaled_img, img_rect)
else:
pygame.draw.circle(screen, YELLOW, (self.x, self.y), 20)
def update(self):
if self.y < LAWN_TOP + GRID_ROWS * GRID_SIZE:
self.y += self.speed
self.lifetime -= 1
self.rect = pygame.Rect(self.x – 20, self.y – 20, 40, 40)
# 爆炸效果类
class Explosion:
def __init__(self, x, y, explosion_type):
self.x = x
self.y = y
self.type = explosion_type
self.lifetime = 60 # 爆炸持续时间
def draw(self, resources):
explosion_img = resources.get('explosion', None)
if explosion_img:
scaled_img = pygame.transform.scale(explosion_img, (100, 100))
img_rect = scaled_img.get_rect(center=(self.x, self.y))
screen.blit(scaled_img, img_rect)
else:
if self.type == “cherry”:
pygame.draw.circle(screen, RED, (self.x, self.y), 50)
else:
pygame.draw.circle(screen, (100, 100, 100), (self.x, self.y), 30)
def update(self):
self.lifetime -= 1
# 主游戏循环
def main():
resources = load_resources()
game = Game(resources)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == MOUSEBUTTONDOWN:
x, y = event.pos
if game.collect_btn_rect.collidepoint(x, y):
game.toggle_auto_collect()
elif y < LAWN_TOP:
# 选择植物
plant_types = [
{“name”: “向日葵”, “cost”: 50, “type”: “sunflower”},
{“name”: “豌豆射手”, “cost”: 100, “type”: “peashooter”},
{“name”: “寒冰射手”, “cost”: 150, “type”: “snowpea”},
{“name”: “坚果墙”, “cost”: 50, “type”: “wallnut”},
{“name”: “樱桃炸弹”, “cost”: 150, “type”: “cherrybomb”},
]
for i, plant in enumerate(plant_types):
rect = pygame.Rect(20 + i * 150, 20, 120, 90)
if rect.collidepoint(x, y):
game.selected_plant = i
elif y >= LAWN_TOP:
col = (x – LAWN_LEFT) // GRID_SIZE
row = (y – LAWN_TOP) // GRID_SIZE
if 0 <= col < GRID_COLS and 0 <= row < GRID_ROWS:
game.add_plant(row, col)
game.collect_sun(x, y)
elif event.type == KEYDOWN:
if event.key == K_r:
if game.game_over or game.game_won:
game.reset()
game.update()
game.draw()
pygame.display.flip()
clock.tick(FPS)
if __name__ == “__main__”:
main()




















暂无评论内容