Ignoring Addons Folder

This commit is contained in:
Jakob Feldmann 2022-06-13 20:42:24 +02:00
parent ad5c9d6d78
commit 6c3be182aa
21 changed files with 2959 additions and 4 deletions

1
.gitignore vendored
View File

@ -14,6 +14,7 @@ Thumbs.db
*.lnk *.lnk
# Godot-specific ignores # Godot-specific ignores
.import/ .import/
.addons/
export.cfg export.cfg
export_presets.cfg export_presets.cfg

View File

@ -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"]

View File

@ -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()

View File

@ -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"]

View File

@ -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

View File

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

View File

@ -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

View File

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

View File

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

View File

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

View File

@ -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()

View File

@ -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"]

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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"]

View File

@ -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

View File

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

View File

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

View File

@ -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

View File

@ -44,16 +44,17 @@ points = PoolVector2Array( 16, 16, 0, 16, 0, 0, 16, 0 )
0/z_index = 0 0/z_index = 0
[node name="LevelTemplate" type="Node2D"] [node name="LevelTemplate" type="Node2D"]
position = Vector2( 1, 0 )
__meta__ = { __meta__ = {
"_edit_horizontal_guides_": [ 464.0 ], "_edit_horizontal_guides_": [ 464.0 ],
"_edit_vertical_guides_": [ 2880.0 ] "_edit_vertical_guides_": [ 2880.0 ]
} }
[node name="UserInterface" parent="." instance=ExtResource( 7 )]
[node name="Simple Background" parent="." instance=ExtResource( 4 )] [node name="Simple Background" parent="." instance=ExtResource( 4 )]
layer = -1 layer = -1
[node name="UserInterface" parent="." instance=ExtResource( 7 )]
[node name="TileMap" type="TileMap" parent="."] [node name="TileMap" type="TileMap" parent="."]
tile_set = SubResource( 4 ) tile_set = SubResource( 4 )
cell_size = Vector2( 16, 16 ) 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 ) position = Vector2( 50.7867, 604.063 )
[node name="Spring4" parent="." instance=ExtResource( 3 )] [node name="Spring4" parent="." instance=ExtResource( 3 )]
position = Vector2( 170, 600.198 ) position = Vector2( 159, 600 )
scale = Vector2( 1.88002, 1 ) scale = Vector2( 1.88002, 1 )
[node name="Track" parent="." instance=ExtResource( 5 )] [node name="Track" parent="." instance=ExtResource( 5 )]
@ -79,7 +80,7 @@ scale = Vector2( 2.83999, 0.56 )
position = Vector2( 25.0812, 0 ) position = Vector2( 25.0812, 0 )
[node name="Turret" parent="." instance=ExtResource( 6 )] [node name="Turret" parent="." instance=ExtResource( 6 )]
position = Vector2( 479, 466 ) position = Vector2( 479, 464 )
scale = Vector2( 0.4, 0.4 ) scale = Vector2( 0.4, 0.4 )
[editable path="Spring4"] [editable path="Spring4"]