Pac-Man led matrix

Build a scrolling Pac-Man on a 8-panel OzzMaker LED strip

In this guide you’ll build the Pac-Man chase animation shown on the 16×16 RGB LED matrix panel product page: Pac-Man and the four classic ghosts running across a long horizontal strip of 8 panels chained end-to-end (128 × 16 pixels), driven by a Raspberry Pi.

It’s a great first project for the panels because it covers everything you need for bigger displays later — chaining, managing power, the driver library, and sprite-based animation in Python.

https://youtu.be/mdd0pqUy4kg

 

What you’ll build

A 8-panel horizontal strip — 128 pixels wide, 16 pixels tall — showing Pac-Man chasing the four ghosts (Blinky, Pinky, Inky and Clyde) across the display. Animation runs at around 25 FPS and loops forever.

 

Bill of materials

  • 8 × OzzMaker 16×16 RGB LED Matrix Panel (each ships with one FFC cable)
  • 1 × OzzMaker RGB LED Matrix Connector for Raspberry Pi
  • 1 × Raspberry Pi 4, Pi 3, Pi Zero 2 W, or Pi Zero
  • 1 × 5 V 3 A power supply
  • A microSD card with Raspberry Pi OS

Assemble hardware

1. Fit the connector to the Pi

Power off the Pi. Sit the OzzMaker LED connector down onto the 40-pin GPIO header so it covers all 40 pins. The connector breaks out the unused GPIO and includes a QWIIC socket for I²C.

2. Chain the panels

Each panel has an IN and OUT FFC port. The arrow on the back of the PCB shows the direction data flows. Plug an FFC cable from the connector on the Pi into the IN port of panel 1, then from OUT of panel 1 to IN of panel 2, and so on, all the way to panel 8.

A common mistake: getting one of the FFC cables in backwards or 180° rotated. The contacts on the cable should always face the same side of the connector latch. If a panel half-way down the chain shows garbage, that’s almost always the cause.

3. Power

The FFC cable carries 5 V and GND alongside the data lines and you should be able to run this animation without needing a separate power supply for some of the LED panels as most of the LEDs are off during the animation. If you see flickering or garbage towards the end of the chain, this would most likely mean the power supply powering your Pi and LED panels isn’t reliable or powerful enough. From the panel before you see the problem, feed 5 V and GND directly to the 5V and GND pads on the back of panel 7. Run those wires from the same supply that’s powering the Pi (or a second 5 V supply with a common ground — never two supplies without a common ground).

If you’re using a second supply, cut the trace between the green-highlighted pads on the back of panel where you have connected the second power supply. See the LED matrix overview guide for the pad locations.

4. Soldering panels together

OzzMaker RGB LED matrix
Back of panels

Before permanently soldering the panels together for a rigid display, you should run a test to make sure everything is connect properly. Once it is confirmed working, you can now begin soldering. First place some solder on the pads near there edge of the panels, then aligned them together and place more solder on the two pads until the solder flows across both pads, as show in the image below.

Software setup

1. Install libraries

Follow this guide to get hzeller’s library installed which will be used to run the RGB LED panels.

2.  Download code

You can download the pacman demo from our github repositry

pi@raspberrypi ~ $ git clone https://github.com/ozzmaker/ozzMaker-LED-matrix

cd into the pacman folder and run the script

pi@raspberrypi ~ $ cd ozzMaker-LED-matrix
pi@raspberrypi ~ $ cd pacman
pi@raspberrypi ~ $ sudo python pacman.py

Below is the main python script, which is well document and easy to follow.

#!/usr/bin/env python
from rgbmatrix import RGBMatrix, RGBMatrixOptions, graphics

#from samplebase import SampleBase
import time
import math
import colorsys
from PIL import Image



options = RGBMatrixOptions()
options.rows = 16
options.cols = 16
options.chain_length = 10
options.gpio_slowdown = 2
options.brightness = 30
matrix = RGBMatrix(options = options)
def clearMatrix():
    for x in range(matrix.width):
        for y in range( matrix.height):
            double_buffer.SetPixel(x, y, 0,0,0)
from PIL import Image
import time
# Define constants
MATRIX_WIDTH = 160
MATRIX_HEIGHT = 16
SPRITE_WIDTH = 16  # Assuming each sprite is 16 pixels wide
PACMAN_WIDTH = 16  # Assuming Pacman is also 16 pixels wide
GAP = 8  # Gap between ghosts
INITIAL_PACMAN_GAP = 80  # Initial gap between Pacman and the first ghost
FINAL_PACMAN_GAP = -SPRITE_WIDTH  # Final gap where ghosts overlap Pacman
ANIMATION_DELAY = 0.03  # Delay between frames in seconds
PACMAN_DIES_LOCATION = 130 
PACMAN_DIES_DELAY = 0.07  # Delay between frames in the Pacman death animation
# Load sprite images
ghostRed1 = Image.open('ghostRed1.ppm').convert('RGB')
ghostRed2 = Image.open('ghostRed2.ppm').convert('RGB')
ghostPink1 = Image.open('ghostPink1.ppm').convert('RGB')
ghostPink2 = Image.open('ghostPink2.ppm').convert('RGB')
ghostLightBlue1 = Image.open('ghostLightBlue1.ppm').convert('RGB')
ghostLightBlue2 = Image.open('ghostLightBlue2.ppm').convert('RGB')
ghostOrange1 = Image.open('ghostOrange1.ppm').convert('RGB')
ghostOrange2 = Image.open('ghostOrange2.ppm').convert('RGB')
# Load Pacman images
pacman1 = Image.open('pacman1.ppm').convert('RGB')
pacman2 = Image.open('pacman2.ppm').convert('RGB')
pacman3 = Image.open('pacman3.ppm').convert('RGB')
# Load PacmanDies images
pacmanDies = [
    Image.open(f'pacmanDies{i+1}.ppm').convert('RGB') for i in range(11)
]
# Initialize double buffer and matrix
double_buffer = matrix.CreateFrameCanvas()
# List of sprites, their positions, and individual frame counters
sprites = [
    {'images': [pacman1, pacman2, pacman3], 'x': -PACMAN_WIDTH, 'frame': 0, 'switch_rate': 4},
    {'images': [ghostRed1, ghostRed2], 'x': -PACMAN_WIDTH - INITIAL_PACMAN_GAP - SPRITE_WIDTH, 'frame': 0, 'switch_rate': 5},
    {'images': [ghostPink1, ghostPink2], 'x': -PACMAN_WIDTH - INITIAL_PACMAN_GAP - 2*(SPRITE_WIDTH + GAP), 'frame': 0, 'switch_rate': 7},
    {'images': [ghostLightBlue1, ghostLightBlue2], 'x': -PACMAN_WIDTH - INITIAL_PACMAN_GAP - 3*(SPRITE_WIDTH + GAP), 'frame': 0, 'switch_rate': 9},
    {'images': [ghostOrange1, ghostOrange2], 'x': -PACMAN_WIDTH - INITIAL_PACMAN_GAP - 4*(SPRITE_WIDTH + GAP), 'frame': 0, 'switch_rate': 11}
]
# Function to play the Pacman death animation
def play_pacman_dies(x_position):
    for image in pacmanDies:
        double_buffer.Clear()
        double_buffer.SetImage(image, x_position)
        matrix.SwapOnVSync(double_buffer)
        time.sleep(PACMAN_DIES_DELAY)
# Main animation loop
while True:
    double_buffer.Clear()
    # Calculate the current gap between Pacman and the first ghost based on Pacman's position
    pacman_position = sprites[0]['x']
    if pacman_position < PACMAN_DIES_LOCATION:
        # Linearly decrease the gap as Pacman approaches 2/3 of the way across the matrix
        current_gap = INITIAL_PACMAN_GAP - (INITIAL_PACMAN_GAP - FINAL_PACMAN_GAP) * (pacman_position / PACMAN_DIES_LOCATION)
    else:
        # Once Pacman is beyond 2/3, the gap is at its minimum (overlap)
        current_gap = FINAL_PACMAN_GAP
    # Update positions of the ghosts based on the current gap
    sprites[1]['x'] = sprites[0]['x'] - current_gap - SPRITE_WIDTH
    sprites[2]['x'] = sprites[1]['x'] - (SPRITE_WIDTH + GAP)
    sprites[3]['x'] = sprites[2]['x'] - (SPRITE_WIDTH + GAP)
    sprites[4]['x'] = sprites[3]['x'] - (SPRITE_WIDTH + GAP)
    # Check if the red ghost catches up to Pacman
    if sprites[1]['x'] >= pacman_position - 2:
        play_pacman_dies(pacman_position)
        break
    for sprite in sprites:
        # Update the frame counter for each sprite
        sprite['frame'] += 1
        
        # Switch between the two images for ghosts or three images for Pacman based on the individual sprite's frame counter
        image = sprite['images'][(sprite['frame'] // sprite['switch_rate']) % len(sprite['images'])]
        
        # Draw the sprite at the current position
        double_buffer.SetImage(image, sprite['x'])
        
        # Move sprite to the right
        sprite['x'] += 1
        
        # Reset sprite position when it moves out of the right side of the matrix
        if sprite['x'] > MATRIX_WIDTH:
            # Reset positions, with Pacman leading and ghosts following with the initial gap
            sprite['x'] = -PACMAN_WIDTH - INITIAL_PACMAN_GAP - (SPRITE_WIDTH + GAP) * (sprites.index(sprite) - 1)
        
    # Swap the buffers to display the new frame
    matrix.SwapOnVSync(double_buffer)
    
    # Wait before the next frame
    time.sleep(ANIMATION_DELAY)

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.