commit 277fb03a69e46837bdfe6d9f7116066da95994d8 Author: Eric Date: Tue May 28 11:03:13 2024 -0500 init diff --git a/.aseprite_wizard_history b/.aseprite_wizard_history new file mode 100644 index 0000000..f22f0aa --- /dev/null +++ b/.aseprite_wizard_history @@ -0,0 +1 @@ +{"import_date":"2024-04-23 06:00:10","options":{"do_not_create_resource":true,"exception_pattern":"","export_mode":0,"only_visible_layers":false,"output_filename":"","output_folder":"res://assets"},"output_location":"res://assets","source_file":"res://assets/aseprite/luminaCity.aseprite"} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4709183 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Godot 4+ specific ignores +.godot/ diff --git a/addons/AsepriteWizard/aseprite/aseprite.gd b/addons/AsepriteWizard/aseprite/aseprite.gd new file mode 100644 index 0000000..a3ebdb5 --- /dev/null +++ b/addons/AsepriteWizard/aseprite/aseprite.gd @@ -0,0 +1,283 @@ +@tool +extends RefCounted + +var _config + +func init(config): + _config = config + +# +# Output: +# { +# "data_file": file path to the json file +# "sprite_sheet": file path to the raw image file +# } +func export_file(file_name: String, output_folder: String, options: Dictionary) -> Dictionary: + var exception_pattern = options.get('exception_pattern', "") + var only_visible_layers = options.get('only_visible_layers', false) + var output_name = file_name if options.get('output_filename') == "" else options.get('output_filename', file_name) + var first_frame_only = options.get("first_frame_only", false) + var basename = _get_file_basename(output_name) + var output_dir = ProjectSettings.globalize_path(output_folder) + var data_file = "%s/%s.json" % [output_dir, basename] + var sprite_sheet = "%s/%s.png" % [output_dir, basename] + var output = [] + var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet) + + if not only_visible_layers: + arguments.push_front("--all-layers") + + if first_frame_only: + arguments.push_front("'[0, 0]'") + arguments.push_front("--frame-range") + + _add_sheet_type_arguments(arguments, options) + + _add_ignore_layer_arguments(file_name, arguments, exception_pattern) + + var local_sprite_sheet_path = ProjectSettings.localize_path(sprite_sheet) + var is_new = not ResourceLoader.exists(local_sprite_sheet_path) + + var exit_code = _execute(arguments, output) + if exit_code != 0: + printerr('aseprite: failed to export spritesheet') + printerr(output) + return {} + + return { + "data_file": ProjectSettings.localize_path(data_file), + "sprite_sheet": local_sprite_sheet_path, + "is_first_import": is_new, + } + + +func export_layers(file_name: String, output_folder: String, options: Dictionary) -> Array: + var exception_pattern = options.get('exception_pattern', "") + var only_visible_layers = options.get('only_visible_layers', false) + var basename = _get_file_basename(file_name) + var layers = list_layers(file_name, only_visible_layers) + var exception_regex = _compile_regex(exception_pattern) + + var output = [] + + for layer in layers: + if layer != "" and (not exception_regex or exception_regex.search(layer) == null): + output.push_back(export_layer(file_name, layer, output_folder, options)) + + return output + + +func export_layer(file_name: String, layer_name: String, output_folder: String, options: Dictionary) -> Dictionary: + var output_prefix = options.get('output_filename', "").strip_edges() + var output_dir = output_folder.replace("res://", "./").strip_edges() + var data_file = "%s/%s%s.json" % [output_dir, output_prefix, layer_name] + var sprite_sheet = "%s/%s%s.png" % [output_dir, output_prefix, layer_name] + var first_frame_only = options.get("first_frame_only", false) + var output = [] + var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet) + arguments.push_front(layer_name) + arguments.push_front("--layer") + + if first_frame_only: + arguments.push_front("'[0, 0]'") + arguments.push_front("--frame-range") + + _add_sheet_type_arguments(arguments, options) + + var local_sprite_sheet_path = ProjectSettings.localize_path(sprite_sheet) + var is_new = not ResourceLoader.exists(local_sprite_sheet_path) + + var exit_code = _execute(arguments, output) + if exit_code != 0: + print('aseprite: failed to export layer spritesheet') + print(output) + return {} + + return { + "data_file": ProjectSettings.localize_path(data_file), + "sprite_sheet": local_sprite_sheet_path, + "is_first_import": is_new, + } + + +func _add_ignore_layer_arguments(file_name: String, arguments: Array, exception_pattern: String): + var layers = _get_exception_layers(file_name, exception_pattern) + if not layers.is_empty(): + for l in layers: + arguments.push_front(l) + arguments.push_front('--ignore-layer') + +func _add_sheet_type_arguments(arguments: Array, options : Dictionary): + var column_count : int = options.get("column_count", 0) + if column_count > 0: + arguments.push_back("--merge-duplicates") # Yes, this is undocumented + arguments.push_back("--sheet-columns") + arguments.push_back(column_count) + else: + arguments.push_back("--sheet-pack") + + +func _get_exception_layers(file_name: String, exception_pattern: String) -> Array: + var layers = list_layers(file_name) + var regex = _compile_regex(exception_pattern) + if regex == null: + return [] + + var exception_layers = [] + for layer in layers: + if regex.search(layer) != null: + exception_layers.push_back(layer) + + return exception_layers + + +func list_layers(file_name: String, only_visible = false) -> Array: + var output = [] + var arguments = ["-b", "--list-layers", file_name] + + if not only_visible: + arguments.push_front("--all-layers") + + var exit_code = _execute(arguments, output) + + if exit_code != 0: + printerr('aseprite: failed listing layers') + printerr(output) + return [] + + if output.is_empty(): + return output + + var raw = output[0].split('\n') + var sanitized = [] + for s in raw: + sanitized.append(s.strip_edges()) + return sanitized + + +func list_slices(file_name: String) -> Array: + var output = [] + var arguments = ["-b", "--list-slices", file_name] + + var exit_code = _execute(arguments, output) + + if exit_code != 0: + printerr('aseprite: failed listing slices') + printerr(output) + return [] + + if output.is_empty(): + return output + + var raw = output[0].split('\n') + var sanitized = [] + for s in raw: + sanitized.append(s.strip_edges()) + return sanitized + + +func _export_command_common_arguments(source_name: String, data_path: String, spritesheet_path: String) -> Array: + return [ + "-b", + "--list-tags", + "--list-slices", + "--data", + data_path, + "--format", + "json-array", + "--sheet", + spritesheet_path, + source_name + ] + + +func _execute(arguments, output): + return OS.execute(_aseprite_command(), arguments, output, true, true) + + +func _aseprite_command() -> String: + return _config.is_command_or_control_pressed() + + +func _get_file_basename(file_path: String) -> String: + return file_path.get_file().trim_suffix('.%s' % file_path.get_extension()) + + +func _compile_regex(pattern): + if pattern == "": + return + + var rgx = RegEx.new() + if rgx.compile(pattern) == OK: + return rgx + + printerr('exception regex error') + + +func test_command(): + var exit_code = OS.execute(_aseprite_command(), ['--version'], [], true) + return exit_code == 0 + + +func is_valid_spritesheet(content): + return content.has("frames") and content.has("meta") and content.meta.has('image') + + +func get_content_frames(content): + return content.frames if typeof(content.frames) == TYPE_ARRAY else content.frames.values() + + +func get_slice_rect(content: Dictionary, slice_name: String) -> Variant: + if not content.has("meta") or not content.meta.has("slices"): + return null + for slice in content.meta.slices: + if slice.name == slice_name: + if slice.keys.size() > 0: + var p = slice.keys[0].bounds + return Rect2(p.x, p.y, p.w, p.h) + return null + + +## +## Exports tileset layers +## +## Return (dictionary): +## data_file: path to aseprite generated JSON file +## sprite_sheet: localized path to spritesheet file +func export_tileset_texture(file_name: String, output_folder: String, options: Dictionary) -> Dictionary: + var exception_pattern = options.get('exception_pattern', "") + var only_visible_layers = options.get('only_visible_layers', false) + var output_name = file_name if options.get('output_filename') == "" else options.get('output_filename', file_name) + var basename = _get_file_basename(output_name) + var output_dir = ProjectSettings.globalize_path(output_folder) + var data_path = "%s/%s.json" % [output_dir, basename] + var sprite_sheet = "%s/%s.png" % [output_dir, basename] + var output = [] + + var arguments = [ + "-b", + "--export-tileset", + "--data", + data_path, + "--format", + "json-array", + "--sheet", + sprite_sheet, + file_name + ] + + if not only_visible_layers: + arguments.push_front("--all-layers") + + _add_ignore_layer_arguments(file_name, arguments, exception_pattern) + + var exit_code = _execute(arguments, output) + if exit_code != 0: + printerr('aseprite: failed to export spritesheet') + printerr(output) + return {} + + return { + "data_file": ProjectSettings.localize_path(data_path), + "sprite_sheet": ProjectSettings.localize_path(sprite_sheet) + } diff --git a/addons/AsepriteWizard/aseprite/file_exporter.gd b/addons/AsepriteWizard/aseprite/file_exporter.gd new file mode 100644 index 0000000..4867c38 --- /dev/null +++ b/addons/AsepriteWizard/aseprite/file_exporter.gd @@ -0,0 +1,146 @@ +@tool +extends RefCounted + +var result_code = preload("../config/result_codes.gd") +var _aseprite = preload("aseprite.gd").new() + +enum { + FILE_EXPORT_MODE, + LAYERS_EXPORT_MODE +} + +func init(config): + _aseprite.init(config) + +## +## Generate Aseprite spritesheet and data files for source. +## +## Options: +## output_folder (string) +## output_filename (string, optional) +## export_mode (FILE_EXPORT_MODE, LAYERS_EXPORT_MODE) default: FILE_EXPORT_MODE +## exception_pattern (string, optional) +## only_visible_layers (boolean, optional) +## +## Return: +## Array +## Dictionary +## sprite_sheet: sprite sheet path +## data_file: json file path +## +func generate_aseprite_files(source_file: String, options: Dictionary): + var check = _initial_checks(source_file, options) + + if check != result_code.SUCCESS: + return result_code.error(check) + + match options.get('export_mode', FILE_EXPORT_MODE): + FILE_EXPORT_MODE: + var output = _aseprite.export_file(source_file, options.output_folder, options) + if output.is_empty(): + return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED) + return result_code.result([output]) + LAYERS_EXPORT_MODE: + var output = _aseprite.export_layers(source_file, options.output_folder, options) + if output.is_empty(): + return result_code.error(result_code.ERR_NO_VALID_LAYERS_FOUND) + return result_code.result(output) + _: + return result_code.error(result_code.ERR_UNKNOWN_EXPORT_MODE) + + +## +## Generate Aseprite spritesheet and data file for source. +## +## Options: +## output_folder (string) +## output_filename (string, optional) +## layer (string, optional) +## exception_pattern (string, optional) +## only_visible_layers (boolean, optional) +## +## Return: +## Dictionary +## sprite_sheet: sprite sheet path +## data_file: json file path +## +func generate_aseprite_file(source_file: String, options: Dictionary) -> Dictionary: + var check = _initial_checks(source_file, options) + + if check != result_code.SUCCESS: + return result_code.error(check) + + var output + + if options.get("layer", "") == "": + output = _aseprite.export_file(source_file, options.output_folder, options) + else: + output = _aseprite.export_layer(source_file, options.layer, options.output_folder, options) + + if output.is_empty(): + return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED) + + return result_code.result(output) + + +## +## Generate a spritesheet with all tilesets in the file +## +## Options: +## exception_pattern (string) +## only_visible_layers (boolean) +## output_filename (string) +## output_folder (string) +## +## Return: +## Dictionary +## sprite_sheet: sprite sheet path +## data_file: json file path +## +func generate_tileset_files(source_file: String, options = {}) -> Dictionary: + var check = _initial_checks(source_file, options) + + if check != result_code.SUCCESS: + return result_code.error(check) + + var output = _aseprite.export_tileset_texture(source_file, options.output_folder, options) + + if output.is_empty(): + return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED) + + return result_code.result(output) + + +## +## Perform initial source file and output folder checks +## +func _initial_checks(source: String, options: Dictionary) -> int: + if not _aseprite.test_command(): + return result_code.ERR_ASEPRITE_CMD_NOT_FOUND + + if not FileAccess.file_exists(source): + return result_code.ERR_SOURCE_FILE_NOT_FOUND + + if not DirAccess.dir_exists_absolute(options.output_folder): + return result_code.ERR_OUTPUT_FOLDER_NOT_FOUND + + return result_code.SUCCESS + + +## +## Load Aseprite source data file and fails if the +## content is not valid +## +func load_json_content(source_file: String) -> Dictionary: + var file = FileAccess.open(source_file, FileAccess.READ) + if file == null: + return result_code.error(FileAccess.get_open_error()) + var test_json_conv = JSON.new() + test_json_conv.parse(file.get_as_text()) + + var content = test_json_conv.get_data() + + if not _aseprite.is_valid_spritesheet(content): + return result_code.error(result_code.ERR_INVALID_ASEPRITE_SPRITESHEET) + + return result_code.result(content) diff --git a/addons/AsepriteWizard/config/config.gd b/addons/AsepriteWizard/config/config.gd new file mode 100644 index 0000000..8b69075 --- /dev/null +++ b/addons/AsepriteWizard/config/config.gd @@ -0,0 +1,295 @@ +@tool +extends RefCounted + +# GLOBAL SETTINGS +const _CONFIG_SECTION_KEY = 'aseprite' +const _COMMAND_KEY = 'aseprite/general/command_path' + +# PROJECT SETTINGS + +# animation import defaults +const _DEFAULT_EXCLUSION_PATTERN_KEY = 'aseprite/animation/layers/exclusion_pattern' +const _DEFAULT_ONLY_VISIBLE_LAYERS = 'aseprite/animation/layers/only_include_visible_layers_by_default' +const _DEFAULT_LOOP_EX_PREFIX = '_' +const _LOOP_ENABLED = 'aseprite/animation/loop/enabled' +const _LOOP_EXCEPTION_PREFIX = 'aseprite/animation/loop/exception_prefix' +const _USE_METADATA = 'aseprite/animation/storage/use_metadata' + + +# cleanup +const _REMOVE_SOURCE_FILES_KEY = 'aseprite/import/cleanup/remove_json_file' +const _SET_VISIBLE_TRACK_AUTOMATICALLY = 'aseprite/import/cleanup/automatically_hide_sprites_not_in_animation' + + +# automatic importer +const _IMPORTER_ENABLE_KEY = 'aseprite/import/import_plugin/enable_automatic_importer' +const _DEFAULT_IMPORTER_KEY = 'aseprite/import/import_plugin/default_automatic_importer' + +const IMPORTER_SPRITEFRAMES_NAME = "SpriteFrames" +const IMPORTER_NOOP_NAME = "No Import" +const IMPORTER_TILESET_TEXTURE_NAME = "Tileset Texture" +const IMPORTER_STATIC_TEXTURE_NAME = "Static Texture" + +# wizard history +const _HISTORY_CONFIG_FILE_CFG_KEY = 'aseprite/wizard/history/cache_file_path' +const _HISTORY_SINGLE_ENTRY_KEY = 'aseprite/wizard/history/keep_one_entry_per_source_file' +const _DEFAULT_HISTORY_CONFIG_FILE_PATH = 'res://.aseprite_wizard_history' + +# IMPORT SETTINGS +const _I_LAST_SOURCE_PATH_KEY = 'i_source' +const _I_LAST_OUTPUT_DIR_KEY = 'i_output' +const _I_SHOULD_SPLIT_LAYERS_KEY = 'i_split_layers' +const _I_EXCEPTIONS_KEY = 'i_exceptions_key' +const _I_ONLY_VISIBLE_LAYERS_KEY = 'i_only_visible_layers' +const _I_CUSTOM_NAME_KEY = 'i_custom_name' +const _I_DO_NOT_CREATE_RES_KEY = 'i_disable_resource_creation' + +# export +const _EXPORTER_ENABLE_KEY = 'aseprite/animation/storage/enable_metadata_removal_on_export' + +var _editor_settings: EditorSettings + +# INTERFACE SETTINGS +var _plugin_icons: Dictionary + +####################################################### +# GLOBAL CONFIGS +###################################################### + +func default_command() -> String: + return 'aseprite' + + +func is_command_or_control_pressed() -> String: + var command = _editor_settings.get(_COMMAND_KEY) if _editor_settings.has_setting(_COMMAND_KEY) else "" + return command if command != "" else default_command() + + +####################################################### +# PROJECT SETTINGS +###################################################### + +# remove this config in the next major version +func is_importer_enabled() -> bool: + return _get_project_setting(_IMPORTER_ENABLE_KEY, false) + + +func get_default_importer() -> String: + return _get_project_setting(_DEFAULT_IMPORTER_KEY, IMPORTER_SPRITEFRAMES_NAME if is_importer_enabled() else IMPORTER_NOOP_NAME) + + +func is_exporter_enabled() -> bool: + return _get_project_setting(_EXPORTER_ENABLE_KEY, true) + + +func should_remove_source_files() -> bool: + return _get_project_setting(_REMOVE_SOURCE_FILES_KEY, true) + + +func is_default_animation_loop_enabled() -> bool: + return _get_project_setting(_LOOP_ENABLED, true) + + +func get_animation_loop_exception_prefix() -> String: + return _get_project_setting(_LOOP_EXCEPTION_PREFIX, _DEFAULT_LOOP_EX_PREFIX) + +func is_use_metadata_enabled() -> bool: + return _get_project_setting(_USE_METADATA, true) + + +func get_default_exclusion_pattern() -> String: + return _get_project_setting(_DEFAULT_EXCLUSION_PATTERN_KEY, "") + + +func should_include_only_visible_layers_by_default() -> bool: + return _get_project_setting(_DEFAULT_ONLY_VISIBLE_LAYERS, false) + + +func is_single_file_history() -> bool: + return ProjectSettings.get(_HISTORY_SINGLE_ENTRY_KEY) == true + + +func get_import_history() -> Array: + var history = [] + var history_path := _get_history_file_path() + + if not FileAccess.file_exists(history_path): + return history + + var file_object = FileAccess.open(history_path, FileAccess.READ) + + while not file_object.eof_reached(): + var line = file_object.get_line() + if line: + var test_json_conv = JSON.new() + test_json_conv.parse(line) + history.push_back(test_json_conv.get_data()) + + return history + + +func is_set_visible_track_automatically_enabled() -> bool: + return _get_project_setting(_SET_VISIBLE_TRACK_AUTOMATICALLY, false) + +# history is saved and retrieved line-by-line so +# file becomes version control friendly +func save_import_history(history: Array): + var file = FileAccess.open(_get_history_file_path(), FileAccess.WRITE) + for entry in history: + file.store_line(JSON.new().stringify(entry)) + file = null + + +func _get_history_file_path() -> String: + return _get_project_setting(_HISTORY_CONFIG_FILE_CFG_KEY, _DEFAULT_HISTORY_CONFIG_FILE_PATH) + + +####################################################### +# IMPORT CONFIGS +###################################################### +func get_last_source_path() -> String: + return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_LAST_SOURCE_PATH_KEY, "") + + +func set_last_source_path(source_path: String) -> void: + _editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_LAST_SOURCE_PATH_KEY, source_path) + + +func get_last_output_path() -> String: + return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_LAST_OUTPUT_DIR_KEY, "") + + +func set_last_output_path(output_path: String) -> void: + _editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_LAST_OUTPUT_DIR_KEY, output_path) + + +func should_split_layers() -> bool: + return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_SHOULD_SPLIT_LAYERS_KEY, false) + + +func set_split_layers(should_split: bool) -> void: + _editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_SHOULD_SPLIT_LAYERS_KEY, false) + + +func get_exception_pattern() -> String: + return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_EXCEPTIONS_KEY, "") + + +func set_exception_pattern(pattern: String) -> void: + _editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_EXCEPTIONS_KEY, pattern) + + +func should_include_only_visible_layers() -> bool: + return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_ONLY_VISIBLE_LAYERS_KEY, false) + + +func set_include_only_visible_layers(include_only_visible: bool) -> void: + _editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_ONLY_VISIBLE_LAYERS_KEY, include_only_visible) + + +func get_last_custom_name() -> String: + return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_CUSTOM_NAME_KEY, "") + + +func set_custom_name(custom_name: String) -> void: + _editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_CUSTOM_NAME_KEY, custom_name) + + +func should_not_create_resource() -> bool: + return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_DO_NOT_CREATE_RES_KEY, false) + + +func set_do_not_create_resource(do_no_create: bool) -> void: + _editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_DO_NOT_CREATE_RES_KEY, do_no_create) + +####################################################### +# INTERFACE SETTINGS +###################################################### + +func set_icons(plugin_icons: Dictionary) -> void: + _plugin_icons = plugin_icons + + +func get_icon(icon_name: String) -> Texture2D: + return _plugin_icons[icon_name] + + +####################################################### +# INITIALIZATION +###################################################### +func initialize_project_settings(): + _initialize_project_cfg(_DEFAULT_EXCLUSION_PATTERN_KEY, "", TYPE_STRING) + _initialize_project_cfg(_DEFAULT_ONLY_VISIBLE_LAYERS, false, TYPE_BOOL) + _initialize_project_cfg(_LOOP_ENABLED, true, TYPE_BOOL) + _initialize_project_cfg(_LOOP_EXCEPTION_PREFIX, _DEFAULT_LOOP_EX_PREFIX, TYPE_STRING) + _initialize_project_cfg(_USE_METADATA, true, TYPE_BOOL) + + _initialize_project_cfg(_REMOVE_SOURCE_FILES_KEY, true, TYPE_BOOL) + _initialize_project_cfg( + _DEFAULT_IMPORTER_KEY, + IMPORTER_SPRITEFRAMES_NAME if is_importer_enabled() else IMPORTER_NOOP_NAME, + TYPE_STRING, + PROPERTY_HINT_ENUM, + "%s,%s,%s,%s" % [IMPORTER_NOOP_NAME, IMPORTER_SPRITEFRAMES_NAME, IMPORTER_TILESET_TEXTURE_NAME, IMPORTER_STATIC_TEXTURE_NAME] + ) + + _initialize_project_cfg(_EXPORTER_ENABLE_KEY, true, TYPE_BOOL) + + _initialize_project_cfg(_HISTORY_CONFIG_FILE_CFG_KEY, _DEFAULT_HISTORY_CONFIG_FILE_PATH, TYPE_STRING, PROPERTY_HINT_GLOBAL_FILE) + _initialize_project_cfg(_HISTORY_SINGLE_ENTRY_KEY, false, TYPE_BOOL) + + _initialize_project_cfg(_SET_VISIBLE_TRACK_AUTOMATICALLY, false, TYPE_BOOL) + + ProjectSettings.save() + + _initialize_editor_cfg(_COMMAND_KEY, default_command(), TYPE_STRING) + + +func clear_project_settings(): + var _all_settings = [ + _DEFAULT_EXCLUSION_PATTERN_KEY, + _LOOP_ENABLED, + _LOOP_EXCEPTION_PREFIX, + _USE_METADATA, + _REMOVE_SOURCE_FILES_KEY, + _DEFAULT_IMPORTER_KEY, + _EXPORTER_ENABLE_KEY, + _HISTORY_CONFIG_FILE_CFG_KEY, + _HISTORY_SINGLE_ENTRY_KEY, + _SET_VISIBLE_TRACK_AUTOMATICALLY, + _DEFAULT_ONLY_VISIBLE_LAYERS, + ] + for key in _all_settings: + ProjectSettings.clear(key) + ProjectSettings.save() + + +func _initialize_project_cfg(key: String, default_value, type: int, hint: int = PROPERTY_HINT_NONE, hint_string = null): + if not ProjectSettings.has_setting(key): + ProjectSettings.set(key, default_value) + ProjectSettings.set_initial_value(key, default_value) + ProjectSettings.add_property_info({ + "name": key, + "type": type, + "hint": hint, + "hint_string": hint_string, + }) + + +func _get_project_setting(key: String, default_value): + if not ProjectSettings.has_setting(key): + return default_value + + var p = ProjectSettings.get(key) + return p if p != null else default_value + + +func _initialize_editor_cfg(key: String, default_value, type: int, hint: int = PROPERTY_HINT_NONE): + if not _editor_settings.has_setting(key): + _editor_settings.set(key, default_value) + _editor_settings.set_initial_value(key, default_value, false) + _editor_settings.add_property_info({ + "name": key, + "type": type, + "hint": hint, + }) diff --git a/addons/AsepriteWizard/config/config_dialog.gd b/addons/AsepriteWizard/config/config_dialog.gd new file mode 100644 index 0000000..caeb64a --- /dev/null +++ b/addons/AsepriteWizard/config/config_dialog.gd @@ -0,0 +1,33 @@ +@tool +extends PopupPanel + +var _config + +@onready var _aseprite_command_field = $MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer/aseprite_command +@onready var _version_label = $MarginContainer/VBoxContainer/VBoxContainer/version_found + +func _ready(): + _aseprite_command_field.text = _config.is_command_or_control_pressed() + _version_label.modulate.a = 0 + + +func init(config): + _config = config + + +func _on_close_button_up(): + self.hide() + + +func _on_test_pressed(): + var output = [] + if _test_command(output): + _version_label.text = "%s found." % "\n".join(PackedStringArray(output)).strip_edges() + else: + _version_label.text = "Command not found." + _version_label.modulate.a = 1 + + +func _test_command(output): + var exit_code = OS.execute(_aseprite_command_field.text, ['--version'], output, true, true) + return exit_code == 0 diff --git a/addons/AsepriteWizard/config/config_dialog.tscn b/addons/AsepriteWizard/config/config_dialog.tscn new file mode 100644 index 0000000..56c92f0 --- /dev/null +++ b/addons/AsepriteWizard/config/config_dialog.tscn @@ -0,0 +1,80 @@ +[gd_scene load_steps=2 format=3 uid="uid://d0whlywijwa6s"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/config/config_dialog.gd" id="1"] + +[node name="config_dialog" type="PopupPanel"] +title = "Aseprite Wizard Config" +size = Vector2i(624, 236) +visible = true +unresizable = false +borderless = false +min_size = Vector2i(624, 236) +content_scale_mode = 1 +script = ExtResource("1") + +[node name="MarginContainer" type="MarginContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 4.0 +offset_top = 4.0 +offset_right = -532.0 +offset_bottom = -416.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 10 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 10 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +text = "This configuration moved. +- To edit the aseprite command path, go to Editor > Editor Settings > Aseprite. +- To edit project specific settings, go to Project > Project Settings > Aseprite." + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Aseprite Command" type="Label" parent="MarginContainer/VBoxContainer/VBoxContainer"] +layout_mode = 2 +tooltip_text = "Define the path for Aseprite command" +mouse_filter = 1 +text = "Aseprite Command Path" + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="aseprite_command" type="LineEdit" parent="MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +editable = false +caret_blink = true + +[node name="test" type="Button" parent="MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Test" + +[node name="version_found" type="Label" parent="MarginContainer/VBoxContainer/VBoxContainer"] +modulate = Color(1, 1, 1, 0) +layout_mode = 2 +size_flags_horizontal = 3 +text = "Aseprite version found" + +[node name="VBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +alignment = 2 + +[node name="close" type="Button" parent="MarginContainer/VBoxContainer/VBoxContainer2"] +layout_mode = 2 +text = "Close" + +[connection signal="pressed" from="MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer/test" to="." method="_on_test_pressed"] +[connection signal="button_up" from="MarginContainer/VBoxContainer/VBoxContainer2/close" to="." method="_on_close_button_up"] diff --git a/addons/AsepriteWizard/config/result_codes.gd b/addons/AsepriteWizard/config/result_codes.gd new file mode 100644 index 0000000..f3d7a02 --- /dev/null +++ b/addons/AsepriteWizard/config/result_codes.gd @@ -0,0 +1,37 @@ +@tool +extends RefCounted + +const SUCCESS = 0 +const ERR_ASEPRITE_CMD_NOT_FOUND = 1 +const ERR_SOURCE_FILE_NOT_FOUND = 2 +const ERR_OUTPUT_FOLDER_NOT_FOUND = 3 +const ERR_ASEPRITE_EXPORT_FAILED = 4 +const ERR_UNKNOWN_EXPORT_MODE = 5 +const ERR_NO_VALID_LAYERS_FOUND = 6 +const ERR_INVALID_ASEPRITE_SPRITESHEET = 7 + + +static func get_error_message(code: int): + match code: + ERR_ASEPRITE_CMD_NOT_FOUND: + return "Aseprite command failed. Please, check if the right command is in your PATH or configured through \"Project > Tools > Aseprite Wizard Config\"." + ERR_SOURCE_FILE_NOT_FOUND: + return "source file does not exist" + ERR_OUTPUT_FOLDER_NOT_FOUND: + return "output location does not exist" + ERR_ASEPRITE_EXPORT_FAILED: + return "unable to import file" + ERR_INVALID_ASEPRITE_SPRITESHEET: + return "aseprite generated bad data file" + ERR_NO_VALID_LAYERS_FOUND: + return "no valid layers found" + _: + return "import failed with code %d" % code + + +static func error(error_code: int): + return { "code": error_code, "content": null, "is_ok": false } + + +static func result(result): + return { "code": SUCCESS, "content": result, "is_ok": true } diff --git a/addons/AsepriteWizard/config/wizard_config.gd b/addons/AsepriteWizard/config/wizard_config.gd new file mode 100644 index 0000000..a709fe7 --- /dev/null +++ b/addons/AsepriteWizard/config/wizard_config.gd @@ -0,0 +1,82 @@ +@tool +extends RefCounted + +const WIZARD_CONFIG_META_NAME = "_aseprite_wizard_config_" +const WIZARD_CONFIG_MARKER = "aseprite_wizard_config" +const WIZARD_INTERFACE_CONFIG_META_NAME = "_aseprite_wizard_interface_config_" +const SEPARATOR = "|=" + +static func encode(object: Dictionary): + var text = "%s\n" % WIZARD_CONFIG_MARKER + + for prop in object: + text += "%s%s%s\n" % [prop, SEPARATOR, object[prop]] + + return Marshalls.utf8_to_base64(text) + + +static func decode(string: String): + var decoded = _decode_base64(string) + if not _is_wizard_config(decoded): + return null + + var cfg = decoded.split("\n") + var config = {} + for c in cfg: + var parts = c.split(SEPARATOR, 1) + if parts.size() == 2: + var key = parts[0].strip_edges() + var value = parts[1].strip_edges() + + #Convert bool properties + if key == "only_visible" or key == "op_exp": + match value: + "True": + config[key] = true + "False": + config[key] = false + _: + config[key] = false + else: + config[key] = value + + return config + + +static func _decode_base64(string: String): + if string != "": + return Marshalls.base64_to_utf8(string) + return null + + +static func _is_wizard_config(cfg) -> bool: + return cfg != null and cfg.begins_with(WIZARD_CONFIG_MARKER) + + +static func load_config(node:Node): + if node.has_meta(WIZARD_CONFIG_META_NAME): + return node.get_meta(WIZARD_CONFIG_META_NAME) + + return decode(node.editor_description) + + +static func save_config(node:Node, use_metadata:bool, cfg:Dictionary): + if use_metadata: + node.set_meta(WIZARD_CONFIG_META_NAME, cfg) + + #Delete config from editor_description + var decoded = _decode_base64(node.editor_description) + if _is_wizard_config(decoded): + node.editor_description = "" + else: + node.editor_description = encode(cfg) + + +static func load_interface_config(node: Node, default: Dictionary = {}) -> Dictionary: + if node.has_meta(WIZARD_INTERFACE_CONFIG_META_NAME): + return node.get_meta(WIZARD_INTERFACE_CONFIG_META_NAME) + return default + + +static func save_interface_config(node:Node, cfg:Dictionary) -> void: + node.set_meta(WIZARD_INTERFACE_CONFIG_META_NAME, cfg) diff --git a/addons/AsepriteWizard/creators/animation_player/animation_creator.gd b/addons/AsepriteWizard/creators/animation_player/animation_creator.gd new file mode 100644 index 0000000..0a782f3 --- /dev/null +++ b/addons/AsepriteWizard/creators/animation_player/animation_creator.gd @@ -0,0 +1,306 @@ +@tool +extends "../base_sprite_resource_creator.gd" + +var _DEFAULT_ANIMATION_LIBRARY = "" # GLOBAL + +func create_animations(target_node: Node, player: AnimationPlayer, aseprite_files: Dictionary, options: Dictionary): + var result = _import(target_node, player, aseprite_files, options) + + if result != result_code.SUCCESS: + printerr(result_code.get_error_message(result)) + + +func _import(target_node: Node, player: AnimationPlayer, aseprite_files: Dictionary, options: Dictionary): + var source_file = aseprite_files.data_file + var sprite_sheet = aseprite_files.sprite_sheet + var data = _aseprite_file_exporter.load_json_content(source_file) + + if not data.is_ok: + return data.code + + var content = data.content + + var context = {} + + if target_node is CanvasItem: + target_node.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + else: + target_node.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST + + _setup_texture(target_node, sprite_sheet, content, context, options.slice != "") + var result = _configure_animations(target_node, player, content, context, options) + if result != result_code.SUCCESS: + return result + + return _cleanup_animations(target_node, player, content, options) + + +func _load_texture(sprite_sheet: String) -> Texture2D: + var texture = ResourceLoader.load(sprite_sheet, 'Image', ResourceLoader.CACHE_MODE_IGNORE) + texture.take_over_path(sprite_sheet) + return texture + + +func _configure_animations(target_node: Node, player: AnimationPlayer, content: Dictionary, context: Dictionary, options: Dictionary): + var frames = _aseprite.get_content_frames(content) + var slice_rect = null + if options.slice != "": + options["slice_rect"] = _aseprite.get_slice_rect(content, options.slice) + + if not player.has_animation_library(_DEFAULT_ANIMATION_LIBRARY): + player.add_animation_library(_DEFAULT_ANIMATION_LIBRARY, AnimationLibrary.new()) + + if content.meta.has("frameTags") and content.meta.frameTags.size() > 0: + var result = result_code.SUCCESS + for tag in content.meta.frameTags: + var selected_frames = frames.slice(tag.from, tag.to + 1) + result = _add_animation_frames(target_node, player, tag.name, selected_frames, context, options, tag.direction, int(tag.get("repeat", -1))) + if result != result_code.SUCCESS: + break + return result + else: + return _add_animation_frames(target_node, player, "default", frames, context, options) + + +func _add_animation_frames(target_node: Node, player: AnimationPlayer, anim_name: String, frames: Array, context: Dictionary, options: Dictionary, direction = 'forward', repeat = -1): + var animation_name = anim_name + var library_name = _DEFAULT_ANIMATION_LIBRARY + var is_loopable = _config.is_default_animation_loop_enabled() + var slice_rect = options.get("slice_rect") + var is_importing_slice: bool = slice_rect != null + + var anim_tokens := anim_name.split("/") + + if anim_tokens.size() > 2: + push_error("Invalid animation name: %s" % animation_name) + return + elif anim_tokens.size() == 2: + library_name = anim_tokens[0] + animation_name = anim_tokens[1] + + if not _validate_animation_name(animation_name): + push_error("Invalid animation name: %s" % animation_name) + return + + # Create library if doesn't exist + if library_name != _DEFAULT_ANIMATION_LIBRARY and not player.has_animation_library(library_name): + player.add_animation_library(library_name, AnimationLibrary.new()) + + # Check loop + if animation_name.begins_with(_config.get_animation_loop_exception_prefix()): + animation_name = animation_name.substr(_config.get_animation_loop_exception_prefix().length()) + is_loopable = not is_loopable + + # Add library + if not player.get_animation_library(library_name).has_animation(animation_name): + player.get_animation_library(library_name).add_animation(animation_name, Animation.new()) + + var full_name = ( + animation_name if library_name == "" else "%s/%s" % [library_name, animation_name] + ) + + var animation = player.get_animation(full_name) + _cleanup_tracks(target_node, player, animation) + _create_meta_tracks(target_node, player, animation) + + var frame_track = _get_property_track_path(player, target_node, _get_frame_property(is_importing_slice)) + var frame_track_index = _create_track(target_node, animation, frame_track) + + if direction == "reverse" or direction == "pingpong_reverse": + frames.reverse() + + var animation_length = 0 + + var repetition = 1 + + if repeat != -1: + is_loopable = false + repetition = repeat + + for i in range(repetition): + for frame in frames: + var frame_key = _get_frame_key(target_node, frame, context, slice_rect) + animation.track_insert_key(frame_track_index, animation_length, frame_key) + animation_length += frame.duration / 1000 + + # Godot 4 has an Animation.LOOP_PINGPONG mode, however it does not + # behave like in Aseprite, so I'm keeping the custom implementation + if direction.begins_with("pingpong"): + var working_frames = frames.duplicate() + working_frames.remove_at(working_frames.size() - 1) + if is_loopable or (repetition > 1 and i < repetition - 1): + working_frames.remove_at(0) + working_frames.reverse() + + for frame in working_frames: + var frame_key = _get_frame_key(target_node, frame, context, slice_rect) + animation.track_insert_key(frame_track_index, animation_length, frame_key) + animation_length += frame.duration / 1000 + + # if keep_anim_length is enabled only adjust length if + # - there aren't other tracks besides metas and frame + # - the current animation is shorter than new one + if not options.keep_anim_length or (animation.get_track_count() == (_get_meta_prop_names().size() + 1) or animation.length < animation_length): + animation.length = animation_length + + animation.loop_mode = Animation.LOOP_LINEAR if is_loopable else Animation.LOOP_NONE + + return result_code.SUCCESS + + +const _INVALID_TOKENS := ["/", ":", ",", "["] + + +func _validate_animation_name(name: String) -> bool: + return not _INVALID_TOKENS.any(func(token: String): return token in name) + + +func _create_track(target_node: Node, animation: Animation, track: String): + var track_index = animation.find_track(track, Animation.TYPE_VALUE) + + if track_index != -1: + animation.remove_track(track_index) + + track_index = animation.add_track(Animation.TYPE_VALUE) + animation.track_set_path(track_index, track) + animation.track_set_interpolation_loop_wrap(track_index, false) + animation.value_track_set_update_mode(track_index, Animation.UPDATE_DISCRETE) + + return track_index + + +func _get_property_track_path(player: AnimationPlayer, target_node: Node, prop: String) -> String: + var node_path = player.get_node(player.root_node).get_path_to(target_node) + return "%s:%s" % [node_path, prop] + + +func _cleanup_animations(target_node: Node, player: AnimationPlayer, content: Dictionary, options: Dictionary): + if not (content.meta.has("frameTags") and content.meta.frameTags.size() > 0): + return result_code.SUCCESS + + _remove_unused_animations(content, player) + + if options.get("cleanup_hide_unused_nodes", false): + _hide_unused_nodes(target_node, player, content) + + return result_code.SUCCESS + + +func _remove_unused_animations(content: Dictionary, player: AnimationPlayer): + pass # FIXME it's not removing unused animations anymore. Sample impl bellow +# var tags = ["RESET"] +# for t in content.meta.frameTags: +# var a = t.name +# if a.begins_with(_config.get_animation_loop_exception_prefix()): +# a = a.substr(_config.get_animation_loop_exception_prefix().length()) +# tags.push_back(a) + +# var track = _get_frame_track_path(player, sprite) +# for a in player.get_animation_list(): +# if tags.has(a): +# continue +# +# var animation = player.get_animation(a) +# if animation.get_track_count() != 1: +# var t = animation.find_track(track) +# if t != -1: +# animation.remove_track(t) +# continue +# +# if animation.find_track(track) != -1: +# player.remove_animation(a) + + +func _hide_unused_nodes(target_node: Node, player: AnimationPlayer, content: Dictionary): + var root_node := player.get_node(player.root_node) + var all_animations := player.get_animation_list() + var all_sprite_nodes := [] + var animation_sprites := {} + + for a in all_animations: + var animation := player.get_animation(a) + var sprite_nodes := [] + + for track_idx in animation.get_track_count(): + var raw_path := animation.track_get_path(track_idx) + + if raw_path.get_subname(0) == "visible": + continue + + var path := _remove_properties_from_path(raw_path) + var sprite_node := root_node.get_node(path) + + if !(sprite_node is Sprite2D || sprite_node is Sprite3D): + continue + + if sprite_nodes.has(sprite_node): + continue + sprite_nodes.append(sprite_node) + + animation_sprites[animation] = sprite_nodes + for sn in sprite_nodes: + if all_sprite_nodes.has(sn): + continue + all_sprite_nodes.append(sn) + + for animation in animation_sprites: + var sprite_nodes : Array = animation_sprites[animation] + for node in all_sprite_nodes: + if sprite_nodes.has(node): + continue + var visible_track = _get_property_track_path(player, node, "visible") + if animation.find_track(visible_track, Animation.TYPE_VALUE) != -1: + continue + var visible_track_index = _create_track(node, animation, visible_track) + animation.track_insert_key(visible_track_index, 0, false) + + +func list_layers(file: String, only_visibles = false) -> Array: + return _aseprite.list_layers(file, only_visibles) + + +func list_slices(file: String) -> Array: + return _aseprite.list_slices(file) + + +func _remove_properties_from_path(path: NodePath) -> NodePath: + var string_path := path as String + if !(":" in string_path): + return string_path as NodePath + + var property_path := path.get_concatenated_subnames() as String + string_path = string_path.substr(0, string_path.length() - property_path.length() - 1) + + return string_path as NodePath + + +func _create_meta_tracks(target_node: Node, player: AnimationPlayer, animation: Animation): + for prop in _get_meta_prop_names(): + var track = _get_property_track_path(player, target_node, prop) + var track_index = _create_track(target_node, animation, track) + animation.track_insert_key(track_index, 0, true if prop == "visible" else target_node.get(prop)) + + +func _cleanup_tracks(target_node: Node, player: AnimationPlayer, animation: Animation): + for track_key in ["texture", "hframes", "vframes", "region_rect", "frame"]: + var track = _get_property_track_path(player, target_node, track_key) + var track_index = animation.find_track(track, Animation.TYPE_VALUE) + if track_index != -1: + animation.remove_track(track_index) + + +func _setup_texture(target_node: Node, sprite_sheet: String, content: Dictionary, context: Dictionary, is_importing_slice: bool): + push_error("_setup_texture not implemented!") + + +func _get_frame_property(is_importing_slice: bool) -> String: + push_error("_get_frame_property not implemented!") + return "" + + +func _get_frame_key(target_node: Node, frame: Dictionary, context: Dictionary, slice_info: Variant): + push_error("_get_frame_key not implemented!") + + +func _get_meta_prop_names(): + push_error("_get_meta_prop_names not implemented!") diff --git a/addons/AsepriteWizard/creators/animation_player/sprite_animation_creator.gd b/addons/AsepriteWizard/creators/animation_player/sprite_animation_creator.gd new file mode 100644 index 0000000..93a9a6d --- /dev/null +++ b/addons/AsepriteWizard/creators/animation_player/sprite_animation_creator.gd @@ -0,0 +1,48 @@ +extends "animation_creator.gd" + +func _get_meta_prop_names(): + return [ "visible" ] + + +func _setup_texture(sprite: Node, sprite_sheet: String, content: Dictionary, context: Dictionary, is_importing_slice: bool): + var texture = _load_texture(sprite_sheet) + sprite.texture = texture + + if content.frames.is_empty(): + return + + if is_importing_slice: + sprite.region_enabled = true + sprite.hframes = 1 + sprite.vframes = 1 + sprite.frame = 0 + else: + sprite.region_enabled = false + sprite.hframes = content.meta.size.w / content.frames[0].sourceSize.w + sprite.vframes = content.meta.size.h / content.frames[0].sourceSize.h + + +func _get_frame_property(is_importing_slice: bool) -> String: + return "frame" if not is_importing_slice else "region_rect" + + +func _get_frame_key(sprite: Node, frame: Dictionary, context: Dictionary, slice_info: Variant): + if slice_info != null: + return _create_slice_rect(frame, slice_info) + return _calculate_frame_index(sprite,frame) + + +func _calculate_frame_index(sprite: Node, frame: Dictionary) -> int: + var column = floor(frame.frame.x * sprite.hframes / sprite.texture.get_width()) + var row = floor(frame.frame.y * sprite.vframes / sprite.texture.get_height()) + return (row * sprite.hframes) + column + + +func _create_slice_rect(frame_data: Dictionary, slice_rect: Rect2) -> Rect2: + var frame = frame_data.frame + return Rect2( + frame.x + slice_rect.position.x, + frame.y + slice_rect.position.y, + slice_rect.size.x, + slice_rect.size.y + ) diff --git a/addons/AsepriteWizard/creators/animation_player/texture_rect_animation_creator.gd b/addons/AsepriteWizard/creators/animation_player/texture_rect_animation_creator.gd new file mode 100644 index 0000000..7cc80df --- /dev/null +++ b/addons/AsepriteWizard/creators/animation_player/texture_rect_animation_creator.gd @@ -0,0 +1,32 @@ +extends "animation_creator.gd" + + +func _get_meta_prop_names(): + return [ "visible" ] + + +func _setup_texture(target_node: Node, sprite_sheet: String, content: Dictionary, context: Dictionary, _is_importing_slice: bool): + context["base_texture"] = _load_texture(sprite_sheet) + + +func _get_frame_property(_is_importing_slice: bool) -> String: + return "texture" + + +func _get_frame_key(target_node: Node, frame: Dictionary, context: Dictionary, slice_info: Variant): + return _get_atlas_texture(context["base_texture"], frame, slice_info) + + +func _get_atlas_texture(base_texture: Texture2D, frame_data: Dictionary, slice_info: Variant) -> AtlasTexture: + var tex = AtlasTexture.new() + tex.atlas = base_texture + tex.region = Rect2(Vector2(frame_data.frame.x, frame_data.frame.y), Vector2(frame_data.frame.w, frame_data.frame.h)) + tex.filter_clip = true + + if slice_info != null: + tex.region.position.x += slice_info.position.x + tex.region.position.y += slice_info.position.y + tex.region.size.x = slice_info.size.x + tex.region.size.y = slice_info.size.y + + return tex diff --git a/addons/AsepriteWizard/creators/base_sprite_resource_creator.gd b/addons/AsepriteWizard/creators/base_sprite_resource_creator.gd new file mode 100644 index 0000000..ee8b4ce --- /dev/null +++ b/addons/AsepriteWizard/creators/base_sprite_resource_creator.gd @@ -0,0 +1,16 @@ +@tool +extends RefCounted + +var result_code = preload("../config/result_codes.gd") +var _aseprite = preload("../aseprite/aseprite.gd").new() +var _aseprite_file_exporter = preload("../aseprite/file_exporter.gd").new() + +var _config + +## +## Load initial dependencies +## +func init(config): + _config = config + _aseprite.init(config) + _aseprite_file_exporter.init(config) diff --git a/addons/AsepriteWizard/creators/sprite_frames/sprite_frames_creator.gd b/addons/AsepriteWizard/creators/sprite_frames/sprite_frames_creator.gd new file mode 100644 index 0000000..bbb445b --- /dev/null +++ b/addons/AsepriteWizard/creators/sprite_frames/sprite_frames_creator.gd @@ -0,0 +1,242 @@ +@tool +extends "../base_sprite_resource_creator.gd" + +enum { + FILE_EXPORT_MODE, + LAYERS_EXPORT_MODE +} + +### +### Create SpriteFrames from aseprite files and insert +### them to the animated_sprite node +### +func create_animations(animated_sprite: Node, aseprite_files: Dictionary, options: Dictionary) -> void: + var sprite_frames_result = _create_sprite_frames(aseprite_files, options) + if not sprite_frames_result.is_ok: + printerr(result_code.get_error_message(sprite_frames_result.code)) + return + + animated_sprite.frames = sprite_frames_result.content + + if animated_sprite is CanvasItem: + animated_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + else: + animated_sprite.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST + + +func create_and_save_resources(source_files: Array) -> int: + var resources = create_resources(source_files) + if resources.is_ok: + return _save_resources(resources.content) + + return resources.code + + +func create_resources(source_files: Array, options: Dictionary = {}) -> Dictionary: + var resources = [] + + for o in source_files: + if o.is_empty(): + return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED) + + var resource = _create_sprite_frames(o, options) + + if not resource.is_ok: + return resource + + resources.push_back({ + "data_file": o.data_file, + "resource": resource.content, + }) + + return result_code.result(resources) + + +func _create_sprite_frames(data: Dictionary, options: Dictionary) -> Dictionary: + var aseprite_resources = _load_aseprite_resources(data) + if not aseprite_resources.is_ok: + return aseprite_resources + + return result_code.result( + _create_sprite_frames_with_animations( + aseprite_resources.content.metadata, + aseprite_resources.content.texture, + options, + ) + ) + + +func _load_aseprite_resources(aseprite_data: Dictionary): + var content_result = _aseprite_file_exporter.load_json_content(aseprite_data.data_file) + + if not content_result.is_ok: + return content_result + + var texture = _load_texture(aseprite_data.sprite_sheet) + + return result_code.result({ + "metadata": content_result.content, + "texture": texture + }) + + +func _save_resources(resources: Array) -> int: + for resource in resources: + var code = _save_resource(resource.resource, resource.data_file) + if code != OK: + return code + return OK + + +func _save_resource(resource, source_path: String) -> int: + var save_path = "%s.%s" % [source_path.get_basename(), "res"] + var code = ResourceSaver.save(resource, save_path, ResourceSaver.FLAG_REPLACE_SUBRESOURCE_PATHS) + resource.take_over_path(save_path) + return code + + +func _create_sprite_frames_with_animations(content: Dictionary, texture, options: Dictionary) -> SpriteFrames: + var frame_cache = {} + var frames = _aseprite.get_content_frames(content) + var sprite_frames := SpriteFrames.new() + sprite_frames.remove_animation("default") + + var frame_rect: Variant = null + + # currently, aseprite does not work with the --slice option, so we need to manually + # do it. https://github.com/aseprite/aseprite/issues/2469 + if options.get("slice", "") != "": + frame_rect = _aseprite.get_slice_rect(content, options.slice) + + if content.meta.has("frameTags") and content.meta.frameTags.size() > 0: + for tag in content.meta.frameTags: + var selected_frames = frames.slice(tag.from, tag.to + 1) + _add_animation_frames(sprite_frames, tag.name, selected_frames, texture, frame_rect, tag.direction, int(tag.get("repeat", -1)), frame_cache) + else: + _add_animation_frames(sprite_frames, "default", frames, texture, frame_rect) + + return sprite_frames + + +func _add_animation_frames( + sprite_frames: SpriteFrames, + anim_name: String, + frames: Array, + texture, + frame_rect: Variant, + direction = 'forward', + repeat = -1, + frame_cache = {} +): + var animation_name := anim_name + var is_loopable = _config.is_default_animation_loop_enabled() + + var loop_prefix = _config.get_animation_loop_exception_prefix() + if animation_name.begins_with(loop_prefix): + animation_name = anim_name.trim_prefix(loop_prefix) + is_loopable = not is_loopable + + sprite_frames.add_animation(animation_name) + + var min_duration = _get_min_duration(frames) + var fps = _calculate_fps(min_duration) + + if direction == "reverse" or direction == "pingpong_reverse": + frames.reverse() + + var repetition = 1 + + if repeat != -1: + is_loopable = false + repetition = repeat + + for i in range(repetition): + for frame in frames: + _add_to_sprite_frames(sprite_frames, animation_name, texture, frame, min_duration, frame_cache, frame_rect) + + if direction.begins_with("pingpong"): + var working_frames = frames.duplicate() + working_frames.remove_at(working_frames.size() - 1) + if is_loopable or (repetition > 1 and i < repetition - 1): + working_frames.remove_at(0) + working_frames.reverse() + + for frame in working_frames: + _add_to_sprite_frames(sprite_frames, animation_name, texture, frame, min_duration, frame_cache, frame_rect) + + sprite_frames.set_animation_loop(animation_name, is_loopable) + sprite_frames.set_animation_speed(animation_name, fps) + + +func _calculate_fps(min_duration: int) -> float: + return ceil(1000.0 / min_duration) + + +func _get_min_duration(frames) -> int: + var min_duration = 100000 + for frame in frames: + if frame.duration < min_duration: + min_duration = frame.duration + return min_duration + + +func _load_texture(path) -> CompressedTexture2D: + return ResourceLoader.load(path, "CompressedTexture2D", ResourceLoader.CACHE_MODE_REPLACE) + + +func _add_to_sprite_frames( + sprite_frames, + animation_name: String, + texture, + frame: Dictionary, + min_duration: int, + frame_cache: Dictionary, + frame_rect: Variant, +): + var atlas : AtlasTexture = _create_atlastexture_from_frame(texture, frame, sprite_frames, frame_cache, frame_rect) + var duration = frame.duration / min_duration + sprite_frames.add_frame(animation_name, atlas, duration) + + +func _create_atlastexture_from_frame( + image, + frame_data, + sprite_frames: SpriteFrames, + frame_cache: Dictionary, + frame_rect: Variant, +) -> AtlasTexture: + var frame = frame_data.frame + var region := Rect2(frame.x, frame.y, frame.w, frame.h) + + # this is to manually set the slice + if frame_rect != null: + region.position.x += frame_rect.position.x + region.position.y += frame_rect.position.y + region.size.x = frame_rect.size.x + region.size.y = frame_rect.size.y + + var key := "%s_%s_%s_%s" % [frame.x, frame.y, frame.w, frame.h] + var texture = frame_cache.get(key) + + if texture != null and texture.atlas == image: + return texture + + var atlas_texture := AtlasTexture.new() + atlas_texture.atlas = image + atlas_texture.region = region + + frame_cache[key] = atlas_texture + + return atlas_texture + + +func list_layers(file: String, only_visibles = false) -> Array: + return _aseprite.list_layers(file, only_visibles) + + +func list_slices(file: String) -> Array: + return _aseprite.list_slices(file) + + +func _get_file_basename(file_path: String) -> String: + return file_path.get_file().trim_suffix('.%s' % file_path.get_extension()) diff --git a/addons/AsepriteWizard/creators/static_texture/texture_creator.gd b/addons/AsepriteWizard/creators/static_texture/texture_creator.gd new file mode 100644 index 0000000..8cebcf1 --- /dev/null +++ b/addons/AsepriteWizard/creators/static_texture/texture_creator.gd @@ -0,0 +1,26 @@ +@tool +extends "../base_sprite_resource_creator.gd" + +func load_texture(target_node: Node, aseprite_files: Dictionary, options: Dictionary) -> void: + var source_file = aseprite_files.data_file + var sprite_sheet = aseprite_files.sprite_sheet + var data = _aseprite_file_exporter.load_json_content(source_file) + var texture = ResourceLoader.load(sprite_sheet) + + if not data.is_ok: + printerr("Failed to load aseprite source %s" % source_file) + return + + if options.slice == "": + target_node.texture = texture + else: + var region = _aseprite.get_slice_rect(data.content, options.slice) + var atlas_texture := AtlasTexture.new() + atlas_texture.atlas = texture + atlas_texture.region = region + target_node.texture = atlas_texture + + if target_node is CanvasItem: + target_node.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + else: + target_node.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST diff --git a/addons/AsepriteWizard/export/metadata_export_plugin.gd b/addons/AsepriteWizard/export/metadata_export_plugin.gd new file mode 100644 index 0000000..888067f --- /dev/null +++ b/addons/AsepriteWizard/export/metadata_export_plugin.gd @@ -0,0 +1,60 @@ +extends EditorExportPlugin + +const wizard_config = preload("../config/wizard_config.gd") + +func _get_name(): + return "aseprite_wizard_metadata_export_plugin" + + +func _export_file(path: String, type: String, features: PackedStringArray) -> void: + if type != "PackedScene": return + + var scene : PackedScene = ResourceLoader.load(path, type, ResourceLoader.CACHE_MODE_IGNORE) + var scene_changed := false + var root_node := scene.instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE) + var nodes := [root_node] + + #remove_at metadata from scene + while not nodes.is_empty(): + var node : Node = nodes.pop_front() + + for child in node.get_children(): + nodes.push_back(child) + + if _remove_meta(node): + scene_changed = true + + #save scene if changed + if scene_changed: + var filtered_scene := PackedScene.new() + if filtered_scene.pack(root_node) != OK: + print("Error updating scene") + return + + var content := _get_scene_content(path, filtered_scene) + + add_file(path, content, true) + + root_node.free() + + +func _remove_meta(node:Node) -> bool: + if node.has_meta(wizard_config.WIZARD_CONFIG_META_NAME): + node.remove_meta(wizard_config.WIZARD_CONFIG_META_NAME) + return true + + return false + + +func _get_scene_content(path:String, scene:PackedScene) -> PackedByteArray: + var tmp_path = OS.get_cache_dir() + "tmp_scene." + path.get_extension() + ResourceSaver.save(scene, tmp_path) + + var tmp_file = FileAccess.open(tmp_path, FileAccess.READ) + var content : PackedByteArray = tmp_file.get_buffer(tmp_file.get_length()) + tmp_file.close() + + if FileAccess.file_exists(tmp_path): + DirAccess.remove_absolute(tmp_path) + + return content diff --git a/addons/AsepriteWizard/importers/noop_import_plugin.gd b/addons/AsepriteWizard/importers/noop_import_plugin.gd new file mode 100644 index 0000000..7ef469c --- /dev/null +++ b/addons/AsepriteWizard/importers/noop_import_plugin.gd @@ -0,0 +1,58 @@ +@tool +extends EditorImportPlugin + +## +## No-op importer to allow files to be seen and +## managed, but without triggering a real import +## + +var config +var file_system: EditorFileSystem + +func _get_importer_name(): + return "aseprite_wizard.plugin.noop" + + +func _get_visible_name(): + return "Aseprite (No Import)" + + +func _get_recognized_extensions(): + return ["aseprite", "ase"] + + +func _get_save_extension(): + return "res" + + +func _get_resource_type(): + return "PackedDataContainer" + + +func _get_preset_count(): + return 1 + + +func _get_preset_name(i): + return "Default" + + +func _get_priority(): + return 2.0 if config.get_default_importer() == config.IMPORTER_NOOP_NAME else 1.0 + + +func _get_import_order(): + return 1 + + +func _get_import_options(_path, _i): + return [] + + +func _get_option_visibility(path, option, options): + return true + + +func _import(source_file, save_path, options, platform_variants, gen_files): + var container = PackedDataContainer.new() + return ResourceSaver.save(container, "%s.%s" % [save_path, _get_save_extension()]) diff --git a/addons/AsepriteWizard/importers/sprite_frames_import_plugin.gd b/addons/AsepriteWizard/importers/sprite_frames_import_plugin.gd new file mode 100644 index 0000000..08814c5 --- /dev/null +++ b/addons/AsepriteWizard/importers/sprite_frames_import_plugin.gd @@ -0,0 +1,153 @@ +@tool +extends EditorImportPlugin + +const result_codes = preload("../config/result_codes.gd") + +var config +var _aseprite_file_exporter = preload("../aseprite/file_exporter.gd").new() +var _sf_creator = preload("../creators/sprite_frames/sprite_frames_creator.gd").new() +var file_system: EditorFileSystem + +func _get_importer_name(): + # ideally this should be called aseprite_wizard.plugin.spriteframes + # but I'm keeping it like this to avoid unnecessary breaking changes + return "aseprite_wizard.plugin" + + +func _get_visible_name(): + return "Aseprite SpriteFrames" + + +func _get_recognized_extensions(): + return ["aseprite", "ase"] + + +func _get_save_extension(): + return "res" + + +func _get_resource_type(): + return "SpriteFrames" + + +func _get_preset_count(): + return 1 + + +func _get_preset_name(i): + return "Default" + + +func _get_priority(): + return 2.0 if config.get_default_importer() == config.IMPORTER_SPRITEFRAMES_NAME else 1.0 + + +func _get_import_order(): + return 1 + + +func _get_import_options(_path, _i): + return [ + {"name": "split_layers", "default_value": false}, + {"name": "exclude_layers_pattern", "default_value": config.get_default_exclusion_pattern()}, + {"name": "only_visible_layers", "default_value": false}, + { + "name": "sheet_type", + "default_value": "Packed", + "property_hint": PROPERTY_HINT_ENUM, + "hint_string": get_sheet_type_hint_string() + }, + ] + + +func _get_option_visibility(path, option, options): + return true + + +static func get_sheet_type_hint_string() -> String: + var hint_string := "Packed" + for number in [2, 4, 8, 16, 32]: + hint_string += ",%s columns" % number + hint_string += ",Strip" + return hint_string + + +func _import(source_file, save_path, options, platform_variants, gen_files): + var absolute_source_file = ProjectSettings.globalize_path(source_file) + var absolute_save_path = ProjectSettings.globalize_path(save_path) + + var source_path = source_file.substr(0, source_file.rfind('/')) + var source_basename = source_file.substr(source_path.length()+1, -1) + source_basename = source_basename.substr(0, source_basename.rfind('.')) + + _sf_creator.init(config) + _aseprite_file_exporter.init(config) + + var export_mode = _sf_creator.LAYERS_EXPORT_MODE if options['split_layers'] else _sf_creator.FILE_EXPORT_MODE + + var aseprite_opts = { + "export_mode": export_mode, + "exception_pattern": options['exclude_layers_pattern'], + "only_visible_layers": options['only_visible_layers'], + "output_filename": '' if export_mode == _sf_creator.FILE_EXPORT_MODE else '%s_' % source_basename, + "column_count" : int(options['sheet_type']) if options['sheet_type'] != "Strip" else 128, + "output_folder": source_path, + } + + var source_files = _aseprite_file_exporter.generate_aseprite_files(absolute_source_file, aseprite_opts) + if not source_files.is_ok: + printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(source_files.code)) + return FAILED + + + var should_trigger_scan = false + + for sf in source_files.content: + if sf.is_first_import: + file_system.update_file(sf.sprite_sheet) + append_import_external_resource(sf.sprite_sheet) + else: + should_trigger_scan = true + + if should_trigger_scan: + file_system.scan() + + var resources = _sf_creator.create_resources(source_files.content) + + if not resources.is_ok: + printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(resources.code)) + return FAILED + + if export_mode == _sf_creator.LAYERS_EXPORT_MODE: + # each layer is saved as one resource using base file name to prevent collisions + # the first layer will be saved in the default resource path to prevent + # godot from keeping re-importing it + for resource in resources.content: + var resource_path = "%s.res" % resource.data_file.get_basename(); + var exit_code = ResourceSaver.save(resource.resource, resource_path) + resource.resource.take_over_path(resource_path) + + if exit_code != OK: + printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code)) + return FAILED + + var resource = resources.content[0] + var resource_path = "%s.res" % save_path + var exit_code = ResourceSaver.save(resource.resource, resource_path) + resource.resource.take_over_path(resource_path) + + if config.should_remove_source_files(): + _remove_source_files(source_files.content) + + if exit_code != OK: + printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code)) + return FAILED + + return OK + + +func _remove_source_files(source_files: Array): + for s in source_files: + DirAccess.remove_absolute(s.data_file) + + file_system.call_deferred("scan") diff --git a/addons/AsepriteWizard/importers/static_texture_import_plugin.gd b/addons/AsepriteWizard/importers/static_texture_import_plugin.gd new file mode 100644 index 0000000..61a4dcf --- /dev/null +++ b/addons/AsepriteWizard/importers/static_texture_import_plugin.gd @@ -0,0 +1,138 @@ +@tool +extends EditorImportPlugin + +## +## Static texture importer. +## Imports first frame from Aseprite file as texture +## + +const result_codes = preload("../config/result_codes.gd") +var _aseprite_file_exporter = preload("../aseprite/file_exporter.gd").new() + +var config +var file_system: EditorFileSystem + + +func _get_importer_name(): + return "aseprite_wizard.plugin.static-texture" + + +func _get_visible_name(): + return "Aseprite Texture" + + +func _get_recognized_extensions(): + return ["aseprite", "ase"] + + +func _get_save_extension(): + return "res" + + +func _get_resource_type(): + return "AtlasTexture" + + +func _get_preset_count(): + return 1 + + +func _get_preset_name(i): + return "Default" + + +func _get_priority(): + return 2.0 if config.get_default_importer() == config.IMPORTER_STATIC_TEXTURE_NAME else 0.8 + + +func _get_import_order(): + return 1 + + +func _get_import_options(_path, _i): + return [ + {"name": "exclude_layers_pattern", "default_value": config.get_default_exclusion_pattern()}, + {"name": "only_visible_layers", "default_value": false}, + ] + + +func _get_option_visibility(path, option, options): + return true + + +func _import(source_file, save_path, options, platform_variants, gen_files): + var absolute_source_file = ProjectSettings.globalize_path(source_file) + var absolute_save_path = ProjectSettings.globalize_path(save_path) + var source_path = source_file.substr(0, source_file.rfind('/')) + var source_basename = source_file.substr(source_path.length()+1, -1) + source_basename = source_basename.substr(0, source_basename.rfind('.')) + + _aseprite_file_exporter.init(config) + + var aseprite_opts = { + "exception_pattern": options['exclude_layers_pattern'], + "only_visible_layers": options['only_visible_layers'], + "output_filename": '', + "output_folder": source_path, + "first_frame_only": true, + } + + var result = _generate_texture(absolute_source_file, aseprite_opts) + + if not result.is_ok: + printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(result.code)) + return FAILED + + var sprite_sheet = result.content.sprite_sheet + var data = result.content.data + + if ResourceLoader.exists(sprite_sheet): + file_system.scan() + else: + file_system.update_file(sprite_sheet) + append_import_external_resource(sprite_sheet) + + var texture: CompressedTexture2D = ResourceLoader.load(sprite_sheet, "CompressedTexture2D", ResourceLoader.CACHE_MODE_REPLACE) + + return _save_resource(texture, save_path, result.content.data_file, data.meta.size) + + +func _generate_texture(absolute_source_file: String, options: Dictionary) -> Dictionary: + var result = _aseprite_file_exporter.generate_aseprite_file(absolute_source_file, options) + + if not result.is_ok: + return result + + var sprite_sheet = result.content.sprite_sheet + + var data_result = _aseprite_file_exporter.load_json_content(result.content.data_file) + + if not data_result.is_ok: + return data_result + + var data = data_result.content + + return result_codes.result({ + "data_file": result.content.data_file, + "sprite_sheet": sprite_sheet, + "data": data + }) + + +func _save_resource(texture: CompressedTexture2D, save_path: String, data_file_path: String, size: Dictionary) -> int: + var resource = AtlasTexture.new() + resource.atlas = texture + resource.region = Rect2(0, 0, size.w, size.h) + + var resource_path = "%s.res" % save_path + var exit_code = ResourceSaver.save(resource, resource_path) + resource.take_over_path(resource_path) + + if config.should_remove_source_files(): + DirAccess.remove_absolute(data_file_path) + file_system.call_deferred("scan") + + if exit_code != OK: + printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code)) + return FAILED + return OK diff --git a/addons/AsepriteWizard/importers/tileset_texture_import_plugin.gd b/addons/AsepriteWizard/importers/tileset_texture_import_plugin.gd new file mode 100644 index 0000000..ea84b29 --- /dev/null +++ b/addons/AsepriteWizard/importers/tileset_texture_import_plugin.gd @@ -0,0 +1,137 @@ +@tool +extends EditorImportPlugin + +## +## Tileset texture importer. +## Imports Aseprite tileset layers as an AtlasTexture +## + +const result_codes = preload("../config/result_codes.gd") +var _aseprite_file_exporter = preload("../aseprite/file_exporter.gd").new() + +var config +var file_system: EditorFileSystem + + +func _get_importer_name(): + return "aseprite_wizard.plugin.tileset-texture" + + +func _get_visible_name(): + return "Aseprite Tileset Texture" + + +func _get_recognized_extensions(): + return ["aseprite", "ase"] + + +func _get_save_extension(): + return "res" + + +func _get_resource_type(): + return "AtlasTexture" + + +func _get_preset_count(): + return 1 + + +func _get_preset_name(i): + return "Default" + + +func _get_priority(): + return 2.0 if config.get_default_importer() == config.IMPORTER_TILESET_TEXTURE_NAME else 0.9 + + +func _get_import_order(): + return 1 + + +func _get_import_options(_path, _i): + return [ + {"name": "exclude_layers_pattern", "default_value": config.get_default_exclusion_pattern()}, + {"name": "only_visible_layers", "default_value": false}, + ] + + +func _get_option_visibility(path, option, options): + return true + + +func _import(source_file, save_path, options, platform_variants, gen_files): + var absolute_source_file = ProjectSettings.globalize_path(source_file) + var absolute_save_path = ProjectSettings.globalize_path(save_path) + var source_path = source_file.substr(0, source_file.rfind('/')) + var source_basename = source_file.substr(source_path.length()+1, -1) + source_basename = source_basename.substr(0, source_basename.rfind('.')) + + _aseprite_file_exporter.init(config) + + var aseprite_opts = { + "exception_pattern": options['exclude_layers_pattern'], + "only_visible_layers": options['only_visible_layers'], + "output_filename": '', + "output_folder": source_path, + } + + var result = _generate_texture(absolute_source_file, aseprite_opts) + + if not result.is_ok: + printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(result.code)) + return FAILED + + var sprite_sheet = result.content.sprite_sheet + var data = result.content.data + + if ResourceLoader.exists(sprite_sheet): + file_system.scan() + else: + file_system.update_file(sprite_sheet) + append_import_external_resource(sprite_sheet) + + var texture: CompressedTexture2D = ResourceLoader.load(sprite_sheet, "CompressedTexture2D", ResourceLoader.CACHE_MODE_REPLACE) + + return _save_resource(texture, save_path, result.content.data_file, data.meta.size) + + +func _generate_texture(absolute_source_file: String, options: Dictionary) -> Dictionary: + var result = _aseprite_file_exporter.generate_tileset_files(absolute_source_file, options) + + if not result.is_ok: + return result + + var sprite_sheet = result.content.sprite_sheet + + var data_result = _aseprite_file_exporter.load_json_content(result.content.data_file) + + if not data_result.is_ok: + return data_result + + var data = data_result.content + + return result_codes.result({ + "data_file": result.content.data_file, + "sprite_sheet": sprite_sheet, + "data": data + }) + + +func _save_resource(texture: CompressedTexture2D, save_path: String, data_file_path: String, size: Dictionary) -> int: + var resource = AtlasTexture.new() + resource.atlas = texture + resource.region = Rect2(0, 0, size.w, size.h) + + var resource_path = "%s.res" % save_path + var exit_code = ResourceSaver.save(resource, resource_path) + resource.take_over_path(resource_path) + + if config.should_remove_source_files(): + DirAccess.remove_absolute(data_file_path) + file_system.call_deferred("scan") + + if exit_code != OK: + printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code)) + return FAILED + return OK diff --git a/addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.gd b/addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.gd new file mode 100644 index 0000000..1cd7b5c --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.gd @@ -0,0 +1,45 @@ +@tool +extends "../base_inspector_dock.gd" + +var sprite_frames_creator = preload("../../../creators/sprite_frames/sprite_frames_creator.gd").new() + +func _setup(): + sprite_frames_creator.init(config) + + +func _get_available_layers(global_source_path: String) -> Array: + return sprite_frames_creator.list_layers(global_source_path) + + +func _get_available_slices(global_source_path: String) -> Array: + return sprite_frames_creator.list_slices(global_source_path) + + +func _do_import(): + var root = get_tree().get_edited_scene_root() + + var source_path = ProjectSettings.globalize_path(_source) + var options = { + "output_folder": _output_folder if _output_folder != "" else root.scene_file_path.get_base_dir(), + "exception_pattern": _ex_pattern_field.text, + "only_visible_layers": _visible_layers_field.button_pressed, + "output_filename": _out_filename_field.text, + "layer": _layer, + } + + _save_config() + + var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source_path, options) + + if not aseprite_output.is_ok: + var error = result_code.get_error_message(aseprite_output.code) + printerr(error) + _show_message(error) + return + + file_system.scan() + await file_system.filesystem_changed + + sprite_frames_creator.create_animations(target_node, aseprite_output.content, { "slice": _slice }) + + _handle_cleanup(aseprite_output.content) diff --git a/addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.tscn b/addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.tscn new file mode 100644 index 0000000..e9bf35a --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=3 format=3 uid="uid://vej7yqkbtd5f"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.gd" id="1"] +[ext_resource type="PackedScene" uid="uid://uxm7b02wry10" path="res://addons/AsepriteWizard/interface/docks/dock_fields.tscn" id="2_2ilip"] + +[node name="animated_sprite_inspector_dock" type="PanelContainer"] +offset_right = 14.0 +offset_bottom = 14.0 +script = ExtResource("1") + +[node name="dock_fields" parent="." instance=ExtResource("2_2ilip")] +layout_mode = 2 diff --git a/addons/AsepriteWizard/interface/docks/animated_sprite/inspector_plugin.gd b/addons/AsepriteWizard/interface/docks/animated_sprite/inspector_plugin.gd new file mode 100644 index 0000000..a406252 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/animated_sprite/inspector_plugin.gd @@ -0,0 +1,18 @@ +@tool +extends EditorInspectorPlugin + +const ASInspectorDock = preload("./animated_sprite_inspector_dock.tscn") + +var config +var file_system: EditorFileSystem + +func _can_handle(object): + return object is AnimatedSprite2D || object is AnimatedSprite3D + + +func _parse_end(object): + var dock = ASInspectorDock.instantiate() + dock.target_node = object + dock.config = config + dock.file_system = file_system + add_custom_control(dock) diff --git a/addons/AsepriteWizard/interface/docks/base_inspector_dock.gd b/addons/AsepriteWizard/interface/docks/base_inspector_dock.gd new file mode 100644 index 0000000..6157b9b --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/base_inspector_dock.gd @@ -0,0 +1,413 @@ +@tool +extends PanelContainer + +const wizard_config = preload("../../config/wizard_config.gd") +const result_code = preload("../../config/result_codes.gd") +var _aseprite_file_exporter = preload("../../aseprite/file_exporter.gd").new() + +var scene: Node +var target_node: Node +var config +var file_system: EditorFileSystem + +var _layer: String = "" +var _slice: String = "" +var _source: String = "" +var _file_dialog_aseprite: EditorFileDialog +var _output_folder_dialog: EditorFileDialog +var _importing := false +var _output_folder := "" +var _out_folder_default := "[Same as scene]" +var _layer_default := "[all]" + +var _interface_section_state + +@onready var _section_title := $dock_fields/VBoxContainer/title as Button + +# general +@onready var _source_field := $dock_fields/VBoxContainer/source/button as Button +# layers +@onready var _layer_section_header := $dock_fields/VBoxContainer/extra/sections/layers/section_header as Button +@onready var _layer_section_container := $dock_fields/VBoxContainer/extra/sections/layers/section_content as MarginContainer +@onready var _layer_field := $dock_fields/VBoxContainer/extra/sections/layers/section_content/content/layer/options as OptionButton +@onready var _visible_layers_field := $dock_fields/VBoxContainer/extra/sections/layers/section_content/content/visible_layers/CheckBox as CheckBox +@onready var _ex_pattern_field := $dock_fields/VBoxContainer/extra/sections/layers/section_content/content/ex_pattern/LineEdit as LineEdit +# slice +@onready var _slice_section_header := $dock_fields/VBoxContainer/extra/sections/slices/section_header as Button +@onready var _slice_section_container := $dock_fields/VBoxContainer/extra/sections/slices/section_content as MarginContainer +@onready var _slice_field := $dock_fields/VBoxContainer/extra/sections/slices/section_content/content/slice/options as OptionButton +# output +@onready var _output_section_header := $dock_fields/VBoxContainer/extra/sections/output/section_header as Button +@onready var _output_section_container := $dock_fields/VBoxContainer/extra/sections/output/section_content as MarginContainer +@onready var _out_folder_field := $dock_fields/VBoxContainer/extra/sections/output/section_content/content/out_folder/button as Button +@onready var _out_filename_field := $dock_fields/VBoxContainer/extra/sections/output/section_content/content/out_filename/LineEdit as LineEdit + +@onready var _import_button := $dock_fields/VBoxContainer/import as Button + +const INTERFACE_SECTION_KEY_LAYER = "layer_section" +const INTERFACE_SECTION_KEY_SLICE = "slice_section" +const INTERFACE_SECTION_KEY_OUTPUT = "output_section" + +@onready var _expandable_sections = { + INTERFACE_SECTION_KEY_LAYER: { "header": _layer_section_header, "content": _layer_section_container}, + INTERFACE_SECTION_KEY_SLICE: { "header": _slice_section_header, "content": _slice_section_container}, + INTERFACE_SECTION_KEY_OUTPUT: { "header": _output_section_header, "content": _output_section_container}, +} + +func _ready(): + _pre_setup() + _setup_interface() + _setup_config() + _aseprite_file_exporter.init(config) + _setup_field_listeners() + _setup() + +func _setup_interface(): + _hide_fields() + _show_specific_fields() + var cfg = wizard_config.load_interface_config(target_node) + _interface_section_state = cfg + + _section_title.add_theme_stylebox_override("normal", _section_title.get_theme_stylebox("hover")) + + for key in _expandable_sections: + _adjust_section_visibility(key) + + +func _setup_config(): + var cfg = wizard_config.load_config(target_node) + if cfg == null: + _load_common_default_config() + else: + _load_common_config(cfg) + + +func _load_common_config(cfg): + if cfg.has("source"): + _set_source(cfg.source) + + if cfg.get("layer", "") != "": + _layer_field.clear() + _set_layer(cfg.layer) + + if cfg.get("slice", "") != "": + _slice_field.clear() + _set_slice(cfg.slice) + + _set_out_folder(cfg.get("o_folder", "")) + _out_filename_field.text = cfg.get("o_name", "") + _visible_layers_field.button_pressed = cfg.get("only_visible", false) + _ex_pattern_field.text = cfg.get("o_ex_p", "") + + _load_config(cfg) + + +func _load_common_default_config(): + _ex_pattern_field.text = config.get_default_exclusion_pattern() + _visible_layers_field.button_pressed = config.should_include_only_visible_layers_by_default() + #_cleanup_hide_unused_nodes.button_pressed = config.is_set_visible_track_automatically_enabled() + _load_default_config() + + +func _set_source(source): + _source = source + _source_field.text = _source + _source_field.tooltip_text = _source + + +func _set_layer(layer): + _layer = layer + _layer_field.add_item(_layer) + + +func _set_slice(slice): + _slice = slice + _slice_field.add_item(_slice) + + +func _set_out_folder(path): + _output_folder = path + _out_folder_field.text = _output_folder if _output_folder != "" else _out_folder_default + _out_folder_field.tooltip_text = _out_folder_field.text + + +func _toggle_section_visibility(key: String) -> void: + _interface_section_state[key] = not _interface_section_state.get(key, false) + _adjust_section_visibility(key) + wizard_config.save_interface_config(target_node, _interface_section_state) + + +func _adjust_section_visibility(key: String) -> void: + var section = _expandable_sections[key] + var is_visible = _interface_section_state.get(key, false) + _adjust_icon(section.header, is_visible) + section.content.visible = is_visible + + +func _adjust_icon(section: Button, is_visible: bool = true) -> void: + var icon_name = "GuiTreeArrowDown" if is_visible else "GuiTreeArrowRight" + section.icon = get_theme_icon(icon_name, "EditorIcons") + + +func _setup_field_listeners(): + _layer_section_header.button_down.connect(_on_layer_header_button_down) + _slice_section_header.button_down.connect(_on_slice_header_button_down) + _output_section_header.button_down.connect(_on_output_header_button_down) + + _source_field.pressed.connect(_on_source_pressed) + _source_field.aseprite_file_dropped.connect(_on_source_aseprite_file_dropped) + + _layer_field.button_down.connect(_on_layer_button_down) + _layer_field.item_selected.connect(_on_layer_item_selected) + + _slice_field.button_down.connect(_on_slice_button_down) + _slice_field.item_selected.connect(_on_slice_item_selected) + + _out_folder_field.dir_dropped.connect(_on_out_dir_dropped) + _out_folder_field.pressed.connect(_on_out_folder_pressed) + + _import_button.pressed.connect(_on_import_pressed) + + +func _on_layer_header_button_down(): + _toggle_section_visibility(INTERFACE_SECTION_KEY_LAYER) + + +func _on_slice_header_button_down(): + _toggle_section_visibility(INTERFACE_SECTION_KEY_SLICE) + + +func _on_output_header_button_down(): + _toggle_section_visibility(INTERFACE_SECTION_KEY_OUTPUT) + + +func _on_layer_button_down(): + if _source == "": + _show_message("Please. Select source file first.") + return + + var layers = _get_available_layers(ProjectSettings.globalize_path(_source)) + _populate_options_field(_layer_field, layers, _layer) + + +func _on_layer_item_selected(index): + if index == 0: + _layer = "" + return + _layer = _layer_field.get_item_text(index) + _save_config() + + +func _on_slice_item_selected(index): + if index == 0: + _slice = "" + return + _slice = _slice_field.get_item_text(index) + _save_config() + + +func _on_slice_button_down(): + if _source == "": + _show_message("Please, select source file first.") + return + + var slices = _get_available_slices(ProjectSettings.globalize_path(_source)) + var current = 0 + _slice_field.clear() + _slice_field.add_item(_layer_default) + + for s in slices: + if s == "": + continue + + _slice_field.add_item(s) + if s == _slice: + current = _slice_field.get_item_count() - 1 + _slice_field.select(current) + + +func _on_source_pressed(): + _open_source_dialog() + +## +## Save current import options to node metadata +## +func _save_config(): + var child_config = _get_current_field_values() + + var cfg := { + "source": _source, + "layer": _layer, + "slice": _slice, + "o_folder": _output_folder, + "o_name": _out_filename_field.text, + "only_visible": _visible_layers_field.button_pressed, + "o_ex_p": _ex_pattern_field.text, + } + + for c in child_config: + cfg[c] = child_config[c] + + wizard_config.save_config(target_node, config.is_use_metadata_enabled(), cfg) + + +func _get_import_options(default_folder: String): + return { + "output_folder": _output_folder if _output_folder != "" else default_folder, + "exception_pattern": _ex_pattern_field.text, + "only_visible_layers": _visible_layers_field.button_pressed, + "output_filename": _out_filename_field.text, + "layer": _layer + } + + +func _open_source_dialog(): + _file_dialog_aseprite = _create_aseprite_file_selection() + get_parent().add_child(_file_dialog_aseprite) + if _source != "": + _file_dialog_aseprite.current_dir = ProjectSettings.globalize_path(_source.get_base_dir()) + _file_dialog_aseprite.popup_centered_ratio() + + +func _create_aseprite_file_selection(): + var file_dialog = EditorFileDialog.new() + file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE + file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM + file_dialog.connect("file_selected",Callable(self,"_on_aseprite_file_selected")) + file_dialog.set_filters(PackedStringArray(["*.ase","*.aseprite"])) + return file_dialog + + +func _on_aseprite_file_selected(path): + _set_source(ProjectSettings.localize_path(path)) + _save_config() + _file_dialog_aseprite.queue_free() + + +func _on_source_aseprite_file_dropped(path): + _set_source(path) + _save_config() + + +## Helper method to populate field with values +func _populate_options_field(field: OptionButton, values: Array, current_name: String): + var current = 0 + field.clear() + field.add_item("[all]") + + for v in values: + if v == "": + continue + + field.add_item(v) + if v == current_name: + current = field.get_item_count() - 1 + field.select(current) + + +func _on_out_folder_pressed(): + _output_folder_dialog = _create_output_folder_selection() + get_parent().add_child(_output_folder_dialog) + if _output_folder != _out_folder_default: + _output_folder_dialog.current_dir = _output_folder + _output_folder_dialog.popup_centered_ratio() + + +func _create_output_folder_selection(): + var file_dialog = EditorFileDialog.new() + file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_DIR + file_dialog.access = EditorFileDialog.ACCESS_RESOURCES + file_dialog.connect("dir_selected",Callable(self,"_on_output_folder_selected")) + return file_dialog + + +func _on_output_folder_selected(path): + _set_out_folder(path) + _output_folder_dialog.queue_free() + + +func _on_out_dir_dropped(path): + _set_out_folder(path) + + +func _show_message(message: String): + var _warning_dialog = AcceptDialog.new() + get_parent().add_child(_warning_dialog) + _warning_dialog.dialog_text = message + _warning_dialog.popup_centered() + _warning_dialog.connect("popup_hide",Callable(_warning_dialog,"queue_free")) + + +func _notify_aseprite_error(aseprite_error_code): + var error = result_code.get_error_message(aseprite_error_code) + printerr(error) + _show_message(error) + + +func _handle_cleanup(aseprite_content): + if config.should_remove_source_files(): + DirAccess.remove_absolute(aseprite_content.data_file) + file_system.call_deferred("scan") + + +func _on_import_pressed(): + if _importing: + return + _importing = true + + if _source == "": + _show_message("Aseprite file not selected") + _importing = false + return + + await _do_import() + _importing = false + + +# This is a little bit leaky as this base scene contains fields only relevant to animation players. +# However, this is the simplest thing I can do without overcomplicating stuff. +func _hide_fields(): + $dock_fields/VBoxContainer/modes.hide() + $dock_fields/VBoxContainer/animation_player.hide() + $dock_fields/VBoxContainer/extra/sections/animation.hide() + + +## this will be called before base class does its setup +func _pre_setup(): + pass + + +## this will be called after base class setup is complete +func _setup(): + pass + + +func _load_default_config(): + pass + + +func _load_config(cfg: Dictionary): + pass + + +## Override to return available layers +func _get_available_layers(global_source_path: String) -> Array: + return [] + + +## Override to return available slices +func _get_available_slices(global_source_path: String) -> Array: + return [] + + +## Override this method for extra import options to add to node metadata +func _get_current_field_values() -> Dictionary: + return {} + + +func _do_import(): + pass + + +func _show_specific_fields() -> void: + pass diff --git a/addons/AsepriteWizard/interface/docks/base_inspector_dock.tscn b/addons/AsepriteWizard/interface/docks/base_inspector_dock.tscn new file mode 100644 index 0000000..2c2166a --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/base_inspector_dock.tscn @@ -0,0 +1,345 @@ +[gd_scene load_steps=8 format=3 uid="uid://ljeu0l1ld6v5"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/base_inspector_dock.gd" id="1_0bpq8"] +[ext_resource type="PackedScene" uid="uid://x1f1t87m582u" path="res://addons/AsepriteWizard/interface/shared/animation_player_drop_button.tscn" id="2_pge1b"] +[ext_resource type="PackedScene" uid="uid://dj1uo3blocr8e" path="res://addons/AsepriteWizard/interface/shared/source_drop_button.tscn" id="3_nt1oj"] +[ext_resource type="PackedScene" uid="uid://cwvgnm3o7eed2" path="res://addons/AsepriteWizard/interface/shared/dir_drop_button.tscn" id="4_r7t2l"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_x6usu"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225, 0.225, 0.225, 0.6) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 +corner_detail = 5 + +[sub_resource type="Image" id="Image_46k7x"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id="ImageTexture_dxtgh"] +image = SubResource("Image_46k7x") + +[node name="base_inspector_dock" type="PanelContainer"] +offset_right = 14.0 +offset_bottom = 14.0 +script = ExtResource("1_0bpq8") + +[node name="margin" type="MarginContainer" parent="."] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="margin"] +layout_mode = 2 + +[node name="title" type="Button" parent="margin/VBoxContainer"] +layout_mode = 2 +focus_mode = 0 +theme_override_styles/normal = SubResource("StyleBoxFlat_x6usu") +button_mask = 0 +text = "Aseprite" + +[node name="section_title" type="PanelContainer" parent="margin/VBoxContainer"] +visible = false +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="margin/VBoxContainer/section_title"] +layout_mode = 2 +alignment = 1 + +[node name="icon" type="TextureRect" parent="margin/VBoxContainer/section_title/HBoxContainer"] +custom_minimum_size = Vector2(16, 16) +layout_mode = 2 + +[node name="title" type="Label" parent="margin/VBoxContainer/section_title/HBoxContainer"] +layout_mode = 2 +text = "Aseprite" +horizontal_alignment = 1 + +[node name="modes" type="HBoxContainer" parent="margin/VBoxContainer"] +layout_mode = 2 +tooltip_text = "Import mode. +Animation mode (default): set spritesheet as texture and imports animations to the selected AnimationPlayer. +Image mode: Import only first frame and set as texture." + +[node name="Label" type="Label" parent="margin/VBoxContainer/modes"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Mode" + +[node name="options" type="OptionButton" parent="margin/VBoxContainer/modes"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +item_count = 2 +selected = 0 +popup/item_0/text = "Animation" +popup/item_0/id = 0 +popup/item_1/text = "Image" +popup/item_1/id = 1 + +[node name="animation_player" type="HBoxContainer" parent="margin/VBoxContainer"] +layout_mode = 2 +tooltip_text = "AnimationPlayer node where animations should be added to." + +[node name="Label" type="Label" parent="margin/VBoxContainer/animation_player"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "AnimationPlayer" + +[node name="options" parent="margin/VBoxContainer/animation_player" instance=ExtResource("2_pge1b")] +layout_mode = 2 + +[node name="source" type="HBoxContainer" parent="margin/VBoxContainer"] +layout_mode = 2 +tooltip_text = "Location of the Aseprite (*.ase, *.aseprite) source file." + +[node name="Label" type="Label" parent="margin/VBoxContainer/source"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Aseprite File" + +[node name="button" parent="margin/VBoxContainer/source" instance=ExtResource("3_nt1oj")] +layout_mode = 2 + +[node name="extra" type="MarginContainer" parent="margin/VBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="sections" type="VBoxContainer" parent="margin/VBoxContainer/extra"] +layout_mode = 2 + +[node name="layers" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections"] +layout_mode = 2 + +[node name="section_header" type="Button" parent="margin/VBoxContainer/extra/sections/layers"] +layout_mode = 2 +focus_mode = 0 +text = "Layers" +icon = SubResource("ImageTexture_dxtgh") +alignment = 0 + +[node name="section_content" type="MarginContainer" parent="margin/VBoxContainer/extra/sections/layers"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="content" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections/layers/section_content"] +layout_mode = 2 + +[node name="layer" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/layers/section_content/content"] +layout_mode = 2 +tooltip_text = "Aseprite layer to be used in the animation. By default all layers are included." + +[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/layer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Layer" + +[node name="options" type="OptionButton" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/layer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +item_count = 1 +selected = 0 +popup/item_0/text = "[all]" +popup/item_0/id = 0 + +[node name="ex_pattern" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/layers/section_content/content"] +layout_mode = 2 +tooltip_text = "Exclude layers with name matching this pattern (regex)." + +[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/ex_pattern"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Exclude Pattern" + +[node name="LineEdit" type="LineEdit" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/ex_pattern"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 + +[node name="visible_layers" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/layers/section_content/content"] +layout_mode = 2 +tooltip_text = "If active, layers not visible in the source file won't be included in the final image." + +[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/visible_layers"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Only Visible Layers" + +[node name="CheckBox" type="CheckBox" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/visible_layers"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "On" + +[node name="slices" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections"] +layout_mode = 2 + +[node name="section_header" type="Button" parent="margin/VBoxContainer/extra/sections/slices"] +layout_mode = 2 +focus_mode = 0 +text = "Slices" +icon = SubResource("ImageTexture_dxtgh") +alignment = 0 + +[node name="section_content" type="MarginContainer" parent="margin/VBoxContainer/extra/sections/slices"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="content" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections/slices/section_content"] +layout_mode = 2 + +[node name="slice" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/slices/section_content/content"] +layout_mode = 2 +tooltip_text = "Aseprite slice to be used in the animation. By default, the whole file is included." + +[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/slices/section_content/content/slice"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Slice" + +[node name="options" type="OptionButton" parent="margin/VBoxContainer/extra/sections/slices/section_content/content/slice"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +item_count = 1 +selected = 0 +popup/item_0/text = "[all]" +popup/item_0/id = 0 + +[node name="animation" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections"] +layout_mode = 2 + +[node name="section_header" type="Button" parent="margin/VBoxContainer/extra/sections/animation"] +layout_mode = 2 +focus_mode = 0 +text = "Animation" +icon = SubResource("ImageTexture_dxtgh") +alignment = 0 + +[node name="section_content" type="MarginContainer" parent="margin/VBoxContainer/extra/sections/animation"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="content" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections/animation/section_content"] +layout_mode = 2 + +[node name="keep_length" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/animation/section_content/content"] +layout_mode = 2 +tooltip_text = "When this is active the animation length won't be adjusted if other properties were added and the resulting imported animation is shorter." + +[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/animation/section_content/content/keep_length"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Keep Manual Animation Length" + +[node name="CheckBox" type="CheckBox" parent="margin/VBoxContainer/extra/sections/animation/section_content/content/keep_length"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "On" + +[node name="auto_visible_track" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/animation/section_content/content"] +layout_mode = 2 +tooltip_text = "If active, it will automatically determine unused Sprite2D and Sprite3D nodes in each animation and hide them." + +[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/animation/section_content/content/auto_visible_track"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Hide Unused Sprites" + +[node name="CheckBox" type="CheckBox" parent="margin/VBoxContainer/extra/sections/animation/section_content/content/auto_visible_track"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "On" + +[node name="output" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections"] +layout_mode = 2 + +[node name="section_header" type="Button" parent="margin/VBoxContainer/extra/sections/output"] +layout_mode = 2 +focus_mode = 0 +text = "Output" +icon = SubResource("ImageTexture_dxtgh") +alignment = 0 + +[node name="section_content" type="MarginContainer" parent="margin/VBoxContainer/extra/sections/output"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="content" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections/output/section_content"] +layout_mode = 2 + +[node name="out_folder" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/output/section_content/content"] +layout_mode = 2 +tooltip_text = "Location where the spritesheet file should be saved." + +[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/output/section_content/content/out_folder"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Output Folder" + +[node name="button" parent="margin/VBoxContainer/extra/sections/output/section_content/content/out_folder" instance=ExtResource("4_r7t2l")] +layout_mode = 2 + +[node name="out_filename" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/output/section_content/content"] +layout_mode = 2 +tooltip_text = "Base filename for spritesheet. In case the layer option is used, this works as a prefix to the layer name." + +[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/output/section_content/content/out_filename"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Output File Name" + +[node name="LineEdit" type="LineEdit" parent="margin/VBoxContainer/extra/sections/output/section_content/content/out_filename"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 + +[node name="import" type="Button" parent="margin/VBoxContainer"] +layout_mode = 2 +text = "Import" + +[connection signal="item_selected" from="margin/VBoxContainer/modes/options" to="." method="_on_modes_item_selected"] +[connection signal="button_down" from="margin/VBoxContainer/animation_player/options" to="." method="_on_options_button_down"] +[connection signal="item_selected" from="margin/VBoxContainer/animation_player/options" to="." method="_on_options_item_selected"] +[connection signal="node_dropped" from="margin/VBoxContainer/animation_player/options" to="." method="_on_animation_player_node_dropped"] +[connection signal="aseprite_file_dropped" from="margin/VBoxContainer/source/button" to="." method="_on_source_aseprite_file_dropped"] +[connection signal="pressed" from="margin/VBoxContainer/source/button" to="." method="_on_source_pressed"] +[connection signal="button_down" from="margin/VBoxContainer/extra/sections/layers/section_header" to="." method="_on_layer_header_button_down"] +[connection signal="button_down" from="margin/VBoxContainer/extra/sections/layers/section_content/content/layer/options" to="." method="_on_layer_button_down"] +[connection signal="item_selected" from="margin/VBoxContainer/extra/sections/layers/section_content/content/layer/options" to="." method="_on_layer_item_selected"] +[connection signal="button_down" from="margin/VBoxContainer/extra/sections/slices/section_header" to="." method="_on_slice_header_button_down"] +[connection signal="button_down" from="margin/VBoxContainer/extra/sections/slices/section_content/content/slice/options" to="." method="_on_slice_button_down"] +[connection signal="item_selected" from="margin/VBoxContainer/extra/sections/slices/section_content/content/slice/options" to="." method="_on_slice_item_selected"] +[connection signal="button_down" from="margin/VBoxContainer/extra/sections/animation/section_header" to="." method="_on_animation_header_button_down"] +[connection signal="button_down" from="margin/VBoxContainer/extra/sections/output/section_header" to="." method="_on_output_header_button_down"] +[connection signal="dir_dropped" from="margin/VBoxContainer/extra/sections/output/section_content/content/out_folder/button" to="." method="_on_out_dir_dropped"] +[connection signal="pressed" from="margin/VBoxContainer/extra/sections/output/section_content/content/out_folder/button" to="." method="_on_out_folder_pressed"] +[connection signal="pressed" from="margin/VBoxContainer/import" to="." method="_on_import_pressed"] diff --git a/addons/AsepriteWizard/interface/docks/dock_fields.tscn b/addons/AsepriteWizard/interface/docks/dock_fields.tscn new file mode 100644 index 0000000..47c42ad --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/dock_fields.tscn @@ -0,0 +1,320 @@ +[gd_scene load_steps=7 format=3 uid="uid://uxm7b02wry10"] + +[ext_resource type="PackedScene" uid="uid://x1f1t87m582u" path="res://addons/AsepriteWizard/interface/shared/animation_player_drop_button.tscn" id="1_gh7xg"] +[ext_resource type="PackedScene" uid="uid://dj1uo3blocr8e" path="res://addons/AsepriteWizard/interface/shared/source_drop_button.tscn" id="1_y4qrm"] +[ext_resource type="PackedScene" uid="uid://cwvgnm3o7eed2" path="res://addons/AsepriteWizard/interface/shared/dir_drop_button.tscn" id="2_pypqg"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ptlkd"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225, 0.225, 0.225, 0.6) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 +corner_detail = 5 + +[sub_resource type="Image" id="Image_dv8o3"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id="ImageTexture_qqpl1"] +image = SubResource("Image_dv8o3") + +[node name="dock_fields" type="MarginContainer"] + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="title" type="Button" parent="VBoxContainer"] +layout_mode = 2 +focus_mode = 0 +theme_override_styles/normal = SubResource("StyleBoxFlat_ptlkd") +button_mask = 0 +text = "Aseprite" + +[node name="section_title" type="PanelContainer" parent="VBoxContainer"] +visible = false +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/section_title"] +layout_mode = 2 +alignment = 1 + +[node name="icon" type="TextureRect" parent="VBoxContainer/section_title/HBoxContainer"] +custom_minimum_size = Vector2(16, 16) +layout_mode = 2 + +[node name="title" type="Label" parent="VBoxContainer/section_title/HBoxContainer"] +layout_mode = 2 +text = "Aseprite" +horizontal_alignment = 1 + +[node name="modes" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 +tooltip_text = "Import mode. +Animation mode (default): set spritesheet as texture and imports animations to the selected AnimationPlayer. +Image mode: Import only first frame and set as texture." + +[node name="Label" type="Label" parent="VBoxContainer/modes"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Mode" + +[node name="options" type="OptionButton" parent="VBoxContainer/modes"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +item_count = 2 +selected = 0 +popup/item_0/text = "Animation" +popup/item_0/id = 0 +popup/item_1/text = "Image" +popup/item_1/id = 1 + +[node name="animation_player" type="HBoxContainer" parent="VBoxContainer" groups=["aw_animation_player_only"]] +layout_mode = 2 +tooltip_text = "AnimationPlayer node where animations should be added to." + +[node name="Label" type="Label" parent="VBoxContainer/animation_player"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "AnimationPlayer" + +[node name="options" parent="VBoxContainer/animation_player" instance=ExtResource("1_gh7xg")] +layout_mode = 2 + +[node name="source" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 +tooltip_text = "Location of the Aseprite (*.ase, *.aseprite) source file." + +[node name="Label" type="Label" parent="VBoxContainer/source"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Aseprite File" + +[node name="button" parent="VBoxContainer/source" instance=ExtResource("1_y4qrm")] +layout_mode = 2 + +[node name="extra" type="MarginContainer" parent="VBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="sections" type="VBoxContainer" parent="VBoxContainer/extra"] +layout_mode = 2 + +[node name="layers" type="VBoxContainer" parent="VBoxContainer/extra/sections"] +layout_mode = 2 + +[node name="section_header" type="Button" parent="VBoxContainer/extra/sections/layers"] +layout_mode = 2 +focus_mode = 0 +text = "Layers" +icon = SubResource("ImageTexture_qqpl1") +alignment = 0 + +[node name="section_content" type="MarginContainer" parent="VBoxContainer/extra/sections/layers"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="content" type="VBoxContainer" parent="VBoxContainer/extra/sections/layers/section_content"] +layout_mode = 2 + +[node name="layer" type="HBoxContainer" parent="VBoxContainer/extra/sections/layers/section_content/content"] +layout_mode = 2 +tooltip_text = "Aseprite layer to be used in the animation. By default all layers are included." + +[node name="Label" type="Label" parent="VBoxContainer/extra/sections/layers/section_content/content/layer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Layer" + +[node name="options" type="OptionButton" parent="VBoxContainer/extra/sections/layers/section_content/content/layer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +item_count = 1 +selected = 0 +popup/item_0/text = "[all]" +popup/item_0/id = 0 + +[node name="ex_pattern" type="HBoxContainer" parent="VBoxContainer/extra/sections/layers/section_content/content"] +layout_mode = 2 +tooltip_text = "Exclude layers with name matching this pattern (regex)." + +[node name="Label" type="Label" parent="VBoxContainer/extra/sections/layers/section_content/content/ex_pattern"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Exclude Pattern" + +[node name="LineEdit" type="LineEdit" parent="VBoxContainer/extra/sections/layers/section_content/content/ex_pattern"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 + +[node name="visible_layers" type="HBoxContainer" parent="VBoxContainer/extra/sections/layers/section_content/content"] +layout_mode = 2 +tooltip_text = "If active, layers not visible in the source file won't be included in the final image." + +[node name="Label" type="Label" parent="VBoxContainer/extra/sections/layers/section_content/content/visible_layers"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Only Visible Layers" + +[node name="CheckBox" type="CheckBox" parent="VBoxContainer/extra/sections/layers/section_content/content/visible_layers"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "On" + +[node name="slices" type="VBoxContainer" parent="VBoxContainer/extra/sections"] +layout_mode = 2 + +[node name="section_header" type="Button" parent="VBoxContainer/extra/sections/slices"] +layout_mode = 2 +focus_mode = 0 +text = "Slices" +icon = SubResource("ImageTexture_qqpl1") +alignment = 0 + +[node name="section_content" type="MarginContainer" parent="VBoxContainer/extra/sections/slices"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="content" type="VBoxContainer" parent="VBoxContainer/extra/sections/slices/section_content"] +layout_mode = 2 + +[node name="slice" type="HBoxContainer" parent="VBoxContainer/extra/sections/slices/section_content/content"] +layout_mode = 2 +tooltip_text = "Aseprite slice to be used in the animation. By default, the whole file is included." + +[node name="Label" type="Label" parent="VBoxContainer/extra/sections/slices/section_content/content/slice"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Slice" + +[node name="options" type="OptionButton" parent="VBoxContainer/extra/sections/slices/section_content/content/slice"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +item_count = 1 +selected = 0 +popup/item_0/text = "[all]" +popup/item_0/id = 0 + +[node name="animation" type="VBoxContainer" parent="VBoxContainer/extra/sections" groups=["aw_animation_player_only"]] +layout_mode = 2 + +[node name="section_header" type="Button" parent="VBoxContainer/extra/sections/animation"] +layout_mode = 2 +focus_mode = 0 +text = "Animation" +icon = SubResource("ImageTexture_qqpl1") +alignment = 0 + +[node name="section_content" type="MarginContainer" parent="VBoxContainer/extra/sections/animation"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="content" type="VBoxContainer" parent="VBoxContainer/extra/sections/animation/section_content"] +layout_mode = 2 + +[node name="keep_length" type="HBoxContainer" parent="VBoxContainer/extra/sections/animation/section_content/content"] +layout_mode = 2 +tooltip_text = "When this is active the animation length won't be adjusted if other properties were added and the resulting imported animation is shorter." + +[node name="Label" type="Label" parent="VBoxContainer/extra/sections/animation/section_content/content/keep_length"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Keep Manual Animation Length" + +[node name="CheckBox" type="CheckBox" parent="VBoxContainer/extra/sections/animation/section_content/content/keep_length"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "On" + +[node name="auto_visible_track" type="HBoxContainer" parent="VBoxContainer/extra/sections/animation/section_content/content"] +layout_mode = 2 +tooltip_text = "If active, it will automatically determine unused Sprite2D and Sprite3D nodes in each animation and hide them." + +[node name="Label" type="Label" parent="VBoxContainer/extra/sections/animation/section_content/content/auto_visible_track"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Hide Unused Sprites" + +[node name="CheckBox" type="CheckBox" parent="VBoxContainer/extra/sections/animation/section_content/content/auto_visible_track"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "On" + +[node name="output" type="VBoxContainer" parent="VBoxContainer/extra/sections"] +layout_mode = 2 + +[node name="section_header" type="Button" parent="VBoxContainer/extra/sections/output"] +layout_mode = 2 +focus_mode = 0 +text = "Output" +icon = SubResource("ImageTexture_qqpl1") +alignment = 0 + +[node name="section_content" type="MarginContainer" parent="VBoxContainer/extra/sections/output"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="content" type="VBoxContainer" parent="VBoxContainer/extra/sections/output/section_content"] +layout_mode = 2 + +[node name="out_folder" type="HBoxContainer" parent="VBoxContainer/extra/sections/output/section_content/content"] +layout_mode = 2 +tooltip_text = "Location where the spritesheet file should be saved." + +[node name="Label" type="Label" parent="VBoxContainer/extra/sections/output/section_content/content/out_folder"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Output Folder" + +[node name="button" parent="VBoxContainer/extra/sections/output/section_content/content/out_folder" instance=ExtResource("2_pypqg")] +layout_mode = 2 + +[node name="out_filename" type="HBoxContainer" parent="VBoxContainer/extra/sections/output/section_content/content"] +layout_mode = 2 +tooltip_text = "Base filename for spritesheet. In case the layer option is used, this works as a prefix to the layer name." + +[node name="Label" type="Label" parent="VBoxContainer/extra/sections/output/section_content/content/out_filename"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Output File Name" + +[node name="LineEdit" type="LineEdit" parent="VBoxContainer/extra/sections/output/section_content/content/out_filename"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 + +[node name="import" type="Button" parent="VBoxContainer"] +layout_mode = 2 +text = "Import" diff --git a/addons/AsepriteWizard/interface/docks/sprite/inspector_plugin.gd b/addons/AsepriteWizard/interface/docks/sprite/inspector_plugin.gd new file mode 100644 index 0000000..8bb6a62 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/sprite/inspector_plugin.gd @@ -0,0 +1,18 @@ +@tool +extends EditorInspectorPlugin + +const APInspectorDock = preload("./sprite_inspector_dock.tscn") + +var config +var file_system: EditorFileSystem + +func _can_handle(object): + return object is Sprite2D || object is Sprite3D || object is TextureRect + + +func _parse_end(object): + var dock = APInspectorDock.instantiate() + dock.target_node = object + dock.config = config + dock.file_system = file_system + add_custom_control(dock) diff --git a/addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.gd b/addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.gd new file mode 100644 index 0000000..04d2db4 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.gd @@ -0,0 +1,257 @@ +@tool +extends "../base_inspector_dock.gd" + +const AnimationCreator = preload("../../../creators/animation_player/animation_creator.gd") +const SpriteAnimationCreator = preload("../../../creators/animation_player/sprite_animation_creator.gd") +const TextureRectAnimationCreator = preload("../../../creators/animation_player/texture_rect_animation_creator.gd") +const StaticTextureCreator = preload("../../../creators/static_texture/texture_creator.gd") + +enum ImportMode { + ANIMATION = 0, + IMAGE = 1 +} + +var animation_creator: AnimationCreator +var static_texture_creator: StaticTextureCreator + +var _import_mode = -1 +var _animation_player_path: String + +@onready var _import_mode_options_field := $dock_fields/VBoxContainer/modes/options as OptionButton +@onready var _animation_player_field := $dock_fields/VBoxContainer/animation_player/options as OptionButton +@onready var _animation_player_container := $dock_fields/VBoxContainer/animation_player as HBoxContainer + +# animation +@onready var _animation_section := $dock_fields/VBoxContainer/extra/sections/animation as VBoxContainer +@onready var _animation_section_header := $dock_fields/VBoxContainer/extra/sections/animation/section_header as Button +@onready var _animation_section_container := $dock_fields/VBoxContainer/extra/sections/animation/section_content as MarginContainer +@onready var _cleanup_hide_unused_nodes := $dock_fields/VBoxContainer/extra/sections/animation/section_content/content/auto_visible_track/CheckBox as CheckBox +@onready var _keep_length := $dock_fields/VBoxContainer/extra/sections/animation/section_content/content/keep_length/CheckBox as CheckBox + +const INTERFACE_SECTION_KEY_ANIMATION = "animation_section" + + +func _pre_setup(): + _expandable_sections[INTERFACE_SECTION_KEY_ANIMATION] = { "header": _animation_section_header, "content": _animation_section_container} + + +func _setup(): + if target_node is Sprite2D || target_node is Sprite3D: + animation_creator = SpriteAnimationCreator.new() + if target_node is TextureRect: + animation_creator = TextureRectAnimationCreator.new() + + static_texture_creator = StaticTextureCreator.new() + + animation_creator.init(config) + static_texture_creator.init(config) + + _setup_animation_fields_listeners() + + +func _load_config(cfg): + if cfg.has("player"): + _animation_player_field.clear() + _set_animation_player(cfg.player) + + _cleanup_hide_unused_nodes.button_pressed = cfg.get("set_vis_track", config.is_set_visible_track_automatically_enabled()) + _keep_length.button_pressed = cfg.get("keep_anim_length", false) + _set_import_mode(int(cfg.get("i_mode", 0))) + + +func _load_default_config(): + _cleanup_hide_unused_nodes.button_pressed = config.is_set_visible_track_automatically_enabled() + + +func _set_animation_player(player): + _animation_player_path = player + _animation_player_field.add_item(_animation_player_path) + + +func _set_import_mode(import_mode): + if _import_mode == import_mode: + return + + _import_mode = import_mode + var index = _import_mode_options_field.get_item_index(import_mode) + _import_mode_options_field.select(index) + _handle_import_mode() + + +func _handle_import_mode(): + match _import_mode: + ImportMode.ANIMATION: + _animation_player_container.show() + _animation_section.show() + ImportMode.IMAGE: + _animation_player_container.hide() + _animation_section.hide() + + +func _setup_animation_fields_listeners(): + _animation_section_header.button_down.connect(_on_animation_header_button_down) + _animation_player_field.node_dropped.connect(_on_animation_player_node_dropped) + _animation_player_field.button_down.connect(_on_animation_player_button_down) + _animation_player_field.item_selected.connect(_on_animation_player_item_selected) + + _import_mode_options_field.item_selected.connect(_on_modes_item_selected) + + +func _on_animation_player_button_down(): + _refresh_animation_players() + + +func _refresh_animation_players(): + var animation_players = [] + var root = get_tree().get_edited_scene_root() + _find_animation_players(root, root, animation_players) + + var current = 0 + _animation_player_field.clear() + _animation_player_field.add_item("[empty]") + + for ap in animation_players: + _animation_player_field.add_item(ap) + if ap.get_concatenated_names() == _animation_player_path: + current = _animation_player_field.get_item_count() - 1 + + _animation_player_field.select(current) + + +func _find_animation_players(root: Node, node: Node, players: Array): + if node is AnimationPlayer: + players.push_back(root.get_path_to(node)) + + for c in node.get_children(): + _find_animation_players(root, c, players) + + +func _on_animation_player_item_selected(index): + if index == 0: + _animation_player_path = "" + return + _animation_player_path = _animation_player_field.get_item_text(index) + _save_config() + + +func _do_import(): + if _import_mode == ImportMode.IMAGE: + await _import_static() + return + + await _import_for_animation_player() + +## +## Import aseprite animations to target AnimationPlayer and set +## spritesheet as the node's texture +## +func _import_for_animation_player(): + var root = get_tree().get_edited_scene_root() + + if _animation_player_path == "" or not root.has_node(_animation_player_path): + _show_message("AnimationPlayer not found") + _importing = false + return + + var source_path = ProjectSettings.globalize_path(_source) + + var options = _get_import_options(root.scene_file_path.get_base_dir()) + + _save_config() + + var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source_path, options) + + if not aseprite_output.is_ok: + _notify_aseprite_error(aseprite_output.code) + return + + file_system.scan() + await file_system.filesystem_changed + + var anim_options = { + "keep_anim_length": _keep_length.button_pressed, + "cleanup_hide_unused_nodes": _cleanup_hide_unused_nodes.button_pressed, + "slice": _slice, + } + + animation_creator.create_animations(target_node, root.get_node(_animation_player_path), aseprite_output.content, anim_options) + _importing = false + + _handle_cleanup(aseprite_output.content) + +## +## Import first frame from aseprite file as node texture +## +func _import_static(): + var source_path = ProjectSettings.globalize_path(_source) + var root = get_tree().get_edited_scene_root() + + var options = _get_import_options(root.scene_file_path.get_base_dir()) + options["first_frame_only"] = true + + _save_config() + + var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source_path, options) + + if not aseprite_output.is_ok: + _notify_aseprite_error(aseprite_output.code) + return + + file_system.scan() + await file_system.filesystem_changed + + static_texture_creator.load_texture(target_node, aseprite_output.content, { "slice": _slice }) + + + _importing = false + _handle_cleanup(aseprite_output.content) + + +func _get_current_field_values() -> Dictionary: + var cfg := { + "i_mode": _import_mode, + "player": _animation_player_path, + "keep_anim_length": _keep_length.button_pressed, + } + + if _cleanup_hide_unused_nodes.button_pressed != config.is_set_visible_track_automatically_enabled(): + cfg["set_vis_track"] = _cleanup_hide_unused_nodes.button_pressed + + return cfg + + +func _get_available_layers(global_source_path: String) -> Array: + return animation_creator.list_layers(global_source_path) + + +func _get_available_slices(global_source_path: String) -> Array: + return animation_creator.list_slices(global_source_path) + + +func _on_animation_player_node_dropped(node_path): + var node = get_node(node_path) + var root = get_tree().get_edited_scene_root() + + _animation_player_path = root.get_path_to(node) + + for i in range(_animation_player_field.get_item_count()): + if _animation_player_field.get_item_text(i) == _animation_player_path: + _animation_player_field.select(i) + break + _save_config() + + +func _on_modes_item_selected(index): + var id = _import_mode_options_field.get_item_id(index) + _import_mode = id + _handle_import_mode() + + +func _on_animation_header_button_down(): + _toggle_section_visibility(INTERFACE_SECTION_KEY_ANIMATION) + + +func _show_specific_fields(): + _import_mode_options_field.get_parent().show() + _animation_player_container.show() + _animation_section.show() diff --git a/addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.tscn b/addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.tscn new file mode 100644 index 0000000..1d8e10b --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=3 format=3 uid="uid://biyshalfalqqw"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.gd" id="1_ku6wj"] +[ext_resource type="PackedScene" uid="uid://uxm7b02wry10" path="res://addons/AsepriteWizard/interface/docks/dock_fields.tscn" id="2_wkqx4"] + +[node name="sprite_inspector_dock" type="PanelContainer"] +offset_right = 14.0 +offset_bottom = 14.0 +script = ExtResource("1_ku6wj") + +[node name="dock_fields" parent="." instance=ExtResource("2_wkqx4")] +layout_mode = 2 diff --git a/addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.gd b/addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.gd new file mode 100644 index 0000000..0078a5a --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.gd @@ -0,0 +1,37 @@ +@tool +extends TabContainer + +signal close_requested + +const WizardWindow = preload("./as_wizard_window.tscn") + +var _config +var _file_system: EditorFileSystem + +func _ready(): + $Import.init(_config, _file_system) + $Import.connect("close_requested",Callable(self,"emit_signal").bind("close_requested")) + $Import.connect("import_success",Callable($History,"add_entry")) + $History.init(_config) + $History.connect("request_edit",Callable(self,"_on_edit_request")) + $History.connect("request_import",Callable(self,"_on_import_request")) + + +func init(config, editor_file_system: EditorFileSystem): + _config = config + _file_system = editor_file_system + + +func _on_AsWizardDockContainer_tab_changed(tab: int): + if tab == 1: + $History.reload() + + +func _on_edit_request(import_cfg: Dictionary): + $Import.load_import_config(import_cfg) + self.current_tab = 0 + + +func _on_import_request(import_cfg: Dictionary): + $Import.load_import_config(import_cfg) + $Import.trigger_import() diff --git a/addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.tscn b/addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.tscn new file mode 100644 index 0000000..c44534b --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.tscn @@ -0,0 +1,26 @@ +[gd_scene load_steps=4 format=3] + +[ext_resource type="PackedScene" uid="uid://c5dwobjd34w3p" path="res://addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.tscn" id="1"] +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.gd" id="2"] +[ext_resource type="PackedScene" uid="uid://cyoin5ncul0fm" path="res://addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.tscn" id="3"] + +[node name="AsWizardDockContainer" type="TabContainer"] +offset_right = 281.0 +offset_bottom = 36.0 +tab_alignment = 2 +use_hidden_tabs_for_min_size = true +script = ExtResource( 2 ) + +[node name="Import" parent="." instance=ExtResource( 1 )] +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 4.0 +offset_top = 32.0 +offset_right = -4.0 +offset_bottom = -4.0 +tooltip_text = "SpriteFrames Importer" + +[node name="History" parent="." instance=ExtResource( 3 )] +visible = false + +[connection signal="tab_changed" from="." to="." method="_on_AsWizardDockContainer_tab_changed"] diff --git a/addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.gd b/addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.gd new file mode 100644 index 0000000..f3d99e7 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.gd @@ -0,0 +1,222 @@ +@tool +extends PanelContainer + +signal close_requested +signal import_success(file_settings) + +var result_code = preload("../../../config/result_codes.gd") +var _aseprite_file_exporter = preload("../../../aseprite/file_exporter.gd").new() +var _sf_creator = preload("../../../creators/sprite_frames/sprite_frames_creator.gd").new() + +var _config +var _file_system: EditorFileSystem + +var _file_dialog_aseprite: EditorFileDialog +var _output_folder_dialog: EditorFileDialog +var _warning_dialog: AcceptDialog + +func _exit_tree(): + _file_dialog_aseprite.queue_free() + _output_folder_dialog.queue_free() + _warning_dialog.queue_free() + + +func init(config, editor_file_system: EditorFileSystem): + _config = config + _file_system = editor_file_system + _file_dialog_aseprite = _create_aseprite_file_selection() + _output_folder_dialog = _create_outuput_folder_selection() + _warning_dialog = AcceptDialog.new() + _warning_dialog.exclusive = false + + _sf_creator.init(_config) + _aseprite_file_exporter.init(config) + + get_parent().get_parent().add_child(_file_dialog_aseprite) + get_parent().get_parent().add_child(_output_folder_dialog) + get_parent().get_parent().add_child(_warning_dialog) + + _load_persisted_config() + + +func _load_persisted_config(): + _split_mode_field().button_pressed = _config.should_split_layers() + _only_visible_layers_field().button_pressed = _config.should_include_only_visible_layers() + _exception_pattern_field().text = _config.get_exception_pattern() + _custom_name_field().text = _config.get_last_custom_name() + _file_location_field().text = _config.get_last_source_path() + _do_not_create_res_field().button_pressed = _config.should_not_create_resource() + + var output_folder = _config.get_last_output_path() + _output_folder_field().text = output_folder if output_folder != "" else "res://" + + +func load_import_config(import_config: Dictionary): + _split_mode_field().button_pressed = import_config.options.export_mode == _aseprite_file_exporter.LAYERS_EXPORT_MODE + _only_visible_layers_field().button_pressed = import_config.options.only_visible_layers + _exception_pattern_field().text = import_config.options.exception_pattern + _custom_name_field().text = import_config.options.output_filename + _file_location_field().text = import_config.source_file + _do_not_create_res_field().button_pressed = import_config.options.do_not_create_resource + _output_folder_field().text = import_config.output_location if import_config.output_location != "" else "res://" + + +func _open_aseprite_file_selection_dialog(): + var current_selection = _file_location_field().text + if current_selection != "": + _file_dialog_aseprite.current_dir = current_selection.get_base_dir() + _file_dialog_aseprite.popup_centered_ratio() + + +func _open_output_folder_selection_dialog(): + var current_selection = _output_folder_field().text + if current_selection != "": + _output_folder_dialog.current_dir = current_selection + _output_folder_dialog.popup_centered_ratio() + + +func _create_aseprite_file_selection(): + var file_dialog = EditorFileDialog.new() + file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE + file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM + file_dialog.connect("file_selected", _on_aseprite_file_selected) + file_dialog.set_filters(PackedStringArray(["*.ase","*.aseprite"])) + return file_dialog + + +func _create_outuput_folder_selection(): + var file_dialog = EditorFileDialog.new() + file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_DIR + file_dialog.access = EditorFileDialog.ACCESS_RESOURCES + file_dialog.connect("dir_selected", _on_output_folder_selected) + return file_dialog + + +func _on_aseprite_file_selected(path): + var localized_path = ProjectSettings.localize_path(path) + _file_location_field().text = localized_path + _config.set_last_source_path(localized_path) + + +func _on_output_folder_selected(path): + _output_folder_field().text = path + _config.set_last_output_path(path) + + +func _on_next_btn_up(): + var aseprite_file = _file_location_field().text + var output_location = _output_folder_field().text + var split_layers = _split_mode_field().button_pressed + + var export_mode = _aseprite_file_exporter.LAYERS_EXPORT_MODE if split_layers else _aseprite_file_exporter.FILE_EXPORT_MODE + var options = { + "export_mode": export_mode, + "exception_pattern": _exception_pattern_field().text, + "only_visible_layers": _only_visible_layers_field().button_pressed, + "output_filename": _custom_name_field().text, + "do_not_create_resource": _do_not_create_res_field().button_pressed, + "output_folder": output_location, + } + + var aseprite_output = _aseprite_file_exporter.generate_aseprite_files( + ProjectSettings.globalize_path(aseprite_file), + options + ) + + if not aseprite_output.is_ok: + _show_error(aseprite_output.code) + return + + _file_system.scan() + await _file_system.filesystem_changed + + var exit_code = OK + + if !options.get("do_not_create_resource", false): + exit_code = _sf_creator.create_and_save_resources(aseprite_output.content) + + if _config.should_remove_source_files(): + _remove_source_files(aseprite_output.content) + + if exit_code != OK: + _show_error(exit_code) + return + + emit_signal("import_success", { + "source_file": aseprite_file, + "output_location": output_location, + "options": options, + }) + + _show_import_success_message() + + +func trigger_import(): + _on_next_btn_up() + + +func _on_close_btn_up(): + _close_window() + + +func _close_window(): + _save_config() + self.emit_signal("close_requested") + + +func _save_config(): + _config.set_split_layers(_split_mode_field().button_pressed) + _config.set_exception_pattern(_exception_pattern_field().text) + _config.set_custom_name(_custom_name_field().text) + _config.set_include_only_visible_layers(_only_visible_layers_field().button_pressed) + _config.set_do_not_create_resource(_do_not_create_res_field().button_pressed) + + +func _show_error(code: int): + _show_error_message(result_code.get_error_message(code)) + + +func _show_error_message(message: String): + _warning_dialog.dialog_text = "Error: %s" % message + _warning_dialog.popup_centered() + + +func _show_import_success_message(): + _warning_dialog.dialog_text = "Aseprite import succeeded" + _warning_dialog.popup_centered() + _save_config() + + +func _file_location_field() -> LineEdit: + return $container/options/file_location/HBoxContainer/file_location_path as LineEdit + + +func _output_folder_field() -> LineEdit: + return $container/options/output_folder/HBoxContainer/file_location_path as LineEdit + + +func _exception_pattern_field() -> LineEdit: + return $container/options/exclude_pattern/pattern as LineEdit + + +func _split_mode_field() -> CheckBox: + return $container/options/layer_importing_mode/split_layers/field as CheckBox + + +func _only_visible_layers_field() -> CheckBox: + return $container/options/layer_importing_mode/visible_layers/field as CheckBox + + +func _custom_name_field() -> LineEdit: + return $container/options/custom_filename/pattern as LineEdit + + +func _do_not_create_res_field() -> CheckBox: + return $container/options/layer_importing_mode/disable_resource_creation/field as CheckBox + + +func _remove_source_files(source_files: Array): + for s in source_files: + DirAccess.remove_absolute(s.data_file) + + _file_system.call_deferred("scan") diff --git a/addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.tscn b/addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.tscn new file mode 100644 index 0000000..5d32514 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.tscn @@ -0,0 +1,139 @@ +[gd_scene load_steps=2 format=3 uid="uid://c5dwobjd34w3p"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.gd" id="1"] + +[node name="ASWizardWindow" type="PanelContainer"] +offset_right = 600.0 +offset_bottom = 600.0 +size_flags_horizontal = 3 +size_flags_vertical = 0 +script = ExtResource("1") + +[node name="container" type="MarginContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="options" type="VBoxContainer" parent="container"] +layout_mode = 2 + +[node name="file_location" type="VBoxContainer" parent="container/options"] +layout_mode = 2 + +[node name="file_location_label" type="Label" parent="container/options/file_location"] +layout_mode = 2 +tooltip_text = "Location of the Aseprite *.ase source file" +mouse_filter = 1 +text = "Aseprite File Location:*" +clip_text = true + +[node name="HBoxContainer" type="HBoxContainer" parent="container/options/file_location"] +layout_mode = 2 + +[node name="file_location_path" type="LineEdit" parent="container/options/file_location/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +caret_blink = true + +[node name="file_location_btn" type="Button" parent="container/options/file_location/HBoxContainer"] +layout_mode = 2 +text = "Select" + +[node name="output_folder" type="VBoxContainer" parent="container/options"] +layout_mode = 2 + +[node name="label" type="Label" parent="container/options/output_folder"] +layout_mode = 2 +tooltip_text = "Folder where the SpriteSheet resource is going to be saved" +mouse_filter = 1 +text = "Output folder:*" + +[node name="HBoxContainer" type="HBoxContainer" parent="container/options/output_folder"] +layout_mode = 2 + +[node name="file_location_path" type="LineEdit" parent="container/options/output_folder/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "res://" +caret_blink = true + +[node name="output_folder_btn" type="Button" parent="container/options/output_folder/HBoxContainer"] +layout_mode = 2 +text = "Select" + +[node name="exclude_pattern" type="VBoxContainer" parent="container/options"] +layout_mode = 2 + +[node name="label" type="Label" parent="container/options/exclude_pattern"] +layout_mode = 2 +tooltip_text = "If layer name matches this pattern, it won't be exported." +mouse_filter = 1 +text = "Exclude layers with name matching pattern:" + +[node name="pattern" type="LineEdit" parent="container/options/exclude_pattern"] +layout_mode = 2 +caret_blink = true + +[node name="custom_filename" type="VBoxContainer" parent="container/options"] +layout_mode = 2 + +[node name="label" type="Label" parent="container/options/custom_filename"] +layout_mode = 2 +tooltip_text = "Output filename. In case layers are not being merged, this is used as file prefix. +If not set, source filename is used." +mouse_filter = 1 +text = "Output file name / prefix:" + +[node name="pattern" type="LineEdit" parent="container/options/custom_filename"] +layout_mode = 2 +caret_blink = true + +[node name="layer_importing_mode" type="VBoxContainer" parent="container/options"] +layout_mode = 2 + +[node name="label" type="Label" parent="container/options/layer_importing_mode"] +layout_mode = 2 +mouse_filter = 1 +text = "Options:" + +[node name="split_layers" type="HBoxContainer" parent="container/options/layer_importing_mode"] +layout_mode = 2 + +[node name="field" type="CheckBox" parent="container/options/layer_importing_mode/split_layers"] +layout_mode = 2 +tooltip_text = "If selected, one resource will be created for each layer. +If not selected, layers will be merged and exported as one SpriteSheet." +text = "Split layers in multiple resources" + +[node name="visible_layers" type="HBoxContainer" parent="container/options/layer_importing_mode"] +layout_mode = 2 + +[node name="field" type="CheckBox" parent="container/options/layer_importing_mode/visible_layers"] +layout_mode = 2 +tooltip_text = "If selected, layers not visible in the source file won't be included in the final image." +text = "Only include visible layers" + +[node name="disable_resource_creation" type="HBoxContainer" parent="container/options/layer_importing_mode"] +layout_mode = 2 + +[node name="field" type="CheckBox" parent="container/options/layer_importing_mode/disable_resource_creation"] +layout_mode = 2 +tooltip_text = "Does not create SpriteFrames resource. Useful if you are only interested in the .json and .png output from Aseprite." +text = "Do not create resource file" + +[node name="buttons" type="HBoxContainer" parent="container/options"] +layout_mode = 2 +alignment = 2 + +[node name="close" type="Button" parent="container/options/buttons"] +layout_mode = 2 +text = "Close" + +[node name="next" type="Button" parent="container/options/buttons"] +layout_mode = 2 +text = "Import" + +[connection signal="button_up" from="container/options/file_location/HBoxContainer/file_location_btn" to="." method="_open_aseprite_file_selection_dialog"] +[connection signal="button_up" from="container/options/output_folder/HBoxContainer/output_folder_btn" to="." method="_open_output_folder_selection_dialog"] +[connection signal="button_up" from="container/options/buttons/close" to="." method="_on_close_btn_up"] +[connection signal="button_up" from="container/options/buttons/next" to="." method="_on_next_btn_up"] diff --git a/addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.gd b/addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.gd new file mode 100644 index 0000000..2fcb76d --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.gd @@ -0,0 +1,226 @@ +@tool +extends PanelContainer + +signal request_edit(import_cfg) +signal request_import(import_cfg) + +const SourcePathField = preload("./wizard_nodes/source_path.tscn") +const OutputPathField = preload("./wizard_nodes/output_path.tscn") +const ImportDateField = preload("./wizard_nodes/import_date.tscn") +const ItemActions = preload("./wizard_nodes/list_actions.tscn") + +const SORT_BY_DATE := 0 +const SORT_BY_PATH := 1 +const INITIAL_GRID_INDEX := 3 + +var _config +var _history: Array +var _history_nodes := {} +var _history_nodes_list := [] +var _is_busy := false +var _import_requested_for := -1 +var _sort_by = SORT_BY_DATE + +@onready var grid = $MarginContainer/VBoxContainer/ScrollContainer/GridContainer +@onready var loading_warning = $MarginContainer/VBoxContainer/loading_warning +@onready var no_history_warning = $MarginContainer/VBoxContainer/no_history_warning + +func init(config): + _config = config + + +func reload(): + if _history: + return + + _history = _config.get_import_history() + + for index in range(_history.size()): + var entry = _history[index] + _create_node_list_entry(entry, index) + + loading_warning.hide() + if _history.is_empty(): + no_history_warning.show() + else: + grid.get_parent().show() + + +func _create_node_list_entry(entry: Dictionary, index: int): + _add_to_node_list(entry, _create_nodes(entry, index)) + + +func _create_nodes(entry: Dictionary, index: int) -> Dictionary: + var source_path = SourcePathField.instantiate() + source_path.set_entry(entry) + + grid.get_child(INITIAL_GRID_INDEX).add_sibling(source_path) + + var output_path = OutputPathField.instantiate() + output_path.text = entry.output_location + source_path.add_sibling(output_path) + var import_date = ImportDateField.instantiate() + import_date.set_date(entry.import_date) + output_path.add_sibling(import_date) + var actions = ItemActions.instantiate() + actions.history_index = index + import_date.add_sibling(actions) + actions.connect("import_clicked",Callable(self,"_on_entry_reimport_clicked")) + actions.connect("edit_clicked",Callable(self,"_on_entry_edit_clicked")) + actions.connect("removed_clicked",Callable(self,"_on_entry_remove_clicked")) + return { + "history_index": index, + "timestamp": entry.import_date, + "source_file": entry.source_file, + "source_path_node": source_path, + "output_path_node": output_path, + "import_date_node": import_date, + "actions_node": actions, + } + + +func _add_to_node_list(entry: Dictionary, node: Dictionary): + if not _history_nodes.has(entry.source_file): + _history_nodes[entry.source_file] = [] + _history_nodes[entry.source_file].push_front(node) + _history_nodes_list.push_front(node) + + +func add_entry(file_settings: Dictionary): + if _history == null: + reload() + + var dt = Time.get_datetime_dict_from_system() + file_settings["import_date"] = "%04d-%02d-%02d %02d:%02d:%02d" % [dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second] + + if _import_requested_for != -1: + _remove_item(_import_requested_for) + _import_requested_for = -1 + elif _config.is_single_file_history() and _history_nodes.has(file_settings.source_file): + _remove_entries(file_settings.source_file) + + _history.push_back(file_settings) + _config.save_import_history(_history) + _create_node_list_entry(file_settings, _history.size() - 1) + + if _sort_by == SORT_BY_PATH: + _trigger_sort() + + no_history_warning.hide() + loading_warning.hide() + grid.get_parent().show() + _is_busy = false + + +func _on_entry_reimport_clicked(entry_index: int): + if _is_busy: + return + _is_busy = true + _import_requested_for = entry_index + emit_signal("request_import", _history[entry_index]) + + +func _on_entry_edit_clicked(entry_index: int): + if _is_busy: + return + emit_signal("request_edit", _history[entry_index]) + + +func _on_entry_remove_clicked(entry_index: int): + if _is_busy: + return + _is_busy = true + + _remove_item(entry_index) + _config.save_import_history(_history) + + if (_history.is_empty()): + grid.get_parent().hide() + no_history_warning.show() + + _is_busy = false + + +func _remove_item(entry_index: int): + var entry = _history[entry_index] + _remove_entries(entry.source_file, entry_index) + + +# removes nodes and entry from history. If entry_index is not provided, all +# entries for path are removed. +func _remove_entries(source_file_path: String, entry_index: int = -1): + var files_entries = _history_nodes[source_file_path] + var indexes_to_remove = [] + + for f in files_entries: + if entry_index == -1 or f.history_index == entry_index: + _free_entry_nodes(f) + _history_nodes_list.erase(f) + + if entry_index != -1: + files_entries.erase(f) + _remove_from_history(f.history_index) + return + + indexes_to_remove.push_back(f.history_index) + + for i in indexes_to_remove: + _remove_from_history(i) + + _history_nodes[source_file_path] = [] + + +func _remove_from_history(entry_index: int): + var _already_adjusted = [] + # to avoid re-creating the whole nodes list, I just decrement + # the index from items newer than the excluded one + for index in range(entry_index + 1, _history.size()): + if _already_adjusted.has(_history[index].source_file): + continue + _already_adjusted.push_back(_history[index].source_file) + var nodes = _history_nodes[_history[index].source_file] + for f in nodes: + if f.history_index > entry_index: + f.history_index -= 1 + f.actions_node.history_index = f.history_index + + _history.remove_at(entry_index) + + +func _free_entry_nodes(entry_history_node: Dictionary): + entry_history_node.source_path_node.queue_free() + entry_history_node.output_path_node.queue_free() + entry_history_node.import_date_node.queue_free() + entry_history_node.actions_node.queue_free() + + +func _on_SortOptions_item_selected(index): + if index == _sort_by: + return + + _trigger_sort(index) + + +func _trigger_sort(sort_type: int = _sort_by): + if sort_type == SORT_BY_DATE: + _history_nodes_list.sort_custom(Callable(self,"_sort_by_date")) + else: + _history_nodes_list.sort_custom(Callable(self,"_sort_by_path")) + _reorganise_nodes() + _sort_by = sort_type + + +func _sort_by_date(a, b): + return a.timestamp < b.timestamp + + +func _sort_by_path(a, b): + return a.source_file > b.source_file + + +func _reorganise_nodes(): + for entry in _history_nodes_list: + grid.move_child(entry.source_path_node, INITIAL_GRID_INDEX + 1) + grid.move_child(entry.output_path_node, INITIAL_GRID_INDEX + 2) + grid.move_child(entry.import_date_node, INITIAL_GRID_INDEX + 3) + grid.move_child(entry.actions_node, INITIAL_GRID_INDEX + 4) diff --git a/addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.tscn b/addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.tscn new file mode 100644 index 0000000..7dded74 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.tscn @@ -0,0 +1,81 @@ +[gd_scene load_steps=2 format=3 uid="uid://cyoin5ncul0fm"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.gd" id="1"] + +[node name="SpriteFramesImportHistory" type="PanelContainer"] +anchors_preset = 10 +anchor_right = 1.0 +grow_horizontal = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("1") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="list actions" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 0 +alignment = 2 + +[node name="divider" type="Label" parent="MarginContainer/VBoxContainer/list actions"] +layout_mode = 2 + +[node name="sort_label" type="Label" parent="MarginContainer/VBoxContainer/list actions"] +layout_mode = 2 +text = "Sort by:" + +[node name="SortOptions" type="OptionButton" parent="MarginContainer/VBoxContainer/list actions"] +layout_mode = 2 +item_count = 2 +selected = 0 +popup/item_0/text = "Date" +popup/item_0/id = 0 +popup/item_1/text = "Name" +popup/item_1/id = 1 + +[node name="loading_warning" type="Label" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 6 +text = "Loading..." + +[node name="no_history_warning" type="Label" parent="MarginContainer/VBoxContainer"] +visible = false +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 6 +text = "No import history" + +[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer"] +visible = false +layout_mode = 2 +size_flags_vertical = 3 + +[node name="GridContainer" type="GridContainer" parent="MarginContainer/VBoxContainer/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +columns = 4 + +[node name="source_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Source File" + +[node name="output_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"] +layout_mode = 2 +text = "Output Folder" + +[node name="date_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"] +layout_mode = 2 +text = "Date" + +[node name="actions_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"] +layout_mode = 2 +text = "Actions" + +[connection signal="item_selected" from="MarginContainer/VBoxContainer/list actions/SortOptions" to="." method="_on_SortOptions_item_selected"] diff --git a/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.gd b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.gd new file mode 100644 index 0000000..785a7b3 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.gd @@ -0,0 +1,12 @@ +@tool +extends Label + + +func set_date(timestamp: String): + self.text = timestamp +# self.text = _format_date(timestamp) + + +#func _format_date(timestamp: int) -> String: +# var dt = Time.get_datetime_dict_from_system_from_unix_time(timestamp) +# return "%04d-%02d-%02d %02d:%02d:%02d" % [dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second] diff --git a/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.tscn b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.tscn new file mode 100644 index 0000000..92f94ba --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.tscn @@ -0,0 +1,11 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.gd" id="1"] + +[node name="import_date" type="Label"] +offset_left = 660.0 +offset_top = 39.0 +offset_right = 774.0 +offset_bottom = 53.0 +text = "2022-07-18 18:01" +script = ExtResource( 1 ) diff --git a/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/list_actions.gd b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/list_actions.gd new file mode 100644 index 0000000..ace8376 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/list_actions.gd @@ -0,0 +1,20 @@ +@tool +extends HBoxContainer + +signal import_clicked(index) +signal edit_clicked(index) +signal removed_clicked(index) + +var history_index = -1 + + +func _on_edit_pressed(): + emit_signal("edit_clicked", history_index) + + +func _on_reimport_pressed(): + emit_signal("import_clicked", history_index) + + +func _on_remove_pressed(): + emit_signal("removed_clicked", history_index) diff --git a/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/list_actions.tscn b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/list_actions.tscn new file mode 100644 index 0000000..b620fad --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/list_actions.tscn @@ -0,0 +1,35 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/list_actions.gd" id="1"] + +[node name="actions" type="HBoxContainer"] +offset_left = 784.0 +offset_top = 34.0 +offset_right = 950.0 +offset_bottom = 58.0 +custom_constants/separation = 5 +script = ExtResource( 1 ) + +[node name="edit" type="Button" parent="."] +offset_right = 36.0 +offset_bottom = 20.0 +size_flags_vertical = 0 +text = "Edit" + +[node name="reimport" type="Button" parent="."] +offset_left = 41.0 +offset_right = 97.0 +offset_bottom = 20.0 +size_flags_vertical = 0 +text = "Import" + +[node name="remove_at" type="Button" parent="."] +offset_left = 102.0 +offset_right = 166.0 +offset_bottom = 20.0 +size_flags_vertical = 0 +text = "Remove" + +[connection signal="pressed" from="edit" to="." method="_on_edit_pressed"] +[connection signal="pressed" from="reimport" to="." method="_on_reimport_pressed"] +[connection signal="pressed" from="remove_at" to="." method="_on_remove_pressed"] diff --git a/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/output_path.tscn b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/output_path.tscn new file mode 100644 index 0000000..6b9fa5b --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/output_path.tscn @@ -0,0 +1,12 @@ +[gd_scene format=2] + +[node name="output_path" type="LineEdit"] +offset_left = 450.0 +offset_top = 34.0 +offset_right = 650.0 +offset_bottom = 58.0 +minimum_size = Vector2( 200, 0 ) +size_flags_vertical = 0 +text = "/some/output" +align = 3 +editable = false diff --git a/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/source_path.gd b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/source_path.gd new file mode 100644 index 0000000..1f316bc --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/source_path.gd @@ -0,0 +1,22 @@ +@tool +extends LineEdit + + +func set_entry(entry: Dictionary): + self.text = entry.source_file + self.tooltip_text = _format_hint(entry) + + +func _format_hint(entry: Dictionary) -> String: + return """Output filename/prefix: %s +Ex. pattern: %s +Split: %s +Only visible: %s +No Resource: %s +""" % [ + entry.options.output_filename, + entry.options.exception_pattern, + "yes" if entry.options.export_mode else "no", + "yes" if entry.options.only_visible_layers else "no", + "yes" if entry.options.do_not_create_resource else "no", +] diff --git a/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/source_path.tscn b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/source_path.tscn new file mode 100644 index 0000000..c28e803 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/source_path.tscn @@ -0,0 +1,18 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/source_path.gd" id="1"] + +[node name="source_path" type="LineEdit"] +offset_top = 34.0 +offset_right = 440.0 +offset_bottom = 58.0 +minimum_size = Vector2( 200, 0 ) +tooltip_text = "Output name / prefix: some_name +Ex. Pattern: $_ +Split: Yes +Only Visible: No +No Resource: No" +size_flags_vertical = 0 +text = "/some/very/long/source/path/to/test/field" +editable = false +script = ExtResource( 1 ) diff --git a/addons/AsepriteWizard/interface/shared/animation_player_drop_button.gd b/addons/AsepriteWizard/interface/shared/animation_player_drop_button.gd new file mode 100644 index 0000000..dbcd0ac --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/animation_player_drop_button.gd @@ -0,0 +1,15 @@ +@tool +extends Button + +signal node_dropped(node_path) + +func _can_drop_data(_pos, data): + if data.type == "nodes": + var node = get_node(data.nodes[0]) + return node is AnimationPlayer + return false + + +func _drop_data(_pos, data): + var path = data.nodes[0] + node_dropped.emit(path) diff --git a/addons/AsepriteWizard/interface/shared/animation_player_drop_button.tscn b/addons/AsepriteWizard/interface/shared/animation_player_drop_button.tscn new file mode 100644 index 0000000..14148a5 --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/animation_player_drop_button.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=3 uid="uid://x1f1t87m582u"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/shared/animation_player_drop_button.gd" id="1_tfmxw"] + +[node name="options" type="OptionButton"] +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +item_count = 1 +selected = 0 +popup/item_0/text = "[empty]" +popup/item_0/id = 0 +script = ExtResource("1_tfmxw") diff --git a/addons/AsepriteWizard/interface/shared/dir_drop_button.gd b/addons/AsepriteWizard/interface/shared/dir_drop_button.gd new file mode 100644 index 0000000..42c5489 --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/dir_drop_button.gd @@ -0,0 +1,14 @@ +@tool +extends Button + +signal dir_dropped(path) + +func _can_drop_data(_pos, data): + if data.type == "files_and_dirs": + var dir_access = DirAccess.open(data.files[0]) + return dir_access != null + return false + + +func _drop_data(_pos, data): + dir_dropped.emit(data.files[0]) diff --git a/addons/AsepriteWizard/interface/shared/dir_drop_button.tscn b/addons/AsepriteWizard/interface/shared/dir_drop_button.tscn new file mode 100644 index 0000000..332d3cc --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/dir_drop_button.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=2 format=3 uid="uid://cwvgnm3o7eed2"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/shared/dir_drop_button.gd" id="1_7uqph"] + +[node name="button" type="Button"] +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "[empty]" +clip_text = true +script = ExtResource("1_7uqph") diff --git a/addons/AsepriteWizard/interface/shared/source_drop_button.gd b/addons/AsepriteWizard/interface/shared/source_drop_button.gd new file mode 100644 index 0000000..3fdeed2 --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/source_drop_button.gd @@ -0,0 +1,15 @@ +@tool +extends Button + +signal aseprite_file_dropped(path) + +func _can_drop_data(_pos, data): + if data.type == "files": + var extension = data.files[0].get_extension() + return extension == "ase" or extension == "aseprite" + return false + + +func _drop_data(_pos, data): + var path = data.files[0] + aseprite_file_dropped.emit(path) diff --git a/addons/AsepriteWizard/interface/shared/source_drop_button.tscn b/addons/AsepriteWizard/interface/shared/source_drop_button.tscn new file mode 100644 index 0000000..dc0c25e --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/source_drop_button.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=2 format=3 uid="uid://dj1uo3blocr8e"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/shared/source_drop_button.gd" id="1_smfgi"] + +[node name="source_drop_button" type="Button"] +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "[empty]" +clip_text = true +script = ExtResource("1_smfgi") diff --git a/addons/AsepriteWizard/plugin.cfg b/addons/AsepriteWizard/plugin.cfg new file mode 100644 index 0000000..f0abd13 --- /dev/null +++ b/addons/AsepriteWizard/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Aseprite Wizard" +description="Import Aseprite files to Godot in many different ways." +author="Vinicius Gerevini" +version="7.3.0" +script="plugin.gd" diff --git a/addons/AsepriteWizard/plugin.gd b/addons/AsepriteWizard/plugin.gd new file mode 100644 index 0000000..d2f31ca --- /dev/null +++ b/addons/AsepriteWizard/plugin.gd @@ -0,0 +1,156 @@ +@tool +extends EditorPlugin + +# importers +const NoopImportPlugin = preload("importers/noop_import_plugin.gd") +const SpriteFramesImportPlugin = preload("importers/sprite_frames_import_plugin.gd") +const TilesetTextureImportPlugin = preload("importers/tileset_texture_import_plugin.gd") +const TextureImportPlugin = preload("importers/static_texture_import_plugin.gd") + +# export +const ExportPlugin = preload("export/metadata_export_plugin.gd") +# interface +const ConfigDialog = preload('config/config_dialog.tscn') +const WizardWindow = preload("interface/docks/wizard/as_wizard_dock_container.tscn") +const AnimatedSpriteInspectorPlugin = preload("interface/docks/animated_sprite/inspector_plugin.gd") +const SpriteInspectorPlugin = preload("interface/docks/sprite/inspector_plugin.gd") + +const menu_item_name = "Aseprite Spritesheet Wizard" +const config_menu_item_name = "Aseprite Wizard Config" + +var config = preload("config/config.gd").new() +var window: TabContainer +var config_window: PopupPanel +var export_plugin : EditorExportPlugin +var sprite_inspector_plugin: EditorInspectorPlugin +var animated_sprite_inspector_plugin: EditorInspectorPlugin + +var _exporter_enabled = false + +var _importers = [] + +func _enter_tree(): + _load_config() + _setup_menu_entries() + _setup_importer() + _setup_exporter() + _setup_animated_sprite_inspector_plugin() + _setup_sprite_inspector_plugin() + + +func _disable_plugin(): + _remove_menu_entries() + _remove_importer() + _remove_exporter() + _remove_wizard_dock() + _remove_inspector_plugins() + config.clear_project_settings() + config.set_icons({}) + + +func _load_config(): + var editor_gui = get_editor_interface().get_base_control() + config._editor_settings = get_editor_interface().get_editor_settings() + config.set_icons({ + "collapsed": editor_gui.get_theme_icon("GuiTreeArrowRight", "EditorIcons"), + "expanded": editor_gui.get_theme_icon("GuiTreeArrowDown", "EditorIcons"), + }) + config.initialize_project_settings() + + +func _setup_menu_entries(): + add_tool_menu_item(menu_item_name, _open_window) + add_tool_menu_item(config_menu_item_name, _open_config_dialog) + + +func _remove_menu_entries(): + remove_tool_menu_item(menu_item_name) + remove_tool_menu_item(config_menu_item_name) + + +func _setup_importer(): + _importers = [ + NoopImportPlugin.new(), + SpriteFramesImportPlugin.new(), + TilesetTextureImportPlugin.new(), + TextureImportPlugin.new(), + ] + + for i in _importers: + if not i is NoopImportPlugin: + i.file_system = get_editor_interface().get_resource_filesystem() + i.config = config + add_import_plugin(i) + + +func _remove_importer(): + for i in _importers: + remove_import_plugin(i) + + +func _setup_exporter(): + if config.is_exporter_enabled(): + export_plugin = ExportPlugin.new() + add_export_plugin(export_plugin) + _exporter_enabled = true + + +func _remove_exporter(): + if _exporter_enabled: + remove_export_plugin(export_plugin) + _exporter_enabled = false + + +func _setup_sprite_inspector_plugin(): + sprite_inspector_plugin = SpriteInspectorPlugin.new() + sprite_inspector_plugin.file_system = get_editor_interface().get_resource_filesystem() + sprite_inspector_plugin.config = config + add_inspector_plugin(sprite_inspector_plugin) + + +func _setup_animated_sprite_inspector_plugin(): + animated_sprite_inspector_plugin = AnimatedSpriteInspectorPlugin.new() + animated_sprite_inspector_plugin.file_system = get_editor_interface().get_resource_filesystem() + animated_sprite_inspector_plugin.config = config + add_inspector_plugin(animated_sprite_inspector_plugin) + + +func _remove_inspector_plugins(): + remove_inspector_plugin(sprite_inspector_plugin) + remove_inspector_plugin(animated_sprite_inspector_plugin) + + +func _remove_wizard_dock(): + if window: + remove_control_from_bottom_panel(window) + window.queue_free() + window = null + + +func _open_window(): + if window: + make_bottom_panel_item_visible(window) + return + + window = WizardWindow.instantiate() + window.init(config, get_editor_interface().get_resource_filesystem()) + window.connect("close_requested",Callable(self,"_on_window_closed")) + add_control_to_bottom_panel(window, "Aseprite Wizard") + make_bottom_panel_item_visible(window) + + +func _open_config_dialog(): + if is_instance_valid(config_window): + config_window.queue_free() + + config_window = ConfigDialog.instantiate() + config_window.init(config) + get_editor_interface().get_base_control().add_child(config_window) + config_window.popup_centered() + + +func _on_window_closed(): + if window: + remove_control_from_bottom_panel(window) + window.queue_free() + window = null diff --git a/assets/aseprite/bullet.aseprite b/assets/aseprite/bullet.aseprite new file mode 100644 index 0000000..3b55828 Binary files /dev/null and b/assets/aseprite/bullet.aseprite differ diff --git a/assets/aseprite/bullet.aseprite.import b/assets/aseprite/bullet.aseprite.import new file mode 100644 index 0000000..6369be2 --- /dev/null +++ b/assets/aseprite/bullet.aseprite.import @@ -0,0 +1,14 @@ +[remap] + +importer="aseprite_wizard.plugin.noop" +type="PackedDataContainer" +uid="uid://dx8eefmcdt3sd" +path="res://.godot/imported/bullet.aseprite-21c319dcb02028c8f5a92983c96c2be1.res" + +[deps] + +source_file="res://assets/aseprite/bullet.aseprite" +dest_files=["res://.godot/imported/bullet.aseprite-21c319dcb02028c8f5a92983c96c2be1.res"] + +[params] + diff --git a/assets/aseprite/kai.aseprite b/assets/aseprite/kai.aseprite new file mode 100644 index 0000000..ff53532 Binary files /dev/null and b/assets/aseprite/kai.aseprite differ diff --git a/assets/aseprite/kai.aseprite.import b/assets/aseprite/kai.aseprite.import new file mode 100644 index 0000000..983370d --- /dev/null +++ b/assets/aseprite/kai.aseprite.import @@ -0,0 +1,14 @@ +[remap] + +importer="aseprite_wizard.plugin.noop" +type="PackedDataContainer" +uid="uid://dq4rvjvowyx2c" +path="res://.godot/imported/kai.aseprite-b1b60f0bb9653c1404969ad4e81aee97.res" + +[deps] + +source_file="res://assets/aseprite/kai.aseprite" +dest_files=["res://.godot/imported/kai.aseprite-b1b60f0bb9653c1404969ad4e81aee97.res"] + +[params] + diff --git a/assets/aseprite/luminaCity.aseprite b/assets/aseprite/luminaCity.aseprite new file mode 100644 index 0000000..860ccc4 Binary files /dev/null and b/assets/aseprite/luminaCity.aseprite differ diff --git a/assets/aseprite/luminaCity.aseprite.import b/assets/aseprite/luminaCity.aseprite.import new file mode 100644 index 0000000..446503d --- /dev/null +++ b/assets/aseprite/luminaCity.aseprite.import @@ -0,0 +1,14 @@ +[remap] + +importer="aseprite_wizard.plugin.noop" +type="PackedDataContainer" +uid="uid://bhtqj7focrcii" +path="res://.godot/imported/luminaCity.aseprite-e101a45c1a0cb4d808d501d30520b7e8.res" + +[deps] + +source_file="res://assets/aseprite/luminaCity.aseprite" +dest_files=["res://.godot/imported/luminaCity.aseprite-e101a45c1a0cb4d808d501d30520b7e8.res"] + +[params] + diff --git a/assets/kai.png b/assets/kai.png new file mode 100644 index 0000000..1152707 Binary files /dev/null and b/assets/kai.png differ diff --git a/assets/kai.png.import b/assets/kai.png.import new file mode 100644 index 0000000..f8a4b6d --- /dev/null +++ b/assets/kai.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://pqcr6hxnafee" +path="res://.godot/imported/kai.png-9e407647307b03e8f6491982bc557e2f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/kai.png" +dest_files=["res://.godot/imported/kai.png-9e407647307b03e8f6491982bc557e2f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/luminaCity.png b/assets/luminaCity.png new file mode 100644 index 0000000..a8a2544 Binary files /dev/null and b/assets/luminaCity.png differ diff --git a/assets/luminaCity.png.import b/assets/luminaCity.png.import new file mode 100644 index 0000000..9cf78b9 --- /dev/null +++ b/assets/luminaCity.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://0d1migp7yxn4" +path="res://.godot/imported/luminaCity.png-9dce35cefad2c5a3e6058b7a824920ff.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/luminaCity.png" +dest_files=["res://.godot/imported/luminaCity.png-9dce35cefad2c5a3e6058b7a824920ff.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/luminaDeflect.png b/assets/luminaDeflect.png new file mode 100644 index 0000000..7bc7f00 Binary files /dev/null and b/assets/luminaDeflect.png differ diff --git a/assets/luminaDeflect.png.import b/assets/luminaDeflect.png.import new file mode 100644 index 0000000..7b41939 --- /dev/null +++ b/assets/luminaDeflect.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bk0e828p023ph" +path="res://.godot/imported/luminaDeflect.png-fb6721020dc16cd804113da02bccedbd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/luminaDeflect.png" +dest_files=["res://.godot/imported/luminaDeflect.png-fb6721020dc16cd804113da02bccedbd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/bullet.png b/bullet.png new file mode 100644 index 0000000..c86fa56 Binary files /dev/null and b/bullet.png differ diff --git a/bullet.png.import b/bullet.png.import new file mode 100644 index 0000000..ceec819 --- /dev/null +++ b/bullet.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://5228u5ni0i4q" +path="res://.godot/imported/bullet.png-ff1424653e10246c11e3724e402c519e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://bullet.png" +dest_files=["res://.godot/imported/bullet.png-ff1424653e10246c11e3724e402c519e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/bullet.tscn b/bullet.tscn new file mode 100644 index 0000000..18239bc --- /dev/null +++ b/bullet.tscn @@ -0,0 +1,39 @@ +[gd_scene load_steps=4 format=3 uid="uid://b5kpn56c3w5h6"] + +[ext_resource type="Script" path="res://scripts/bullet.gd" id="1_67gkv"] +[ext_resource type="Texture2D" uid="uid://5228u5ni0i4q" path="res://bullet.png" id="1_c7mr3"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_t8jlo"] +size = Vector2(8, 2) + +[node name="Bullet" type="RigidBody2D"] +collision_layer = 0 +collision_mask = 2 +gravity_scale = 0.0 +script = ExtResource("1_67gkv") + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture_filter = 1 +texture = ExtResource("1_c7mr3") +region_enabled = true +region_rect = Rect2(0, 0, 16, 16) +metadata/_aseprite_wizard_config_ = { +"i_mode": 1, +"keep_anim_length": false, +"layer": "", +"o_ex_p": "", +"o_folder": "", +"o_name": "", +"only_visible": false, +"player": "", +"slice": "", +"source": "res://assets/aseprite/bullet.aseprite" +} +metadata/_aseprite_wizard_interface_config_ = { +"layer_section": false, +"output_section": false, +"slice_section": false +} + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("RectangleShape2D_t8jlo") diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..b370ceb --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..2946972 --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bcg86jd1gomby" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/luminaDeflect.aseprite b/luminaDeflect.aseprite new file mode 100644 index 0000000..5de9f09 Binary files /dev/null and b/luminaDeflect.aseprite differ diff --git a/luminaDeflect.aseprite.import b/luminaDeflect.aseprite.import new file mode 100644 index 0000000..c88284e --- /dev/null +++ b/luminaDeflect.aseprite.import @@ -0,0 +1,16 @@ +[remap] + +importer="aseprite_wizard.plugin.tileset-texture" +type="PackedDataContainer" +uid="uid://b7jlbjs2p4e41" +path="res://.godot/imported/luminaDeflect.aseprite-ab646bc1a7c27ba8662373cdabb42634.res" + +[deps] + +source_file="res://luminaDeflect.aseprite" +dest_files=["res://.godot/imported/luminaDeflect.aseprite-ab646bc1a7c27ba8662373cdabb42634.res"] + +[params] + +exclude_layers_pattern="" +only_visible_layers=false diff --git a/player/player.gd b/player/player.gd new file mode 100644 index 0000000..6e9c82c --- /dev/null +++ b/player/player.gd @@ -0,0 +1,22 @@ +extends CharacterBody2D + +@export var speed = 1750 +var can_primary = true +const BULLET = preload("res://bullet.tscn") + +func _physics_process(delta: float) -> void: + var direction = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down") + velocity = direction * speed * delta + move_and_slide() + + var look_direction = Input.get_vector("look_left","look_right","look_up","look_down") + if look_direction: + rotation = look_direction.angle() + if Input.is_action_pressed("primary_fire") and can_primary: + print("primary fire") + var fire = BULLET.instantiate() + fire.direction = rotation + fire.position = position + add_sibling(fire) + can_primary = false + get_tree().create_timer(1.1).timeout.connect(func (): can_primary = true) diff --git a/player/player.tscn b/player/player.tscn new file mode 100644 index 0000000..0e2c7fa --- /dev/null +++ b/player/player.tscn @@ -0,0 +1,39 @@ +[gd_scene load_steps=4 format=3 uid="uid://bqfiujl4mo4sr"] + +[ext_resource type="Script" path="res://player/player.gd" id="1_iccmg"] +[ext_resource type="Texture2D" uid="uid://pqcr6hxnafee" path="res://assets/kai.png" id="2_brm00"] + +[sub_resource type="CircleShape2D" id="CircleShape2D_ohava"] +radius = 5.09902 + +[node name="Player" type="CharacterBody2D"] +script = ExtResource("1_iccmg") + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture_filter = 1 +texture = ExtResource("2_brm00") +region_enabled = true +region_rect = Rect2(0, 0, 16, 16) +metadata/_aseprite_wizard_config_ = { +"i_mode": 1, +"keep_anim_length": false, +"layer": "", +"o_ex_p": "", +"o_folder": "res://assets", +"o_name": "", +"only_visible": false, +"player": "", +"slice": "", +"source": "res://assets/aseprite/kai.aseprite" +} +metadata/_aseprite_wizard_interface_config_ = { +"layer_section": true, +"output_section": true, +"slice_section": true +} + +[node name="Camera2D" type="Camera2D" parent="."] +zoom = Vector2(7, 7) + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("CircleShape2D_ohava") diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..e773480 --- /dev/null +++ b/project.godot @@ -0,0 +1,81 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Lumina Deflect" +run/main_scene="res://world.tscn" +config/features=PackedStringArray("4.2", "GL Compatibility") +config/icon="res://icon.svg" + +[editor_plugins] + +enabled=PackedStringArray("res://addons/AsepriteWizard/plugin.cfg") + +[input] + +ui_left={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null) +] +} +ui_right={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":14,"pressure":0.0,"pressed":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null) +] +} +ui_up={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":11,"pressure":0.0,"pressed":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null) +] +} +ui_down={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":12,"pressure":0.0,"pressed":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null) +] +} +look_left={ +"deadzone": 0.5, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":-1.0,"script":null) +] +} +look_right={ +"deadzone": 0.5, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":1.0,"script":null) +] +} +look_up={ +"deadzone": 0.5, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":-1.0,"script":null) +] +} +look_down={ +"deadzone": 0.5, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":1.0,"script":null) +] +} +primary_fire={ +"deadzone": 0.5, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":5,"axis_value":1.0,"script":null) +] +} + +[rendering] + +renderer/rendering_method="gl_compatibility" +renderer/rendering_method.mobile="gl_compatibility" diff --git a/scripts/bullet.gd b/scripts/bullet.gd new file mode 100644 index 0000000..1518c43 --- /dev/null +++ b/scripts/bullet.gd @@ -0,0 +1,8 @@ +extends RigidBody2D + +@export var initial_velocity = 800 +@export var direction: float = 0 + +func _ready() -> void: + rotation = direction + linear_velocity = Vector2.from_angle(direction) * initial_velocity diff --git a/world.tscn b/world.tscn new file mode 100644 index 0000000..3445e40 --- /dev/null +++ b/world.tscn @@ -0,0 +1,68 @@ +[gd_scene load_steps=5 format=3 uid="uid://bjk2hwate6lee"] + +[ext_resource type="PackedScene" uid="uid://bqfiujl4mo4sr" path="res://player/player.tscn" id="1_xsiva"] +[ext_resource type="Texture2D" uid="uid://0d1migp7yxn4" path="res://assets/luminaCity.png" id="2_pdc8h"] + +[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_2ik3n"] +texture = ExtResource("2_pdc8h") +0:1/0 = 0 +0:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) +0:1/0/physics_layer_0/angular_velocity = 0.0 +1:1/0 = 0 +1:1/0/physics_layer_0/linear_velocity = Vector2(0, 0) +1:1/0/physics_layer_0/angular_velocity = 0.0 +6:2/0 = 0 +6:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) +6:2/0/physics_layer_0/angular_velocity = 0.0 +7:2/0 = 0 +7:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) +7:2/0/physics_layer_0/angular_velocity = 0.0 +6:3/0 = 0 +6:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) +6:3/0/physics_layer_0/angular_velocity = 0.0 +7:3/0 = 0 +7:3/0/physics_layer_0/linear_velocity = Vector2(0, 0) +7:3/0/physics_layer_0/angular_velocity = 0.0 +2:4/0 = 0 +2:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) +2:4/0/physics_layer_0/angular_velocity = 0.0 +3:4/0 = 0 +3:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) +3:4/0/physics_layer_0/angular_velocity = 0.0 +6:4/0 = 0 +6:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) +6:4/0/physics_layer_0/angular_velocity = 0.0 +7:4/0 = 0 +7:4/0/physics_layer_0/linear_velocity = Vector2(0, 0) +7:4/0/physics_layer_0/angular_velocity = 0.0 +2:5/0 = 0 +2:5/0/physics_layer_0/linear_velocity = Vector2(0, 0) +2:5/0/physics_layer_0/angular_velocity = 0.0 +3:5/0 = 0 +3:5/0/physics_layer_0/linear_velocity = Vector2(0, 0) +3:5/0/physics_layer_0/angular_velocity = 0.0 +6:5/0 = 0 +6:5/0/physics_layer_0/linear_velocity = Vector2(0, 0) +6:5/0/physics_layer_0/angular_velocity = 0.0 +7:5/0 = 0 +7:5/0/physics_layer_0/linear_velocity = Vector2(0, 0) +7:5/0/physics_layer_0/angular_velocity = 0.0 +2:2/size_in_atlas = Vector2i(2, 2) +2:2/0 = 0 +2:2/0/physics_layer_0/linear_velocity = Vector2(0, 0) +2:2/0/physics_layer_0/angular_velocity = 0.0 +2:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) + +[sub_resource type="TileSet" id="TileSet_jepr2"] +physics_layer_0/collision_layer = 3 +physics_layer_0/collision_mask = 3 +sources/1 = SubResource("TileSetAtlasSource_2ik3n") + +[node name="World" type="Node2D"] + +[node name="Player" parent="." instance=ExtResource("1_xsiva")] + +[node name="TileMap" type="TileMap" parent="."] +tile_set = SubResource("TileSet_jepr2") +format = 2 +layer_0/tile_data = PackedInt32Array(-458749, 131073, 2, -393213, 131073, 2, -327677, 131073, 2, -262141, 131073, 2, -196605, 131073, 2, -131069, 131073, 2, -393216, 131073, 2, -393217, 131073, 2, -393218, 131073, 2, -393219, 131073, 2, -393220, 131073, 2, -393221, 131073, 2, -393222, 131073, 2, -131081, 131073, 2, -65545, 131073, 2, -9, 131073, 2, 65527, 131073, 2, 131063, 131073, 2, 196599, 131073, 2, 327675, 131073, 2, 327676, 131073, 2, 327677, 131073, 2, 327678, 131073, 2, 327679, 131073, 2, 262144, 131073, 2, 262145, 131073, 2, 262146, 131073, 2, 262147, 131073, 2, 262148, 131073, 2, 262149, 131073, 2, 262150, 131073, 2, 262151, 131073, 2, 262152, 131073, 2, 262153, 131073, 2, 262154, 131073, 2, 262155, 131073, 2, 262156, 131073, 2, 262157, 131073, 2, -393205, 131073, 2, -327669, 131073, 2, -262133, 131073, 2, -196597, 131073, 2, -131061, 131073, 2, -65524, 131073, 2, 12, 131073, 2)