661 lines
21 KiB
Python
661 lines
21 KiB
Python
|
|
"""shade_sprite interactive demo.
|
||
|
|
|
||
|
|
Run from the build directory:
|
||
|
|
./mcrogueface --exec ../shade_sprite/demo.py
|
||
|
|
|
||
|
|
Or copy the shade_sprite directory into build/scripts/ and run:
|
||
|
|
./mcrogueface --exec scripts/shade_sprite/demo.py
|
||
|
|
|
||
|
|
Scenes:
|
||
|
|
1 - Animation Viewer: cycle animations and directions
|
||
|
|
2 - HSL Recolor: live hue/saturation/lightness shifting
|
||
|
|
3 - Creature Gallery: grid of animated characters
|
||
|
|
4 - Faction Generator: random faction color schemes
|
||
|
|
|
||
|
|
Controls shown on-screen per scene.
|
||
|
|
"""
|
||
|
|
import mcrfpy
|
||
|
|
import sys
|
||
|
|
import os
|
||
|
|
import random
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# Asset discovery
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
# Search paths for Puny Character sprites
|
||
|
|
_SEARCH_PATHS = [
|
||
|
|
"assets/Puny-Characters",
|
||
|
|
"../assets/Puny-Characters",
|
||
|
|
# 7DRL dev location
|
||
|
|
os.path.expanduser(
|
||
|
|
"~/Development/7DRL2026_Liber_Noster_jmccardle/"
|
||
|
|
"assets_sources/Puny-Characters"
|
||
|
|
),
|
||
|
|
]
|
||
|
|
|
||
|
|
def _find_asset_dir():
|
||
|
|
for p in _SEARCH_PATHS:
|
||
|
|
if os.path.isdir(p):
|
||
|
|
return p
|
||
|
|
return None
|
||
|
|
|
||
|
|
ASSET_DIR = _find_asset_dir()
|
||
|
|
|
||
|
|
# Character sheets available in the free CC0 pack
|
||
|
|
_CHARACTER_FILES = [
|
||
|
|
"Warrior-Red.png", "Warrior-Blue.png",
|
||
|
|
"Soldier-Red.png", "Soldier-Blue.png", "Soldier-Yellow.png",
|
||
|
|
"Archer-Green.png", "Archer-Purple.png",
|
||
|
|
"Mage-Red.png", "Mage-Cyan.png",
|
||
|
|
"Human-Soldier-Red.png", "Human-Soldier-Cyan.png",
|
||
|
|
"Human-Worker-Red.png", "Human-Worker-Cyan.png",
|
||
|
|
"Orc-Grunt.png", "Orc-Peon-Red.png", "Orc-Peon-Cyan.png",
|
||
|
|
"Orc-Soldier-Red.png", "Orc-Soldier-Cyan.png",
|
||
|
|
"Character-Base.png",
|
||
|
|
]
|
||
|
|
|
||
|
|
def _available_sheets():
|
||
|
|
"""Return list of full paths to available character sheets."""
|
||
|
|
if not ASSET_DIR:
|
||
|
|
return []
|
||
|
|
sheets = []
|
||
|
|
for f in _CHARACTER_FILES:
|
||
|
|
p = os.path.join(ASSET_DIR, f)
|
||
|
|
if os.path.isfile(p):
|
||
|
|
sheets.append(p)
|
||
|
|
return sheets
|
||
|
|
|
||
|
|
# Import shade_sprite (handle being run from different locations)
|
||
|
|
if __name__ == "__main__":
|
||
|
|
# Add parent dir to path so shade_sprite can be imported
|
||
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||
|
|
parent_dir = os.path.dirname(script_dir)
|
||
|
|
if parent_dir not in sys.path:
|
||
|
|
sys.path.insert(0, parent_dir)
|
||
|
|
|
||
|
|
from shade_sprite import (
|
||
|
|
AnimatedSprite, Direction, PUNY_24, PUNY_29, SLIME,
|
||
|
|
detect_format, CharacterAssembler,
|
||
|
|
)
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# Globals
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
_animated_sprites = [] # all AnimatedSprite instances to tick
|
||
|
|
_active_scene = None
|
||
|
|
|
||
|
|
|
||
|
|
def _tick_all(timer, runtime):
|
||
|
|
"""Global animation tick callback."""
|
||
|
|
for a in _animated_sprites:
|
||
|
|
a.tick(timer.interval)
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# Scene 1: Animation Viewer
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
def _build_scene_viewer():
|
||
|
|
scene = mcrfpy.Scene("viewer")
|
||
|
|
ui = scene.children
|
||
|
|
|
||
|
|
# Background
|
||
|
|
bg = mcrfpy.Frame(pos=(0, 0), size=(1024, 768),
|
||
|
|
fill_color=mcrfpy.Color(30, 30, 40))
|
||
|
|
ui.append(bg)
|
||
|
|
|
||
|
|
# Title
|
||
|
|
title = mcrfpy.Caption(text="shade_sprite - Animation Viewer",
|
||
|
|
pos=(20, 10),
|
||
|
|
fill_color=mcrfpy.Color(220, 220, 255))
|
||
|
|
ui.append(title)
|
||
|
|
|
||
|
|
sheets = _available_sheets()
|
||
|
|
if not sheets:
|
||
|
|
msg = mcrfpy.Caption(
|
||
|
|
text="No sprite assets found. Place Puny-Characters PNGs in assets/Puny-Characters/",
|
||
|
|
pos=(20, 60),
|
||
|
|
fill_color=mcrfpy.Color(255, 100, 100))
|
||
|
|
ui.append(msg)
|
||
|
|
return scene
|
||
|
|
|
||
|
|
# State
|
||
|
|
state = {
|
||
|
|
"sheet_idx": 0,
|
||
|
|
"anim_idx": 0,
|
||
|
|
"dir_idx": 0,
|
||
|
|
}
|
||
|
|
|
||
|
|
# Determine format
|
||
|
|
fmt = PUNY_24 # Free pack is 768x256
|
||
|
|
|
||
|
|
anim_names = list(fmt.animations.keys())
|
||
|
|
dir_names = [d.name for d in Direction]
|
||
|
|
|
||
|
|
# Load first sheet
|
||
|
|
tex = mcrfpy.Texture(sheets[0], fmt.tile_w, fmt.tile_h)
|
||
|
|
|
||
|
|
# Main sprite display (scaled up 4x)
|
||
|
|
sprite = mcrfpy.Sprite(texture=tex, pos=(200, 200), scale=6.0)
|
||
|
|
ui.append(sprite)
|
||
|
|
|
||
|
|
anim = AnimatedSprite(sprite, fmt, Direction.S)
|
||
|
|
anim.play("idle")
|
||
|
|
_animated_sprites.append(anim)
|
||
|
|
|
||
|
|
# Info labels
|
||
|
|
sheet_label = mcrfpy.Caption(
|
||
|
|
text=f"Sheet: {os.path.basename(sheets[0])}",
|
||
|
|
pos=(20, 50),
|
||
|
|
fill_color=mcrfpy.Color(180, 180, 200))
|
||
|
|
ui.append(sheet_label)
|
||
|
|
|
||
|
|
anim_label = mcrfpy.Caption(
|
||
|
|
text=f"Animation: idle",
|
||
|
|
pos=(20, 80),
|
||
|
|
fill_color=mcrfpy.Color(180, 180, 200))
|
||
|
|
ui.append(anim_label)
|
||
|
|
|
||
|
|
dir_label = mcrfpy.Caption(
|
||
|
|
text=f"Direction: S",
|
||
|
|
pos=(20, 110),
|
||
|
|
fill_color=mcrfpy.Color(180, 180, 200))
|
||
|
|
ui.append(dir_label)
|
||
|
|
|
||
|
|
controls = mcrfpy.Caption(
|
||
|
|
text="[Q/E] Sheet [A/D] Animation [W/S] Direction [2] HSL [3] Gallery [4] Factions",
|
||
|
|
pos=(20, 740),
|
||
|
|
fill_color=mcrfpy.Color(120, 120, 140))
|
||
|
|
ui.append(controls)
|
||
|
|
|
||
|
|
# Also show all 8 directions as small sprites
|
||
|
|
dir_sprites = []
|
||
|
|
dir_anims = []
|
||
|
|
for i, d in enumerate(Direction):
|
||
|
|
dx = 500 + (i % 4) * 80
|
||
|
|
dy = 200 + (i // 4) * 100
|
||
|
|
s = mcrfpy.Sprite(texture=tex, pos=(dx, dy), scale=3.0)
|
||
|
|
ui.append(s)
|
||
|
|
a = AnimatedSprite(s, fmt, d)
|
||
|
|
a.play("idle")
|
||
|
|
_animated_sprites.append(a)
|
||
|
|
dir_sprites.append(s)
|
||
|
|
dir_anims.append(a)
|
||
|
|
|
||
|
|
# Direction label
|
||
|
|
lbl = mcrfpy.Caption(text=d.name, pos=(dx + 10, dy - 18),
|
||
|
|
fill_color=mcrfpy.Color(150, 150, 170))
|
||
|
|
ui.append(lbl)
|
||
|
|
|
||
|
|
def on_key(key, action):
|
||
|
|
if action != mcrfpy.InputState.PRESSED:
|
||
|
|
return
|
||
|
|
|
||
|
|
if key == mcrfpy.Key.Q:
|
||
|
|
# Previous sheet
|
||
|
|
state["sheet_idx"] = (state["sheet_idx"] - 1) % len(sheets)
|
||
|
|
_reload_sheet()
|
||
|
|
elif key == mcrfpy.Key.E:
|
||
|
|
# Next sheet
|
||
|
|
state["sheet_idx"] = (state["sheet_idx"] + 1) % len(sheets)
|
||
|
|
_reload_sheet()
|
||
|
|
elif key == mcrfpy.Key.A:
|
||
|
|
# Previous animation
|
||
|
|
state["anim_idx"] = (state["anim_idx"] - 1) % len(anim_names)
|
||
|
|
_update_anim()
|
||
|
|
elif key == mcrfpy.Key.D:
|
||
|
|
# Next animation
|
||
|
|
state["anim_idx"] = (state["anim_idx"] + 1) % len(anim_names)
|
||
|
|
_update_anim()
|
||
|
|
elif key == mcrfpy.Key.W:
|
||
|
|
# Previous direction
|
||
|
|
state["dir_idx"] = (state["dir_idx"] - 1) % 8
|
||
|
|
_update_dir()
|
||
|
|
elif key == mcrfpy.Key.S:
|
||
|
|
# Next direction
|
||
|
|
state["dir_idx"] = (state["dir_idx"] + 1) % 8
|
||
|
|
_update_dir()
|
||
|
|
elif key == mcrfpy.Key.Num2:
|
||
|
|
mcrfpy.Scene("hsl").activate()
|
||
|
|
elif key == mcrfpy.Key.Num3:
|
||
|
|
mcrfpy.Scene("gallery").activate()
|
||
|
|
elif key == mcrfpy.Key.Num4:
|
||
|
|
mcrfpy.Scene("factions").activate()
|
||
|
|
|
||
|
|
def _reload_sheet():
|
||
|
|
path = sheets[state["sheet_idx"]]
|
||
|
|
new_tex = mcrfpy.Texture(path, fmt.tile_w, fmt.tile_h)
|
||
|
|
sprite.texture = new_tex
|
||
|
|
for s in dir_sprites:
|
||
|
|
s.texture = new_tex
|
||
|
|
sheet_label.text = f"Sheet: {os.path.basename(path)}"
|
||
|
|
_update_anim()
|
||
|
|
|
||
|
|
def _update_anim():
|
||
|
|
name = anim_names[state["anim_idx"]]
|
||
|
|
anim.play(name)
|
||
|
|
for a in dir_anims:
|
||
|
|
a.play(name)
|
||
|
|
anim_label.text = f"Animation: {name}"
|
||
|
|
|
||
|
|
def _update_dir():
|
||
|
|
d = Direction(state["dir_idx"])
|
||
|
|
anim.direction = d
|
||
|
|
dir_label.text = f"Direction: {d.name}"
|
||
|
|
|
||
|
|
scene.on_key = on_key
|
||
|
|
return scene
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# Scene 2: HSL Recolor Demo
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
def _build_scene_hsl():
|
||
|
|
scene = mcrfpy.Scene("hsl")
|
||
|
|
ui = scene.children
|
||
|
|
|
||
|
|
bg = mcrfpy.Frame(pos=(0, 0), size=(1024, 768),
|
||
|
|
fill_color=mcrfpy.Color(30, 30, 40))
|
||
|
|
ui.append(bg)
|
||
|
|
|
||
|
|
title = mcrfpy.Caption(text="shade_sprite - HSL Recoloring",
|
||
|
|
pos=(20, 10),
|
||
|
|
fill_color=mcrfpy.Color(220, 220, 255))
|
||
|
|
ui.append(title)
|
||
|
|
|
||
|
|
sheets = _available_sheets()
|
||
|
|
if not sheets:
|
||
|
|
msg = mcrfpy.Caption(
|
||
|
|
text="No sprite assets found.",
|
||
|
|
pos=(20, 60),
|
||
|
|
fill_color=mcrfpy.Color(255, 100, 100))
|
||
|
|
ui.append(msg)
|
||
|
|
|
||
|
|
def on_key(key, action):
|
||
|
|
if action == mcrfpy.InputState.PRESSED and key == mcrfpy.Key.Num1:
|
||
|
|
mcrfpy.Scene("viewer").activate()
|
||
|
|
scene.on_key = on_key
|
||
|
|
return scene
|
||
|
|
|
||
|
|
fmt = PUNY_24
|
||
|
|
|
||
|
|
state = {
|
||
|
|
"hue": 0.0,
|
||
|
|
"sat": 0.0,
|
||
|
|
"lit": 0.0,
|
||
|
|
"sheet_idx": 0,
|
||
|
|
}
|
||
|
|
|
||
|
|
# Original sprite (left)
|
||
|
|
orig_tex = mcrfpy.Texture(sheets[0], fmt.tile_w, fmt.tile_h)
|
||
|
|
orig_sprite = mcrfpy.Sprite(texture=orig_tex, pos=(150, 250), scale=6.0)
|
||
|
|
ui.append(orig_sprite)
|
||
|
|
orig_anim = AnimatedSprite(orig_sprite, fmt, Direction.S)
|
||
|
|
orig_anim.play("walk")
|
||
|
|
_animated_sprites.append(orig_anim)
|
||
|
|
|
||
|
|
orig_label = mcrfpy.Caption(text="Original", pos=(170, 220),
|
||
|
|
fill_color=mcrfpy.Color(180, 180, 200))
|
||
|
|
ui.append(orig_label)
|
||
|
|
|
||
|
|
# Shifted sprite (right)
|
||
|
|
shifted_sprite = mcrfpy.Sprite(texture=orig_tex, pos=(550, 250), scale=6.0)
|
||
|
|
ui.append(shifted_sprite)
|
||
|
|
shifted_anim = AnimatedSprite(shifted_sprite, fmt, Direction.S)
|
||
|
|
shifted_anim.play("walk")
|
||
|
|
_animated_sprites.append(shifted_anim)
|
||
|
|
|
||
|
|
shifted_label = mcrfpy.Caption(text="Shifted", pos=(570, 220),
|
||
|
|
fill_color=mcrfpy.Color(180, 180, 200))
|
||
|
|
ui.append(shifted_label)
|
||
|
|
|
||
|
|
# HSL value displays
|
||
|
|
hue_label = mcrfpy.Caption(text="Hue: 0.0", pos=(20, 500),
|
||
|
|
fill_color=mcrfpy.Color(255, 180, 180))
|
||
|
|
ui.append(hue_label)
|
||
|
|
sat_label = mcrfpy.Caption(text="Sat: 0.0", pos=(20, 530),
|
||
|
|
fill_color=mcrfpy.Color(180, 255, 180))
|
||
|
|
ui.append(sat_label)
|
||
|
|
lit_label = mcrfpy.Caption(text="Lit: 0.0", pos=(20, 560),
|
||
|
|
fill_color=mcrfpy.Color(180, 180, 255))
|
||
|
|
ui.append(lit_label)
|
||
|
|
|
||
|
|
controls = mcrfpy.Caption(
|
||
|
|
text="[Left/Right] Hue +/-30 [Up/Down] Sat +/-0.1 [Z/X] Lit +/-0.1 [Q/E] Sheet [1] Viewer",
|
||
|
|
pos=(20, 740),
|
||
|
|
fill_color=mcrfpy.Color(120, 120, 140))
|
||
|
|
ui.append(controls)
|
||
|
|
|
||
|
|
def _rebuild_shifted():
|
||
|
|
path = sheets[state["sheet_idx"]]
|
||
|
|
base = mcrfpy.Texture(path, fmt.tile_w, fmt.tile_h)
|
||
|
|
shifted = base.hsl_shift(state["hue"], state["sat"], state["lit"])
|
||
|
|
shifted_sprite.texture = shifted
|
||
|
|
hue_label.text = f"Hue: {state['hue']:.0f}"
|
||
|
|
sat_label.text = f"Sat: {state['sat']:.1f}"
|
||
|
|
lit_label.text = f"Lit: {state['lit']:.1f}"
|
||
|
|
|
||
|
|
def on_key(key, action):
|
||
|
|
if action != mcrfpy.InputState.PRESSED:
|
||
|
|
return
|
||
|
|
|
||
|
|
changed = False
|
||
|
|
if key == mcrfpy.Key.LEFT:
|
||
|
|
state["hue"] = (state["hue"] - 30.0) % 360.0
|
||
|
|
changed = True
|
||
|
|
elif key == mcrfpy.Key.RIGHT:
|
||
|
|
state["hue"] = (state["hue"] + 30.0) % 360.0
|
||
|
|
changed = True
|
||
|
|
elif key == mcrfpy.Key.UP:
|
||
|
|
state["sat"] = min(1.0, state["sat"] + 0.1)
|
||
|
|
changed = True
|
||
|
|
elif key == mcrfpy.Key.DOWN:
|
||
|
|
state["sat"] = max(-1.0, state["sat"] - 0.1)
|
||
|
|
changed = True
|
||
|
|
elif key == mcrfpy.Key.Z:
|
||
|
|
state["lit"] = max(-1.0, state["lit"] - 0.1)
|
||
|
|
changed = True
|
||
|
|
elif key == mcrfpy.Key.X:
|
||
|
|
state["lit"] = min(1.0, state["lit"] + 0.1)
|
||
|
|
changed = True
|
||
|
|
elif key == mcrfpy.Key.Q:
|
||
|
|
state["sheet_idx"] = (state["sheet_idx"] - 1) % len(sheets)
|
||
|
|
_reload()
|
||
|
|
elif key == mcrfpy.Key.E:
|
||
|
|
state["sheet_idx"] = (state["sheet_idx"] + 1) % len(sheets)
|
||
|
|
_reload()
|
||
|
|
elif key == mcrfpy.Key.Num1:
|
||
|
|
mcrfpy.Scene("viewer").activate()
|
||
|
|
return
|
||
|
|
elif key == mcrfpy.Key.Num3:
|
||
|
|
mcrfpy.Scene("gallery").activate()
|
||
|
|
return
|
||
|
|
elif key == mcrfpy.Key.Num4:
|
||
|
|
mcrfpy.Scene("factions").activate()
|
||
|
|
return
|
||
|
|
|
||
|
|
if changed:
|
||
|
|
_rebuild_shifted()
|
||
|
|
|
||
|
|
def _reload():
|
||
|
|
path = sheets[state["sheet_idx"]]
|
||
|
|
new_tex = mcrfpy.Texture(path, fmt.tile_w, fmt.tile_h)
|
||
|
|
orig_sprite.texture = new_tex
|
||
|
|
_rebuild_shifted()
|
||
|
|
|
||
|
|
scene.on_key = on_key
|
||
|
|
_rebuild_shifted()
|
||
|
|
return scene
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# Scene 3: Creature Gallery
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
def _build_scene_gallery():
|
||
|
|
scene = mcrfpy.Scene("gallery")
|
||
|
|
ui = scene.children
|
||
|
|
|
||
|
|
bg = mcrfpy.Frame(pos=(0, 0), size=(1024, 768),
|
||
|
|
fill_color=mcrfpy.Color(30, 30, 40))
|
||
|
|
ui.append(bg)
|
||
|
|
|
||
|
|
title = mcrfpy.Caption(text="shade_sprite - Character Gallery",
|
||
|
|
pos=(20, 10),
|
||
|
|
fill_color=mcrfpy.Color(220, 220, 255))
|
||
|
|
ui.append(title)
|
||
|
|
|
||
|
|
sheets = _available_sheets()
|
||
|
|
if not sheets:
|
||
|
|
msg = mcrfpy.Caption(
|
||
|
|
text="No sprite assets found.",
|
||
|
|
pos=(20, 60),
|
||
|
|
fill_color=mcrfpy.Color(255, 100, 100))
|
||
|
|
ui.append(msg)
|
||
|
|
|
||
|
|
def on_key(key, action):
|
||
|
|
if action == mcrfpy.InputState.PRESSED and key == mcrfpy.Key.Num1:
|
||
|
|
mcrfpy.Scene("viewer").activate()
|
||
|
|
scene.on_key = on_key
|
||
|
|
return scene
|
||
|
|
|
||
|
|
fmt = PUNY_24
|
||
|
|
directions = [Direction.S, Direction.SW, Direction.W, Direction.NW,
|
||
|
|
Direction.N, Direction.NE, Direction.E, Direction.SE]
|
||
|
|
|
||
|
|
# Layout: grid of characters, 4 columns
|
||
|
|
cols = 4
|
||
|
|
x_start, y_start = 40, 60
|
||
|
|
x_spacing, y_spacing = 240, 130
|
||
|
|
scale = 3.0
|
||
|
|
|
||
|
|
gallery_anims = []
|
||
|
|
count = min(len(sheets), 16) # max 4x4 grid
|
||
|
|
|
||
|
|
for i in range(count):
|
||
|
|
col = i % cols
|
||
|
|
row = i // cols
|
||
|
|
x = x_start + col * x_spacing
|
||
|
|
y = y_start + row * y_spacing
|
||
|
|
|
||
|
|
tex = mcrfpy.Texture(sheets[i], fmt.tile_w, fmt.tile_h)
|
||
|
|
sprite = mcrfpy.Sprite(texture=tex, pos=(x + 20, y + 20),
|
||
|
|
scale=scale)
|
||
|
|
ui.append(sprite)
|
||
|
|
|
||
|
|
a = AnimatedSprite(sprite, fmt, Direction.S)
|
||
|
|
a.play("walk")
|
||
|
|
_animated_sprites.append(a)
|
||
|
|
gallery_anims.append(a)
|
||
|
|
|
||
|
|
name = os.path.basename(sheets[i]).replace(".png", "")
|
||
|
|
lbl = mcrfpy.Caption(text=name, pos=(x, y),
|
||
|
|
fill_color=mcrfpy.Color(150, 150, 170))
|
||
|
|
ui.append(lbl)
|
||
|
|
|
||
|
|
state = {"dir_idx": 0, "anim_idx": 1} # start with walk
|
||
|
|
anim_names = list(fmt.animations.keys())
|
||
|
|
|
||
|
|
controls = mcrfpy.Caption(
|
||
|
|
text="[W/S] Direction [A/D] Animation [1] Viewer [2] HSL [4] Factions",
|
||
|
|
pos=(20, 740),
|
||
|
|
fill_color=mcrfpy.Color(120, 120, 140))
|
||
|
|
ui.append(controls)
|
||
|
|
|
||
|
|
def on_key(key, action):
|
||
|
|
if action != mcrfpy.InputState.PRESSED:
|
||
|
|
return
|
||
|
|
if key == mcrfpy.Key.W:
|
||
|
|
state["dir_idx"] = (state["dir_idx"] - 1) % 8
|
||
|
|
d = Direction(state["dir_idx"])
|
||
|
|
for a in gallery_anims:
|
||
|
|
a.direction = d
|
||
|
|
elif key == mcrfpy.Key.S:
|
||
|
|
state["dir_idx"] = (state["dir_idx"] + 1) % 8
|
||
|
|
d = Direction(state["dir_idx"])
|
||
|
|
for a in gallery_anims:
|
||
|
|
a.direction = d
|
||
|
|
elif key == mcrfpy.Key.A:
|
||
|
|
state["anim_idx"] = (state["anim_idx"] - 1) % len(anim_names)
|
||
|
|
name = anim_names[state["anim_idx"]]
|
||
|
|
for a in gallery_anims:
|
||
|
|
a.play(name)
|
||
|
|
elif key == mcrfpy.Key.D:
|
||
|
|
state["anim_idx"] = (state["anim_idx"] + 1) % len(anim_names)
|
||
|
|
name = anim_names[state["anim_idx"]]
|
||
|
|
for a in gallery_anims:
|
||
|
|
a.play(name)
|
||
|
|
elif key == mcrfpy.Key.Num1:
|
||
|
|
mcrfpy.Scene("viewer").activate()
|
||
|
|
elif key == mcrfpy.Key.Num2:
|
||
|
|
mcrfpy.Scene("hsl").activate()
|
||
|
|
elif key == mcrfpy.Key.Num4:
|
||
|
|
mcrfpy.Scene("factions").activate()
|
||
|
|
|
||
|
|
scene.on_key = on_key
|
||
|
|
return scene
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# Scene 4: Faction Generator
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
def _build_scene_factions():
|
||
|
|
scene = mcrfpy.Scene("factions")
|
||
|
|
ui = scene.children
|
||
|
|
|
||
|
|
bg = mcrfpy.Frame(pos=(0, 0), size=(1024, 768),
|
||
|
|
fill_color=mcrfpy.Color(30, 30, 40))
|
||
|
|
ui.append(bg)
|
||
|
|
|
||
|
|
title = mcrfpy.Caption(text="shade_sprite - Faction Generator",
|
||
|
|
pos=(20, 10),
|
||
|
|
fill_color=mcrfpy.Color(220, 220, 255))
|
||
|
|
ui.append(title)
|
||
|
|
|
||
|
|
sheets = _available_sheets()
|
||
|
|
if not sheets:
|
||
|
|
msg = mcrfpy.Caption(
|
||
|
|
text="No sprite assets found.",
|
||
|
|
pos=(20, 60),
|
||
|
|
fill_color=mcrfpy.Color(255, 100, 100))
|
||
|
|
ui.append(msg)
|
||
|
|
|
||
|
|
def on_key(key, action):
|
||
|
|
if action == mcrfpy.InputState.PRESSED and key == mcrfpy.Key.Num1:
|
||
|
|
mcrfpy.Scene("viewer").activate()
|
||
|
|
scene.on_key = on_key
|
||
|
|
return scene
|
||
|
|
|
||
|
|
fmt = PUNY_24
|
||
|
|
scale = 3.0
|
||
|
|
|
||
|
|
# State for generated factions
|
||
|
|
faction_anims = [] # store references for animation ticking
|
||
|
|
faction_sprites = [] # sprites to update on re-roll
|
||
|
|
faction_labels = []
|
||
|
|
|
||
|
|
# Faction colors (HSL hue values)
|
||
|
|
faction_hues = [0, 60, 120, 180, 240, 300]
|
||
|
|
faction_names_pool = [
|
||
|
|
"Iron Guard", "Shadow Pact", "Dawn Order", "Ember Clan",
|
||
|
|
"Frost Legion", "Vine Court", "Storm Band", "Ash Wardens",
|
||
|
|
"Gold Company", "Crimson Oath", "Azure Fleet", "Jade Circle",
|
||
|
|
]
|
||
|
|
|
||
|
|
def _generate_factions():
|
||
|
|
# Clear old faction animations from global list
|
||
|
|
for a in faction_anims:
|
||
|
|
if a in _animated_sprites:
|
||
|
|
_animated_sprites.remove(a)
|
||
|
|
faction_anims.clear()
|
||
|
|
|
||
|
|
# Pick 4 factions with random hues and characters
|
||
|
|
hues = random.sample(faction_hues, min(4, len(faction_hues)))
|
||
|
|
names = random.sample(faction_names_pool, 4)
|
||
|
|
|
||
|
|
# We'll create sprites dynamically
|
||
|
|
# Clear old sprites (rebuild scene content below bg/title/controls)
|
||
|
|
while len(ui) > 3: # keep bg, title, controls
|
||
|
|
# Can't easily remove from UICollection, so we rebuild the scene
|
||
|
|
pass
|
||
|
|
# Actually, just position everything and update textures
|
||
|
|
return hues, names
|
||
|
|
|
||
|
|
def _build_faction_display():
|
||
|
|
for a in faction_anims:
|
||
|
|
if a in _animated_sprites:
|
||
|
|
_animated_sprites.remove(a)
|
||
|
|
faction_anims.clear()
|
||
|
|
faction_sprites.clear()
|
||
|
|
faction_labels.clear()
|
||
|
|
|
||
|
|
hues = [random.uniform(0, 360) for _ in range(4)]
|
||
|
|
names = random.sample(faction_names_pool, 4)
|
||
|
|
|
||
|
|
y_start = 80
|
||
|
|
for fi in range(4):
|
||
|
|
y = y_start + fi * 160
|
||
|
|
hue = hues[fi]
|
||
|
|
|
||
|
|
# Faction name
|
||
|
|
lbl = mcrfpy.Caption(
|
||
|
|
text=f"{names[fi]} (hue {hue:.0f})",
|
||
|
|
pos=(20, y),
|
||
|
|
fill_color=mcrfpy.Color(200, 200, 220))
|
||
|
|
ui.append(lbl)
|
||
|
|
faction_labels.append(lbl)
|
||
|
|
|
||
|
|
# Pick 4 random character sheets for this faction
|
||
|
|
chosen = random.sample(sheets, min(4, len(sheets)))
|
||
|
|
for ci, path in enumerate(chosen):
|
||
|
|
x = 40 + ci * 200
|
||
|
|
|
||
|
|
# Apply faction hue shift
|
||
|
|
base_tex = mcrfpy.Texture(path, fmt.tile_w, fmt.tile_h)
|
||
|
|
shifted_tex = base_tex.hsl_shift(hue)
|
||
|
|
|
||
|
|
s = mcrfpy.Sprite(texture=shifted_tex, pos=(x, y + 30),
|
||
|
|
scale=scale)
|
||
|
|
ui.append(s)
|
||
|
|
faction_sprites.append(s)
|
||
|
|
|
||
|
|
a = AnimatedSprite(s, fmt, Direction.S)
|
||
|
|
a.play("walk")
|
||
|
|
_animated_sprites.append(a)
|
||
|
|
faction_anims.append(a)
|
||
|
|
|
||
|
|
controls = mcrfpy.Caption(
|
||
|
|
text="[Space] Re-roll factions [1] Viewer [2] HSL [3] Gallery",
|
||
|
|
pos=(20, 740),
|
||
|
|
fill_color=mcrfpy.Color(120, 120, 140))
|
||
|
|
ui.append(controls)
|
||
|
|
|
||
|
|
_build_faction_display()
|
||
|
|
|
||
|
|
def on_key(key, action):
|
||
|
|
if action != mcrfpy.InputState.PRESSED:
|
||
|
|
return
|
||
|
|
if key == mcrfpy.Key.SPACE:
|
||
|
|
# Rebuild scene
|
||
|
|
_rebuild_factions_scene()
|
||
|
|
elif key == mcrfpy.Key.Num1:
|
||
|
|
mcrfpy.Scene("viewer").activate()
|
||
|
|
elif key == mcrfpy.Key.Num2:
|
||
|
|
mcrfpy.Scene("hsl").activate()
|
||
|
|
elif key == mcrfpy.Key.Num3:
|
||
|
|
mcrfpy.Scene("gallery").activate()
|
||
|
|
|
||
|
|
def _rebuild_factions_scene():
|
||
|
|
# Easiest: rebuild the whole scene
|
||
|
|
new_scene = _build_scene_factions()
|
||
|
|
new_scene.activate()
|
||
|
|
|
||
|
|
scene.on_key = on_key
|
||
|
|
return scene
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# Main
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
def main():
|
||
|
|
if not ASSET_DIR:
|
||
|
|
print("WARNING: No Puny-Characters asset directory found.")
|
||
|
|
print("Searched:", _SEARCH_PATHS)
|
||
|
|
print("The demo will show placeholder messages.")
|
||
|
|
print()
|
||
|
|
|
||
|
|
# Build all scenes
|
||
|
|
_build_scene_viewer()
|
||
|
|
_build_scene_hsl()
|
||
|
|
_build_scene_gallery()
|
||
|
|
_build_scene_factions()
|
||
|
|
|
||
|
|
# Start animation timer (20fps animation updates)
|
||
|
|
mcrfpy.Timer("shade_anim", _tick_all, 50)
|
||
|
|
|
||
|
|
# Activate first scene
|
||
|
|
mcrfpy.Scene("viewer").activate()
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|
||
|
|
sys.exit(0)
|