Game Development 14 min read

Python Plants vs Zombies Game Development Tutorial with Pygame

This article provides a step‑by‑step Python tutorial for building a simplified Plants vs Zombies clone using Pygame, covering module imports, image handling, map and plant classes, bullet mechanics, zombie behavior, and the main game loop with event handling.

Python Programming Learning Circle
Python Programming Learning Circle
Python Programming Learning Circle
Python Plants vs Zombies Game Development Tutorial with Pygame

This tutorial walks through creating a basic "Plants vs Zombies" style game using Python and the Pygame library. It explains each component of the game, from importing modules to handling the main game loop.

1. Import required modules

<code>import pygame
import random</code>

2. Configure image path

<code>IMAGE_PATH = 'imgs/'</code>

3. Set screen dimensions

<code>scrrr_width = 800
scrrr_height = 560</code>

4. Game‑over flag

<code>GAMEOVER = False</code>

5. Logging helper for image loading errors

<code>LOG = '文件:{}中的方法:{}出错'.format(__file__, __name__)</code>

6. Map class – stores map tile images and whether a plant can be placed.

<code>class Map():
    def __init__(self, x, y, img_index):
        self.image = pygame.image.load(Map.map_names_list[img_index])
        self.position = (x, y)
        self.can_grow = True</code>

7. Plant base class – common attributes for all plants.

<code>class Plant(pygame.sprite.Sprite):
    def __init__(self):
        super(Plant, self).__init__()
        self.live = True</code>

8. Sunflower class – generates money (sun) over time.

<code>class Sunflower(Plant):
    def __init__(self, x, y):
        super(Sunflower, self).__init__()
        self.image = pygame.image.load('imgs/sunflower.png')
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.price = 50
        self.hp = 100
        self.time_count = 0
    def produce_money(self):
        self.time_count += 1
        if self.time_count == 25:
            MainGame.money += 5
            self.time_count = 0</code>

9. PeaShooter class – shoots peas at zombies.

<code>class PeaShooter(Plant):
    def __init__(self, x, y):
        super(PeaShooter, self).__init__()
        self.image = pygame.image.load('imgs/peashooter.png')
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.price = 50
        self.hp = 200
        self.shot_count = 0
    def shot(self):
        # determine if a zombie is in line and fire
        ...</code>

10. PeaBullet class – handles bullet movement and collision with zombies.

<code>class PeaBullet(pygame.sprite.Sprite):
    def __init__(self, peashooter):
        self.live = True
        self.image = pygame.image.load('imgs/peabullet.png')
        self.damage = 50
        self.speed = 10
        self.rect = self.image.get_rect()
        self.rect.x = peashooter.rect.x + 60
        self.rect.y = peashooter.rect.y + 15
    def move_bullet(self):
        if self.rect.x < scrrr_width:
            self.rect.x += self.speed
        else:
            self.live = False
    def hit_zombie(self):
        for zombie in MainGame.zombie_list:
            if pygame.sprite.collide_rect(self, zombie):
                self.live = False
                zombie.hp -= self.damage
                if zombie.hp <= 0:
                    zombie.live = False
                    self.nextLevel()
    def nextLevel(self):
        MainGame.score += 20
        MainGame.remnant_score -= 20
        ...</code>

11. Zombie class – moves leftward, attacks plants, and triggers game over if it reaches the left edge.

<code>class Zombie(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super(Zombie, self).__init__()
        self.image = pygame.image.load('imgs/zombie.png')
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.hp = 1000
        self.damage = 2
        self.speed = 1
        self.live = True
        self.stop = False
    def move_zombie(self):
        if self.live and not self.stop:
            self.rect.x -= self.speed
            if self.rect.x < -80:
                MainGame().gameOver()
    def hit_plant(self):
        for plant in MainGame.plants_list:
            if pygame.sprite.collide_rect(self, plant):
                self.stop = True
                self.eat_plant(plant)
    def eat_plant(self, plant):
        plant.hp -= self.damage
        if plant.hp <= 0:
            ...
                self.stop = False</code>

12. MainGame class – orchestrates window creation, map initialization, plant and zombie management, event handling, and the game loop.

<code>class MainGame():
    shaoguan = 1
    score = 0
    remnant_score = 100
    money = 200
    map_points_list = []
    map_list = []
    plants_list = []
    peabullet_list = []
    zombie_list = []
    count_zombie = 0
    produce_zombie = 100
    def init_window(self):
        pygame.display.init()
        MainGame.window = pygame.display.set_mode([scrrr_width, scrrr_height])
    def draw_text(self, content, size, color):
        pygame.font.init()
        font = pygame.font.SysFont('kaiti', size)
        return font.render(content, True, color)
    def load_help_text(self):
        text1 = self.draw_text('1.按左键创建向日葵 2.按右键创建豌豆射手', 26, (255,0,0))
        MainGame.window.blit(text1, (5,5))
    def init_plant_points(self):
        for y in range(1,7):
            points = []
            for x in range(10):
                points.append((x,y))
            MainGame.map_points_list.append(points)
    def init_map(self):
        for points in MainGame.map_points_list:
            temp_map_list = []
            for point in points:
                if (point[0] + point[1]) % 2 == 0:
                    map = Map(point[0]*80, point[1]*80, 0)
                else:
                    map = Map(point[0]*80, point[1]*80, 1)
                temp_map_list.append(map)
            MainGame.map_list.append(temp_map_list)
    def load_map(self):
        for temp_map_list in MainGame.map_list:
            for map in temp_map_list:
                map.load_map()
    def load_plants(self):
        for plant in MainGame.plants_list:
            if plant.live:
                if isinstance(plant, Sunflower):
                    plant.display_sunflower()
                    plant.produce_money()
                elif isinstance(plant, PeaShooter):
                    plant.display_peashooter()
                    plant.shot()
            else:
                MainGame.plants_list.remove(plant)
    def load_peabullets(self):
        for b in MainGame.peabullet_list:
            if b.live:
                b.display_peabullet()
                b.move_bullet()
                b.hit_zombie()
            else:
                MainGame.peabullet_list.remove(b)
    def deal_events(self):
        for e in pygame.event.get():
            if e.type == pygame.QUIT:
                self.gameOver()
            elif e.type == pygame.MOUSEBUTTONDOWN:
                x = e.pos[0] // 80
                y = e.pos[1] // 80
                map = MainGame.map_list[y-1][x]
                if e.button == 1 and map.can_grow and MainGame.money >= 50:
                    sunflower = Sunflower(map.position[0], map.position[1])
                    MainGame.plants_list.append(sunflower)
                    map.can_grow = False
                    MainGame.money -= 50
                elif e.button == 3 and map.can_grow and MainGame.money >= 50:
                    peashooter = PeaShooter(map.position[0], map.position[1])
                    MainGame.plants_list.append(peashooter)
                    map.can_grow = False
                    MainGame.money -= 50
    def init_zombies(self):
        for i in range(1,7):
            dis = random.randint(1,5) * 200
            zombie = Zombie(800 + dis, i * 80)
            MainGame.zombie_list.append(zombie)
    def load_zombies(self):
        for zombie in MainGame.zombie_list:
            if zombie.live:
                zombie.display_zombie()
                zombie.move_zombie()
                zombie.hit_plant()
            else:
                MainGame.zombie_list.remove(zombie)
    def start_game(self):
        self.init_window()
        self.init_plant_points()
        self.init_map()
        self.init_zombies()
        while not GAMEOVER:
            MainGame.window.fill((255,255,255))
            MainGame.window.blit(self.draw_text('当前钱数$: {}'.format(MainGame.money), 26, (255,0,0)), (500,40))
            MainGame.window.blit(self.draw_text('当前关数{},得分{},距离下关还差{}分'.format(MainGame.shaoguan, MainGame.score, MainGame.remnant_score), 26, (255,0,0)), (5,40))
            self.load_help_text()
            self.load_map()
            self.load_plants()
            self.load_peabullets()
            self.deal_events()
            self.load_zombies()
            MainGame.count_zombie += 1
            if MainGame.count_zombie == MainGame.produce_zombie:
                self.init_zombies()
                MainGame.count_zombie = 0
            pygame.time.wait(10)
            pygame.display.update()
    def gameOver(self):
        MainGame.window.blit(self.draw_text('游戏结束', 50, (255,0,0)), (300,200))
        pygame.time.wait(400)
        global GAMEOVER
        GAMEOVER = True
if __name__ == '__main__':
    game = MainGame()
    game.start_game()</code>

The tutorial ends with a screenshot of the finished game and promotional material encouraging readers to follow the public account for more Python resources.

PythonGame developmentPygamePlants vs Zombies
Python Programming Learning Circle
Written by

Python Programming Learning Circle

A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.