# 使用Claude Code开发植物大战僵尸游戏(pygame,附源码)

文章目录

    • 引言
    • 项目速览
    • 架构设计:状态机驱动的游戏循环
    • 模块职责划分
    • 技术亮点
      • [1. 程序化图形:用几何图元画出一个世界](#1. 程序化图形:用几何图元画出一个世界)
      • [2. 程序化音效:用数学公式合成声音](#2. 程序化音效:用数学公式合成声音)
      • [3. 严格的时间管理:统一使用 `pygame.time.get_ticks()`](#3. 严格的时间管理:统一使用 pygame.time.get_ticks())
      • [4. 坐标系统:网格与像素的清晰转换](#4. 坐标系统:网格与像素的清晰转换)
      • [5. 实体交互模式:通过 `game` 引用解耦](#5. 实体交互模式:通过 game 引用解耦)
      • [6. 特殊僵尸的差异化行为](#6. 特殊僵尸的差异化行为)
      • [7. 中文显示与字体回退](#7. 中文显示与字体回退)
    • 波次与关卡系统
    • 扩展指南
    • 总结
    • 运行效果

人工备注说明

  • 开发工具Claude Code + (火山方舟)glm-5.1;
  • 这个项目使用Claude Code开发,2小时直接生成80%代码,2小时让AI微调代码;
  • 这篇文章也是使用Claude Code编写;

引言

植物大战僵尸(Plants vs. Zombies)是无数玩家的童年回忆。你是否想过,不依赖任何图片、音频等外部资源,仅用纯代码能否复刻这款经典塔防游戏?本文将带你走进一个完全基于 Pygame 的植物大战僵尸克隆项目------所有图形通过几何图元程序化绘制,所有音效通过数学公式程序化生成,界面全中文支持,整个项目仅约 2000 行 Python 代码。

项目速览

  • 代码量:约 2055 行,11 个 Python 模块
  • 外部依赖 :仅需 pygame,无图片、无音频文件、无其他第三方库
  • 运行方式pip install pygame && python main.py
  • 特性:5 种植物、5 种僵尸、10 关关卡、波次系统、割草机、阳光收集、暂停/设置持久化

架构设计:状态机驱动的游戏循环

游戏的核心是经典的游戏循环模式:处理事件 → 更新逻辑 → 渲染画面 → 刷新屏幕。在此基础上,通过状态机管理不同的游戏阶段:

复制代码
menu → level_select → playing ⇄ paused ⇄ settings
                    ↓
              level_complete → playing (下一关)
                    ↓
              win / lose → menu

game.py 中的 Game 类是整个项目的中枢,拥有主循环、状态机、所有实体列表和波次系统。每个状态都有独立的 draw_* 方法和事件处理分支:

python 复制代码
def run(self):
    while running:
        for event in pygame.event.get():
            self.handle_event(event)
        self.update()
        # 根据状态选择渲染方法
        if self.state == "menu":
            self.draw_menu()
        elif self.state == "playing":
            self.draw()
        # ...
        pygame.display.flip()
        self.clock.tick(FPS)

这种设计将不同阶段的逻辑完全隔离,避免了状态间的耦合。例如暂停状态下不会触发游戏更新,设置界面的事件处理也不会影响战斗逻辑。

模块职责划分

项目遵循单一职责原则,每个模块都有清晰的边界:

模块 行数 职责
game.py 816 游戏主循环、状态机、实体管理、波次系统
plants.py 299 植物基类及 5 个子类(向日葵、豌豆射手、双发射手、食人花、坚果墙)
zombies.py 202 僵尸基类及 5 个子类(普通、路障、铁桶、跨栏、报纸僵尸)
ui.py 207 植物卡片栏、铲子、暂停按钮、波次进度条
sound.py 152 程序化音效生成,SoundManager 管理 13 种音效
map_grid.py 96 网格数据、坐标转换、草坪/街道渲染、割草机
sun.py 81 阳光下落、收集动画、生命周期
settings.py 111 所有常量定义、中文字体辅助函数
projectile.py 40 豌豆弹丸移动与碰撞检测
button.py 34 通用按钮组件
main.py 17 入口文件

settings.py 是游戏平衡的唯一数据源------血量、费用、速度、时间间隔等所有常量集中管理,调整难度只需修改一个文件。

技术亮点

1. 程序化图形:用几何图元画出一个世界

这是项目最有趣的特性。没有加载任何 PNG/SVG,所有角色都通过 pygame.drawcirclerectellipsepolygonlinearc 组合绘制。

以豌豆射手为例,它由茎、叶子、头部、嘴巴和眼睛组成:

python 复制代码
def draw(self, surface, x, y):
    # 茎
    pygame.draw.rect(surface, (0, 80, 0), (x - 4, y + 5, 8, 30))
    # 叶子
    pygame.draw.ellipse(surface, DARK_GREEN, (x - 18, y + 10, 16, 8))
    # 头部(渐变效果用两个同心圆模拟)
    pygame.draw.circle(surface, (0, 120, 0), (x, y - 5), 20)
    pygame.draw.circle(surface, (0, 160, 0), (x, y - 5), 17)
    # 嘴巴/炮管
    pygame.draw.rect(surface, (0, 80, 0), (x + 5, y - 10, 22, 14))
    # 眼睛
    pygame.draw.circle(surface, WHITE, (x - 5, y - 11), 6)
    pygame.draw.circle(surface, BLACK, (x - 4, y - 11), 3)

这种绘制方式不仅避免了版权问题,还带来了独特的手绘风格。每个角色都有动画细节------向日葵的花瓣会微微摇摆,僵尸走路时身体会上下晃动,坚果墙受伤后会出现裂纹:

python 复制代码
# 坚果墙的损伤可视化
damage_ratio = 1 - self.hp / self.max_hp
if damage_ratio > 0.33:
    pygame.draw.line(surface, DARK_BROWN, ...)  # 第一道裂纹
if damage_ratio > 0.66:
    pygame.draw.line(surface, BLACK, ...)        # 更深的裂纹

2. 程序化音效:用数学公式合成声音

sound.py 实现了一个完整的程序化音效系统。核心思路是用正弦波合成乐音,用随机噪声合成打击音效:

python 复制代码
def _tone(freq, duration, volume=0.5, sample_rate=22050):
    """生成指定频率的正弦波音调"""
    n = int(sample_rate * duration)
    samples = []
    for i in range(n):
        t = i / sample_rate
        env = min(1.0, min(i, n - i) / (sample_rate * 0.01))  # 包络线
        samples.append(volume * env * math.sin(2 * math.pi * freq * t))
    return samples

通过组合不同频率和时长的音调,可以模拟出丰富的游戏音效。例如阳光收集音效使用上行音阶(880Hz → 1100Hz → 1320Hz),给出愉悦的反馈感;僵尸死亡音效使用下行音阶(300Hz → 200Hz → 100Hz),营造低沉的氛围;游戏胜利音效则使用 C 大调上行琶音(C5 → E5 → G5 → C6),带来通关的成就感。

SoundManager 在初始化时一次性生成 13 种音效并缓存,运行时通过 play(name) 按名称播放:

python 复制代码
self.sounds = {
    'place': create_place_sound(),
    'pea': create_pea_sound(),
    'sun_collect': create_sun_collect_sound(),
    'zombie_die': create_zombie_die_sound(),
    # ...
}

3. 严格的时间管理:统一使用 pygame.time.get_ticks()

游戏开发中计时器的管理是容易出现 bug 的地方。本项目坚持一个原则:所有计时器统一使用 pygame.time.get_ticks() ,禁止使用 time.time() 或帧计数。

这个原则在暂停/恢复机制中尤为重要。当游戏暂停时,记录暂停时间戳;恢复时,计算暂停时长 delta_ms,然后将所有计时器偏移该时长:

python 复制代码
def _resume(self):
    delta = pygame.time.get_ticks() - self.paused_from
    self._offset_timers(delta)
    self.state = "playing"

def _offset_timers(self, delta_ms):
    self.spawn_timer += delta_ms
    self.sky_sun_timer += delta_ms
    self.wave_timer += delta_ms
    for plant in self.plants_list:
        if hasattr(plant, 'sun_timer'):
            plant.sun_timer += delta_ms
        if hasattr(plant, 'fire_timer'):
            plant.fire_timer += delta_ms
    for card in self.ui.cards:
        card.last_placed += delta_ms
    # ...

这确保了暂停不会导致"时间跳跃"------恢复后植物不会突然发射大量豌豆,阳光不会突然过期。每一个有计时器的实体都被精心偏移,包括植物的射击/产阳光/咀嚼计时器、卡片的冷却计时器、阳光的生命周期计时器等。

4. 坐标系统:网格与像素的清晰转换

游戏场地是一个 5 行 9 列的网格,但实际渲染和交互都在像素空间。MapGrid 封装了两个关键的转换方法:

python 复制代码
def pixel_to_grid(self, x, y):
    col = (x - GRID_X_OFFSET) // CELL_WIDTH
    row = (y - GRID_Y_OFFSET) // CELL_HEIGHT
    if 0 <= row < GRID_ROWS and 0 <= col < GRID_COLS:
        return row, col
    return None, None

def grid_to_pixel(self, row, col):
    x = GRID_X_OFFSET + col * CELL_WIDTH + CELL_WIDTH // 2
    y = GRID_Y_OFFSET + row * CELL_HEIGHT + CELL_HEIGHT // 2
    return x, y

所有涉及坐标的操作------放置植物、碰撞检测、幽灵预览------都通过这两个方法进行转换,绝不硬编码像素坐标。这使得调整网格偏移或单元格尺寸时,整个游戏自动适配。

5. 实体交互模式:通过 game 引用解耦

植物和僵尸的 update() 方法接收 game 对象引用,通过它与其他系统交互:

python 复制代码
class Peashooter(Plant):
    def update(self, game):
        now = pygame.time.get_ticks()
        if now >= self.fire_timer:
            if game.has_zombie_in_row(self.row):  # 查询同行僵尸
                self.fire_timer = now + PEASHOOTER_FIRE_INTERVAL
                game.spawn_pea(self.row, self.col)  # 生成豌豆

这种设计让实体逻辑保持内聚------豌豆射手自己决定何时射击,但不需要知道豌豆是如何被创建和管理的。碰撞检测同样遵循这个模式,在 projectile.py 中豌豆自行检测与僵尸的碰撞并造成伤害。

6. 特殊僵尸的差异化行为

5 种僵尸共享基类的移动和啃食逻辑,但通过方法重写实现差异化的行为:

  • 路障僵尸/铁桶僵尸 :单纯增加血量,通过 _draw_accessory 绘制不同的头部装饰
  • 跨栏僵尸 :重写 _check_plant_collision,首次遇到植物时跳过而非啃食
  • 报纸僵尸 :重写 take_damage,报纸被打掉后进入暴怒状态,速度提升 3 倍
python 复制代码
class NewspaperZombie(Zombie):
    def take_damage(self, amount):
        if self.newspaper_alive:
            self.newspaper_hp -= amount
            self.hp -= amount
            if self.newspaper_hp <= 0:
                self.newspaper_alive = False
                self.angry = True
                self.base_speed = ZOMBIE_SPEED * NEWSPAPER_ZOMBIE_ANGRY_SPEED_MULT
                self.speed = self.base_speed

7. 中文显示与字体回退

所有界面文字均为中文,settings.py 中的 get_chinese_font() 实现了多级字体回退:

python 复制代码
def get_chinese_font(size):
    font_paths = [
        "C:/Windows/Fonts/msyh.ttc",   # 微软雅黑
        "C:/Windows/Fonts/simhei.ttf",  # 黑体
        "C:/Windows/Fonts/simsun.ttc",  # 宋体
    ]
    for path in font_paths:
        if os.path.exists(path):
            try:
                return pygame.font.Font(path, size)
            except Exception:
                continue
    return pygame.font.Font(None, size)  # 最终回退

所有 render() 调用都必须使用此函数,而非 Pygame 默认字体,确保中文字符正确显示。

波次与关卡系统

游戏包含 10 个关卡,每关 5 波僵尸。僵尸数量随关卡和波次递增:

python 复制代码
def _zombie_count_for_wave(self):
    base = BASE_ZOMBIE_COUNT * self.level
    return base + self.wave * self.level

不同关卡的僵尸种类逐步解锁:第 2 关出现路障僵尸,第 3 关出现跨栏僵尸,第 4 关出现报纸僵尸,第 6 关出现铁桶僵尸。每波开始前有 20 秒准备时间,带有倒计时提示和音效。

关卡进度通过 JSON 文件持久化,解锁的最高关卡会被保存:

python 复制代码
def _save_settings(self):
    data = {
        'volume': self.volume,
        'speed': SPEED_OPTIONS[self.speed_index],
        'level_unlocked': self.level_unlocked,
    }
    with open(SETTINGS_FILE, 'w') as f:
        json.dump(data, f)

扩展指南

添加新植物

  1. settings.py 中添加常量(费用、血量、计时器间隔)
  2. plants.py 中创建子类,实现 update(game)draw(surface, x, y)
  3. 将新类追加到 PLANT_CLASSES 列表
  4. ui.pyPlantCard._draw_plant_icon() 中添加卡片图标
  5. 如果植物有计时器,在 game.py_offset_timers() 中添加偏移逻辑

添加新僵尸

  1. settings.py 中添加常量(血量、速度倍率)
  2. zombies.py 中创建子类,重写 _draw_accessory() 绘制装饰,必要时重写 _check_plant_collision()take_damage()
  3. game.py_spawn_zombie() 中添加生成逻辑

总结

这个项目展示了如何用最简洁的技术栈实现一个完整的塔防游戏。程序化图形和音效消除了对外部资源的依赖,状态机管理了复杂的游戏流程,统一的时间系统避免了暂停后的时间跳跃,清晰的模块划分让代码易于理解和扩展。

对于想入门游戏开发的 Python 程序员来说,这是一个很好的学习案例------你不需要美术素材,不需要音频工程知识,只需要理解游戏循环、状态管理和碰撞检测这几个核心概念,就能创造出有趣的游戏体验。

运行效果




源码

相关推荐
hhb_6181 小时前
Groovy语法进阶与工程实践指南
开发语言·python
hmywillstronger2 小时前
Rhino 中文字方向问题的解析与解决方案
python
AI技术增长2 小时前
Pytorch图像去噪实战(四):Attention UNet图像去噪实战,让模型重点恢复边缘和纹理区域
人工智能·pytorch·python
2401_833033622 小时前
如何修复固定定位头部容器中悬浮下拉菜单的错位问题
jvm·数据库·python
深念Y2 小时前
Denuvo加密被全面攻破?聊聊D加密原理和这次的破解事件
人工智能·游戏·ai·逆向·虚拟机·虚拟·d加密
魔士于安2 小时前
Unity UI图片 复活节UI,卡通风格
游戏·ui·unity·游戏引擎·材质·贴图
z4424753262 小时前
CSS Grid布局如何实现网格项目的自动增长_设置grid-auto-flow- row
jvm·数据库·python
GeLx2 小时前
从反爬角度:Playwright CDP 模式、Playwright 传统模式与 DrissionPage 的比较
python·程序人生·playwright·drissionpage·pyppeteer·浏览器自动化控制
m0_740352422 小时前
如何在 SvelteKit 中为动态加载的图片实现响应式悬停覆盖层
jvm·数据库·python