Simulating simple CRT and glitch effects in Pygame

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5168

    #1

    Simulating simple CRT and glitch effects in Pygame

    For my retro-style arcade shooter Planetoids, I wanted to give the visuals a nostalgic kick—like you were staring at a dusty CRT screen in an '80s arcade.


    I also wanted VHS-style glitch effects that make the screen feel alive—shaking, flickering, distorting—especially when something chaotic is happening in-game.


    In this post, I’ll walk through how I implemented all of that using plain Pygame.





    🔧 Overview: apply_crt_effect

    Here's the single entry point we use every frame:






    apply_crt_effect(screen, intensity="medium", pixelation="minimum")






    This applies multiple visual layers that can be configured through the settings screen:

    1. Scanlines
    2. Pixelation
    3. Flicker
    4. Glow
    5. VHS-style glitch effects (jitter, static, color separation)


    Let’s break each one down.



    1. Scanlines: Simulating CRT Lines




    def _apply_scanlines(screen):
    width, height = screen.get_size()
    scanline_surface = pygame.Surface((width, height), pygame.SRCALPHA)

    for y in range(0, height, 4):
    pygame.draw.line(scanline_surface, (0, 0, 0, 60), (0, y), (width, y))

    screen.blit(scanline_surface, (0, 0))






    This draws semi-transparent horizontal black lines across the screen, spaced every 4 pixels, simulating the horizontal scanlines seen on CRT monitors.
    • Want more intense lines? Decrease the spacing or increase alpha.
    • It adds an immediate analog vibe and subtly breaks up the digital sharpness.



    2. Pixelation: Deliberate Resolution Downgrade







    def _apply_pixelation(screen, pixelation):
    pixelation = {"minimum": 2, "medium": 4, "maximum": 6}.get(pixelation, 2)
    width, height = screen.get_size()
    small_surf = pygame.transform.scale(screen, (width // pixelation, height // pixelation))
    screen.blit(pygame.transform.scale(small_surf, (width, height)), (0, 0))






    This temporarily shrinks the screen resolution and scales it back up, creating a pixelated, low-res effect.
    • We use this after rendering everything else so the whole frame gets affected.
    • Makes the game feel more gritty and less digital-clean.



    3. Flicker: A Soft Glow Pulse




    def _apply_flicker(screen):
    if random.randint(0, 20) == 0:
    flicker_surface = pygame.Surface(screen.get_size(), pygame.SRCALPHA)
    flicker_surface.fill((255, 255, 255, 5))
    screen.blit(flicker_surface, (0, 0))






    Every so often (about 5% of frames), a subtle white overlay is applied.
    • This simulates the fluctuating brightness of a CRT bulb.
    • Super subtle, but it makes the screen feel alive, especially when combined with glow.



    4. Glow: Simulated Light Bleed




    def _apply_glow(screen):
    width, height = screen.get_size()
    glow_surf = pygame.transform.smoothscale(screen, (width // 2, height // 2))
    glow_surf = pygame.transform.smoothscale(glow_surf, (width, height))
    glow_surf.set_alpha(100)
    screen.blit(glow_surf, (0, 0))






    This creates a soft bloom/glow effect by:

    1. Downscaling the screen (blurring it),
    2. Upscaling it again,
    3. Layering it back on top.


    It's an easy way to fake blur and create that soft glow old CRTs had when they burned too bright.



    5. VHS Glitch: The Controlled Chaos

    This section ties everything together by adding movement, noise, and distortion.

    5.1 Horizontal Slice Glitches




    def _add_glitch_effect(height, width, glitch_surface, intensity):
    shift_amount = {"minimum": 10, "medium": 20, "maximum": 40}.get(intensity, 20)
    if random.random() 0.1:
    y_start = random.randint(0, height - 20)
    slice_height = random.randint(5, 20)
    offset = random.randint(-shift_amount, shift_amount)

    slice_area = pygame.Rect(0, y_start, width, slice_height)
    slice_copy = glitch_surface.subsurface(slice_area).copy()
    glitch_surface.blit(slice_copy, (offset, y_start))






    Random slices of the screen are offset left or right—mimicking VHS tracking issues or old screen flickers.
    • Triggered randomly per frame.
    • Intensity controls how far the lines can jump.



    5.2 RGB Color Separation




    def _add_color_separation(screen, glitch_surface, intensity):
    color_shift = {"minimum": 2, "medium": 6, "maximum": 10}.get(intensity, 4)
    if random.random() 0.05:
    for i in range(3):
    x_offset = random.randint(-color_shift, color_shift)
    y_offset = random.randint(-color_shift, color_shift)
    color_shift_surface = glitch_surface.copy()
    color_shift_surface.fill((0, 0, 0))
    color_shift_surface.blit(glitch_surface, (x_offset, y_offset))
    screen.blit(color_shift_surface, (0, 0), special_flags=pygame.BLEND_ADD)






    This simulates the separation of red, green, and blue channels for glitchy VHS color bleed.
    • Only triggers occasionally.
    • Intensity controls how far apart the color layers get.



    5.3 Rolling Static




    def _add_rolling_static(screen, height, width, intensity):
    static_chance = {"minimum": 0.1, "medium": 0.3, "maximum": 0.8}.get(intensity, 0.2)
    static_surface = pygame.Surface((width, height), pygame.SRCALPHA)

    for y in range(0, height, 8):
    if random.random() static_chance:
    pygame.draw.line(static_surface, (255, 255, 255, random.randint(30, 80)), (0, y), (width, y))

    screen.blit(static_surface, (0, 0), special_flags=pygame.BLEND_ADD)






    A final touch: horizontal white noise lines that appear and disappear in random rows.
    • Like old static interference rolling up the screen.
    • Controlled by intensity, which raises or lowers the frequency.



    🧠 Final Thoughts

    All of this runs in real time using Pygame’s surface tools.


    None of it requires shaders, OpenGL, or special GPU code and is a fairly lightweight approach for integrating into 2D games.


    These effects make Planetoids feel like an experience—not just a game. When the screen distorts after a power-up or flickers from a near-death explosion, you don’t just see it you feel it.



    🚀 Try It Out

    You can install Planetoids via pip:





    pip install planetoids-game






    then start right from the command line



    > planetoids









    chris-greening
    /
    planetoids


    Planetoids is a fast-paced, retro-inspired arcade space shooter built in Python with Pygame. Dodge asteroids and experience a vintage arcade feel with CRT effects, glitch animations, and pixel-perfect scaling.






    Planetoids: A Retro-Inspired Space Shooter









    What is it?



    Planetoids is a fast-paced, retro-inspired arcade space shooter built in Python with Pygame.

    Dodge asteroids and experience a vintage arcade feel with CRT effects, glitch animations, and pixel-perfect scaling.









    🪐 Key Features

    • Fast-paced asteroid destruction
    • Smooth FPS-independent physics
    • Retro CRT visual effects & glitch animations
    • Classic arcade gameplay mechanics
    • Power-ups and increasing difficulty
    • Pixel-perfect scaling for all screen sizes










    💾 Installation





    🔹 Install from PyPI (Recommended)




    The easiest way to install Planetoids is through pip:




    pip install planetoids-game




    Once installed, launch the game by running:




    planetoids





    🔹 Install from Source




    If you want the latest development version, you can install directly from GitHub:




    git clone https://github.com/chris-greening/planetoids-game.git
    cd planetoids-game
    pip install -e .



    Then, start the game with:



    planetoids





    🚀 Running the Game




    Once installed, you can start Planetoids in one of the following ways:



    🔹 Run from Source




    If…









    View on GitHub









    Follow along for more devlogs, insights, and behind-the-scenes experiments.


    Conclusion

    Thanks so much for reading and if you liked my content, be sure to check out some of my other work or connect with me on social media or my personal website 😄






    Chris Greening - Software Developer


    Hey! My name's Chris Greening and I'm a software developer from the New York metro area with a diverse range of engineering experience - beam me a message and let's build something great!




    christophergreening.com








    Cheers!












    Draw beautiful geometric visualizations with Python and Spyrograph

    Chris Greening ・ Mar 12 '23

    #python
    #math
    #opensource
    #showdev


















    I added a JavaScript arcade game to my portfolio's homepage

    Chris Greening ・ Jun 8 '21

    #showdev
    #javascript
    #webdev
    #blogging











    More...
Working...