2026-07-21 19:36:02 -04:00
|
|
|
"""Sprite loading with graceful placeholders.
|
|
|
|
|
|
|
|
|
|
Every drawable in the game asks the AssetStore for a surface by name. If a PNG
|
|
|
|
|
named ``<name>.png`` exists in the assets directory it is loaded and scaled to
|
|
|
|
|
the requested size; otherwise a labeled colored rectangle is drawn instead, so
|
|
|
|
|
the game is fully playable before any art exists.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import pygame
|
|
|
|
|
|
|
|
|
|
from . import settings
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AssetStore:
|
|
|
|
|
def __init__(self, assets_dir):
|
|
|
|
|
self.assets_dir = assets_dir
|
2026-08-01 12:19:46 -04:00
|
|
|
self._raw = {} # name -> original loaded Surface (or None if missing)
|
|
|
|
|
self._cache = {} # (name, w, h) -> scaled Surface
|
2026-07-21 19:36:02 -04:00
|
|
|
self._font = None
|
|
|
|
|
|
|
|
|
|
def _font_for(self, h):
|
|
|
|
|
# Lazily build a font sized to the tile so placeholder labels fit.
|
|
|
|
|
size = max(10, int(h * 0.5))
|
|
|
|
|
return pygame.font.SysFont("consolas,menlo,monospace", size, bold=True)
|
|
|
|
|
|
|
|
|
|
def _load_raw(self, name):
|
|
|
|
|
if name in self._raw:
|
|
|
|
|
return self._raw[name]
|
|
|
|
|
path = os.path.join(self.assets_dir, name + ".png")
|
|
|
|
|
surf = None
|
|
|
|
|
if os.path.isfile(path):
|
|
|
|
|
try:
|
|
|
|
|
surf = pygame.image.load(path).convert_alpha()
|
|
|
|
|
except pygame.error:
|
|
|
|
|
surf = None
|
|
|
|
|
self._raw[name] = surf
|
|
|
|
|
return surf
|
|
|
|
|
|
|
|
|
|
def get(self, name, w, h, angle=0):
|
|
|
|
|
"""Return a Surface of exactly (w, h) for the given sprite name,
|
|
|
|
|
optionally rotated counter-clockwise by ``angle`` degrees first (used to
|
|
|
|
|
point directional sprites like spikes the right way)."""
|
|
|
|
|
w, h = int(w), int(h)
|
|
|
|
|
angle %= 360
|
|
|
|
|
key = (name, w, h, angle)
|
|
|
|
|
if key in self._cache:
|
|
|
|
|
return self._cache[key]
|
|
|
|
|
|
|
|
|
|
raw = self._load_raw(name)
|
|
|
|
|
if raw is not None:
|
|
|
|
|
if angle:
|
|
|
|
|
raw = pygame.transform.rotate(raw, angle)
|
|
|
|
|
# Nearest-neighbour keeps the pixel-art sprites crisp at any size.
|
|
|
|
|
surf = pygame.transform.scale(raw, (w, h))
|
|
|
|
|
else:
|
|
|
|
|
surf = self._make_placeholder(name, w, h)
|
|
|
|
|
|
|
|
|
|
self._cache[key] = surf
|
|
|
|
|
return surf
|
|
|
|
|
|
|
|
|
|
def _make_placeholder(self, name, w, h):
|
|
|
|
|
color, label = settings.PLACEHOLDERS.get(name, ((200, 60, 200), "?"))
|
|
|
|
|
surf = pygame.Surface((w, h), pygame.SRCALPHA)
|
|
|
|
|
surf.fill(color)
|
|
|
|
|
# A subtle border helps distinguish adjacent tiles of the same color.
|
|
|
|
|
pygame.draw.rect(surf, (0, 0, 0, 90), surf.get_rect(), max(1, w // 16))
|
|
|
|
|
if label:
|
|
|
|
|
font = self._font_for(h)
|
|
|
|
|
text = font.render(label, True, (15, 15, 20))
|
|
|
|
|
surf.blit(text, text.get_rect(center=(w // 2, h // 2)))
|
|
|
|
|
return surf
|