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.
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.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.