植物大战僵尸

import pygame
import random
import sys

初始化pygame

3019

pygame.init()

屏幕设置

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("简易植物大战僵尸")

颜色定义

GREEN = (0, 255, 0)
BROWN = (139, 69, 19)
BLUE = (0, 0, 255)
RED = (255, 0, 0)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)

游戏参数

FPS = 60
clock = pygame.time.Clock()

草坪格子

GRID_SIZE = 80
GRID_COLS = 9
GRID_ROWS = 5
GRID_OFFSET_X = 50
GRID_OFFSET_Y = 100

植物类

class Plant:
def init(self, x, y):
self.x = x
self.y = y
self.health = 100
self.attack_cooldown = 0

def draw(self):
    pygame.draw.circle(screen, GREEN, (self.x + GRID_SIZE // 2, self.y + GRID_SIZE // 2), 30)

def update(self):
    if self.attack_cooldown > 0:
        self.attack_cooldown -= 1

def can_attack(self):
    return self.attack_cooldown == 0

def attack(self):
    self.attack_cooldown = 30
    return Projectile(self.x + GRID_SIZE, self.y + GRID_SIZE // 2)

豌豆射手类

class Peashooter(Plant):
def init(self, x, y):
super().init(x, y)
self.cost = 100

def draw(self):
    pygame.draw.circle(screen, GREEN, (self.x + GRID_SIZE // 2, self.y + GRID_SIZE // 2), 30)
    pygame.draw.circle(screen, BLACK, (self.x + GRID_SIZE // 2 + 15, self.y + GRID_SIZE // 2), 5)

向日葵类

class Sunflower(Plant):
def init(self, x, y):
super().init(x, y)
self.cost = 50
self.sun_cooldown = 0

def draw(self):
    pygame.draw.circle(screen, (255, 255, 0), (self.x + GRID_SIZE // 2, self.y + GRID_SIZE // 2), 30)
    for i in range(8):
        angle = i * (360 / 8)
        end_x = self.x + GRID_SIZE // 2 + 40 * pygame.math.Vector2(1, 0).rotate(angle).x
        end_y = self.y + GRID_SIZE // 2 + 40 * pygame.math.Vector2(1, 0).rotate(angle).y
        pygame.draw.line(screen, (255, 165, 0),
                         (self.x + GRID_SIZE // 2, self.y + GRID_SIZE // 2),
                         (end_x, end_y), 3)

def update(self):
    super().update()
    if self.sun_cooldown > 0:
        self.sun_cooldown -= 1
    else:
        self.sun_cooldown = 300  # 5秒(60帧/秒)
        return Sun(self.x, self.y)
    return None

坚果墙类

class WallNut(Plant):
def init(self, x, y):
super().init(x, y)
self.health = 300
self.cost = 50

def draw(self):
    pygame.draw.circle(screen, BROWN, (self.x + GRID_SIZE // 2, self.y + GRID_SIZE // 2), 30)

僵尸类

class Zombie:
def init(self, row):
self.x = SCREEN_WIDTH
self.y = GRID_OFFSET_Y + row * GRID_SIZE + GRID_SIZE // 2
self.row = row
self.speed = 1
self.health = 100
self.damage = 0.5
self.attack_cooldown = 0

def draw(self):
    pygame.draw.rect(screen, (100, 100, 100), (self.x - 30, self.y - 50, 60, 100))
    # 血条
    pygame.draw.rect(screen, RED, (self.x - 30, self.y - 70, 60, 10))
    pygame.draw.rect(screen, GREEN, (self.x - 30, self.y - 70, 60 * (self.health / 100), 10))

def update(self, plants):
    # 检查是否在攻击植物
    attacking = False
    for plant in plants:
        if plant.y // GRID_SIZE == self.row and abs(plant.x - self.x) < GRID_SIZE:
            if self.attack_cooldown <= 0:
                plant.health -= self.damage
                self.attack_cooldown = 30
            attacking = True
            break

    if not attacking:
        self.x -= self.speed
        self.attack_cooldown = 0

def is_dead(self):
    return self.health <= 0

def reached_house(self):
    return self.x < 0

子弹类

class Projectile:
def init(self, x, y):
self.x = x
self.y = y
self.speed = 5
self.damage = 20

def draw(self):
    pygame.draw.circle(screen, GREEN, (self.x, self.y), 5)

def update(self):
    self.x += self.speed

def off_screen(self):
    return self.x > SCREEN_WIDTH

def hits(self, zombie):
    return (abs(self.x - zombie.x) < 30 and
            abs(self.y - zombie.y) < 50)

阳光类

class Sun:
def init(self, x, y):
self.x = x
self.y = y
self.target_y = y + random.randint(50, 200)
self.speed = 1
self.lifetime = 300 # 5秒
self.collected = False

def draw(self):
    pygame.draw.circle(screen, (255, 255, 0), (self.x, self.y), 20)

def update(self):
    if self.y < self.target_y:
        self.y += self.speed
    self.lifetime -= 1

def is_dead(self):
    return self.lifetime <= 0 or self.collected

def is_clicked(self, pos):
    return ((pos[0] - self.x) ** 2 + (pos[1] - self.y) ** 2) <= 400  # 20^2

游戏主类

class Game:
def init(self):
self.plants = []
self.zombies = []
self.projectiles = []
self.suns = []
self.sun_count = 100
self.selected_plant = None
self.zombie_spawn_timer = 0
self.game_over = False
self.font = pygame.font.SysFont(None, 36)

def draw_grid(self):
    for row in range(GRID_ROWS):
        for col in range(GRID_COLS):
            rect = pygame.Rect(
                GRID_OFFSET_X + col * GRID_SIZE,
                GRID_OFFSET_Y + row * GRID_SIZE,
                GRID_SIZE,
                GRID_SIZE
            )
            pygame.draw.rect(screen, (200, 200, 200), rect, 1)

def draw_ui(self):
    # 阳光计数
    sun_text = self.font.render(f"阳光: {self.sun_count}", True, BLACK)
    screen.blit(sun_text, (20, 20))

    # 植物选择菜单
    plants = [Peashooter(0, 0), Sunflower(0, 0), WallNut(0, 0)]
    for i, plant in enumerate(plants):
        rect = pygame.Rect(GRID_OFFSET_X + i * 100, 20, 80, 80)
        # 修改为调用 draw 方法
        plant.draw()
        cost_text = self.font.render(str(plant.cost), True, BLACK)
        screen.blit(cost_text, (rect.x + 40 - cost_text.get_width() // 2, rect.y + 70))

        if self.sun_count < plant.cost:
            pygame.draw.rect(screen, (100, 100, 100, 150), rect)

def handle_events(self):
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            return False

        if event.type == pygame.MOUSEBUTTONDOWN:
            pos = pygame.mouse.get_pos()

            # 收集阳光
            for sun in self.suns:
                if sun.is_clicked(pos):
                    self.sun_count += 25
                    sun.collected = True

            # 选择植物
            plants = [Peashooter(0, 0), Sunflower(0, 0), WallNut(0, 0)]
            for i, plant in enumerate(plants):
                rect = pygame.Rect(GRID_OFFSET_X + i * 100, 20, 80, 80)
                if rect.collidepoint(pos) and self.sun_count >= plant.cost:
                    self.selected_plant = (i, plant)

            # 放置植物
            if self.selected_plant:
                grid_col = (pos[0] - GRID_OFFSET_X) // GRID_SIZE
                grid_row = (pos[1] - GRID_OFFSET_Y) // GRID_SIZE

                if (0 <= grid_col < GRID_COLS and
                        0 <= grid_row < GRID_ROWS and
                        not any(p.x == GRID_OFFSET_X + grid_col * GRID_SIZE and
                                p.y == GRID_OFFSET_Y + grid_row * GRID_SIZE
                                for p in self.plants)):

                    plant_type = self.selected_plant[0]
                    if plant_type == 0:
                        new_plant = Peashooter(
                            GRID_OFFSET_X + grid_col * GRID_SIZE,
                            GRID_OFFSET_Y + grid_row * GRID_SIZE
                        )
                    elif plant_type == 1:
                        new_plant = Sunflower(
                            GRID_OFFSET_X + grid_col * GRID_SIZE,
                            GRID_OFFSET_Y + grid_row * GRID_SIZE
                        )
                    else:
                        new_plant = WallNut(
                            GRID_OFFSET_X + grid_col * GRID_SIZE,
                            GRID_OFFSET_Y + grid_row * GRID_SIZE
                        )

                    if self.sun_count >= new_plant.cost:
                        self.plants.append(new_plant)
                        self.sun_count -= new_plant.cost
                        self.selected_plant = None

    return True

def update(self):
    if self.game_over:
        return

    # 生成僵尸
    self.zombie_spawn_timer -= 1
    if self.zombie_spawn_timer <= 0:
        row = random.randint(0, GRID_ROWS - 1)
        self.zombies.append(Zombie(row))
        self.zombie_spawn_timer = random.randint(120, 360)  # 2-6秒

    # 更新植物
    for plant in self.plants[:]:
        plant.update()

        if isinstance(plant, Sunflower):
            sun = plant.update()
            if sun:
                self.suns.append(sun)

        if plant.health <= 0:
            self.plants.remove(plant)

        if isinstance(plant, Peashooter) and plant.can_attack():
            # 检查这一行是否有僵尸
            row = plant.y // GRID_SIZE
            if any(zombie.row == row and zombie.x > plant.x for zombie in self.zombies):
                self.projectiles.append(plant.attack())

    # 更新子弹
    for projectile in self.projectiles[:]:
        projectile.update()

        if projectile.off_screen():
            self.projectiles.remove(projectile)
            continue

        for zombie in self.zombies:
            if projectile.hits(zombie):
                zombie.health -= projectile.damage
                if projectile in self.projectiles:
                    self.projectiles.remove(projectile)
                break

    # 更新僵尸
    for zombie in self.zombies[:]:
        zombie.update(self.plants)

        if zombie.is_dead():
            self.zombies.remove(zombie)

        if zombie.reached_house():
            self.game_over = True

    # 更新阳光
    for sun in self.suns[:]:
        sun.update()
        if sun.is_dead():
            self.suns.remove(sun)

    # 随机生成阳光
    if random.random() < 0.001:  # 每帧0.1%几率
        self.suns.append(Sun(
            random.randint(GRID_OFFSET_X, GRID_OFFSET_X + GRID_COLS * GRID_SIZE),
            GRID_OFFSET_Y - 50
        ))

def draw(self):
    screen.fill(WHITE)
    self.draw_grid()

    # 绘制植物
    for plant in self.plants:
        plant.draw()

    # 绘制僵尸
    for zombie in self.zombies:
        zombie.draw()

    # 绘制子弹
    for projectile in self.projectiles:
        projectile.draw()

    # 绘制阳光
    for sun in self.suns:
        sun.draw()

    # 绘制UI
    self.draw_ui()

    # 绘制选中的植物预览
    if self.selected_plant:
        pos = pygame.mouse.get_pos()
        plant_type = self.selected_plant[0]
        if plant_type == 0:
            plant = Peashooter(pos[0] - GRID_SIZE // 2, pos[1] - GRID_SIZE // 2)
        elif plant_type == 1:
            plant = Sunflower(pos[0] - GRID_SIZE // 2, pos[1] - GRID_SIZE // 2)
        else:
            plant = WallNut(pos[0] - GRID_SIZE // 2, pos[1] - GRID_SIZE // 2)
        plant.draw()

    # 游戏结束画面
    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 = self.font.render("游戏结束! 按R键重新开始", True, WHITE)
        screen.blit(game_over_text,
                    (SCREEN_WIDTH // 2 - game_over_text.get_width() // 2,
                     SCREEN_HEIGHT // 2 - game_over_text.get_height() // 2))

    pygame.display.flip()

def reset(self):
    self.plants = []
    self.zombies = []
    self.projectiles = []
    self.suns = []
    self.sun_count = 100
    self.selected_plant = None
    self.zombie_spawn_timer = 0
    self.game_over = False

主游戏循环

def main():
game = Game()
running = True

while running:
    running = game.handle_events()

    keys = pygame.key.get_pressed()
    if keys[pygame.K_r] and game.game_over:
        game.reset()

    game.update()
    game.draw()
    clock.tick(FPS)

pygame.quit()
sys.exit()

if name == "main":
main()

3019

posted @ 2025-06-23 01:54  城阳  阅读(28)  评论(0)    收藏  举报