Making snow and rain effects with pygame and particle

version 1
version 2

Let’s dive into the code to make particles that we’ve see in the last post.

I made some changes to the code, as you can see comparing the two versions.

The list with the parameters for the snow flakes

I’ve put the list for the particles (the parameters of the particles, that are the circles that will rapresent the flakes) into this class

# pygame.math module
# https://www.pygame.org/docs/ref/math.html
#
# Pygame swap text with another text
# https://stackoverflow.com/questions/60944070/pygame-swap-text-with-another-text/60953697#60953697
#
# GitHub - PyGameExamplesAndAnswers - Draw 2D - list_of_particles
# https://github.com/Rabbid76/PyGameExamplesAndAnswers/blob/master/documentation/pygame/pygame_2D.md

import pygame
import random
import sys


pygame.mixer.init()
jingle = pygame.mixer.music.load("snow.mp3")
class Particle:
    def __init__(self):
        "Setting up screen, clock and loading images"

        pygame.init()
        self.screen = pygame.display.set_mode((800, 600))
        self.frame_rate = pygame.time.Clock().tick
        self.snow = pygame.image.load("snow6.png").convert()
        self.cloud = pygame.image.load("cloud.png")
        self.window2 = pygame.image.load("snow5.png")
        self.list_of_particles = []

    def update(self):
        "How to quit the window"
        self.frame_rate(60)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.run = False
        self.images_display()

    def images_display(self):
        "Make snow fall down - blitting images and calling the flake generator"

        self.screen.blit(self.snow, (0,0))
        self.snow_flakes_generator()
        self.screen.blit(self.cloud, (-110,-100))
        self.screen.blit(self.cloud, (-50, 250))
        self.screen.blit(self.cloud, (450, 250))
        self.screen.blit(self.window2, (0, 0))
        pygame.display.flip()

    def mainloop(self):
        "A call to the self.update method to run stuffs"
        pygame.mixer.music.play()
        self.run = True
        while self.run:
            run = self.update()
        pygame.quit()
        exit()

    # the surface with the snow as a background


    def generate_one_particle(self):
        starting_pos = [random_pos(), 50]
        # direction of x and next y pos incremented of 0.01
        move_pos = [random.randint(0, 20) / 10 - 1, 2]
        radius = random.randint(4, 6)
        return [starting_pos, move_pos, radius]



    def snow_flakes_generator(self):
        "circles move from random start position at the top until bottom and the disappear"
        
        self.list_of_particles.append(self.generate_one_particle())

        # Every particle  moves... if list_of_particles[2] (the radius) is >= than 0 it is removed
        for particle in self.list_of_particles[:]:
            
            # parameters for new position (aka speed) and shrinking
            direction = particle[1][0]      # random direction fixed
            velocity = particle[1][1]  # get the next position on the y axes
            gravity = 0.01            # how fast it falls
            melting = 0.005         # how fast it shrinks

            # the starting point changes going right or left
            particle[0][0] += direction # increase the x position of an angle (to the right or left)
            particle[0][1] +=  velocity # the y movement going down speed fixed 2
                                            # but it is increased of 0.01 down here, so it goes down at the same speed
            particle[2] -= melting # the snowflake shriks
            particle[1][1] += gravity # * random.randint(-3, 6) # increase down speed a bit
            if particle[2] <= 0: # when the radius is 0 it is removed, so we do not see it anymore and it's not in the memory
                self.list_of_particles.remove(particle)

        # draws a circle on the screen white, at x y corrds and with a ray of particle[2]
        for particle in self.list_of_particles:
            pos = particle[0][0]
            speed = particle[0][1]
            radius = particle[2]
            # circle: surface, color, pos, radius
            pygame.draw.circle(
            	self.screen,
            	(255, 244, 255), # color
            	(
            		round(pos), # Pos x, y
            		round(speed)), # direction / speed
            		round(radius)) # radius
def random_pos():
	" to make a flame like list_of_particles [150, 20] # flame, so that all circles with start at the same point"
	return random.randint(30, 8000)
	# return 150



fx = Particle()
fx.mainloop()

In this list we will have:

  • a first list with x and y intergers for the position on the screen of the snow flakes
  • a second list with the angle of the direction of the snow flake and the new pos towards down
  • the size of the radius (when 0 the particle parameters will be deleted)

so it will be like (for just one particle)…

list_of_particle = [[20, 0], [0.5, 2], 4]

The first two [2, 0], is the original position on top of the screen (the 20 is randomly generated)

The second two [0.5, 2] is the angle addedd each time (so it will go to left or right randomly) and the falling speed that will be increased of 0.01 each time, so every flake falls at the same speed, but at a different angle

The third [4] is the random radius among 4 to 6, that will be shrinked each time and when o it will be deleted.

Let’s see it in the code.

Initial position of the snow flake

Let it snow… and rain

This is an improved version of the code, that now has also rain together with the snow.

A 60 seconds video with the particles effect for snow and rain of the following code in python using pygame
import pygame
import random
import sys


pygame.mixer.init()
pygame.mixer.music.load("snow.mp3")
FPS = 30


class Particle:
    "Makes particles to appear on the screen"

    def __init__(self):
        "Setting up screen, clock and loading images"

        pygame.init()
        
        # the two main surfaces
        self.screen = pygame.display.set_mode((800, 600), pygame.RESIZABLE)
        self.new_surface = pygame.Surface((800, 600))

        self.frame_rate = pygame.time.Clock().tick
        # loading images for the script
        self.bg = pygame.image.load("snow6.png").convert()
        self.cloud = pygame.image.load("cloud.png")
        self.fg = pygame.image.load("snow7.png")
        self.snow_flakes_list = []
        self.rain_drops_list = []
        self.resize = 0

    def update(self):
        "How to quit the window"
        self.frame_rate(FPS)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.run = False
            if event.type == pygame.VIDEORESIZE:
                self.resize = 1
                self.w, self.h = event.w, event.h
        self.images_display()

    def images_display(self):
        "Make snow fall down - blitting images and calling the flake generator"
        self.new_surface.blit(self.bg, (0, 80))
        self.snow_flakes_generator()
        # self.new_surface.blit(self.cloud, (-110,-100))
        self.new_surface.blit(self.cloud, (-50, 290))
        self.new_surface.blit(self.cloud, (450, 290))
        self.new_surface.blit(self.fg, (0, 0))
        if self.resize:
            self.screen.blit(pygame.transform.scale(self.new_surface, (self.w, self.h)), (0, 0))
        else:
            self.screen.blit(self.new_surface, (0, 0))
        pygame.display.flip()

    def mainloop(self):
        "A call to the self.update method to run stuffs"
        
        # Start the mp3 loaded at the beginning
        pygame.mixer.music.play()
        self.run = True
        while self.run:
            run = self.update()
        pygame.quit()
        exit()

    # the surface with the snow as a background

    def generate_one_particle(self, dim):
        direction = [random_pos(), random.randint(30,50)]
        # direction of x and next y pos incremented of 0.01
        # change the 2 to change the way the snow flake falls
        angle_vel = [random.randint(0, 20) / 10 - 1, 2]
        radius = random.randint(4, 6)
        if dim == 4:
            angle_vel = [0, 6]
            radius = 1
        return [direction, angle_vel, radius]

    def snow_flakes_generator(self):
        "circles move from random start position at the top until bottom and the disappear"
        self.snow()
        self.rain()

    def snow(self):
        self.snow_flakes_list.append(self.generate_one_particle(6))
        self.falling_elements(self.snow_flakes_list)
        
    def rain(self):
        for drop in range(15): # 5 drops for frame
            self.rain_drops_list.append(self.generate_one_particle(4))

        self.falling_elements(self.rain_drops_list)

    def falling_elements(self, elements_list):

        # Every particle  moves... if snow_flakes_list[2] (the radius) is >= than 0 it is removed
        for particle in elements_list[:]:
            
            # parameters for new position (aka speed) and shrinking
            direction = particle[1][0]      # random direction fixed
            velocity = particle[1][1]  # how fast the flake falls
            gravity = 0.01            # how fast it falls
            melting = 0.005         # how fast it shrinks

            # DIRECTION
            particle[0][0] += direction # increase the x position of an angle (to the right or left)
            particle[0][1] +=  velocity # the y movement going down speed fixed 2
                                            # but it is increased of 0.01 down here, so it goes down at the same speed
            particle[2] -= melting # the snowflake shriks
            particle[1][1] += gravity # * random.randint(-3, 6) # increase down speed a bit
            if particle[0][1] > 330: # when the radius is 0 it is removed, so we do not see it anymore and it's not in the memory
                elements_list.remove(particle)

        # draws a circle on the screen white, at x y corrds and with a ray of particle[2]
        for particle in elements_list:
            pos = particle[0][0]
            speed = particle[0][1]
            radius = particle[2]
            # circle: surface, color, pos, radius
            pygame.draw.circle(
            	self.new_surface,
            	(255, 255, 255), # color
            	(
            		round(pos), # Pos x, y
            		round(speed)), # direction / speed
            		round(radius)) # radius
def random_pos():
	" to make a flame like snow_flakes_list [150, 20] # flame, so that all circles with start at the same point"
	return random.randint(30, 800)
	# return 150



fx = Particle()
fx.mainloop()

Github repository for particles effect with assets

I added also sounds. Download this script and the assets here.

Another article on particles.


Subscribe to the newsletter for updates
Tkinter templates
Avatar My youtube channel

Twitter: @pythonprogrammi - python_pygame

Videos

Speech recognition game

Pygame's Platform Game

Other Pygame's posts

Published by pythonprogramming

Started with basic on the spectrum, loved javascript in the 90ies and python in the 2000, now I am back with python, still making some javascript stuff when needed.