Game Development 11 min read

Building a Python Plants vs Zombies Clone: Image Loading & Sprite Animation

This article walks through creating a functional Python clone of the classic Plants vs Zombies game, detailing supported plant and zombie types, level data storage, image resource organization, and the implementation of graphics loading, frame handling, state management, and animation logic using Pygame.

Python Programming Learning Circle
Python Programming Learning Circle
Python Programming Learning Circle
Building a Python Plants vs Zombies Clone: Image Loading & Sprite Animation

Motivated by the popularity of the original Plants vs Zombies game, the author created a Python clone using Pygame, downloading limited graphic assets from GitHub and implementing a subset of plants and zombies.

Supported plant types include Sunflower, Peashooter, Snow Pea, Walnut, Cherry Bomb, with added Double Peashooter and Triple Peashooter. Supported zombie types include Normal, Conehead, Buckethead, and others, with level data stored in JSON and a lawn mower added.

Below are screenshots of the game.

Image Switching

The article explains how to switch images for different zombie states, using the Conehead zombie as an example with seven image types: walking with helmet, attacking with helmet, walking without helmet, attacking without helmet, headless walking, headless attacking, and death.

Image Loading

Each action of a graphic type is stored as a separate image file. The load_all_gfx function traverses the resources/graphics directory, distinguishing between single images and subfolders that contain a series of frames.

<code>def load_all_gfx(directory, colorkey=c.WHITE, accept=('.png', '.jpg', '.bmp', '.gif')):
    graphics = {}
    for name1 in os.listdir(directory):
        dir1 = os.path.join(directory, name1)
        if os.path.isdir(dir1):
            for name2 in os.listdir(dir1):
                dir2 = os.path.join(dir1, name2)
                if os.path.isdir(dir2):
                    for name3 in os.listdir(dir2):
                        dir3 = os.path.join(dir2, name3)
                        if os.path.isdir(dir3):
                            image_name, _ = os.path.splitext(name3)
                            graphics[image_name] = load_image_frames(dir3, image_name, colorkey, accept)
                        else:
                            image_name, _ = os.path.splitext(name2)
                            graphics[image_name] = load_image_frames(dir2, image_name, colorkey, accept)
                            break
                else:
                    name, ext = os.path.splitext(name2)
                    if ext.lower() in accept:
                        img = pg.image.load(dir2)
                        if img.get_alpha():
                            img = img.convert_alpha()
                        else:
                            img = img.convert()
                        img.set_colorkey(colorkey)
                        graphics[name] = img
    return graphics

GFX = load_all_gfx(os.path.join("resources","graphics"))</code>

The load_image_frames function groups images by the numeric index in their filenames and returns an ordered list of frames.

<code>def load_image_frames(directory, image_name, colorkey, accept):
    frame_list = []
    tmp = {}
    index_start = len(image_name) + 1
    frame_num = 0
    for pic in os.listdir(directory):
        name, ext = os.path.splitext(pic)
        if ext.lower() in accept:
            index = int(name[index_start:])
            img = pg.image.load(os.path.join(directory, pic))
            if img.get_alpha():
                img = img.convert_alpha()
            else:
                img = img.convert()
                img.set_colorkey(colorkey)
            tmp[index] = img
            frame_num += 1
    for i in range(frame_num):
        frame_list.append(tmp[i])
    return frame_list</code>

Image Display Switching

In source/component/zombie.py , the Zombie base class loads all supported images via loadImages , sets the sprite’s image and rect , and provides methods for state changes and animation.

<code>class Zombie(pg.sprite.Sprite):
    def __init__(self, x, y, name, health, head_group=None, damage=1):
        pg.sprite.Sprite.__init__(self)
        self.name = name
        self.frames = []
        self.frame_index = 0
        self.loadImages()
        self.frame_num = len(self.frames)
        self.image = self.frames[self.frame_index]
        self.rect = self.image.get_rect()
        self.rect.centerx = x
        self.rect.bottom = y

    def loadFrames(self, frames, name, image_x):
        frame_list = tool.GFX[name]
        rect = frame_list[0].get_rect()
        width, height = rect.w, rect.h
        width -= image_x
        for frame in frame_list:
            frames.append(tool.get_image(frame, image_x, 0, width, height))
</code>

The update , handleState , and animation methods drive the zombie’s behavior each tick, while setWalk , setAttack , setDie , and changeFrames adjust the current state and reload the appropriate frame set.

<code>def setWalk(self):
    self.state = c.WALK
    self.animate_interval = 150
    if self.helmet:
        self.changeFrames(self.helmet_walk_frames)
    elif self.losHead:
        self.changeFrames(self.losthead_walk_frames)
    else:
        self.changeFrames(self.walk_frames)

def setAttack(self, plant):
    self.plant = plant
    self.state = c.ATTACK
    self.animate_interval = 100
    if self.helmet:
        self.changeFrames(self.helmet_attack_frames)
    elif self.losHead:
        self.changeFrames(self.losthead_attack_frames)
    else:
        self.changeFrames(self.attack_frames)

def setDie(self):
    self.state = c.DIE
    self.animate_interval = 200
    self.changeFrames(self.die_frames)

def changeFrames(self, frames):
    '''change image frames and modify rect position'''
    self.frames = frames
    self.frame_num = len(self.frames)
    self.frame_index = 0
    bottom = self.rect.bottom
    centerx = self.rect.centerx
    self.image = self.frames[self.frame_index]
    self.rect = self.image.get_rect()
    self.rect.bottom = bottom
    self.rect.centerx = centerx
</code>

The ConeHeadZombie subclass implements loadImages to populate the various frame lists for helmet‑walk, helmet‑attack, normal walk, etc., using the naming conventions defined in the graphics folder.

<code>class ConeHeadZombie(Zombie):
    def __init__(self, x, y, head_group):
        Zombie.__init__(self, x, y, c.CONEHEAD_ZOMBIE, c.CONEHEAD_HEALTH, head_group)
        self.helmet = True

    def loadImages(self):
        self.helmet_walk_frames = []
        self.helmet_attack_frames = []
        self.walk_frames = []
        self.attack_frames = []
        self.losthead_walk_frames = []
        self.losthead_attack_frames = []
        self.die_frames = []

        helmet_walk_name = self.name
        helmet_attack_name = self.name + 'Attack'
        walk_name = c.NORMAL_ZOMBIE
        attack_name = c.NORMAL_ZOMBIE + 'Attack'
        losthead_walk_name = c.NORMAL_ZOMBIE + 'LostHead'
        losthead_attack_name = c.NORMAL_ZOMBIE + 'LostHeadAttack'
        die_name = c.NORMAL_ZOMBIE + 'Die'

        frame_list = [self.helmet_walk_frames, self.helmet_attack_frames,
                      self.walk_frames, self.attack_frames,
                      self.losthead_walk_frames, self.losthead_attack_frames,
                      self.die_frames]
        name_list = [helmet_walk_name, helmet_attack_name,
                     walk_name, attack_name,
                     losthead_walk_name, losthead_attack_name,
                     die_name]

        for i, name in enumerate(name_list):
            self.loadFrames(frame_list[i], name, tool.ZOMBIE_RECT[name]['x'])
        self.frames = self.helmet_walk_frames
</code>

The full source code can be downloaded from https://download.csdn.net/download/marble_xu/11639414.

PythonGame developmentImage LoadingPygameSprite Animation
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.