Blobby/addons/AsepriteWizard/animated_sprite/sprite_frames_creator.gd

311 lines
8.8 KiB
GDScript

tool
extends Reference
var result_code = preload("../config/result_codes.gd")
var _aseprite = preload("../aseprite/aseprite.gd").new()
enum {
FILE_EXPORT_MODE,
LAYERS_EXPORT_MODE
}
var _config
var _file_system: EditorFileSystem
var _should_check_file_system := false
func init(config, editor_file_system: EditorFileSystem = null):
_config = config
_file_system = editor_file_system
_should_check_file_system = _file_system != null
_aseprite.init(config)
func _loop_config_prefix() -> String:
return _config.get_animation_loop_exception_prefix()
func _is_loop_config_enabled() -> String:
return _config.is_default_animation_loop_enabled()
func create_animations(sprite: AnimatedSprite, options: Dictionary):
if not _aseprite.test_command():
return result_code.ERR_ASEPRITE_CMD_NOT_FOUND
var dir = Directory.new()
if not dir.file_exists(options.source):
return result_code.ERR_SOURCE_FILE_NOT_FOUND
if not dir.dir_exists(options.output_folder):
return result_code.ERR_OUTPUT_FOLDER_NOT_FOUND
var result = _create_animations_from_file(sprite, options)
if result is GDScriptFunctionState:
result = yield(result, "completed")
if result != result_code.SUCCESS:
printerr(result_code.get_error_message(result))
func _create_animations_from_file(sprite: AnimatedSprite, options: Dictionary):
var output
if options.get("layer", "") == "":
output = _aseprite.export_file(options.source, options.output_folder, options)
else:
output = _aseprite.export_layer(options.source, options.layer, options.output_folder, options)
if output.empty():
return result_code.ERR_ASEPRITE_EXPORT_FAILED
yield(_scan_filesystem(), "completed")
var result = _import(output, sprite)
if _config.should_remove_source_files():
var dir = Directory.new()
dir.remove(output.data_file)
yield(_scan_filesystem(), "completed")
return result
func create_resource(source_file: String, output_folder: String, options = {}):
var export_mode = options.get('export_mode', FILE_EXPORT_MODE)
if not _aseprite.test_command():
return result_code.ERR_ASEPRITE_CMD_NOT_FOUND
var dir = Directory.new()
if not dir.file_exists(source_file):
return result_code.ERR_SOURCE_FILE_NOT_FOUND
if not dir.dir_exists(output_folder):
return result_code.ERR_OUTPUT_FOLDER_NOT_FOUND
match export_mode:
FILE_EXPORT_MODE:
if _should_check_file_system:
return yield(create_sprite_frames_from_aseprite_file(source_file, output_folder, options), "completed")
return create_sprite_frames_from_aseprite_file(source_file, output_folder, options)
LAYERS_EXPORT_MODE:
if _should_check_file_system:
return yield(create_sprite_frames_from_aseprite_layers(source_file, output_folder, options), "completed")
return create_sprite_frames_from_aseprite_layers(source_file, output_folder, options)
_:
return result_code.ERR_UNKNOWN_EXPORT_MODE
func create_sprite_frames_from_aseprite_file(source_file: String, output_folder: String, options: Dictionary):
var output = _aseprite.export_file(source_file, output_folder, options)
if output.empty():
return result_code.ERR_ASEPRITE_EXPORT_FAILED
if (_should_check_file_system):
yield(_scan_filesystem(), "completed")
if options.get("do_not_create_resource", false):
return OK
var result = _import(output)
if options.get("remove_source_files_allowed", false) and _config.should_remove_source_files():
var dir = Directory.new()
dir.remove(output.data_file)
if (_should_check_file_system):
yield(_scan_filesystem(), "completed")
return result
func create_sprite_frames_from_aseprite_layers(source_file: String, output_folder: String, options: Dictionary):
var output = _aseprite.export_layers(source_file, output_folder, options)
if output.empty():
return result_code.ERR_NO_VALID_LAYERS_FOUND
var result = OK
if (_should_check_file_system):
yield(_scan_filesystem(), "completed")
var should_remove_source = options.get("remove_source_files_allowed", false) and _config.should_remove_source_files()
for o in output:
if o.empty():
result = result_code.ERR_ASEPRITE_EXPORT_FAILED
else:
if options.get("do_not_create_resource", false):
result = OK
else:
result = _import(o)
if should_remove_source:
var dir = Directory.new()
dir.remove(o.data_file)
if should_remove_source and _should_check_file_system:
yield(_scan_filesystem(), "completed")
return result
func _get_file_basename(file_path: String) -> String:
return file_path.get_file().trim_suffix('.%s' % file_path.get_extension())
func _import(data, animated_sprite = null) -> int:
var source_file = data.data_file
var sprite_sheet = data.sprite_sheet
var file = File.new()
var err = file.open(source_file, File.READ)
if err != OK:
return err
var content = parse_json(file.get_as_text())
if not _aseprite.is_valid_spritesheet(content):
return result_code.ERR_INVALID_ASEPRITE_SPRITESHEET
var texture = _parse_texture_path(sprite_sheet)
var resource = _create_sprite_frames_with_animations(content, texture)
if is_instance_valid(animated_sprite):
animated_sprite.frames = resource
return result_code.SUCCESS
var save_path = "%s.%s" % [source_file.get_basename(), "res"]
var code = ResourceSaver.save(save_path, resource, ResourceSaver.FLAG_REPLACE_SUBRESOURCE_PATHS)
resource.take_over_path(save_path)
return code
func _create_sprite_frames_with_animations(content, texture) -> SpriteFrames:
var frame_cache = {}
var frames = _aseprite.get_content_frames(content)
var sprite_frames := SpriteFrames.new()
sprite_frames.remove_animation("default")
if content.meta.has("frameTags") and content.meta.frameTags.size() > 0:
for tag in content.meta.frameTags:
var selected_frames = frames.slice(tag.from, tag.to)
_add_animation_frames(sprite_frames, tag.name, selected_frames, texture, tag.direction, frame_cache)
else:
_add_animation_frames(sprite_frames, "default", frames, texture)
return sprite_frames
func _add_animation_frames(
sprite_frames: SpriteFrames,
anim_name: String,
frames : Array,
texture,
direction = 'forward',
frame_cache = {}
):
var animation_name := anim_name
var is_loopable = _is_loop_config_enabled()
var loop_prefix := _loop_config_prefix()
if animation_name.begins_with(loop_prefix):
animation_name = anim_name.trim_prefix(loop_prefix)
is_loopable = not is_loopable
sprite_frames.add_animation(animation_name)
var min_duration = _get_min_duration(frames)
var fps = _calculate_fps(min_duration)
if direction == 'reverse':
frames.invert()
for frame in frames:
_add_to_sprite_frames(sprite_frames, animation_name, texture, frame, min_duration, frame_cache)
if direction == 'pingpong':
frames.remove(frames.size() - 1)
if is_loopable:
frames.remove(0)
frames.invert()
for frame in frames:
_add_to_sprite_frames(sprite_frames, animation_name, texture, frame, min_duration, frame_cache)
sprite_frames.set_animation_loop(animation_name, is_loopable)
sprite_frames.set_animation_speed(animation_name, fps)
func _calculate_fps(min_duration: int) -> float:
return ceil(1000.0 / min_duration)
func _get_min_duration(frames) -> int:
var min_duration = 100000
for frame in frames:
if frame.duration < min_duration:
min_duration = frame.duration
return min_duration
func _parse_texture_path(path):
if not _should_check_file_system and not ResourceLoader.has_cached(path):
# this is a fallback for the importer. It generates the spritesheet file when it hasn't
# been imported before. Files generated in this method are usually
# bigger in size than the ones imported by Godot's default importer.
var image = Image.new()
image.load(path)
var texture = ImageTexture.new()
texture.create_from_image(image, 0)
return texture
return ResourceLoader.load(path, 'Image', true)
func _add_to_sprite_frames(
sprite_frames,
animation_name: String,
texture,
frame: Dictionary,
min_duration: int,
frame_cache: Dictionary
):
var atlas : AtlasTexture = _create_atlastexture_from_frame(texture, frame, sprite_frames, frame_cache)
var number_of_sprites = ceil(frame.duration / min_duration)
for _i in range(number_of_sprites):
sprite_frames.add_frame(animation_name, atlas)
func _create_atlastexture_from_frame(
image,
frame_data,
sprite_frames: SpriteFrames,
frame_cache: Dictionary
) -> AtlasTexture:
var frame = frame_data.frame
var region := Rect2(frame.x, frame.y, frame.w, frame.h)
var key := "%s_%s_%s_%s" % [frame.x, frame.y, frame.w, frame.h]
var texture = frame_cache.get(key)
if texture != null and texture.atlas == image:
return texture
var atlas_texture := AtlasTexture.new()
atlas_texture.atlas = image
atlas_texture.region = region
frame_cache[key] = atlas_texture
return atlas_texture
func _scan_filesystem():
_file_system.scan()
yield(_file_system, "filesystem_changed")
func list_layers(file: String, only_visibles = false) -> Array:
return _aseprite.list_layers(file, only_visibles)