feat: player movement

This commit is contained in:
newt 2024-08-17 15:22:33 +01:00
commit 183ef59826
104 changed files with 8997 additions and 0 deletions

2
.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

BIN
Layer 2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

34
Layer 2.png.import Normal file
View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bfvlix1nyb3j5"
path="res://.godot/imported/Layer 2.png-ac1279a7a9bf1062890d6c96fd5ce0c8.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Layer 2.png"
dest_files=["res://.godot/imported/Layer 2.png-ac1279a7a9bf1062890d6c96fd5ce0c8.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
Layer 3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

34
Layer 3.png.import Normal file
View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cxdjxd2sv21hu"
path="res://.godot/imported/Layer 3.png-cd7bea8c5ddca19297a526bc9b9b3d56.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Layer 3.png"
dest_files=["res://.godot/imported/Layer 3.png-cd7bea8c5ddca19297a526bc9b9b3d56.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

222
Player.tscn Normal file
View file

@ -0,0 +1,222 @@
[gd_scene load_steps=30 format=3 uid="uid://cjiuycwqqxaxn"]
[ext_resource type="Script" path="res://player.gd" id="1_oy25y"]
[ext_resource type="Texture2D" uid="uid://cxdjxd2sv21hu" path="res://Layer 3.png" id="2_u5vg1"]
[sub_resource type="AtlasTexture" id="AtlasTexture_8d47g"]
atlas = ExtResource("2_u5vg1")
region = Rect2(0, 0, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_e4ysm"]
atlas = ExtResource("2_u5vg1")
region = Rect2(32, 0, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_4rhkl"]
atlas = ExtResource("2_u5vg1")
region = Rect2(64, 0, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_arcxo"]
atlas = ExtResource("2_u5vg1")
region = Rect2(96, 0, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_00jl2"]
atlas = ExtResource("2_u5vg1")
region = Rect2(128, 0, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_0hb6a"]
atlas = ExtResource("2_u5vg1")
region = Rect2(0, 64, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_yn35a"]
atlas = ExtResource("2_u5vg1")
region = Rect2(32, 64, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_f27hx"]
atlas = ExtResource("2_u5vg1")
region = Rect2(64, 64, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_si5lp"]
atlas = ExtResource("2_u5vg1")
region = Rect2(96, 64, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_mmtke"]
atlas = ExtResource("2_u5vg1")
region = Rect2(128, 64, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_dkmnd"]
atlas = ExtResource("2_u5vg1")
region = Rect2(0, 128, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_cermf"]
atlas = ExtResource("2_u5vg1")
region = Rect2(32, 128, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_hdhbl"]
atlas = ExtResource("2_u5vg1")
region = Rect2(64, 128, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_frlcl"]
atlas = ExtResource("2_u5vg1")
region = Rect2(96, 128, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_rkycb"]
atlas = ExtResource("2_u5vg1")
region = Rect2(128, 128, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_kgrgd"]
atlas = ExtResource("2_u5vg1")
region = Rect2(0, 192, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_ulb1s"]
atlas = ExtResource("2_u5vg1")
region = Rect2(32, 192, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_cpo0y"]
atlas = ExtResource("2_u5vg1")
region = Rect2(64, 192, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_51m40"]
atlas = ExtResource("2_u5vg1")
region = Rect2(96, 192, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_aqja2"]
atlas = ExtResource("2_u5vg1")
region = Rect2(128, 192, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_m750d"]
atlas = ExtResource("2_u5vg1")
region = Rect2(0, 256, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_psi6a"]
atlas = ExtResource("2_u5vg1")
region = Rect2(32, 256, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_0s74t"]
atlas = ExtResource("2_u5vg1")
region = Rect2(64, 256, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_qjvpa"]
atlas = ExtResource("2_u5vg1")
region = Rect2(96, 256, 32, 64)
[sub_resource type="AtlasTexture" id="AtlasTexture_fveui"]
atlas = ExtResource("2_u5vg1")
region = Rect2(128, 256, 32, 64)
[sub_resource type="SpriteFrames" id="SpriteFrames_iat2j"]
animations = [{
"frames": [{
"duration": 1.0,
"texture": SubResource("AtlasTexture_8d47g")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_e4ysm")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_4rhkl")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_arcxo")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_00jl2")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_0hb6a")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_yn35a")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_f27hx")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_si5lp")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_mmtke")
}],
"loop": true,
"name": &"idle",
"speed": 10.0
}, {
"frames": [{
"duration": 1.0,
"texture": SubResource("AtlasTexture_dkmnd")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_cermf")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_hdhbl")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_frlcl")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_rkycb")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_kgrgd")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_ulb1s")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_cpo0y")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_51m40")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_aqja2")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_m750d")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_psi6a")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_0s74t")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_qjvpa")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_fveui")
}],
"loop": true,
"name": &"walk",
"speed": 10.0
}]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_0on8q"]
size = Vector2(28, 64)
[node name="Player" type="CharacterBody2D"]
script = ExtResource("1_oy25y")
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."]
texture_filter = 1
sprite_frames = SubResource("SpriteFrames_iat2j")
animation = &"walk"
frame_progress = 0.300851
metadata/_aseprite_wizard_config_ = {
"layer": "Layer 3",
"o_ex_p": "",
"o_folder": "",
"o_name": "",
"only_visible": false,
"slice": "",
"source": "res://assets/PlayerCharacter.aseprite"
}
metadata/_aseprite_wizard_source_file_hash_ = "c2f10613af41076ed14f7dd74dccbea5"
metadata/_aseprite_wizard_interface_config_ = {
"layer_section": true
}
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(2, 0)
shape = SubResource("RectangleShape2D_0on8q")
debug_color = Color(1, 0.254902, 0.415686, 0.105882)

BIN
PlayerCharacter.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dvnohr8gjh56t"
path="res://.godot/imported/PlayerCharacter.png-ab2ae3be5ec39b25693dfd99826d05ab.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://PlayerCharacter.png"
dest_files=["res://.godot/imported/PlayerCharacter.png-ab2ae3be5ec39b25693dfd99826d05ab.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

23
World.tscn Normal file
View file

@ -0,0 +1,23 @@
[gd_scene load_steps=4 format=3 uid="uid://cei5gjgfoginb"]
[ext_resource type="PackedScene" uid="uid://cjiuycwqqxaxn" path="res://Player.tscn" id="1_4tauq"]
[sub_resource type="QuadMesh" id="QuadMesh_seics"]
size = Vector2(480, 20)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_h5wua"]
size = Vector2(480, 20)
[node name="World" type="Node2D"]
position = Vector2(272, 240)
[node name="Player" parent="." instance=ExtResource("1_4tauq")]
position = Vector2(-37, -59)
[node name="Floor" type="StaticBody2D" parent="."]
[node name="MeshInstance2D" type="MeshInstance2D" parent="Floor"]
mesh = SubResource("QuadMesh_seics")
[node name="CollisionShape2D" type="CollisionShape2D" parent="Floor"]
shape = SubResource("RectangleShape2D_h5wua")

View file

@ -0,0 +1,280 @@
@tool
extends RefCounted
var _config = preload("../config/config.gd").new()
#
# Output:
# {
# "data_file": file path to the json file
# "sprite_sheet": file path to the raw image file
# }
func export_file(file_name: String, output_folder: String, options: Dictionary) -> Dictionary:
var exception_pattern = options.get('exception_pattern', "")
var only_visible_layers = options.get('only_visible_layers', false)
var output_name = file_name if options.get('output_filename') == "" else options.get('output_filename', file_name)
var first_frame_only = options.get("first_frame_only", false)
var basename = _get_file_basename(output_name)
var output_dir = ProjectSettings.globalize_path(output_folder)
var data_file = "%s/%s.json" % [output_dir, basename]
var sprite_sheet = "%s/%s.png" % [output_dir, basename]
var output = []
var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet)
if not only_visible_layers:
arguments.push_front("--all-layers")
if first_frame_only:
arguments.push_front("'[0, 0]'")
arguments.push_front("--frame-range")
_add_sheet_type_arguments(arguments, options)
_add_ignore_layer_arguments(file_name, arguments, exception_pattern)
var local_sprite_sheet_path = ProjectSettings.localize_path(sprite_sheet)
var is_new = not ResourceLoader.exists(local_sprite_sheet_path)
var exit_code = _execute(arguments, output)
if exit_code != 0:
printerr('aseprite: failed to export spritesheet')
printerr(output)
return {}
return {
"data_file": ProjectSettings.localize_path(data_file),
"sprite_sheet": local_sprite_sheet_path,
"is_first_import": is_new,
}
func export_layers(file_name: String, output_folder: String, options: Dictionary) -> Array:
var exception_pattern = options.get('exception_pattern', "")
var only_visible_layers = options.get('only_visible_layers', false)
var basename = _get_file_basename(file_name)
var layers = list_layers(file_name, only_visible_layers)
var exception_regex = _compile_regex(exception_pattern)
var output = []
for layer in layers:
if layer != "" and (not exception_regex or exception_regex.search(layer) == null):
output.push_back(export_layer(file_name, layer, output_folder, options))
return output
func export_layer(file_name: String, layer_name: String, output_folder: String, options: Dictionary) -> Dictionary:
var output_prefix = options.get('output_filename', "").strip_edges()
var output_dir = output_folder.replace("res://", "./").strip_edges()
var data_file = "%s/%s%s.json" % [output_dir, output_prefix, layer_name]
var sprite_sheet = "%s/%s%s.png" % [output_dir, output_prefix, layer_name]
var first_frame_only = options.get("first_frame_only", false)
var output = []
var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet)
arguments.push_front(layer_name)
arguments.push_front("--layer")
if first_frame_only:
arguments.push_front("'[0, 0]'")
arguments.push_front("--frame-range")
_add_sheet_type_arguments(arguments, options)
var local_sprite_sheet_path = ProjectSettings.localize_path(sprite_sheet)
var is_new = not ResourceLoader.exists(local_sprite_sheet_path)
var exit_code = _execute(arguments, output)
if exit_code != 0:
print('aseprite: failed to export layer spritesheet')
print(output)
return {}
return {
"data_file": ProjectSettings.localize_path(data_file),
"sprite_sheet": local_sprite_sheet_path,
"is_first_import": is_new,
}
func _add_ignore_layer_arguments(file_name: String, arguments: Array, exception_pattern: String):
var layers = _get_exception_layers(file_name, exception_pattern)
if not layers.is_empty():
for l in layers:
arguments.push_front(l)
arguments.push_front('--ignore-layer')
func _add_sheet_type_arguments(arguments: Array, options : Dictionary):
var column_count : int = options.get("column_count", 0)
if column_count > 0:
arguments.push_back("--merge-duplicates") # Yes, this is undocumented
arguments.push_back("--sheet-columns")
arguments.push_back(column_count)
else:
arguments.push_back("--sheet-pack")
func _get_exception_layers(file_name: String, exception_pattern: String) -> Array:
var layers = list_layers(file_name)
var regex = _compile_regex(exception_pattern)
if regex == null:
return []
var exception_layers = []
for layer in layers:
if regex.search(layer) != null:
exception_layers.push_back(layer)
return exception_layers
func list_layers(file_name: String, only_visible = false) -> Array:
var output = []
var arguments = ["-b", "--list-layers", file_name]
if not only_visible:
arguments.push_front("--all-layers")
var exit_code = _execute(arguments, output)
if exit_code != 0:
printerr('aseprite: failed listing layers')
printerr(output)
return []
if output.is_empty():
return output
var raw = output[0].split('\n')
var sanitized = []
for s in raw:
sanitized.append(s.strip_edges())
return sanitized
func list_slices(file_name: String) -> Array:
var output = []
var arguments = ["-b", "--list-slices", file_name]
var exit_code = _execute(arguments, output)
if exit_code != 0:
printerr('aseprite: failed listing slices')
printerr(output)
return []
if output.is_empty():
return output
var raw = output[0].split('\n')
var sanitized = []
for s in raw:
sanitized.append(s.strip_edges())
return sanitized
func _export_command_common_arguments(source_name: String, data_path: String, spritesheet_path: String) -> Array:
return [
"-b",
"--list-tags",
"--list-slices",
"--data",
data_path,
"--format",
"json-array",
"--sheet",
spritesheet_path,
source_name
]
func _execute(arguments, output):
return OS.execute(_aseprite_command(), arguments, output, true, true)
func _aseprite_command() -> String:
return _config.is_command_or_control_pressed()
func _get_file_basename(file_path: String) -> String:
return file_path.get_file().trim_suffix('.%s' % file_path.get_extension())
func _compile_regex(pattern):
if pattern == "":
return
var rgx = RegEx.new()
if rgx.compile(pattern) == OK:
return rgx
printerr('exception regex error')
func test_command():
var exit_code = OS.execute(_aseprite_command(), ['--version'], [], true)
return exit_code == 0
func is_valid_spritesheet(content):
return content.has("frames") and content.has("meta") and content.meta.has('image')
func get_content_frames(content):
return content.frames if typeof(content.frames) == TYPE_ARRAY else content.frames.values()
func get_slice_rect(content: Dictionary, slice_name: String) -> Variant:
if not content.has("meta") or not content.meta.has("slices"):
return null
for slice in content.meta.slices:
if slice.name == slice_name:
if slice.keys.size() > 0:
var p = slice.keys[0].bounds
return Rect2(p.x, p.y, p.w, p.h)
return null
##
## Exports tileset layers
##
## Return (dictionary):
## data_file: path to aseprite generated JSON file
## sprite_sheet: localized path to spritesheet file
func export_tileset_texture(file_name: String, output_folder: String, options: Dictionary) -> Dictionary:
var exception_pattern = options.get('exception_pattern', "")
var only_visible_layers = options.get('only_visible_layers', false)
var output_name = file_name if options.get('output_filename') == "" else options.get('output_filename', file_name)
var basename = _get_file_basename(output_name)
var output_dir = ProjectSettings.globalize_path(output_folder)
var data_path = "%s/%s.json" % [output_dir, basename]
var sprite_sheet = "%s/%s.png" % [output_dir, basename]
var output = []
var arguments = [
"-b",
"--export-tileset",
"--data",
data_path,
"--format",
"json-array",
"--sheet",
sprite_sheet,
file_name
]
if not only_visible_layers:
arguments.push_front("--all-layers")
_add_ignore_layer_arguments(file_name, arguments, exception_pattern)
var exit_code = _execute(arguments, output)
if exit_code != 0:
printerr('aseprite: failed to export spritesheet')
printerr(output)
return {}
return {
"data_file": ProjectSettings.localize_path(data_path),
"sprite_sheet": ProjectSettings.localize_path(sprite_sheet)
}

View file

@ -0,0 +1,143 @@
@tool
extends RefCounted
var result_code = preload("../config/result_codes.gd")
var _aseprite = preload("aseprite.gd").new()
enum {
FILE_EXPORT_MODE,
LAYERS_EXPORT_MODE
}
##
## Generate Aseprite spritesheet and data files for source.
##
## Options:
## output_folder (string)
## output_filename (string, optional)
## export_mode (FILE_EXPORT_MODE, LAYERS_EXPORT_MODE) default: FILE_EXPORT_MODE
## exception_pattern (string, optional)
## only_visible_layers (boolean, optional)
##
## Return:
## Array
## Dictionary
## sprite_sheet: sprite sheet path
## data_file: json file path
##
func generate_aseprite_files(source_file: String, options: Dictionary):
var check = _initial_checks(source_file, options)
if check != result_code.SUCCESS:
return result_code.error(check)
match options.get('export_mode', FILE_EXPORT_MODE):
FILE_EXPORT_MODE:
var output = _aseprite.export_file(source_file, options.output_folder, options)
if output.is_empty():
return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED)
return result_code.result([output])
LAYERS_EXPORT_MODE:
var output = _aseprite.export_layers(source_file, options.output_folder, options)
if output.is_empty():
return result_code.error(result_code.ERR_NO_VALID_LAYERS_FOUND)
return result_code.result(output)
_:
return result_code.error(result_code.ERR_UNKNOWN_EXPORT_MODE)
##
## Generate Aseprite spritesheet and data file for source.
##
## Options:
## output_folder (string)
## output_filename (string, optional)
## layer (string, optional)
## exception_pattern (string, optional)
## only_visible_layers (boolean, optional)
##
## Return:
## Dictionary
## sprite_sheet: sprite sheet path
## data_file: json file path
##
func generate_aseprite_file(source_file: String, options: Dictionary) -> Dictionary:
var check = _initial_checks(source_file, options)
if check != result_code.SUCCESS:
return result_code.error(check)
var output
if options.get("layer", "") == "":
output = _aseprite.export_file(source_file, options.output_folder, options)
else:
output = _aseprite.export_layer(source_file, options.layer, options.output_folder, options)
if output.is_empty():
return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED)
return result_code.result(output)
##
## Generate a spritesheet with all tilesets in the file
##
## Options:
## exception_pattern (string)
## only_visible_layers (boolean)
## output_filename (string)
## output_folder (string)
##
## Return:
## Dictionary
## sprite_sheet: sprite sheet path
## data_file: json file path
##
func generate_tileset_files(source_file: String, options = {}) -> Dictionary:
var check = _initial_checks(source_file, options)
if check != result_code.SUCCESS:
return result_code.error(check)
var output = _aseprite.export_tileset_texture(source_file, options.output_folder, options)
if output.is_empty():
return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED)
return result_code.result(output)
##
## Perform initial source file and output folder checks
##
func _initial_checks(source: String, options: Dictionary) -> int:
if not _aseprite.test_command():
return result_code.ERR_ASEPRITE_CMD_NOT_FOUND
if not FileAccess.file_exists(source):
return result_code.ERR_SOURCE_FILE_NOT_FOUND
if not DirAccess.dir_exists_absolute(options.output_folder):
return result_code.ERR_OUTPUT_FOLDER_NOT_FOUND
return result_code.SUCCESS
##
## Load Aseprite source data file and fails if the
## content is not valid
##
func load_json_content(source_file: String) -> Dictionary:
var file = FileAccess.open(source_file, FileAccess.READ)
if file == null:
return result_code.error(FileAccess.get_open_error())
var test_json_conv = JSON.new()
test_json_conv.parse(file.get_as_text())
var content = test_json_conv.get_data()
if not _aseprite.is_valid_spritesheet(content):
return result_code.error(result_code.ERR_INVALID_ASEPRITE_SPRITESHEET)
return result_code.result(content)

View file

@ -0,0 +1,261 @@
@tool
extends RefCounted
# GLOBAL SETTINGS
const _CONFIG_SECTION_KEY = 'aseprite'
const _COMMAND_KEY = 'aseprite/general/command_path'
# PROJECT SETTINGS
# animation import defaults
const _DEFAULT_EXCLUSION_PATTERN_KEY = 'aseprite/animation/layers/exclusion_pattern'
const _DEFAULT_ONLY_VISIBLE_LAYERS = 'aseprite/animation/layers/only_include_visible_layers_by_default'
const _DEFAULT_LOOP_EX_PREFIX = '_'
const _LOOP_ENABLED = 'aseprite/animation/loop/enabled'
const _LOOP_EXCEPTION_PREFIX = 'aseprite/animation/loop/exception_prefix'
const _USE_METADATA = 'aseprite/animation/storage/use_metadata'
# cleanup
const _REMOVE_SOURCE_FILES_KEY = 'aseprite/import/cleanup/remove_json_file'
const _SET_VISIBLE_TRACK_AUTOMATICALLY = 'aseprite/import/cleanup/automatically_hide_sprites_not_in_animation'
# automatic importer
const _IMPORTER_ENABLE_KEY = 'aseprite/import/import_plugin/enable_automatic_importer'
const _DEFAULT_IMPORTER_KEY = 'aseprite/import/import_plugin/default_automatic_importer'
const IMPORTER_SPRITEFRAMES_NAME = "SpriteFrames"
const IMPORTER_NOOP_NAME = "No Import"
const IMPORTER_TILESET_TEXTURE_NAME = "Tileset Texture"
const IMPORTER_STATIC_TEXTURE_NAME = "Static Texture"
# wizard history
const _WIZARD_HISTORY = "wizard_history"
const _HISTORY_MAX_ENTRIES = 'aseprite/wizard/history/max_history_entries'
const _HISTORY_DEFAULT_MAX_ENTRIES = 100
## DEPRECATED (v7.4.0): remove in a next major version
const _HISTORY_CONFIG_FILE_CFG_KEY = 'aseprite/wizard/history/cache_file_path'
## DEPRECATED (v7.4.0): remove in a next major version
const _DEFAULT_HISTORY_CONFIG_FILE_PATH = 'res://.aseprite_wizard_history'
# SpriteFrames import last config
const _STANDALONE_SPRITEFRAMES_LAST_IMPORT_CFG = "standalone_sf_last_import_cfg"
# export
const _EXPORTER_ENABLE_KEY = 'aseprite/animation/storage/enable_metadata_removal_on_export'
var _editor_settings: EditorSettings = EditorInterface.get_editor_settings()
#######################################################
# GLOBAL CONFIGS
######################################################
func default_command() -> String:
match OS.get_name():
"Windows":
return "C:\\\\Steam\\steamapps\\common\\Aseprite\\aseprite.exe"
"macOS":
return "/Applications/Aseprite.app/Contents/MacOS/aseprite"
_:
return 'aseprite'
func is_command_or_control_pressed() -> String:
var command = _editor_settings.get(_COMMAND_KEY) if _editor_settings.has_setting(_COMMAND_KEY) else ""
return command if command != "" else default_command()
#######################################################
# PROJECT SETTINGS
######################################################
# remove this config in the next major version
func is_importer_enabled() -> bool:
return _get_project_setting(_IMPORTER_ENABLE_KEY, false)
func get_default_importer() -> String:
return _get_project_setting(_DEFAULT_IMPORTER_KEY, IMPORTER_SPRITEFRAMES_NAME if is_importer_enabled() else IMPORTER_NOOP_NAME)
func is_exporter_enabled() -> bool:
return _get_project_setting(_EXPORTER_ENABLE_KEY, true)
func should_remove_source_files() -> bool:
return _get_project_setting(_REMOVE_SOURCE_FILES_KEY, true)
func is_default_animation_loop_enabled() -> bool:
return _get_project_setting(_LOOP_ENABLED, true)
func get_animation_loop_exception_prefix() -> String:
return _get_project_setting(_LOOP_EXCEPTION_PREFIX, _DEFAULT_LOOP_EX_PREFIX)
func is_use_metadata_enabled() -> bool:
return _get_project_setting(_USE_METADATA, true)
func get_default_exclusion_pattern() -> String:
return _get_project_setting(_DEFAULT_EXCLUSION_PATTERN_KEY, "")
func should_include_only_visible_layers_by_default() -> bool:
return _get_project_setting(_DEFAULT_ONLY_VISIBLE_LAYERS, false)
func get_history_max_entries() -> int:
return _get_project_setting(_HISTORY_MAX_ENTRIES, _HISTORY_DEFAULT_MAX_ENTRIES)
func get_import_history() -> Array:
return get_plugin_metadata(_WIZARD_HISTORY, [])
func get_old_import_history() -> Array:
var history = []
var history_path := _get_history_file_path()
if not FileAccess.file_exists(history_path):
return history
var file_object = FileAccess.open(history_path, FileAccess.READ)
while not file_object.eof_reached():
var line = file_object.get_line()
if line:
var test_json_conv = JSON.new()
test_json_conv.parse(line)
history.push_back(test_json_conv.get_data())
return history
func is_set_visible_track_automatically_enabled() -> bool:
return _get_project_setting(_SET_VISIBLE_TRACK_AUTOMATICALLY, false)
func save_import_history(history: Array):
set_plugin_metadata(_WIZARD_HISTORY, history)
## DEPRECATED
func _get_history_file_path() -> String:
return _get_project_setting(_HISTORY_CONFIG_FILE_CFG_KEY, _DEFAULT_HISTORY_CONFIG_FILE_PATH)
## used for old history migration. Should be removed together with the history cleanup
func has_old_history() -> bool:
return ProjectSettings.has_setting(_HISTORY_CONFIG_FILE_CFG_KEY) or FileAccess.file_exists(_DEFAULT_HISTORY_CONFIG_FILE_PATH)
## used for old history migration. Should be removed together with the history cleanup
func remove_old_history_setting() -> void:
DirAccess.remove_absolute(_get_history_file_path())
if ProjectSettings.has_setting(_HISTORY_CONFIG_FILE_CFG_KEY):
ProjectSettings.clear(_HISTORY_CONFIG_FILE_CFG_KEY)
#=========================================================
# IMPORT CONFIGS
#=========================================================
## Return config for last import done via standalone SpriteFrames import dock
func get_standalone_spriteframes_last_import_config() -> Dictionary:
return get_plugin_metadata(_STANDALONE_SPRITEFRAMES_LAST_IMPORT_CFG, {})
## Set config for last import done via standalone SpriteFrames import dock
func set_standalone_spriteframes_last_import_config(data: Dictionary) -> void:
set_plugin_metadata(_STANDALONE_SPRITEFRAMES_LAST_IMPORT_CFG, data)
func clear_standalone_spriteframes_last_import_config() -> void:
set_plugin_metadata(_STANDALONE_SPRITEFRAMES_LAST_IMPORT_CFG, {})
func get_plugin_metadata(key: String, default: Variant = null) -> Variant:
return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, key, default)
func set_plugin_metadata(key: String, data: Variant):
_editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, key, data)
#######################################################
# INITIALIZATION
######################################################
func initialize_project_settings():
_initialize_project_cfg(_DEFAULT_EXCLUSION_PATTERN_KEY, "", TYPE_STRING)
_initialize_project_cfg(_DEFAULT_ONLY_VISIBLE_LAYERS, false, TYPE_BOOL)
_initialize_project_cfg(_LOOP_ENABLED, true, TYPE_BOOL)
_initialize_project_cfg(_LOOP_EXCEPTION_PREFIX, _DEFAULT_LOOP_EX_PREFIX, TYPE_STRING)
_initialize_project_cfg(_USE_METADATA, true, TYPE_BOOL)
_initialize_project_cfg(_REMOVE_SOURCE_FILES_KEY, true, TYPE_BOOL)
_initialize_project_cfg(
_DEFAULT_IMPORTER_KEY,
IMPORTER_SPRITEFRAMES_NAME if is_importer_enabled() else IMPORTER_NOOP_NAME,
TYPE_STRING,
PROPERTY_HINT_ENUM,
"%s,%s,%s,%s" % [IMPORTER_NOOP_NAME, IMPORTER_SPRITEFRAMES_NAME, IMPORTER_TILESET_TEXTURE_NAME, IMPORTER_STATIC_TEXTURE_NAME]
)
_initialize_project_cfg(_EXPORTER_ENABLE_KEY, true, TYPE_BOOL)
# TODO remove (history max entries)
#_initialize_project_cfg(_HISTORY_CONFIG_FILE_CFG_KEY, _DEFAULT_HISTORY_CONFIG_FILE_PATH, TYPE_STRING, PROPERTY_HINT_GLOBAL_FILE)
_initialize_project_cfg(_HISTORY_MAX_ENTRIES, _HISTORY_DEFAULT_MAX_ENTRIES, TYPE_INT)
_initialize_project_cfg(_SET_VISIBLE_TRACK_AUTOMATICALLY, false, TYPE_BOOL)
ProjectSettings.save()
_initialize_editor_cfg(_COMMAND_KEY, default_command(), TYPE_STRING)
func clear_project_settings():
var _all_settings = [
_DEFAULT_EXCLUSION_PATTERN_KEY,
_LOOP_ENABLED,
_LOOP_EXCEPTION_PREFIX,
_USE_METADATA,
_REMOVE_SOURCE_FILES_KEY,
_DEFAULT_IMPORTER_KEY,
_EXPORTER_ENABLE_KEY,
_HISTORY_MAX_ENTRIES,
_SET_VISIBLE_TRACK_AUTOMATICALLY,
_DEFAULT_ONLY_VISIBLE_LAYERS,
]
for key in _all_settings:
ProjectSettings.clear(key)
ProjectSettings.save()
func _initialize_project_cfg(key: String, default_value, type: int, hint: int = PROPERTY_HINT_NONE, hint_string = null):
if not ProjectSettings.has_setting(key):
ProjectSettings.set(key, default_value)
ProjectSettings.set_initial_value(key, default_value)
ProjectSettings.add_property_info({
"name": key,
"type": type,
"hint": hint,
"hint_string": hint_string,
})
func _get_project_setting(key: String, default_value):
if not ProjectSettings.has_setting(key):
return default_value
var p = ProjectSettings.get(key)
return p if p != null else default_value
func _initialize_editor_cfg(key: String, default_value, type: int, hint: int = PROPERTY_HINT_NONE):
if not _editor_settings.has_setting(key):
_editor_settings.set(key, default_value)
_editor_settings.set_initial_value(key, default_value, false)
_editor_settings.add_property_info({
"name": key,
"type": type,
"hint": hint,
})

View file

@ -0,0 +1,29 @@
@tool
extends PopupPanel
var _config = preload("./config.gd").new()
@onready var _aseprite_command_field = $MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer/aseprite_command
@onready var _version_label = $MarginContainer/VBoxContainer/VBoxContainer/version_found
func _ready():
_aseprite_command_field.text = _config.is_command_or_control_pressed()
_version_label.modulate.a = 0
func _on_close_button_up():
self.hide()
func _on_test_pressed():
var output = []
if _test_command(output):
_version_label.text = "%s found." % "\n".join(PackedStringArray(output)).strip_edges()
else:
_version_label.text = "Command not found."
_version_label.modulate.a = 1
func _test_command(output):
var exit_code = OS.execute(_aseprite_command_field.text, ['--version'], output, true, true)
return exit_code == 0

View file

@ -0,0 +1,80 @@
[gd_scene load_steps=2 format=3 uid="uid://d0whlywijwa6s"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/config/config_dialog.gd" id="1"]
[node name="config_dialog" type="PopupPanel"]
title = "Aseprite Wizard Config"
size = Vector2i(624, 236)
visible = true
unresizable = false
borderless = false
min_size = Vector2i(624, 236)
content_scale_mode = 1
script = ExtResource("1")
[node name="MarginContainer" type="MarginContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 4.0
offset_top = 4.0
offset_right = -532.0
offset_bottom = -416.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
text = "This configuration moved.
- To edit the aseprite command path, go to Editor > Editor Settings > Aseprite.
- To edit project specific settings, go to Project > Project Settings > Aseprite."
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="Aseprite Command" type="Label" parent="MarginContainer/VBoxContainer/VBoxContainer"]
layout_mode = 2
tooltip_text = "Define the path for Aseprite command"
mouse_filter = 1
text = "Aseprite Command Path"
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="aseprite_command" type="LineEdit" parent="MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
editable = false
caret_blink = true
[node name="test" type="Button" parent="MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "Test"
[node name="version_found" type="Label" parent="MarginContainer/VBoxContainer/VBoxContainer"]
modulate = Color(1, 1, 1, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "Aseprite version found"
[node name="VBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
alignment = 2
[node name="close" type="Button" parent="MarginContainer/VBoxContainer/VBoxContainer2"]
layout_mode = 2
text = "Close"
[connection signal="pressed" from="MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer/test" to="." method="_on_test_pressed"]
[connection signal="button_up" from="MarginContainer/VBoxContainer/VBoxContainer2/close" to="." method="_on_close_button_up"]

View file

@ -0,0 +1,37 @@
@tool
extends RefCounted
const SUCCESS = 0
const ERR_ASEPRITE_CMD_NOT_FOUND = 1
const ERR_SOURCE_FILE_NOT_FOUND = 2
const ERR_OUTPUT_FOLDER_NOT_FOUND = 3
const ERR_ASEPRITE_EXPORT_FAILED = 4
const ERR_UNKNOWN_EXPORT_MODE = 5
const ERR_NO_VALID_LAYERS_FOUND = 6
const ERR_INVALID_ASEPRITE_SPRITESHEET = 7
static func get_error_message(code: int):
match code:
ERR_ASEPRITE_CMD_NOT_FOUND:
return "Aseprite command failed. Please, check if the right command is in your PATH or configured through \"Editor > Editor Settings > Aseprite > General > Command Path\"."
ERR_SOURCE_FILE_NOT_FOUND:
return "source file does not exist"
ERR_OUTPUT_FOLDER_NOT_FOUND:
return "output location does not exist"
ERR_ASEPRITE_EXPORT_FAILED:
return "unable to import file"
ERR_INVALID_ASEPRITE_SPRITESHEET:
return "aseprite generated bad data file"
ERR_NO_VALID_LAYERS_FOUND:
return "no valid layers found"
_:
return "import failed with code %d" % code
static func error(error_code: int):
return { "code": error_code, "content": null, "is_ok": false }
static func result(result):
return { "code": SUCCESS, "content": result, "is_ok": true }

View file

@ -0,0 +1,85 @@
@tool
extends RefCounted
const WIZARD_CONFIG_META_NAME = "_aseprite_wizard_config_"
const WIZARD_CONFIG_MARKER = "aseprite_wizard_config"
const WIZARD_INTERFACE_CONFIG_META_NAME = "_aseprite_wizard_interface_config_"
const SOURCE_FILE_HASH_META_NAME = "_aseprite_wizard_source_file_hash_"
const SEPARATOR = "|="
static func encode(object: Dictionary):
var text = "%s\n" % WIZARD_CONFIG_MARKER
for prop in object:
text += "%s%s%s\n" % [prop, SEPARATOR, object[prop]]
return Marshalls.utf8_to_base64(text)
static func decode(string: String):
var decoded = _decode_base64(string)
if not _is_wizard_config(decoded):
return null
var cfg = decoded.split("\n")
var config = {}
for c in cfg:
var parts = c.split(SEPARATOR, 1)
if parts.size() == 2:
var key = parts[0].strip_edges()
var value = parts[1].strip_edges()
#Convert bool properties
if key == "only_visible" or key == "op_exp":
match value:
"True":
config[key] = true
"False":
config[key] = false
_:
config[key] = false
else:
config[key] = value
return config
static func _decode_base64(string: String):
if string != "":
return Marshalls.base64_to_utf8(string)
return null
static func _is_wizard_config(cfg) -> bool:
return cfg != null and cfg.begins_with(WIZARD_CONFIG_MARKER)
static func load_config(node: Object):
if node.has_meta(WIZARD_CONFIG_META_NAME):
return node.get_meta(WIZARD_CONFIG_META_NAME)
return decode(node.editor_description)
static func save_config(node: Object, cfg: Dictionary):
node.set_meta(WIZARD_CONFIG_META_NAME, cfg)
static func load_interface_config(node: Node, default: Dictionary = {}) -> Dictionary:
if node.has_meta(WIZARD_INTERFACE_CONFIG_META_NAME):
return node.get_meta(WIZARD_INTERFACE_CONFIG_META_NAME)
return default
static func save_interface_config(node: Node, cfg:Dictionary) -> void:
node.set_meta(WIZARD_INTERFACE_CONFIG_META_NAME, cfg)
static func set_source_hash(node: Object, hash: String) -> void:
node.set_meta(SOURCE_FILE_HASH_META_NAME, hash)
static func get_source_hash(node: Object) -> String:
if node.has_meta(SOURCE_FILE_HASH_META_NAME):
return node.get_meta(SOURCE_FILE_HASH_META_NAME)
return ""

View file

@ -0,0 +1,306 @@
@tool
extends "../base_sprite_resource_creator.gd"
var _DEFAULT_ANIMATION_LIBRARY = "" # GLOBAL
func create_animations(target_node: Node, player: AnimationPlayer, aseprite_files: Dictionary, options: Dictionary):
var result = _import(target_node, player, aseprite_files, options)
if result != result_code.SUCCESS:
printerr(result_code.get_error_message(result))
func _import(target_node: Node, player: AnimationPlayer, aseprite_files: Dictionary, options: Dictionary):
var source_file = aseprite_files.data_file
var sprite_sheet = aseprite_files.sprite_sheet
var data = _aseprite_file_exporter.load_json_content(source_file)
if not data.is_ok:
return data.code
var content = data.content
var context = {}
if target_node is CanvasItem:
target_node.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
else:
target_node.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST
_setup_texture(target_node, sprite_sheet, content, context, options.slice != "")
var result = _configure_animations(target_node, player, content, context, options)
if result != result_code.SUCCESS:
return result
return _cleanup_animations(target_node, player, content, options)
func _load_texture(sprite_sheet: String) -> Texture2D:
var texture = ResourceLoader.load(sprite_sheet, 'Image', ResourceLoader.CACHE_MODE_IGNORE)
texture.take_over_path(sprite_sheet)
return texture
func _configure_animations(target_node: Node, player: AnimationPlayer, content: Dictionary, context: Dictionary, options: Dictionary):
var frames = _aseprite.get_content_frames(content)
var slice_rect = null
if options.slice != "":
options["slice_rect"] = _aseprite.get_slice_rect(content, options.slice)
if not player.has_animation_library(_DEFAULT_ANIMATION_LIBRARY):
player.add_animation_library(_DEFAULT_ANIMATION_LIBRARY, AnimationLibrary.new())
if content.meta.has("frameTags") and content.meta.frameTags.size() > 0:
var result = result_code.SUCCESS
for tag in content.meta.frameTags:
var selected_frames = frames.slice(tag.from, tag.to + 1)
result = _add_animation_frames(target_node, player, tag.name, selected_frames, context, options, tag.direction, int(tag.get("repeat", -1)))
if result != result_code.SUCCESS:
break
return result
else:
return _add_animation_frames(target_node, player, "default", frames, context, options)
func _add_animation_frames(target_node: Node, player: AnimationPlayer, anim_name: String, frames: Array, context: Dictionary, options: Dictionary, direction = 'forward', repeat = -1):
var animation_name = anim_name
var library_name = _DEFAULT_ANIMATION_LIBRARY
var is_loopable = _config.is_default_animation_loop_enabled()
var slice_rect = options.get("slice_rect")
var is_importing_slice: bool = slice_rect != null
var anim_tokens := anim_name.split("/")
if anim_tokens.size() > 2:
push_error("Invalid animation name: %s" % animation_name)
return
elif anim_tokens.size() == 2:
library_name = anim_tokens[0]
animation_name = anim_tokens[1]
if not _validate_animation_name(animation_name):
push_error("Invalid animation name: %s" % animation_name)
return
# Create library if doesn't exist
if library_name != _DEFAULT_ANIMATION_LIBRARY and not player.has_animation_library(library_name):
player.add_animation_library(library_name, AnimationLibrary.new())
# Check loop
if animation_name.begins_with(_config.get_animation_loop_exception_prefix()):
animation_name = animation_name.substr(_config.get_animation_loop_exception_prefix().length())
is_loopable = not is_loopable
# Add library
if not player.get_animation_library(library_name).has_animation(animation_name):
player.get_animation_library(library_name).add_animation(animation_name, Animation.new())
var full_name = (
animation_name if library_name == "" else "%s/%s" % [library_name, animation_name]
)
var animation = player.get_animation(full_name)
_cleanup_tracks(target_node, player, animation)
_create_meta_tracks(target_node, player, animation)
var frame_track = _get_property_track_path(player, target_node, _get_frame_property(is_importing_slice))
var frame_track_index = _create_track(target_node, animation, frame_track)
if direction == "reverse" or direction == "pingpong_reverse":
frames.reverse()
var animation_length = 0
var repetition = 1
if repeat != -1:
is_loopable = false
repetition = repeat
for i in range(repetition):
for frame in frames:
var frame_key = _get_frame_key(target_node, frame, context, slice_rect)
animation.track_insert_key(frame_track_index, animation_length, frame_key)
animation_length += frame.duration / 1000
# Godot 4 has an Animation.LOOP_PINGPONG mode, however it does not
# behave like in Aseprite, so I'm keeping the custom implementation
if direction.begins_with("pingpong"):
var working_frames = frames.duplicate()
working_frames.remove_at(working_frames.size() - 1)
if is_loopable or (repetition > 1 and i < repetition - 1):
working_frames.remove_at(0)
working_frames.reverse()
for frame in working_frames:
var frame_key = _get_frame_key(target_node, frame, context, slice_rect)
animation.track_insert_key(frame_track_index, animation_length, frame_key)
animation_length += frame.duration / 1000
# if keep_anim_length is enabled only adjust length if
# - there aren't other tracks besides metas and frame
# - the current animation is shorter than new one
if not options.keep_anim_length or (animation.get_track_count() == (_get_meta_prop_names().size() + 1) or animation.length < animation_length):
animation.length = animation_length
animation.loop_mode = Animation.LOOP_LINEAR if is_loopable else Animation.LOOP_NONE
return result_code.SUCCESS
const _INVALID_TOKENS := ["/", ":", ",", "["]
func _validate_animation_name(name: String) -> bool:
return not _INVALID_TOKENS.any(func(token: String): return token in name)
func _create_track(target_node: Node, animation: Animation, track: String):
var track_index = animation.find_track(track, Animation.TYPE_VALUE)
if track_index != -1:
animation.remove_track(track_index)
track_index = animation.add_track(Animation.TYPE_VALUE)
animation.track_set_path(track_index, track)
animation.track_set_interpolation_loop_wrap(track_index, false)
animation.value_track_set_update_mode(track_index, Animation.UPDATE_DISCRETE)
return track_index
func _get_property_track_path(player: AnimationPlayer, target_node: Node, prop: String) -> String:
var node_path = player.get_node(player.root_node).get_path_to(target_node)
return "%s:%s" % [node_path, prop]
func _cleanup_animations(target_node: Node, player: AnimationPlayer, content: Dictionary, options: Dictionary):
if not (content.meta.has("frameTags") and content.meta.frameTags.size() > 0):
return result_code.SUCCESS
_remove_unused_animations(content, player)
if options.get("cleanup_hide_unused_nodes", false):
_hide_unused_nodes(target_node, player, content)
return result_code.SUCCESS
func _remove_unused_animations(content: Dictionary, player: AnimationPlayer):
pass # FIXME it's not removing unused animations anymore. Sample impl bellow
# var tags = ["RESET"]
# for t in content.meta.frameTags:
# var a = t.name
# if a.begins_with(_config.get_animation_loop_exception_prefix()):
# a = a.substr(_config.get_animation_loop_exception_prefix().length())
# tags.push_back(a)
# var track = _get_frame_track_path(player, sprite)
# for a in player.get_animation_list():
# if tags.has(a):
# continue
#
# var animation = player.get_animation(a)
# if animation.get_track_count() != 1:
# var t = animation.find_track(track)
# if t != -1:
# animation.remove_track(t)
# continue
#
# if animation.find_track(track) != -1:
# player.remove_animation(a)
func _hide_unused_nodes(target_node: Node, player: AnimationPlayer, content: Dictionary):
var root_node := player.get_node(player.root_node)
var all_animations := player.get_animation_list()
var all_sprite_nodes := []
var animation_sprites := {}
for a in all_animations:
var animation := player.get_animation(a)
var sprite_nodes := []
for track_idx in animation.get_track_count():
var raw_path := animation.track_get_path(track_idx)
if raw_path.get_subname(0) == "visible":
continue
var path := _remove_properties_from_path(raw_path)
var sprite_node := root_node.get_node(path)
if !(sprite_node is Sprite2D || sprite_node is Sprite3D):
continue
if sprite_nodes.has(sprite_node):
continue
sprite_nodes.append(sprite_node)
animation_sprites[animation] = sprite_nodes
for sn in sprite_nodes:
if all_sprite_nodes.has(sn):
continue
all_sprite_nodes.append(sn)
for animation in animation_sprites:
var sprite_nodes : Array = animation_sprites[animation]
for node in all_sprite_nodes:
if sprite_nodes.has(node):
continue
var visible_track = _get_property_track_path(player, node, "visible")
if animation.find_track(visible_track, Animation.TYPE_VALUE) != -1:
continue
var visible_track_index = _create_track(node, animation, visible_track)
animation.track_insert_key(visible_track_index, 0, false)
func list_layers(file: String, only_visibles = false) -> Array:
return _aseprite.list_layers(file, only_visibles)
func list_slices(file: String) -> Array:
return _aseprite.list_slices(file)
func _remove_properties_from_path(path: NodePath) -> NodePath:
var string_path := path as String
if !(":" in string_path):
return string_path as NodePath
var property_path := path.get_concatenated_subnames() as String
string_path = string_path.substr(0, string_path.length() - property_path.length() - 1)
return string_path as NodePath
func _create_meta_tracks(target_node: Node, player: AnimationPlayer, animation: Animation):
for prop in _get_meta_prop_names():
var track = _get_property_track_path(player, target_node, prop)
var track_index = _create_track(target_node, animation, track)
animation.track_insert_key(track_index, 0, true if prop == "visible" else target_node.get(prop))
func _cleanup_tracks(target_node: Node, player: AnimationPlayer, animation: Animation):
for track_key in ["texture", "hframes", "vframes", "region_rect", "frame"]:
var track = _get_property_track_path(player, target_node, track_key)
var track_index = animation.find_track(track, Animation.TYPE_VALUE)
if track_index != -1:
animation.remove_track(track_index)
func _setup_texture(target_node: Node, sprite_sheet: String, content: Dictionary, context: Dictionary, is_importing_slice: bool):
push_error("_setup_texture not implemented!")
func _get_frame_property(is_importing_slice: bool) -> String:
push_error("_get_frame_property not implemented!")
return ""
func _get_frame_key(target_node: Node, frame: Dictionary, context: Dictionary, slice_info: Variant):
push_error("_get_frame_key not implemented!")
func _get_meta_prop_names():
push_error("_get_meta_prop_names not implemented!")

View file

@ -0,0 +1,48 @@
extends "animation_creator.gd"
func _get_meta_prop_names():
return [ "visible" ]
func _setup_texture(sprite: Node, sprite_sheet: String, content: Dictionary, context: Dictionary, is_importing_slice: bool):
var texture = _load_texture(sprite_sheet)
sprite.texture = texture
if content.frames.is_empty():
return
if is_importing_slice:
sprite.region_enabled = true
sprite.hframes = 1
sprite.vframes = 1
sprite.frame = 0
else:
sprite.region_enabled = false
sprite.hframes = content.meta.size.w / content.frames[0].sourceSize.w
sprite.vframes = content.meta.size.h / content.frames[0].sourceSize.h
func _get_frame_property(is_importing_slice: bool) -> String:
return "frame" if not is_importing_slice else "region_rect"
func _get_frame_key(sprite: Node, frame: Dictionary, context: Dictionary, slice_info: Variant):
if slice_info != null:
return _create_slice_rect(frame, slice_info)
return _calculate_frame_index(sprite,frame)
func _calculate_frame_index(sprite: Node, frame: Dictionary) -> int:
var column = floor(frame.frame.x * sprite.hframes / sprite.texture.get_width())
var row = floor(frame.frame.y * sprite.vframes / sprite.texture.get_height())
return (row * sprite.hframes) + column
func _create_slice_rect(frame_data: Dictionary, slice_rect: Rect2) -> Rect2:
var frame = frame_data.frame
return Rect2(
frame.x + slice_rect.position.x,
frame.y + slice_rect.position.y,
slice_rect.size.x,
slice_rect.size.y
)

View file

@ -0,0 +1,32 @@
extends "animation_creator.gd"
func _get_meta_prop_names():
return [ "visible" ]
func _setup_texture(target_node: Node, sprite_sheet: String, content: Dictionary, context: Dictionary, _is_importing_slice: bool):
context["base_texture"] = _load_texture(sprite_sheet)
func _get_frame_property(_is_importing_slice: bool) -> String:
return "texture"
func _get_frame_key(target_node: Node, frame: Dictionary, context: Dictionary, slice_info: Variant):
return _get_atlas_texture(context["base_texture"], frame, slice_info)
func _get_atlas_texture(base_texture: Texture2D, frame_data: Dictionary, slice_info: Variant) -> AtlasTexture:
var tex = AtlasTexture.new()
tex.atlas = base_texture
tex.region = Rect2(Vector2(frame_data.frame.x, frame_data.frame.y), Vector2(frame_data.frame.w, frame_data.frame.h))
tex.filter_clip = true
if slice_info != null:
tex.region.position.x += slice_info.position.x
tex.region.position.y += slice_info.position.y
tex.region.size.x = slice_info.size.x
tex.region.size.y = slice_info.size.y
return tex

View file

@ -0,0 +1,8 @@
@tool
extends RefCounted
var result_code = preload("../config/result_codes.gd")
var _aseprite = preload("../aseprite/aseprite.gd").new()
var _aseprite_file_exporter = preload("../aseprite/file_exporter.gd").new()
var _config = preload("../config/config.gd").new()

View file

@ -0,0 +1,234 @@
@tool
extends "../base_sprite_resource_creator.gd"
enum {
FILE_EXPORT_MODE,
LAYERS_EXPORT_MODE
}
###
### Create SpriteFrames from aseprite files and insert
### them to the animated_sprite node
###
func create_animations(animated_sprite: Node, aseprite_files: Dictionary, options: Dictionary) -> void:
var sprite_frames_result = _create_sprite_frames(aseprite_files, options)
if not sprite_frames_result.is_ok:
printerr(result_code.get_error_message(sprite_frames_result.code))
return
animated_sprite.frames = sprite_frames_result.content
if animated_sprite is CanvasItem:
animated_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
else:
animated_sprite.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST
func create_resources(source_files: Array, options: Dictionary = {}) -> Dictionary:
var resources = []
for o in source_files:
if o.is_empty():
return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED)
var resource = _create_sprite_frames(o, options)
if not resource.is_ok:
return resource
resources.push_back({
"data_file": o.data_file,
"resource": resource.content,
})
return result_code.result(resources)
func _create_sprite_frames(data: Dictionary, options: Dictionary) -> Dictionary:
var aseprite_resources = _load_aseprite_resources(data)
if not aseprite_resources.is_ok:
return aseprite_resources
return result_code.result(
_create_sprite_frames_with_animations(
aseprite_resources.content.metadata,
aseprite_resources.content.texture,
options,
)
)
func _load_aseprite_resources(aseprite_data: Dictionary):
var content_result = _aseprite_file_exporter.load_json_content(aseprite_data.data_file)
if not content_result.is_ok:
return content_result
var texture = _load_texture(aseprite_data.sprite_sheet)
return result_code.result({
"metadata": content_result.content,
"texture": texture
})
func save_resources(resources: Array) -> int:
for resource in resources:
var code = _save_resource(resource.resource, resource.data_file)
if code != OK:
return code
return OK
func _save_resource(resource, source_path: String) -> int:
var save_path = "%s.%s" % [source_path.get_basename(), "res"]
var code = ResourceSaver.save(resource, save_path, ResourceSaver.FLAG_REPLACE_SUBRESOURCE_PATHS)
resource.take_over_path(save_path)
return code
func _create_sprite_frames_with_animations(content: Dictionary, texture, options: Dictionary) -> SpriteFrames:
var frame_cache = {}
var frames = _aseprite.get_content_frames(content)
var sprite_frames := SpriteFrames.new()
sprite_frames.remove_animation("default")
var frame_rect: Variant = null
# currently, aseprite does not work with the --slice option, so we need to manually
# do it. https://github.com/aseprite/aseprite/issues/2469
if options.get("slice", "") != "":
frame_rect = _aseprite.get_slice_rect(content, options.slice)
if content.meta.has("frameTags") and content.meta.frameTags.size() > 0:
for tag in content.meta.frameTags:
var selected_frames = frames.slice(tag.from, tag.to + 1)
_add_animation_frames(sprite_frames, tag.name, selected_frames, texture, frame_rect, tag.direction, int(tag.get("repeat", -1)), frame_cache)
else:
_add_animation_frames(sprite_frames, "default", frames, texture, frame_rect)
return sprite_frames
func _add_animation_frames(
sprite_frames: SpriteFrames,
anim_name: String,
frames: Array,
texture,
frame_rect: Variant,
direction = 'forward',
repeat = -1,
frame_cache = {}
):
var animation_name := anim_name
var is_loopable = _config.is_default_animation_loop_enabled()
var loop_prefix = _config.get_animation_loop_exception_prefix()
if animation_name.begins_with(loop_prefix):
animation_name = anim_name.trim_prefix(loop_prefix)
is_loopable = not is_loopable
sprite_frames.add_animation(animation_name)
var min_duration = _get_min_duration(frames)
var fps = _calculate_fps(min_duration)
if direction == "reverse" or direction == "pingpong_reverse":
frames.reverse()
var repetition = 1
if repeat != -1:
is_loopable = false
repetition = repeat
for i in range(repetition):
for frame in frames:
_add_to_sprite_frames(sprite_frames, animation_name, texture, frame, min_duration, frame_cache, frame_rect)
if direction.begins_with("pingpong"):
var working_frames = frames.duplicate()
working_frames.remove_at(working_frames.size() - 1)
if is_loopable or (repetition > 1 and i < repetition - 1):
working_frames.remove_at(0)
working_frames.reverse()
for frame in working_frames:
_add_to_sprite_frames(sprite_frames, animation_name, texture, frame, min_duration, frame_cache, frame_rect)
sprite_frames.set_animation_loop(animation_name, is_loopable)
sprite_frames.set_animation_speed(animation_name, fps)
func _calculate_fps(min_duration: int) -> float:
return ceil(1000.0 / min_duration)
func _get_min_duration(frames) -> int:
var min_duration = 100000
for frame in frames:
if frame.duration < min_duration:
min_duration = frame.duration
return min_duration
func _load_texture(path) -> CompressedTexture2D:
return ResourceLoader.load(path, "CompressedTexture2D", ResourceLoader.CACHE_MODE_REPLACE)
func _add_to_sprite_frames(
sprite_frames,
animation_name: String,
texture,
frame: Dictionary,
min_duration: int,
frame_cache: Dictionary,
frame_rect: Variant,
):
var atlas : AtlasTexture = _create_atlastexture_from_frame(texture, frame, sprite_frames, frame_cache, frame_rect)
var duration = frame.duration / min_duration
sprite_frames.add_frame(animation_name, atlas, duration)
func _create_atlastexture_from_frame(
image,
frame_data,
sprite_frames: SpriteFrames,
frame_cache: Dictionary,
frame_rect: Variant,
) -> AtlasTexture:
var frame = frame_data.frame
var region := Rect2(frame.x, frame.y, frame.w, frame.h)
# this is to manually set the slice
if frame_rect != null:
region.position.x += frame_rect.position.x
region.position.y += frame_rect.position.y
region.size.x = frame_rect.size.x
region.size.y = frame_rect.size.y
var key := "%s_%s_%s_%s" % [frame.x, frame.y, frame.w, frame.h]
var texture = frame_cache.get(key)
if texture != null and texture.atlas == image:
return texture
var atlas_texture := AtlasTexture.new()
atlas_texture.atlas = image
atlas_texture.region = region
frame_cache[key] = atlas_texture
return atlas_texture
func list_layers(file: String, only_visibles = false) -> Array:
return _aseprite.list_layers(file, only_visibles)
func list_slices(file: String) -> Array:
return _aseprite.list_slices(file)
func _get_file_basename(file_path: String) -> String:
return file_path.get_file().trim_suffix('.%s' % file_path.get_extension())

View file

@ -0,0 +1,26 @@
@tool
extends "../base_sprite_resource_creator.gd"
func load_texture(target_node: Node, aseprite_files: Dictionary, options: Dictionary) -> void:
var source_file = aseprite_files.data_file
var sprite_sheet = aseprite_files.sprite_sheet
var data = _aseprite_file_exporter.load_json_content(source_file)
var texture = ResourceLoader.load(sprite_sheet)
if not data.is_ok:
printerr("Failed to load aseprite source %s" % source_file)
return
if options.slice == "":
target_node.texture = texture
else:
var region = _aseprite.get_slice_rect(data.content, options.slice)
var atlas_texture := AtlasTexture.new()
atlas_texture.atlas = texture
atlas_texture.region = region
target_node.texture = atlas_texture
if target_node is CanvasItem:
target_node.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
else:
target_node.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST

View file

@ -0,0 +1,91 @@
## Some animations might have been imported from outside the project folder,
## potentially leaking the host's path, which is not ideal.
## This plugin removes the aseprite metadata from scenes and spriteframes before
## generating the game export.
extends EditorExportPlugin
const wizard_config = preload("../config/wizard_config.gd")
func _get_name():
return "aseprite_wizard_metadata_export_plugin"
func _export_file(path: String, type: String, features: PackedStringArray) -> void:
match type:
"PackedScene":
_cleanup_scene(path, type)
"SpriteFrames":
_cleanup_spriteframes(path, type)
func _cleanup_scene(path: String, type: String):
var scene : PackedScene = ResourceLoader.load(path, type, ResourceLoader.CACHE_MODE_IGNORE)
var scene_changed := false
var root_node := scene.instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE)
var nodes := [root_node]
#remove_at metadata from scene
while not nodes.is_empty():
var node : Node = nodes.pop_front()
for child in node.get_children():
nodes.push_back(child)
if _remove_meta(node):
scene_changed = true
#save scene if changed
if scene_changed:
var filtered_scene := PackedScene.new()
if filtered_scene.pack(root_node) != OK:
print("Error updating scene")
return
var content := _get_scene_content(path, filtered_scene)
add_file(path, content, true)
root_node.free()
func _remove_meta(node: Object) -> bool:
if node.has_meta(wizard_config.WIZARD_CONFIG_META_NAME):
node.remove_meta(wizard_config.WIZARD_CONFIG_META_NAME)
return true
return false
func _get_scene_content(path:String, scene:PackedScene) -> PackedByteArray:
var tmp_path = OS.get_cache_dir() + "tmp_scene." + path.get_extension()
ResourceSaver.save(scene, tmp_path)
var tmp_file = FileAccess.open(tmp_path, FileAccess.READ)
var content : PackedByteArray = tmp_file.get_buffer(tmp_file.get_length())
tmp_file.close()
if FileAccess.file_exists(tmp_path):
DirAccess.remove_absolute(tmp_path)
return content
func _cleanup_spriteframes(path: String, type: String):
var resource : SpriteFrames = ResourceLoader.load(path, type, ResourceLoader.CACHE_MODE_IGNORE)
if _remove_meta(resource):
var content := _create_temp_resource(path, resource)
add_file(path, content, true)
func _create_temp_resource(path: String, resource: SpriteFrames) -> PackedByteArray:
var tmp_path = OS.get_cache_dir() + "tmp_spriteframes_resource." + path.get_extension()
ResourceSaver.save(resource, tmp_path)
var tmp_file = FileAccess.open(tmp_path, FileAccess.READ)
var content : PackedByteArray = tmp_file.get_buffer(tmp_file.get_length())
tmp_file.close()
if FileAccess.file_exists(tmp_path):
DirAccess.remove_absolute(tmp_path)
return content

View file

@ -0,0 +1,57 @@
@tool
extends EditorImportPlugin
##
## No-op importer to allow files to be seen and
## managed, but without triggering a real import
##
var config = preload("../config/config.gd").new()
func _get_importer_name():
return "aseprite_wizard.plugin.noop"
func _get_visible_name():
return "Aseprite (No Import)"
func _get_recognized_extensions():
return ["aseprite", "ase"]
func _get_save_extension():
return "res"
func _get_resource_type():
return "PackedDataContainer"
func _get_preset_count():
return 1
func _get_preset_name(i):
return "Default"
func _get_priority():
return 2.0 if config.get_default_importer() == config.IMPORTER_NOOP_NAME else 1.0
func _get_import_order():
return 1
func _get_import_options(_path, _i):
return []
func _get_option_visibility(path, option, options):
return true
func _import(source_file, save_path, options, platform_variants, gen_files):
var container = PackedDataContainer.new()
return ResourceSaver.save(container, "%s.%s" % [save_path, _get_save_extension()])

View file

@ -0,0 +1,150 @@
@tool
extends EditorImportPlugin
const result_codes = preload("../config/result_codes.gd")
var config = preload("../config/config.gd").new()
var _aseprite_file_exporter = preload("../aseprite/file_exporter.gd").new()
var _sf_creator = preload("../creators/sprite_frames/sprite_frames_creator.gd").new()
var file_system: EditorFileSystem = EditorInterface.get_resource_filesystem()
func _get_importer_name():
# ideally this should be called aseprite_wizard.plugin.spriteframes
# but I'm keeping it like this to avoid unnecessary breaking changes
return "aseprite_wizard.plugin"
func _get_visible_name():
return "Aseprite SpriteFrames"
func _get_recognized_extensions():
return ["aseprite", "ase"]
func _get_save_extension():
return "res"
func _get_resource_type():
return "SpriteFrames"
func _get_preset_count():
return 1
func _get_preset_name(i):
return "Default"
func _get_priority():
return 2.0 if config.get_default_importer() == config.IMPORTER_SPRITEFRAMES_NAME else 1.0
func _get_import_order():
return 1
func _get_import_options(_path, _i):
return [
{"name": "split_layers", "default_value": false},
{"name": "exclude_layers_pattern", "default_value": config.get_default_exclusion_pattern()},
{"name": "only_visible_layers", "default_value": false},
{
"name": "sheet_type",
"default_value": "Packed",
"property_hint": PROPERTY_HINT_ENUM,
"hint_string": get_sheet_type_hint_string()
},
]
func _get_option_visibility(path, option, options):
return true
static func get_sheet_type_hint_string() -> String:
var hint_string := "Packed"
for number in [2, 4, 8, 16, 32]:
hint_string += ",%s columns" % number
hint_string += ",Strip"
return hint_string
func _import(source_file, save_path, options, platform_variants, gen_files):
var absolute_source_file = ProjectSettings.globalize_path(source_file)
var absolute_save_path = ProjectSettings.globalize_path(save_path)
var source_path = source_file.get_base_dir()
var source_basename = source_file.substr(source_path.length()+1, -1)
source_basename = source_basename.substr(0, source_basename.rfind('.'))
var export_mode = _sf_creator.LAYERS_EXPORT_MODE if options['split_layers'] else _sf_creator.FILE_EXPORT_MODE
var aseprite_opts = {
"export_mode": export_mode,
"exception_pattern": options['exclude_layers_pattern'],
"only_visible_layers": options['only_visible_layers'],
"output_filename": '' if export_mode == _sf_creator.FILE_EXPORT_MODE else '%s_' % source_basename,
"column_count" : int(options['sheet_type']) if options['sheet_type'] != "Strip" else 128,
"output_folder": source_path,
}
var source_files = _aseprite_file_exporter.generate_aseprite_files(absolute_source_file, aseprite_opts)
if not source_files.is_ok:
printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(source_files.code))
return FAILED
var should_trigger_scan = false
for sf in source_files.content:
if sf.is_first_import:
file_system.update_file(sf.sprite_sheet)
append_import_external_resource(sf.sprite_sheet)
else:
should_trigger_scan = true
if should_trigger_scan:
file_system.scan()
var resources = _sf_creator.create_resources(source_files.content)
if not resources.is_ok:
printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(resources.code))
return FAILED
if export_mode == _sf_creator.LAYERS_EXPORT_MODE:
# each layer is saved as one resource using base file name to prevent collisions
# the first layer will be saved in the default resource path to prevent
# godot from keeping re-importing it
for resource in resources.content:
var resource_path = "%s.res" % resource.data_file.get_basename();
var exit_code = ResourceSaver.save(resource.resource, resource_path)
resource.resource.take_over_path(resource_path)
if exit_code != OK:
printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code))
return FAILED
var resource = resources.content[0]
var resource_path = "%s.res" % save_path
var exit_code = ResourceSaver.save(resource.resource, resource_path)
resource.resource.take_over_path(resource_path)
if config.should_remove_source_files():
_remove_source_files(source_files.content)
if exit_code != OK:
printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code))
return FAILED
return OK
func _remove_source_files(source_files: Array):
for s in source_files:
DirAccess.remove_absolute(s.data_file)
file_system.call_deferred("scan")

View file

@ -0,0 +1,136 @@
@tool
extends EditorImportPlugin
##
## Static texture importer.
## Imports first frame from Aseprite file as texture
##
const result_codes = preload("../config/result_codes.gd")
var _aseprite_file_exporter = preload("../aseprite/file_exporter.gd").new()
var config = preload("../config/config.gd").new()
var file_system: EditorFileSystem = EditorInterface.get_resource_filesystem()
func _get_importer_name():
return "aseprite_wizard.plugin.static-texture"
func _get_visible_name():
return "Aseprite Texture"
func _get_recognized_extensions():
return ["aseprite", "ase"]
func _get_save_extension():
return "res"
func _get_resource_type():
return "AtlasTexture"
func _get_preset_count():
return 1
func _get_preset_name(i):
return "Default"
func _get_priority():
return 2.0 if config.get_default_importer() == config.IMPORTER_STATIC_TEXTURE_NAME else 0.8
func _get_import_order():
return 1
func _get_import_options(_path, _i):
return [
{"name": "exclude_layers_pattern", "default_value": config.get_default_exclusion_pattern()},
{"name": "only_visible_layers", "default_value": false},
]
func _get_option_visibility(path, option, options):
return true
func _import(source_file, save_path, options, platform_variants, gen_files):
var absolute_source_file = ProjectSettings.globalize_path(source_file)
var absolute_save_path = ProjectSettings.globalize_path(save_path)
var source_path = source_file.get_base_dir()
var source_basename = source_file.substr(source_path.length()+1, -1)
source_basename = source_basename.substr(0, source_basename.rfind('.'))
var aseprite_opts = {
"exception_pattern": options['exclude_layers_pattern'],
"only_visible_layers": options['only_visible_layers'],
"output_filename": '',
"output_folder": source_path,
"first_frame_only": true,
}
var result = _generate_texture(absolute_source_file, aseprite_opts)
if not result.is_ok:
printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(result.code))
return FAILED
var sprite_sheet = result.content.sprite_sheet
var data = result.content.data
if ResourceLoader.exists(sprite_sheet):
file_system.scan()
else:
file_system.update_file(sprite_sheet)
append_import_external_resource(sprite_sheet)
var texture: CompressedTexture2D = ResourceLoader.load(sprite_sheet, "CompressedTexture2D", ResourceLoader.CACHE_MODE_REPLACE)
return _save_resource(texture, save_path, result.content.data_file, data.meta.size)
func _generate_texture(absolute_source_file: String, options: Dictionary) -> Dictionary:
var result = _aseprite_file_exporter.generate_aseprite_file(absolute_source_file, options)
if not result.is_ok:
return result
var sprite_sheet = result.content.sprite_sheet
var data_result = _aseprite_file_exporter.load_json_content(result.content.data_file)
if not data_result.is_ok:
return data_result
var data = data_result.content
return result_codes.result({
"data_file": result.content.data_file,
"sprite_sheet": sprite_sheet,
"data": data
})
func _save_resource(texture: CompressedTexture2D, save_path: String, data_file_path: String, size: Dictionary) -> int:
var resource = AtlasTexture.new()
resource.atlas = texture
resource.region = Rect2(0, 0, size.w, size.h)
var resource_path = "%s.res" % save_path
var exit_code = ResourceSaver.save(resource, resource_path)
resource.take_over_path(resource_path)
if config.should_remove_source_files():
DirAccess.remove_absolute(data_file_path)
file_system.call_deferred("scan")
if exit_code != OK:
printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code))
return FAILED
return OK

View file

@ -0,0 +1,135 @@
@tool
extends EditorImportPlugin
##
## Tileset texture importer.
## Imports Aseprite tileset layers as an AtlasTexture
##
const result_codes = preload("../config/result_codes.gd")
var _aseprite_file_exporter = preload("../aseprite/file_exporter.gd").new()
var config = preload("../config/config.gd").new()
var file_system: EditorFileSystem = EditorInterface.get_resource_filesystem()
func _get_importer_name():
return "aseprite_wizard.plugin.tileset-texture"
func _get_visible_name():
return "Aseprite Tileset Texture"
func _get_recognized_extensions():
return ["aseprite", "ase"]
func _get_save_extension():
return "res"
func _get_resource_type():
return "AtlasTexture"
func _get_preset_count():
return 1
func _get_preset_name(i):
return "Default"
func _get_priority():
return 2.0 if config.get_default_importer() == config.IMPORTER_TILESET_TEXTURE_NAME else 0.9
func _get_import_order():
return 1
func _get_import_options(_path, _i):
return [
{"name": "exclude_layers_pattern", "default_value": config.get_default_exclusion_pattern()},
{"name": "only_visible_layers", "default_value": false},
]
func _get_option_visibility(path, option, options):
return true
func _import(source_file, save_path, options, platform_variants, gen_files):
var absolute_source_file = ProjectSettings.globalize_path(source_file)
var absolute_save_path = ProjectSettings.globalize_path(save_path)
var source_path = source_file.get_base_dir()
var source_basename = source_file.substr(source_path.length()+1, -1)
source_basename = source_basename.substr(0, source_basename.rfind('.'))
var aseprite_opts = {
"exception_pattern": options['exclude_layers_pattern'],
"only_visible_layers": options['only_visible_layers'],
"output_filename": '',
"output_folder": source_path,
}
var result = _generate_texture(absolute_source_file, aseprite_opts)
if not result.is_ok:
printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(result.code))
return FAILED
var sprite_sheet = result.content.sprite_sheet
var data = result.content.data
if ResourceLoader.exists(sprite_sheet):
file_system.scan()
else:
file_system.update_file(sprite_sheet)
append_import_external_resource(sprite_sheet)
var texture: CompressedTexture2D = ResourceLoader.load(sprite_sheet, "CompressedTexture2D", ResourceLoader.CACHE_MODE_REPLACE)
return _save_resource(texture, save_path, result.content.data_file, data.meta.size)
func _generate_texture(absolute_source_file: String, options: Dictionary) -> Dictionary:
var result = _aseprite_file_exporter.generate_tileset_files(absolute_source_file, options)
if not result.is_ok:
return result
var sprite_sheet = result.content.sprite_sheet
var data_result = _aseprite_file_exporter.load_json_content(result.content.data_file)
if not data_result.is_ok:
return data_result
var data = data_result.content
return result_codes.result({
"data_file": result.content.data_file,
"sprite_sheet": sprite_sheet,
"data": data
})
func _save_resource(texture: CompressedTexture2D, save_path: String, data_file_path: String, size: Dictionary) -> int:
var resource = AtlasTexture.new()
resource.atlas = texture
resource.region = Rect2(0, 0, size.w, size.h)
var resource_path = "%s.res" % save_path
var exit_code = ResourceSaver.save(resource, resource_path)
resource.take_over_path(resource_path)
if config.should_remove_source_files():
DirAccess.remove_absolute(data_file_path)
file_system.call_deferred("scan")
if exit_code != OK:
printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code))
return FAILED
return OK

View file

@ -0,0 +1,44 @@
@tool
extends "../base_inspector_dock.gd"
var sprite_frames_creator = preload("../../../creators/sprite_frames/sprite_frames_creator.gd").new()
func _get_available_layers(global_source_path: String) -> Array:
return sprite_frames_creator.list_layers(global_source_path)
func _get_available_slices(global_source_path: String) -> Array:
return sprite_frames_creator.list_slices(global_source_path)
func _do_import():
var root = get_tree().get_edited_scene_root()
var source_path = ProjectSettings.globalize_path(_source)
var options = {
"output_folder": _output_folder if _output_folder != "" else root.scene_file_path.get_base_dir(),
"exception_pattern": _ex_pattern_field.text,
"only_visible_layers": _visible_layers_field.button_pressed,
"output_filename": _out_filename_field.text,
"layer": _layer,
}
_save_config()
var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source_path, options)
if not aseprite_output.is_ok:
var error = result_code.get_error_message(aseprite_output.code)
printerr(error)
_show_message(error)
return
file_system.scan()
await file_system.filesystem_changed
sprite_frames_creator.create_animations(target_node, aseprite_output.content, { "slice": _slice })
wizard_config.set_source_hash(target_node, FileAccess.get_md5(source_path))
_handle_cleanup(aseprite_output.content)

View file

@ -0,0 +1,12 @@
[gd_scene load_steps=3 format=3 uid="uid://vej7yqkbtd5f"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://uxm7b02wry10" path="res://addons/AsepriteWizard/interface/docks/dock_fields.tscn" id="2_2ilip"]
[node name="animated_sprite_inspector_dock" type="PanelContainer"]
offset_right = 14.0
offset_bottom = 14.0
script = ExtResource("1")
[node name="dock_fields" parent="." instance=ExtResource("2_2ilip")]
layout_mode = 2

View file

@ -0,0 +1,13 @@
@tool
extends EditorInspectorPlugin
const ASInspectorDock = preload("./animated_sprite_inspector_dock.tscn")
func _can_handle(object):
return object is AnimatedSprite2D || object is AnimatedSprite3D
func _parse_end(object):
var dock = ASInspectorDock.instantiate()
dock.target_node = object
add_custom_control(dock)

View file

@ -0,0 +1,429 @@
@tool
extends PanelContainer
const wizard_config = preload("../../config/wizard_config.gd")
const result_code = preload("../../config/result_codes.gd")
var _aseprite_file_exporter = preload("../../aseprite/file_exporter.gd").new()
var config = preload("../../config/config.gd").new()
var scene: Node
var target_node: Node
var file_system: EditorFileSystem = EditorInterface.get_resource_filesystem()
var _layer: String = ""
var _slice: String = ""
var _source: String = ""
var _file_dialog_aseprite: EditorFileDialog
var _output_folder_dialog: EditorFileDialog
var _importing := false
var _output_folder := ""
var _out_folder_default := "[Same as scene]"
var _layer_default := "[all]"
var _interface_section_state
@onready var _section_title := $dock_fields/VBoxContainer/title as Button
# general
@onready var _source_field := $dock_fields/VBoxContainer/source/button as Button
# layers
@onready var _layer_section_header := $dock_fields/VBoxContainer/extra/sections/layers/section_header as Button
@onready var _layer_section_container := $dock_fields/VBoxContainer/extra/sections/layers/section_content as MarginContainer
@onready var _layer_field := $dock_fields/VBoxContainer/extra/sections/layers/section_content/content/layer/options as OptionButton
@onready var _visible_layers_field := $dock_fields/VBoxContainer/extra/sections/layers/section_content/content/visible_layers/CheckBox as CheckBox
@onready var _ex_pattern_field := $dock_fields/VBoxContainer/extra/sections/layers/section_content/content/ex_pattern/LineEdit as LineEdit
# slice
@onready var _slice_section_header := $dock_fields/VBoxContainer/extra/sections/slices/section_header as Button
@onready var _slice_section_container := $dock_fields/VBoxContainer/extra/sections/slices/section_content as MarginContainer
@onready var _slice_field := $dock_fields/VBoxContainer/extra/sections/slices/section_content/content/slice/options as OptionButton
# output
@onready var _output_section_header := $dock_fields/VBoxContainer/extra/sections/output/section_header as Button
@onready var _output_section_container := $dock_fields/VBoxContainer/extra/sections/output/section_content as MarginContainer
@onready var _out_folder_field := $dock_fields/VBoxContainer/extra/sections/output/section_content/content/out_folder/button as Button
@onready var _out_filename_field := $dock_fields/VBoxContainer/extra/sections/output/section_content/content/out_filename/LineEdit as LineEdit
@onready var _import_button := $dock_fields/VBoxContainer/import as Button
const INTERFACE_SECTION_KEY_LAYER = "layer_section"
const INTERFACE_SECTION_KEY_SLICE = "slice_section"
const INTERFACE_SECTION_KEY_OUTPUT = "output_section"
@onready var _expandable_sections = {
INTERFACE_SECTION_KEY_LAYER: { "header": _layer_section_header, "content": _layer_section_container},
INTERFACE_SECTION_KEY_SLICE: { "header": _slice_section_header, "content": _slice_section_container},
INTERFACE_SECTION_KEY_OUTPUT: { "header": _output_section_header, "content": _output_section_container},
}
func _ready():
_pre_setup()
_setup_interface()
_setup_config()
_setup_field_listeners()
_setup()
_check_for_changes()
func _check_for_changes():
if not _source or _source == "":
return
var saved_hash = wizard_config.get_source_hash(target_node)
if saved_hash == "":
return
if saved_hash != FileAccess.get_md5(_source):
$dock_fields.show_source_change_warning()
func _setup_interface():
_hide_fields()
_show_specific_fields()
var cfg = wizard_config.load_interface_config(target_node)
_interface_section_state = cfg
_section_title.add_theme_stylebox_override("normal", _section_title.get_theme_stylebox("hover"))
for key in _expandable_sections:
_adjust_section_visibility(key)
func _setup_config():
var cfg = wizard_config.load_config(target_node)
if cfg == null:
_load_common_default_config()
else:
_load_common_config(cfg)
func _load_common_config(cfg):
if cfg.has("source"):
_set_source(cfg.source)
if cfg.get("layer", "") != "":
_layer_field.clear()
_set_layer(cfg.layer)
if cfg.get("slice", "") != "":
_slice_field.clear()
_set_slice(cfg.slice)
_set_out_folder(cfg.get("o_folder", ""))
_out_filename_field.text = cfg.get("o_name", "")
_visible_layers_field.button_pressed = cfg.get("only_visible", false)
_ex_pattern_field.text = cfg.get("o_ex_p", "")
_load_config(cfg)
func _load_common_default_config():
_ex_pattern_field.text = config.get_default_exclusion_pattern()
_visible_layers_field.button_pressed = config.should_include_only_visible_layers_by_default()
#_cleanup_hide_unused_nodes.button_pressed = config.is_set_visible_track_automatically_enabled()
_load_default_config()
func _set_source(source):
_source = source
_source_field.text = _source
_source_field.tooltip_text = _source
func _set_layer(layer):
_layer = layer
_layer_field.add_item(_layer)
func _set_slice(slice):
_slice = slice
_slice_field.add_item(_slice)
func _set_out_folder(path):
_output_folder = path
_out_folder_field.text = _output_folder if _output_folder != "" else _out_folder_default
_out_folder_field.tooltip_text = _out_folder_field.text
func _toggle_section_visibility(key: String) -> void:
_interface_section_state[key] = not _interface_section_state.get(key, false)
_adjust_section_visibility(key)
wizard_config.save_interface_config(target_node, _interface_section_state)
func _adjust_section_visibility(key: String) -> void:
var section = _expandable_sections[key]
var is_visible = _interface_section_state.get(key, false)
_adjust_icon(section.header, is_visible)
section.content.visible = is_visible
func _adjust_icon(section: Button, is_visible: bool = true) -> void:
var icon_name = "GuiTreeArrowDown" if is_visible else "GuiTreeArrowRight"
section.icon = get_theme_icon(icon_name, "EditorIcons")
func _setup_field_listeners():
_layer_section_header.button_down.connect(_on_layer_header_button_down)
_slice_section_header.button_down.connect(_on_slice_header_button_down)
_output_section_header.button_down.connect(_on_output_header_button_down)
_source_field.pressed.connect(_on_source_pressed)
_source_field.aseprite_file_dropped.connect(_on_source_aseprite_file_dropped)
_layer_field.button_down.connect(_on_layer_button_down)
_layer_field.item_selected.connect(_on_layer_item_selected)
_slice_field.button_down.connect(_on_slice_button_down)
_slice_field.item_selected.connect(_on_slice_item_selected)
_out_folder_field.dir_dropped.connect(_on_out_dir_dropped)
_out_folder_field.pressed.connect(_on_out_folder_pressed)
_import_button.pressed.connect(_on_import_pressed)
func _on_layer_header_button_down():
_toggle_section_visibility(INTERFACE_SECTION_KEY_LAYER)
func _on_slice_header_button_down():
_toggle_section_visibility(INTERFACE_SECTION_KEY_SLICE)
func _on_output_header_button_down():
_toggle_section_visibility(INTERFACE_SECTION_KEY_OUTPUT)
func _on_layer_button_down():
if _source == "":
_show_message("Please. Select source file first.")
return
var layers = _get_available_layers(ProjectSettings.globalize_path(_source))
_populate_options_field(_layer_field, layers, _layer)
func _on_layer_item_selected(index):
if index == 0:
_layer = ""
return
_layer = _layer_field.get_item_text(index)
_save_config()
func _on_slice_item_selected(index):
if index == 0:
_slice = ""
return
_slice = _slice_field.get_item_text(index)
_save_config()
func _on_slice_button_down():
if _source == "":
_show_message("Please, select source file first.")
return
var slices = _get_available_slices(ProjectSettings.globalize_path(_source))
var current = 0
_slice_field.clear()
_slice_field.add_item(_layer_default)
for s in slices:
if s == "":
continue
_slice_field.add_item(s)
if s == _slice:
current = _slice_field.get_item_count() - 1
_slice_field.select(current)
func _on_source_pressed():
_open_source_dialog()
##
## Save current import options to node metadata
##
func _save_config():
var child_config = _get_current_field_values()
var cfg := {
"source": _source,
"layer": _layer,
"slice": _slice,
"o_folder": _output_folder,
"o_name": _out_filename_field.text,
"only_visible": _visible_layers_field.button_pressed,
"o_ex_p": _ex_pattern_field.text,
}
for c in child_config:
cfg[c] = child_config[c]
wizard_config.save_config(target_node, cfg)
func _get_import_options(default_folder: String):
return {
"output_folder": _output_folder if _output_folder != "" else default_folder,
"exception_pattern": _ex_pattern_field.text,
"only_visible_layers": _visible_layers_field.button_pressed,
"output_filename": _out_filename_field.text,
"layer": _layer
}
func _open_source_dialog():
_file_dialog_aseprite = _create_aseprite_file_selection()
get_parent().add_child(_file_dialog_aseprite)
if _source != "":
_file_dialog_aseprite.current_dir = ProjectSettings.globalize_path(_source.get_base_dir())
_file_dialog_aseprite.popup_centered_ratio()
func _create_aseprite_file_selection():
var file_dialog = EditorFileDialog.new()
file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE
file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM
file_dialog.connect("file_selected",Callable(self,"_on_aseprite_file_selected"))
file_dialog.set_filters(PackedStringArray(["*.ase","*.aseprite"]))
return file_dialog
func _on_aseprite_file_selected(path):
_set_source(ProjectSettings.localize_path(path))
_save_config()
_file_dialog_aseprite.queue_free()
func _on_source_aseprite_file_dropped(path):
_set_source(path)
_save_config()
## Helper method to populate field with values
func _populate_options_field(field: OptionButton, values: Array, current_name: String):
var current = 0
field.clear()
field.add_item("[all]")
for v in values:
if v == "":
continue
field.add_item(v)
if v == current_name:
current = field.get_item_count() - 1
field.select(current)
func _on_out_folder_pressed():
_output_folder_dialog = _create_output_folder_selection()
get_parent().add_child(_output_folder_dialog)
if _output_folder != _out_folder_default:
_output_folder_dialog.current_dir = _output_folder
_output_folder_dialog.popup_centered_ratio()
func _create_output_folder_selection():
var file_dialog = EditorFileDialog.new()
file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_DIR
file_dialog.access = EditorFileDialog.ACCESS_RESOURCES
file_dialog.connect("dir_selected",Callable(self,"_on_output_folder_selected"))
return file_dialog
func _on_output_folder_selected(path):
_set_out_folder(path)
_output_folder_dialog.queue_free()
func _on_out_dir_dropped(path):
_set_out_folder(path)
func _show_message(message: String):
var _warning_dialog = AcceptDialog.new()
get_parent().add_child(_warning_dialog)
_warning_dialog.dialog_text = message
_warning_dialog.popup_centered()
_warning_dialog.connect("popup_hide",Callable(_warning_dialog,"queue_free"))
func _notify_aseprite_error(aseprite_error_code):
var error = result_code.get_error_message(aseprite_error_code)
printerr(error)
_show_message(error)
func _handle_cleanup(aseprite_content):
if config.should_remove_source_files():
DirAccess.remove_absolute(aseprite_content.data_file)
file_system.call_deferred("scan")
func _on_import_pressed():
if _importing:
return
_importing = true
if _source == "":
_show_message("Aseprite file not selected")
_importing = false
return
await _do_import()
_importing = false
$dock_fields.hide_source_change_warning()
EditorInterface.save_scene()
# This is a little bit leaky as this base scene contains fields only relevant to animation players.
# However, this is the simplest thing I can do without overcomplicating stuff.
func _hide_fields():
$dock_fields/VBoxContainer/modes.hide()
$dock_fields/VBoxContainer/animation_player.hide()
$dock_fields/VBoxContainer/extra/sections/animation.hide()
## this will be called before base class does its setup
func _pre_setup():
pass
## this will be called after base class setup is complete
func _setup():
pass
func _load_default_config():
pass
func _load_config(cfg: Dictionary):
pass
## Override to return available layers
func _get_available_layers(global_source_path: String) -> Array:
return []
## Override to return available slices
func _get_available_slices(global_source_path: String) -> Array:
return []
## Override this method for extra import options to add to node metadata
func _get_current_field_values() -> Dictionary:
return {}
func _do_import():
pass
func _show_specific_fields() -> void:
pass

View file

@ -0,0 +1,345 @@
[gd_scene load_steps=8 format=3 uid="uid://ljeu0l1ld6v5"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/base_inspector_dock.gd" id="1_0bpq8"]
[ext_resource type="PackedScene" uid="uid://x1f1t87m582u" path="res://addons/AsepriteWizard/interface/shared/animation_player_drop_button.tscn" id="2_pge1b"]
[ext_resource type="PackedScene" uid="uid://dj1uo3blocr8e" path="res://addons/AsepriteWizard/interface/shared/source_drop_button.tscn" id="3_nt1oj"]
[ext_resource type="PackedScene" uid="uid://cwvgnm3o7eed2" path="res://addons/AsepriteWizard/interface/shared/dir_drop_button.tscn" id="4_r7t2l"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_x6usu"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
bg_color = Color(0.225, 0.225, 0.225, 0.6)
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
corner_detail = 5
[sub_resource type="Image" id="Image_46k7x"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_dxtgh"]
image = SubResource("Image_46k7x")
[node name="base_inspector_dock" type="PanelContainer"]
offset_right = 14.0
offset_bottom = 14.0
script = ExtResource("1_0bpq8")
[node name="margin" type="MarginContainer" parent="."]
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="margin"]
layout_mode = 2
[node name="title" type="Button" parent="margin/VBoxContainer"]
layout_mode = 2
focus_mode = 0
theme_override_styles/normal = SubResource("StyleBoxFlat_x6usu")
button_mask = 0
text = "Aseprite"
[node name="section_title" type="PanelContainer" parent="margin/VBoxContainer"]
visible = false
layout_mode = 2
size_flags_horizontal = 3
[node name="HBoxContainer" type="HBoxContainer" parent="margin/VBoxContainer/section_title"]
layout_mode = 2
alignment = 1
[node name="icon" type="TextureRect" parent="margin/VBoxContainer/section_title/HBoxContainer"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
[node name="title" type="Label" parent="margin/VBoxContainer/section_title/HBoxContainer"]
layout_mode = 2
text = "Aseprite"
horizontal_alignment = 1
[node name="modes" type="HBoxContainer" parent="margin/VBoxContainer"]
layout_mode = 2
tooltip_text = "Import mode.
Animation mode (default): set spritesheet as texture and imports animations to the selected AnimationPlayer.
Image mode: Import only first frame and set as texture."
[node name="Label" type="Label" parent="margin/VBoxContainer/modes"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Mode"
[node name="options" type="OptionButton" parent="margin/VBoxContainer/modes"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
item_count = 2
selected = 0
popup/item_0/text = "Animation"
popup/item_0/id = 0
popup/item_1/text = "Image"
popup/item_1/id = 1
[node name="animation_player" type="HBoxContainer" parent="margin/VBoxContainer"]
layout_mode = 2
tooltip_text = "AnimationPlayer node where animations should be added to."
[node name="Label" type="Label" parent="margin/VBoxContainer/animation_player"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "AnimationPlayer"
[node name="options" parent="margin/VBoxContainer/animation_player" instance=ExtResource("2_pge1b")]
layout_mode = 2
[node name="source" type="HBoxContainer" parent="margin/VBoxContainer"]
layout_mode = 2
tooltip_text = "Location of the Aseprite (*.ase, *.aseprite) source file."
[node name="Label" type="Label" parent="margin/VBoxContainer/source"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Aseprite File"
[node name="button" parent="margin/VBoxContainer/source" instance=ExtResource("3_nt1oj")]
layout_mode = 2
[node name="extra" type="MarginContainer" parent="margin/VBoxContainer"]
layout_mode = 2
theme_override_constants/margin_left = 10
[node name="sections" type="VBoxContainer" parent="margin/VBoxContainer/extra"]
layout_mode = 2
[node name="layers" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections"]
layout_mode = 2
[node name="section_header" type="Button" parent="margin/VBoxContainer/extra/sections/layers"]
layout_mode = 2
focus_mode = 0
text = "Layers"
icon = SubResource("ImageTexture_dxtgh")
alignment = 0
[node name="section_content" type="MarginContainer" parent="margin/VBoxContainer/extra/sections/layers"]
visible = false
layout_mode = 2
theme_override_constants/margin_left = 10
[node name="content" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections/layers/section_content"]
layout_mode = 2
[node name="layer" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/layers/section_content/content"]
layout_mode = 2
tooltip_text = "Aseprite layer to be used in the animation. By default all layers are included."
[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/layer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Layer"
[node name="options" type="OptionButton" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/layer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
item_count = 1
selected = 0
popup/item_0/text = "[all]"
popup/item_0/id = 0
[node name="ex_pattern" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/layers/section_content/content"]
layout_mode = 2
tooltip_text = "Exclude layers with name matching this pattern (regex)."
[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/ex_pattern"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Exclude Pattern"
[node name="LineEdit" type="LineEdit" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/ex_pattern"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
[node name="visible_layers" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/layers/section_content/content"]
layout_mode = 2
tooltip_text = "If active, layers not visible in the source file won't be included in the final image."
[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/visible_layers"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Only Visible Layers"
[node name="CheckBox" type="CheckBox" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/visible_layers"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "On"
[node name="slices" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections"]
layout_mode = 2
[node name="section_header" type="Button" parent="margin/VBoxContainer/extra/sections/slices"]
layout_mode = 2
focus_mode = 0
text = "Slices"
icon = SubResource("ImageTexture_dxtgh")
alignment = 0
[node name="section_content" type="MarginContainer" parent="margin/VBoxContainer/extra/sections/slices"]
visible = false
layout_mode = 2
theme_override_constants/margin_left = 10
[node name="content" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections/slices/section_content"]
layout_mode = 2
[node name="slice" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/slices/section_content/content"]
layout_mode = 2
tooltip_text = "Aseprite slice to be used in the animation. By default, the whole file is included."
[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/slices/section_content/content/slice"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Slice"
[node name="options" type="OptionButton" parent="margin/VBoxContainer/extra/sections/slices/section_content/content/slice"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
item_count = 1
selected = 0
popup/item_0/text = "[all]"
popup/item_0/id = 0
[node name="animation" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections"]
layout_mode = 2
[node name="section_header" type="Button" parent="margin/VBoxContainer/extra/sections/animation"]
layout_mode = 2
focus_mode = 0
text = "Animation"
icon = SubResource("ImageTexture_dxtgh")
alignment = 0
[node name="section_content" type="MarginContainer" parent="margin/VBoxContainer/extra/sections/animation"]
visible = false
layout_mode = 2
theme_override_constants/margin_left = 10
[node name="content" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections/animation/section_content"]
layout_mode = 2
[node name="keep_length" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/animation/section_content/content"]
layout_mode = 2
tooltip_text = "When this is active the animation length won't be adjusted if other properties were added and the resulting imported animation is shorter."
[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/animation/section_content/content/keep_length"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Keep Manual Animation Length"
[node name="CheckBox" type="CheckBox" parent="margin/VBoxContainer/extra/sections/animation/section_content/content/keep_length"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "On"
[node name="auto_visible_track" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/animation/section_content/content"]
layout_mode = 2
tooltip_text = "If active, it will automatically determine unused Sprite2D and Sprite3D nodes in each animation and hide them."
[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/animation/section_content/content/auto_visible_track"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Hide Unused Sprites"
[node name="CheckBox" type="CheckBox" parent="margin/VBoxContainer/extra/sections/animation/section_content/content/auto_visible_track"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "On"
[node name="output" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections"]
layout_mode = 2
[node name="section_header" type="Button" parent="margin/VBoxContainer/extra/sections/output"]
layout_mode = 2
focus_mode = 0
text = "Output"
icon = SubResource("ImageTexture_dxtgh")
alignment = 0
[node name="section_content" type="MarginContainer" parent="margin/VBoxContainer/extra/sections/output"]
visible = false
layout_mode = 2
theme_override_constants/margin_left = 10
[node name="content" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections/output/section_content"]
layout_mode = 2
[node name="out_folder" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/output/section_content/content"]
layout_mode = 2
tooltip_text = "Location where the spritesheet file should be saved."
[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/output/section_content/content/out_folder"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Output Folder"
[node name="button" parent="margin/VBoxContainer/extra/sections/output/section_content/content/out_folder" instance=ExtResource("4_r7t2l")]
layout_mode = 2
[node name="out_filename" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/output/section_content/content"]
layout_mode = 2
tooltip_text = "Base filename for spritesheet. In case the layer option is used, this works as a prefix to the layer name."
[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/output/section_content/content/out_filename"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Output File Name"
[node name="LineEdit" type="LineEdit" parent="margin/VBoxContainer/extra/sections/output/section_content/content/out_filename"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
[node name="import" type="Button" parent="margin/VBoxContainer"]
layout_mode = 2
text = "Import"
[connection signal="item_selected" from="margin/VBoxContainer/modes/options" to="." method="_on_modes_item_selected"]
[connection signal="button_down" from="margin/VBoxContainer/animation_player/options" to="." method="_on_options_button_down"]
[connection signal="item_selected" from="margin/VBoxContainer/animation_player/options" to="." method="_on_options_item_selected"]
[connection signal="node_dropped" from="margin/VBoxContainer/animation_player/options" to="." method="_on_animation_player_node_dropped"]
[connection signal="aseprite_file_dropped" from="margin/VBoxContainer/source/button" to="." method="_on_source_aseprite_file_dropped"]
[connection signal="pressed" from="margin/VBoxContainer/source/button" to="." method="_on_source_pressed"]
[connection signal="button_down" from="margin/VBoxContainer/extra/sections/layers/section_header" to="." method="_on_layer_header_button_down"]
[connection signal="button_down" from="margin/VBoxContainer/extra/sections/layers/section_content/content/layer/options" to="." method="_on_layer_button_down"]
[connection signal="item_selected" from="margin/VBoxContainer/extra/sections/layers/section_content/content/layer/options" to="." method="_on_layer_item_selected"]
[connection signal="button_down" from="margin/VBoxContainer/extra/sections/slices/section_header" to="." method="_on_slice_header_button_down"]
[connection signal="button_down" from="margin/VBoxContainer/extra/sections/slices/section_content/content/slice/options" to="." method="_on_slice_button_down"]
[connection signal="item_selected" from="margin/VBoxContainer/extra/sections/slices/section_content/content/slice/options" to="." method="_on_slice_item_selected"]
[connection signal="button_down" from="margin/VBoxContainer/extra/sections/animation/section_header" to="." method="_on_animation_header_button_down"]
[connection signal="button_down" from="margin/VBoxContainer/extra/sections/output/section_header" to="." method="_on_output_header_button_down"]
[connection signal="dir_dropped" from="margin/VBoxContainer/extra/sections/output/section_content/content/out_folder/button" to="." method="_on_out_dir_dropped"]
[connection signal="pressed" from="margin/VBoxContainer/extra/sections/output/section_content/content/out_folder/button" to="." method="_on_out_folder_pressed"]
[connection signal="pressed" from="margin/VBoxContainer/import" to="." method="_on_import_pressed"]

View file

@ -0,0 +1,26 @@
@tool
extends MarginContainer
@onready
var _source_changed_warning_container = $VBoxContainer/source_changed_warning
@onready
var _source_changed_warning_icon = $VBoxContainer/source_changed_warning/MarginContainer/HBoxContainer/Icon
func _ready():
var sb = _source_changed_warning_container.get_theme_stylebox("panel")
var color = EditorInterface.get_editor_settings().get_setting("interface/theme/accent_color")
color.a = 0.2
sb.bg_color = color
_source_changed_warning_icon.texture = get_theme_icon("NodeInfo", "EditorIcons")
hide_source_change_warning()
func show_source_change_warning():
_source_changed_warning_container.show()
func hide_source_change_warning():
_source_changed_warning_container.hide()

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,14 @@
@tool
extends EditorInspectorPlugin
const APInspectorDock = preload("./sprite_inspector_dock.tscn")
func _can_handle(object):
return object is Sprite2D || object is Sprite3D || object is TextureRect
func _parse_end(object):
var dock = APInspectorDock.instantiate()
dock.target_node = object
add_custom_control(dock)

View file

@ -0,0 +1,255 @@
@tool
extends "../base_inspector_dock.gd"
const AnimationCreator = preload("../../../creators/animation_player/animation_creator.gd")
const SpriteAnimationCreator = preload("../../../creators/animation_player/sprite_animation_creator.gd")
const TextureRectAnimationCreator = preload("../../../creators/animation_player/texture_rect_animation_creator.gd")
const StaticTextureCreator = preload("../../../creators/static_texture/texture_creator.gd")
enum ImportMode {
ANIMATION = 0,
IMAGE = 1
}
var animation_creator: AnimationCreator
var static_texture_creator: StaticTextureCreator
var _import_mode = -1
var _animation_player_path: String
@onready var _import_mode_options_field := $dock_fields/VBoxContainer/modes/options as OptionButton
@onready var _animation_player_field := $dock_fields/VBoxContainer/animation_player/options as OptionButton
@onready var _animation_player_container := $dock_fields/VBoxContainer/animation_player as HBoxContainer
# animation
@onready var _animation_section := $dock_fields/VBoxContainer/extra/sections/animation as VBoxContainer
@onready var _animation_section_header := $dock_fields/VBoxContainer/extra/sections/animation/section_header as Button
@onready var _animation_section_container := $dock_fields/VBoxContainer/extra/sections/animation/section_content as MarginContainer
@onready var _cleanup_hide_unused_nodes := $dock_fields/VBoxContainer/extra/sections/animation/section_content/content/auto_visible_track/CheckBox as CheckBox
@onready var _keep_length := $dock_fields/VBoxContainer/extra/sections/animation/section_content/content/keep_length/CheckBox as CheckBox
const INTERFACE_SECTION_KEY_ANIMATION = "animation_section"
func _pre_setup():
_expandable_sections[INTERFACE_SECTION_KEY_ANIMATION] = { "header": _animation_section_header, "content": _animation_section_container}
func _setup():
if target_node is Sprite2D || target_node is Sprite3D:
animation_creator = SpriteAnimationCreator.new()
if target_node is TextureRect:
animation_creator = TextureRectAnimationCreator.new()
static_texture_creator = StaticTextureCreator.new()
_setup_animation_fields_listeners()
func _load_config(cfg):
if cfg.has("player"):
_animation_player_field.clear()
_set_animation_player(cfg.player)
_cleanup_hide_unused_nodes.button_pressed = cfg.get("set_vis_track", config.is_set_visible_track_automatically_enabled())
_keep_length.button_pressed = cfg.get("keep_anim_length", false)
_set_import_mode(int(cfg.get("i_mode", 0)))
func _load_default_config():
_cleanup_hide_unused_nodes.button_pressed = config.is_set_visible_track_automatically_enabled()
func _set_animation_player(player):
_animation_player_path = player
_animation_player_field.add_item(_animation_player_path)
func _set_import_mode(import_mode):
if _import_mode == import_mode:
return
_import_mode = import_mode
var index = _import_mode_options_field.get_item_index(import_mode)
_import_mode_options_field.select(index)
_handle_import_mode()
func _handle_import_mode():
match _import_mode:
ImportMode.ANIMATION:
_animation_player_container.show()
_animation_section.show()
ImportMode.IMAGE:
_animation_player_container.hide()
_animation_section.hide()
func _setup_animation_fields_listeners():
_animation_section_header.button_down.connect(_on_animation_header_button_down)
_animation_player_field.node_dropped.connect(_on_animation_player_node_dropped)
_animation_player_field.button_down.connect(_on_animation_player_button_down)
_animation_player_field.item_selected.connect(_on_animation_player_item_selected)
_import_mode_options_field.item_selected.connect(_on_modes_item_selected)
func _on_animation_player_button_down():
_refresh_animation_players()
func _refresh_animation_players():
var animation_players = []
var root = get_tree().get_edited_scene_root()
_find_animation_players(root, root, animation_players)
var current = 0
_animation_player_field.clear()
_animation_player_field.add_item("[empty]")
for ap in animation_players:
_animation_player_field.add_item(ap)
if ap.get_concatenated_names() == _animation_player_path:
current = _animation_player_field.get_item_count() - 1
_animation_player_field.select(current)
func _find_animation_players(root: Node, node: Node, players: Array):
if node is AnimationPlayer:
players.push_back(root.get_path_to(node))
for c in node.get_children():
_find_animation_players(root, c, players)
func _on_animation_player_item_selected(index):
if index == 0:
_animation_player_path = ""
return
_animation_player_path = _animation_player_field.get_item_text(index)
_save_config()
func _do_import():
if _import_mode == ImportMode.IMAGE:
await _import_static()
return
await _import_for_animation_player()
##
## Import aseprite animations to target AnimationPlayer and set
## spritesheet as the node's texture
##
func _import_for_animation_player():
var root = get_tree().get_edited_scene_root()
if _animation_player_path == "" or not root.has_node(_animation_player_path):
_show_message("AnimationPlayer not found")
_importing = false
return
var source_path = ProjectSettings.globalize_path(_source)
var options = _get_import_options(root.scene_file_path.get_base_dir())
_save_config()
var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source_path, options)
if not aseprite_output.is_ok:
_notify_aseprite_error(aseprite_output.code)
return
file_system.scan()
await file_system.filesystem_changed
var anim_options = {
"keep_anim_length": _keep_length.button_pressed,
"cleanup_hide_unused_nodes": _cleanup_hide_unused_nodes.button_pressed,
"slice": _slice,
}
animation_creator.create_animations(target_node, root.get_node(_animation_player_path), aseprite_output.content, anim_options)
_importing = false
wizard_config.set_source_hash(target_node, FileAccess.get_md5(source_path))
_handle_cleanup(aseprite_output.content)
##
## Import first frame from aseprite file as node texture
##
func _import_static():
var source_path = ProjectSettings.globalize_path(_source)
var root = get_tree().get_edited_scene_root()
var options = _get_import_options(root.scene_file_path.get_base_dir())
options["first_frame_only"] = true
_save_config()
var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source_path, options)
if not aseprite_output.is_ok:
_notify_aseprite_error(aseprite_output.code)
return
file_system.scan()
await file_system.filesystem_changed
static_texture_creator.load_texture(target_node, aseprite_output.content, { "slice": _slice })
_importing = false
wizard_config.set_source_hash(target_node, FileAccess.get_md5(source_path))
_handle_cleanup(aseprite_output.content)
func _get_current_field_values() -> Dictionary:
var cfg := {
"i_mode": _import_mode,
"player": _animation_player_path,
"keep_anim_length": _keep_length.button_pressed,
}
if _cleanup_hide_unused_nodes.button_pressed != config.is_set_visible_track_automatically_enabled():
cfg["set_vis_track"] = _cleanup_hide_unused_nodes.button_pressed
return cfg
func _get_available_layers(global_source_path: String) -> Array:
return animation_creator.list_layers(global_source_path)
func _get_available_slices(global_source_path: String) -> Array:
return animation_creator.list_slices(global_source_path)
func _on_animation_player_node_dropped(node_path):
var node = get_node(node_path)
var root = get_tree().get_edited_scene_root()
_animation_player_path = root.get_path_to(node)
for i in range(_animation_player_field.get_item_count()):
if _animation_player_field.get_item_text(i) == _animation_player_path:
_animation_player_field.select(i)
break
_save_config()
func _on_modes_item_selected(index):
var id = _import_mode_options_field.get_item_id(index)
_import_mode = id
_handle_import_mode()
func _on_animation_header_button_down():
_toggle_section_visibility(INTERFACE_SECTION_KEY_ANIMATION)
func _show_specific_fields():
_import_mode_options_field.get_parent().show()
_animation_player_container.show()
_animation_section.show()

View file

@ -0,0 +1,12 @@
[gd_scene load_steps=3 format=3 uid="uid://biyshalfalqqw"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.gd" id="1_ku6wj"]
[ext_resource type="PackedScene" uid="uid://uxm7b02wry10" path="res://addons/AsepriteWizard/interface/docks/dock_fields.tscn" id="2_wkqx4"]
[node name="sprite_inspector_dock" type="PanelContainer"]
offset_right = 14.0
offset_bottom = 14.0
script = ExtResource("1_ku6wj")
[node name="dock_fields" parent="." instance=ExtResource("2_wkqx4")]
layout_mode = 2

View file

@ -0,0 +1,39 @@
@tool
extends TabContainer
signal close_requested
const WizardWindow = preload("./as_wizard_window.tscn")
func _ready():
$Import.close_requested.connect(emit_signal.bind("close_requested"))
$Import.import_success.connect(_on_import_success)
$History.request_edit.connect(_on_edit_request)
$History.request_import.connect(_on_import_request)
$ImportedSpriteFrames.import_success.connect($History.add_entry)
self.set_tab_title(1, "Imported Resources")
func _on_AsWizardDockContainer_tab_changed(tab: int):
match tab:
1:
$ImportedSpriteFrames.init_resources()
2:
$History.reload()
func _on_edit_request(import_cfg: Dictionary):
$Import.load_import_config(import_cfg)
self.current_tab = 0
func _on_import_request(import_cfg: Dictionary):
$Import.load_import_config(import_cfg)
$Import.trigger_import()
func _on_import_success(settings: Dictionary):
$ImportedSpriteFrames.init_resources()
$ImportedSpriteFrames.reload_tree()
$History.add_entry(settings)

View file

@ -0,0 +1,28 @@
[gd_scene load_steps=5 format=3 uid="uid://b844j1tk3vxer"]
[ext_resource type="PackedScene" uid="uid://c5dwobjd34w3p" path="res://addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.tscn" id="1"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.gd" id="2"]
[ext_resource type="PackedScene" uid="uid://cyoin5ncul0fm" path="res://addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.tscn" id="3"]
[ext_resource type="PackedScene" uid="uid://bfhsdslj8kt7b" path="res://addons/AsepriteWizard/interface/docks/wizard/imported_sprite_frames.tscn" id="3_25qb4"]
[node name="AsWizardDockContainer" type="TabContainer"]
offset_right = 281.0
offset_bottom = 36.0
tab_alignment = 2
use_hidden_tabs_for_min_size = true
script = ExtResource("2")
[node name="Import" parent="." instance=ExtResource("1")]
layout_mode = 2
tooltip_text = "SpriteFrames Importer"
[node name="ImportedSpriteFrames" parent="." instance=ExtResource("3_25qb4")]
visible = false
layout_mode = 2
metadata/_tab_name = "Imported Resources"
[node name="History" parent="." instance=ExtResource("3")]
visible = false
layout_mode = 2
[connection signal="tab_changed" from="." to="." method="_on_AsWizardDockContainer_tab_changed"]

View file

@ -0,0 +1,274 @@
@tool
extends PanelContainer
signal close_requested
signal import_success(file_settings)
const result_code = preload("../../../config/result_codes.gd")
var _config = preload("../../../config/config.gd").new()
var _import_helper = preload("./wizard_import_helper.gd").new()
var _file_system: EditorFileSystem = EditorInterface.get_resource_filesystem()
var _file_dialog_aseprite: EditorFileDialog
var _output_folder_dialog: EditorFileDialog
var _warning_dialog: AcceptDialog
@onready var _layer_section_btn: Button = $container/options/layer_section/header/section_header
@onready var _layer_section_content: MarginContainer = $container/options/layer_section/section_content
@onready var _output_section_btn: Button = $container/options/output_section/header/section_header
@onready var _output_section_content: MarginContainer = $container/options/output_section/section_content
@onready var _file_location_field: LineEdit = $container/options/file_location/HBoxContainer/file_location_path
@onready var _output_folder_field: LineEdit = $container/options/output_section/section_content/items/output_folder/HBoxContainer/file_location_path
@onready var _exception_pattern_field: LineEdit = $container/options/layer_section/section_content/items/exclude_pattern/pattern
@onready var _split_mode_field: CheckBox = $container/options/layer_section/section_content/items/split_layers/field
@onready var _only_visible_layers_field: CheckBox = $container/options/layer_section/section_content/items/visible_layers/field
@onready var _custom_name_field: LineEdit = $container/options/output_section/section_content/items/custom_filename/pattern
@onready var _do_not_create_res_field: CheckBox = $container/options/output_section/section_content/items/disable_resource_creation/field
const INTERFACE_SECTION_KEY_LAYER = "layer_section"
const INTERFACE_SECTION_KEY_OUTPUT = "output_section"
@onready var _expandable_sections = {
INTERFACE_SECTION_KEY_LAYER: { "header": _layer_section_btn, "content": _layer_section_content},
INTERFACE_SECTION_KEY_OUTPUT: { "header": _output_section_btn, "content": _output_section_content},
}
var _interface_section_state = {}
func _ready():
_configure_sections()
_load_persisted_config()
func _exit_tree():
if is_instance_valid(_file_dialog_aseprite):
_file_dialog_aseprite.queue_free()
if is_instance_valid(_output_folder_dialog):
_output_folder_dialog.queue_free()
if is_instance_valid(_warning_dialog):
_warning_dialog.queue_free()
func _init_aseprite_file_selection_dialog():
_file_dialog_aseprite = _create_aseprite_file_selection()
get_parent().get_parent().add_child(_file_dialog_aseprite)
func _init_output_folder_selection_dialog():
_output_folder_dialog = _create_outuput_folder_selection()
get_parent().get_parent().add_child(_output_folder_dialog)
func _init_warning_dialog():
_warning_dialog = AcceptDialog.new()
_warning_dialog.exclusive = false
get_parent().get_parent().add_child(_warning_dialog)
func _load_persisted_config() -> void:
var cfg = _load_last_import_cfg()
_load_config(cfg)
func _load_config(cfg: Dictionary) -> void:
_split_mode_field.button_pressed = cfg.split_layers
_only_visible_layers_field.button_pressed = cfg.only_visible_layers
_exception_pattern_field.text = cfg.layer_exclusion_pattern
_custom_name_field.text = cfg.output_name
_file_location_field.text = cfg.source_file
_do_not_create_res_field.button_pressed = cfg.do_not_create_resource
_output_folder_field.text = cfg.output_location if cfg.output_location != "" else "res://"
func _save_config() -> void:
_config.set_standalone_spriteframes_last_import_config(_get_field_values())
func _get_field_values() -> Dictionary:
return {
"split_layers": _split_mode_field.button_pressed,
"only_visible_layers": _only_visible_layers_field.button_pressed,
"layer_exclusion_pattern": _exception_pattern_field.text,
"output_name": _custom_name_field.text,
"source_file": _file_location_field.text,
"do_not_create_resource": _do_not_create_res_field.button_pressed,
"output_location": _output_folder_field.text if _output_folder_field.text != "" else "res://",
}
func _clear_config() -> void:
_config.clear_standalone_spriteframes_last_import_config()
var default = _get_default_config()
_load_config(default)
## This is used by the other tabs to set the form fields
func load_import_config(field_values: Dictionary):
_split_mode_field.button_pressed = field_values.split_layers
_only_visible_layers_field.button_pressed = field_values.only_visible_layers
_exception_pattern_field.text = field_values.layer_exclusion_pattern
_custom_name_field.text = field_values.output_name
_file_location_field.text = field_values.source_file
_do_not_create_res_field.button_pressed = field_values.do_not_create_resource
_output_folder_field.text = field_values.output_location
func _open_aseprite_file_selection_dialog():
if not is_instance_valid(_file_dialog_aseprite):
_init_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():
if not is_instance_valid(_output_folder_dialog):
_init_output_folder_selection_dialog()
var current_selection = _output_folder_field.text
if current_selection != "":
_output_folder_dialog.current_dir = current_selection
_output_folder_dialog.popup_centered_ratio()
func _create_aseprite_file_selection():
var file_dialog = EditorFileDialog.new()
file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE
file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM
file_dialog.connect("file_selected", _on_aseprite_file_selected)
file_dialog.set_filters(PackedStringArray(["*.ase","*.aseprite"]))
return file_dialog
func _create_outuput_folder_selection():
var file_dialog = EditorFileDialog.new()
file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_DIR
file_dialog.access = EditorFileDialog.ACCESS_RESOURCES
file_dialog.connect("dir_selected", _on_output_folder_selected)
return file_dialog
func _on_aseprite_file_selected(path):
var localized_path = ProjectSettings.localize_path(path)
_file_location_field.text = localized_path
func _on_output_folder_selected(path):
_output_folder_field.text = path
func _on_next_btn_up():
var aseprite_file = _file_location_field.text
var fields = _get_field_values()
var exit_code = await _import_helper.import_and_create_resources(aseprite_file, _get_field_values())
if exit_code != OK:
_show_error(exit_code)
return
emit_signal("import_success", fields)
if _config.should_remove_source_files():
_file_system.call_deferred("scan")
_show_import_success_message()
func trigger_import():
_on_next_btn_up()
func _on_close_btn_up():
_close_window()
func _close_window():
_save_config()
self.emit_signal("close_requested")
func _on_clear_button_up():
_clear_config()
func _show_error(code: int):
_show_error_message(result_code.get_error_message(code))
func _show_error_message(message: String):
if not is_instance_valid(_warning_dialog):
_init_warning_dialog()
_warning_dialog.dialog_text = "Error: %s" % message
_warning_dialog.popup_centered()
func _show_import_success_message():
if not is_instance_valid(_warning_dialog):
_init_warning_dialog()
_warning_dialog.dialog_text = "Aseprite import succeeded"
_warning_dialog.popup_centered()
_save_config()
func _configure_sections():
for key in _expandable_sections:
_adjust_section_visibility(key)
func _adjust_section_visibility(key: String) -> void:
var section = _expandable_sections[key]
var is_visible = _interface_section_state.get(key, true)
_adjust_icon(section.header, is_visible)
section.content.visible = is_visible
func _adjust_icon(section: Button, is_visible: bool = true) -> void:
var icon_name = "GuiTreeArrowDown" if is_visible else "GuiTreeArrowRight"
section.icon = get_theme_icon(icon_name, "EditorIcons")
func _toggle_section_visibility(key: String) -> void:
_interface_section_state[key] = not _interface_section_state.get(key, true)
_adjust_section_visibility(key)
func _on_layer_section_header_button_down():
_toggle_section_visibility(INTERFACE_SECTION_KEY_LAYER)
func _on_output_section_header_button_down():
_toggle_section_visibility(INTERFACE_SECTION_KEY_OUTPUT)
func _load_last_import_cfg() -> Dictionary:
var cfg = _config.get_standalone_spriteframes_last_import_config()
if cfg.is_empty():
return _get_default_config()
return cfg
func _get_default_config() -> Dictionary:
return {
"split_layers": false,
"only_visible_layers": _config.should_include_only_visible_layers_by_default(),
"layer_exclusion_pattern": _config.get_default_exclusion_pattern(),
"output_name": "",
"source_file": "",
"do_not_create_resource": false,
"output_location": "res://",
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,304 @@
@tool
extends PanelContainer
signal import_success(fields)
const result_code = preload("../../../config/result_codes.gd")
const wizard_config = preload("../../../config/wizard_config.gd")
var _import_helper = preload("./wizard_import_helper.gd").new()
@onready var _tree_container = $MarginContainer/HSplitContainer/tree
@onready var _resource_tree = _tree_container.get_resource_tree()
@onready var _nothing_container = $MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/nothing
@onready var _single_item_container = $MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/single_item
@onready var _multiple_items_container = $MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/multiple_items
@onready var _confirmation_warning_container = $MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/confirmation_warning
var _selection_count = 0
var _current_container = null
var _resources_to_process = null
var _is_loaded = false
var _groups = {}
func init_resources():
if _is_loaded:
return
_is_loaded = true
var file_tree = _get_file_tree("res://")
_setup_tree(file_tree)
func _get_file_tree(base_path: String, dir_name: String = "") -> Dictionary:
var dir_path = base_path.path_join(dir_name)
var dir = DirAccess.open(dir_path)
var dir_data = { "path": dir_path, "name": dir_name, "children": [], "type": "dir", }
if not dir:
return dir_data
dir.list_dir_begin()
var file_name = dir.get_next()
while file_name != "":
if dir.current_is_dir() and _is_importable_folder(dir_path, file_name):
var child_data = _get_file_tree(dir_path, file_name)
if not child_data.children.is_empty():
dir_data.children.push_back(child_data)
elif file_name.ends_with(".res"):
var resource_path = dir_path.path_join(file_name)
var resource = ResourceLoader.load(resource_path)
if resource is SpriteFrames:
if resource.has_meta(wizard_config.WIZARD_CONFIG_META_NAME):
var meta = resource.get_meta(wizard_config.WIZARD_CONFIG_META_NAME)
var parent_node = dir_data
if meta.group != "":
var group
if _groups.has(meta.group):
group = _groups[meta.group]
else:
group = { "folders": {} }
_groups[meta.group] = group
if not group.folders.has(dir_path):
group.folders[dir_path] = {
"node": {
"type": "group",
"name": "Split: %s" % meta.fields.source_file.get_file(),
"has_moved": meta.fields.output_location != dir_path,
"path": meta.fields.source_file,
"folder": dir_path,
"has_changes": _has_source_changes(resource, meta),
"children": [],
}
}
parent_node.children.push_back(group.folders[dir_path].node)
parent_node = group.folders[dir_path].node
parent_node.children.push_back({
"type": "resource",
"resource_type": "SpriteFrames",
"name": resource.resource_path.get_file(),
"path": resource.resource_path,
"meta": meta,
"has_changes": _has_source_changes(resource, meta),
"has_moved": meta.fields.output_location != dir_path,
})
file_name = dir.get_next()
return dir_data
func _is_importable_folder(dir_path: String, dir_name: String) -> bool:
return dir_path != "res://" or dir_name != "addons"
func _setup_tree(resource_tree: Dictionary) -> void:
var root = _resource_tree.create_item()
_add_items_to_tree(root, resource_tree.children)
func _add_items_to_tree(root: TreeItem, children: Array):
for node in children:
var item: TreeItem = _resource_tree.create_item(root)
item.set_text(0, node.name)
item.set_meta("node", node)
match node.type:
"dir":
item.set_icon(0, get_theme_icon("Folder", "EditorIcons"))
_add_items_to_tree(item, node.children)
#
"group":
item.set_icon(0, get_theme_icon("CompressedTexture2D", "EditorIcons"))
_add_items_to_tree(item, node.children)
if node.has_changes:
item.set_text(0, "%s (*)" % node.name)
if node.has_moved:
item.set_suffix(0, "(moved)")
"resource":
item.set_icon(0, get_theme_icon(node.resource_type, "EditorIcons"))
if node.has_changes:
item.set_text(0, "%s (*)" % node.name)
if node.meta.group != "":
item.set_custom_color(0, item.get_icon_modulate(0).darkened(0.5))
item.set_selectable(0, false)
elif node.has_moved:
item.set_suffix(0, "(moved)")
func _has_source_changes(resource: Object, meta: Dictionary) -> bool:
var current_hash = FileAccess.get_md5(meta.fields.source_file)
var saved_hash = wizard_config.get_source_hash(resource)
return saved_hash != current_hash
func _is_supported_type(resource_type: String) -> bool:
return resource_type == "SpriteFrames"
func reload_tree():
_confirmation_warning_container.hide()
_resources_to_process = null
if _current_container != null:
_current_container.show_buttons()
_current_container = null
_groups = {}
_selection_count = 0
_resource_tree.clear()
var file_tree = _get_file_tree("res://")
_setup_tree(file_tree)
func _set_empty_details_state():
_nothing_container.show()
_single_item_container.hide()
_multiple_items_container.hide()
func _on_tree_multi_selected(item: TreeItem, column: int, selected: bool):
_confirmation_warning_container.hide()
_resources_to_process = null
if _current_container != null:
_current_container.show_buttons()
#
if selected:
_selection_count += 1
else:
_selection_count -= 1
#
_nothing_container.hide()
_single_item_container.hide()
_multiple_items_container.hide()
#
match _selection_count:
0:
_nothing_container.show()
1:
_single_item_container.show()
_set_item_details(_resource_tree.get_selected())
_current_container = _single_item_container
_:
_multiple_items_container.show()
_multiple_items_container.set_selected_count(_selection_count)
_current_container = _multiple_items_container
func _set_item_details(item: TreeItem) -> void:
if not item.has_meta("node"):
return
var data = item.get_meta("node")
_single_item_container.set_resource_details(data)
func _on_single_item_import_triggered():
var selected = _resource_tree.get_selected()
var meta = selected.get_meta("node")
match meta.type:
"dir":
var selected_item = _resource_tree.get_selected()
var all_resources = []
var scenes_to_open = _set_all_resources(selected_item.get_meta("node"), all_resources)
_resources_to_process = all_resources
_show_confirmation_message(all_resources.size())
"resource":
var code = await _do_import(meta.path, meta.meta)
_set_tree_item_as_saved(_resource_tree.get_selected())
_single_item_container.hide_source_change_warning()
if code == OK:
EditorInterface.get_resource_filesystem().scan()
"group":
var first_item = meta.children[0]
var code = await _do_import(first_item.path, first_item.meta)
_set_tree_item_as_saved(selected)
_single_item_container.hide_source_change_warning()
if code == OK:
EditorInterface.get_resource_filesystem().scan()
_on_tree_refresh_triggered()
func _on_confirmation_warning_warning_confirmed():
_confirmation_warning_container.hide()
_current_container.show_buttons()
for resource in _resources_to_process:
await _do_import(resource.path, resource.meta)
_resources_to_process = null
EditorInterface.get_resource_filesystem().scan()
_on_tree_refresh_triggered()
func _on_confirmation_warning_warning_declined():
_confirmation_warning_container.hide()
_current_container.show_buttons()
_resources_to_process = null
func _on_tree_refresh_triggered():
_set_empty_details_state()
reload_tree()
func _set_tree_item_as_saved(item: TreeItem) -> void:
var meta = item.get_meta("node")
meta.has_changes = false
meta.has_moved = false
item.set_meta("node", meta)
item.set_text(0, meta.name)
item.set_suffix(0, "")
func _do_import(resource_path: String, metadata: Dictionary) -> int:
var resource_base_dir = resource_path.get_base_dir()
if resource_base_dir != metadata.fields.output_location:
print("Resource has moved. Changing output folder from %s to %s" % [resource_base_dir, metadata.fields.output_location])
metadata.fields.output_location = resource_base_dir
var exit_code := await _import_helper.import_and_create_resources(metadata.fields.source_file, metadata.fields)
if exit_code == OK:
print("Import complete: %s" % resource_path)
import_success.emit(metadata.fields)
else:
printerr("Failed to import %s. Error: %s" % [resource_path, result_code.get_error_message(exit_code)])
return exit_code
func _set_all_resources(meta: Dictionary, resources: Array):
match meta.type:
"dir":
for c in meta.children:
_set_all_resources(c, resources)
"resource":
if not resources.has(meta):
resources.push_back(meta)
"group":
var first_item = meta.children[0]
resources.push_back(first_item)
func _show_confirmation_message(resources: int):
_current_container.hide_buttons()
_confirmation_warning_container.set_message("You are about to re-import %s resources. Do you wish to continue?" % resources)
_confirmation_warning_container.show()
func _on_multiple_items_import_triggered():
var selected_item = _resource_tree.get_next_selected(null)
var all_resources = []
while selected_item != null:
_set_all_resources(selected_item.get_meta("node"), all_resources)
selected_item = _resource_tree.get_next_selected(selected_item)
_resources_to_process = all_resources
_show_confirmation_message(all_resources.size())

View file

@ -0,0 +1,67 @@
[gd_scene load_steps=6 format=3 uid="uid://bfhsdslj8kt7b"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/imported_sprite_frames.gd" id="1_dk2ra"]
[ext_resource type="PackedScene" uid="uid://cisgsfvp4nf1g" path="res://addons/AsepriteWizard/interface/shared/tree/resource_tree.tscn" id="2_svrqo"]
[ext_resource type="PackedScene" uid="uid://q7eyyg2kvvv2" path="res://addons/AsepriteWizard/interface/docks/wizard/resource_tree_single_item.tscn" id="3_pe1cg"]
[ext_resource type="PackedScene" uid="uid://fscemkx5w1dw" path="res://addons/AsepriteWizard/interface/docks/wizard/resource_tree_multiple_items.tscn" id="4_vteew"]
[ext_resource type="PackedScene" uid="uid://qgmln507kjnm" path="res://addons/AsepriteWizard/interface/shared/tree/tree_selection_confirmation_warning.tscn" id="5_jigim"]
[node name="ImportedSpriteFrames" type="PanelContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_dk2ra")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
[node name="HSplitContainer" type="HSplitContainer" parent="MarginContainer"]
layout_mode = 2
[node name="tree" parent="MarginContainer/HSplitContainer" instance=ExtResource("2_svrqo")]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/HSplitContainer"]
custom_minimum_size = Vector2(200, 0)
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/HSplitContainer/MarginContainer"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/MarginContainer"]
layout_mode = 2
text = "Details"
horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="nothing" type="Label" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 6
text = "Nothing selected"
horizontal_alignment = 1
[node name="single_item" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer" instance=ExtResource("3_pe1cg")]
visible = false
layout_mode = 2
[node name="multiple_items" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer" instance=ExtResource("4_vteew")]
visible = false
layout_mode = 2
[node name="confirmation_warning" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer" instance=ExtResource("5_jigim")]
visible = false
layout_mode = 2
[connection signal="multi_selected" from="MarginContainer/HSplitContainer/tree" to="." method="_on_tree_multi_selected"]
[connection signal="refresh_triggered" from="MarginContainer/HSplitContainer/tree" to="." method="_on_tree_refresh_triggered"]
[connection signal="import_triggered" from="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/single_item" to="." method="_on_single_item_import_triggered"]
[connection signal="import_triggered" from="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/multiple_items" to="." method="_on_multiple_items_import_triggered"]
[connection signal="warning_confirmed" from="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/confirmation_warning" to="." method="_on_confirmation_warning_warning_confirmed"]
[connection signal="warning_declined" from="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/confirmation_warning" to="." method="_on_confirmation_warning_warning_declined"]

View file

@ -0,0 +1,22 @@
@tool
extends VBoxContainer
signal import_triggered
@onready var _import_message = $message
@onready var _import_button = $buttons
func set_selected_count(number_of_items: int) -> void:
_import_message.text = "%2d items selected" % number_of_items
func show_buttons():
_import_button.show()
func hide_buttons():
_import_button.hide()
func _on_import_selected_button_up():
import_triggered.emit()

View file

@ -0,0 +1,24 @@
[gd_scene load_steps=2 format=3 uid="uid://fscemkx5w1dw"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/resource_tree_multiple_items.gd" id="1_lg81l"]
[node name="multiple_items" type="VBoxContainer"]
size_flags_vertical = 3
script = ExtResource("1_lg81l")
[node name="message" type="Label" parent="."]
layout_mode = 2
size_flags_vertical = 6
text = "Multiple items selected
"
horizontal_alignment = 1
[node name="buttons" type="HFlowContainer" parent="."]
layout_mode = 2
[node name="import_selected" type="Button" parent="buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Re-Import all"
[connection signal="button_up" from="buttons/import_selected" to="." method="_on_import_selected_button_up"]

View file

@ -0,0 +1,171 @@
@tool
extends VBoxContainer
signal import_triggered
@onready var _file_name = $GridContainer/file_name_value
@onready var _type = $GridContainer/type_value
@onready var _path = $GridContainer/path_value
@onready var _source_label = $GridContainer/source_file_label
@onready var _source = $GridContainer/source_file_value
@onready var _only_visible_layers_label = $GridContainer/only_visible_layers_label
@onready var _only_visible_layers = $GridContainer/only_visible_layers_value
@onready var _layer_ex_pattern_label = $GridContainer/layer_ex_patt_label
@onready var _layer_ex_pattern = $GridContainer/layer_ex_patt_value
@onready var _o_name_label = $GridContainer/o_name_label
@onready var _o_name = $GridContainer/o_name_value
@onready var _o_folder_label = $GridContainer/o_folder_label
@onready var _o_folder_value = $GridContainer/o_folder_value
@onready var _resource_list_label = $GridContainer/resource_list_label
@onready var _resource_list = $GridContainer/resource_list
@onready var _resource_list_separator_1 = $GridContainer/HSeparator3
@onready var _resource_list_separator_2 = $GridContainer/HSeparator4
@onready var _resource_buttons = $resource_buttons
@onready var _dir_buttons = $dir_buttons
@onready var _group_buttons = $group_buttons
@onready var _source_change_warning = $source_changed_warning
@onready var _resource_only_fields = [
_source_label,
_source,
_only_visible_layers_label,
_only_visible_layers,
_layer_ex_pattern_label,
_layer_ex_pattern,
_o_name_label,
_o_name,
_o_folder_label,
_o_folder_value,
_source_change_warning,
]
var _current_resource_type = "resource"
var _resource_config: Dictionary = {}
func _ready():
_source_change_warning.set_text("Source file changed since last import")
_source_change_warning.hide()
func set_resource_details(resource_details: Dictionary) -> void:
_resource_config = resource_details
_resource_buttons.hide()
_dir_buttons.hide()
_group_buttons.hide()
_hide_resource_list()
_source_change_warning.hide()
_file_name.text = resource_details.name
_path.text = resource_details.path
_current_resource_type = resource_details.type
match resource_details.type:
"resource":
_type.text = resource_details.resource_type
_show_resource_fields()
_resource_buttons.show()
var fields = resource_details.meta.fields
_load_fields(fields)
_resource_buttons.show()
_source_change_warning.visible = resource_details.has_changes
"group":
_type.text = "Split Group"
_show_resource_fields()
_load_fields(resource_details.children[0].meta.fields)
_source_change_warning.visible = resource_details.children[0].has_changes
_group_buttons.show()
_show_resource_list()
for c in _resource_list.get_children():
c.queue_free()
for child_resource in resource_details.children:
var label = Label.new()
label.text = child_resource.name
_resource_list.add_child(label)
_:
_type.text = "Folder"
_hide_resource_fields()
_dir_buttons.show()
return
func _load_fields(fields: Dictionary):
_only_visible_layers.text = "Yes" if fields.only_visible_layers else "No"
_layer_ex_pattern.text = fields.layer_exclusion_pattern
_o_name.text = fields.output_name
_o_folder_value.text = fields.output_location
_source.text = fields.source_file
func _hide_resource_fields():
for f in _resource_only_fields:
f.hide()
func _show_resource_fields():
for f in _resource_only_fields:
f.show()
func show_buttons():
match _current_resource_type:
"resource":
_resource_buttons.show()
_:
_dir_buttons.show()
func hide_buttons():
_resource_buttons.hide()
_dir_buttons.hide()
func hide_source_change_warning():
_source_change_warning.hide()
func _on_show_in_fs_button_up():
EditorInterface.get_file_system_dock().navigate_to_path(_path.text)
func _on_show_dir_in_fs_button_up():
EditorInterface.get_file_system_dock().navigate_to_path(_path.text)
func _on_import_all_button_up():
import_triggered.emit()
func _on_import_button_up():
import_triggered.emit()
func _hide_resource_list():
_resource_list_separator_1.hide()
_resource_list_separator_2.hide()
_resource_list_label.hide()
_resource_list.hide()
func _show_resource_list():
_resource_list_separator_1.show()
_resource_list_separator_2.show()
_resource_list_label.show()
_resource_list.show()
func _on_import_all_pressed():
import_triggered.emit()
func _on_show_in_fs_pressed():
EditorInterface.get_file_system_dock().navigate_to_path(_resource_config.children[0].path)

View file

@ -0,0 +1,172 @@
[gd_scene load_steps=3 format=3 uid="uid://q7eyyg2kvvv2"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/resource_tree_single_item.gd" id="1_h1q4t"]
[ext_resource type="PackedScene" uid="uid://c1l0bk12iwln3" path="res://addons/AsepriteWizard/interface/shared/tree/inline_warning_panel.tscn" id="2_hmv61"]
[node name="single_item" type="VBoxContainer"]
script = ExtResource("1_h1q4t")
[node name="GridContainer" type="GridContainer" parent="."]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/h_separation = 10
columns = 2
[node name="type_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Type"
[node name="type_value" type="Label" parent="GridContainer"]
layout_mode = 2
text = "-"
[node name="file_name_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "File"
[node name="file_name_value" type="Label" parent="GridContainer"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
text = "-"
autowrap_mode = 1
[node name="path_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Path"
[node name="path_value" type="Label" parent="GridContainer"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "-"
autowrap_mode = 1
[node name="HSeparator" type="HSeparator" parent="GridContainer"]
layout_mode = 2
[node name="HSeparator2" type="HSeparator" parent="GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="source_file_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Aseprite File"
[node name="source_file_value" type="Label" parent="GridContainer"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "-"
autowrap_mode = 1
[node name="only_visible_layers_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Only Visible Layers"
[node name="only_visible_layers_value" type="Label" parent="GridContainer"]
layout_mode = 2
text = "-"
[node name="layer_ex_patt_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Slice"
[node name="layer_ex_patt_value" type="Label" parent="GridContainer"]
layout_mode = 2
text = "-"
[node name="o_folder_label" type="Label" parent="GridContainer"]
visible = false
layout_mode = 2
size_flags_vertical = 0
text = "Output Folder"
[node name="o_folder_value" type="Label" parent="GridContainer"]
visible = false
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "-"
autowrap_mode = 2
[node name="o_name_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Output Name"
[node name="o_name_value" type="Label" parent="GridContainer"]
layout_mode = 2
text = "-"
[node name="HSeparator3" type="HSeparator" parent="GridContainer"]
layout_mode = 2
[node name="HSeparator4" type="HSeparator" parent="GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="resource_list_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Resources"
[node name="resource_list" type="VBoxContainer" parent="GridContainer"]
layout_mode = 2
[node name="source_changed_warning" parent="." instance=ExtResource("2_hmv61")]
layout_mode = 2
[node name="resource_buttons" type="HFlowContainer" parent="."]
visible = false
layout_mode = 2
[node name="import" type="Button" parent="resource_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Re-Import"
[node name="show_in_fs" type="Button" parent="resource_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Show In FileSystem"
[node name="group_buttons" type="HFlowContainer" parent="."]
visible = false
layout_mode = 2
[node name="import_all" type="Button" parent="group_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Re-Import all"
[node name="show_in_fs" type="Button" parent="group_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Show In FileSystem"
[node name="dir_buttons" type="HFlowContainer" parent="."]
visible = false
layout_mode = 2
[node name="import_all" type="Button" parent="dir_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Re-Import all"
[node name="show_dir_in_fs" type="Button" parent="dir_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Show In FileSystem"
[connection signal="button_up" from="resource_buttons/import" to="." method="_on_import_button_up"]
[connection signal="button_up" from="resource_buttons/show_in_fs" to="." method="_on_show_in_fs_button_up"]
[connection signal="pressed" from="group_buttons/import_all" to="." method="_on_import_all_pressed"]
[connection signal="pressed" from="group_buttons/show_in_fs" to="." method="_on_show_in_fs_pressed"]
[connection signal="button_up" from="dir_buttons/import_all" to="." method="_on_import_all_button_up"]
[connection signal="button_up" from="dir_buttons/show_dir_in_fs" to="." method="_on_show_dir_in_fs_button_up"]

View file

@ -0,0 +1,260 @@
@tool
extends PanelContainer
signal request_edit(import_cfg)
signal request_import(import_cfg)
const SourcePathField = preload("./wizard_nodes/source_path.tscn")
const OutputPathField = preload("./wizard_nodes/output_path.tscn")
const ImportDateField = preload("./wizard_nodes/import_date.tscn")
const ItemActions = preload("./wizard_nodes/list_actions.tscn")
const DetailsField = preload("./wizard_nodes/details.tscn")
const SORT_BY_DATE := 0
const SORT_BY_PATH := 1
const INITIAL_GRID_INDEX := 4
var _config = preload("../../../config/config.gd").new()
var _history: Array
var _history_nodes := {}
var _history_nodes_list := []
var _is_busy := false
var _import_requested_for := -1
var _sort_by = SORT_BY_DATE
@onready var grid = $MarginContainer/VBoxContainer/ScrollContainer/GridContainer
@onready var loading_warning = $MarginContainer/VBoxContainer/loading_warning
@onready var no_history_warning = $MarginContainer/VBoxContainer/no_history_warning
func reload():
if _history:
return
if _config.has_old_history():
_migrate_history()
_history = _config.get_import_history()
for index in range(_history.size()):
var entry = _history[index]
_create_node_list_entry(entry, index)
loading_warning.hide()
if _history.is_empty():
no_history_warning.show()
else:
grid.get_parent().show()
func _create_node_list_entry(entry: Dictionary, index: int):
_add_to_node_list(entry, _create_nodes(entry, index))
func _create_nodes(entry: Dictionary, index: int) -> Dictionary:
var import_date = ImportDateField.instantiate()
import_date.set_date(entry.import_date)
var source_path = SourcePathField.instantiate()
source_path.set_entry(entry)
var output_path = OutputPathField.instantiate()
output_path.text = entry.output_location
output_path.tooltip_text = entry.output_location
var details = DetailsField.instantiate()
details.set_details(entry)
var actions = ItemActions.instantiate()
actions.history_index = index
actions.connect("import_clicked",Callable(self,"_on_entry_reimport_clicked"))
actions.connect("edit_clicked",Callable(self,"_on_entry_edit_clicked"))
actions.connect("removed_clicked",Callable(self,"_on_entry_remove_clicked"))
grid.get_child(INITIAL_GRID_INDEX).add_sibling(import_date)
import_date.add_sibling(source_path)
source_path.add_sibling(output_path)
output_path.add_sibling(details)
details.add_sibling(actions)
return {
"history_index": index,
"timestamp": entry.import_date,
"source_file": entry.source_file,
"source_path_node": source_path,
"output_path_node": output_path,
"import_date_node": import_date,
"actions_node": actions,
"details_node": details,
}
func _add_to_node_list(entry: Dictionary, node: Dictionary):
if not _history_nodes.has(entry.source_file):
_history_nodes[entry.source_file] = []
_history_nodes[entry.source_file].push_front(node)
_history_nodes_list.push_front(node)
func add_entry(file_settings: Dictionary):
if _history == null:
reload()
#
var dt = Time.get_datetime_dict_from_system()
file_settings["import_date"] = "%04d-%02d-%02d %02d:%02d:%02d" % [dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second]
#
if _import_requested_for != -1:
_remove_item(_import_requested_for)
_import_requested_for = -1
elif _history.size() > _config.get_history_max_entries():
_remove_entries(_history[0].source_file, 0)
#
_history.push_back(file_settings)
_config.save_import_history(_history)
_create_node_list_entry(file_settings, _history.size() - 1)
if _sort_by == SORT_BY_PATH:
_trigger_sort()
no_history_warning.hide()
loading_warning.hide()
grid.get_parent().show()
_is_busy = false
func _on_entry_reimport_clicked(entry_index: int):
if _is_busy:
return
_is_busy = true
_import_requested_for = entry_index
emit_signal("request_import", _history[entry_index])
func _on_entry_edit_clicked(entry_index: int):
if _is_busy:
return
emit_signal("request_edit", _history[entry_index])
func _on_entry_remove_clicked(entry_index: int):
if _is_busy:
return
_is_busy = true
_remove_item(entry_index)
_config.save_import_history(_history)
if (_history.is_empty()):
grid.get_parent().hide()
no_history_warning.show()
_is_busy = false
func _remove_item(entry_index: int):
var entry = _history[entry_index]
_remove_entries(entry.source_file, entry_index)
# removes nodes and entry from history. If entry_index is not provided, all
# entries for path are removed.
func _remove_entries(source_file_path: String, entry_index: int = -1):
var files_entries = _history_nodes[source_file_path]
var indexes_to_remove = []
for f in files_entries:
if entry_index == -1 or f.history_index == entry_index:
_free_entry_nodes(f)
_history_nodes_list.erase(f)
if entry_index != -1:
files_entries.erase(f)
_remove_from_history(f.history_index)
return
indexes_to_remove.push_back(f.history_index)
for i in indexes_to_remove:
_remove_from_history(i)
_history_nodes[source_file_path] = []
func _remove_from_history(entry_index: int):
var _already_adjusted = []
# to avoid re-creating the whole nodes list, I just decrement
# the index from items newer than the excluded one
for index in range(entry_index + 1, _history.size()):
if _already_adjusted.has(_history[index].source_file):
continue
_already_adjusted.push_back(_history[index].source_file)
var nodes = _history_nodes[_history[index].source_file]
for f in nodes:
if f.history_index > entry_index:
f.history_index -= 1
f.actions_node.history_index = f.history_index
_history.remove_at(entry_index)
func _free_entry_nodes(entry_history_node: Dictionary):
entry_history_node.source_path_node.queue_free()
entry_history_node.output_path_node.queue_free()
entry_history_node.import_date_node.queue_free()
entry_history_node.actions_node.queue_free()
entry_history_node.details_node.queue_free()
func _on_SortOptions_item_selected(index):
if index == _sort_by:
return
_trigger_sort(index)
func _trigger_sort(sort_type: int = _sort_by):
if sort_type == SORT_BY_DATE:
_history_nodes_list.sort_custom(Callable(self,"_sort_by_date"))
else:
_history_nodes_list.sort_custom(Callable(self,"_sort_by_path"))
_reorganise_nodes()
_sort_by = sort_type
func _sort_by_date(a, b):
return a.timestamp < b.timestamp
func _sort_by_path(a, b):
return a.source_file > b.source_file
func _reorganise_nodes():
for entry in _history_nodes_list:
grid.move_child(entry.import_date_node, INITIAL_GRID_INDEX + 1)
grid.move_child(entry.source_path_node, INITIAL_GRID_INDEX + 2)
grid.move_child(entry.output_path_node, INITIAL_GRID_INDEX + 3)
grid.move_child(entry.details_node, INITIAL_GRID_INDEX + 4)
grid.move_child(entry.actions_node, INITIAL_GRID_INDEX + 5)
func _migrate_history():
var history = _config.get_old_import_history()
var new_history = []
for index in range(history.size()):
var entry = history[index]
new_history.push_back({
"split_layers": true if entry.options.export_mode else false,
"only_visible_layers": entry.options.only_visible_layers,
"layer_exclusion_pattern": entry.options.exception_pattern,
"output_name": entry.options.output_filename,
"source_file": entry.source_file,
"do_not_create_resource": entry.options.do_not_create_resource,
"output_location": entry.output_location,
"import_date": entry.import_date,
})
_config.save_import_history(new_history)
_config.remove_old_history_setting()

View file

@ -0,0 +1,85 @@
[gd_scene load_steps=2 format=3 uid="uid://cyoin5ncul0fm"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.gd" id="1"]
[node name="SpriteFramesImportHistory" type="PanelContainer"]
anchors_preset = 10
anchor_right = 1.0
grow_horizontal = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
[node name="list actions" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 0
alignment = 2
[node name="divider" type="Label" parent="MarginContainer/VBoxContainer/list actions"]
layout_mode = 2
[node name="sort_label" type="Label" parent="MarginContainer/VBoxContainer/list actions"]
layout_mode = 2
text = "Sort by:"
[node name="SortOptions" type="OptionButton" parent="MarginContainer/VBoxContainer/list actions"]
layout_mode = 2
item_count = 2
selected = 0
popup/item_0/text = "Date"
popup/item_0/id = 0
popup/item_1/text = "Source File"
popup/item_1/id = 1
[node name="loading_warning" type="Label" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 6
text = "Loading..."
[node name="no_history_warning" type="Label" parent="MarginContainer/VBoxContainer"]
visible = false
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 6
text = "No import history"
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer"]
visible = false
layout_mode = 2
size_flags_vertical = 3
[node name="GridContainer" type="GridContainer" parent="MarginContainer/VBoxContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
columns = 5
[node name="date_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"]
layout_mode = 2
text = "Date"
[node name="source_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Source File"
[node name="output_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"]
layout_mode = 2
text = "Output Folder"
[node name="details_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"]
layout_mode = 2
text = "Details"
[node name="actions_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"]
layout_mode = 2
text = "Actions"
[connection signal="item_selected" from="MarginContainer/VBoxContainer/list actions/SortOptions" to="." method="_on_SortOptions_item_selected"]

View file

@ -0,0 +1,69 @@
@tool
extends Node
const wizard_meta = preload("../../../config/wizard_config.gd")
const result_code = preload("../../../config/result_codes.gd")
var _aseprite_file_exporter = preload("../../../aseprite/file_exporter.gd").new()
var _sf_creator = preload("../../../creators/sprite_frames/sprite_frames_creator.gd").new()
var _config = preload("../../../config/config.gd").new()
var _file_system: EditorFileSystem = EditorInterface.get_resource_filesystem()
# fields
# "split_layers"
# "only_visible_layers"
# "layer_exclusion_pattern"
# "output_name"
# "source_file"
# "do_not_create_resource"
# "output_location"
func import_and_create_resources(aseprite_file: String, fields: Dictionary) -> int:
var export_mode = _aseprite_file_exporter.LAYERS_EXPORT_MODE if fields.split_layers else _aseprite_file_exporter.FILE_EXPORT_MODE
var options = {
"export_mode": export_mode,
"exception_pattern": fields.layer_exclusion_pattern,
"only_visible_layers": fields.only_visible_layers,
"output_filename": fields.output_name,
"do_not_create_resource": fields.do_not_create_resource,
"output_folder": fields.output_location,
}
var aseprite_output = _aseprite_file_exporter.generate_aseprite_files(
ProjectSettings.globalize_path(aseprite_file),
options
)
if not aseprite_output.is_ok:
return aseprite_output.code
_file_system.scan()
await _file_system.filesystem_changed
var exit_code = OK
if !options.get("do_not_create_resource", false):
var resources = _sf_creator.create_resources(aseprite_output.content)
if resources.is_ok:
_add_metadata(resources.content, aseprite_file, fields, options)
exit_code = _sf_creator.save_resources(resources.content)
if _config.should_remove_source_files():
_remove_source_files(aseprite_output.content)
return exit_code
func _add_metadata(resources: Array, aseprite_file: String, fields: Dictionary, options: Dictionary) -> void:
var source_hash = FileAccess.get_md5(aseprite_file)
var group = str(ResourceUID.create_id()) if options.export_mode == _aseprite_file_exporter.LAYERS_EXPORT_MODE else ""
for r in resources:
wizard_meta.set_source_hash(r.resource, source_hash)
wizard_meta.save_config(r.resource, { "fields": fields, "group": group })
func _remove_source_files(source_files: Array):
for s in source_files:
DirAccess.remove_absolute(s.data_file)

View file

@ -0,0 +1,42 @@
@tool
extends VBoxContainer
@onready var _details_btn = $label
@onready var _details_container = $MarginContainer/GridContainer
@onready var _split_layers_field = $MarginContainer/GridContainer/split_layers
@onready var _only_visible_layers = $MarginContainer/GridContainer/only_visible_layers
@onready var _layer_exclusion_pattern = $MarginContainer/GridContainer/layer_exclusion_pattern
@onready var _output_name = $MarginContainer/GridContainer/output_name
@onready var _do_not_create_resource = $MarginContainer/GridContainer/do_not_create_resource
var _entry
func _ready():
_adjust_icon(false)
_details_container.hide()
_load_fields()
func set_details(entry: Dictionary):
_entry = entry
func _load_fields():
_split_layers_field.text = "Yes" if _entry.split_layers else "No"
_only_visible_layers.text = "Yes" if _entry.only_visible_layers else "No"
_layer_exclusion_pattern.text = _entry.layer_exclusion_pattern
_output_name.text = _entry.output_name
_output_name.text = _entry.output_name
_do_not_create_resource.text = "Yes" if _entry.do_not_create_resource else "No"
func _adjust_icon(is_visible: bool) -> void:
var icon_name = "GuiTreeArrowDown" if is_visible else "GuiTreeArrowRight"
_details_btn.icon = get_theme_icon(icon_name, "EditorIcons")
func _on_label_pressed():
_details_container.visible = not _details_container.visible
_adjust_icon(_details_container.visible)

View file

@ -0,0 +1,60 @@
[gd_scene load_steps=2 format=3 uid="uid://b1jb5yierm2bq"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/details.gd" id="1_1v4dc"]
[node name="Details" type="VBoxContainer"]
offset_right = 40.0
offset_bottom = 40.0
script = ExtResource("1_1v4dc")
[node name="label" type="Button" parent="."]
layout_mode = 2
text = "Details"
flat = true
alignment = 0
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
theme_override_constants/margin_left = 10
[node name="GridContainer" type="GridContainer" parent="MarginContainer"]
layout_mode = 2
theme_override_constants/h_separation = 5
columns = 2
[node name="split_layers_label" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
text = "Split layers:"
[node name="split_layers" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
[node name="only_visible_layers_label" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
text = "Only visible layers:"
[node name="only_visible_layers" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
[node name="layer_exclusion_pattern_label" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
text = "Layer Ex Pattern:"
[node name="layer_exclusion_pattern" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
[node name="output_name_label" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
text = "Output name:"
[node name="output_name" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
[node name="do_not_create_resource_label" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
text = "Do not create resource:"
[node name="do_not_create_resource" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
[connection signal="pressed" from="label" to="." method="_on_label_pressed"]

View file

@ -0,0 +1,6 @@
@tool
extends Label
func set_date(timestamp: String):
self.text = timestamp

View file

@ -0,0 +1,12 @@
[gd_scene load_steps=2 format=3 uid="uid://bs0i357m5d7ho"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.gd" id="1"]
[node name="import_date" type="Label"]
offset_left = 660.0
offset_top = 39.0
offset_right = 774.0
offset_bottom = 53.0
size_flags_vertical = 0
text = "2022-07-18 18:01"
script = ExtResource("1")

View file

@ -0,0 +1,20 @@
@tool
extends HBoxContainer
signal import_clicked(index)
signal edit_clicked(index)
signal removed_clicked(index)
var history_index = -1
func _on_edit_pressed():
emit_signal("edit_clicked", history_index)
func _on_reimport_pressed():
emit_signal("import_clicked", history_index)
func _on_remove_pressed():
emit_signal("removed_clicked", history_index)

View file

@ -0,0 +1,35 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/list_actions.gd" id="1"]
[node name="actions" type="HBoxContainer"]
offset_left = 784.0
offset_top = 34.0
offset_right = 950.0
offset_bottom = 58.0
custom_constants/separation = 5
script = ExtResource( 1 )
[node name="edit" type="Button" parent="."]
offset_right = 36.0
offset_bottom = 20.0
size_flags_vertical = 0
text = "Edit"
[node name="reimport" type="Button" parent="."]
offset_left = 41.0
offset_right = 97.0
offset_bottom = 20.0
size_flags_vertical = 0
text = "Import"
[node name="remove_at" type="Button" parent="."]
offset_left = 102.0
offset_right = 166.0
offset_bottom = 20.0
size_flags_vertical = 0
text = "Remove"
[connection signal="pressed" from="edit" to="." method="_on_edit_pressed"]
[connection signal="pressed" from="reimport" to="." method="_on_reimport_pressed"]
[connection signal="pressed" from="remove_at" to="." method="_on_remove_pressed"]

View file

@ -0,0 +1,12 @@
[gd_scene format=2]
[node name="output_path" type="LineEdit"]
offset_left = 450.0
offset_top = 34.0
offset_right = 650.0
offset_bottom = 58.0
minimum_size = Vector2( 200, 0 )
size_flags_vertical = 0
text = "/some/output"
align = 3
editable = false

View file

@ -0,0 +1,6 @@
@tool
extends LineEdit
func set_entry(entry: Dictionary):
self.text = entry.source_file
self.tooltip_text = entry.source_file

View file

@ -0,0 +1,18 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/source_path.gd" id="1"]
[node name="source_path" type="LineEdit"]
offset_top = 34.0
offset_right = 440.0
offset_bottom = 58.0
minimum_size = Vector2( 200, 0 )
tooltip_text = "Output name / prefix: some_name
Ex. Pattern: $_
Split: Yes
Only Visible: No
No Resource: No"
size_flags_vertical = 0
text = "/some/very/long/source/path/to/test/field"
editable = false
script = ExtResource( 1 )

View file

@ -0,0 +1,5 @@
@tool
extends Window
func _on_close_requested():
self.queue_free()

View file

@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3 uid="uid://0x3mlxsex7eb"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/imports_manager/aseprite_imports_manager.gd" id="1_wrm15"]
[node name="AsepriteDockImportsWindow" type="Window"]
title = "Aseprite Imports Manager"
initial_position = 1
size = Vector2i(612, 458)
wrap_controls = true
transient = true
min_size = Vector2i(600, 400)
script = ExtResource("1_wrm15")
[connection signal="close_requested" from="." to="." method="_on_close_requested"]

View file

@ -0,0 +1,330 @@
@tool
extends Panel
const wizard_config = preload("../../config/wizard_config.gd")
var _import_helper = preload("./import_helper.gd").new()
@onready var _tree_container = $MarginContainer/VBoxContainer/HSplitContainer/tree
@onready var _resource_tree = _tree_container.get_resource_tree()
@onready var _details = $MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer
@onready var _nothing_container = $MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/nothing
@onready var _single_item_container = $MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/single_item
@onready var _multiple_items_container = $MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/multiple_items
@onready var _confirmation_warning_container = $MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/confirmation_warning
const supported_types = [
"Sprite2D",
"Sprite3D",
"AnimatedSprite2D",
"AnimatedSprite3D",
"TextureRect",
]
var _selection_count = 0
var _current_buttons_container
var _resources_to_process
var _should_save_in = 0
func _ready():
_set_empty_details_state()
var file_tree = _get_file_tree("res://")
_setup_tree(file_tree)
# Unfortunately godot throws some nasty warnings when trying to save after
# multiple import operations. I implemented this late save as a workaround
func _process(delta):
if _should_save_in > 0:
_should_save_in -= delta
if _should_save_in <= 0:
_should_save_in = 0
_save_all_scenes()
func _get_file_tree(base_path: String, dir_name: String = "") -> Dictionary:
var dir_path = base_path.path_join(dir_name)
var dir = DirAccess.open(dir_path)
var dir_data = { "path": dir_path, "name": dir_name, "children": [], "type": "dir", }
if not dir:
return dir_data
dir.list_dir_begin()
var file_name = dir.get_next()
while file_name != "":
if dir.current_is_dir() and _is_importable_folder(dir_path, file_name):
var child_data = _get_file_tree(dir_path, file_name)
if not child_data.children.is_empty():
dir_data.children.push_back(child_data)
elif file_name.ends_with(".tscn"):
var file_path = dir_path.path_join(file_name)
var metadata = _get_aseprite_metadata(file_path)
if not metadata.is_empty():
dir_data.children.push_back({
"name": file_name,
"path": file_path,
"resources": metadata,
"type": "file",
})
file_name = dir.get_next()
return dir_data
func _is_importable_folder(dir_path: String, dir_name: String) -> bool:
return dir_path != "res://" or dir_name != "addons"
func _setup_tree(resource_tree: Dictionary) -> void:
_resource_tree.set_column_title(0, "Resource")
var root = _resource_tree.create_item()
_add_items_to_tree(root, resource_tree.children)
func _add_items_to_tree(root: TreeItem, children: Array):
for node in children:
var item: TreeItem = _resource_tree.create_item(root)
item.set_text(0, node.name)
item.set_meta("node", node)
match node.type:
"dir":
item.set_icon(0, get_theme_icon("Folder", "EditorIcons"))
_add_items_to_tree(item, node.children)
"file":
item.set_icon(0, get_theme_icon("PackedScene", "EditorIcons"))
_add_items_to_tree(item, node.resources)
"resource":
item.set_icon(0, get_theme_icon(node.node_type, "EditorIcons"))
if node.has_changes:
item.set_text(0, "%s (*)" % node.name)
func _get_aseprite_metadata(file_path: String) -> Array:
var scene: PackedScene = load(file_path)
var root = scene.instantiate()
var state = scene.get_state()
var resources = []
for i in range(state.get_node_count()):
var node_type = state.get_node_type(i)
if _is_supported_type(node_type):
var node_path = state.get_node_path(i)
var target_node = root.get_node(node_path)
var meta = wizard_config.load_config(target_node)
if meta != null:
resources.push_back({
"type": "resource",
"node_type": node_type,
"name": node_path,
"node_path": node_path,
"node_name": state.get_node_name(i),
"meta": meta,
"scene_path": file_path,
"has_changes": _has_source_changes(target_node, meta.get("source"))
})
return resources
func _has_source_changes(target_node: Node, source_path: String) -> bool:
if not source_path or source_path == "":
return false
var saved_hash = wizard_config.get_source_hash(target_node)
if saved_hash == "":
return false
var current_hash = FileAccess.get_md5(source_path)
return saved_hash != current_hash
func _is_supported_type(node_type: String) -> bool:
return supported_types.has(node_type)
func _open_scene(item: TreeItem) -> void:
var meta = item.get_meta("node")
if meta:
EditorInterface.open_scene_from_path(meta.path)
func _trigger_import(meta: Dictionary) -> void:
# A more elegant way would have been to change the PackedScene directly, however
# during my attempts changing external resources this way was buggy. I decided
# to open and edit the scene via editor with the caveat of having to keep it open afterwards.
EditorInterface.open_scene_from_path(meta.scene_path)
var root_node = EditorInterface.get_edited_scene_root()
if not root_node:
printerr("couldn´t open scene %s" % meta.scene_path)
await _import_helper.import_node(root_node, meta)
print("Import complete: %s (%s) node from %s" % [ meta.node_path, meta.meta.source, meta.scene_path])
func _on_resource_tree_multi_selected(_item: TreeItem, _column: int, selected: bool) -> void:
_confirmation_warning_container.hide()
_resources_to_process = null
if _current_buttons_container != null:
_current_buttons_container.show_buttons()
if selected:
_selection_count += 1
else:
_selection_count -= 1
_nothing_container.hide()
_single_item_container.hide()
_multiple_items_container.hide()
match _selection_count:
0:
_nothing_container.show()
1:
_single_item_container.show()
_set_item_details(_resource_tree.get_selected())
_current_buttons_container = _single_item_container
_:
_multiple_items_container.show()
_multiple_items_container.set_selected_count(_selection_count)
_current_buttons_container = _multiple_items_container
func _set_item_details(item: TreeItem) -> void:
if not item.has_meta("node"):
return
var data = item.get_meta("node")
_single_item_container.set_resource_details(data)
func _on_multiple_items_import_triggered():
var selected_item = _resource_tree.get_next_selected(null)
var all_resources = []
var scenes_to_open = 0
while selected_item != null:
scenes_to_open += _set_all_resources(selected_item.get_meta("node"), all_resources)
selected_item = _resource_tree.get_next_selected(selected_item)
_resources_to_process = all_resources
_show_confirmation_message(scenes_to_open, all_resources.size())
func _on_single_item_import_triggered():
var selected = _resource_tree.get_selected()
var meta = selected.get_meta("node")
if meta.type == "resource":
await _trigger_import(_resource_tree.get_selected().get_meta("node"))
_set_tree_item_as_saved(_resource_tree.get_selected())
_single_item_container.hide_source_change_warning()
EditorInterface.save_scene()
else:
var selected_item = _resource_tree.get_selected()
var all_resources = []
var scenes_to_open = _set_all_resources(selected_item.get_meta("node"), all_resources)
_resources_to_process = all_resources
_show_confirmation_message(scenes_to_open, all_resources.size())
func _on_single_item_open_scene_triggered():
var selected_item = _resource_tree.get_selected()
var meta = selected_item.get_meta("node")
if meta.type == "file":
EditorInterface.open_scene_from_path(meta.path)
else:
EditorInterface.open_scene_from_path(meta.scene_path)
func _set_all_resources(meta: Dictionary, resources: Array):
var scenes_to_open = 0
match meta.type:
"dir":
for c in meta.children:
scenes_to_open += _set_all_resources(c, resources)
"file":
scenes_to_open += 1
for r in meta.resources:
if not resources.has(r):
resources.push_back(r)
"resource":
if not resources.has(meta):
resources.push_back(meta)
return scenes_to_open
func _save_all_scenes():
EditorInterface.save_all_scenes()
_reload_tree()
func _show_confirmation_message(scenes: int, resources: int):
_current_buttons_container.hide_buttons()
if scenes > 1:
_confirmation_warning_container.set_message("You are about to open %s scenes and re-import %s resources. Do you wish to continue?" % [scenes, resources])
else:
_confirmation_warning_container.set_message("You are about to re-import %s resources. Do you wish to continue?" % resources)
_confirmation_warning_container.show()
func _on_resource_tree_refresh_triggered():
_set_empty_details_state()
_reload_tree()
func _reload_tree():
_confirmation_warning_container.hide()
_resources_to_process = null
if _current_buttons_container != null:
_current_buttons_container.show_buttons()
_current_buttons_container = null
_selection_count = 0
_resource_tree.clear()
var file_tree = _get_file_tree("res://")
_setup_tree(file_tree)
func _set_empty_details_state():
_nothing_container.show()
_single_item_container.hide()
_multiple_items_container.hide()
_confirmation_warning_container.hide()
func _set_tree_item_as_saved(item: TreeItem) -> void:
var meta = item.get_meta("node")
meta.has_changes = false
item.set_meta("node", meta)
item.set_text(0, meta.name)
func _on_confirmation_warning_warning_confirmed():
_confirmation_warning_container.hide()
_current_buttons_container.show_buttons()
for resource in _resources_to_process:
await _trigger_import(resource)
EditorInterface.mark_scene_as_unsaved()
_resources_to_process = null
_should_save_in = 1
func _on_confirmation_warning_warning_declined():
_confirmation_warning_container.hide()
_current_buttons_container.show_buttons()
_resources_to_process = null

View file

@ -0,0 +1,87 @@
[gd_scene load_steps=7 format=3 uid="uid://ci67r2f2btg5"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/imports_manager/dock_imports_panel.gd" id="1_1xyeb"]
[ext_resource type="PackedScene" uid="uid://cisgsfvp4nf1g" path="res://addons/AsepriteWizard/interface/shared/tree/resource_tree.tscn" id="2_d1s4o"]
[ext_resource type="PackedScene" uid="uid://qgmln507kjnm" path="res://addons/AsepriteWizard/interface/shared/tree/tree_selection_confirmation_warning.tscn" id="3_2us73"]
[ext_resource type="PackedScene" uid="uid://dnlk2yep7teea" path="res://addons/AsepriteWizard/interface/imports_manager/tree_selection_single_item.tscn" id="3_4ufqa"]
[ext_resource type="PackedScene" uid="uid://bhtu6mlwmthqo" path="res://addons/AsepriteWizard/interface/imports_manager/tree_selection_multiple_items.tscn" id="3_pw8ds"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_edrvq"]
[node name="dock_imports" type="Panel"]
custom_minimum_size = Vector2(600, 400)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_styles/panel = SubResource("StyleBoxEmpty_edrvq")
script = ExtResource("1_1xyeb")
[node name="MarginContainer" type="MarginContainer" parent="."]
custom_minimum_size = Vector2(400, 300)
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
[node name="HSplitContainer" type="HSplitContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="tree" parent="MarginContainer/VBoxContainer/HSplitContainer" instance=ExtResource("2_d1s4o")]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/HSplitContainer"]
custom_minimum_size = Vector2(200, 0)
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/MarginContainer"]
layout_mode = 2
text = "Details"
horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="nothing" type="Label" parent="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 6
text = "Nothing selected"
horizontal_alignment = 1
[node name="single_item" parent="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer" instance=ExtResource("3_4ufqa")]
layout_mode = 2
[node name="multiple_items" parent="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer" instance=ExtResource("3_pw8ds")]
layout_mode = 2
[node name="confirmation_warning" parent="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer" instance=ExtResource("3_2us73")]
layout_mode = 2
[connection signal="multi_selected" from="MarginContainer/VBoxContainer/HSplitContainer/tree" to="." method="_on_resource_tree_multi_selected"]
[connection signal="refresh_triggered" from="MarginContainer/VBoxContainer/HSplitContainer/tree" to="." method="_on_resource_tree_refresh_triggered"]
[connection signal="import_triggered" from="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/single_item" to="." method="_on_single_item_import_triggered"]
[connection signal="open_scene_triggered" from="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/single_item" to="." method="_on_single_item_open_scene_triggered"]
[connection signal="import_triggered" from="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/multiple_items" to="." method="_on_multiple_items_import_triggered"]
[connection signal="warning_confirmed" from="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/confirmation_warning" to="." method="_on_confirmation_warning_warning_confirmed"]
[connection signal="warning_declined" from="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/confirmation_warning" to="." method="_on_confirmation_warning_warning_declined"]

View file

@ -0,0 +1,123 @@
@tool
extends RefCounted
const wizard_config = preload("../../config/wizard_config.gd")
const result_code = preload("../../config/result_codes.gd")
var _sprite_animation_creator = preload("../../creators/animation_player/sprite_animation_creator.gd").new()
var _texture_rect_animation_creator = preload("../../creators/animation_player/texture_rect_animation_creator.gd").new()
var _static_texture_creator = preload("../../creators/static_texture/texture_creator.gd").new()
var _sprite_frames_creator = preload("../../creators/sprite_frames/sprite_frames_creator.gd").new()
var _aseprite_file_exporter = preload("../../aseprite/file_exporter.gd").new()
var _config = preload("../../config/config.gd").new()
func import_node(root_node: Node, meta: Dictionary) -> void:
var node = root_node.get_node(meta.node_path)
if node == null:
printerr("Node not found: %s" % meta.node_path)
return
if node is AnimatedSprite2D or node is AnimatedSprite3D:
await _sprite_frames_import(node, meta)
else:
await _animation_import(node, root_node, meta)
func _sprite_frames_import(node: Node, resource_config: Dictionary) -> void:
var config = resource_config.meta
if not config.source:
printerr("Node config missing information.")
return
var source = ProjectSettings.globalize_path(config.source)
var options = _parse_import_options(config, resource_config.scene_path.get_base_dir())
var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source, options)
if not aseprite_output.is_ok:
printerr(result_code.get_error_message(aseprite_output.code))
return
EditorInterface.get_resource_filesystem().scan()
await EditorInterface.get_resource_filesystem().filesystem_changed
_sprite_frames_creator.create_animations(node, aseprite_output.content, { "slice": options.slice })
wizard_config.set_source_hash(node, FileAccess.get_md5(source))
_handle_cleanup(aseprite_output.content)
func _animation_import(node: Node, root_node: Node, resource_config: Dictionary) -> void:
if not resource_config.meta.source:
printerr("Node config missing information.")
return
if resource_config.meta.get("i_mode", 0) == 0:
await _import_to_animation_player(node, root_node, resource_config)
else:
await _import_static(node, resource_config)
func _import_to_animation_player(node: Node, root: Node, resource_config: Dictionary) -> void:
var config = resource_config.meta
var source = ProjectSettings.globalize_path(config.source)
var options = _parse_import_options(config, resource_config.scene_path.get_base_dir())
var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source, options)
if not aseprite_output.is_ok:
printerr(result_code.get_error_message(aseprite_output.code))
return
EditorInterface.get_resource_filesystem().scan()
await EditorInterface.get_resource_filesystem().filesystem_changed
var anim_options = {
"keep_anim_length": config.keep_anim_length,
"cleanup_hide_unused_nodes": config.get("set_vis_track"),
"slice": config.get("slice", ""),
}
var animation_creator = _texture_rect_animation_creator if node is TextureRect else _sprite_animation_creator
animation_creator.create_animations(node, root.get_node(config.player), aseprite_output.content, anim_options)
wizard_config.set_source_hash(node, FileAccess.get_md5(source))
_handle_cleanup(aseprite_output.content)
func _import_static(node: Node, resource_config: Dictionary) -> void:
var config = resource_config.meta
var source = ProjectSettings.globalize_path(config.source)
var options = _parse_import_options(config, resource_config.scene_path.get_base_dir())
options["first_frame_only"] = true
var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source, options)
if not aseprite_output.is_ok:
printerr(result_code.get_error_message(aseprite_output.code))
return
EditorInterface.get_resource_filesystem().scan()
await EditorInterface.get_resource_filesystem().filesystem_changed
_static_texture_creator.load_texture(node, aseprite_output.content, { "slice": options.slice })
wizard_config.set_source_hash(node, FileAccess.get_md5(source))
_handle_cleanup(aseprite_output.content)
func _parse_import_options(config: Dictionary, scene_base_path: String) -> Dictionary:
return {
"output_folder": config.o_folder if config.o_folder != "" else scene_base_path,
"exception_pattern": config.o_ex_p,
"only_visible_layers": config.only_visible,
"output_filename": config.o_name,
"layer": config.get("layer"),
"slice": config.get("slice", ""),
}
func _handle_cleanup(aseprite_content):
if _config.should_remove_source_files():
DirAccess.remove_absolute(aseprite_content.data_file)
EditorInterface.get_resource_filesystem().scan()

View file

@ -0,0 +1,28 @@
@tool
extends MarginContainer
signal dock_requested
enum Tabs {
DOCK_IMPORTS = 0,
}
@onready var _tabs: TabContainer = $TabContainer
@onready var _dock_button: Button = $dock_button
func _ready():
_tabs.set_tab_title(Tabs.DOCK_IMPORTS, "Dock Imports")
_dock_button.icon = get_theme_icon("MakeFloating", "EditorIcons")
set_as_floating()
func _on_dock_button_pressed():
dock_requested.emit()
func set_as_floating():
_dock_button.tooltip_text = "Dock window"
func set_as_docked():
_dock_button.tooltip_text = "Undock window"

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,22 @@
@tool
extends LineEdit
signal change_finished(text: String)
@export var debounce_time_in_seconds: float = 0.3
var _time_since_last_change: float = 0.0
var _has_pending_changes: bool = false
func _process(delta: float) -> void:
if _has_pending_changes:
_time_since_last_change += delta
if _time_since_last_change > debounce_time_in_seconds:
_has_pending_changes = false
change_finished.emit(self.text)
func _on_text_changed(_new_text: String) -> void:
_has_pending_changes = true
_time_since_last_change = 0

View file

@ -0,0 +1,22 @@
@tool
extends VBoxContainer
signal import_triggered
@onready var _import_message = $message
@onready var _import_button = $buttons
func set_selected_count(number_of_items: int) -> void:
_import_message.text = "%2d items selected" % number_of_items
func show_buttons():
_import_button.show()
func hide_buttons():
_import_button.hide()
func _on_import_selected_button_up():
import_triggered.emit()

View file

@ -0,0 +1,25 @@
[gd_scene load_steps=2 format=3 uid="uid://bhtu6mlwmthqo"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/imports_manager/tree_selection_multiple_items.gd" id="1_beamo"]
[node name="multiple_items" type="VBoxContainer"]
visible = false
size_flags_vertical = 3
script = ExtResource("1_beamo")
[node name="message" type="Label" parent="."]
layout_mode = 2
size_flags_vertical = 6
text = "Multiple items selected
"
horizontal_alignment = 1
[node name="buttons" type="HFlowContainer" parent="."]
layout_mode = 2
[node name="import_selected" type="Button" parent="buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Re-Import all"
[connection signal="button_up" from="buttons/import_selected" to="." method="_on_import_selected_button_up"]

View file

@ -0,0 +1,141 @@
@tool
extends VBoxContainer
signal import_triggered
signal open_scene_triggered
@onready var _name = $GridContainer/name_value
@onready var _type = $GridContainer/type_value
@onready var _path = $GridContainer/path_value
@onready var _source_label = $GridContainer/source_file_label
@onready var _source = $GridContainer/source_file_value
@onready var _layer_label = $GridContainer/layer_label
@onready var _layer = $GridContainer/layer_value
@onready var _slice_label = $GridContainer/slice_label
@onready var _slice = $GridContainer/slice_value
@onready var _o_name_label = $GridContainer/o_name_label
@onready var _o_name = $GridContainer/o_name_value
@onready var _o_folder_label = $GridContainer/o_folder_label
@onready var _o_folder_value = $GridContainer/o_folder_value
@onready var _resource_buttons = $resource_buttons
@onready var _dir_buttons = $dir_buttons
@onready var _scene_buttons = $scene_buttons
@onready var _source_change_warning = $source_changed_warning
@onready var _resource_only_fields = [
_source_label,
_source,
_layer_label,
_layer,
_slice_label,
_slice,
_o_name_label,
_o_name,
_o_folder_label,
_o_folder_value,
_source_change_warning,
]
var _current_resource_type = "resource"
var _resource_config: Dictionary = {}
func _ready():
_source_change_warning.set_text("Source file changed since last import")
_source_change_warning.hide()
func set_resource_details(resource_details: Dictionary) -> void:
_resource_config = resource_details
_resource_buttons.hide()
_dir_buttons.hide()
_scene_buttons.hide()
_source_change_warning.hide()
_current_resource_type = resource_details.type
match resource_details.type:
"dir":
_name.text = resource_details.name
_type.text = "Folder"
_path.text = resource_details.path
_hide_resource_fields()
_dir_buttons.show()
"file":
_name.text = resource_details.name
_type.text = "File"
_path.text = resource_details.path
_hide_resource_fields()
_scene_buttons.show()
"resource":
_name.text = resource_details.node_name
_type.text = resource_details.node_type
_path.text = resource_details.node_path
var meta = resource_details.meta
_source.text = meta.source
_layer.text = "All" if meta.get("layer", "") == "" else meta.layer
_slice.text = "All" if meta.get("slice", "") == "" else meta.slice
var folder = resource_details.scene_path.get_base_dir() if meta.get("o_folder", "") == "" else meta.o_folder
var file_name = "" if meta.get("o_name", "") == "" else meta.o_name
if _layer.text != "All":
file_name += _layer.text
elif file_name == "":
file_name = meta.source.get_basename().get_file()
_o_name.text = "%s/%s.png" % [folder, file_name]
_show_resource_fields()
_resource_buttons.show()
_source_change_warning.visible = resource_details.has_changes
func _hide_resource_fields():
for f in _resource_only_fields:
f.hide()
func _show_resource_fields():
for f in _resource_only_fields:
f.show()
func show_buttons():
match _current_resource_type:
"resource":
_resource_buttons.show()
"scene":
_scene_buttons.show()
_:
_dir_buttons.show()
func hide_buttons():
_resource_buttons.hide()
_dir_buttons.hide()
_scene_buttons.hide()
func hide_source_change_warning():
_source_change_warning.hide()
func _on_show_dir_in_fs_button_up():
EditorInterface.get_file_system_dock().navigate_to_path(_path.text)
func _on_import_all_button_up():
import_triggered.emit()
func _on_import_button_up():
import_triggered.emit()
func _on_open_scene_button_up():
open_scene_triggered.emit()

View file

@ -0,0 +1,158 @@
[gd_scene load_steps=3 format=3 uid="uid://dnlk2yep7teea"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/imports_manager/tree_selection_single_item.gd" id="1_bb3ui"]
[ext_resource type="PackedScene" uid="uid://c1l0bk12iwln3" path="res://addons/AsepriteWizard/interface/shared/tree/inline_warning_panel.tscn" id="2_weuqf"]
[node name="single_item" type="VBoxContainer"]
visible = false
script = ExtResource("1_bb3ui")
[node name="GridContainer" type="GridContainer" parent="."]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/h_separation = 10
columns = 2
[node name="type_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Type"
[node name="type_value" type="Label" parent="GridContainer"]
layout_mode = 2
text = "-"
[node name="name_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Name"
[node name="name_value" type="Label" parent="GridContainer"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "-"
autowrap_mode = 2
[node name="path_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Path"
[node name="path_value" type="Label" parent="GridContainer"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "-"
autowrap_mode = 1
[node name="HSeparator" type="HSeparator" parent="GridContainer"]
layout_mode = 2
[node name="HSeparator2" type="HSeparator" parent="GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="source_file_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Aseprite File"
[node name="source_file_value" type="Label" parent="GridContainer"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "-"
autowrap_mode = 1
[node name="layer_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Layer"
[node name="layer_value" type="Label" parent="GridContainer"]
layout_mode = 2
text = "-"
[node name="slice_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Slice"
[node name="slice_value" type="Label" parent="GridContainer"]
layout_mode = 2
text = "-"
[node name="o_folder_label" type="Label" parent="GridContainer"]
visible = false
layout_mode = 2
size_flags_vertical = 0
text = "Output folder"
[node name="o_folder_value" type="Label" parent="GridContainer"]
visible = false
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "-"
[node name="o_name_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Spritesheet name"
[node name="o_name_value" type="Label" parent="GridContainer"]
layout_mode = 2
text = "-"
[node name="source_changed_warning" parent="." instance=ExtResource("2_weuqf")]
layout_mode = 2
[node name="resource_buttons" type="HFlowContainer" parent="."]
visible = false
layout_mode = 2
[node name="import" type="Button" parent="resource_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Re-Import"
[node name="open_scene" type="Button" parent="resource_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Open Scene"
[node name="scene_buttons" type="HFlowContainer" parent="."]
visible = false
layout_mode = 2
[node name="import_all" type="Button" parent="scene_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Re-Import all"
[node name="open_scene" type="Button" parent="scene_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Open Scene"
[node name="dir_buttons" type="HFlowContainer" parent="."]
visible = false
layout_mode = 2
[node name="import_all" type="Button" parent="dir_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Re-Import all"
[node name="show_dir_in_fs" type="Button" parent="dir_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Show In FileSystem"
[connection signal="button_up" from="resource_buttons/import" to="." method="_on_import_button_up"]
[connection signal="button_up" from="resource_buttons/open_scene" to="." method="_on_open_scene_button_up"]
[connection signal="button_up" from="scene_buttons/import_all" to="." method="_on_import_all_button_up"]
[connection signal="button_up" from="scene_buttons/open_scene" to="." method="_on_open_scene_button_up"]
[connection signal="button_up" from="dir_buttons/import_all" to="." method="_on_import_all_button_up"]
[connection signal="button_up" from="dir_buttons/show_dir_in_fs" to="." method="_on_show_dir_in_fs_button_up"]

View file

@ -0,0 +1,15 @@
@tool
extends Button
signal node_dropped(node_path)
func _can_drop_data(_pos, data):
if data.type == "nodes":
var node = get_node(data.nodes[0])
return node is AnimationPlayer
return false
func _drop_data(_pos, data):
var path = data.nodes[0]
node_dropped.emit(path)

View file

@ -0,0 +1,12 @@
[gd_scene load_steps=2 format=3 uid="uid://x1f1t87m582u"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/shared/animation_player_drop_button.gd" id="1_tfmxw"]
[node name="options" type="OptionButton"]
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
item_count = 1
selected = 0
popup/item_0/text = "[empty]"
popup/item_0/id = 0
script = ExtResource("1_tfmxw")

View file

@ -0,0 +1,14 @@
@tool
extends Button
signal dir_dropped(path)
func _can_drop_data(_pos, data):
if data.type == "files_and_dirs":
var dir_access = DirAccess.open(data.files[0])
return dir_access != null
return false
func _drop_data(_pos, data):
dir_dropped.emit(data.files[0])

View file

@ -0,0 +1,10 @@
[gd_scene load_steps=2 format=3 uid="uid://cwvgnm3o7eed2"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/shared/dir_drop_button.gd" id="1_7uqph"]
[node name="button" type="Button"]
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "[empty]"
clip_text = true
script = ExtResource("1_7uqph")

View file

@ -0,0 +1,15 @@
@tool
extends Button
signal aseprite_file_dropped(path)
func _can_drop_data(_pos, data):
if data.type == "files":
var extension = data.files[0].get_extension()
return extension == "ase" or extension == "aseprite"
return false
func _drop_data(_pos, data):
var path = data.files[0]
aseprite_file_dropped.emit(path)

View file

@ -0,0 +1,10 @@
[gd_scene load_steps=2 format=3 uid="uid://dj1uo3blocr8e"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/shared/source_drop_button.gd" id="1_smfgi"]
[node name="source_drop_button" type="Button"]
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "[empty]"
clip_text = true
script = ExtResource("1_smfgi")

View file

@ -0,0 +1,19 @@
@tool
extends PanelContainer
@onready var _message = $MarginContainer/HBoxContainer/Label
func _ready():
_configure_source_warning()
func _configure_source_warning():
var sb = self.get_theme_stylebox("panel")
var color = EditorInterface.get_editor_settings().get_setting("interface/theme/accent_color")
color.a = 0.2
sb.bg_color = color
self.get_node("MarginContainer/HBoxContainer/Icon").texture = get_theme_icon("NodeInfo", "EditorIcons")
func set_text(text: String):
_message.text = text

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,55 @@
@tool
extends VBoxContainer
signal refresh_triggered
signal multi_selected(item: TreeItem, column: int, selected: bool)
@onready var _tree: Tree = $Tree
func _on_tree_filter_change_finished(text):
var tree_root: TreeItem = _tree.get_root()
if text == "":
tree_root.call_recursive("set", "visible", true)
return
tree_root.call_recursive("set", "visible", false)
_make_matching_children_visible(tree_root, text.to_lower())
func _make_matching_children_visible(tree_root: TreeItem, text: String) -> void:
for c in tree_root.get_children():
if c.get_text(0).to_lower().contains(text):
c.visible = true
_ensure_parent_visible(c)
_make_matching_children_visible(c, text)
func _ensure_parent_visible(tree_item: TreeItem) -> void:
var node_parent = tree_item.get_parent()
if node_parent != null and not node_parent.visible:
node_parent.visible = true
_ensure_parent_visible(node_parent)
func _on_expand_all_pressed():
var tree_root: TreeItem = _tree.get_root()
tree_root.set_collapsed_recursive(false)
func _on_collapse_all_pressed():
var tree_root: TreeItem = _tree.get_root()
tree_root.set_collapsed_recursive(true)
tree_root.collapsed = false
func _on_refresh_tree_pressed():
refresh_triggered.emit()
func _on_tree_multi_selected(item: TreeItem, column: int, selected: bool):
multi_selected.emit(item, column, selected)
func get_resource_tree() -> Tree:
return _tree

View file

@ -0,0 +1,51 @@
[gd_scene load_steps=3 format=3 uid="uid://cisgsfvp4nf1g"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/shared/tree/resource_tree.gd" id="1_io4rc"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/imports_manager/tree_filter_field.gd" id="1_q7epo"]
[node name="resource_tree" type="VBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
script = ExtResource("1_io4rc")
[node name="buttons" type="HFlowContainer" parent="."]
layout_mode = 2
[node name="tree_filter" type="LineEdit" parent="buttons"]
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Filter..."
clear_button_enabled = true
script = ExtResource("1_q7epo")
[node name="expand_all" type="Button" parent="buttons"]
custom_minimum_size = Vector2(150, 0)
layout_mode = 2
text = "Expand All"
[node name="collapse_all" type="Button" parent="buttons"]
custom_minimum_size = Vector2(150, 0)
layout_mode = 2
text = "Collapse All"
[node name="refresh_tree" type="Button" parent="buttons"]
custom_minimum_size = Vector2(150, 0)
layout_mode = 2
text = "Refresh Tree"
[node name="Tree" type="Tree" parent="."]
layout_mode = 2
size_flags_vertical = 3
hide_root = true
select_mode = 2
[connection signal="change_finished" from="buttons/tree_filter" to="." method="_on_tree_filter_change_finished"]
[connection signal="text_changed" from="buttons/tree_filter" to="buttons/tree_filter" method="_on_text_changed"]
[connection signal="pressed" from="buttons/expand_all" to="." method="_on_expand_all_pressed"]
[connection signal="pressed" from="buttons/collapse_all" to="." method="_on_collapse_all_pressed"]
[connection signal="pressed" from="buttons/refresh_tree" to="." method="_on_refresh_tree_pressed"]
[connection signal="multi_selected" from="Tree" to="." method="_on_tree_multi_selected"]

View file

@ -0,0 +1,18 @@
@tool
extends VBoxContainer
signal warning_confirmed
signal warning_declined
@onready var _warning_message = $MarginContainer/warning_message
func set_message(text: String) -> void:
_warning_message.text = text
func _on_confirm_button_up():
warning_confirmed.emit()
func _on_cancel_button_up():
warning_declined.emit()

View file

@ -0,0 +1,34 @@
[gd_scene load_steps=2 format=3 uid="uid://qgmln507kjnm"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/shared/tree/tree_selection_confirmation_warning.gd" id="1_7gtu1"]
[node name="confirmation_warning" type="VBoxContainer"]
script = ExtResource("1_7gtu1")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="warning_message" type="Label" parent="MarginContainer"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
autowrap_mode = 2
[node name="buttons" type="HFlowContainer" parent="."]
layout_mode = 2
[node name="confirm" type="Button" parent="buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Confirm"
[node name="cancel" type="Button" parent="buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Cancel"
[connection signal="button_up" from="buttons/confirm" to="." method="_on_confirm_button_up"]
[connection signal="button_up" from="buttons/cancel" to="." method="_on_cancel_button_up"]

View file

@ -0,0 +1,7 @@
[plugin]
name="Aseprite Wizard"
description="Import Aseprite files to Godot in many different ways."
author="Vinicius Gerevini"
version="7.5.0"
script="plugin.gd"

View file

@ -0,0 +1,208 @@
@tool
extends EditorPlugin
# importers
const NoopImportPlugin = preload("importers/noop_import_plugin.gd")
const SpriteFramesImportPlugin = preload("importers/sprite_frames_import_plugin.gd")
const TilesetTextureImportPlugin = preload("importers/tileset_texture_import_plugin.gd")
const TextureImportPlugin = preload("importers/static_texture_import_plugin.gd")
# export
const ExportPlugin = preload("export/metadata_export_plugin.gd")
# interface
const ConfigDialog = preload('config/config_dialog.tscn')
const WizardWindow = preload("interface/docks/wizard/as_wizard_dock_container.tscn")
const AsepriteDockImportsWindow = preload('interface/imports_manager/aseprite_imports_manager.tscn')
const ImportsManagerPanels = preload('interface/imports_manager/import_panels.tscn')
const AnimatedSpriteInspectorPlugin = preload("interface/docks/animated_sprite/inspector_plugin.gd")
const SpriteInspectorPlugin = preload("interface/docks/sprite/inspector_plugin.gd")
const tool_menu_name = "Aseprite Wizard"
const menu_item_name = "Spritesheet Wizard Dock..."
const config_menu_item_name = "Config..."
const import_menu_item_name = "Imports Manager..."
var config = preload("config/config.gd").new()
var window: TabContainer
var config_window: PopupPanel
var imports_list_window: Window
var imports_list_panel: MarginContainer
var export_plugin : EditorExportPlugin
var sprite_inspector_plugin: EditorInspectorPlugin
var animated_sprite_inspector_plugin: EditorInspectorPlugin
var _exporter_enabled = false
var _importers = []
var _is_import_list_docked = false
func _enter_tree():
_load_config()
_setup_menu_entries()
_setup_importer()
_setup_exporter()
_setup_animated_sprite_inspector_plugin()
_setup_sprite_inspector_plugin()
func _exit_tree():
_disable_plugin()
func _disable_plugin():
_remove_menu_entries()
_remove_importer()
_remove_exporter()
_remove_wizard_dock()
_remove_inspector_plugins()
func _load_config():
config.initialize_project_settings()
func _setup_menu_entries():
var submenu = PopupMenu.new()
add_tool_submenu_item(tool_menu_name, submenu)
submenu.add_item(menu_item_name)
submenu.add_item(import_menu_item_name)
submenu.add_item(config_menu_item_name)
submenu.index_pressed.connect(_on_tool_menu_pressed)
func _remove_menu_entries():
remove_tool_menu_item(tool_menu_name)
func _setup_importer():
_importers = [
NoopImportPlugin.new(),
SpriteFramesImportPlugin.new(),
TilesetTextureImportPlugin.new(),
TextureImportPlugin.new(),
]
for i in _importers:
add_import_plugin(i)
func _remove_importer():
for i in _importers:
remove_import_plugin(i)
func _setup_exporter():
if config.is_exporter_enabled():
export_plugin = ExportPlugin.new()
add_export_plugin(export_plugin)
_exporter_enabled = true
func _remove_exporter():
if _exporter_enabled:
remove_export_plugin(export_plugin)
_exporter_enabled = false
func _setup_sprite_inspector_plugin():
sprite_inspector_plugin = SpriteInspectorPlugin.new()
add_inspector_plugin(sprite_inspector_plugin)
func _setup_animated_sprite_inspector_plugin():
animated_sprite_inspector_plugin = AnimatedSpriteInspectorPlugin.new()
add_inspector_plugin(animated_sprite_inspector_plugin)
func _remove_inspector_plugins():
remove_inspector_plugin(sprite_inspector_plugin)
remove_inspector_plugin(animated_sprite_inspector_plugin)
func _remove_wizard_dock():
if window:
remove_control_from_bottom_panel(window)
window.queue_free()
window = null
func _open_window():
if window:
make_bottom_panel_item_visible(window)
return
window = WizardWindow.instantiate()
window.connect("close_requested",Callable(self,"_on_window_closed"))
add_control_to_bottom_panel(window, "Aseprite Wizard")
make_bottom_panel_item_visible(window)
func _open_config_dialog():
if is_instance_valid(config_window):
config_window.queue_free()
config_window = ConfigDialog.instantiate()
get_editor_interface().get_base_control().add_child(config_window)
config_window.popup_centered()
func _open_import_list_dialog():
if is_instance_valid(imports_list_window):
imports_list_window.queue_free()
if is_instance_valid(imports_list_panel):
if _is_import_list_docked:
remove_control_from_bottom_panel(imports_list_panel)
_is_import_list_docked = false
imports_list_panel.queue_free()
imports_list_panel = null
imports_list_panel = ImportsManagerPanels.instantiate()
imports_list_panel.dock_requested.connect(_on_import_list_dock_requested)
_create_imports_manager_window(imports_list_panel)
func _on_window_closed():
if window:
remove_control_from_bottom_panel(window)
window.queue_free()
window = null
func _on_tool_menu_pressed(index):
match index:
0: # wizard dock
_open_window()
1: # imports
_open_import_list_dialog()
2: # config
_open_config_dialog()
func _on_import_list_dock_requested():
if _is_import_list_docked:
remove_control_from_bottom_panel(imports_list_panel)
_is_import_list_docked = false
_create_imports_manager_window(imports_list_panel)
imports_list_panel.show()
imports_list_panel.anchors_preset = Control.PRESET_FULL_RECT
imports_list_panel.size_flags_vertical = Control.SIZE_EXPAND_FILL
imports_list_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
imports_list_panel.set_as_floating()
return
_is_import_list_docked = true
imports_list_panel.set_as_docked()
imports_list_window.remove_child(imports_list_panel)
imports_list_window.queue_free()
add_control_to_bottom_panel(imports_list_panel, "Aseprite Imports Manager")
make_bottom_panel_item_visible(imports_list_panel)
func _create_imports_manager_window(panel: MarginContainer):
imports_list_window = AsepriteDockImportsWindow.instantiate()
imports_list_window.add_child(panel)
get_editor_interface().get_base_control().add_child(imports_list_window)
imports_list_window.popup_centered_ratio(0.5)

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016-2023 The Godot Engine community
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,12 @@
[configuration]
entry_symbol = "git_plugin_init"
compatibility_minimum = "4.1.0"
[libraries]
macos.editor = "macos/libgit_plugin.macos.editor.universal.dylib"
windows.editor.x86_64 = "win64/libgit_plugin.windows.editor.x86_64.dll"
linux.editor.x86_64 = "linux/libgit_plugin.linux.editor.x86_64.so"
linux.editor.arm64 = "linux/libgit_plugin.linux.editor.arm64.so"
linux.editor.rv64 = ""

View file

@ -0,0 +1,7 @@
[plugin]
name="Godot Git Plugin"
description="This plugin lets you interact with Git without leaving the Godot editor. More information can be found at https://github.com/godotengine/godot-git-plugin/wiki"
author="twaritwaikar"
version="v3.1.1"
script="godot-git-plugin.gd"

Binary file not shown.

View file

@ -0,0 +1,14 @@
[remap]
importer="aseprite_wizard.plugin.noop"
type="PackedDataContainer"
uid="uid://bye3fycptipre"
path="res://.godot/imported/PlayerCharacter.aseprite-1bf420fc00cecceb6a9caea06a8e4ec7.res"
[deps]
source_file="res://assets/PlayerCharacter.aseprite"
dest_files=["res://.godot/imported/PlayerCharacter.aseprite-1bf420fc00cecceb6a9caea06a8e4ec7.res"]
[params]

1
icon.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 994 B

37
icon.svg.import Normal file
View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dp6yumdudwpwh"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

Some files were not shown because too many files have changed in this diff Show more