Add addons
This commit is contained in:
parent
2c862b2235
commit
3367bad24f
527 changed files with 36197 additions and 0 deletions
|
|
@ -0,0 +1,286 @@
|
|||
extends RefCounted
|
||||
|
||||
var result_code = preload("../config/result_codes.gd")
|
||||
var _aseprite = preload("../aseprite/aseprite.gd").new()
|
||||
|
||||
var _config
|
||||
var _file_system
|
||||
|
||||
var _DEFAULT_ANIMATION_LIBRARY = "" # GLOBAL
|
||||
|
||||
|
||||
func init(config, editor_file_system: EditorFileSystem = null):
|
||||
_config = config
|
||||
_file_system = editor_file_system
|
||||
_aseprite.init(config)
|
||||
|
||||
|
||||
func create_animations(target_node: Node, player: AnimationPlayer, options: Dictionary):
|
||||
if not _aseprite.test_command():
|
||||
return result_code.ERR_ASEPRITE_CMD_NOT_FOUND
|
||||
|
||||
if not FileAccess.file_exists(options.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
|
||||
|
||||
var result = await _create_animations_from_file(target_node, player, options)
|
||||
|
||||
if result != result_code.SUCCESS:
|
||||
printerr(result_code.get_error_message(result))
|
||||
|
||||
|
||||
func _create_animations_from_file(target_node: Node, player: AnimationPlayer, options: Dictionary):
|
||||
var output
|
||||
|
||||
if options.get("layer", "") == "":
|
||||
output = _aseprite.export_file(options.source, options.output_folder, options)
|
||||
else:
|
||||
output = _aseprite.export_layer(options.source, options.layer, options.output_folder, options)
|
||||
|
||||
if output.is_empty():
|
||||
return result_code.ERR_ASEPRITE_EXPORT_FAILED
|
||||
|
||||
if _config.is_import_preset_enabled():
|
||||
_config.create_import_file(output)
|
||||
|
||||
await _scan_filesystem()
|
||||
|
||||
var result = _import(target_node, player, output, options)
|
||||
|
||||
if _config.should_remove_source_files():
|
||||
DirAccess.remove_absolute(output.data_file)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
func _import(target_node: Node, player: AnimationPlayer, data: Dictionary, options: Dictionary):
|
||||
var source_file = data.data_file
|
||||
var sprite_sheet = data.sprite_sheet
|
||||
|
||||
var file = FileAccess.open(source_file, FileAccess.READ)
|
||||
if file == null:
|
||||
return file.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.ERR_INVALID_ASEPRITE_SPRITESHEET
|
||||
|
||||
var context = {}
|
||||
|
||||
target_node.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
_setup_texture(target_node, sprite_sheet, content, context)
|
||||
var result = _configure_animations(target_node, player, content, context)
|
||||
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', true)
|
||||
texture.take_over_path(sprite_sheet)
|
||||
return texture
|
||||
|
||||
|
||||
func _configure_animations(target_node: Node, player: AnimationPlayer, content: Dictionary, context: Dictionary):
|
||||
var frames = _aseprite.get_content_frames(content)
|
||||
|
||||
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, tag.direction)
|
||||
if result != result_code.SUCCESS:
|
||||
break
|
||||
return result
|
||||
else:
|
||||
return _add_animation_frames(target_node, player, "default", frames, context)
|
||||
|
||||
|
||||
func _add_animation_frames(target_node: Node, player: AnimationPlayer, anim_name: String, frames: Array, context: Dictionary, direction = 'forward'):
|
||||
var animation_name = anim_name
|
||||
var is_loopable = _config.is_default_animation_loop_enabled()
|
||||
|
||||
if animation_name.begins_with(_config.get_animation_loop_exception_prefix()):
|
||||
animation_name = anim_name.substr(_config.get_animation_loop_exception_prefix().length())
|
||||
is_loopable = not is_loopable
|
||||
|
||||
if not player.has_animation(animation_name):
|
||||
player.get_animation_library(_DEFAULT_ANIMATION_LIBRARY).add_animation(animation_name, Animation.new())
|
||||
|
||||
var animation = player.get_animation(animation_name)
|
||||
_create_meta_tracks(target_node, player, animation)
|
||||
var frame_track = _get_property_track_path(player, target_node, _get_frame_property())
|
||||
var frame_track_index = _create_track(target_node, animation, frame_track)
|
||||
|
||||
if direction == 'reverse':
|
||||
frames.reverse()
|
||||
|
||||
var animation_length = 0
|
||||
|
||||
for frame in frames:
|
||||
var frame_key = _get_frame_key(target_node, frame, context)
|
||||
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 == 'pingpong':
|
||||
frames.remove_at(frames.size() - 1)
|
||||
if is_loopable:
|
||||
frames.remove_at(0)
|
||||
frames.reverse()
|
||||
|
||||
for frame in frames:
|
||||
var frame_key = _get_frame_key(target_node, frame, context)
|
||||
animation.track_insert_key(frame_track_index, animation_length, frame_key)
|
||||
animation_length += frame.duration / 1000
|
||||
|
||||
animation.length = animation_length
|
||||
animation.loop_mode = Animation.LOOP_LINEAR if is_loopable else Animation.LOOP_NONE
|
||||
|
||||
return result_code.SUCCESS
|
||||
|
||||
|
||||
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) != -1:
|
||||
continue
|
||||
var visible_track_index = _create_track(node, animation, visible_track)
|
||||
animation.track_insert_key(visible_track_index, 0, false)
|
||||
|
||||
|
||||
func _scan_filesystem():
|
||||
_file_system.scan()
|
||||
await _file_system.filesystem_changed
|
||||
|
||||
|
||||
func list_layers(file: String, only_visibles = false) -> Array:
|
||||
return _aseprite.list_layers(file, only_visibles)
|
||||
|
||||
|
||||
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 _setup_texture(target_node: Node, sprite_sheet: String, content: Dictionary, context: Dictionary):
|
||||
push_error("_setup_texture not implemented!")
|
||||
|
||||
|
||||
func _get_frame_property() -> String:
|
||||
push_error("_get_frame_property not implemented!")
|
||||
return ""
|
||||
|
||||
|
||||
func _get_frame_key(target_node: Node, frame: Dictionary, context: Dictionary):
|
||||
push_error("_get_frame_key not implemented!")
|
||||
|
||||
|
||||
func _create_meta_tracks(target_node: Node, player: AnimationPlayer, animation: Animation):
|
||||
push_error("_create_meta_tracks not implemented!")
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
@tool
|
||||
extends PanelContainer
|
||||
|
||||
const wizard_config = preload("../../config/wizard_config.gd")
|
||||
const result_code = preload("../../config/result_codes.gd")
|
||||
|
||||
const AnimationCreator = preload("../animation_creator.gd")
|
||||
const SpriteAnimationCreator = preload("../sprite_animation_creator.gd")
|
||||
const TextureRectAnimationCreator = preload("../texture_rect_animation_creator.gd")
|
||||
|
||||
var animation_creator: AnimationCreator
|
||||
|
||||
var scene: Node
|
||||
var target_node: Node
|
||||
|
||||
var config
|
||||
var file_system: EditorFileSystem
|
||||
|
||||
var _layer: String = ""
|
||||
var _source: String = ""
|
||||
var _animation_player_path: String
|
||||
var _file_dialog_aseprite: FileDialog
|
||||
var _output_folder_dialog: FileDialog
|
||||
var _importing := false
|
||||
|
||||
var _output_folder := ""
|
||||
var _out_folder_default := "[Same as scene]"
|
||||
var _layer_default := "[all]"
|
||||
|
||||
@onready var _options_field = $margin/VBoxContainer/animation_player/options
|
||||
@onready var _source_field = $margin/VBoxContainer/source/button
|
||||
@onready var _layer_field = $margin/VBoxContainer/layer/options
|
||||
@onready var _options_title = $margin/VBoxContainer/options_title/options_title
|
||||
@onready var _options_container = $margin/VBoxContainer/options
|
||||
@onready var _out_folder_field = $margin/VBoxContainer/options/out_folder/button
|
||||
@onready var _out_filename_field = $margin/VBoxContainer/options/out_filename/LineEdit
|
||||
@onready var _visible_layers_field = $margin/VBoxContainer/options/visible_layers/CheckButton
|
||||
@onready var _ex_pattern_field = $margin/VBoxContainer/options/ex_pattern/LineEdit
|
||||
@onready var _cleanup_hide_unused_nodes = $margin/VBoxContainer/options/auto_visible_track/CheckButton
|
||||
|
||||
|
||||
func _ready():
|
||||
var cfg = wizard_config.load_config(target_node)
|
||||
if cfg == null:
|
||||
_load_default_config()
|
||||
else:
|
||||
_load_config(cfg)
|
||||
|
||||
if target_node is Sprite2D || target_node is Sprite3D:
|
||||
animation_creator = SpriteAnimationCreator.new()
|
||||
if target_node is TextureRect:
|
||||
animation_creator = TextureRectAnimationCreator.new()
|
||||
|
||||
animation_creator.init(config, file_system)
|
||||
|
||||
|
||||
func _load_config(cfg):
|
||||
if cfg.has("source"):
|
||||
_set_source(cfg.source)
|
||||
|
||||
if cfg.has("player"):
|
||||
_options_field.clear()
|
||||
_set_animation_player(cfg.player)
|
||||
|
||||
if cfg.get("layer", "") != "":
|
||||
_layer_field.clear()
|
||||
_set_layer(cfg.layer)
|
||||
|
||||
_output_folder = cfg.get("o_folder", "")
|
||||
_out_folder_field.text = _output_folder if _output_folder != "" else _out_folder_default
|
||||
_out_filename_field.text = cfg.get("o_name", "")
|
||||
_visible_layers_field.button_pressed = cfg.get("only_visible", false)
|
||||
_ex_pattern_field.text = cfg.get("o_ex_p", "")
|
||||
_cleanup_hide_unused_nodes.button_pressed = cfg.get("set_vis_track", config.is_set_visible_track_automatically_enabled())
|
||||
|
||||
_set_options_visible(cfg.get("op_exp", false))
|
||||
|
||||
|
||||
func _load_default_config():
|
||||
_ex_pattern_field.text = config.get_default_exclusion_pattern()
|
||||
_cleanup_hide_unused_nodes.button_pressed = config.is_set_visible_track_automatically_enabled()
|
||||
_set_options_visible(false)
|
||||
|
||||
|
||||
func _set_source(source):
|
||||
_source = source
|
||||
_source_field.text = _source
|
||||
_source_field.tooltip_text = _source
|
||||
|
||||
|
||||
func _set_animation_player(player):
|
||||
_animation_player_path = player
|
||||
_options_field.add_item(_animation_player_path)
|
||||
|
||||
|
||||
func _set_layer(layer):
|
||||
_layer = layer
|
||||
_layer_field.add_item(_layer)
|
||||
|
||||
|
||||
func _on_options_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
|
||||
_options_field.clear()
|
||||
_options_field.add_item("[empty]")
|
||||
|
||||
for ap in animation_players:
|
||||
_options_field.add_item(ap)
|
||||
if ap.get_concatenated_names() == _animation_player_path:
|
||||
current = _options_field.get_item_count() - 1
|
||||
|
||||
_options_field.select(current)
|
||||
|
||||
|
||||
func _find_animation_players(root: Node, node: Node, players: Array):
|
||||
if node is AnimationPlayer:
|
||||
players.push_back(root.get_path_to(node))
|
||||
|
||||
for c in node.get_children():
|
||||
_find_animation_players(root, c, players)
|
||||
|
||||
|
||||
func _on_options_item_selected(index):
|
||||
if index == 0:
|
||||
_animation_player_path = ""
|
||||
return
|
||||
_animation_player_path = _options_field.get_item_text(index)
|
||||
_save_config()
|
||||
|
||||
|
||||
func _on_layer_button_down():
|
||||
if _source == "":
|
||||
_show_message("Please. Select source file first.")
|
||||
return
|
||||
|
||||
var layers = animation_creator.list_layers(ProjectSettings.globalize_path(_source))
|
||||
var current = 0
|
||||
_layer_field.clear()
|
||||
_layer_field.add_item("[all]")
|
||||
|
||||
for l in layers:
|
||||
if l == "":
|
||||
continue
|
||||
|
||||
_layer_field.add_item(l)
|
||||
if l == _layer:
|
||||
current = _layer_field.get_item_count() - 1
|
||||
_layer_field.select(current)
|
||||
|
||||
|
||||
func _on_layer_item_selected(index):
|
||||
if index == 0:
|
||||
_layer = ""
|
||||
return
|
||||
_layer = _layer_field.get_item_text(index)
|
||||
_save_config()
|
||||
|
||||
|
||||
func _on_source_pressed():
|
||||
_open_source_dialog()
|
||||
|
||||
|
||||
func _on_import_pressed():
|
||||
if _importing:
|
||||
return
|
||||
_importing = true
|
||||
|
||||
var root = get_tree().get_edited_scene_root()
|
||||
|
||||
if _animation_player_path == "" or not root.has_node(_animation_player_path):
|
||||
_show_message("AnimationPlayer not found")
|
||||
_importing = false
|
||||
return
|
||||
|
||||
if _source == "":
|
||||
_show_message("Aseprite file not selected")
|
||||
_importing = false
|
||||
return
|
||||
|
||||
var options = {
|
||||
"source": ProjectSettings.globalize_path(_source),
|
||||
"output_folder": _output_folder if _output_folder != "" else root.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,
|
||||
"cleanup_hide_unused_nodes": _cleanup_hide_unused_nodes.button_pressed,
|
||||
"layer": _layer
|
||||
}
|
||||
|
||||
_save_config()
|
||||
|
||||
await animation_creator.create_animations(target_node, root.get_node(_animation_player_path), options)
|
||||
_importing = false
|
||||
|
||||
|
||||
func _save_config():
|
||||
var cfg := {
|
||||
"player": _animation_player_path,
|
||||
"source": _source,
|
||||
"layer": _layer,
|
||||
"op_exp": _options_title.button_pressed,
|
||||
"o_folder": _output_folder,
|
||||
"o_name": _out_filename_field.text,
|
||||
"only_visible": _visible_layers_field.button_pressed,
|
||||
"o_ex_p": _ex_pattern_field.text,
|
||||
}
|
||||
|
||||
if _cleanup_hide_unused_nodes.button_pressed != config.is_set_visible_track_automatically_enabled():
|
||||
cfg["set_vis_track"] = _cleanup_hide_unused_nodes.button_pressed
|
||||
|
||||
wizard_config.save_config(target_node, config.is_use_metadata_enabled(), cfg)
|
||||
|
||||
|
||||
func _open_source_dialog():
|
||||
_file_dialog_aseprite = _create_aseprite_file_selection()
|
||||
get_parent().add_child(_file_dialog_aseprite)
|
||||
if _source != "":
|
||||
_file_dialog_aseprite.current_dir = _source.get_base_dir()
|
||||
_file_dialog_aseprite.popup_centered_ratio()
|
||||
|
||||
|
||||
func _create_aseprite_file_selection():
|
||||
var file_dialog = FileDialog.new()
|
||||
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||
file_dialog.access = FileDialog.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 _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 _on_options_title_toggled(button_pressed):
|
||||
_set_options_visible(button_pressed)
|
||||
_save_config()
|
||||
|
||||
|
||||
func _set_options_visible(is_visible):
|
||||
_options_container.visible = is_visible
|
||||
_options_title.icon = config.get_icon("expanded") if is_visible else config.get_icon("collapsed")
|
||||
|
||||
|
||||
func _on_out_folder_pressed():
|
||||
_output_folder_dialog = _create_output_folder_selection()
|
||||
get_parent().add_child(_output_folder_dialog)
|
||||
if _output_folder != _out_folder_default:
|
||||
_output_folder_dialog.current_dir = _output_folder
|
||||
_output_folder_dialog.popup_centered_ratio()
|
||||
|
||||
|
||||
func _create_output_folder_selection():
|
||||
var file_dialog = FileDialog.new()
|
||||
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR
|
||||
file_dialog.access = FileDialog.ACCESS_RESOURCES
|
||||
file_dialog.connect("dir_selected",Callable(self,"_on_output_folder_selected"))
|
||||
return file_dialog
|
||||
|
||||
|
||||
func _on_output_folder_selected(path):
|
||||
_output_folder = path
|
||||
_out_folder_field.text = _output_folder if _output_folder != "" else _out_folder_default
|
||||
_output_folder_dialog.queue_free()
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://biyshalfalqqw"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/AsepriteWizard/animation_player/docks/animation_player_inspector_dock.gd" id="1"]
|
||||
|
||||
[node name="sprite_inspector_dock" type="PanelContainer"]
|
||||
offset_right = 14.0
|
||||
offset_bottom = 14.0
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="margin" type="MarginContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="margin"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="section_title" type="PanelContainer" parent="margin/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="title" type="Label" parent="margin/VBoxContainer/section_title"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Aseprite"
|
||||
horizontal_alignment = 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" type="OptionButton" parent="margin/VBoxContainer/animation_player"]
|
||||
layout_mode = 2
|
||||
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
|
||||
|
||||
[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" type="Button" parent="margin/VBoxContainer/source"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "[empty]"
|
||||
clip_text = true
|
||||
|
||||
[node name="layer" type="HBoxContainer" parent="margin/VBoxContainer"]
|
||||
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/layer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "Layer"
|
||||
|
||||
[node name="options" type="OptionButton" parent="margin/VBoxContainer/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="options_title" type="PanelContainer" parent="margin/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="options_title" type="Button" parent="margin/VBoxContainer/options_title"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "Options"
|
||||
|
||||
[node name="options" type="VBoxContainer" parent="margin/VBoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="out_folder" type="HBoxContainer" parent="margin/VBoxContainer/options"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Location where the spritesheet file should be saved."
|
||||
|
||||
[node name="Label" type="Label" parent="margin/VBoxContainer/options/out_folder"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "Output folder"
|
||||
|
||||
[node name="button" type="Button" parent="margin/VBoxContainer/options/out_folder"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "[empty]"
|
||||
clip_text = true
|
||||
|
||||
[node name="out_filename" type="HBoxContainer" parent="margin/VBoxContainer/options"]
|
||||
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/options/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/options/out_filename"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
|
||||
[node name="ex_pattern" type="HBoxContainer" parent="margin/VBoxContainer/options"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Exclude layers with name matching this pattern (regex)."
|
||||
|
||||
[node name="Label" type="Label" parent="margin/VBoxContainer/options/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/options/ex_pattern"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
|
||||
[node name="visible_layers" type="HBoxContainer" parent="margin/VBoxContainer/options"]
|
||||
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/options/visible_layers"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "Only visible layers"
|
||||
|
||||
[node name="CheckButton" type="CheckButton" parent="margin/VBoxContainer/options/visible_layers"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
|
||||
[node name="auto_visible_track" type="HBoxContainer" parent="margin/VBoxContainer/options"]
|
||||
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/options/auto_visible_track"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "Hide unused sprites"
|
||||
|
||||
[node name="CheckButton" type="CheckButton" parent="margin/VBoxContainer/options/auto_visible_track"]
|
||||
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="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="pressed" from="margin/VBoxContainer/source/button" to="." method="_on_source_pressed"]
|
||||
[connection signal="button_down" from="margin/VBoxContainer/layer/options" to="." method="_on_layer_button_down"]
|
||||
[connection signal="item_selected" from="margin/VBoxContainer/layer/options" to="." method="_on_layer_item_selected"]
|
||||
[connection signal="toggled" from="margin/VBoxContainer/options_title/options_title" to="." method="_on_options_title_toggled"]
|
||||
[connection signal="pressed" from="margin/VBoxContainer/options/out_folder/button" to="." method="_on_out_folder_pressed"]
|
||||
[connection signal="pressed" from="margin/VBoxContainer/import" to="." method="_on_import_pressed"]
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
@tool
|
||||
extends EditorInspectorPlugin
|
||||
|
||||
const APInspectorDock = preload("./docks/animation_player_inspector_dock.tscn")
|
||||
|
||||
var config
|
||||
var file_system: EditorFileSystem
|
||||
|
||||
func _can_handle(object):
|
||||
return object is Sprite2D || object is Sprite3D || object is TextureRect
|
||||
|
||||
|
||||
func _parse_end(object):
|
||||
var dock = APInspectorDock.instantiate()
|
||||
dock.target_node = object
|
||||
dock.config = config
|
||||
dock.file_system = file_system
|
||||
add_custom_control(dock)
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
extends "animation_creator.gd"
|
||||
|
||||
|
||||
func _setup_texture(sprite: Node, sprite_sheet: String, content: Dictionary, context: Dictionary):
|
||||
var texture = _load_texture(sprite_sheet)
|
||||
sprite.texture = texture
|
||||
|
||||
if content.frames.is_empty():
|
||||
return
|
||||
|
||||
sprite.hframes = content.meta.size.w / content.frames[0].sourceSize.w
|
||||
sprite.vframes = content.meta.size.h / content.frames[0].sourceSize.h
|
||||
|
||||
|
||||
func _get_frame_property() -> String:
|
||||
return "frame"
|
||||
|
||||
|
||||
func _create_meta_tracks(sprite: Node, player: AnimationPlayer, animation: Animation):
|
||||
var texture_track = _get_property_track_path(player, sprite, "texture")
|
||||
var texture_track_index = _create_track(sprite, animation, texture_track)
|
||||
animation.track_insert_key(texture_track_index, 0, sprite.texture)
|
||||
|
||||
var hframes_track = _get_property_track_path(player, sprite, "hframes")
|
||||
var hframes_track_index = _create_track(sprite, animation, hframes_track)
|
||||
animation.track_insert_key(hframes_track_index, 0, sprite.hframes)
|
||||
|
||||
var vframes_track = _get_property_track_path(player, sprite, "vframes")
|
||||
var vframes_track_index = _create_track(sprite, animation, vframes_track)
|
||||
animation.track_insert_key(vframes_track_index, 0, sprite.vframes)
|
||||
|
||||
var visible_track = _get_property_track_path(player, sprite, "visible")
|
||||
var visible_track_index = _create_track(sprite, animation, visible_track)
|
||||
animation.track_insert_key(visible_track_index, 0, true)
|
||||
|
||||
|
||||
func _get_frame_key(sprite: Node, frame: Dictionary, context: Dictionary):
|
||||
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
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
extends "animation_creator.gd"
|
||||
|
||||
|
||||
func _setup_texture(target_node: Node, sprite_sheet: String, content: Dictionary, context: Dictionary):
|
||||
context["base_texture"] = _load_texture(sprite_sheet)
|
||||
|
||||
|
||||
func _get_frame_property() -> String:
|
||||
return "texture"
|
||||
|
||||
|
||||
func _get_frame_key(target_node: Node, frame: Dictionary, context: Dictionary):
|
||||
return _get_atlas_texture(context["base_texture"], frame)
|
||||
|
||||
|
||||
func _create_meta_tracks(target_node: Node, player: AnimationPlayer, animation: Animation):
|
||||
var texture_track = _get_property_track_path(player, target_node, "texture")
|
||||
var texture_track_index = _create_track(target_node, animation, texture_track)
|
||||
animation.track_insert_key(texture_track_index, 0, target_node.texture)
|
||||
|
||||
var visible_track = _get_property_track_path(player, target_node, "visible")
|
||||
var visible_track_index = _create_track(target_node, animation, visible_track)
|
||||
animation.track_insert_key(visible_track_index, 0, true)
|
||||
|
||||
|
||||
func _get_atlas_texture(base_texture: Texture2D, frame_data: Dictionary) -> 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
|
||||
return tex
|
||||
Loading…
Add table
Add a link
Reference in a new issue