植物大战僵尸-源代码

游戏核心概念

这是一个基于经典《植物大战僵尸》的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()

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容