import json import pygame from pygame import transform, gfxdraw, Vector2, Surface from math import sin, cos, pi from time import time, sleep from random import random INTERPOLATION = { 'sin2': lambda x: sin(x) ** 2, 'sin': lambda x: sin(x), 'cos': lambda x: 1 - cos(x), 'lin': lambda x: x } FADE = { (True, True): lambda x: sin(x) ** 2, # fade both (True, False): lambda x: 1 - cos(x), # fade in (False, True): lambda x: sin(x), # fade out (False, False): lambda x: x / (pi/2) # linear } class Star: def __init__(self, sid: str, x: int, y: int, alpha: float, rot: float, scale: float, offset: float, image: pygame.Surface): self.sid = sid self.x = x self.y = y self.rot = rot self.alpha = alpha self.scale = scale self.offset = offset self.image = image class Screen: def __init__(self, stars: list[Star], background: Surface | None, framerate: float = 30) -> None: if not pygame.get_init(): pygame.init() self.screen = pygame.display.set_mode((1920, 1080), display=1, flags=pygame.FULLSCREEN | pygame.DOUBLEBUF | pygame.WINDOWTAKEFOCUS) self.clock = pygame.time.Clock() self.stars = stars self.last = time() self.brightness = 1.0 self.active_scene = 0 self.last_scene = 0 self.scene_time: float = 0 self.framerate: float = framerate self.overlay = pygame.surface.Surface(self.screen.get_size(), depth=32) if background: ratio_x = self.screen.get_width() / background.get_width() ratio_y = self.screen.get_height() / background.get_height() scaled_background = transform.rotozoom(background, 0, max(ratio_x, ratio_y)) self.bg_pos = ( (self.screen.get_width() - scaled_background.get_width()) / 2, (self.screen.get_height() - scaled_background.get_height()) / 2 ) self.background = scaled_background else: self.background = None self.bg_pos = None def update(self, scene_data): if not self.background: self.screen.fill((0, 0, 0)) now = time() delta = now - self.last self.last = now self.scene_time += delta fade_in = scene_data[self.active_scene]['transition']['fade_in'] fade_out = scene_data[self.active_scene]['transition']['fade_out'] t = FADE[(fade_in, fade_out)](min(self.scene_time / scene_data[self.active_scene]['transition']['t'], 1) * pi/2) * pi/2 weight_x = INTERPOLATION[scene_data[self.active_scene]['transition']['x']](t) weight_y = INTERPOLATION[scene_data[self.active_scene]['transition']['y']](t) weight = INTERPOLATION['sin2'](t) def update_star_scene(star, old, new): star.x = weight_x*new[star.sid]['x'] + (1-weight_x)*old[star.sid]['x'] star.y = weight_y*new[star.sid]['y'] + (1-weight_y)*old[star.sid]['y'] star.rot = weight*new[star.sid]['rot'] + (1-weight)*old[star.sid]['rot'] star.scale = weight*new[star.sid]['scale'] + (1-weight)*old[star.sid]['scale'] # first draw the background, if any if self.background: self.screen.blit(self.background, self.bg_pos) # draw all the star objects for star in self.stars: update_star_scene(star, scene_data[self.last_scene], scene_data[self.active_scene]) star.alpha += star.rot * delta computed_scale = star.scale * (self.screen.get_width() / star.image.get_width() / 100) scaled = transform.rotozoom(star.image, star.alpha, computed_scale) (w, h) = scaled.get_size() pos = (star.x*self.screen.get_width() - w/2, star.y*self.screen.get_height() - h/2) self.screen.blit(scaled, pos) self.overlay.set_alpha(int(255 * (1 - self.brightness))) self.screen.blit(self.overlay, (0, 0)) pygame.display.flip() self.clock.tick(self.framerate) def close(self): pygame.quit()