smurfs / src /rendering /sprite_generator.py
BolyosCsaba
initial commit
82fa936
"""
Sprite Generation - Shared utilities and resource/building sprites
Character sprites are generated by separate pipeline classes
"""
import numpy as np
from PIL import Image, ImageDraw
from dataclasses import dataclass
from typing import List
@dataclass
class Sprite:
frames: List[np.ndarray]
current_frame: int = 0
frame_speed: int = 5
tick_counter: int = 0
def next_frame(self) -> np.ndarray:
self.tick_counter += 1
if self.tick_counter >= self.frame_speed:
self.tick_counter = 0
self.current_frame = (self.current_frame + 1) % len(self.frames)
return self.frames[self.current_frame]
def get_current_frame(self) -> np.ndarray:
return self.frames[self.current_frame]
def reset(self):
self.current_frame = 0
self.tick_counter = 0
class SpriteGenerator:
"""Shared sprite generation utilities for resources and buildings"""
# ========================================================================
# RESOURCE SPRITES
# ========================================================================
@staticmethod
def generate_mushroom_sprite() -> Sprite:
img = Image.new('RGBA', (32, 32), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# Stem
draw.rectangle([13, 16, 19, 28], fill=(240, 230, 210))
# Cap
draw.ellipse([8, 10, 24, 22], fill=(200, 50, 50))
# White spots
draw.ellipse([11, 13, 15, 17], fill=(255, 255, 255))
draw.ellipse([17, 12, 20, 15], fill=(255, 255, 255))
return Sprite(frames=[np.array(img)])
@staticmethod
def generate_berry_sprite() -> Sprite:
img = Image.new('RGBA', (32, 32), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# Bush
draw.ellipse([6, 12, 26, 28], fill=(34, 139, 34))
# Berries
draw.ellipse([10, 15, 14, 19], fill=(200, 0, 0))
draw.ellipse([18, 14, 22, 18], fill=(200, 0, 0))
draw.ellipse([14, 20, 18, 24], fill=(200, 0, 0))
return Sprite(frames=[np.array(img)])
@staticmethod
def generate_wood_sprite() -> Sprite:
img = Image.new('RGBA', (32, 32), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# Log pile
draw.rectangle([8, 18, 24, 24], fill=(101, 67, 33))
draw.rectangle([10, 12, 22, 18], fill=(139, 90, 43))
draw.rectangle([12, 6, 20, 12], fill=(101, 67, 33))
# Wood rings
draw.ellipse([14, 8, 18, 10], fill=(80, 50, 20))
return Sprite(frames=[np.array(img)])
# ========================================================================
# BUILDING SPRITES (IMPROVED MUSHROOM HOUSES!)
# ========================================================================
@staticmethod
def generate_house_sprite(level: int = 1) -> Sprite:
"""Beautiful mushroom house!"""
size = 64 + (level - 1) * 8
img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# Stem (house base)
stem_color = (240, 230, 210) if level == 1 else (250, 240, 220)
draw.rectangle([size//4, size//2, size*3//4, size-4], fill=stem_color)
# Add texture to stem
for i in range(3):
y = size//2 + 10 + i*8
draw.line([(size//4+2, y), (size*3//4-2, y)], fill=(220, 210, 190), width=1)
# Mushroom cap (red with white spots)
cap_color = (200, 50, 50) if level == 1 else (220, 70, 70)
draw.ellipse([4, size//4, size-4, size//2+10], fill=cap_color)
# White spots on cap
spot_positions = [
(size//4, size//3),
(size//2 - 4, size//4 + 4),
(size*3//4 - 8, size//3 + 2)
]
if level > 1:
spot_positions.extend([
(size//3 - 4, size//2 - 2),
(size*2//3, size//2 - 4)
])
for x, y in spot_positions:
spot_size = 6 if level == 1 else 8
draw.ellipse([x, y, x+spot_size, y+spot_size], fill=(255, 255, 255))
# Door
door_width = 12 if level == 1 else 14
door_x = size//2 - door_width//2
draw.rectangle([door_x, size-18, door_x+door_width, size-4], fill=(101, 67, 33))
# Door knob
draw.ellipse([door_x+door_width-4, size-12, door_x+door_width-2, size-10], fill=(255, 215, 0))
# Windows (cute round windows)
if level > 1:
window_size = 8
# Left window
draw.ellipse([size//4-4, size//2+8, size//4+4, size//2+16], fill=(135, 206, 250))
draw.line([(size//4, size//2+8), (size//4, size//2+16)], fill=(100, 150, 200), width=1)
# Right window
draw.ellipse([size*3//4-4, size//2+8, size*3//4+4, size//2+16], fill=(135, 206, 250))
draw.line([(size*3//4, size//2+8), (size*3//4, size//2+16)], fill=(100, 150, 200), width=1)
if level > 2:
# Chimney
draw.rectangle([size*3//4-4, size//4-4, size*3//4+4, size//4+8], fill=(120, 80, 50))
# Smoke puffs
draw.ellipse([size*3//4-2, size//4-8, size*3//4+2, size//4-4], fill=(200, 200, 200))
return Sprite(frames=[np.array(img)])
@staticmethod
def generate_storage_sprite(level: int = 1) -> Sprite:
size = 64
img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# Barn/warehouse style
draw.rectangle([8, 20, size-8, size-4], fill=(139, 90, 43))
draw.polygon([(8, 20), (size//2, 8), (size-8, 20)], fill=(120, 80, 50))
# Capacity bars
for i in range(level):
y = size - 12 - i*8
draw.rectangle([12, y, size-12, y+6], fill=(200, 180, 140))
# Door
draw.rectangle([size//2-8, size-16, size//2+8, size-4], fill=(80, 50, 30))
return Sprite(frames=[np.array(img)])
@staticmethod
def generate_workshop_sprite(level: int = 1) -> Sprite:
size = 64
img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# Workshop building
draw.rectangle([8, 24, size-8, size-4], fill=(120, 100, 80))
draw.polygon([(8, 24), (size//2, 12), (size-8, 24)], fill=(100, 80, 60))
# Chimney with smoke
draw.rectangle([size-16, 8, size-12, 24], fill=(80, 60, 40))
draw.ellipse([size-18, 4, size-10, 10], fill=(180, 180, 180))
# Tool rack visual
draw.line([(12, 32), (size-12, 32)], fill=(60, 40, 20), width=2)
return Sprite(frames=[np.array(img)])
@staticmethod
def generate_townhall_sprite(level: int = 1) -> Sprite:
size = 80
img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# Grand mushroom building
draw.rectangle([10, 30, size-10, size-4], fill=(180, 100, 60))
draw.ellipse([5, 10, size-5, 35], fill=(220, 80, 80))
# Multiple white spots
spots = [(15, 18), (30, 15), (45, 17), (60, 19)]
for x, y in spots:
draw.ellipse([x, y, x+8, y+8], fill=(255, 255, 255))
# Flag on top
draw.rectangle([size//2-2, 4, size//2+2, 20], fill=(101, 67, 33))
draw.polygon([(size//2+2, 8), (size//2+2, 16), (size//2+14, 12)], fill=(255, 0, 0))
# Large entrance
draw.rectangle([size//2-10, size-24, size//2+10, size-4], fill=(101, 67, 33))
# Star emblem
draw.text((size//2-6, size//2+4), "★", fill=(255, 215, 0))
return Sprite(frames=[np.array(img)])
@staticmethod
def generate_grave_sprite() -> Sprite:
img = Image.new('RGBA', (32, 32), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# Tombstone
draw.rectangle([10, 12, 22, 28], fill=(100, 100, 100))
draw.ellipse([10, 8, 22, 16], fill=(100, 100, 100))
# Cross engraving
draw.line([(16, 14), (16, 20)], fill=(80, 80, 80), width=2)
draw.line([(13, 17), (19, 17)], fill=(80, 80, 80), width=2)
# Flowers
draw.ellipse([8, 26, 11, 29], fill=(255, 100, 100))
draw.ellipse([21, 26, 24, 29], fill=(255, 200, 100))
return Sprite(frames=[np.array(img)])