| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- class_name AssetEditor
- extends AssetRipper
- const CUR_SPRITE_TEXT: String = "Current Sprite:\n%s"
- var list_index: int = 0
- var sprite_list: Array
- var cached_sprites: Dictionary[String, Dictionary]
- var tile_atlas: Texture
- var rom_provided: bool
- var source_path: String
- var tile_index: int = 0
- var columns: int = 4
- var sheet_size := Vector2i(16, 16)
- var palette_base: String = "Tile"
- var palettes: Dictionary
- var tiles: Dictionary
- @onready var rom_required: Label = %RomRequired
- @onready var file_dialog: FileDialog = %FileDialog
- @onready var image_preview: TextureRect = %ImagePreview
- @onready var green_preview: TextureRect = %GreenPreview
- @onready var tiles_preview: TextureRect = %TilesPreview
- @onready var cur_sprite: Label = %CurSprite
- @onready var preview: Sprite2D = %Preview
- @onready var buttons: HBoxContainer = %Buttons
- @onready var palette_override: LineEdit = %PaletteOverride
- @onready var scroll_container: ScrollContainer = %ScrollContainer
- @onready var json_container: VBoxContainer = %JSONContainer
- @onready var json_edit: TextEdit = %JSONEdit
- func _ready() -> void:
- Global.get_node("GameHUD").hide()
- if Global.rom_path != "":
- on_file_selected(Global.rom_path)
- func _exit_tree() -> void:
- Global.get_node("GameHUD").show()
- func _unhandled_input(event: InputEvent) -> void:
- if not rom_provided:
- return
-
- if event is InputEventMouseMotion:
- var is_snapped = not Input.is_action_pressed("editor_cam_fast")
- var mouse_pos: Vector2 = event.position + Vector2(
- scroll_container.scroll_horizontal,
- scroll_container.scroll_vertical
- ) + Vector2(-4, -8)
- preview.position = Vector2i(mouse_pos).snappedi(8 if is_snapped else 1)
-
- preview.visible = true
- if preview.position.x + 8 > sheet_size.x:
- preview.visible = false
- if preview.position.y + 8 > sheet_size.y:
- preview.visible = false
-
- if not preview.is_visible_in_tree(): return
-
- var direction: int = 0
- if event.is_action_pressed("editor_cam_left"): direction = -1
- if event.is_action_pressed("editor_cam_right"): direction = 1
- if event.is_action_pressed("pick_tile"):
- if tiles.has(preview.position):
- tile_index = tiles[preview.position].index
- preview.flip_h = tiles[preview.position].flip_h
- preview.flip_v = tiles[preview.position].flip_v
- if Input.is_action_pressed("editor_select"):
- if tiles[preview.position].has("palette"):
- palette_override.text = tiles[preview.position]["palette"]
- else:
- palette_override.text = ""
- preview.region_rect = Rect2i(index_to_coords(tile_index, 32) * 8, Vector2i(8, 8))
- if direction != 0:
- var multiply = 1 if not Input.is_action_pressed("editor_cam_fast") else 8
- tile_index = wrapi(tile_index + direction * multiply, 0, 512)
- preview.region_rect = Rect2i(index_to_coords(tile_index, 32) * 8, Vector2i(8, 8))
-
- if event.is_action_pressed("jump_0"): preview.flip_h = not preview.flip_h
- if event.is_action_pressed("run_0"): preview.flip_v = not preview.flip_v
-
- var left_click: bool = event.is_action_pressed("mb_left")
- var right_click: bool = event.is_action_pressed("mb_right")
- if left_click or right_click:
- if left_click:
- tiles[preview.position] = {
- "index": tile_index,
- "flip_h": preview.flip_h,
- "flip_v": preview.flip_v
- }
- if not palette_override.text.is_empty():
- tiles[preview.position]["palette"] = palette_override.text
- else:
- tiles.erase(preview.position)
-
- update_edited_image()
- func _process(_delta: float) -> void:
- if Input.is_action_pressed("editor_select") == false:
- return
- var left_click: bool = Input.is_action_pressed("mb_left")
- var right_click: bool = Input.is_action_pressed("mb_right")
- if left_click or right_click:
- if left_click:
- tiles[preview.position] = {
- "index": tile_index,
- "flip_h": preview.flip_h,
- "flip_v": preview.flip_v
- }
- if not palette_override.text.is_empty():
- tiles[preview.position]["palette"] = palette_override.text
- else:
- tiles.erase(preview.position)
-
- update_edited_image()
- func update_edited_image() -> void:
- var tiles_images: Array[Image] = get_tile_images(image_preview.texture.get_image().get_size(), tiles, palettes)
- tiles_preview.texture = ImageTexture.create_from_image(tiles_images[0])
- green_preview.texture = ImageTexture.create_from_image(tiles_images[1])
- func get_tile_images(
- img_size: Vector2i, tile_list: Dictionary, palette_lists: Dictionary
- ) -> Array[Image]:
- var image := Image.create(img_size.x, img_size.y, false, Image.FORMAT_RGBA8)
- var green_image := Image.create(img_size.x, img_size.y, false, Image.FORMAT_RGBA8)
-
- for palette_name in palette_lists.keys():
- var cur_column: int = 0
- var offset := Vector2.ZERO
-
- var pal_json: String = FileAccess.get_file_as_string(
- PALETTES_FOLDER % [DEFAULT_PALETTE_GROUP, palette_name])
- var pal_dict: Dictionary = JSON.parse_string(pal_json).palettes
-
- for palette_id: String in palette_lists[palette_name]:
- var palette: Array = pal_dict.get(palette_id, PREVIEW_PALETTE)
-
- for tile_pos: Vector2 in tile_list:
- var tile_dict: Dictionary = tile_list[tile_pos]
- var tile_palette: String = tile_dict.get("palette", palette_base)
- if tile_palette == palette_name:
- var destination: Vector2 = tile_pos + offset
- if destination.x < img_size.x and destination.y < img_size.y:
- draw_green(green_image, destination)
- draw_tile(
- false,
- image,
- tile_dict.get("index", 0),
- destination,
- palette,
- tile_dict.get("flip_h", false),
- tile_dict.get("flip_v", false)
- )
- cur_column += 1
- if cur_column >= columns:
- cur_column = 0
- offset.x = 0
- offset.y += sheet_size.y
- else:
- offset.x += sheet_size.x
- return [image, green_image]
- func on_file_selected(path: String) -> void:
- rom = FileAccess.get_file_as_bytes(path)
- prg_rom_size = rom[4] * 16384
- chr_rom = rom.slice(16 + prg_rom_size)
-
- rom_provided = true
- rom_required.hide()
- buttons.show()
-
- # setup sprite atlas for placing tiles
- var atlas := Image.create(256, 256, false, Image.FORMAT_RGBA8)
- for index in range(512):
- var pos: Vector2i = index_to_coords(index, 32) * 8
- draw_tile(false, atlas, index, pos, PREVIEW_PALETTE)
- preview.texture = ImageTexture.create_from_image(atlas)
- #
-
- var list_json: String = FileAccess.get_file_as_string(SPRITE_LIST_PATH)
- var list_dict: Dictionary = JSON.parse_string(list_json)
- sprite_list = list_dict.get("sprites", [])
-
- cycle_list(0)
- func load_sprite(sprite_dict: Dictionary) -> void:
- source_path = sprite_dict.source_path
-
- if source_path.begins_with("res://"):
- image_preview.texture = load(source_path)
- else:
- var image := Image.load_from_file(source_path)
- image_preview.texture = ImageTexture.create_from_image(image)
- cur_sprite.text = CUR_SPRITE_TEXT % source_path.get_file()
-
- columns = str_to_var(sprite_dict.get("columns", "4"))
- sheet_size = str_to_var(sprite_dict.get("sheet_size", "Vector2i(16, 16)"))
- palette_base = sprite_dict.get("palette_base", "Tile")
-
- var palettes_var: Variant = str_to_var(sprite_dict.get("palettes", var_to_str(get_default_palettes())))
- if typeof(palettes_var) == TYPE_ARRAY:
- palettes = {}
- palettes[palette_base] = palettes_var
- elif typeof(palettes_var) == TYPE_DICTIONARY:
- palettes = palettes_var
-
- tiles = str_to_var(sprite_dict.get("tiles", "{}"))
-
- update_palettes()
- update_edited_image()
- func save_sprite() -> void:
- var sprite_dict: Dictionary = get_as_dict()
- var destination_path: String = png_path_to_json(sprite_dict.source_path)
-
- var json_string: String = JSON.stringify(sprite_dict)
- DirAccess.make_dir_recursive_absolute(destination_path.get_base_dir())
- var file: FileAccess = FileAccess.open(destination_path, FileAccess.WRITE)
- file.store_line(json_string)
- file.close()
-
- # save green over original image
- var base_image: Image = image_preview.texture.get_image()
- var green_image: Image = green_preview.texture.get_image()
- for y in range(green_image.get_size().y):
- for x in range(green_image.get_size().x):
- var found_color: Color = green_image.get_pixel(x, y)
- if found_color.a > 0:
- base_image.set_pixel(x, y, found_color)
- base_image.save_png(sprite_dict.source_path)
- func cycle_list(add_index: int) -> void:
- if add_index != 0:
- cached_sprites[sprite_list[list_index]] = get_as_dict()
- list_index = wrapi(list_index + add_index, 0, sprite_list.size())
-
- if sprite_list[list_index] in cached_sprites:
- load_sprite(cached_sprites[sprite_list[list_index]])
- else:
- var json_path: String = sprite_list[list_index].replace(
- "res://Assets/Sprites/", "res://Resources/AssetRipper/Sprites/"
- ).replace(".png", ".json")
- if FileAccess.file_exists(json_path):
- var json_string: String = FileAccess.get_file_as_string(json_path)
- load_sprite(JSON.parse_string(json_string))
- else:
- load_sprite({"source_path": sprite_list[list_index]})
- func get_as_dict() -> Dictionary:
- return {
- "source_path": source_path,
- "columns": var_to_str(columns),
- "sheet_size": var_to_str(sheet_size),
- "palette_base": palette_base,
- "palettes": var_to_str(palettes),
- "tiles": var_to_str(tiles)
- }
- func get_default_palettes() -> Dictionary:
- var pal_json: String = FileAccess.get_file_as_string(
- PALETTES_FOLDER % [DEFAULT_PALETTE_GROUP, palette_base])
- var pal_dict: Dictionary = JSON.parse_string(pal_json)
- var default_palettes: Array = pal_dict.get("palettes", []).keys()
- return {palette_base: default_palettes}
- func update_palettes(load_textedit: bool = false) -> void:
- if load_textedit:
- var dict: Dictionary = JSON.parse_string(json_edit.text)
- columns = dict.get("columns", 1)
- var size_array: Array = dict.get("sheet_size", [16, 16])
- sheet_size = Vector2i(size_array[0], size_array[1])
- palette_base = dict.get("palette_base", "Tile")
- var pal_var: Variant = dict.get("palettes", get_default_palettes())
- if typeof(pal_var) == TYPE_ARRAY:
- palettes = {}
- palettes[palette_base] = pal_var
- elif typeof(pal_var) == TYPE_DICTIONARY:
- palettes = pal_var
- update_edited_image()
-
- json_edit.text = JSON.stringify(
- {
- "columns": columns,
- "sheet_size": [sheet_size.x, sheet_size.y],
- "palette_base": palette_base,
- "palettes": palettes
- }, "\t", false)
- func toggle_palettes_view(toggled_on: bool) -> void:
- json_container.visible = toggled_on
- scroll_container.visible = not toggled_on
- func draw_green(
- image: Image,
- pos: Vector2i
- ) -> void:
- for y in range(8):
- for x in range(8):
- image.set_pixelv(Vector2i(x, y) + pos, Color.GREEN)
|