"""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")