From 6c3be182aa38bd106ac71d85531aa31188fdb203 Mon Sep 17 00:00:00 2001 From: Jakob Feldmann Date: Mon, 13 Jun 2022 20:42:24 +0200 Subject: [PATCH] Ignoring Addons Folder --- .gitignore | 1 + .../animated_sprite/ASWizardWindow.tscn | 239 ++++++++++++++ .../animated_sprite_inspector_dock.gd | 206 ++++++++++++ .../animated_sprite_inspector_dock.tscn | 227 +++++++++++++ .../animated_sprite/import_plugin.gd | 233 +++++++++++++ .../animated_sprite/inspector_plugin.gd | 25 ++ .../animated_sprite/sf_wizard_dock.gd | 178 ++++++++++ .../animated_sprite/sprite_frames_creator.gd | 310 ++++++++++++++++++ .../animation_player/animation_creator.gd | 204 ++++++++++++ .../animation_player/inspector_plugin.gd | 25 ++ .../animation_player/sprite_inspector_dock.gd | 256 +++++++++++++++ .../sprite_inspector_dock.tscn | 252 ++++++++++++++ addons/AsepriteWizard/aseprite/aseprite.gd | 183 +++++++++++ addons/AsepriteWizard/config/config.gd | 175 ++++++++++ addons/AsepriteWizard/config/config_dialog.gd | 61 ++++ .../AsepriteWizard/config/config_dialog.tscn | 176 ++++++++++ addons/AsepriteWizard/config/result_codes.gd | 29 ++ addons/AsepriteWizard/config/wizard_config.gd | 38 +++ addons/AsepriteWizard/plugin.cfg | 7 + addons/AsepriteWizard/plugin.gd | 129 ++++++++ src/Levels/Plattforms Level.tscn | 9 +- 21 files changed, 2959 insertions(+), 4 deletions(-) create mode 100644 addons/AsepriteWizard/animated_sprite/ASWizardWindow.tscn create mode 100644 addons/AsepriteWizard/animated_sprite/animated_sprite_inspector_dock.gd create mode 100644 addons/AsepriteWizard/animated_sprite/animated_sprite_inspector_dock.tscn create mode 100644 addons/AsepriteWizard/animated_sprite/import_plugin.gd create mode 100644 addons/AsepriteWizard/animated_sprite/inspector_plugin.gd create mode 100644 addons/AsepriteWizard/animated_sprite/sf_wizard_dock.gd create mode 100644 addons/AsepriteWizard/animated_sprite/sprite_frames_creator.gd create mode 100644 addons/AsepriteWizard/animation_player/animation_creator.gd create mode 100644 addons/AsepriteWizard/animation_player/inspector_plugin.gd create mode 100644 addons/AsepriteWizard/animation_player/sprite_inspector_dock.gd create mode 100644 addons/AsepriteWizard/animation_player/sprite_inspector_dock.tscn create mode 100644 addons/AsepriteWizard/aseprite/aseprite.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/plugin.cfg create mode 100644 addons/AsepriteWizard/plugin.gd diff --git a/.gitignore b/.gitignore index ae25925..b0a2daf 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ Thumbs.db *.lnk # Godot-specific ignores .import/ +.addons/ export.cfg export_presets.cfg diff --git a/addons/AsepriteWizard/animated_sprite/ASWizardWindow.tscn b/addons/AsepriteWizard/animated_sprite/ASWizardWindow.tscn new file mode 100644 index 0000000..32ddcf6 --- /dev/null +++ b/addons/AsepriteWizard/animated_sprite/ASWizardWindow.tscn @@ -0,0 +1,239 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/AsepriteWizard/animated_sprite/sf_wizard_dock.gd" type="Script" id=1] + +[node name="ASWizardWindow" type="PanelContainer"] +margin_right = 600.0 +margin_bottom = 600.0 +rect_min_size = Vector2( 600, 600 ) +size_flags_horizontal = 3 +size_flags_vertical = 0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="container" type="MarginContainer" parent="."] +margin_left = 7.0 +margin_top = 7.0 +margin_right = 593.0 +margin_bottom = 593.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +custom_constants/margin_right = 30 +custom_constants/margin_top = 30 +custom_constants/margin_left = 30 +custom_constants/margin_bottom = 30 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="options" type="VBoxContainer" parent="container"] +margin_left = 30.0 +margin_top = 30.0 +margin_right = 556.0 +margin_bottom = 556.0 +custom_constants/separation = 20 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="file_location" type="VBoxContainer" parent="container/options"] +margin_right = 526.0 +margin_bottom = 48.0 +custom_constants/separation = 10 + +[node name="file_location_label" type="Label" parent="container/options/file_location"] +margin_right = 526.0 +margin_bottom = 14.0 +hint_tooltip = "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"] +margin_top = 24.0 +margin_right = 526.0 +margin_bottom = 48.0 +custom_constants/separation = 20 + +[node name="file_location_path" type="LineEdit" parent="container/options/file_location/HBoxContainer"] +margin_right = 431.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +caret_blink = true + +[node name="file_location_btn" type="Button" parent="container/options/file_location/HBoxContainer"] +margin_left = 451.0 +margin_right = 526.0 +margin_bottom = 24.0 +text = "Select file" + +[node name="output_folder" type="VBoxContainer" parent="container/options"] +margin_top = 68.0 +margin_right = 526.0 +margin_bottom = 116.0 +custom_constants/separation = 10 + +[node name="label" type="Label" parent="container/options/output_folder"] +margin_right = 526.0 +margin_bottom = 14.0 +hint_tooltip = "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"] +margin_top = 24.0 +margin_right = 526.0 +margin_bottom = 48.0 +custom_constants/separation = 20 + +[node name="file_location_path" type="LineEdit" parent="container/options/output_folder/HBoxContainer"] +margin_right = 431.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +text = "res://" +caret_blink = true + +[node name="output_folder_btn" type="Button" parent="container/options/output_folder/HBoxContainer"] +margin_left = 451.0 +margin_right = 526.0 +margin_bottom = 24.0 +text = "Select file" + +[node name="exclude_pattern" type="VBoxContainer" parent="container/options"] +margin_top = 136.0 +margin_right = 526.0 +margin_bottom = 184.0 +custom_constants/separation = 10 + +[node name="label" type="Label" parent="container/options/exclude_pattern"] +margin_right = 526.0 +margin_bottom = 14.0 +hint_tooltip = "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"] +margin_top = 24.0 +margin_right = 526.0 +margin_bottom = 48.0 +caret_blink = true + +[node name="custom_filename" type="VBoxContainer" parent="container/options"] +margin_top = 204.0 +margin_right = 526.0 +margin_bottom = 252.0 +custom_constants/separation = 10 + +[node name="label" type="Label" parent="container/options/custom_filename"] +margin_right = 526.0 +margin_bottom = 14.0 +hint_tooltip = "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"] +margin_top = 24.0 +margin_right = 526.0 +margin_bottom = 48.0 +caret_blink = true + +[node name="layer_importing_mode" type="VBoxContainer" parent="container/options"] +margin_top = 272.0 +margin_right = 526.0 +margin_bottom = 388.0 +custom_constants/separation = 10 + +[node name="label" type="Label" parent="container/options/layer_importing_mode"] +margin_right = 526.0 +margin_bottom = 14.0 +mouse_filter = 1 +text = "Options:" + +[node name="split_layers" type="HBoxContainer" parent="container/options/layer_importing_mode"] +margin_top = 24.0 +margin_right = 526.0 +margin_bottom = 48.0 +hint_tooltip = "If selected, one resource will be created for each layer. +If not selected, layers will be merged and exported as one SpriteSheet." + +[node name="label" type="Label" parent="container/options/layer_importing_mode/split_layers"] +margin_top = 5.0 +margin_right = 250.0 +margin_bottom = 19.0 +rect_min_size = Vector2( 250, 0 ) +mouse_filter = 1 +text = "Split layers in multiple resources" + +[node name="field" type="CheckBox" parent="container/options/layer_importing_mode/split_layers"] +margin_left = 254.0 +margin_right = 301.0 +margin_bottom = 24.0 +text = "On" + +[node name="visible_layers" type="HBoxContainer" parent="container/options/layer_importing_mode"] +margin_top = 58.0 +margin_right = 526.0 +margin_bottom = 82.0 +hint_tooltip = "If selected, layers not visible in the source file won't be included in the final image." + +[node name="label" type="Label" parent="container/options/layer_importing_mode/visible_layers"] +margin_top = 5.0 +margin_right = 250.0 +margin_bottom = 19.0 +rect_min_size = Vector2( 250, 0 ) +text = "Only include visible layers" + +[node name="field" type="CheckBox" parent="container/options/layer_importing_mode/visible_layers"] +margin_left = 254.0 +margin_right = 301.0 +margin_bottom = 24.0 +text = "On" + +[node name="disable_resource_creation" type="HBoxContainer" parent="container/options/layer_importing_mode"] +margin_top = 92.0 +margin_right = 526.0 +margin_bottom = 116.0 +hint_tooltip = "Does not create SpriteFrames resource. Useful if you are only interested in the .json and .png output from Aseprite." + +[node name="label" type="Label" parent="container/options/layer_importing_mode/disable_resource_creation"] +margin_top = 5.0 +margin_right = 250.0 +margin_bottom = 19.0 +rect_min_size = Vector2( 250, 0 ) +text = "Do not create resource file" + +[node name="field" type="CheckBox" parent="container/options/layer_importing_mode/disable_resource_creation"] +margin_left = 254.0 +margin_right = 301.0 +margin_bottom = 24.0 +text = "On" +__meta__ = { +"_editor_description_": "Only source *.json and *.png files will be created." +} + +[node name="buttons" type="HBoxContainer" parent="container/options"] +margin_top = 408.0 +margin_right = 526.0 +margin_bottom = 428.0 +custom_constants/separation = 30 +alignment = 2 + +[node name="close" type="Button" parent="container/options/buttons"] +margin_left = 393.0 +margin_right = 440.0 +margin_bottom = 20.0 +text = "Close" + +[node name="next" type="Button" parent="container/options/buttons"] +margin_left = 470.0 +margin_right = 526.0 +margin_bottom = 20.0 +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/animated_sprite/animated_sprite_inspector_dock.gd b/addons/AsepriteWizard/animated_sprite/animated_sprite_inspector_dock.gd new file mode 100644 index 0000000..7342454 --- /dev/null +++ b/addons/AsepriteWizard/animated_sprite/animated_sprite_inspector_dock.gd @@ -0,0 +1,206 @@ +tool +extends PanelContainer + +const wizard_config = preload("../config/wizard_config.gd") +const result_code = preload("../config/result_codes.gd") +var sprite_frames_creator = preload("sprite_frames_creator.gd").new() + +var scene: Node +var sprite: AnimatedSprite + +var config +var file_system: EditorFileSystem + +var _layer: String = "" +var _source: String = "" +var _file_dialog_aseprite: FileDialog +var _output_folder_dialog: FileDialog +var _importing := false + +var _output_folder := "" +var _out_folder_default := "[Same as scene]" +var _layer_default := "[all]" + +onready var _source_field = $margin/VBoxContainer/source/button +onready var _layer_field = $margin/VBoxContainer/layer/options +onready var _options_title = $margin/VBoxContainer/options_title/options_title +onready var _options_container = $margin/VBoxContainer/options +onready var _out_folder_field = $margin/VBoxContainer/options/out_folder/button +onready var _out_filename_field = $margin/VBoxContainer/options/out_filename/LineEdit +onready var _visible_layers_field = $margin/VBoxContainer/options/visible_layers/CheckButton +onready var _ex_pattern_field = $margin/VBoxContainer/options/ex_pattern/LineEdit + +func _ready(): + var cfg = wizard_config.decode(sprite.editor_description) + + if cfg == null: + _load_default_config() + else: + _load_config(cfg) + + sprite_frames_creator.init(config, file_system) + + +func _load_config(cfg): + if cfg.has("source"): + _set_source(cfg.source) + + if cfg.get("layer", "") != "": + _set_layer(cfg.layer) + + _output_folder = cfg.get("o_folder", "") + _out_folder_field.text = _output_folder if _output_folder != "" else _out_folder_default + _out_filename_field.text = cfg.get("o_name", "") + _visible_layers_field.pressed = cfg.get("only_visible", "") == "True" + _ex_pattern_field.text = cfg.get("o_ex_p", "") + + _set_options_visible(cfg.get("op_exp", "false") == "True") + + +func _load_default_config(): + _ex_pattern_field.text = config.get_default_exclusion_pattern() + _set_options_visible(false) + + +func _set_source(source): + _source = source + _source_field.text = _source + _source_field.hint_tooltip = _source + + +func _set_layer(layer): + _layer = layer + _layer_field.add_item(_layer) + + +func _on_layer_pressed(): + if _source == "": + _show_message("Please. Select source file first.") + return + + var layers = sprite_frames_creator.list_layers(ProjectSettings.globalize_path(_source)) + var current = 0 + _layer_field.clear() + _layer_field.add_item("[all]") + + for l in layers: + if l == "": + continue + + _layer_field.add_item(l) + if l == _layer: + current = _layer_field.get_item_count() - 1 + _layer_field.select(current) + + +func _on_layer_item_selected(index): + if index == 0: + _layer = "" + return + _layer = _layer_field.get_item_text(index) + _save_config() + + +func _on_source_pressed(): + _open_source_dialog() + + +func _on_import_pressed(): + if _importing: + return + _importing = true + + var root = get_tree().get_edited_scene_root() + + if _source == "": + _show_message("Aseprite file not selected.") + _importing = false + return + + var options = { + "source": ProjectSettings.globalize_path(_source), + "output_folder": _output_folder if _output_folder != "" else root.filename.get_base_dir(), + "exception_pattern": _ex_pattern_field.text, + "only_visible_layers": _visible_layers_field.pressed, + "output_filename": _out_filename_field.text, + "layer": _layer + } + + _save_config() + + sprite_frames_creator.create_animations(sprite, options) + _importing = false + + +func _save_config(): + sprite.editor_description = wizard_config.encode({ + "source": _source, + "layer": _layer, + "op_exp": _options_title.pressed, + "o_folder": _output_folder, + "o_name": _out_filename_field.text, + "only_visible": _visible_layers_field.pressed, + "o_ex_p": _ex_pattern_field.text + }) + + +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 = _source.get_base_dir() + _file_dialog_aseprite.popup_centered_ratio() + + +func _create_aseprite_file_selection(): + var file_dialog = FileDialog.new() + file_dialog.mode = FileDialog.MODE_OPEN_FILE + file_dialog.access = FileDialog.ACCESS_FILESYSTEM + file_dialog.connect("file_selected", self, "_on_aseprite_file_selected") + file_dialog.set_filters(PoolStringArray(["*.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 _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", _warning_dialog, "queue_free") + + +func _on_options_title_toggled(button_pressed): + _set_options_visible(button_pressed) + _save_config() + + +func _set_options_visible(is_visible): + _options_container.visible = is_visible + _options_title.icon = config.get_icon_arrow_down() if is_visible else config.get_icon_arrow_right() + +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 = FileDialog.new() + file_dialog.mode = FileDialog.MODE_OPEN_DIR + file_dialog.access = FileDialog.ACCESS_RESOURCES + file_dialog.connect("dir_selected", self, "_on_output_folder_selected") + return file_dialog + + +func _on_output_folder_selected(path): + _output_folder = path + _out_folder_field.text = _output_folder if _output_folder != "" else _out_folder_default + _output_folder_dialog.queue_free() diff --git a/addons/AsepriteWizard/animated_sprite/animated_sprite_inspector_dock.tscn b/addons/AsepriteWizard/animated_sprite/animated_sprite_inspector_dock.tscn new file mode 100644 index 0000000..186d410 --- /dev/null +++ b/addons/AsepriteWizard/animated_sprite/animated_sprite_inspector_dock.tscn @@ -0,0 +1,227 @@ +[gd_scene load_steps=5 format=2] + +[ext_resource path="res://addons/AsepriteWizard/animated_sprite/animated_sprite_inspector_dock.gd" type="Script" id=1] + +[sub_resource type="StyleBoxEmpty" id=1] + +[sub_resource type="StyleBoxFlat" id=2] +content_margin_left = 2.0 +content_margin_right = 2.0 +content_margin_top = 1.0 +content_margin_bottom = 1.0 +bg_color = Color( 0.25098, 0.270588, 0.32549, 1 ) + +[sub_resource type="StyleBoxFlat" id=3] +content_margin_left = 1.0 +content_margin_right = 1.0 +content_margin_top = 1.0 +content_margin_bottom = 1.0 +bg_color = Color( 0.2, 0.219608, 0.278431, 1 ) + +[node name="animated_sprite_inspector_dock" type="PanelContainer"] +margin_right = 14.0 +margin_bottom = 14.0 +custom_styles/panel = SubResource( 1 ) +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false, +"_editor_description_": "" +} + +[node name="margin" type="MarginContainer" parent="."] +margin_right = 97.0 +margin_bottom = 120.0 +custom_constants/margin_top = 2 +custom_constants/margin_bottom = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="margin"] +margin_top = 2.0 +margin_right = 97.0 +margin_bottom = 118.0 + +[node name="section_title" type="PanelContainer" parent="margin/VBoxContainer"] +margin_right = 97.0 +margin_bottom = 16.0 +size_flags_horizontal = 3 +custom_styles/panel = SubResource( 2 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="title" type="Label" parent="margin/VBoxContainer/section_title"] +margin_left = 2.0 +margin_top = 1.0 +margin_right = 95.0 +margin_bottom = 15.0 +size_flags_horizontal = 3 +text = "Aseprite" +align = 1 + +[node name="source" type="HBoxContainer" parent="margin/VBoxContainer"] +margin_top = 20.0 +margin_right = 97.0 +margin_bottom = 40.0 +hint_tooltip = "Location of the Aseprite (*.ase, *.aseprite) source file." + +[node name="Label" type="Label" parent="margin/VBoxContainer/source"] +margin_top = 3.0 +margin_right = 81.0 +margin_bottom = 17.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Aseprite File" + +[node name="button" type="Button" parent="margin/VBoxContainer/source"] +margin_left = 85.0 +margin_right = 97.0 +margin_bottom = 20.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "[empty]" +clip_text = true + +[node name="layer" type="HBoxContainer" parent="margin/VBoxContainer"] +margin_top = 44.0 +margin_right = 97.0 +margin_bottom = 64.0 +hint_tooltip = "Aseprite layer to be used in the animation. By default all layers are included." + +[node name="Label" type="Label" parent="margin/VBoxContainer/layer"] +margin_top = 3.0 +margin_right = 41.0 +margin_bottom = 17.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Layer" + +[node name="options" type="OptionButton" parent="margin/VBoxContainer/layer"] +margin_left = 45.0 +margin_right = 97.0 +margin_bottom = 20.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "[all]" +align = 1 + +[node name="options_title" type="PanelContainer" parent="margin/VBoxContainer"] +margin_top = 68.0 +margin_right = 97.0 +margin_bottom = 92.0 +custom_styles/panel = SubResource( 3 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="options_title" type="ToolButton" parent="margin/VBoxContainer/options_title"] +margin_left = 1.0 +margin_top = 1.0 +margin_right = 96.0 +margin_bottom = 23.0 +custom_colors/font_color_pressed = Color( 0.8, 0.807843, 0.827451, 1 ) +toggle_mode = true +text = "Options" +align = 0 + +[node name="options" type="VBoxContainer" parent="margin/VBoxContainer"] +visible = false +margin_top = 120.0 +margin_right = 198.0 +margin_bottom = 240.0 + +[node name="out_folder" type="HBoxContainer" parent="margin/VBoxContainer/options"] +margin_right = 198.0 +margin_bottom = 20.0 +hint_tooltip = "Location where the spritesheet file should be saved." + +[node name="Label" type="Label" parent="margin/VBoxContainer/options/out_folder"] +margin_top = 3.0 +margin_right = 97.0 +margin_bottom = 17.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Output folder" + +[node name="button" type="Button" parent="margin/VBoxContainer/options/out_folder"] +margin_left = 101.0 +margin_right = 198.0 +margin_bottom = 20.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "[empty]" +clip_text = true + +[node name="out_filename" type="HBoxContainer" parent="margin/VBoxContainer/options"] +margin_top = 24.0 +margin_right = 198.0 +margin_bottom = 48.0 +hint_tooltip = "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/options/out_filename"] +margin_top = 5.0 +margin_right = 109.0 +margin_bottom = 19.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Output file name" + +[node name="LineEdit" type="LineEdit" parent="margin/VBoxContainer/options/out_filename"] +margin_left = 113.0 +margin_right = 198.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 + +[node name="ex_pattern" type="HBoxContainer" parent="margin/VBoxContainer/options"] +margin_top = 52.0 +margin_right = 198.0 +margin_bottom = 76.0 +hint_tooltip = "Exclude layers with name matching this pattern (regex)." + +[node name="Label" type="Label" parent="margin/VBoxContainer/options/ex_pattern"] +margin_top = 5.0 +margin_right = 99.0 +margin_bottom = 19.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Exclude pattern" + +[node name="LineEdit" type="LineEdit" parent="margin/VBoxContainer/options/ex_pattern"] +margin_left = 103.0 +margin_right = 198.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 + +[node name="visible_layers" type="HBoxContainer" parent="margin/VBoxContainer/options"] +margin_top = 80.0 +margin_right = 198.0 +margin_bottom = 120.0 +hint_tooltip = "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/options/visible_layers"] +margin_top = 13.0 +margin_right = 118.0 +margin_bottom = 27.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Only visible layers" + +[node name="CheckButton" type="CheckButton" parent="margin/VBoxContainer/options/visible_layers"] +margin_left = 122.0 +margin_right = 198.0 +margin_bottom = 40.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 + +[node name="import" type="Button" parent="margin/VBoxContainer"] +margin_top = 96.0 +margin_right = 97.0 +margin_bottom = 116.0 +text = "Import" + +[connection signal="pressed" from="margin/VBoxContainer/source/button" to="." method="_on_source_pressed"] +[connection signal="item_selected" from="margin/VBoxContainer/layer/options" to="." method="_on_layer_item_selected"] +[connection signal="pressed" from="margin/VBoxContainer/layer/options" to="." method="_on_layer_pressed"] +[connection signal="toggled" from="margin/VBoxContainer/options_title/options_title" to="." method="_on_options_title_toggled"] +[connection signal="pressed" from="margin/VBoxContainer/options/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/animated_sprite/import_plugin.gd b/addons/AsepriteWizard/animated_sprite/import_plugin.gd new file mode 100644 index 0000000..3fbed82 --- /dev/null +++ b/addons/AsepriteWizard/animated_sprite/import_plugin.gd @@ -0,0 +1,233 @@ +tool +extends EditorImportPlugin + +const result_codes = preload("../config/result_codes.gd") + +var _config = preload("../config/config.gd").new() +var _sf_creator = preload("sprite_frames_creator.gd").new() + +func get_importer_name(): + return "aseprite.wizard.plugin" + + +func get_visible_name(): + return "Aseprite Importer" + + +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_import_options(i): + return [ + {"name": "split_layers", "default_value": false}, + {"name": "exclude_layers_pattern", "default_value": ''}, + {"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()}, + + {"name": "sprite_filename_pattern", "default_value": "{basename}.{layer}.{extension}"}, + + {"name": "texture_strip/import_texture_strip", "default_value": false}, + {"name": "texture_strip/filename_pattern", "default_value": "{basename}.{layer}.Strip.{extension}"}, + + {"name": "texture_atlas/import_texture_atlas", "default_value": false}, + {"name": "texture_atlas/filename_pattern", "default_value": "{basename}.{layer}.Atlas.{extension}"}, + {"name": "texture_atlas/frame_filename_pattern", "default_value": "{basename}.{layer}.{animation}.{frame}.Atlas.{extension}"}, + + {"name": "animated_texture/import_animated_texture", "default_value": false}, + {"name": "animated_texture/filename_pattern", "default_value": "{basename}.{layer}.{animation}.Texture.{extension}"}, + {"name": "animated_texture/frame_filename_pattern", "default_value": "{basename}.{layer}.{animation}.{frame}.Texture.{extension}"}, + ] + + +func get_option_visibility(option, options): + return true + + +static func replace_vars(pattern : String, vars : Dictionary): + var result = pattern; + for k in vars: + var v = vars[k] + result = result.replace("{%s}" % k, v) + return result + +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.find_last('/')) + var source_basename = source_file.substr(source_path.length()+1, -1) + source_basename = source_basename.substr(0, source_basename.find_last('.')) + + _config.load_config() + + _sf_creator.init(_config) + + var dir = Directory.new() + dir.make_dir(save_path) + + # Clear the directories contents + dir.open(save_path) + dir.list_dir_begin() + var file_name = dir.get_next() + while file_name != "": + if file_name != '.' and file_name != '..': + dir.remove(file_name) + file_name = dir.get_next() + + 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": '', + "column_count" : int(options['sheet_type']) if options['sheet_type'] != "Strip" else 128, + } + + var exit_code = _sf_creator.create_resource(absolute_source_file, absolute_save_path, aseprite_opts) + if exit_code != OK: + printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(exit_code)) + return FAILED + + dir.open(save_path) + dir.list_dir_begin() + + file_name = dir.get_next() + + var main_sprite_frame_saved = false + + var global_replacement_vars = { + "basename": source_basename, + } + + # Scan through the import directory and process the generated resources based on what options have been selected. + while file_name != "": + if file_name != ".." and file_name != ".": + if file_name.ends_with(".res"): + # This is a SpriteFrames resource generated for a layer. + var local_replacement_vars = global_replacement_vars.duplicate() + local_replacement_vars["layer"] = file_name.substr(0, file_name.length() - 4) + + if not main_sprite_frame_saved: + # Save this resource as the main resource. We need to set something here or Godot won't stop + # re-importing the resource. So this is either the SpriteFrames instance of the first layer + # (alphabetically) found in the import directory. + var sprite_frames : SpriteFrames = ResourceLoader.load("%s/%s" % [save_path, file_name], 'SpriteFrames', true) + main_sprite_frame_saved = true + var resource_path = "%s.res" % save_path; + sprite_frames.take_over_path(resource_path) + ResourceSaver.save(resource_path, sprite_frames) + + var sprite_frames : SpriteFrames = ResourceLoader.load("%s/%s" % [save_path, file_name], 'SpriteFrames', true) + + if options["split_layers"]: + var sprite_replacement_vars = local_replacement_vars.duplicate() + sprite_replacement_vars["extension"] = "res" + + var sprite_filename = "%s/%s" % [source_path, replace_vars(options["sprite_filename_pattern"], sprite_replacement_vars)] + ResourceSaver.save(sprite_filename, sprite_frames) + sprite_frames.take_over_path(sprite_filename) + + if options["texture_atlas/import_texture_atlas"]: + # Create a TextureAtlas resource for this layer. + var atlas_texture = null + var replacement_vars = local_replacement_vars.duplicate() + + for anim in sprite_frames.animations: + var i=0 + + replacement_vars["animation"] = anim.name + replacement_vars["extension"] = "res" + + for frame in anim.frames: + replacement_vars["frame"] = i + + if not atlas_texture: + atlas_texture = (frame as AtlasTexture).atlas + + var atlas_filename = "%s/%s" % [source_path, replace_vars(options["texture_atlas/filename_pattern"], replacement_vars)] + ResourceSaver.save(atlas_filename, atlas_texture) + atlas_texture.take_over_path(atlas_filename) + + frame.atlas = atlas_texture + + var frame_filename = "%s/%s" % [source_path, replace_vars(options["texture_atlas/frame_filename_pattern"], replacement_vars)] + ResourceSaver.save(frame_filename, frame) + i+=1 + + if options["animated_texture/import_animated_texture"]: + var replacement_vars = local_replacement_vars.duplicate() + replacement_vars["extension"] = "res" + + for anim in sprite_frames.animations: + replacement_vars["animation"] = anim.name + + var tex : AnimatedTexture = AnimatedTexture.new() + tex.frames = anim.frames.size() + + var i=0 + for frame in anim.frames: + replacement_vars["frame"] = i + + var atlas_tex = frame as AtlasTexture + var image : Image = atlas_tex.atlas.get_data() + var single_image = Image.new() + single_image.create(atlas_tex.get_width(), atlas_tex.get_height(), false, image.get_format()) + single_image.blit_rect(image, atlas_tex.region, Vector2.ZERO) + + var frame_filename = "%s/%s" % [source_path, replace_vars(options["animated_texture/frame_filename_pattern"], replacement_vars)] + + var res = ImageTexture.new() + res.create_from_image(single_image, 0) + res.flags = atlas_tex.flags + ResourceSaver.save(frame_filename, res) + res.take_over_path(frame_filename) + + tex.set_frame_texture(i, res) + + i+=1 + + var texture_filename = "%s/%s" % [source_path, replace_vars(options["animated_texture/filename_pattern"], replacement_vars)] + ResourceSaver.save(texture_filename, tex) + + elif options['texture_strip/import_texture_strip'] and file_name.ends_with(".png"): + var replacement_vars = global_replacement_vars.duplicate() + replacement_vars["layer"] = file_name.substr(0, file_name.length() - 4) + replacement_vars["extension"] = "png" + + var texture_filename = "%s/%s" % [source_path, replace_vars(options["texture_strip/filename_pattern"], replacement_vars)] + + var img : Image = Image.new() + img.load("%s/%s" % [save_path, file_name]) + var res = ImageTexture.new() + res.create_from_image(img, 0) + ResourceSaver.save(texture_filename, res) + + file_name = dir.get_next() + return OK diff --git a/addons/AsepriteWizard/animated_sprite/inspector_plugin.gd b/addons/AsepriteWizard/animated_sprite/inspector_plugin.gd new file mode 100644 index 0000000..2a0aec0 --- /dev/null +++ b/addons/AsepriteWizard/animated_sprite/inspector_plugin.gd @@ -0,0 +1,25 @@ +tool +extends EditorInspectorPlugin + +const InspectorDock = preload("animated_sprite_inspector_dock.tscn") + +var config +var file_system: EditorFileSystem + +var _sprite: AnimatedSprite + +func can_handle(object): + return object is AnimatedSprite + + +func parse_begin(object): + _sprite = object + + +func parse_end(): + var dock = InspectorDock.instance() + dock.sprite = _sprite + dock.config = config + dock.file_system = file_system + + add_custom_control(dock) diff --git a/addons/AsepriteWizard/animated_sprite/sf_wizard_dock.gd b/addons/AsepriteWizard/animated_sprite/sf_wizard_dock.gd new file mode 100644 index 0000000..26065aa --- /dev/null +++ b/addons/AsepriteWizard/animated_sprite/sf_wizard_dock.gd @@ -0,0 +1,178 @@ +tool +extends PanelContainer + +signal importer_state_changed +signal close_requested + +var result_code = preload("../config/result_codes.gd") +var _sf_creator = preload("./sprite_frames_creator.gd").new() + +var _config +var _file_system: EditorFileSystem + +var _file_dialog_aseprite: FileDialog +var _output_folder_dialog: FileDialog +var _warning_dialog: AcceptDialog + + +func _ready(): + _file_dialog_aseprite = _create_aseprite_file_selection() + _output_folder_dialog = _create_outuput_folder_selection() + _warning_dialog = AcceptDialog.new() + + _sf_creator.init(_config, _file_system) + + get_parent().add_child(_file_dialog_aseprite) + get_parent().add_child(_output_folder_dialog) + get_parent().add_child(_warning_dialog) + + _load_persisted_config() + +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 + + +func _load_persisted_config(): + _split_mode_field().pressed = _config.should_split_layers() + _only_visible_layers_field().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().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 _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 = FileDialog.new() + file_dialog.mode = FileDialog.MODE_OPEN_FILE + file_dialog.access = FileDialog.ACCESS_FILESYSTEM + file_dialog.connect("file_selected", self, "_on_aseprite_file_selected") + file_dialog.set_filters(PoolStringArray(["*.ase","*.aseprite"])) + return file_dialog + + +func _create_outuput_folder_selection(): + var file_dialog = FileDialog.new() + file_dialog.mode = FileDialog.MODE_OPEN_DIR + file_dialog.access = FileDialog.ACCESS_RESOURCES + file_dialog.connect("dir_selected", self, "_on_output_folder_selected") + return file_dialog + + +func _on_aseprite_file_selected(path): + _file_location_field().text = path + _config.set_last_source_path(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().pressed + + var export_mode = _sf_creator.LAYERS_EXPORT_MODE if split_layers else _sf_creator.FILE_EXPORT_MODE + var options = { + "export_mode": export_mode, + "exception_pattern": _exception_pattern_field().text, + "only_visible_layers": _only_visible_layers_field().pressed, + "output_filename": _custom_name_field().text, + "do_not_create_resource": _do_not_create_res_field().pressed, + "remove_source_files_allowed": true + } + var exit_code = _sf_creator.create_resource(aseprite_file, output_location, options) + if exit_code is GDScriptFunctionState: + exit_code = yield(exit_code, "completed") + + if exit_code != 0: + _show_error(exit_code) + return + _show_import_success_message() + + +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().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().pressed) + _config.set_do_not_create_resource(_do_not_create_res_field().pressed) + _config.save() + + +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 + diff --git a/addons/AsepriteWizard/animated_sprite/sprite_frames_creator.gd b/addons/AsepriteWizard/animated_sprite/sprite_frames_creator.gd new file mode 100644 index 0000000..e46e052 --- /dev/null +++ b/addons/AsepriteWizard/animated_sprite/sprite_frames_creator.gd @@ -0,0 +1,310 @@ +tool +extends Reference + +var result_code = preload("../config/result_codes.gd") +var _aseprite = preload("../aseprite/aseprite.gd").new() + +enum { + FILE_EXPORT_MODE, + LAYERS_EXPORT_MODE +} + +var _config +var _file_system: EditorFileSystem +var _should_check_file_system := false + + +func init(config, editor_file_system: EditorFileSystem = null): + _config = config + _file_system = editor_file_system + _should_check_file_system = _file_system != null + _aseprite.init(config) + + +func _loop_config_prefix() -> String: + return _config.get_animation_loop_exception_prefix() + + +func _is_loop_config_enabled() -> String: + return _config.is_default_animation_loop_enabled() + + +func create_animations(sprite: AnimatedSprite, options: Dictionary): + if not _aseprite.test_command(): + return result_code.ERR_ASEPRITE_CMD_NOT_FOUND + + var dir = Directory.new() + if not dir.file_exists(options.source): + return result_code.ERR_SOURCE_FILE_NOT_FOUND + + if not dir.dir_exists(options.output_folder): + return result_code.ERR_OUTPUT_FOLDER_NOT_FOUND + + var result = _create_animations_from_file(sprite, options) + + if result is GDScriptFunctionState: + result = yield(result, "completed") + + if result != result_code.SUCCESS: + printerr(result_code.get_error_message(result)) + + +func _create_animations_from_file(sprite: AnimatedSprite, options: Dictionary): + var output + + if options.get("layer", "") == "": + output = _aseprite.export_file(options.source, options.output_folder, options) + else: + output = _aseprite.export_layer(options.source, options.layer, options.output_folder, options) + + if output.empty(): + return result_code.ERR_ASEPRITE_EXPORT_FAILED + yield(_scan_filesystem(), "completed") + + var result = _import(output, sprite) + + if _config.should_remove_source_files(): + var dir = Directory.new() + dir.remove(output.data_file) + yield(_scan_filesystem(), "completed") + + return result + + +func create_resource(source_file: String, output_folder: String, options = {}): + var export_mode = options.get('export_mode', FILE_EXPORT_MODE) + + if not _aseprite.test_command(): + return result_code.ERR_ASEPRITE_CMD_NOT_FOUND + + var dir = Directory.new() + if not dir.file_exists(source_file): + return result_code.ERR_SOURCE_FILE_NOT_FOUND + + if not dir.dir_exists(output_folder): + return result_code.ERR_OUTPUT_FOLDER_NOT_FOUND + + match export_mode: + FILE_EXPORT_MODE: + if _should_check_file_system: + return yield(create_sprite_frames_from_aseprite_file(source_file, output_folder, options), "completed") + return create_sprite_frames_from_aseprite_file(source_file, output_folder, options) + LAYERS_EXPORT_MODE: + if _should_check_file_system: + return yield(create_sprite_frames_from_aseprite_layers(source_file, output_folder, options), "completed") + return create_sprite_frames_from_aseprite_layers(source_file, output_folder, options) + _: + return result_code.ERR_UNKNOWN_EXPORT_MODE + + +func create_sprite_frames_from_aseprite_file(source_file: String, output_folder: String, options: Dictionary): + var output = _aseprite.export_file(source_file, output_folder, options) + if output.empty(): + return result_code.ERR_ASEPRITE_EXPORT_FAILED + + if (_should_check_file_system): + yield(_scan_filesystem(), "completed") + + if options.get("do_not_create_resource", false): + return OK + + var result = _import(output) + + if options.get("remove_source_files_allowed", false) and _config.should_remove_source_files(): + var dir = Directory.new() + dir.remove(output.data_file) + if (_should_check_file_system): + yield(_scan_filesystem(), "completed") + + return result + + +func create_sprite_frames_from_aseprite_layers(source_file: String, output_folder: String, options: Dictionary): + var output = _aseprite.export_layers(source_file, output_folder, options) + if output.empty(): + return result_code.ERR_NO_VALID_LAYERS_FOUND + + var result = OK + + if (_should_check_file_system): + yield(_scan_filesystem(), "completed") + + var should_remove_source = options.get("remove_source_files_allowed", false) and _config.should_remove_source_files() + + for o in output: + if o.empty(): + result = result_code.ERR_ASEPRITE_EXPORT_FAILED + else: + if options.get("do_not_create_resource", false): + result = OK + else: + result = _import(o) + if should_remove_source: + var dir = Directory.new() + dir.remove(o.data_file) + + if should_remove_source and _should_check_file_system: + yield(_scan_filesystem(), "completed") + + return result + + +func _get_file_basename(file_path: String) -> String: + return file_path.get_file().trim_suffix('.%s' % file_path.get_extension()) + + +func _import(data, animated_sprite = null) -> int: + var source_file = data.data_file + var sprite_sheet = data.sprite_sheet + var file = File.new() + var err = file.open(source_file, File.READ) + if err != OK: + return err + var content = parse_json(file.get_as_text()) + + if not _aseprite.is_valid_spritesheet(content): + return result_code.ERR_INVALID_ASEPRITE_SPRITESHEET + + var texture = _parse_texture_path(sprite_sheet) + + var resource = _create_sprite_frames_with_animations(content, texture) + + if is_instance_valid(animated_sprite): + animated_sprite.frames = resource + return result_code.SUCCESS + + var save_path = "%s.%s" % [source_file.get_basename(), "res"] + var code = ResourceSaver.save(save_path, resource, ResourceSaver.FLAG_REPLACE_SUBRESOURCE_PATHS) + resource.take_over_path(save_path) + return code + + +func _create_sprite_frames_with_animations(content, texture) -> SpriteFrames: + var frame_cache = {} + var frames = _aseprite.get_content_frames(content) + var sprite_frames := SpriteFrames.new() + sprite_frames.remove_animation("default") + + 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) + _add_animation_frames(sprite_frames, tag.name, selected_frames, texture, tag.direction, frame_cache) + else: + _add_animation_frames(sprite_frames, "default", frames, texture) + + return sprite_frames + + +func _add_animation_frames( + sprite_frames: SpriteFrames, + anim_name: String, + frames : Array, + texture, + direction = 'forward', + frame_cache = {} +): + + var animation_name := anim_name + var is_loopable = _is_loop_config_enabled() + + var loop_prefix := _loop_config_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': + frames.invert() + + for frame in frames: + _add_to_sprite_frames(sprite_frames, animation_name, texture, frame, min_duration, frame_cache) + + if direction == 'pingpong': + frames.remove(frames.size() - 1) + if is_loopable: + frames.remove(0) + frames.invert() + + for frame in frames: + _add_to_sprite_frames(sprite_frames, animation_name, texture, frame, min_duration, frame_cache) + + 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 _parse_texture_path(path): + if not _should_check_file_system and not ResourceLoader.has_cached(path): + # this is a fallback for the importer. It generates the spritesheet file when it hasn't + # been imported before. Files generated in this method are usually + # bigger in size than the ones imported by Godot's default importer. + var image = Image.new() + image.load(path) + var texture = ImageTexture.new() + texture.create_from_image(image, 0) + return texture + + return ResourceLoader.load(path, 'Image', true) + + +func _add_to_sprite_frames( + sprite_frames, + animation_name: String, + texture, + frame: Dictionary, + min_duration: int, + frame_cache: Dictionary +): + var atlas : AtlasTexture = _create_atlastexture_from_frame(texture, frame, sprite_frames, frame_cache) + + var number_of_sprites = ceil(frame.duration / min_duration) + for _i in range(number_of_sprites): + sprite_frames.add_frame(animation_name, atlas) + + +func _create_atlastexture_from_frame( + image, + frame_data, + sprite_frames: SpriteFrames, + frame_cache: Dictionary +) -> AtlasTexture: + var frame = frame_data.frame + var region := Rect2(frame.x, frame.y, frame.w, frame.h) + 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 _scan_filesystem(): + _file_system.scan() + yield(_file_system, "filesystem_changed") + + +func list_layers(file: String, only_visibles = false) -> Array: + return _aseprite.list_layers(file, only_visibles) diff --git a/addons/AsepriteWizard/animation_player/animation_creator.gd b/addons/AsepriteWizard/animation_player/animation_creator.gd new file mode 100644 index 0000000..4f6d8f3 --- /dev/null +++ b/addons/AsepriteWizard/animation_player/animation_creator.gd @@ -0,0 +1,204 @@ +extends Reference + +var result_code = preload("../config/result_codes.gd") +var _aseprite = preload("../aseprite/aseprite.gd").new() + +var _config +var _file_system + +func init(config, editor_file_system: EditorFileSystem = null): + _config = config + _file_system = editor_file_system + _aseprite.init(config) + + +func create_animations(sprite: Sprite, player: AnimationPlayer, options: Dictionary): + if not _aseprite.test_command(): + return result_code.ERR_ASEPRITE_CMD_NOT_FOUND + + var dir = Directory.new() + if not dir.file_exists(options.source): + return result_code.ERR_SOURCE_FILE_NOT_FOUND + + if not dir.dir_exists(options.output_folder): + return result_code.ERR_OUTPUT_FOLDER_NOT_FOUND + + var result = _create_animations_from_file(sprite, player, options) + if result is GDScriptFunctionState: + result = yield(result, "completed") + + if result != result_code.SUCCESS: + printerr(result_code.get_error_message(result)) + + +func _create_animations_from_file(sprite: Sprite, player: AnimationPlayer, options: Dictionary): + var output + + if options.get("layer", "") == "": + output = _aseprite.export_file(options.source, options.output_folder, options) + else: + output = _aseprite.export_layer(options.source, options.layer, options.output_folder, options) + + if output.empty(): + return result_code.ERR_ASEPRITE_EXPORT_FAILED + yield(_scan_filesystem(), "completed") + + var result = _import(sprite, player, output) + + if _config.should_remove_source_files(): + var dir = Directory.new() + dir.remove(output.data_file) + + return result + + +func _import(sprite: Sprite, player: AnimationPlayer, data: Dictionary): + var source_file = data.data_file + var sprite_sheet = data.sprite_sheet + + var file = File.new() + var err = file.open(source_file, File.READ) + if err != OK: + return err + + var content = parse_json(file.get_as_text()) + + if not _aseprite.is_valid_spritesheet(content): + return result_code.ERR_INVALID_ASEPRITE_SPRITESHEET + + _load_texture(sprite, sprite_sheet, content) + var result = _configure_animations(sprite, player, content) + if result != result_code.SUCCESS: + return result + + return _cleanup_animations(sprite, player, content) + + +func _load_texture(sprite: Sprite, sprite_sheet: String, content: Dictionary): + var texture = ResourceLoader.load(sprite_sheet, 'Image', true) + sprite.texture = texture + + if content.frames.empty(): + return + + sprite.hframes = content.meta.size.w / content.frames[0].sourceSize.w + sprite.vframes = content.meta.size.h / content.frames[0].sourceSize.h + + +func _configure_animations(sprite: Sprite, player: AnimationPlayer, content: Dictionary): + var frames = _aseprite.get_content_frames(content) + 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) + result = _add_animation_frames(sprite, player, tag.name, selected_frames, tag.direction) + if result != result_code.SUCCESS: + break + return result + else: + return _add_animation_frames(sprite, player, "default", frames) + + +func _add_animation_frames(sprite: Sprite, player: AnimationPlayer, anim_name: String, frames: Array, direction = 'forward'): + var animation_name = anim_name + var is_loopable = _config.is_default_animation_loop_enabled() + + if animation_name.begins_with(_config.get_animation_loop_exception_prefix()): + animation_name = anim_name.substr(_config.get_animation_loop_exception_prefix().length()) + is_loopable = not is_loopable + + if not player.has_animation(animation_name): + player.add_animation(animation_name, Animation.new()) + + var animation = player.get_animation(animation_name) + var track = _get_frame_track_path(player, sprite) + var track_index = _create_frame_track(sprite, animation, track) + + if direction == 'reverse': + frames.invert() + + var animation_length = 0 + + for frame in frames: + var frame_index = _calculate_frame_index(sprite, frame) + animation.track_insert_key(track_index, animation_length, frame_index) + animation_length += frame.duration / 1000 + + if direction == 'pingpong': + frames.remove(frames.size() - 1) + if is_loopable: + frames.remove(0) + frames.invert() + + for frame in frames: + var frame_index = _calculate_frame_index(sprite, frame) + animation.track_insert_key(track_index, animation_length, frame_index) + animation_length += frame.duration / 1000 + + animation.length = animation_length + animation.loop = is_loopable + + return result_code.SUCCESS + + +func _calculate_frame_index(sprite: Sprite, 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_frame_track(sprite: Sprite, animation: Animation, track: String): + var track_index = animation.find_track(track) + + 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_frame_track_path(player: AnimationPlayer, sprite: Sprite): + var node_path = player.get_node(player.root_node).get_path_to(sprite) + return "%s:frame" % node_path + + +func _cleanup_animations(sprite: Sprite, player: AnimationPlayer, content: Dictionary): + if not (content.meta.has("frameTags") and content.meta.frameTags.size() > 0): + return result_code.SUCCESS + + var track = _get_frame_track_path(player, sprite) + 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) + + 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) + + return result_code.SUCCESS + +func _scan_filesystem(): + _file_system.scan() + yield(_file_system, "filesystem_changed") + + +func list_layers(file: String, only_visibles = false) -> Array: + return _aseprite.list_layers(file, only_visibles) diff --git a/addons/AsepriteWizard/animation_player/inspector_plugin.gd b/addons/AsepriteWizard/animation_player/inspector_plugin.gd new file mode 100644 index 0000000..b1926ec --- /dev/null +++ b/addons/AsepriteWizard/animation_player/inspector_plugin.gd @@ -0,0 +1,25 @@ +tool +extends EditorInspectorPlugin + +const InspectorDock = preload("sprite_inspector_dock.tscn") + +var config +var file_system: EditorFileSystem + +var _sprite: Sprite + +func can_handle(object): + return object is Sprite + + +func parse_begin(object): + _sprite = object + + +func parse_end(): + var dock = InspectorDock.instance() + dock.sprite = _sprite + dock.config = config + dock.file_system = file_system + + add_custom_control(dock) diff --git a/addons/AsepriteWizard/animation_player/sprite_inspector_dock.gd b/addons/AsepriteWizard/animation_player/sprite_inspector_dock.gd new file mode 100644 index 0000000..1abe356 --- /dev/null +++ b/addons/AsepriteWizard/animation_player/sprite_inspector_dock.gd @@ -0,0 +1,256 @@ +tool +extends PanelContainer + +const wizard_config = preload("../config/wizard_config.gd") +const result_code = preload("../config/result_codes.gd") +var animation_creator = preload("animation_creator.gd").new() + +var scene: Node +var sprite: Sprite + +var config +var file_system: EditorFileSystem + +var _layer: String = "" +var _source: String = "" +var _animation_player_path: String +var _file_dialog_aseprite: FileDialog +var _output_folder_dialog: FileDialog +var _importing := false + +var _output_folder := "" +var _out_folder_default := "[Same as scene]" +var _layer_default := "[all]" + +onready var _options_field = $margin/VBoxContainer/animation_player/options +onready var _source_field = $margin/VBoxContainer/source/button +onready var _layer_field = $margin/VBoxContainer/layer/options +onready var _options_title = $margin/VBoxContainer/options_title/options_title +onready var _options_container = $margin/VBoxContainer/options +onready var _out_folder_field = $margin/VBoxContainer/options/out_folder/button +onready var _out_filename_field = $margin/VBoxContainer/options/out_filename/LineEdit +onready var _visible_layers_field = $margin/VBoxContainer/options/visible_layers/CheckButton +onready var _ex_pattern_field = $margin/VBoxContainer/options/ex_pattern/LineEdit + +func _ready(): + var cfg = wizard_config.decode(sprite.editor_description) + + if cfg == null: + _load_default_config() + else: + _load_config(cfg) + + animation_creator.init(config, file_system) + + +func _load_config(cfg): + if cfg.has("source"): + _set_source(cfg.source) + + if cfg.has("player"): + _set_animation_player(cfg.player) + + if cfg.get("layer", "") != "": + _set_layer(cfg.layer) + + _output_folder = cfg.get("o_folder", "") + _out_folder_field.text = _output_folder if _output_folder != "" else _out_folder_default + _out_filename_field.text = cfg.get("o_name", "") + _visible_layers_field.pressed = cfg.get("only_visible", "") == "True" + _ex_pattern_field.text = cfg.get("o_ex_p", "") + + _set_options_visible(cfg.get("op_exp", "false") == "True") + + +func _load_default_config(): + _ex_pattern_field.text = config.get_default_exclusion_pattern() + _set_options_visible(false) + + +func _set_source(source): + _source = source + _source_field.text = _source + _source_field.hint_tooltip = _source + + +func _set_animation_player(player): + _animation_player_path = player + _options_field.add_item(_animation_player_path) + + +func _set_layer(layer): + _layer = layer + _layer_field.add_item(_layer) + + +func _on_options_pressed(): + var animation_players = [] + var root = get_tree().get_edited_scene_root() + _find_animation_players(root, root, animation_players) + + var current = 0 + _options_field.clear() + _options_field.add_item("[empty]") + + for ap in animation_players: + _options_field.add_item(ap) + if ap == _animation_player_path: + current = _options_field.get_item_count() - 1 + + _options_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_options_item_selected(index): + if index == 0: + _animation_player_path = "" + return + _animation_player_path = _options_field.get_item_text(index) + _save_config() + + +func _on_layer_pressed(): + if _source == "": + _show_message("Please. Select source file first.") + return + + var layers = animation_creator.list_layers(ProjectSettings.globalize_path(_source)) + var current = 0 + _layer_field.clear() + _layer_field.add_item("[all]") + + for l in layers: + if l == "": + continue + + _layer_field.add_item(l) + if l == _layer: + current = _layer_field.get_item_count() - 1 + _layer_field.select(current) + + +func _on_layer_item_selected(index): + if index == 0: + _layer = "" + return + _layer = _layer_field.get_item_text(index) + _save_config() + + +func _on_source_pressed(): + _open_source_dialog() + + +func _on_import_pressed(): + if _importing: + return + _importing = true + + 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 + + if _source == "": + _show_message("Aseprite file not selected") + _importing = false + return + + var options = { + "source": ProjectSettings.globalize_path(_source), + "output_folder": _output_folder if _output_folder != "" else root.filename.get_base_dir(), + "exception_pattern": _ex_pattern_field.text, + "only_visible_layers": _visible_layers_field.pressed, + "output_filename": _out_filename_field.text, + "layer": _layer + } + + _save_config() + + animation_creator.create_animations(sprite, root.get_node(_animation_player_path), options) + _importing = false + + +func _save_config(): + sprite.editor_description = wizard_config.encode({ + "player": _animation_player_path, + "source": _source, + "layer": _layer, + "op_exp": _options_title.pressed, + "o_folder": _output_folder, + "o_name": _out_filename_field.text, + "only_visible": _visible_layers_field.pressed, + "o_ex_p": _ex_pattern_field.text + }) + + +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 = _source.get_base_dir() + _file_dialog_aseprite.popup_centered_ratio() + + +func _create_aseprite_file_selection(): + var file_dialog = FileDialog.new() + file_dialog.mode = FileDialog.MODE_OPEN_FILE + file_dialog.access = FileDialog.ACCESS_FILESYSTEM + file_dialog.connect("file_selected", self, "_on_aseprite_file_selected") + file_dialog.set_filters(PoolStringArray(["*.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 _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", _warning_dialog, "queue_free") + + +func _on_options_title_toggled(button_pressed): + _set_options_visible(button_pressed) + _save_config() + + +func _set_options_visible(is_visible): + _options_container.visible = is_visible + _options_title.icon = config.get_icon_arrow_down() if is_visible else config.get_icon_arrow_right() + + +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 = FileDialog.new() + file_dialog.mode = FileDialog.MODE_OPEN_DIR + file_dialog.access = FileDialog.ACCESS_RESOURCES + file_dialog.connect("dir_selected", self, "_on_output_folder_selected") + return file_dialog + + +func _on_output_folder_selected(path): + _output_folder = path + _out_folder_field.text = _output_folder if _output_folder != "" else _out_folder_default + _output_folder_dialog.queue_free() diff --git a/addons/AsepriteWizard/animation_player/sprite_inspector_dock.tscn b/addons/AsepriteWizard/animation_player/sprite_inspector_dock.tscn new file mode 100644 index 0000000..6a3236e --- /dev/null +++ b/addons/AsepriteWizard/animation_player/sprite_inspector_dock.tscn @@ -0,0 +1,252 @@ +[gd_scene load_steps=5 format=2] + +[ext_resource path="res://addons/AsepriteWizard/animation_player/sprite_inspector_dock.gd" type="Script" id=1] + +[sub_resource type="StyleBoxEmpty" id=1] + +[sub_resource type="StyleBoxFlat" id=2] +content_margin_left = 2.0 +content_margin_right = 2.0 +content_margin_top = 1.0 +content_margin_bottom = 1.0 +bg_color = Color( 0.25098, 0.270588, 0.32549, 1 ) + +[sub_resource type="StyleBoxFlat" id=3] +content_margin_left = 1.0 +content_margin_right = 1.0 +content_margin_top = 1.0 +content_margin_bottom = 1.0 +bg_color = Color( 0.2, 0.219608, 0.278431, 1 ) + +[node name="sprite_inspector_dock" type="PanelContainer"] +margin_right = 14.0 +margin_bottom = 14.0 +custom_styles/panel = SubResource( 1 ) +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false, +"_editor_description_": "" +} + +[node name="margin" type="MarginContainer" parent="."] +margin_right = 187.0 +margin_bottom = 144.0 +custom_constants/margin_top = 2 +custom_constants/margin_bottom = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="margin"] +margin_top = 2.0 +margin_right = 187.0 +margin_bottom = 142.0 + +[node name="section_title" type="PanelContainer" parent="margin/VBoxContainer"] +margin_right = 187.0 +margin_bottom = 16.0 +size_flags_horizontal = 3 +custom_styles/panel = SubResource( 2 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="title" type="Label" parent="margin/VBoxContainer/section_title"] +margin_left = 2.0 +margin_top = 1.0 +margin_right = 185.0 +margin_bottom = 15.0 +size_flags_horizontal = 3 +text = "Aseprite" +align = 1 + +[node name="animation_player" type="HBoxContainer" parent="margin/VBoxContainer"] +margin_top = 20.0 +margin_right = 187.0 +margin_bottom = 40.0 +hint_tooltip = "AnimationPlayer node where animations should be added to." + +[node name="Label" type="Label" parent="margin/VBoxContainer/animation_player"] +margin_top = 3.0 +margin_right = 105.0 +margin_bottom = 17.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "AnimationPlayer" + +[node name="options" type="OptionButton" parent="margin/VBoxContainer/animation_player"] +margin_left = 109.0 +margin_right = 187.0 +margin_bottom = 20.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "[empty]" +align = 1 + +[node name="source" type="HBoxContainer" parent="margin/VBoxContainer"] +margin_top = 44.0 +margin_right = 187.0 +margin_bottom = 64.0 +hint_tooltip = "Location of the Aseprite (*.ase, *.aseprite) source file." + +[node name="Label" type="Label" parent="margin/VBoxContainer/source"] +margin_top = 3.0 +margin_right = 91.0 +margin_bottom = 17.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Aseprite File" + +[node name="button" type="Button" parent="margin/VBoxContainer/source"] +margin_left = 95.0 +margin_right = 187.0 +margin_bottom = 20.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "[empty]" +clip_text = true + +[node name="layer" type="HBoxContainer" parent="margin/VBoxContainer"] +margin_top = 68.0 +margin_right = 187.0 +margin_bottom = 88.0 +hint_tooltip = "Aseprite layer to be used in the animation. By default all layers are included." + +[node name="Label" type="Label" parent="margin/VBoxContainer/layer"] +margin_top = 3.0 +margin_right = 91.0 +margin_bottom = 17.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Layer" + +[node name="options" type="OptionButton" parent="margin/VBoxContainer/layer"] +margin_left = 95.0 +margin_right = 187.0 +margin_bottom = 20.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "[all]" +align = 1 + +[node name="options_title" type="PanelContainer" parent="margin/VBoxContainer"] +margin_top = 92.0 +margin_right = 187.0 +margin_bottom = 116.0 +custom_styles/panel = SubResource( 3 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="options_title" type="ToolButton" parent="margin/VBoxContainer/options_title"] +margin_left = 1.0 +margin_top = 1.0 +margin_right = 186.0 +margin_bottom = 23.0 +custom_colors/font_color_pressed = Color( 0.8, 0.807843, 0.827451, 1 ) +toggle_mode = true +text = "Options" +align = 0 + +[node name="options" type="VBoxContainer" parent="margin/VBoxContainer"] +visible = false +margin_top = 120.0 +margin_right = 198.0 +margin_bottom = 240.0 + +[node name="out_folder" type="HBoxContainer" parent="margin/VBoxContainer/options"] +margin_right = 198.0 +margin_bottom = 20.0 +hint_tooltip = "Location where the spritesheet file should be saved." + +[node name="Label" type="Label" parent="margin/VBoxContainer/options/out_folder"] +margin_top = 3.0 +margin_right = 97.0 +margin_bottom = 17.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Output folder" + +[node name="button" type="Button" parent="margin/VBoxContainer/options/out_folder"] +margin_left = 101.0 +margin_right = 198.0 +margin_bottom = 20.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "[empty]" +clip_text = true + +[node name="out_filename" type="HBoxContainer" parent="margin/VBoxContainer/options"] +margin_top = 24.0 +margin_right = 198.0 +margin_bottom = 48.0 +hint_tooltip = "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/options/out_filename"] +margin_top = 5.0 +margin_right = 109.0 +margin_bottom = 19.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Output file name" + +[node name="LineEdit" type="LineEdit" parent="margin/VBoxContainer/options/out_filename"] +margin_left = 113.0 +margin_right = 198.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 + +[node name="ex_pattern" type="HBoxContainer" parent="margin/VBoxContainer/options"] +margin_top = 52.0 +margin_right = 198.0 +margin_bottom = 76.0 +hint_tooltip = "Exclude layers with name matching this pattern (regex)." + +[node name="Label" type="Label" parent="margin/VBoxContainer/options/ex_pattern"] +margin_top = 5.0 +margin_right = 99.0 +margin_bottom = 19.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Exclude pattern" + +[node name="LineEdit" type="LineEdit" parent="margin/VBoxContainer/options/ex_pattern"] +margin_left = 103.0 +margin_right = 198.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 + +[node name="visible_layers" type="HBoxContainer" parent="margin/VBoxContainer/options"] +margin_top = 80.0 +margin_right = 198.0 +margin_bottom = 120.0 +hint_tooltip = "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/options/visible_layers"] +margin_top = 13.0 +margin_right = 118.0 +margin_bottom = 27.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Only visible layers" + +[node name="CheckButton" type="CheckButton" parent="margin/VBoxContainer/options/visible_layers"] +margin_left = 122.0 +margin_right = 198.0 +margin_bottom = 40.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 + +[node name="import" type="Button" parent="margin/VBoxContainer"] +margin_top = 120.0 +margin_right = 187.0 +margin_bottom = 140.0 +text = "Import" + +[connection signal="item_selected" from="margin/VBoxContainer/animation_player/options" to="." method="_on_options_item_selected"] +[connection signal="pressed" from="margin/VBoxContainer/animation_player/options" to="." method="_on_options_pressed"] +[connection signal="pressed" from="margin/VBoxContainer/source/button" to="." method="_on_source_pressed"] +[connection signal="item_selected" from="margin/VBoxContainer/layer/options" to="." method="_on_layer_item_selected"] +[connection signal="pressed" from="margin/VBoxContainer/layer/options" to="." method="_on_layer_pressed"] +[connection signal="toggled" from="margin/VBoxContainer/options_title/options_title" to="." method="_on_options_title_toggled"] +[connection signal="pressed" from="margin/VBoxContainer/options/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/aseprite/aseprite.gd b/addons/AsepriteWizard/aseprite/aseprite.gd new file mode 100644 index 0000000..474122a --- /dev/null +++ b/addons/AsepriteWizard/aseprite/aseprite.gd @@ -0,0 +1,183 @@ +tool +extends Reference + +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 basename = _get_file_basename(output_name) + var output_dir = output_folder.replace("res://", "./") + 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") + + _add_sheet_type_arguments(arguments, options) + + _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': data_file.replace("./", "res://"), + "sprite_sheet": sprite_sheet.replace("./", "res://") + } + + +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', "") + var output_dir = output_folder.replace("res://", "./") + 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 output = [] + var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet) + arguments.push_front(layer_name) + arguments.push_front("--layer") + + _add_sheet_type_arguments(arguments, options) + + var exit_code = _execute(arguments, output) + if exit_code != 0: + print('aseprite: failed to export layer spritesheet') + print(output) + return {} + + return { + 'data_file': data_file.replace("./", "res://"), + "sprite_sheet": sprite_sheet.replace("./", "res://") + } + + +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.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.empty(): + return output + + return output[0].split('\n') + + +func _export_command_common_arguments(source_name: String, data_path: String, spritesheet_path: String) -> Array: + return [ + "-b", + "--list-tags", + "--data", + data_path, + "--format", + "json-array", + "--sheet", + spritesheet_path, + source_name + ] + + +func _execute(arguments, output): + return OS.execute(_aseprite_command(), arguments, true, output, true) + + +func _aseprite_command() -> String: + return _config.get_command() + + +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() diff --git a/addons/AsepriteWizard/config/config.gd b/addons/AsepriteWizard/config/config.gd new file mode 100644 index 0000000..dcd7f9f --- /dev/null +++ b/addons/AsepriteWizard/config/config.gd @@ -0,0 +1,175 @@ +tool +extends Reference + +# GLOBAL CONFIGS +const CONFIG_FILE_PATH = 'user://aseprite_wizard.cfg' +const _CONFIG_SECTION_KEY = 'aseprite' +const _COMMAND_KEY = 'command' +const _IMPORTER_ENABLE_KEY = 'is_importer_enabled' +const _REMOVE_SOURCE_FILES_KEY = 'remove_source_files' +const _LOOP_ENABLED = 'loop_enabled' +const _LOOP_EXCEPTION_PREFIX = 'loop_config_prefix' +const _DEFAULT_LOOP_EX_PREFIX = '_' +const _DEFAULT_EXCLUSION_PATTERN_KEY = 'default_layer_ex_pattern' + +# IMPORT CONFIGS +const _IMPORT_SECTION_KEY = 'file_locations' +const _I_LAST_SOURCE_PATH_KEY = 'source' +const _I_LAST_OUTPUT_DIR_KEY = 'output' +const _I_SHOULD_SPLIT_LAYERS_KEY = 'split_layers' +const _I_EXCEPTIONS_KEY = 'exceptions_key' +const _I_ONLY_VISIBLE_LAYERS_KEY = 'only_visible_layers' +const _I_CUSTOM_NAME_KEY = 'custom_name' +const _I_DO_NOT_CREATE_RES_KEY = 'disable_resource_creation' + +# INTERFACE CONFIGS +var _icon_arrow_down: Texture +var _icon_arrow_right: Texture + +var _config := ConfigFile.new() + + +func load_config() -> void: + _config = ConfigFile.new() + _config.load(CONFIG_FILE_PATH) + + +func save() -> void: + _config.save(CONFIG_FILE_PATH) + +####################################################### +# GLOBAL CONFIGS +###################################################### + +func default_command() -> String: + return 'aseprite' + + +func get_command() -> String: + var command = _config.get_value(_CONFIG_SECTION_KEY, _COMMAND_KEY, "") + return command if command != "" else default_command() + + +func set_command(aseprite_command: String) -> void: + if aseprite_command == "": + _config.set_value(_CONFIG_SECTION_KEY, _COMMAND_KEY, default_command()) + else: + _config.set_value(_CONFIG_SECTION_KEY, _COMMAND_KEY, aseprite_command) + + +func is_importer_enabled() -> bool: + return _config.get_value(_CONFIG_SECTION_KEY, _IMPORTER_ENABLE_KEY, false) + + +func set_importer_enabled(is_enabled: bool) -> void: + _config.set_value(_CONFIG_SECTION_KEY, _IMPORTER_ENABLE_KEY, is_enabled) + + +func should_remove_source_files() -> bool: + return _config.get_value(_CONFIG_SECTION_KEY, _REMOVE_SOURCE_FILES_KEY, true) + + +func set_remove_source_files(should_remove: bool) -> void: + _config.set_value(_CONFIG_SECTION_KEY, _REMOVE_SOURCE_FILES_KEY, should_remove) + + +func is_default_animation_loop_enabled() -> bool: + return _config.get_value(_CONFIG_SECTION_KEY, _LOOP_ENABLED, true) + + +func set_default_animation_loop(should_loop: bool) -> void: + _config.set_value(_CONFIG_SECTION_KEY, _LOOP_ENABLED, should_loop) + + +func get_animation_loop_exception_prefix() -> String: + return _config.get_value(_CONFIG_SECTION_KEY, _LOOP_EXCEPTION_PREFIX, _DEFAULT_LOOP_EX_PREFIX) + + +func set_animation_loop_exception_prefix(prefix: String) -> void: + _config.set_value(_CONFIG_SECTION_KEY, _LOOP_EXCEPTION_PREFIX, prefix if prefix != "" else _DEFAULT_LOOP_EX_PREFIX) + + +func get_default_exclusion_pattern() -> String: + return _config.get_value(_CONFIG_SECTION_KEY, _DEFAULT_EXCLUSION_PATTERN_KEY, "") + + +func set_default_exclusion_pattern(pattern: String) -> void: + _config.set_value(_CONFIG_SECTION_KEY, _DEFAULT_EXCLUSION_PATTERN_KEY, pattern) + + +####################################################### +# IMPORT CONFIGS +###################################################### +func get_last_source_path() -> String: + return _config.get_value(_IMPORT_SECTION_KEY, _I_LAST_SOURCE_PATH_KEY, "") + + +func set_last_source_path(source_path: String) -> void: + _config.set_value(_IMPORT_SECTION_KEY, _I_LAST_SOURCE_PATH_KEY, source_path) + + +func get_last_output_path() -> String: + return _config.get_value(_IMPORT_SECTION_KEY, _I_LAST_OUTPUT_DIR_KEY, "") + + +func set_last_output_path(output_path: String) -> void: + _config.set_value(_IMPORT_SECTION_KEY, _I_LAST_OUTPUT_DIR_KEY, output_path) + + +func should_split_layers() -> bool: + return _config.get_value(_IMPORT_SECTION_KEY, _I_SHOULD_SPLIT_LAYERS_KEY, false) + + +func set_split_layers(should_split: bool) -> void: + _config.set_value(_IMPORT_SECTION_KEY, _I_SHOULD_SPLIT_LAYERS_KEY, false) + + +func get_exception_pattern() -> String: + return _config.get_value(_IMPORT_SECTION_KEY, _I_EXCEPTIONS_KEY, "") + + +func set_exception_pattern(pattern: String) -> void: + _config.set_value(_IMPORT_SECTION_KEY, _I_EXCEPTIONS_KEY, pattern) + + +func should_include_only_visible_layers() -> bool: + return _config.get_value(_IMPORT_SECTION_KEY, _I_ONLY_VISIBLE_LAYERS_KEY, false) + + +func set_include_only_visible_layers(include_only_visible: bool) -> void: + _config.set_value(_IMPORT_SECTION_KEY, _I_ONLY_VISIBLE_LAYERS_KEY, include_only_visible) + + +func get_last_custom_name() -> String: + return _config.get_value(_IMPORT_SECTION_KEY, _I_CUSTOM_NAME_KEY, "") + + +func set_custom_name(custom_name: String) -> void: + _config.set_value(_IMPORT_SECTION_KEY, _I_CUSTOM_NAME_KEY, custom_name) + + +func should_not_create_resource() -> bool: + return _config.get_value(_IMPORT_SECTION_KEY, _I_DO_NOT_CREATE_RES_KEY, false) + + +func set_do_not_create_resource(do_no_create: bool) -> void: + _config.set_value(_IMPORT_SECTION_KEY, _I_DO_NOT_CREATE_RES_KEY, do_no_create) + + +####################################################### +# INTERFACE CONFIGS +###################################################### +func set_icon_arrow_down(icon: Texture) -> void: + _icon_arrow_down = icon + + +func get_icon_arrow_down() -> Texture: + return _icon_arrow_down + + +func set_icon_arrow_right(icon: Texture) -> void: + _icon_arrow_right = icon + + +func get_icon_arrow_right() -> Texture: + return _icon_arrow_right diff --git a/addons/AsepriteWizard/config/config_dialog.gd b/addons/AsepriteWizard/config/config_dialog.gd new file mode 100644 index 0000000..89987a1 --- /dev/null +++ b/addons/AsepriteWizard/config/config_dialog.gd @@ -0,0 +1,61 @@ +tool +extends PopupPanel + +signal importer_state_changed + +var _config + +onready var _aseprite_command_field = $MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer/aseprite_command +onready var _importer_enable_field = $MarginContainer/VBoxContainer/enable_importer +onready var _remove_source_files_field = $MarginContainer/VBoxContainer/remove_source +onready var _enable_animation_loop = $MarginContainer/VBoxContainer/loop_animations +onready var _loop_ex_prefix = $MarginContainer/VBoxContainer/loop/loop_config_prefix +onready var _layer_ex_pattern = $MarginContainer/VBoxContainer/layer_ex/ex_p_config_prefix +onready var _version_label = $MarginContainer/VBoxContainer/VBoxContainer/version_found + +func _ready(): + _aseprite_command_field.text = _config.get_command() + _importer_enable_field.pressed = _config.is_importer_enabled() + _remove_source_files_field.pressed = _config.should_remove_source_files() + _enable_animation_loop.pressed = _config.is_default_animation_loop_enabled() + _loop_ex_prefix.text = _config.get_animation_loop_exception_prefix() + _layer_ex_pattern.text = _config.get_default_exclusion_pattern() + _version_label.modulate.a = 0 + + +func init(config): + _config = config + + +func _on_save_button_up(): + _config.set_command(_aseprite_command_field.text) + + if _importer_enable_field.pressed != _config.is_importer_enabled(): + _config.set_importer_enabled(_importer_enable_field.pressed) + self.emit_signal("importer_state_changed") + + _config.set_remove_source_files(_remove_source_files_field.pressed) + _config.set_default_animation_loop(_enable_animation_loop.pressed) + _config.set_animation_loop_exception_prefix(_loop_ex_prefix.text) + _config.set_default_exclusion_pattern(_layer_ex_pattern.text) + + _config.save() + self.hide() + + +func _on_close_button_up(): + self.hide() + + +func _on_test_pressed(): + var output = [] + if _test_command(output): + _version_label.text = "%s found." % PoolStringArray(output).join("\n").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'], true, output, 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..d681668 --- /dev/null +++ b/addons/AsepriteWizard/config/config_dialog.tscn @@ -0,0 +1,176 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/AsepriteWizard/config/config_dialog.gd" type="Script" id=1] + +[node name="config_dialog" type="PopupPanel"] +margin_right = 400.0 +margin_bottom = 250.0 +rect_min_size = Vector2( 600, 250 ) +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="MarginContainer" type="MarginContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 4.0 +margin_top = 4.0 +margin_right = -4.0 +margin_bottom = -4.0 +size_flags_horizontal = 3 +custom_constants/margin_right = 40 +custom_constants/margin_top = 40 +custom_constants/margin_left = 40 +custom_constants/margin_bottom = 40 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +margin_left = 40.0 +margin_top = 40.0 +margin_right = 552.0 +margin_bottom = 420.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +custom_constants/separation = 20 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer"] +margin_right = 512.0 +margin_bottom = 72.0 +custom_constants/separation = 10 + +[node name="Aseprite Command" type="Label" parent="MarginContainer/VBoxContainer/VBoxContainer"] +margin_right = 512.0 +margin_bottom = 14.0 +hint_tooltip = "Define the path for Aseprite command" +mouse_filter = 1 +text = "Aseprite Command Path" + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/VBoxContainer"] +margin_top = 24.0 +margin_right = 512.0 +margin_bottom = 48.0 +size_flags_horizontal = 3 + +[node name="aseprite_command" type="LineEdit" parent="MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer"] +margin_right = 469.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +caret_blink = true + +[node name="test" type="Button" parent="MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer"] +margin_left = 473.0 +margin_right = 512.0 +margin_bottom = 24.0 +text = "Test" + +[node name="version_found" type="Label" parent="MarginContainer/VBoxContainer/VBoxContainer"] +modulate = Color( 1, 1, 1, 0 ) +margin_top = 58.0 +margin_right = 512.0 +margin_bottom = 72.0 +rect_min_size = Vector2( 300, 0 ) +size_flags_horizontal = 3 +text = "Aseprite version found" + +[node name="enable_importer" type="CheckBox" parent="MarginContainer/VBoxContainer"] +margin_top = 92.0 +margin_right = 512.0 +margin_bottom = 116.0 +hint_tooltip = "Enable Aseprite automatic importer for files located inside the project folder." +pressed = true +text = "Enable Aseprite Importer" + +[node name="remove_source" type="CheckBox" parent="MarginContainer/VBoxContainer"] +margin_top = 136.0 +margin_right = 512.0 +margin_bottom = 160.0 +hint_tooltip = "removes *.json files generated during import." +text = "Remove source files" + +[node name="loop_animations" type="CheckBox" parent="MarginContainer/VBoxContainer"] +margin_top = 180.0 +margin_right = 512.0 +margin_bottom = 204.0 +hint_tooltip = "Default loop value for animations" +pressed = true +text = "Loop animations" +__meta__ = { +"_editor_description_": "" +} + +[node name="loop" type="VBoxContainer" parent="MarginContainer/VBoxContainer"] +margin_top = 224.0 +margin_right = 512.0 +margin_bottom = 272.0 +hint_tooltip = "Animations starting with this prefix should be imported with opposite loop configuration. Prefix is removed from animation name." +custom_constants/separation = 10 +__meta__ = { +"_editor_description_": "" +} + +[node name="loop_prefix_label" type="Label" parent="MarginContainer/VBoxContainer/loop"] +margin_right = 512.0 +margin_bottom = 14.0 +mouse_filter = 1 +text = "Loop exception prefix" + +[node name="loop_config_prefix" type="LineEdit" parent="MarginContainer/VBoxContainer/loop"] +margin_top = 24.0 +margin_right = 512.0 +margin_bottom = 48.0 +text = "_" +caret_blink = true +__meta__ = { +"_editor_description_": "" +} + +[node name="layer_ex" type="VBoxContainer" parent="MarginContainer/VBoxContainer"] +margin_top = 292.0 +margin_right = 512.0 +margin_bottom = 340.0 +hint_tooltip = "Exclude layers with name matching this pattern (regex). This is the default value. It can be changed during import." +custom_constants/separation = 10 +__meta__ = { +"_editor_description_": "" +} + +[node name="ex_p_prefix_label" type="Label" parent="MarginContainer/VBoxContainer/layer_ex"] +margin_right = 512.0 +margin_bottom = 14.0 +mouse_filter = 1 +text = "Default layer exclusion pattern" + +[node name="ex_p_config_prefix" type="LineEdit" parent="MarginContainer/VBoxContainer/layer_ex"] +margin_top = 24.0 +margin_right = 512.0 +margin_bottom = 48.0 +caret_blink = true +__meta__ = { +"_editor_description_": "" +} + +[node name="VBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +margin_top = 360.0 +margin_right = 512.0 +margin_bottom = 380.0 +custom_constants/separation = 30 +alignment = 2 + +[node name="close" type="Button" parent="MarginContainer/VBoxContainer/VBoxContainer2"] +margin_left = 394.0 +margin_right = 441.0 +margin_bottom = 20.0 +text = "Close" + +[node name="save" type="Button" parent="MarginContainer/VBoxContainer/VBoxContainer2"] +margin_left = 471.0 +margin_right = 512.0 +margin_bottom = 20.0 +text = "Save" + +[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"] +[connection signal="button_up" from="MarginContainer/VBoxContainer/VBoxContainer2/save" to="." method="_on_save_button_up"] diff --git a/addons/AsepriteWizard/config/result_codes.gd b/addons/AsepriteWizard/config/result_codes.gd new file mode 100644 index 0000000..34df688 --- /dev/null +++ b/addons/AsepriteWizard/config/result_codes.gd @@ -0,0 +1,29 @@ +tool +extends Reference + +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 diff --git a/addons/AsepriteWizard/config/wizard_config.gd b/addons/AsepriteWizard/config/wizard_config.gd new file mode 100644 index 0000000..2003b4b --- /dev/null +++ b/addons/AsepriteWizard/config/wizard_config.gd @@ -0,0 +1,38 @@ +tool +extends Reference + +const WIZARD_CONFIG_MARKER = "aseprite_wizard_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: + config[parts[0].strip_edges()] = parts[1].strip_edges() + 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) + diff --git a/addons/AsepriteWizard/plugin.cfg b/addons/AsepriteWizard/plugin.cfg new file mode 100644 index 0000000..d0b142f --- /dev/null +++ b/addons/AsepriteWizard/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Aseprite Wizard" +description="Import Aseprite files to AnimationPlayers, AnimatedSprites and SpriteFrames." +author="Vinicius Gerevini" +version="4.1.1" +script="plugin.gd" diff --git a/addons/AsepriteWizard/plugin.gd b/addons/AsepriteWizard/plugin.gd new file mode 100644 index 0000000..d80dbd0 --- /dev/null +++ b/addons/AsepriteWizard/plugin.gd @@ -0,0 +1,129 @@ +tool +extends EditorPlugin + +const ConfigDialog = preload('config/config_dialog.tscn') +const WizardWindow = preload("animated_sprite/ASWizardWindow.tscn") +const ImportPlugin = preload("animated_sprite/import_plugin.gd") +const AnimatedSpriteInspectorPlugin = preload("animated_sprite/inspector_plugin.gd") +const SpriteInspectorPlugin = preload("animation_player/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: PanelContainer +var config_window: PopupPanel +var import_plugin : EditorImportPlugin +var sprite_inspector_plugin: EditorInspectorPlugin +var animated_sprite_inspector_plugin: EditorInspectorPlugin + +var _importer_enabled = false + +func _enter_tree(): + _load_config() + _setup_menu_entries() + _setup_importer() + _setup_animated_sprite_inspector_plugin() + _setup_sprite_inspector_plugin() + + +func _exit_tree(): + _remove_menu_entries() + _remove_importer() + _remove_wizard_dock() + _remove_inspector_plugins() + + +func _load_config(): + var editor_gui = get_editor_interface().get_base_control() + config.load_config() + config.set_icon_arrow_down(editor_gui.get_icon("GuiTreeArrowDown", "EditorIcons")) + config.set_icon_arrow_right(editor_gui.get_icon("GuiTreeArrowRight", "EditorIcons")) + + +func _setup_menu_entries(): + add_tool_menu_item(menu_item_name, self, "_open_window") + add_tool_menu_item(config_menu_item_name, self, "_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(): + if (config.is_importer_enabled()): + import_plugin = ImportPlugin.new() + add_import_plugin(import_plugin) + _importer_enabled = true + + +func _remove_importer(): + if _importer_enabled: + remove_import_plugin(import_plugin) + _importer_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(_ud): + if window: + make_bottom_panel_item_visible(window) + return + + window = WizardWindow.instance() + window.init(config, get_editor_interface().get_resource_filesystem()) + window.connect("close_requested", self, "_on_window_closed") + add_control_to_bottom_panel(window, "Aseprite Wizard") + make_bottom_panel_item_visible(window) + + +func _open_config_dialog(_ud): + if is_instance_valid(config_window): + config_window.queue_free() + + config_window = ConfigDialog.instance() + config_window.init(config) + config_window.connect("importer_state_changed", self, "_on_importer_state_changed") + 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 + + +func _on_importer_state_changed(): + if _importer_enabled: + remove_import_plugin(import_plugin) + _importer_enabled = false + else: + import_plugin = ImportPlugin.new() + add_import_plugin(import_plugin) + _importer_enabled = true diff --git a/src/Levels/Plattforms Level.tscn b/src/Levels/Plattforms Level.tscn index 49d4c8c..a10b17d 100644 --- a/src/Levels/Plattforms Level.tscn +++ b/src/Levels/Plattforms Level.tscn @@ -44,16 +44,17 @@ points = PoolVector2Array( 16, 16, 0, 16, 0, 0, 16, 0 ) 0/z_index = 0 [node name="LevelTemplate" type="Node2D"] +position = Vector2( 1, 0 ) __meta__ = { "_edit_horizontal_guides_": [ 464.0 ], "_edit_vertical_guides_": [ 2880.0 ] } -[node name="UserInterface" parent="." instance=ExtResource( 7 )] - [node name="Simple Background" parent="." instance=ExtResource( 4 )] layer = -1 +[node name="UserInterface" parent="." instance=ExtResource( 7 )] + [node name="TileMap" type="TileMap" parent="."] tile_set = SubResource( 4 ) cell_size = Vector2( 16, 16 ) @@ -68,7 +69,7 @@ tile_data = PoolIntArray( 0, 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, position = Vector2( 50.7867, 604.063 ) [node name="Spring4" parent="." instance=ExtResource( 3 )] -position = Vector2( 170, 600.198 ) +position = Vector2( 159, 600 ) scale = Vector2( 1.88002, 1 ) [node name="Track" parent="." instance=ExtResource( 5 )] @@ -79,7 +80,7 @@ scale = Vector2( 2.83999, 0.56 ) position = Vector2( 25.0812, 0 ) [node name="Turret" parent="." instance=ExtResource( 6 )] -position = Vector2( 479, 466 ) +position = Vector2( 479, 464 ) scale = Vector2( 0.4, 0.4 ) [editable path="Spring4"]