352 lines
9.4 KiB
GDScript
352 lines
9.4 KiB
GDScript
@tool
|
|
extends RefCounted
|
|
|
|
var result_code = preload("../config/result_codes.gd")
|
|
var _aseprite = preload("../aseprite/aseprite.gd").new()
|
|
|
|
enum {
|
|
FILE_EXPORT_MODE,
|
|
LAYERS_EXPORT_MODE
|
|
}
|
|
|
|
var _config
|
|
var _file_system: EditorFileSystem
|
|
var _should_check_file_system := false
|
|
|
|
|
|
func init(config, editor_file_system: EditorFileSystem = null):
|
|
_config = config
|
|
_file_system = editor_file_system
|
|
_should_check_file_system = _file_system != null
|
|
_aseprite.init(config)
|
|
|
|
|
|
func _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
|
|
|
|
|
|
func create_animations(sprite: Node, options: Dictionary) -> void:
|
|
var input_check = _initial_checks(options.source, options)
|
|
|
|
if input_check != result_code.SUCCESS:
|
|
printerr(result_code.get_error_message(input_check))
|
|
return
|
|
|
|
var result = await _create_animations_from_file(sprite, options)
|
|
|
|
if result != result_code.SUCCESS:
|
|
printerr(result_code.get_error_message(result))
|
|
|
|
|
|
func _create_animations_from_file(animated_sprite: Node, options: Dictionary) -> int:
|
|
var output = _export_aseprite_file(options)
|
|
|
|
if not output.is_ok:
|
|
return output.code
|
|
|
|
if _config.is_import_preset_enabled():
|
|
_config.create_import_file(output.content)
|
|
|
|
await _scan_filesystem()
|
|
|
|
var sprite_frames_result = _create_sprite_frames(output.content)
|
|
if not sprite_frames_result.is_ok:
|
|
return sprite_frames_result.code
|
|
|
|
animated_sprite.frames = sprite_frames_result.content
|
|
animated_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
|
|
|
if _config.should_remove_source_files():
|
|
DirAccess.remove_absolute(output.content.data_file)
|
|
|
|
return result_code.SUCCESS
|
|
|
|
|
|
func _export_aseprite_file(options: Dictionary) -> 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.error(result_code.ERR_ASEPRITE_EXPORT_FAILED)
|
|
|
|
return result_code.result(output)
|
|
|
|
|
|
func create_and_save_resources(source_file: String, options: Dictionary) -> int:
|
|
var resources = await create_resources(source_file, options)
|
|
if resources.is_ok:
|
|
return _save_resources(resources.content)
|
|
|
|
return resources.code
|
|
|
|
|
|
func create_resources(source_file: String, options = {}) -> Dictionary:
|
|
var input_check = _initial_checks(source_file, options)
|
|
|
|
if input_check != result_code.SUCCESS:
|
|
return result_code.error(input_check)
|
|
|
|
var output = _create_aseprite_output_files(source_file, options)
|
|
|
|
if not output.is_ok:
|
|
return output
|
|
|
|
await _scan_filesystem()
|
|
|
|
var result = await _create_sprite_frames_from_source(output.content, options)
|
|
|
|
if not result.is_ok:
|
|
return result
|
|
|
|
var should_remove_source = options.get("remove_source_files_allowed", false) and _config.should_remove_source_files()
|
|
|
|
if should_remove_source:
|
|
_remove_source_files(result.content)
|
|
|
|
return result
|
|
|
|
|
|
func _remove_source_files(source_files: Array):
|
|
for s in source_files:
|
|
DirAccess.remove_absolute(s.data_file)
|
|
|
|
await _scan_filesystem()
|
|
|
|
|
|
func _create_aseprite_output_files(source_file: String, options: Dictionary):
|
|
match options.get('export_mode', FILE_EXPORT_MODE):
|
|
FILE_EXPORT_MODE:
|
|
return result_code.result(
|
|
[_aseprite.export_file(source_file, options.output_folder, options)]
|
|
)
|
|
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)
|
|
|
|
|
|
func _create_sprite_frames_from_source(source_files: Array, options: Dictionary) -> Dictionary:
|
|
var should_remove_source = options.get("remove_source_files_allowed", false) and _config.should_remove_source_files()
|
|
|
|
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)
|
|
|
|
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:
|
|
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
|
|
)
|
|
)
|
|
|
|
|
|
func _load_aseprite_resources(aseprite_data: Dictionary):
|
|
var content_result = _load_json_content(aseprite_data.data_file)
|
|
|
|
if not content_result.is_ok:
|
|
return content_result
|
|
|
|
var texture = _parse_texture_path(aseprite_data.sprite_sheet)
|
|
|
|
return result_code.result({
|
|
"metadata": content_result.content,
|
|
"texture": texture
|
|
})
|
|
|
|
|
|
func _load_json_content(source_file: String) -> Dictionary:
|
|
var file = FileAccess.open(source_file, FileAccess.READ)
|
|
if file == null:
|
|
return result_code.error(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.error(result_code.ERR_INVALID_ASEPRITE_SPRITESHEET)
|
|
|
|
return result_code.result(content)
|
|
|
|
|
|
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, texture) -> SpriteFrames:
|
|
var frame_cache = {}
|
|
var frames = _aseprite.get_content_frames(content)
|
|
var sprite_frames := SpriteFrames.new()
|
|
sprite_frames.remove_animation("default")
|
|
|
|
if content.meta.has("frameTags") and content.meta.frameTags.size() > 0:
|
|
for tag in content.meta.frameTags:
|
|
var selected_frames = frames.slice(tag.from, tag.to + 1)
|
|
_add_animation_frames(sprite_frames, tag.name, selected_frames, texture, tag.direction, frame_cache)
|
|
else:
|
|
_add_animation_frames(sprite_frames, "default", frames, texture)
|
|
|
|
return sprite_frames
|
|
|
|
|
|
func _add_animation_frames(
|
|
sprite_frames: SpriteFrames,
|
|
anim_name: String,
|
|
frames : Array,
|
|
texture,
|
|
direction = 'forward',
|
|
frame_cache = {}
|
|
):
|
|
var animation_name := anim_name
|
|
var is_loopable = _is_loop_config_enabled()
|
|
|
|
var loop_prefix := _loop_config_prefix()
|
|
if animation_name.begins_with(loop_prefix):
|
|
animation_name = anim_name.trim_prefix(loop_prefix)
|
|
is_loopable = not is_loopable
|
|
|
|
sprite_frames.add_animation(animation_name)
|
|
|
|
var min_duration = _get_min_duration(frames)
|
|
var fps = _calculate_fps(min_duration)
|
|
|
|
if direction == 'reverse':
|
|
frames.reverse()
|
|
|
|
for frame in frames:
|
|
_add_to_sprite_frames(sprite_frames, animation_name, texture, frame, min_duration, frame_cache)
|
|
|
|
if direction == 'pingpong':
|
|
frames.remove_at(frames.size() - 1)
|
|
if is_loopable:
|
|
frames.remove_at(0)
|
|
frames.reverse()
|
|
|
|
for frame in frames:
|
|
_add_to_sprite_frames(sprite_frames, animation_name, texture, frame, min_duration, frame_cache)
|
|
|
|
sprite_frames.set_animation_loop(animation_name, is_loopable)
|
|
sprite_frames.set_animation_speed(animation_name, fps)
|
|
|
|
|
|
func _calculate_fps(min_duration: int) -> float:
|
|
return ceil(1000.0 / min_duration)
|
|
|
|
|
|
func _get_min_duration(frames) -> int:
|
|
var min_duration = 100000
|
|
for frame in frames:
|
|
if frame.duration < min_duration:
|
|
min_duration = frame.duration
|
|
return min_duration
|
|
|
|
|
|
func _parse_texture_path(path):
|
|
return ResourceLoader.load(path, 'Image', ResourceLoader.CACHE_MODE_REPLACE)
|
|
|
|
|
|
func _add_to_sprite_frames(
|
|
sprite_frames,
|
|
animation_name: String,
|
|
texture,
|
|
frame: Dictionary,
|
|
min_duration: int,
|
|
frame_cache: Dictionary
|
|
):
|
|
var atlas : AtlasTexture = _create_atlastexture_from_frame(texture, frame, sprite_frames, frame_cache)
|
|
|
|
var number_of_sprites = ceil(frame.duration / min_duration)
|
|
for _i in range(number_of_sprites):
|
|
sprite_frames.add_frame(animation_name, atlas)
|
|
|
|
|
|
func _create_atlastexture_from_frame(
|
|
image,
|
|
frame_data,
|
|
sprite_frames: SpriteFrames,
|
|
frame_cache: Dictionary
|
|
) -> AtlasTexture:
|
|
var frame = frame_data.frame
|
|
var region := Rect2(frame.x, frame.y, frame.w, frame.h)
|
|
var key := "%s_%s_%s_%s" % [frame.x, frame.y, frame.w, frame.h]
|
|
|
|
var texture = frame_cache.get(key)
|
|
|
|
if texture != null and texture.atlas == image:
|
|
return texture
|
|
|
|
var atlas_texture := AtlasTexture.new()
|
|
atlas_texture.atlas = image
|
|
atlas_texture.region = region
|
|
|
|
frame_cache[key] = atlas_texture
|
|
|
|
return atlas_texture
|
|
|
|
|
|
func _scan_filesystem():
|
|
_file_system.scan()
|
|
await _file_system.filesystem_changed
|
|
|
|
|
|
func list_layers(file: String, only_visibles = false) -> Array:
|
|
return _aseprite.list_layers(file, only_visibles)
|
|
|
|
|
|
func _get_file_basename(file_path: String) -> String:
|
|
return file_path.get_file().trim_suffix('.%s' % file_path.get_extension())
|
|
|
|
|
|
func _loop_config_prefix() -> String:
|
|
return _config.get_animation_loop_exception_prefix()
|
|
|
|
|
|
func _is_loop_config_enabled() -> bool:
|
|
return _config.is_default_animation_loop_enabled()
|