1. 从零搭建Python版植物大战僵尸

记得第一次玩植物大战僵尸还是在大学宿舍,那时候通宵达旦研究植物搭配策略。现在用Python复刻这个经典游戏,不仅能重温童年乐趣,还能学习游戏开发的核心技术。我们先从最基础的游戏框架搭建开始。

Python版游戏的核心是Pygame库,它就像游戏开发的"乐高积木",提供了图像渲染、音效播放等基础模块。安装非常简单:

pip install pygame numpy

游戏主循环就像人的心脏,每秒钟跳动60次(即60FPS)。下面这段代码是游戏引擎的核心骨架:

import pygame

class Game:
    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode((800, 600))
        self.clock = pygame.time.Clock()
        self.running = True
        
    def run(self):
        while self.running:
            self.handle_events()
            self.update()
            self.draw()
            self.clock.tick(60)
            
    def handle_events(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.running = False
                
    def update(self):
        # 游戏逻辑更新
        pass
        
    def draw(self):
        self.screen.fill((0, 0, 0))  # 黑色背景
        pygame.display.flip()

if __name__ == "__main__":
    game = Game()
    game.run()

资源管理是游戏开发中的重头戏。我建议使用面向对象的方式组织游戏元素,比如创建一个资源加载器:

class ResourceLoader:
    @staticmethod
    def load_image(path, scale=1):
        img = pygame.image.load(path).convert_alpha()
        return pygame.transform.scale(img, 
                    (int(img.get_width() * scale), 
                     int(img.get_height() * scale)))
    
    @staticmethod 
    def load_sound(path):
        return pygame.mixer.Sound(path)

2. 游戏核心机制实现

2.1 植物与僵尸的类设计

用面向对象思维建模,植物和僵尸都应该有自己的基类。这是我实践下来最合理的继承体系:

class Plant:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.health = 100
        self.cost = 50
        self.cooldown = 0
        
    def update(self):
        if self.cooldown > 0:
            self.cooldown -= 1
            
    def draw(self, surface):
        surface.blit(self.image, (self.x, self.y))

class Peashooter(Plant):
    def __init__(self, x, y):
        super().__init__(x, y)
        self.image = ResourceLoader.load_image("peashooter.png")
        self.attack_cooldown = 60
        
    def update(self):
        super().update()
        if self.attack_cooldown <= 0:
            self.shoot()
            self.attack_cooldown = 60
        else:
            self.attack_cooldown -= 1
            
    def shoot(self):
        # 创建豌豆子弹
        pass

僵尸的实现也类似,但需要加入移动逻辑:

class Zombie:
    def __init__(self, x, y):
        self.x = x
        self.y = y 
        self.speed = 0.5
        self.health = 100
        self.damage = 1
        
    def update(self):
        self.x -= self.speed
        
    def draw(self, surface):
        surface.blit(self.image, (self.x, self.y))

2.2 游戏地图与碰撞检测

游戏采用网格布局,每个格子尺寸为80x100像素。碰撞检测使用矩形相交判断:

class GameMap:
    def __init__(self):
        self.grid = [[None for _ in range(5)] for _ in range(9)]
        self.plants = []
        self.zombies = []
        
    def add_plant(self, plant, row, col):
        if self.grid[row][col] is None:
            self.grid[row][col] = plant
            self.plants.append(plant)
            return True
        return False
        
    def check_collisions(self):
        for zombie in self.zombies:
            for plant in self.plants:
                if self.rect_collide(zombie, plant):
                    plant.health -= zombie.damage
                    zombie.state = "attacking"
                    
    @staticmethod
    def rect_collide(a, b):
        return (a.x < b.x + b.width and
                a.x + a.width > b.x and
                a.y < b.y + b.height and
                a.y + a.height > b.y)

2.3 阳光经济系统

阳光是游戏内的货币系统,需要实现收集和消耗机制:

class SunSystem:
    def __init__(self):
        self.sun_count = 50
        self.sun_spawn_timer = 0
        self.suns = []  # 屏幕上的阳光
        
    def update(self):
        # 自动生成阳光
        self.sun_spawn_timer += 1
        if self.sun_spawn_timer >= 300:  # 每5秒
            self.spawn_sun()
            self.sun_spawn_timer = 0
            
        # 更新阳光位置
        for sun in self.suns:
            sun.update()
            
    def spawn_sun(self):
        x = random.randint(100, 700)
        self.suns.append(Sun(x, 0))
        
    def collect_sun(self, sun):
        if sun in self.suns:
            self.suns.remove(sun)
            self.sun_count += 25

3. 机器学习赋能游戏AI

3.1 强化学习环境搭建

要让AI学会玩这个游戏,我们需要将其转化为强化学习问题。使用OpenAI Gym的接口规范:

import gym
from gym import spaces
import numpy as np

class PvZEnv(gym.Env):
    def __init__(self):
        super().__init__()
        self.action_space = spaces.Discrete(20)  # 5植物×4列
        self.observation_space = spaces.Box(
            low=0, high=255, shape=(600, 800, 3), dtype=np.uint8)
            
    def reset(self):
        self.game = Game()
        return self._get_obs()
        
    def step(self, action):
        # 执行动作
        plant_type = action // 4
        col = action % 4
        self.game.plant(plant_type, col)
        
        # 更新游戏
        self.game.update()
        
        # 返回观察、奖励、是否结束
        return self._get_obs(), self._get_reward(), self.game.is_over(), {}
        
    def _get_obs(self):
        return pygame.surfarray.array3d(self.game.screen)
        
    def _get_reward(self):
        # 自定义奖励函数
        return self.game.sun_count - len(self.game.zombies) * 10

3.2 DQN算法实现

深度Q学习是解决这类问题的经典算法。以下是核心实现:

import torch
import torch.nn as nn
import torch.optim as optim
from collections import deque
import random

class DQN(nn.Module):
    def __init__(self, input_shape, n_actions):
        super().__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(3, 32, 8, stride=4),
            nn.ReLU(),
            nn.Conv2d(32, 64, 4, stride=2),
            nn.ReLU(),
            nn.Conv2d(64, 64, 3, stride=1),
            nn.ReLU()
        )
        self.fc = nn.Sequential(
            nn.Linear(self._get_conv_out(input_shape), 512),
            nn.ReLU(),
            nn.Linear(512, n_actions)
        )
        
    def _get_conv_out(self, shape):
        o = self.conv(torch.zeros(1, *shape))
        return int(np.prod(o.size()))
        
    def forward(self, x):
        x = x.float() / 255.0
        conv_out = self.conv(x).view(x.size()[0], -1)
        return self.fc(conv_out)

class DQNAgent:
    def __init__(self, env):
        self.env = env
        self.model = DQN((3, 600, 800), env.action_space.n)
        self.target_model = DQN((3, 600, 800), env.action_space.n)
        self.optimizer = optim.Adam(self.model.parameters())
        self.memory = deque(maxlen=10000)
        self.batch_size = 32
        
    def remember(self, state, action, reward, next_state, done):
        self.memory.append((state, action, reward, next_state, done))
        
    def act(self, state, epsilon=0.1):
        if random.random() < epsilon:
            return self.env.action_space.sample()
        state = torch.FloatTensor(state).permute(2, 0, 1).unsqueeze(0)
        q_values = self.model(state)
        return torch.argmax(q_values).item()
        
    def replay(self):
        if len(self.memory) < self.batch_size:
            return
            
        batch = random.sample(self.memory, self.batch_size)
        states, actions, rewards, next_states, dones = zip(*batch)
        
        states = torch.FloatTensor(np.array(states)).permute(0, 3, 1, 2)
        next_states = torch.FloatTensor(np.array(next_states)).permute(0, 3, 1, 2)
        
        current_q = self.model(states).gather(1, torch.LongTensor(actions).unsqueeze(1))
        next_q = self.target_model(next_states).max(1)[0].detach()
        target = torch.FloatTensor(rewards) + 0.95 * next_q * (1 - torch.FloatTensor(dones))
        
        loss = nn.MSELoss()(current_q.squeeze(), target)
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()

3.3 训练策略与技巧

训练AI玩植物大战僵尸需要一些特殊技巧:

  1. 课程学习:先从简单关卡开始训练,逐步增加难度
  2. 奖励塑形:设计合理的奖励函数,比如:
    • +10 每收集一个阳光
    • +100 击杀一个僵尸
    • -1000 游戏失败
    • -1 每帧惩罚(鼓励快速通关)
  3. 模型架构:使用CNN处理游戏画面,LSTM处理时序信息
def train():
    env = PvZEnv()
    agent = DQNAgent(env)
    episodes = 1000
    
    for e in range(episodes):
        state = env.reset()
        total_reward = 0
        done = False
        
        while not done:
            action = agent.act(state)
            next_state, reward, done, _ = env.step(action)
            agent.remember(state, action, reward, next_state, done)
            agent.replay()
            state = next_state
            total_reward += reward
            
        print(f"Episode: {e}, Total Reward: {total_reward}")
        
        # 定期更新目标网络
        if e % 10 == 0:
            agent.target_model.load_state_dict(agent.model.state_dict())

4. 高级功能与优化

4.1 游戏性能优化

当僵尸数量增多时,游戏可能会出现卡顿。我总结了几个优化技巧:

  1. 精灵批处理:使用pygame.sprite.Groupdraw()方法
  2. 表面缓存:预渲染静态背景
  3. 碰撞检测优化:使用空间分区(如网格划分)
# 优化后的绘制代码
class OptimizedGame:
    def __init__(self):
        self.all_sprites = pygame.sprite.LayeredUpdates()
        self.background = self._create_background()
        
    def _create_background(self):
        bg = pygame.Surface((800, 600))
        bg.fill((124, 252, 0))  # 草地绿
        # 绘制网格线等静态元素
        return bg
        
    def draw(self):
        self.screen.blit(self.background, (0, 0))
        self.all_sprites.draw(self.screen)
        pygame.display.flip()

4.2 游戏存档系统

使用JSON保存游戏进度:

import json

class SaveSystem:
    @staticmethod
    def save(game, filename="save.json"):
        data = {
            "sun_count": game.sun_system.sun_count,
            "plants": [(type(p).__name__, p.x, p.y) for p in game.plants],
            "level": game.level
        }
        with open(filename, "w") as f:
            json.dump(data, f)
            
    @staticmethod
    def load(game, filename="save.json"):
        with open(filename) as f:
            data = json.load(f)
            game.sun_system.sun_count = data["sun_count"]
            game.level = data["level"]
            # 重建植物
            for plant_data in data["plants"]:
                plant_type = globals()[plant_data[0]]
                plant = plant_type(plant_data[1], plant_data[2])
                game.add_plant(plant)

4.3 音效与特效

增强游戏体验的细节处理:

class SoundManager:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.sounds = {}
        return cls._instance
        
    def load_sound(self, name, path):
        self.sounds[name] = pygame.mixer.Sound(path)
        
    def play(self, name):
        self.sounds[name].play()
        
# 使用示例
sound_mgr = SoundManager()
sound_mgr.load_sound("plant", "sounds/plant.wav")
sound_mgr.play("plant")

在开发过程中,我发现粒子特效能极大提升游戏质感。比如阳光收集效果:

class ParticleSystem:
    def __init__(self):
        self.particles = []
        
    def add_particles(self, x, y, color, count=20):
        for _ in range(count):
            self.particles.append({
                "x": x,
                "y": y,
                "vx": random.uniform(-2, 2),
                "vy": random.uniform(-5, -1),
                "life": 60,
                "color": color
            })
            
    def update(self):
        for p in self.particles[:]:
            p["x"] += p["vx"]
            p["y"] += p["vy"]
            p["life"] -= 1
            if p["life"] <= 0:
                self.particles.remove(p)
                
    def draw(self, surface):
        for p in self.particles:
            pygame.draw.circle(surface, p["color"], 
                              (int(p["x"]), int(p["y"])), 
                              max(1, int(p["life"]/10)))
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐