Shade sprite module: faction generation, asset scanning, TextureCache
Extends the shade_sprite module (for merchant-shade.itch.io character sprite sheets) with procedural faction generation and asset management: - FactionGenerator: seed-based faction recipes with Biome, Element, Aesthetic, and RoleType enums for thematic variety - AssetLibrary: filesystem scanner that discovers and categorizes layer PNGs by type (skins, clothes, hair, etc.) - TextureCache: avoids redundant disk I/O when building many variants - CharacterAssembler: HSL shift documentation, method improvements - Demo expanded to 6 interactive scenes (animation viewer, HSL recolor, character gallery, faction generator, layer compositing, equipment) - EVALUATION.md: 7DRL readiness assessment of the full module - 329-line faction generation test suite Assets themselves are not included -- sprite sheets are external dependencies, some under commercial license. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9718153709
commit
80e14163f9
7 changed files with 2471 additions and 314 deletions
|
|
@ -3,11 +3,63 @@
|
|||
Uses the engine's Texture.composite() and texture.hsl_shift() methods to
|
||||
build composite character textures from multiple layer PNG files, without
|
||||
requiring PIL or any external Python packages.
|
||||
|
||||
HSL notes (from C++ investigation):
|
||||
- tex.hsl_shift(h, s, l) always creates a NEW texture by copying all
|
||||
pixels, converting RGB->HSL, applying shifts, converting back.
|
||||
- Works on any texture: file-loaded, from_bytes, composite, or
|
||||
previously shifted. Alpha is preserved; transparent pixels skipped.
|
||||
- No engine-level caching exists -- repeated identical calls produce
|
||||
separate texture objects. The TextureCache below avoids redundant
|
||||
loads and shifts at the Python level.
|
||||
"""
|
||||
import mcrfpy
|
||||
from .formats import PUNY_29, SheetFormat
|
||||
|
||||
|
||||
class TextureCache:
|
||||
"""Cache for loaded and HSL-shifted textures to avoid redundant disk I/O.
|
||||
|
||||
Keys are (path, hue_shift, sat_shift, lit_shift) tuples.
|
||||
Call clear() to free all cached textures.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._cache = {}
|
||||
|
||||
def get(self, path, tile_w, tile_h, hue=0.0, sat=0.0, lit=0.0):
|
||||
"""Load a texture, using cached version if available.
|
||||
|
||||
Args:
|
||||
path: File path to the PNG
|
||||
tile_w: Sprite tile width
|
||||
tile_h: Sprite tile height
|
||||
hue: Hue rotation in degrees
|
||||
sat: Saturation adjustment
|
||||
lit: Lightness adjustment
|
||||
|
||||
Returns:
|
||||
mcrfpy.Texture
|
||||
"""
|
||||
key = (path, hue, sat, lit)
|
||||
if key not in self._cache:
|
||||
tex = mcrfpy.Texture(path, tile_w, tile_h)
|
||||
if hue != 0.0 or sat != 0.0 or lit != 0.0:
|
||||
tex = tex.hsl_shift(hue, sat, lit)
|
||||
self._cache[key] = tex
|
||||
return self._cache[key]
|
||||
|
||||
def clear(self):
|
||||
"""Drop all cached textures."""
|
||||
self._cache.clear()
|
||||
|
||||
def __len__(self):
|
||||
return len(self._cache)
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._cache
|
||||
|
||||
|
||||
class CharacterAssembler:
|
||||
"""Build composite character sheets from layer files.
|
||||
|
||||
|
|
@ -16,13 +68,16 @@ class CharacterAssembler:
|
|||
|
||||
Args:
|
||||
fmt: SheetFormat describing the sprite dimensions (default: PUNY_29)
|
||||
cache: Optional TextureCache for reusing loaded textures across
|
||||
multiple build() calls. If None, a private cache is created.
|
||||
"""
|
||||
|
||||
def __init__(self, fmt=None):
|
||||
def __init__(self, fmt=None, cache=None):
|
||||
if fmt is None:
|
||||
fmt = PUNY_29
|
||||
self.fmt = fmt
|
||||
self.layers = []
|
||||
self.cache = cache if cache is not None else TextureCache()
|
||||
|
||||
def add_layer(self, path, hue_shift=0.0, sat_shift=0.0, lit_shift=0.0):
|
||||
"""Queue a layer PNG with optional HSL recoloring.
|
||||
|
|
@ -44,8 +99,8 @@ class CharacterAssembler:
|
|||
def build(self, name="<composed>"):
|
||||
"""Composite all queued layers into a single Texture.
|
||||
|
||||
Loads each layer file, applies HSL shifts if any, then composites
|
||||
all layers bottom-to-top using alpha blending.
|
||||
Loads each layer file (using the cache to avoid redundant disk reads
|
||||
and HSL computations), then composites all layers bottom-to-top.
|
||||
|
||||
Args:
|
||||
name: Optional name for the resulting texture
|
||||
|
|
@ -62,9 +117,7 @@ class CharacterAssembler:
|
|||
|
||||
textures = []
|
||||
for path, h, s, l in self.layers:
|
||||
tex = mcrfpy.Texture(path, self.fmt.tile_w, self.fmt.tile_h)
|
||||
if h != 0.0 or s != 0.0 or l != 0.0:
|
||||
tex = tex.hsl_shift(h, s, l)
|
||||
tex = self.cache.get(path, self.fmt.tile_w, self.fmt.tile_h, h, s, l)
|
||||
textures.append(tex)
|
||||
|
||||
if len(textures) == 1:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue