Примеры анимаций на основе системы частиц


gif
gif
gif
gif
gif
gif
gif
gif
gif
gif
gif
gif
gif

Установка библиотеки

# В терминале
pip install drawzero --upgrade --user
# На macOs — pip3
Либо запустить программу:
import os, sys
python = sys.executable
user = '--user' if 'venv' not in python else ''
cmd = f'"{python}" -m pip install drawzero --upgrade {user}'
print(cmd)
os.system(cmd)

Примеры кода

gif
Код
from drawzero import *
from random import randint
from dataclasses import dataclass

NUM_STARS = 300


@dataclass
class Star:
    x: float
    y: float
    z: float
    r: int
    color: tuple


def gen_star():
    '''Создать звезду'''
    x = randint(-1000000 // 2, 1000000 // 2)
    y = randint(1, 1000)
    z = randint(-1000000 // 2, 1000000 // 2)
    r = randint(10, 3000)
    color = (randint(0, 255), randint(0, 255), randint(0, 255))
    return Star(x, y, z, r, color)


def create_stars():
    '''Создать массив звёзд'''
    stars = []
    for i in range(NUM_STARS):
        stars.append(gen_star())
    return stars


def move_stars(stars, speed):
    '''Сдвинуть все звёзды
    Если звезда перестала попадать на экран, то заменяем её на новую'''
    Vx, Vy, Vz = speed
    for i, star in enumerate(stars):
        star.x += Vx
        star.y += Vy
        star.z += Vz
        if (
                not (1 < star.y < 1000)
                or not (-500 < star.x / star.y < 500)
                or not (-500 < star.x / star.y < 500)
        ):
            # Вообще это — так себе решение. Но частично работает
            stars[i] = gen_star()


def draw_stars(stars):
    '''Отрисовать все звёзды'''
    # Сортируем звёзды, чтобы те, которые ближе к экрану, отрисовывались позже
    stars.sort(key=lambda star: -star.y)
    for star in stars:
        y = star.y
        # Координаты должны быть целыми, это — требование pygame
        screen_x = int(500 + star.x / y)
        screen_y = int(500 + star.z / y)
        screen_r = int(star.r / y)
        filled_circle(star.color, (screen_x, screen_y), screen_r)
    text('white', 'Press WASD or QE to move', (300, 5), 48)


def process_keys(pressed_keys, speed):
    '''Обрабатываем нажатия клавиш
    Используем WASD для вверх/вниз/влево/вправо и QE для вперёд/назад'''
    if pressed_keys[K.UP] or pressed_keys[K.w]:
        speed[2] += 100
    if pressed_keys[K.DOWN] or pressed_keys[K.s]:
        speed[2] -= 100
    if pressed_keys[K.LEFT] or pressed_keys[K.a]:
        speed[0] += 100
    if pressed_keys[K.RIGHT] or pressed_keys[K.d]:
        speed[0] -= 100
    if pressed_keys[K.q]:
        speed[1] -= 1
    if pressed_keys[K.e]:
        speed[1] += 1


# Здесь ставим размер экрана
stars = create_stars()
# Текущая скорость, стартуем с 0
speed = [0, 0, 0]
while True:
    # Заливаем всё чёрным
    fill((0, 0, 0))
    # Обрабатываем нажатия клавиш
    process_keys(get_keys_pressed(), speed)
    # Двигаем звёзды
    move_stars(stars, speed)
    # Рисуем звёзды
    draw_stars(stars)
    # Ждём 1/60 секунды
    tick()
gif
Код
from drawzero import *
import random
import math
from dataclasses import dataclass

START = START_X, START_Y = (500, 200)
G = -2.0
START_SPEED = 20
STICK_WIDTH = 5
TOP = 600
SAFE = 200
GLOW = 15


@dataclass
class Particle:
    vx: float
    vy: float
    cx: float
    cy: float
    color: tuple
    alive: bool
    max_age: int
    age: int = 0

    def __init__(p, move_y):
        random_angle = random.uniform(0, 2 * math.pi)
        random_speed = random.uniform(START_SPEED * 0.5, START_SPEED * 1.5)
        p.vx = math.cos(random_angle) * random_speed
        p.vy = math.sin(random_angle) * random_speed
        p.color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
        p.cx, p.cy = START_X, START_Y + move_y
        p.max_age = random.randint(5, 13)
        p.alive = True

    def draw(p):
        s = [255, 255, 204]
        f = [230, 230, 0]
        if p.age > GLOW:
            color = f
        else:
            color = [
                s[c] * (GLOW - p.age) / GLOW + f[c] * (p.age) / GLOW
                for c in (0, 1, 2)
            ]
        filled_circle(color, (p.cx, p.cy), 4 - p.age // 6)

    def update(p):
        p.cx += p.vx
        p.cy += p.vy
        p.vy -= G
        p.age += 1
        if p.cy > 1000 or p.age > p.max_age:
            p.alive = False


def draw_particles(particles):
    for p in particles:
        p.draw()


def remove_particles(particles):
    last_good = -1
    for cur in range(len(particles)):
        p = particles[cur]
        if p.alive:
            last_good += 1
            particles[last_good] = p
    del particles[last_good + 1:]


def update_particles(particles):
    for p in particles:
        p.update()


def draw_stick(done):
    border = min(done, TOP - SAFE)
    filled_rect((102, 102, 102), (START_X - STICK_WIDTH / 2 - 2, START_Y), STICK_WIDTH + 4, border)
    filled_rect('white', (START_X - STICK_WIDTH / 2 - 3, START_Y + border), STICK_WIDTH + 6, TOP - border - SAFE)
    filled_rect((64, 64, 64), (START_X - STICK_WIDTH / 2, START_Y + TOP - SAFE), STICK_WIDTH, SAFE)


draw_stick(0)
sleep(1)

particles = []
move_y = 0
while move_y < TOP + 50:
    move_y += 1
    if move_y < TOP - SAFE:
        for __ in range(25):
            particles.append(Particle(move_y=move_y))
    fill('black')
    draw_stick(move_y)
    draw_particles(particles)
    tick()
    update_particles(particles)
    remove_particles(particles)