1
0
forked from ndyer/pygame-dvd
steam-screensaver/steam_saver/surfaces/steam_screen.py

213 lines
8.9 KiB
Python
Raw Normal View History

2023-02-28 09:50:34 -05:00
import pygame
import os
import random
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
SCALE_RATIO = (0.325 / 800.0)
SPEED_RATIO = (2.5 / 800.0)
2023-02-28 09:50:34 -05:00
class SteamLogo(pygame.sprite.Sprite):
"""
A Pygame sprite representing a Steam logo that moves around the screen.
Attributes:
base_image (pygame.Surface): The original image of the Steam logo.
x_speed (int): Current speed in the X axis.
y_speed (int): Current speed in the Y axis.
max_x (int): Maximum X position on the screen.
max_y (int): Maximum Y position on the screen.
2025-01-07 09:17:56 -05:00
is_mini (bool): Bool to determine whether the logo is a mini (background) logo or not.
"""
def __init__(self, window_size: tuple, is_mini: bool = False):
"""
Initialize SteamLogo sprite with given window size.
Args:
window_size (tuple): Size of the game window (width, height).
is_mini (tuple): Determines if the logo is going to be a background logo (mini) or the foreground one.
"""
2023-02-28 09:50:34 -05:00
super().__init__()
self.is_mini = is_mini
# Load and scale original logo image
self.base_image = pygame.image.load(SCRIPT_DIR + '/resources/steam_logo.png')
2023-02-28 09:50:34 -05:00
self.base_image.convert_alpha()
logo_scaling = min(window_size) * SCALE_RATIO
# If the logo is mini, make it 2 to 3 times smaller
if is_mini:
logo_scaling /= random.uniform(2, 3)
self.base_image = pygame.transform.smoothscale(self.base_image,
(self.base_image.get_width() * logo_scaling,
self.base_image.get_height() * logo_scaling))
# Copy the base image and store it in a separate instance variable; this is the image that will be drawn.
2023-02-28 09:50:34 -05:00
self.image = self.base_image.copy()
# Generate random color and apply it to the sprite.
self.random_color()
2023-02-28 09:50:34 -05:00
# Initialize rectangle (position, size) and speed attributes.
2023-02-28 09:50:34 -05:00
self.rect = self.image.get_rect()
self.max_speed = min(window_size) * SPEED_RATIO
# Make it so that the logo travels in a random direction when created
self.x_speed = (self.max_speed - 1) * random.choice([-1, 1])
self.y_speed = (self.max_speed - 1) * random.choice([-1, 1])
2023-02-28 09:50:34 -05:00
# Calculate maximum X and Y positions on the screen for boundary checking.
2023-02-28 09:50:34 -05:00
self.max_x = window_size[0] - self.rect.width
self.max_y = window_size[1] - self.rect.height
# The position of the logo is determined by float values that are separate from the Pygame rect position.
# By storing the position as a float and drawing it to the screen after, we are able to apply more precise
# speed values (e.g. 2.124) and it will appear as though it is moving smoothly.
# The starting location of the logo is randomized.
self.float_x = random.random() * self.max_y
self.float_y = random.random() * self.max_y
self.rect.x = self.float_x
self.rect.y = self.float_y
2023-02-28 09:50:34 -05:00
def random_color(self):
"""
Generate a new, random color and apply it to the sprite.
"""
# Create a surface with alpha channel for generating transparent colors.
2023-02-28 09:50:34 -05:00
color_surface = pygame.Surface(self.image.get_size(), pygame.SRCALPHA)
# Generate random RGB values and fill the color surface accordingly.
new_color = (random.randint(64, 255),
random.randint(64, 255),
random.randint(64, 255))
# If this is a mini logo, make it 80% darker
if self.is_mini:
color_surface.fill(tuple(int(x / 5) for x in new_color))
else: # If not, make it the generated color.
color_surface.fill(new_color)
# Replace the drawn image with a copy of the base image and apply the randomly generated color.
2023-02-28 09:50:34 -05:00
self.image = self.base_image.copy()
self.image.blit(color_surface, (0, 0), special_flags=pygame.BLEND_RGBA_MULT)
def update(self):
"""
Update SteamLogo sprite's position and speed based on its current state.
This method is called each frame during game execution to move the logo around
the screen and to handle collision/color changes.
"""
# Move the logo in the X and Y axis according to its current speed in those directions.
self.float_x += self.x_speed
self.float_y += self.y_speed
2023-02-28 09:50:34 -05:00
# Check for collisions with edges of the screen, change direction if necessary,
# and generate a new random color when hitting an edge.
if self.float_x < 0 or self.float_x > self.max_x:
# Mini logos have some randomness built into their bounce function to make the background look more organic.
if self.is_mini:
# Reflect off the left or right wall, clamping the speed (in case it was raised too high)
# The speed gets clamped if it goes above the max speed or below the starting speed (max - 1)
if self.x_speed > 0:
self.x_speed = max(-1 * self.max_speed, min((self.x_speed * -1), (-1 * self.max_speed) + 1))
else:
self.x_speed = max(self.max_speed - 1, min((self.x_speed * -1), self.max_speed))
# Add or subtract anywhere from 0 to 10% of the max speed to the vertical speed component
self.y_speed += random.uniform(self.max_speed * 0.1, self.max_speed * -0.1)
# If the logo isn't mini, do a simple reflection with no fancy stuff.
else:
self.x_speed *= -1
2023-02-28 09:50:34 -05:00
self.random_color()
# Do the same stuff for the top and bottom walls
if self.float_y < 0 or self.float_y > self.max_y:
if self.is_mini:
if self.y_speed > 0:
self.y_speed = max(-1 * self.max_speed, min((self.y_speed * -1), (-1 * self.max_speed) + 1))
else:
self.y_speed = max(self.max_speed - 1, min((self.y_speed * -1), self.max_speed))
self.x_speed += random.uniform(self.max_speed * 0.1, self.max_speed * -0.1)
else:
self.y_speed *= -1
2023-02-28 09:50:34 -05:00
self.random_color()
# Ensure the logo stays within screen boundaries by clamping its position.
self.float_x = max(0, min(self.float_x, self.max_x))
self.float_y = max(0, min(self.float_y, self.max_y))
# Set the Pygame rectangle's position to the stored floating point position
self.rect.x = self.float_x
self.rect.y = self.float_y
2023-02-28 09:50:34 -05:00
class Surface(pygame.Surface):
"""
A custom Pygame surface class for managing game logic and events.
Attributes:
running (bool): Flag indicating whether the surface is still running.
quit (bool): Flag signaling that the program should end rather than moving to a new surface.
next_surface (str): Name of the next surface to display after current one stops.
all_sprites (pygame.sprite.Group): A group containing sprites for easy sprite management.
"""
def __init__(self, window_size: tuple):
"""
Initialize Surface class with given window size.
Args:
window_size (tuple): Size of the game window (width, height).
"""
# Create a Pygame surface with alpha channel for generating transparent colors.
2023-02-28 09:50:34 -05:00
super().__init__(window_size, pygame.SRCALPHA)
# Initialize flags and attributes for managing game state.
2023-02-28 09:50:34 -05:00
self.running = True
self.quit = False
self.next_surface = ''
pygame.mouse.set_visible(False)
self.all_sprites = pygame.sprite.Group()
# Create all the mini Steam logos
for i in range(25):
self.all_sprites.add(SteamLogo(window_size, is_mini=True))
# Create the big Steam logo
self.all_sprites.add(SteamLogo(window_size))
2023-02-28 09:50:34 -05:00
def update(self):
"""
Update game state by handling events, updating sprites, and redrawing surfaces.
This method is called every frame during game execution.
"""
# Fill the surface with a black background color
2023-02-28 09:50:34 -05:00
self.fill(pygame.colordict.THECOLORS.get('black'))
# Handle events such as mouse button clicks or key presses.
2023-02-28 09:50:34 -05:00
for event in pygame.event.get():
match event.type:
case pygame.MOUSEBUTTONDOWN:
# Stop the game when a user clicks anywhere on the screen.
2023-02-28 09:50:34 -05:00
self.running = False
self.quit = True
case pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
# Also stop the game if the Escape key is pressed.
self.running = False
self.quit = True
2023-02-28 09:50:34 -05:00
# Update positions and speeds of all sprites (in this case, just one logo sprite).
2023-02-28 09:50:34 -05:00
self.all_sprites.update()
# Draw all sprites onto this surface.
self.all_sprites.draw(self)