From 277fb03a69e46837bdfe6d9f7116066da95994d8 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 28 May 2024 11:03:13 -0500 Subject: [PATCH] init --- .aseprite_wizard_history | 1 + .gitattributes | 2 + .gitignore | 2 + addons/AsepriteWizard/aseprite/aseprite.gd | 283 ++++++++++++ .../AsepriteWizard/aseprite/file_exporter.gd | 146 +++++++ addons/AsepriteWizard/config/config.gd | 295 +++++++++++++ addons/AsepriteWizard/config/config_dialog.gd | 33 ++ .../AsepriteWizard/config/config_dialog.tscn | 80 ++++ addons/AsepriteWizard/config/result_codes.gd | 37 ++ addons/AsepriteWizard/config/wizard_config.gd | 82 ++++ .../animation_player/animation_creator.gd | 306 +++++++++++++ .../sprite_animation_creator.gd | 48 ++ .../texture_rect_animation_creator.gd | 32 ++ .../creators/base_sprite_resource_creator.gd | 16 + .../sprite_frames/sprite_frames_creator.gd | 242 ++++++++++ .../static_texture/texture_creator.gd | 26 ++ .../export/metadata_export_plugin.gd | 60 +++ .../importers/noop_import_plugin.gd | 58 +++ .../importers/sprite_frames_import_plugin.gd | 153 +++++++ .../importers/static_texture_import_plugin.gd | 138 ++++++ .../tileset_texture_import_plugin.gd | 137 ++++++ .../animated_sprite_inspector_dock.gd | 45 ++ .../animated_sprite_inspector_dock.tscn | 12 + .../docks/animated_sprite/inspector_plugin.gd | 18 + .../interface/docks/base_inspector_dock.gd | 413 ++++++++++++++++++ .../interface/docks/base_inspector_dock.tscn | 345 +++++++++++++++ .../interface/docks/dock_fields.tscn | 320 ++++++++++++++ .../docks/sprite/inspector_plugin.gd | 18 + .../docks/sprite/sprite_inspector_dock.gd | 257 +++++++++++ .../docks/sprite/sprite_inspector_dock.tscn | 12 + .../docks/wizard/as_wizard_dock_container.gd | 37 ++ .../wizard/as_wizard_dock_container.tscn | 26 ++ .../docks/wizard/as_wizard_window.gd | 222 ++++++++++ .../docks/wizard/as_wizard_window.tscn | 139 ++++++ .../wizard/sprite_frames_import_history.gd | 226 ++++++++++ .../wizard/sprite_frames_import_history.tscn | 81 ++++ .../docks/wizard/wizard_nodes/import_date.gd | 12 + .../wizard/wizard_nodes/import_date.tscn | 11 + .../docks/wizard/wizard_nodes/list_actions.gd | 20 + .../wizard/wizard_nodes/list_actions.tscn | 35 ++ .../wizard/wizard_nodes/output_path.tscn | 12 + .../docks/wizard/wizard_nodes/source_path.gd | 22 + .../wizard/wizard_nodes/source_path.tscn | 18 + .../shared/animation_player_drop_button.gd | 15 + .../shared/animation_player_drop_button.tscn | 12 + .../interface/shared/dir_drop_button.gd | 14 + .../interface/shared/dir_drop_button.tscn | 10 + .../interface/shared/source_drop_button.gd | 15 + .../interface/shared/source_drop_button.tscn | 10 + addons/AsepriteWizard/plugin.cfg | 7 + addons/AsepriteWizard/plugin.gd | 156 +++++++ assets/aseprite/bullet.aseprite | Bin 0 -> 504 bytes assets/aseprite/bullet.aseprite.import | 14 + assets/aseprite/kai.aseprite | Bin 0 -> 540 bytes assets/aseprite/kai.aseprite.import | 14 + assets/aseprite/luminaCity.aseprite | Bin 0 -> 1396 bytes assets/aseprite/luminaCity.aseprite.import | 14 + assets/kai.png | Bin 0 -> 241 bytes assets/kai.png.import | 34 ++ assets/luminaCity.png | Bin 0 -> 1227 bytes assets/luminaCity.png.import | 34 ++ assets/luminaDeflect.png | Bin 0 -> 435 bytes assets/luminaDeflect.png.import | 34 ++ bullet.png | Bin 0 -> 159 bytes bullet.png.import | 34 ++ bullet.tscn | 39 ++ icon.svg | 1 + icon.svg.import | 37 ++ luminaDeflect.aseprite | Bin 0 -> 693 bytes luminaDeflect.aseprite.import | 16 + player/player.gd | 22 + player/player.tscn | 39 ++ project.godot | 81 ++++ scripts/bullet.gd | 8 + world.tscn | 68 +++ 75 files changed, 5206 insertions(+) create mode 100644 .aseprite_wizard_history create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 addons/AsepriteWizard/aseprite/aseprite.gd create mode 100644 addons/AsepriteWizard/aseprite/file_exporter.gd create mode 100644 addons/AsepriteWizard/config/config.gd create mode 100644 addons/AsepriteWizard/config/config_dialog.gd create mode 100644 addons/AsepriteWizard/config/config_dialog.tscn create mode 100644 addons/AsepriteWizard/config/result_codes.gd create mode 100644 addons/AsepriteWizard/config/wizard_config.gd create mode 100644 addons/AsepriteWizard/creators/animation_player/animation_creator.gd create mode 100644 addons/AsepriteWizard/creators/animation_player/sprite_animation_creator.gd create mode 100644 addons/AsepriteWizard/creators/animation_player/texture_rect_animation_creator.gd create mode 100644 addons/AsepriteWizard/creators/base_sprite_resource_creator.gd create mode 100644 addons/AsepriteWizard/creators/sprite_frames/sprite_frames_creator.gd create mode 100644 addons/AsepriteWizard/creators/static_texture/texture_creator.gd create mode 100644 addons/AsepriteWizard/export/metadata_export_plugin.gd create mode 100644 addons/AsepriteWizard/importers/noop_import_plugin.gd create mode 100644 addons/AsepriteWizard/importers/sprite_frames_import_plugin.gd create mode 100644 addons/AsepriteWizard/importers/static_texture_import_plugin.gd create mode 100644 addons/AsepriteWizard/importers/tileset_texture_import_plugin.gd create mode 100644 addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.gd create mode 100644 addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.tscn create mode 100644 addons/AsepriteWizard/interface/docks/animated_sprite/inspector_plugin.gd create mode 100644 addons/AsepriteWizard/interface/docks/base_inspector_dock.gd create mode 100644 addons/AsepriteWizard/interface/docks/base_inspector_dock.tscn create mode 100644 addons/AsepriteWizard/interface/docks/dock_fields.tscn create mode 100644 addons/AsepriteWizard/interface/docks/sprite/inspector_plugin.gd create mode 100644 addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.gd create mode 100644 addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.tscn create mode 100644 addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.gd create mode 100644 addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.tscn create mode 100644 addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.gd create mode 100644 addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.tscn create mode 100644 addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.gd create mode 100644 addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.tscn create mode 100644 addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.gd create mode 100644 addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.tscn create mode 100644 addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/list_actions.gd create mode 100644 addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/list_actions.tscn create mode 100644 addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/output_path.tscn create mode 100644 addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/source_path.gd create mode 100644 addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/source_path.tscn create mode 100644 addons/AsepriteWizard/interface/shared/animation_player_drop_button.gd create mode 100644 addons/AsepriteWizard/interface/shared/animation_player_drop_button.tscn create mode 100644 addons/AsepriteWizard/interface/shared/dir_drop_button.gd create mode 100644 addons/AsepriteWizard/interface/shared/dir_drop_button.tscn create mode 100644 addons/AsepriteWizard/interface/shared/source_drop_button.gd create mode 100644 addons/AsepriteWizard/interface/shared/source_drop_button.tscn create mode 100644 addons/AsepriteWizard/plugin.cfg create mode 100644 addons/AsepriteWizard/plugin.gd create mode 100644 assets/aseprite/bullet.aseprite create mode 100644 assets/aseprite/bullet.aseprite.import create mode 100644 assets/aseprite/kai.aseprite create mode 100644 assets/aseprite/kai.aseprite.import create mode 100644 assets/aseprite/luminaCity.aseprite create mode 100644 assets/aseprite/luminaCity.aseprite.import create mode 100644 assets/kai.png create mode 100644 assets/kai.png.import create mode 100644 assets/luminaCity.png create mode 100644 assets/luminaCity.png.import create mode 100644 assets/luminaDeflect.png create mode 100644 assets/luminaDeflect.png.import create mode 100644 bullet.png create mode 100644 bullet.png.import create mode 100644 bullet.tscn create mode 100644 icon.svg create mode 100644 icon.svg.import create mode 100644 luminaDeflect.aseprite create mode 100644 luminaDeflect.aseprite.import create mode 100644 player/player.gd create mode 100644 player/player.tscn create mode 100644 project.godot create mode 100644 scripts/bullet.gd create mode 100644 world.tscn 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 0000000000000000000000000000000000000000..3b55828103e8bea3fce0dda96ad97bce86e932fe GIT binary patch literal 504 zcmeyt$iVPmDIlU)#=r;?1j-5^1gXfX0Gj^mBP-ZeR-n0JK+LWH zwi3yfc|f+L0!R)7L?INEY`yjRKLbPj$>$()A(+eqkv<8lfTW9i36L~0v<8xHHgP~Q z*1Zf!wgyiHlB*+D0LfDc>wx6v#gNLviHUmAQ^jb0+96Dxfn>gPF@NmEd1jB#jKMfux&F9FUB4 zF9VXT!Bc_c>WCFU@>Ie)Ao+QDJCOW(YdMhYy)gwy#vYsiB)xVn29mCmmjcPSzu$r6 z|JXA?^6$S+5E}@5fG%MHg`F7CEkK6=T@j!69B4n#a-hvXYk_tGEd<&IvvBGKZ51G&)HHVK85TN*T+b3XIMH1BwM_d=$F?ZGkGKb(7)Z z3&(hEC`GLe6=*@B(*mV&h)j9h@M=fBfI!-!tZlHgH?`F6t8SV9z`mRFJ16Iye82B} zH=lqYkHssH?~nrs5m|vC$RQa2g*URoy@|X7-~SOyD-q=RBH^VKN= zA0ChaEe$^ct=b0Qg+D8S^t(R+;}jxbOobSj>BNC9`T)@R;*5I@kpPvfbob61YJoza zimV(1_(2Y4(1H_$U;`C+KmrCx54j<+J1c#_ozLa+cjqF$`PvIXf_NYa-<&wiCWiIG z34w@!>|Mn5#=o}&NFs9lvhh;%G2gJaPjkfim$p9AuN~a-S<9ClruA`NzZ5vmW`%yR zX+n7OkkcogxVq@xa0^=AH#^uski$7G4a8XPv%RpKCf zWBjy(>@nT~fmzF@T)ioLE+JAq_wS33v#4V=q!hl4D+?FXwr;Oz9OdMZ3gBe*U?N}LQ zZcNasCO>4wcCIr`h!sgJrZH&yS36>6g*Y!yGbfy5U=87dYyHn4J zSSWUMcItUoC1=Mi^;AFgVY#C#fIB3Ww~U+_sH@#ZfSZSGSi$ScY4=Lp78~iO#{PgWU9m9&2<1*lZ9R z3#mG?NFfUj#IMJ&V`naOs+(6y(IlmyRK&>AF|Z`E4vT$u9=}JoeEYG6GAv|oG3-2C zMpC&##J<0hZtoK7cS$S}Msu=h$hRP8w02?-V@Yc$>D+*`bC^wR+mNT5(-$%B8L$j^}~`}WrI($_JKm^~!Cp<@l+CjWHzbcOV| z%o5t58{$5QxDraeAE}9BSeN%V?DIr1$`rvry`P59yXGZFaA1lY;;N literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1152707a38397cc9799cfeaced0adf00ff22cbe1 GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|R(rZQhFJ6_ zCrEs15R5%=bng@UE+%%%GNg3TCz+wvgKhc8xW|)upKZJW~ks*V0&Qt;OzC?Pox!= z&gVCOBEMbY@c$de|NkGK_p^T;pK8Jex5vd-GVc5d?`$mG$f+F0Jb|~7kPx(g-Jv~RCt{2oiT6IP!xqvqE4_iYQ(=l?G|-l0Y#O%FfnwA7%OE%{D_LJ5@W@} zz?224ips(ebxXt#U`HwKRz}JYoH{SI6UXoR9mnTeN}sWPec$zc&&SWVfDl3mA%qY@ z2qAcCOQKl}Ov0B~z{B2fsF z065q`e26+;aMKKJfC$SDFuP;(?x^v6JJdd0;gI){6%e}din278e^jeAAefhwJ zG63$T*CxkZ*C`>fvN6r4R2~^#?&16UuWZ94BUk7 z(M@8i^6do1z~~;`q|QxzUcbAIDga!fV2Ea}MPcmtIKJ&$XD&q(09nRbO$r$(``X;| z(aixOcH13qcYd8&6j`5BxwM3kb!}0`+Um4?l!6bu)Jg0W#F>xbA3$Zm9!Tj!XK`WsRBSUa5g@C z;QE+q5}0Ig9c17*);_3|6zr2Kc6=F74FTIM`Lf{pm?i_bhFJy!tHfN^4G=#MO#qY$ zQ>F=R3snF_3m2n*TM*jetFXTAxQ_v%0N8DJmZV?{8F<;j)p!}?%NH{#xWoA}$bClj zy%LsL4qj!OFJIJF3I@34U`fFlNCkYvhc77S07+PNJad5fy5@DSktzVJXXsVzcnjiB z0yF`TxAU9#)6{K$pSFvdLy3=Tj(;Br5Cwos^L~oo_dhq=IblCm^)HRw*>Fu?(dq!e zqH!B{ehRDtP-weo0w75U@^#mSc5)spGF8$_Yq~iA@c&u(Y`MLt3YP6uZY9u%0_djw zI8*_UHWT)_FqCn{aF$(GbpawOAq4q?RSC4VD{Ba(@(80wzirUh2gst%l5j1Qz_Mge z*1!m{D3&S!TT{ z|IKP=UJweXJ^%pK2PgyTMWLz>V2|npR2bCI1mP3+?b#xOQ6jfieTAK0r3A4^W^zf>{n$eE_IFKpD_V z!C7OoRKQ2JvOd6gGHM*`A1?g`P1OhR-ySiZj2Zw*07Uqnp}WVcV#m9ma~FkH2+B*I zZ{=BqpDdET=d-AZKobD&Fpzbzt8n*?evh0~Z(J3002ovPDHLkV1gd2G6ett literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7bc7f00404dd4a37fec78bda6dc34aba52d5c410 GIT binary patch literal 435 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D%zb3I)gLn`LH zy|K~ju!98qhx%DM=ky+Vx1{tYCr|&uqADxX`XK0oB+K@9e63CV4;Gbf;Sds$cL;YD zG{3lXo^xlyohDqnad)eu4&WDKVFhtzH>wNL+EEY2X-z6+dgSZ)v1zz`H z+Wjg*>*Gzn{>6D4uYLaU-@Z2C;r|A!H7^%Q?dLXN3}p{6UCHUeB<*`QwgU}p`N-(E XDdFneTm9dGvB2Qz>gTe~DWM4fe=EgN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c86fa563b20876edebe91ef0b3643952aa8ea63d GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^20$#p!3HD)*8SNCq!^2X+?^QKos)S9#(0r|;kTs(v*7fZK0r$tJYD@<);T3K F0RaAoHWvT@ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5de9f0957221b46c101c40af04ac1d20189d6afe GIT binary patch literal 693 zcmdnW#K7=iDI$qP*Ae<*6aTa4DlzQgUE$oG7m)hB&-6GF772j(#X&nNV?g?0m)eR zG9cL+JQYZ;j#vRCPbI7alAo8i1Ie$qmIKM&8&iN}?7;~@(rf2pAn7`JDUgi&`yELB zk39n<|NiR)v4Ow`=n@uC*ogt%0(1z_74dn`f%XF}2igp@7HB8XLZEFxtAO?ZEdkm9 zG#+R+&|r|Mv1dT${r@iyGE0FO$%jBo7}yzn5-U@S6b#vbwy-KN04-x?_zw&^2(Vy~ zVW^n%_J$*0lYs#1g*eacyFzYOuy(W?+&j26z2I@=o__5$KWELq(x$|_RJOFvNO8-; zx6KX=$#IdP)!DCCn>McTPw(dX>djws`|i@q9OA!{YJ97OOZAq0IGf&`zlN1-Oo&p(3PcGCQ`lfb;VQa`j+vi4-z_WkKJUFAQm|Fzf0o{6u#MZeaT|2ug6yFcguFY>eJTIT`)LtE`| literal 0 HcmV?d00001 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)