ember/ember.py
2026-05-03 15:03:33 +01:00

82 lines
2.8 KiB
Python

import pygame
from typing import Tuple
import numpy as np
import pygame.gfxdraw
from math import ceil
from config import HEIGHT, WIDTH
from utils import clamp, mix
BLUR_RADIUS = 3
TTL_LOSS_NEAR_BORDER_IN_PIXELS = 200
UP_DRAFT = 300
class Ember:
def __init__(self):
self.reset()
def reset(self):
self.position = (np.random.randint(0, WIDTH), HEIGHT)
self.up_draft = np.random.uniform(0.3, 1) * UP_DRAFT
self.time_to_live = np.random.uniform(1, 6)
self.color_offset = np.random.uniform(0, 1)
self.size = np.random.uniform(2.5, 6.5)
def update(self, velocity: Tuple[float, float], delta_time: float):
self.position = (
self.position[0] + velocity[0] * delta_time,
self.position[1] + (velocity[1] - self.up_draft) * delta_time,
)
self.time_to_live -= delta_time
if self.distance_from_screen_border() < TTL_LOSS_NEAR_BORDER_IN_PIXELS:
self.time_to_live = min(
self.distance_from_screen_border() / TTL_LOSS_NEAR_BORDER_IN_PIXELS,
self.time_to_live,
)
if self.time_to_live < 0:
self.reset()
def draw(self, screen: pygame.Surface):
color = (
int(mix(255, 189, self.color_offset)),
int(mix(165, 40, self.color_offset)),
int(mix(0, 40, self.color_offset)),
255 * (1 - clamp((0.5 - self.time_to_live) * 2, 0, 1) ** 2),
)
draw_antialiased_circle(
screen, self.position[0], self.position[1], int(self.size), color
)
def distance_from_screen_border(self):
return min(
self.position[0],
WIDTH - self.position[0],
self.position[1],
# HEIGHT - self.position[1],
)
def __str__(self) -> str:
return f"Ember(position={self.position}, velocity={self.velocity}, time_to_live={self.time_to_live})"
def draw_antialiased_circle(surface: pygame.Surface, x, y, radius, color):
for dx in range(-ceil(radius), ceil(radius) + 1):
for dy in range(-ceil(radius), ceil(radius) + 1):
distance = ((dx - x % 1) ** 2 + (dy - y % 1) ** 2) ** 0.5 - radius
if (
distance < 0
and 0 <= x + dx < surface.get_width()
and 0 <= y + dy < surface.get_height()
):
alpha = min(color[3] / 255, -distance / BLUR_RADIUS)
value = surface.get_at((int(x + dx), int(y + dy)))
surface.set_at(
(int(x + dx), int(y + dy)),
(
mix(value[0], color[0], alpha),
mix(value[1], color[1], alpha),
mix(value[2], color[2], alpha),
mix(value[3], color[3], alpha),
),
)