McRogueFace/docs/cookbook/tools/sprite_labeler.py

397 lines
11 KiB
Python
Raw Permalink Normal View History

2026-01-13 19:42:37 -05:00
"""McRogueFace - Sprite Labeling Tool
A development tool for rapid sprite sheet labeling during game jams.
Creates a dictionary mapping sprite indices to custom labels.
Usage:
./mcrogueface docs/cookbook/tools/sprite_labeler.py
Console commands (while running):
# Save labels to file
import json; json.dump(labels, open('sprite_labels.json', 'w'), indent=2)
# Load labels from file
labels.update(json.load(open('sprite_labels.json')))
# Print current labels
for k, v in sorted(labels.items()): print(f"{k}: {v}")
"""
import mcrfpy
# === Global State ===
labels = {} # sprite_index (int) -> label (str)
selected_label = None # Currently selected label name
current_sprite_index = 0 # Currently hovered sprite
# Label categories - customize these for your game!
DEFAULT_LABELS = [
"player",
"enemy",
"wall",
"floor",
"door",
"item",
"trap",
"decoration",
]
# === Configuration ===
# Change these to match your texture!
TEXTURE_PATH = "assets/kenney_tinydungeon.png" # Path relative to build dir
TILE_SIZE = 16 # Size of each tile in the texture
GRID_COLS = 12 # Number of sprite columns in texture (texture_width / tile_size)
GRID_ROWS = 11 # Number of sprite rows in texture (texture_height / tile_size)
# UI Layout
WINDOW_WIDTH = 1024
WINDOW_HEIGHT = 768
GRID_X, GRID_Y = 50, 50
GRID_WIDTH, GRID_HEIGHT = 12 * 16 * 2, 11 * 16 * 2
PREVIEW_X, PREVIEW_Y = 480, 50
PREVIEW_SCALE = 4.0
PANEL_X = 480 + 180
PANEL_Y = 50
# Colors
BG_COLOR = mcrfpy.Color(30, 30, 40)
PANEL_COLOR = mcrfpy.Color(40, 45, 55)
BUTTON_COLOR = mcrfpy.Color(60, 65, 80)
BUTTON_HOVER = mcrfpy.Color(80, 85, 100)
BUTTON_SELECTED = mcrfpy.Color(80, 140, 80)
TEXT_COLOR = mcrfpy.Color(220, 220, 230)
LABEL_COLOR = mcrfpy.Color(100, 180, 255)
INPUT_BG = mcrfpy.Color(25, 25, 35)
INPUT_ACTIVE = mcrfpy.Color(35, 35, 50)
# === Scene Setup ===
scene = mcrfpy.Scene("sprite_labeler")
ui = scene.children
# Background
bg = mcrfpy.Frame(pos=(0, 0), size=(WINDOW_WIDTH, WINDOW_HEIGHT))
bg.fill_color = BG_COLOR
ui.append(bg)
# Load texture
texture = mcrfpy.Texture(TEXTURE_PATH, TILE_SIZE, TILE_SIZE)
# === Grid (shows all sprites) ===
grid = mcrfpy.Grid(
grid_size=(GRID_COLS, GRID_ROWS),
pos=(GRID_X, GRID_Y),
size=(GRID_WIDTH, GRID_HEIGHT),
texture=texture,
zoom=2.0
)
grid.fill_color = mcrfpy.Color(20, 20, 30)
# Initialize grid with all sprites
for row in range(GRID_ROWS):
for col in range(GRID_COLS):
sprite_index = row * GRID_COLS + col
cell = grid.at(col, row)
if cell:
cell.tilesprite = sprite_index
ui.append(grid)
# === Preview Section ===
preview_frame = mcrfpy.Frame(pos=(PREVIEW_X, PREVIEW_Y), size=(100, 100))
preview_frame.fill_color = PANEL_COLOR
preview_frame.outline = 1
preview_frame.outline_color = mcrfpy.Color(80, 80, 100)
ui.append(preview_frame)
preview_sprite = mcrfpy.Sprite((18, 18), texture, 0)
preview_sprite.scale = PREVIEW_SCALE
preview_frame.children.append(preview_sprite)
# Index caption
index_caption = mcrfpy.Caption(pos=(PREVIEW_X, PREVIEW_Y + 110), text="Index: 0")
index_caption.font_size = 18
index_caption.fill_color = TEXT_COLOR
ui.append(index_caption)
# Label display (shows current label for hovered sprite)
label_display = mcrfpy.Caption(pos=(PREVIEW_X, PREVIEW_Y + 135), text="Label: (none)")
label_display.font_size = 16
label_display.fill_color = LABEL_COLOR
ui.append(label_display)
# === Input Section (for adding new labels) ===
input_panel = mcrfpy.Frame(pos=(PANEL_X, PANEL_Y), size=(300, 80))
input_panel.fill_color = PANEL_COLOR
input_panel.outline = 1
input_panel.outline_color = mcrfpy.Color(80, 80, 100)
ui.append(input_panel)
input_title = mcrfpy.Caption(pos=(10, 8), text="Add New Label:")
input_title.font_size = 14
input_title.fill_color = TEXT_COLOR
input_panel.children.append(input_title)
# Text input field (frame + caption)
input_field = mcrfpy.Frame(pos=(10, 35), size=(200, 30))
input_field.fill_color = INPUT_BG
input_field.outline = 1
input_field.outline_color = mcrfpy.Color(60, 60, 80)
input_panel.children.append(input_field)
input_text = mcrfpy.Caption(pos=(8, 6), text="")
input_text.font_size = 14
input_text.fill_color = TEXT_COLOR
input_field.children.append(input_text)
# Text input state
input_buffer = ""
input_active = False
# Submit button
submit_btn = mcrfpy.Frame(pos=(220, 35), size=(70, 30))
submit_btn.fill_color = BUTTON_COLOR
submit_btn.outline = 1
submit_btn.outline_color = mcrfpy.Color(100, 100, 120)
input_panel.children.append(submit_btn)
submit_text = mcrfpy.Caption(pos=(12, 6), text="Add")
submit_text.font_size = 14
submit_text.fill_color = TEXT_COLOR
submit_btn.children.append(submit_text)
# === Label Selection Panel ===
labels_panel = mcrfpy.Frame(pos=(PANEL_X, PANEL_Y + 100), size=(300, 350))
labels_panel.fill_color = PANEL_COLOR
labels_panel.outline = 1
labels_panel.outline_color = mcrfpy.Color(80, 80, 100)
ui.append(labels_panel)
labels_title = mcrfpy.Caption(pos=(10, 8), text="Select Label (click to choose):")
labels_title.font_size = 14
labels_title.fill_color = TEXT_COLOR
labels_panel.children.append(labels_title)
# Store label button references for updating selection highlight
label_buttons = [] # list of (frame, caption, label_name)
def create_label_button(label_name, index):
"""Create a clickable label button"""
row = index // 2
col = index % 2
btn_x = 10 + col * 145
btn_y = 35 + row * 40
btn = mcrfpy.Frame(pos=(btn_x, btn_y), size=(135, 32))
btn.fill_color = BUTTON_COLOR
btn.outline = 1
btn.outline_color = mcrfpy.Color(80, 80, 100)
btn_caption = mcrfpy.Caption(pos=(8, 7), text=label_name[:14])
btn_caption.font_size = 12
btn_caption.fill_color = TEXT_COLOR
btn.children.append(btn_caption)
labels_panel.children.append(btn)
label_buttons.append((btn, btn_caption, label_name))
# Set click handler
def on_label_click(x, y, button):
select_label(label_name)
#return True
btn.on_click = on_label_click
return btn
def select_label(label_name):
"""Select a label for applying to sprites"""
global selected_label
selected_label = label_name
# Update button colors
for btn, caption, name in label_buttons:
if name == selected_label:
btn.fill_color = BUTTON_SELECTED
else:
btn.fill_color = BUTTON_COLOR
def add_new_label(label_name):
"""Add a new label to the selection list"""
global input_buffer
# Don't add duplicates or empty labels
label_name = label_name.strip()
if not label_name:
return
for _, _, existing in label_buttons:
if existing == label_name:
return
# Create button for new label
create_label_button(label_name, len(label_buttons))
# Clear input
input_buffer = ""
input_text.text = ""
# Select the new label
select_label(label_name)
# Initialize default labels
for i, label in enumerate(DEFAULT_LABELS):
create_label_button(label, i)
# Select first label by default
if DEFAULT_LABELS:
select_label(DEFAULT_LABELS[0])
# === Event Handlers ===
def on_cell_enter(cell):
"""Handle mouse hovering over grid cells"""
global current_sprite_index
x, y = int(cell[0]), int(cell[1])
sprite_index = y * GRID_COLS + x
current_sprite_index = sprite_index
# Update preview
preview_sprite.sprite_index = sprite_index
index_caption.text = f"Index: {sprite_index}"
# Update label display
if sprite_index in labels:
label_display.text = f"Label: {labels[sprite_index]}"
label_display.fill_color = BUTTON_SELECTED
else:
label_display.text = "Label: (none)"
label_display.fill_color = LABEL_COLOR
def on_cell_click(cell):
"""Handle clicking on grid cells to apply labels"""
global labels
x, y = int(cell[0]), int(cell[1])
sprite_index = y * GRID_COLS + x
if selected_label:
labels[sprite_index] = selected_label
print(f"Labeled sprite {sprite_index} as '{selected_label}'")
# Update display if this is the current sprite
if sprite_index == current_sprite_index:
label_display.text = f"Label: {selected_label}"
label_display.fill_color = BUTTON_SELECTED
grid.on_cell_enter = on_cell_enter
grid.on_cell_click = on_cell_click
# Submit button click handler
def on_submit_click(x, y, button):
"""Handle submit button click"""
add_new_label(input_buffer)
#return True
submit_btn.on_click = on_submit_click
# Input field click handler (activate text input)
def on_input_click(x, y, button):
"""Handle input field click to activate typing"""
global input_active
input_active = True
input_field.fill_color = INPUT_ACTIVE
#return True
input_field.on_click = on_input_click
# === Keyboard Handler ===
def on_keypress(key, state):
"""Handle keyboard input for text entry"""
global input_buffer, input_active
if state != "start":
return
# Escape clears input focus
if key == "Escape":
input_active = False
input_field.fill_color = INPUT_BG
return
# Enter submits the label
if key == "Return":
if input_active and input_buffer:
add_new_label(input_buffer)
input_active = False
input_field.fill_color = INPUT_BG
return
# Only process text input when field is active
if not input_active:
return
# Backspace
if key == "BackSpace":
if input_buffer:
input_buffer = input_buffer[:-1]
input_text.text = input_buffer
return
# Handle alphanumeric and common characters
# Map key names to characters
if len(key) == 1 and key.isalpha():
input_buffer += key.lower()
input_text.text = input_buffer
elif key.startswith("Num") and len(key) == 4:
# Numpad numbers
input_buffer += key[3]
input_text.text = input_buffer
elif key == "Space":
input_buffer += " "
input_text.text = input_buffer
elif key == "Minus":
input_buffer += "-"
input_text.text = input_buffer
elif key == "Period":
input_buffer += "."
input_text.text = input_buffer
scene.on_key = on_keypress
# === Instructions Caption ===
#instructions = mcrfpy.Caption(
# pos=(GRID_X, GRID_Y + GRID_HEIGHT + 20),
# text="Hover: preview sprite | Click grid: apply selected label | Type: add new labels"
#)
#instructions.font_size = 12
#instructions.fill_color = mcrfpy.Color(150, 150, 160)
#ui.append(instructions)
#
#instructions2 = mcrfpy.Caption(
# pos=(GRID_X, GRID_Y + GRID_HEIGHT + 40),
# text="Console: labels (dict), json.dump(labels, open('labels.json','w'))"
#)
#instructions2.font_size = 12
#instructions2.fill_color = mcrfpy.Color(150, 150, 160)
#ui.append(instructions2)
# Activate the scene
scene.activate()
#print("=== Sprite Labeler Tool ===")
#print(f"Texture: {TEXTURE_PATH}")
#print(f"Grid: {GRID_COLS}x{GRID_ROWS} = {GRID_COLS * GRID_ROWS} sprites")
#print("")
#print("Usage:")
#print(" - Hover over grid to preview sprites")
#print(" - Click a label button to select it")
#print(" - Click on grid cells to apply the selected label")
#print(" - Type in the text field to add new labels")
#print("")
#print("Console commands:")
#print(" labels # View all labels")
#print(" labels[42] = 'custom' # Manual labeling")
#print(" import json")
#print(" json.dump(labels, open('sprite_labels.json', 'w'), indent=2) # Save")
#print(" labels.update(json.load(open('sprite_labels.json'))) # Load")