Add addons

This commit is contained in:
Joey Yakimowich-Payne 2023-01-03 16:53:53 -07:00
commit 3367bad24f
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
527 changed files with 36197 additions and 0 deletions

View file

@ -0,0 +1,208 @@
@tool
extends PanelContainer
const wizard_config = preload("../../config/wizard_config.gd")
const result_code = preload("../../config/result_codes.gd")
var sprite_frames_creator = preload("../sprite_frames_creator.gd").new()
var scene: Node
var sprite: Node
var config
var file_system: EditorFileSystem
var _layer: String = ""
var _source: String = ""
var _file_dialog_aseprite: FileDialog
var _output_folder_dialog: FileDialog
var _importing := false
var _output_folder := ""
var _out_folder_default := "[Same as scene]"
var _layer_default := "[all]"
@onready var _source_field = $margin/VBoxContainer/source/button
@onready var _layer_field = $margin/VBoxContainer/layer/options
@onready var _options_title = $margin/VBoxContainer/options_title/options_title
@onready var _options_container = $margin/VBoxContainer/options
@onready var _out_folder_field = $margin/VBoxContainer/options/out_folder/button
@onready var _out_filename_field = $margin/VBoxContainer/options/out_filename/LineEdit
@onready var _visible_layers_field = $margin/VBoxContainer/options/visible_layers/CheckButton
@onready var _ex_pattern_field = $margin/VBoxContainer/options/ex_pattern/LineEdit
func _ready():
var cfg = wizard_config.load_config(sprite)
if cfg == null:
_load_default_config()
else:
_load_config(cfg)
sprite_frames_creator.init(config, file_system)
func _load_config(cfg):
if cfg.has("source"):
_set_source(cfg.source)
if cfg.get("layer", "") != "":
_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", "")
_set_options_visible(cfg.get("op_exp", false))
func _load_default_config():
_ex_pattern_field.text = config.get_default_exclusion_pattern()
_set_options_visible(false)
func _set_source(source):
_source = source
_source_field.text = _source
_source_field.tooltip_text = _source
func _set_layer(layer):
_layer = layer
_layer_field.add_item(_layer)
func _on_layer_button_down():
if _source == "":
_show_message("Please. Select source file first.")
return
var layers = sprite_frames_creator.list_layers(ProjectSettings.globalize_path(_source))
var current = 0
_layer_field.clear()
_layer_field.add_item(_layer_default)
for l in layers:
if l == "":
continue
_layer_field.add_item(l)
if l == _layer:
current = _layer_field.get_item_count() - 1
_layer_field.select(current)
func _on_layer_item_selected(index):
if index == 0:
_layer = ""
return
_layer = _layer_field.get_item_text(index)
_save_config()
func _on_source_pressed():
_open_source_dialog()
func _on_import_pressed():
if _importing:
return
_importing = true
var root = get_tree().get_edited_scene_root()
if _source == "":
_show_message("Aseprite file not selected.")
_importing = false
return
var options = {
"source": ProjectSettings.globalize_path(_source),
"output_folder": _output_folder if _output_folder != "" else root.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()
await sprite_frames_creator.create_animations(sprite, options)
_importing = false
func _save_config():
wizard_config.save_config(sprite, config.is_use_metadata_enabled(), {
"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
})
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()

View file

@ -0,0 +1,145 @@
[gd_scene load_steps=2 format=3 uid="uid://vej7yqkbtd5f"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/animated_sprite/docks/animated_sprite_inspector_dock.gd" id="1"]
[node name="animated_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="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="import" type="Button" parent="margin/VBoxContainer"]
layout_mode = 2
text = "Import"
[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"]

View file

@ -0,0 +1,37 @@
@tool
extends TabContainer
signal close_requested
const WizardWindow = preload("./as_wizard_window.tscn")
var _config
var _file_system: EditorFileSystem
func _ready():
$Import.init(_config, _file_system)
$Import.connect("close_requested",Callable(self,"emit_signal").bind("close_requested"))
$Import.connect("import_success",Callable($History,"add_entry"))
$History.init(_config)
$History.connect("request_edit",Callable(self,"_on_edit_request"))
$History.connect("request_import",Callable(self,"_on_import_request"))
func init(config, editor_file_system: EditorFileSystem):
_config = config
_file_system = editor_file_system
func _on_AsWizardDockContainer_tab_changed(tab: int):
if tab == 1:
$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()

View file

@ -0,0 +1,26 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://addons/AsepriteWizard/animated_sprite/docks/as_wizard_window.tscn" type="PackedScene" id=1]
[ext_resource path="res://addons/AsepriteWizard/animated_sprite/docks/as_wizard_dock_container.gd" type="Script" id=2]
[ext_resource path="res://addons/AsepriteWizard/animated_sprite/docks/sprite_frames_import_history.tscn" type="PackedScene" id=3]
[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 )]
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 4.0
offset_top = 32.0
offset_right = -4.0
offset_bottom = -4.0
tooltip_text = "SpriteFrames Importer"
[node name="History" parent="." instance=ExtResource( 3 )]
visible = false
[connection signal="tab_changed" from="." to="." method="_on_AsWizardDockContainer_tab_changed"]

View file

@ -0,0 +1,196 @@
@tool
extends PanelContainer
signal close_requested
signal import_success(file_settings)
var result_code = preload("../../config/result_codes.gd")
var _sf_creator = preload("../sprite_frames_creator.gd").new()
var _config
var _file_system: EditorFileSystem
var _file_dialog_aseprite: FileDialog
var _output_folder_dialog: FileDialog
var _warning_dialog: AcceptDialog
func _exit_tree():
_file_dialog_aseprite.queue_free()
_output_folder_dialog.queue_free()
_warning_dialog.queue_free()
func init(config, editor_file_system: EditorFileSystem):
_config = config
_file_system = editor_file_system
_file_dialog_aseprite = _create_aseprite_file_selection()
_output_folder_dialog = _create_outuput_folder_selection()
_warning_dialog = AcceptDialog.new()
_warning_dialog.exclusive = false
_sf_creator.init(_config, _file_system)
get_parent().get_parent().add_child(_file_dialog_aseprite)
get_parent().get_parent().add_child(_output_folder_dialog)
get_parent().get_parent().add_child(_warning_dialog)
_load_persisted_config()
func _load_persisted_config():
_split_mode_field().button_pressed = _config.should_split_layers()
_only_visible_layers_field().button_pressed = _config.should_include_only_visible_layers()
_exception_pattern_field().text = _config.get_exception_pattern()
_custom_name_field().text = _config.get_last_custom_name()
_file_location_field().text = _config.get_last_source_path()
_do_not_create_res_field().button_pressed = _config.should_not_create_resource()
var output_folder = _config.get_last_output_path()
_output_folder_field().text = output_folder if output_folder != "" else "res://"
func load_import_config(import_config: Dictionary):
_split_mode_field().button_pressed = import_config.options.export_mode == _sf_creator.LAYERS_EXPORT_MODE
_only_visible_layers_field().button_pressed = import_config.options.only_visible_layers
_exception_pattern_field().text = import_config.options.exception_pattern
_custom_name_field().text = import_config.options.output_filename
_file_location_field().text = import_config.source_file
_do_not_create_res_field().button_pressed = import_config.options.do_not_create_resource
_output_folder_field().text = import_config.output_location if import_config.output_location != "" else "res://"
func _open_aseprite_file_selection_dialog():
var current_selection = _file_location_field().text
if current_selection != "":
_file_dialog_aseprite.current_dir = current_selection.get_base_dir()
_file_dialog_aseprite.popup_centered_ratio()
func _open_output_folder_selection_dialog():
var current_selection = _output_folder_field().text
if current_selection != "":
_output_folder_dialog.current_dir = current_selection
_output_folder_dialog.popup_centered_ratio()
func _create_aseprite_file_selection():
var file_dialog = FileDialog.new()
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
file_dialog.access = FileDialog.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 = FileDialog.new()
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR
file_dialog.access = FileDialog.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
_config.set_last_source_path(localized_path)
func _on_output_folder_selected(path):
_output_folder_field().text = path
_config.set_last_output_path(path)
func _on_next_btn_up():
var aseprite_file = _file_location_field().text
var output_location = _output_folder_field().text
var split_layers = _split_mode_field().button_pressed
var export_mode = _sf_creator.LAYERS_EXPORT_MODE if split_layers else _sf_creator.FILE_EXPORT_MODE
var options = {
"export_mode": export_mode,
"exception_pattern": _exception_pattern_field().text,
"only_visible_layers": _only_visible_layers_field().button_pressed,
"output_filename": _custom_name_field().text,
"do_not_create_resource": _do_not_create_res_field().button_pressed,
"remove_source_files_allowed": true,
"output_folder": output_location,
}
var exit_code = await _sf_creator.create_and_save_resources(
ProjectSettings.globalize_path(aseprite_file),
options
)
if exit_code != OK:
_show_error(exit_code)
return
emit_signal("import_success", {
"source_file": aseprite_file,
"output_location": output_location,
"options": options,
})
_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 _save_config():
_config.set_split_layers(_split_mode_field().button_pressed)
_config.set_exception_pattern(_exception_pattern_field().text)
_config.set_custom_name(_custom_name_field().text)
_config.set_include_only_visible_layers(_only_visible_layers_field().button_pressed)
_config.set_do_not_create_resource(_do_not_create_res_field().button_pressed)
func _show_error(code: int):
_show_error_message(result_code.get_error_message(code))
func _show_error_message(message: String):
_warning_dialog.dialog_text = "Error: %s" % message
_warning_dialog.popup_centered()
func _show_import_success_message():
_warning_dialog.dialog_text = "Aseprite import succeeded"
_warning_dialog.popup_centered()
_save_config()
func _file_location_field() -> LineEdit:
return $container/options/file_location/HBoxContainer/file_location_path as LineEdit
func _output_folder_field() -> LineEdit:
return $container/options/output_folder/HBoxContainer/file_location_path as LineEdit
func _exception_pattern_field() -> LineEdit:
return $container/options/exclude_pattern/pattern as LineEdit
func _split_mode_field() -> CheckBox:
return $container/options/layer_importing_mode/split_layers/field as CheckBox
func _only_visible_layers_field() -> CheckBox:
return $container/options/layer_importing_mode/visible_layers/field as CheckBox
func _custom_name_field() -> LineEdit:
return $container/options/custom_filename/pattern as LineEdit
func _do_not_create_res_field() -> CheckBox:
return $container/options/layer_importing_mode/disable_resource_creation/field as CheckBox

View file

@ -0,0 +1,139 @@
[gd_scene load_steps=2 format=3 uid="uid://c5dwobjd34w3p"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/animated_sprite/docks/as_wizard_window.gd" id="1"]
[node name="ASWizardWindow" type="PanelContainer"]
offset_right = 600.0
offset_bottom = 600.0
size_flags_horizontal = 3
size_flags_vertical = 0
script = ExtResource("1")
[node name="container" type="MarginContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="options" type="VBoxContainer" parent="container"]
layout_mode = 2
[node name="file_location" type="VBoxContainer" parent="container/options"]
layout_mode = 2
[node name="file_location_label" type="Label" parent="container/options/file_location"]
layout_mode = 2
tooltip_text = "Location of the Aseprite *.ase source file"
mouse_filter = 1
text = "Aseprite File Location:*"
clip_text = true
[node name="HBoxContainer" type="HBoxContainer" parent="container/options/file_location"]
layout_mode = 2
[node name="file_location_path" type="LineEdit" parent="container/options/file_location/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
caret_blink = true
[node name="file_location_btn" type="Button" parent="container/options/file_location/HBoxContainer"]
layout_mode = 2
text = "Select"
[node name="output_folder" type="VBoxContainer" parent="container/options"]
layout_mode = 2
[node name="label" type="Label" parent="container/options/output_folder"]
layout_mode = 2
tooltip_text = "Folder where the SpriteSheet resource is going to be saved"
mouse_filter = 1
text = "Output folder:*"
[node name="HBoxContainer" type="HBoxContainer" parent="container/options/output_folder"]
layout_mode = 2
[node name="file_location_path" type="LineEdit" parent="container/options/output_folder/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "res://"
caret_blink = true
[node name="output_folder_btn" type="Button" parent="container/options/output_folder/HBoxContainer"]
layout_mode = 2
text = "Select"
[node name="exclude_pattern" type="VBoxContainer" parent="container/options"]
layout_mode = 2
[node name="label" type="Label" parent="container/options/exclude_pattern"]
layout_mode = 2
tooltip_text = "If layer name matches this pattern, it won't be exported."
mouse_filter = 1
text = "Exclude layers with name matching pattern:"
[node name="pattern" type="LineEdit" parent="container/options/exclude_pattern"]
layout_mode = 2
caret_blink = true
[node name="custom_filename" type="VBoxContainer" parent="container/options"]
layout_mode = 2
[node name="label" type="Label" parent="container/options/custom_filename"]
layout_mode = 2
tooltip_text = "Output filename. In case layers are not being merged, this is used as file prefix.
If not set, source filename is used."
mouse_filter = 1
text = "Output file name / prefix:"
[node name="pattern" type="LineEdit" parent="container/options/custom_filename"]
layout_mode = 2
caret_blink = true
[node name="layer_importing_mode" type="VBoxContainer" parent="container/options"]
layout_mode = 2
[node name="label" type="Label" parent="container/options/layer_importing_mode"]
layout_mode = 2
mouse_filter = 1
text = "Options:"
[node name="split_layers" type="HBoxContainer" parent="container/options/layer_importing_mode"]
layout_mode = 2
[node name="field" type="CheckBox" parent="container/options/layer_importing_mode/split_layers"]
layout_mode = 2
tooltip_text = "If selected, one resource will be created for each layer.
If not selected, layers will be merged and exported as one SpriteSheet."
text = "Split layers in multiple resources"
[node name="visible_layers" type="HBoxContainer" parent="container/options/layer_importing_mode"]
layout_mode = 2
[node name="field" type="CheckBox" parent="container/options/layer_importing_mode/visible_layers"]
layout_mode = 2
tooltip_text = "If selected, layers not visible in the source file won't be included in the final image."
text = "Only include visible layers"
[node name="disable_resource_creation" type="HBoxContainer" parent="container/options/layer_importing_mode"]
layout_mode = 2
[node name="field" type="CheckBox" parent="container/options/layer_importing_mode/disable_resource_creation"]
layout_mode = 2
tooltip_text = "Does not create SpriteFrames resource. Useful if you are only interested in the .json and .png output from Aseprite."
text = "Do not create resource file"
[node name="buttons" type="HBoxContainer" parent="container/options"]
layout_mode = 2
alignment = 2
[node name="close" type="Button" parent="container/options/buttons"]
layout_mode = 2
text = "Close"
[node name="next" type="Button" parent="container/options/buttons"]
layout_mode = 2
text = "Import"
[connection signal="button_up" from="container/options/file_location/HBoxContainer/file_location_btn" to="." method="_open_aseprite_file_selection_dialog"]
[connection signal="button_up" from="container/options/output_folder/HBoxContainer/output_folder_btn" to="." method="_open_output_folder_selection_dialog"]
[connection signal="button_up" from="container/options/buttons/close" to="." method="_on_close_btn_up"]
[connection signal="button_up" from="container/options/buttons/next" to="." method="_on_next_btn_up"]

View file

@ -0,0 +1,226 @@
@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 SORT_BY_DATE := 0
const SORT_BY_PATH := 1
const INITIAL_GRID_INDEX := 3
var _config
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 init(config):
_config = config
func reload():
if _history:
return
_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 source_path = SourcePathField.instantiate()
source_path.set_entry(entry)
grid.get_child(INITIAL_GRID_INDEX).add_sibling(source_path)
var output_path = OutputPathField.instantiate()
output_path.text = entry.output_location
source_path.add_sibling(output_path)
var import_date = ImportDateField.instantiate()
import_date.set_date(entry.import_date)
output_path.add_sibling(import_date)
var actions = ItemActions.instantiate()
actions.history_index = index
import_date.add_sibling(actions)
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"))
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,
}
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 _config.is_single_file_history() and _history_nodes.has(file_settings.source_file):
_remove_entries(file_settings.source_file)
_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()
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.source_path_node, INITIAL_GRID_INDEX + 1)
grid.move_child(entry.output_path_node, INITIAL_GRID_INDEX + 2)
grid.move_child(entry.import_date_node, INITIAL_GRID_INDEX + 3)
grid.move_child(entry.actions_node, INITIAL_GRID_INDEX + 4)

View file

@ -0,0 +1,81 @@
[gd_scene load_steps=2 format=3 uid="uid://cyoin5ncul0fm"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/animated_sprite/docks/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 = "Name"
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 = 4
[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="date_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"]
layout_mode = 2
text = "Date"
[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,12 @@
@tool
extends Label
func set_date(timestamp: String):
self.text = timestamp
# self.text = _format_date(timestamp)
#func _format_date(timestamp: int) -> String:
# var dt = Time.get_datetime_dict_from_system_from_unix_time(timestamp)
# return "%04d-%02d-%02d %02d:%02d:%02d" % [dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second]

View file

@ -0,0 +1,11 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/AsepriteWizard/animated_sprite/docks/wizard_nodes/import_date.gd" type="Script" id=1]
[node name="import_date" type="Label"]
offset_left = 660.0
offset_top = 39.0
offset_right = 774.0
offset_bottom = 53.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=2]
[ext_resource path="res://addons/AsepriteWizard/animated_sprite/docks/wizard_nodes/list_actions.gd" type="Script" 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,22 @@
@tool
extends LineEdit
func set_entry(entry: Dictionary):
self.text = entry.source_file
self.tooltip_text = _format_hint(entry)
func _format_hint(entry: Dictionary) -> String:
return """Output filename/prefix: %s
Ex. pattern: %s
Split: %s
Only visible: %s
No Resource: %s
""" % [
entry.options.output_filename,
entry.options.exception_pattern,
"yes" if entry.options.export_mode else "no",
"yes" if entry.options.only_visible_layers else "no",
"yes" if entry.options.do_not_create_resource else "no",
]

View file

@ -0,0 +1,18 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/AsepriteWizard/animated_sprite/docks/wizard_nodes/source_path.gd" type="Script" 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,129 @@
@tool
extends EditorImportPlugin
const result_codes = preload("../config/result_codes.gd")
var config
var _sf_creator = preload("sprite_frames_creator.gd").new()
var file_system: EditorFileSystem
func _get_importer_name():
return "aseprite_wizard.plugin"
func _get_visible_name():
return "Aseprite SpriteFrames Importer"
func _get_recognized_extensions():
return ["aseprite", "ase"]
func _get_save_extension():
return "res"
func _get_resource_type():
return "SpriteFrames"
func _get_preset_count():
return 1
func _get_preset_name(i):
return "Default"
func _get_priority():
return 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": ''},
{"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 replace_vars(pattern : String, vars : Dictionary):
var result = pattern;
for k in vars:
var v = vars[k]
result = result.replace("{%s}" % k, v)
return result
static func get_sheet_type_hint_string() -> String:
var hint_string := "Packed"
for number in [2, 4, 8, 16, 32]:
hint_string += ",%s columns" % number
hint_string += ",Strip"
return hint_string
func _import(source_file, save_path, options, platform_variants, gen_files):
var absolute_source_file = ProjectSettings.globalize_path(source_file)
var absolute_save_path = ProjectSettings.globalize_path(save_path)
var source_path = source_file.substr(0, source_file.rfind('/'))
var source_basename = source_file.substr(source_path.length()+1, -1)
source_basename = source_basename.substr(0, source_basename.rfind('.'))
_sf_creator.init(config, file_system)
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 resources = await _sf_creator.create_resources(absolute_source_file, aseprite_opts)
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 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,18 @@
@tool
extends EditorInspectorPlugin
const ASInspectorDock = preload("./docks/animated_sprite_inspector_dock.tscn")
var config
var file_system: EditorFileSystem
func _can_handle(object):
return object is AnimatedSprite2D || object is AnimatedSprite3D
func _parse_end(object):
var dock = ASInspectorDock.instantiate()
dock.sprite = object
dock.config = config
dock.file_system = file_system
add_custom_control(dock)

View file

@ -0,0 +1,352 @@
@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()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,187 @@
@tool
extends RefCounted
var _config
func init(config):
_config = config
#
# Output:
# {
# "data_file": file path to the json file
# "sprite_sheet": file path to the raw image file
# }
func export_file(file_name: String, output_folder: String, options: Dictionary) -> Dictionary:
var exception_pattern = options.get('exception_pattern', "")
var only_visible_layers = options.get('only_visible_layers', false)
var output_name = file_name if options.get('output_filename') == "" else options.get('output_filename', file_name)
var basename = _get_file_basename(output_name)
var output_dir = output_folder.replace("res://", "./")
var data_file = "%s/%s.json" % [output_dir, basename]
var sprite_sheet = "%s/%s.png" % [output_dir, basename]
var output = []
var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet)
if not only_visible_layers:
arguments.push_front("--all-layers")
_add_sheet_type_arguments(arguments, options)
_add_ignore_layer_arguments(file_name, arguments, exception_pattern)
var exit_code = _execute(arguments, output)
if exit_code != 0:
printerr('aseprite: failed to export spritesheet')
printerr(output)
return {}
return {
'data_file': data_file.replace("./", "res://"),
"sprite_sheet": sprite_sheet.replace("./", "res://")
}
func export_layers(file_name: String, output_folder: String, options: Dictionary) -> Array:
var exception_pattern = options.get('exception_pattern', "")
var only_visible_layers = options.get('only_visible_layers', false)
var basename = _get_file_basename(file_name)
var layers = list_layers(file_name, only_visible_layers)
var exception_regex = _compile_regex(exception_pattern)
var output = []
for layer in layers:
if layer != "" and (not exception_regex or exception_regex.search(layer) == null):
output.push_back(export_layer(file_name, layer, output_folder, options))
return output
func export_layer(file_name: String, layer_name: String, output_folder: String, options: Dictionary) -> Dictionary:
var output_prefix = options.get('output_filename', "").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 output = []
var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet)
arguments.push_front(layer_name)
arguments.push_front("--layer")
_add_sheet_type_arguments(arguments, options)
var exit_code = _execute(arguments, output)
if exit_code != 0:
print('aseprite: failed to export layer spritesheet')
print(output)
return {}
return {
'data_file': data_file.replace("./", "res://"),
"sprite_sheet": sprite_sheet.replace("./", "res://")
}
func _add_ignore_layer_arguments(file_name: String, arguments: Array, exception_pattern: String):
var layers = _get_exception_layers(file_name, exception_pattern)
if not layers.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 _export_command_common_arguments(source_name: String, data_path: String, spritesheet_path: String) -> Array:
return [
"-b",
"--list-tags",
"--data",
data_path,
"--format",
"json-array",
"--sheet",
spritesheet_path,
source_name
]
func _execute(arguments, output):
return OS.execute(_aseprite_command(), arguments, 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()

View file

@ -0,0 +1,19 @@
[preset]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,311 @@
@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_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'
# custom preset
const _IMPORT_PRESET_ENABLED = 'aseprite/import/preset/enable_custom_preset'
const _IMPORT_PRESET_KEY = 'aseprite/import/preset/preset'
const _PIXEL_2D_PRESET_CFG = 'res://addons/AsepriteWizard/config/2d_pixel_preset.cfg'
# 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'
# wizard history
const _HISTORY_CONFIG_FILE_CFG_KEY = 'aseprite/wizard/history/cache_file_path'
const _HISTORY_SINGLE_ENTRY_KEY = 'aseprite/wizard/history/keep_one_entry_per_source_file'
const _DEFAULT_HISTORY_CONFIG_FILE_PATH = 'res://.aseprite_wizard_history'
# IMPORT SETTINGS
const _I_LAST_SOURCE_PATH_KEY = 'i_source'
const _I_LAST_OUTPUT_DIR_KEY = 'i_output'
const _I_SHOULD_SPLIT_LAYERS_KEY = 'i_split_layers'
const _I_EXCEPTIONS_KEY = 'i_exceptions_key'
const _I_ONLY_VISIBLE_LAYERS_KEY = 'i_only_visible_layers'
const _I_CUSTOM_NAME_KEY = 'i_custom_name'
const _I_DO_NOT_CREATE_RES_KEY = 'i_disable_resource_creation'
# export
const _EXPORTER_ENABLE_KEY = 'aseprite/animation/storage/enable_metadata_removal_on_export'
var _editor_settings: EditorSettings
# INTERFACE SETTINGS
var _plugin_icons: Dictionary
#######################################################
# GLOBAL CONFIGS
######################################################
func default_command() -> String:
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
######################################################
func is_importer_enabled() -> bool:
return _get_project_setting(_IMPORTER_ENABLE_KEY, false)
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 is_import_preset_enabled() -> bool:
return _get_project_setting(_IMPORT_PRESET_ENABLED, false)
func is_single_file_history() -> bool:
return ProjectSettings.get(_HISTORY_SINGLE_ENTRY_KEY) == true
func get_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)
# history is saved and retrieved line-by-line so
# file becomes version control friendly
func save_import_history(history: Array):
var file = FileAccess.open(_get_history_file_path(), FileAccess.WRITE)
for entry in history:
file.store_line(JSON.new().stringify(entry))
file = null
func _get_history_file_path() -> String:
return _get_project_setting(_HISTORY_CONFIG_FILE_CFG_KEY, _DEFAULT_HISTORY_CONFIG_FILE_PATH)
func create_import_preset_setting() -> void:
if ProjectSettings.has_setting(_IMPORT_PRESET_KEY) && (ProjectSettings.get_setting(_IMPORT_PRESET_KEY) as Dictionary).size() > 0:
return
var preset := ConfigFile.new()
preset.load(_PIXEL_2D_PRESET_CFG)
var dict = {}
for key in preset.get_section_keys("preset"):
dict[key] = preset.get_value("preset", key)
_initialize_project_cfg(_IMPORT_PRESET_KEY, dict, TYPE_DICTIONARY)
#######################################################
# IMPORT CONFIGS
######################################################
func get_last_source_path() -> String:
return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_LAST_SOURCE_PATH_KEY, "")
func set_last_source_path(source_path: String) -> void:
_editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_LAST_SOURCE_PATH_KEY, source_path)
func get_last_output_path() -> String:
return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_LAST_OUTPUT_DIR_KEY, "")
func set_last_output_path(output_path: String) -> void:
_editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_LAST_OUTPUT_DIR_KEY, output_path)
func should_split_layers() -> bool:
return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_SHOULD_SPLIT_LAYERS_KEY, false)
func set_split_layers(should_split: bool) -> void:
_editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_SHOULD_SPLIT_LAYERS_KEY, false)
func get_exception_pattern() -> String:
return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_EXCEPTIONS_KEY, "")
func set_exception_pattern(pattern: String) -> void:
_editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_EXCEPTIONS_KEY, pattern)
func should_include_only_visible_layers() -> bool:
return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_ONLY_VISIBLE_LAYERS_KEY, false)
func set_include_only_visible_layers(include_only_visible: bool) -> void:
_editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_ONLY_VISIBLE_LAYERS_KEY, include_only_visible)
func get_last_custom_name() -> String:
return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_CUSTOM_NAME_KEY, "")
func set_custom_name(custom_name: String) -> void:
_editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_CUSTOM_NAME_KEY, custom_name)
func should_not_create_resource() -> bool:
return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_DO_NOT_CREATE_RES_KEY, false)
func set_do_not_create_resource(do_no_create: bool) -> void:
_editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_DO_NOT_CREATE_RES_KEY, do_no_create)
#######################################################
# INTERFACE SETTINGS
######################################################
func set_icons(plugin_icons: Dictionary) -> void:
_plugin_icons = plugin_icons
func get_icon(icon_name: String) -> Texture2D:
return _plugin_icons[icon_name]
#######################################################
# INITIALIZATION
######################################################
func initialize_project_settings():
_initialize_project_cfg(_DEFAULT_EXCLUSION_PATTERN_KEY, "", TYPE_STRING)
_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(_IMPORT_PRESET_ENABLED, false, TYPE_BOOL)
_initialize_project_cfg(_REMOVE_SOURCE_FILES_KEY, true, TYPE_BOOL)
_initialize_project_cfg(_IMPORTER_ENABLE_KEY, false, TYPE_BOOL)
_initialize_project_cfg(_EXPORTER_ENABLE_KEY, true, TYPE_BOOL)
_initialize_project_cfg(_HISTORY_CONFIG_FILE_CFG_KEY, _DEFAULT_HISTORY_CONFIG_FILE_PATH, TYPE_STRING, PROPERTY_HINT_GLOBAL_FILE)
_initialize_project_cfg(_HISTORY_SINGLE_ENTRY_KEY, false, TYPE_BOOL)
_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,
_IMPORT_PRESET_ENABLED,
_IMPORT_PRESET_KEY,
_REMOVE_SOURCE_FILES_KEY,
_IMPORTER_ENABLE_KEY,
_EXPORTER_ENABLE_KEY,
_HISTORY_CONFIG_FILE_CFG_KEY,
_HISTORY_SINGLE_ENTRY_KEY,
_SET_VISIBLE_TRACK_AUTOMATICALLY
]
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):
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,
})
func _get_project_setting(key: String, default_value):
var p = ProjectSettings.get(key)
return p if p else default_value
func create_import_file(data: Dictionary) -> void:
if !ProjectSettings.has_setting(_IMPORT_PRESET_KEY):
push_warning("no import settings found for 'aseprite_texture' in Project Settings")
return
var file_path := "%s.import" % [data.sprite_sheet]
var import_file := ConfigFile.new()
if import_file.load(file_path) == OK:
return
import_file.set_value("remap", "importer", "texture")
import_file.set_value("remap", "type", "CompressedTexture2D")
import_file.set_value("deps", "source_file", data.sprite_sheet)
var preset: Dictionary = ProjectSettings.get_setting(_IMPORT_PRESET_KEY)
for key in preset:
import_file.set_value("params", key, preset[key])
import_file.save(file_path)
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,33 @@
@tool
extends PopupPanel
var _config
@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 init(config):
_config = config
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 Path3D"
[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 \"Project > Tools > Aseprite Wizard Config\"."
ERR_SOURCE_FILE_NOT_FOUND:
return "source file does not exist"
ERR_OUTPUT_FOLDER_NOT_FOUND:
return "output location does not exist"
ERR_ASEPRITE_EXPORT_FAILED:
return "unable to import file"
ERR_INVALID_ASEPRITE_SPRITESHEET:
return "aseprite generated bad data file"
ERR_NO_VALID_LAYERS_FOUND:
return "no valid layers found"
_:
return "import failed with code %d" % code
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,71 @@
@tool
extends RefCounted
const WIZARD_CONFIG_META_NAME = "_aseprite_wizard_config_"
const WIZARD_CONFIG_MARKER = "aseprite_wizard_config"
const SEPARATOR = "|="
static func encode(object: Dictionary):
var text = "%s\n" % WIZARD_CONFIG_MARKER
for prop in object:
text += "%s%s%s\n" % [prop, SEPARATOR, object[prop]]
return Marshalls.utf8_to_base64(text)
static func decode(string: String):
var decoded = _decode_base64(string)
if not _is_wizard_config(decoded):
return null
var cfg = decoded.split("\n")
var config = {}
for c in cfg:
var parts = c.split(SEPARATOR, 1)
if parts.size() == 2:
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:Node):
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:Node, use_metadata:bool, cfg:Dictionary):
if use_metadata:
node.set_meta(WIZARD_CONFIG_META_NAME, cfg)
#Delete config from editor_description
var decoded = _decode_base64(node.editor_description)
if _is_wizard_config(decoded):
node.editor_description = ""
else:
node.editor_description = encode(cfg)

View file

@ -0,0 +1,57 @@
extends EditorExportPlugin
const wizard_config = preload("../config/wizard_config.gd")
func _export_file(path: String, type: String, features: PackedStringArray) -> void:
if type != "PackedScene": return
var scene : PackedScene = ResourceLoader.load(path, type, true)
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, path):
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:Node, path: String) -> bool:
if node.has_meta(wizard_config.WIZARD_CONFIG_META_NAME):
node.remove_meta(wizard_config.WIZARD_CONFIG_META_NAME)
print("Aseprite Wizard: Removed metadata from scene %s" % path)
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

View file

@ -0,0 +1,7 @@
[plugin]
name="Aseprite Wizard (Godot 4)"
description="Import Aseprite files to AnimationPlayers, AnimatedSprites and SpriteFrames."
author="Vinicius Gerevini"
version="6.0.0"
script="plugin.gd"

View file

@ -0,0 +1,150 @@
@tool
extends EditorPlugin
const ConfigDialog = preload('config/config_dialog.tscn')
const WizardWindow = preload("animated_sprite/docks/as_wizard_dock_container.tscn")
const ImportPlugin = preload("animated_sprite/import_plugin.gd")
const ExportPlugin = preload("export/metadata_export_plugin.gd")
const AnimatedSpriteInspectorPlugin = preload("animated_sprite/inspector_plugin.gd")
const SpriteInspectorPlugin = preload("animation_player/inspector_plugin.gd")
const menu_item_name = "Aseprite Spritesheet Wizard"
const config_menu_item_name = "Aseprite Wizard Config"
var config = preload("config/config.gd").new()
var window: TabContainer
var config_window: PopupPanel
var import_plugin : EditorImportPlugin
var export_plugin : EditorExportPlugin
var sprite_inspector_plugin: EditorInspectorPlugin
var animated_sprite_inspector_plugin: EditorInspectorPlugin
var _importer_enabled = false
var _exporter_enabled = false
func _enter_tree():
_load_config()
_setup_menu_entries()
_setup_importer()
_setup_exporter()
_configure_preset()
_setup_animated_sprite_inspector_plugin()
_setup_sprite_inspector_plugin()
func _disable_plugin():
_remove_menu_entries()
_remove_importer()
_remove_exporter()
_remove_wizard_dock()
_remove_inspector_plugins()
config.clear_project_settings()
config.set_icons({})
func _load_config():
var editor_gui = get_editor_interface().get_base_control()
config._editor_settings = get_editor_interface().get_editor_settings()
config.set_icons({
"collapsed": editor_gui.get_theme_icon("GuiTreeArrowRight", "EditorIcons"),
"expanded": editor_gui.get_theme_icon("GuiTreeArrowDown", "EditorIcons"),
})
config.initialize_project_settings()
func _setup_menu_entries():
add_tool_menu_item(menu_item_name, _open_window)
add_tool_menu_item(config_menu_item_name, _open_config_dialog)
func _remove_menu_entries():
remove_tool_menu_item(menu_item_name)
remove_tool_menu_item(config_menu_item_name)
func _setup_importer():
if config.is_importer_enabled():
import_plugin = ImportPlugin.new()
import_plugin.file_system = get_editor_interface().get_resource_filesystem()
import_plugin.config = config
add_import_plugin(import_plugin)
_importer_enabled = true
func _configure_preset():
if config.is_import_preset_enabled():
config.create_import_preset_setting()
func _remove_importer():
if _importer_enabled:
remove_import_plugin(import_plugin)
_importer_enabled = false
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()
sprite_inspector_plugin.file_system = get_editor_interface().get_resource_filesystem()
sprite_inspector_plugin.config = config
add_inspector_plugin(sprite_inspector_plugin)
func _setup_animated_sprite_inspector_plugin():
animated_sprite_inspector_plugin = AnimatedSpriteInspectorPlugin.new()
animated_sprite_inspector_plugin.file_system = get_editor_interface().get_resource_filesystem()
animated_sprite_inspector_plugin.config = config
add_inspector_plugin(animated_sprite_inspector_plugin)
func _remove_inspector_plugins():
remove_inspector_plugin(sprite_inspector_plugin)
remove_inspector_plugin(animated_sprite_inspector_plugin)
func _remove_wizard_dock():
if window:
remove_control_from_bottom_panel(window)
window.queue_free()
window = null
func _open_window():
if window:
make_bottom_panel_item_visible(window)
return
window = WizardWindow.instantiate()
window.init(config, get_editor_interface().get_resource_filesystem())
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()
config_window.init(config)
get_editor_interface().get_base_control().add_child(config_window)
config_window.popup_centered()
func _on_window_closed():
if window:
remove_control_from_bottom_panel(window)
window.queue_free()
window = null

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 bitbrain
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.

View file

@ -0,0 +1,27 @@
## The blackboard is an object that can be used to store and access data between
## multiple nodes of the behaviour tree.
class_name Blackboard extends RefCounted
var blackboard: Dictionary = {}
func set_value(key: Variant, value: Variant, blackboard_name: String = 'default') -> void:
if not blackboard.has(blackboard_name):
blackboard[blackboard_name] = {}
blackboard[blackboard_name][key] = value
func get_value(key: Variant, default_value: Variant = null, blackboard_name: String = 'default') -> Variant:
if has_value(key, blackboard_name):
return blackboard[blackboard_name].get(key, default_value)
return default_value
func has_value(key: Variant, blackboard_name: String = 'default') -> bool:
return blackboard.has(blackboard_name) and blackboard[blackboard_name].has(key) and blackboard[blackboard_name][key] != null
func erase_value(key: Variant, blackboard_name: String = 'default') -> void:
if blackboard.has(blackboard_name):
blackboard[blackboard_name][key] = null

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="18.512016"
width="14.055721"
version="1.1"
id="svg8741"
sodipodi:docname="action.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8745" />
<sodipodi:namedview
id="namedview8743"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="7.6367532"
inkscape:cx="-17.284833"
inkscape:cy="19.183545"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg8741" />
<path
d="M 10.176432,3.6912688 Q 9.4137999,3.5779847 8.9573199,2.9622304 8.5018279,2.346623 8.6151119,1.5839909 q 0.113137,-0.76164287 0.727903,-1.21827047 0.615754,-0.45648072 1.3783861,-0.34319658 0.762632,0.1132841 1.218124,0.72889148 0.45648,0.61575427 0.343343,1.37739717 -0.113284,0.7626321 -0.729038,1.2191128 -0.614766,0.4566276 -1.377398,0.3433435 z M 8.3557339,18.50048 q -0.412474,-0.06127 -0.675246,-0.415727 -0.263613,-0.355592 -0.2024895,-0.767077 l 0.5938965,-3.998131 -1.1673025,-1.499791 -0.492278,1.19059 Q 6.067908,13.906466 5.2412215,14.299263 4.4143881,14.693049 3.5141916,14.369273 L 0.68012778,13.316428 q -0.3971933,-0.164141 -0.5825748,-0.549563 -0.1843923,-0.385274 -0.0202507,-0.782468 0.1463082,-0.420062 0.5218382,-0.606912 0.3753831,-0.185862 0.79544492,-0.03955 L 4.2327238,12.370146 6.3483399,6.4922013 5.1950516,6.7424632 4.9226408,8.5763411 Q 4.8584317,9.0085983 4.5028393,9.2722113 4.1483829,9.534983 3.7359088,9.473712 3.3234346,9.412442 3.0497828,9.0563693 2.7772671,8.6994546 2.8414761,8.2671975 L 3.0925819,6.5767458 Q 3.1935238,5.897202 3.6684284,5.3884589 4.1443222,4.8798627 4.8023572,4.703636 L 6.9923039,4.1443377 q 0.743073,-0.1838143 1.140791,-0.2409975 0.398708,-0.057036 0.728094,-0.00811 0.515345,0.076551 0.87859,0.3943727 0.3633911,0.3168322 0.5670941,0.7898969 l 0.629089,1.4410733 q 0.295795,0.6970267 0.885176,1.2688312 0.589381,0.5718045 1.447044,0.8892676 0.421051,0.1464552 0.62705,0.4611386 0.205852,0.3156725 0.147667,0.7073745 -0.07347,0.494573 -0.448694,0.754259 -0.376218,0.259539 -0.813966,0.08937 Q 11.753166,10.370427 10.957563,9.778099 10.159981,9.1854775 9.5990659,8.4288493 l -0.661707,1.9024227 0.845301,1.115306 q 0.2835431,0.357542 0.3859911,0.804445 0.102448,0.446903 0.03501,0.900921 l -0.6642771,4.471933 q -0.06112,0.411485 -0.416716,0.675098 -0.355445,0.262624 -0.76693,0.201501 z"
id="path8739"
style="fill:#ffb649;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bbia60elqjuti"
path="res://.godot/imported/action.svg-e8a91246d0ba9ba3cf84290d65648f06.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/action.svg"
dest_files=["res://.godot/imported/action.svg-e8a91246d0ba9ba3cf84290d65648f06.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="14.416"
width="15.416"
version="1.1"
id="svg1061"
sodipodi:docname="blackboard.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1065" />
<sodipodi:namedview
id="namedview1063"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="30.547013"
inkscape:cx="-0.42557352"
inkscape:cy="10.62297"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg1061" />
<path
d="M 0,4.333 V 1.937 Q 0,1.125 0.573,0.562 1.146,0 1.937,0 h 11.542 q 0.791,0 1.364,0.562 0.573,0.563 0.573,1.375 v 2.396 z m 4.354,1.396 v 8.687 H 1.937 q -0.791,0 -1.364,-0.573 Q 0,13.27 0,12.479 v -6.75 z m 6.708,0 h 4.354 v 6.75 q 0,0.791 -0.573,1.364 -0.573,0.573 -1.364,0.573 h -2.417 z m -1.396,0 v 8.687 H 5.75 V 5.729 Z"
id="path1059"
style="fill:#c689ff;fill-opacity:1;stroke:none;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dd4bmjexgxxu0"
path="res://.godot/imported/blackboard.svg-18d4dfd4f6de558de250b67251ff1e69.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/blackboard.svg"
dest_files=["res://.godot/imported/blackboard.svg-18d4dfd4f6de558de250b67251ff1e69.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16.375999"
width="14.126321"
version="1.1"
id="svg4"
sodipodi:docname="category_bt.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="15.273506"
inkscape:cx="7.071068"
inkscape:cy="10.377447"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="m 11.48968,7.688 q -0.354,0 -0.76,-0.219 -0.406,-0.219 -0.552,-0.51 L 9.5736798,6 q -0.167,-0.291 -0.167,-0.729 0,-0.437 0.167,-0.729 L 10.07368,3.667 q 0.146,-0.291 0.552,-0.531 0.406,-0.24 0.76,-0.24 h 0.875 q 0.375,0 0.792,0.219 0.417,0.219 0.562,0.552 l 0.355,0.854 q 0.145,0.334 0.156,0.761 0.01,0.427 -0.136,0.76 l -0.396,0.875 q -0.145,0.333 -0.552,0.552 -0.406,0.219 -0.781,0.219 z m -4.8120002,2.917 q -0.354,0 -0.761,-0.219 -0.406,-0.219 -0.573,-0.531 l -0.374,-0.917 q -0.146,-0.312 -0.146,-0.75 0,-0.438 0.146,-0.75 l 0.374,-0.917 q 0.167,-0.312 0.573,-0.531 0.407,-0.219 0.761,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.406,0.219 0.552,0.531 l 0.396,0.917 q 0.146,0.312 0.146,0.75 0,0.438 -0.146,0.75 l -0.396,0.917 q -0.146,0.312 -0.552,0.531 -0.406,0.219 -0.781,0.219 z m 0,-5.813 q -0.354,0 -0.761,-0.219 -0.406,-0.218 -0.573,-0.531 l -0.374,-0.875 q -0.146,-0.333 -0.146,-0.771 0,-0.437 0.146,-0.75 l 0.374,-0.896 q 0.167,-0.312 0.573,-0.531 0.407,-0.219 0.761,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.406,0.219 0.552,0.531 l 0.396,0.896 q 0.146,0.313 0.146,0.75 0,0.438 -0.146,0.771 l -0.396,0.875 q -0.146,0.313 -0.552,0.531 -0.406,0.219 -0.781,0.219 z m -4.813,2.896 q -0.375,0 -0.771,-0.219 Q 0.69867978,7.25 0.53167978,6.917 l -0.375,-0.875 Q -0.01032022,5.709 6.7977528e-4,5.282 0.01067978,4.855 0.17767978,4.521 l 0.333,-0.854 q 0.167,-0.333 0.57300002,-0.552 0.406,-0.219 0.781,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.407,0.219 0.573,0.552 l 0.334,0.854 q 0.166,0.334 0.177,0.761 0.01,0.427 -0.157,0.76 l -0.374,0.875 q -0.167,0.333 -0.563,0.552 -0.396,0.219 -0.771,0.219 z m 0,5.75 q -0.375,0 -0.781,-0.219 Q 0.67767978,13 0.51067978,12.667 l -0.333,-0.854 Q 0.01067978,11.48 6.7977528e-4,11.053 -0.01032022,10.626 0.15667978,10.292 l 0.375,-0.875 q 0.167,-0.333 0.56200002,-0.552 0.396,-0.219 0.771,-0.219 h 0.771 q 0.375,0 0.771,0.219 0.396,0.219 0.563,0.552 l 0.374,0.875 q 0.167,0.334 0.157,0.761 -0.011,0.427 -0.177,0.76 l -0.334,0.854 q -0.166,0.333 -0.573,0.552 -0.406,0.219 -0.781,0.219 z m 4.855,2.938 q -0.355,0 -0.761,-0.219 -0.406,-0.219 -0.594,-0.531 l -0.375,-0.896 q -0.166,-0.313 -0.166,-0.75 0,-0.438 0.146,-0.771 l 0.374,-0.875 q 0.167,-0.313 0.573,-0.531 0.407,-0.219 0.761,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.406,0.218 0.552,0.531 l 0.396,0.875 q 0.146,0.333 0.146,0.771 0,0.437 -0.146,0.75 l -0.396,0.896 q -0.146,0.312 -0.552,0.531 -0.406,0.219 -0.781,0.219 z M 11.48968,13.438 q -0.354,0 -0.77,-0.219 -0.417,-0.219 -0.563,-0.51 L 9.5936798,11.771 q -0.166,-0.291 -0.177,-0.718 -0.01,-0.427 0.157,-0.719 L 10.17768,9.376 q 0.146,-0.292 0.552,-0.511 0.406,-0.219 0.76,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.407,0.219 0.552,0.552 l 0.396,0.875 q 0.146,0.334 0.136,0.761 -0.011,0.427 -0.156,0.76 l -0.355,0.854 q -0.145,0.333 -0.562,0.552 -0.417,0.219 -0.792,0.219 z"
id="path2"
style="fill:#c689ff;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dnm43qa88fdpo"
path="res://.godot/imported/category_bt.svg-8537bebd1c5f62dca3d7ee7f17efeed4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/category_bt.svg"
dest_files=["res://.godot/imported/category_bt.svg-8537bebd1c5f62dca3d7ee7f17efeed4.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16.375999"
width="14.126321"
version="1.1"
id="svg4"
sodipodi:docname="category_composite.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="15.273506"
inkscape:cx="7.071068"
inkscape:cy="10.377447"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="m 11.48968,7.688 q -0.354,0 -0.76,-0.219 -0.406,-0.219 -0.552,-0.51 L 9.5736798,6 q -0.167,-0.291 -0.167,-0.729 0,-0.437 0.167,-0.729 L 10.07368,3.667 q 0.146,-0.291 0.552,-0.531 0.406,-0.24 0.76,-0.24 h 0.875 q 0.375,0 0.792,0.219 0.417,0.219 0.562,0.552 l 0.355,0.854 q 0.145,0.334 0.156,0.761 0.01,0.427 -0.136,0.76 l -0.396,0.875 q -0.145,0.333 -0.552,0.552 -0.406,0.219 -0.781,0.219 z m -4.8120002,2.917 q -0.354,0 -0.761,-0.219 -0.406,-0.219 -0.573,-0.531 l -0.374,-0.917 q -0.146,-0.312 -0.146,-0.75 0,-0.438 0.146,-0.75 l 0.374,-0.917 q 0.167,-0.312 0.573,-0.531 0.407,-0.219 0.761,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.406,0.219 0.552,0.531 l 0.396,0.917 q 0.146,0.312 0.146,0.75 0,0.438 -0.146,0.75 l -0.396,0.917 q -0.146,0.312 -0.552,0.531 -0.406,0.219 -0.781,0.219 z m 0,-5.813 q -0.354,0 -0.761,-0.219 -0.406,-0.218 -0.573,-0.531 l -0.374,-0.875 q -0.146,-0.333 -0.146,-0.771 0,-0.437 0.146,-0.75 l 0.374,-0.896 q 0.167,-0.312 0.573,-0.531 0.407,-0.219 0.761,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.406,0.219 0.552,0.531 l 0.396,0.896 q 0.146,0.313 0.146,0.75 0,0.438 -0.146,0.771 l -0.396,0.875 q -0.146,0.313 -0.552,0.531 -0.406,0.219 -0.781,0.219 z m -4.813,2.896 q -0.375,0 -0.771,-0.219 Q 0.69867978,7.25 0.53167978,6.917 l -0.375,-0.875 Q -0.01032022,5.709 6.7977528e-4,5.282 0.01067978,4.855 0.17767978,4.521 l 0.333,-0.854 q 0.167,-0.333 0.57300002,-0.552 0.406,-0.219 0.781,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.407,0.219 0.573,0.552 l 0.334,0.854 q 0.166,0.334 0.177,0.761 0.01,0.427 -0.157,0.76 l -0.374,0.875 q -0.167,0.333 -0.563,0.552 -0.396,0.219 -0.771,0.219 z m 0,5.75 q -0.375,0 -0.781,-0.219 Q 0.67767978,13 0.51067978,12.667 l -0.333,-0.854 Q 0.01067978,11.48 6.7977528e-4,11.053 -0.01032022,10.626 0.15667978,10.292 l 0.375,-0.875 q 0.167,-0.333 0.56200002,-0.552 0.396,-0.219 0.771,-0.219 h 0.771 q 0.375,0 0.771,0.219 0.396,0.219 0.563,0.552 l 0.374,0.875 q 0.167,0.334 0.157,0.761 -0.011,0.427 -0.177,0.76 l -0.334,0.854 q -0.166,0.333 -0.573,0.552 -0.406,0.219 -0.781,0.219 z m 4.855,2.938 q -0.355,0 -0.761,-0.219 -0.406,-0.219 -0.594,-0.531 l -0.375,-0.896 q -0.166,-0.313 -0.166,-0.75 0,-0.438 0.146,-0.771 l 0.374,-0.875 q 0.167,-0.313 0.573,-0.531 0.407,-0.219 0.761,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.406,0.218 0.552,0.531 l 0.396,0.875 q 0.146,0.333 0.146,0.771 0,0.437 -0.146,0.75 l -0.396,0.896 q -0.146,0.312 -0.552,0.531 -0.406,0.219 -0.781,0.219 z M 11.48968,13.438 q -0.354,0 -0.77,-0.219 -0.417,-0.219 -0.563,-0.51 L 9.5936798,11.771 q -0.166,-0.291 -0.177,-0.718 -0.01,-0.427 0.157,-0.719 L 10.17768,9.376 q 0.146,-0.292 0.552,-0.511 0.406,-0.219 0.76,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.407,0.219 0.552,0.552 l 0.396,0.875 q 0.146,0.334 0.136,0.761 -0.011,0.427 -0.156,0.76 l -0.355,0.854 q -0.145,0.333 -0.562,0.552 -0.417,0.219 -0.792,0.219 z"
id="path2"
style="fill:#40d29f;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dpxxgndj5jbe7"
path="res://.godot/imported/category_composite.svg-43f66e63a7ccfa5ac8ec6da0583b3246.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/category_composite.svg"
dest_files=["res://.godot/imported/category_composite.svg-43f66e63a7ccfa5ac8ec6da0583b3246.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16.375999"
width="14.126321"
version="1.1"
id="svg4"
sodipodi:docname="category_decorator.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="15.273506"
inkscape:cx="7.071068"
inkscape:cy="10.377447"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="m 11.48968,7.688 q -0.354,0 -0.76,-0.219 -0.406,-0.219 -0.552,-0.51 L 9.5736798,6 q -0.167,-0.291 -0.167,-0.729 0,-0.437 0.167,-0.729 L 10.07368,3.667 q 0.146,-0.291 0.552,-0.531 0.406,-0.24 0.76,-0.24 h 0.875 q 0.375,0 0.792,0.219 0.417,0.219 0.562,0.552 l 0.355,0.854 q 0.145,0.334 0.156,0.761 0.01,0.427 -0.136,0.76 l -0.396,0.875 q -0.145,0.333 -0.552,0.552 -0.406,0.219 -0.781,0.219 z m -4.8120002,2.917 q -0.354,0 -0.761,-0.219 -0.406,-0.219 -0.573,-0.531 l -0.374,-0.917 q -0.146,-0.312 -0.146,-0.75 0,-0.438 0.146,-0.75 l 0.374,-0.917 q 0.167,-0.312 0.573,-0.531 0.407,-0.219 0.761,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.406,0.219 0.552,0.531 l 0.396,0.917 q 0.146,0.312 0.146,0.75 0,0.438 -0.146,0.75 l -0.396,0.917 q -0.146,0.312 -0.552,0.531 -0.406,0.219 -0.781,0.219 z m 0,-5.813 q -0.354,0 -0.761,-0.219 -0.406,-0.218 -0.573,-0.531 l -0.374,-0.875 q -0.146,-0.333 -0.146,-0.771 0,-0.437 0.146,-0.75 l 0.374,-0.896 q 0.167,-0.312 0.573,-0.531 0.407,-0.219 0.761,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.406,0.219 0.552,0.531 l 0.396,0.896 q 0.146,0.313 0.146,0.75 0,0.438 -0.146,0.771 l -0.396,0.875 q -0.146,0.313 -0.552,0.531 -0.406,0.219 -0.781,0.219 z m -4.813,2.896 q -0.375,0 -0.771,-0.219 Q 0.69867978,7.25 0.53167978,6.917 l -0.375,-0.875 Q -0.01032022,5.709 6.7977528e-4,5.282 0.01067978,4.855 0.17767978,4.521 l 0.333,-0.854 q 0.167,-0.333 0.57300002,-0.552 0.406,-0.219 0.781,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.407,0.219 0.573,0.552 l 0.334,0.854 q 0.166,0.334 0.177,0.761 0.01,0.427 -0.157,0.76 l -0.374,0.875 q -0.167,0.333 -0.563,0.552 -0.396,0.219 -0.771,0.219 z m 0,5.75 q -0.375,0 -0.781,-0.219 Q 0.67767978,13 0.51067978,12.667 l -0.333,-0.854 Q 0.01067978,11.48 6.7977528e-4,11.053 -0.01032022,10.626 0.15667978,10.292 l 0.375,-0.875 q 0.167,-0.333 0.56200002,-0.552 0.396,-0.219 0.771,-0.219 h 0.771 q 0.375,0 0.771,0.219 0.396,0.219 0.563,0.552 l 0.374,0.875 q 0.167,0.334 0.157,0.761 -0.011,0.427 -0.177,0.76 l -0.334,0.854 q -0.166,0.333 -0.573,0.552 -0.406,0.219 -0.781,0.219 z m 4.855,2.938 q -0.355,0 -0.761,-0.219 -0.406,-0.219 -0.594,-0.531 l -0.375,-0.896 q -0.166,-0.313 -0.166,-0.75 0,-0.438 0.146,-0.771 l 0.374,-0.875 q 0.167,-0.313 0.573,-0.531 0.407,-0.219 0.761,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.406,0.218 0.552,0.531 l 0.396,0.875 q 0.146,0.333 0.146,0.771 0,0.437 -0.146,0.75 l -0.396,0.896 q -0.146,0.312 -0.552,0.531 -0.406,0.219 -0.781,0.219 z M 11.48968,13.438 q -0.354,0 -0.77,-0.219 -0.417,-0.219 -0.563,-0.51 L 9.5936798,11.771 q -0.166,-0.291 -0.177,-0.718 -0.01,-0.427 0.157,-0.719 L 10.17768,9.376 q 0.146,-0.292 0.552,-0.511 0.406,-0.219 0.76,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.407,0.219 0.552,0.552 l 0.396,0.875 q 0.146,0.334 0.136,0.761 -0.011,0.427 -0.156,0.76 l -0.355,0.854 q -0.145,0.333 -0.562,0.552 -0.417,0.219 -0.792,0.219 z"
id="path2"
style="fill:#46c0e1;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b3anr3wkwja3y"
path="res://.godot/imported/category_decorator.svg-79d598d6456f32724156248e09d6eaf3.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/category_decorator.svg"
dest_files=["res://.godot/imported/category_decorator.svg-79d598d6456f32724156248e09d6eaf3.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16.375999"
width="14.126321"
version="1.1"
id="svg4"
sodipodi:docname="category_leaf.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="15.273506"
inkscape:cx="7.071068"
inkscape:cy="10.377447"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="m 11.48968,7.688 q -0.354,0 -0.76,-0.219 -0.406,-0.219 -0.552,-0.51 L 9.5736798,6 q -0.167,-0.291 -0.167,-0.729 0,-0.437 0.167,-0.729 L 10.07368,3.667 q 0.146,-0.291 0.552,-0.531 0.406,-0.24 0.76,-0.24 h 0.875 q 0.375,0 0.792,0.219 0.417,0.219 0.562,0.552 l 0.355,0.854 q 0.145,0.334 0.156,0.761 0.01,0.427 -0.136,0.76 l -0.396,0.875 q -0.145,0.333 -0.552,0.552 -0.406,0.219 -0.781,0.219 z m -4.8120002,2.917 q -0.354,0 -0.761,-0.219 -0.406,-0.219 -0.573,-0.531 l -0.374,-0.917 q -0.146,-0.312 -0.146,-0.75 0,-0.438 0.146,-0.75 l 0.374,-0.917 q 0.167,-0.312 0.573,-0.531 0.407,-0.219 0.761,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.406,0.219 0.552,0.531 l 0.396,0.917 q 0.146,0.312 0.146,0.75 0,0.438 -0.146,0.75 l -0.396,0.917 q -0.146,0.312 -0.552,0.531 -0.406,0.219 -0.781,0.219 z m 0,-5.813 q -0.354,0 -0.761,-0.219 -0.406,-0.218 -0.573,-0.531 l -0.374,-0.875 q -0.146,-0.333 -0.146,-0.771 0,-0.437 0.146,-0.75 l 0.374,-0.896 q 0.167,-0.312 0.573,-0.531 0.407,-0.219 0.761,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.406,0.219 0.552,0.531 l 0.396,0.896 q 0.146,0.313 0.146,0.75 0,0.438 -0.146,0.771 l -0.396,0.875 q -0.146,0.313 -0.552,0.531 -0.406,0.219 -0.781,0.219 z m -4.813,2.896 q -0.375,0 -0.771,-0.219 Q 0.69867978,7.25 0.53167978,6.917 l -0.375,-0.875 Q -0.01032022,5.709 6.7977528e-4,5.282 0.01067978,4.855 0.17767978,4.521 l 0.333,-0.854 q 0.167,-0.333 0.57300002,-0.552 0.406,-0.219 0.781,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.407,0.219 0.573,0.552 l 0.334,0.854 q 0.166,0.334 0.177,0.761 0.01,0.427 -0.157,0.76 l -0.374,0.875 q -0.167,0.333 -0.563,0.552 -0.396,0.219 -0.771,0.219 z m 0,5.75 q -0.375,0 -0.781,-0.219 Q 0.67767978,13 0.51067978,12.667 l -0.333,-0.854 Q 0.01067978,11.48 6.7977528e-4,11.053 -0.01032022,10.626 0.15667978,10.292 l 0.375,-0.875 q 0.167,-0.333 0.56200002,-0.552 0.396,-0.219 0.771,-0.219 h 0.771 q 0.375,0 0.771,0.219 0.396,0.219 0.563,0.552 l 0.374,0.875 q 0.167,0.334 0.157,0.761 -0.011,0.427 -0.177,0.76 l -0.334,0.854 q -0.166,0.333 -0.573,0.552 -0.406,0.219 -0.781,0.219 z m 4.855,2.938 q -0.355,0 -0.761,-0.219 -0.406,-0.219 -0.594,-0.531 l -0.375,-0.896 q -0.166,-0.313 -0.166,-0.75 0,-0.438 0.146,-0.771 l 0.374,-0.875 q 0.167,-0.313 0.573,-0.531 0.407,-0.219 0.761,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.406,0.218 0.552,0.531 l 0.396,0.875 q 0.146,0.333 0.146,0.771 0,0.437 -0.146,0.75 l -0.396,0.896 q -0.146,0.312 -0.552,0.531 -0.406,0.219 -0.781,0.219 z M 11.48968,13.438 q -0.354,0 -0.77,-0.219 -0.417,-0.219 -0.563,-0.51 L 9.5936798,11.771 q -0.166,-0.291 -0.177,-0.718 -0.01,-0.427 0.157,-0.719 L 10.17768,9.376 q 0.146,-0.292 0.552,-0.511 0.406,-0.219 0.76,-0.219 h 0.771 q 0.375,0 0.781,0.219 0.407,0.219 0.552,0.552 l 0.396,0.875 q 0.146,0.334 0.136,0.761 -0.011,0.427 -0.156,0.76 l -0.355,0.854 q -0.145,0.333 -0.562,0.552 -0.417,0.219 -0.792,0.219 z"
id="path2"
style="fill:#ffb649;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cgo0oieom3sqk"
path="res://.godot/imported/category_leaf.svg-c740ecab6cfae632574ca5e39e46fd2e.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/category_leaf.svg"
dest_files=["res://.godot/imported/category_leaf.svg-c740ecab6cfae632574ca5e39e46fd2e.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="18.084"
width="18.084"
version="1.1"
id="svg4180"
sodipodi:docname="condition.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs4184" />
<sodipodi:namedview
id="namedview4182"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="7.6367532"
inkscape:cx="-10.737547"
inkscape:cy="24.290428"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg4180" />
<path
d="m 8.938,14.063 q 0.542,0 0.927,-0.386 0.385,-0.385 0.385,-0.927 0,-0.541 -0.385,-0.916 -0.385,-0.375 -0.927,-0.375 -0.542,0 -0.927,0.375 -0.386,0.375 -0.386,0.916 0,0.542 0.386,0.927 0.385,0.386 0.927,0.386 z M 9.042,5.938 q 0.396,0 0.646,0.229 0.25,0.229 0.25,0.542 0,0.229 -0.177,0.562 Q 9.584,7.604 9.292,7.834 8.646,8.417 8.323,8.927 8,9.438 8,9.917 q 0,0.375 0.261,0.635 0.26,0.261 0.656,0.261 0.396,0 0.698,-0.24 0.302,-0.239 0.469,-0.698 0.104,-0.25 0.291,-0.51 0.188,-0.261 0.542,-0.615 0.604,-0.562 0.833,-1.073 0.23,-0.51 0.23,-1.135 0,-1.167 -0.761,-1.865 Q 10.459,3.98 9.188,3.98 8.25,3.98 7.552,4.365 6.854,4.75 6.292,5.48 6.084,5.75 6.198,6.125 6.313,6.5 6.584,6.709 6.938,6.98 7.365,6.886 7.792,6.792 8.104,6.417 8.313,6.209 8.563,6.073 8.813,5.938 9.042,5.938 Z m 0,12.146 q -1.875,0 -3.531,-0.709 Q 3.854,16.667 2.636,15.448 1.417,14.23 0.709,12.584 0,10.938 0,9.042 0,7.146 0.709,5.5 1.417,3.854 2.636,2.636 3.854,1.417 5.511,0.709 7.167,0 9.042,0 q 1.896,0 3.542,0.709 1.646,0.708 2.864,1.927 1.219,1.218 1.927,2.864 0.709,1.646 0.709,3.542 0,1.896 -0.709,3.542 -0.708,1.646 -1.927,2.864 -1.218,1.219 -2.864,1.927 -1.646,0.709 -3.542,0.709 z"
id="path4178"
style="fill:#ffb649;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dn4rf1wddvr3w"
path="res://.godot/imported/condition.svg-57892684b10a64086f68c09c388b17e5.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/condition.svg"
dest_files=["res://.godot/imported/condition.svg-57892684b10a64086f68c09c388b17e5.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="17.666"
width="17.666"
version="1.1"
id="svg6751"
sodipodi:docname="failer.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs6755" />
<sodipodi:namedview
id="namedview6753"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="30.547013"
inkscape:cx="12.308896"
inkscape:cy="5.3851419"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg6751" />
<path
d="m 8.854,9.666 q 0.5,0 0.896,-0.395 0.395,-0.396 0.395,-0.917 v -2.75 q 0,-0.5 -0.395,-0.886 Q 9.354,4.333 8.854,4.333 8.333,4.333 7.937,4.729 7.541,5.125 7.541,5.625 v 2.75 q 0,0.52 0.396,0.906 0.396,0.385 0.917,0.385 z m -0.021,3.959 q 0.583,0 1.01,-0.407 0.428,-0.406 0.428,-1.031 0,-0.583 -0.428,-1.021 -0.427,-0.437 -1.01,-0.437 -0.583,0 -1.01,0.437 -0.428,0.438 -0.428,1.021 0,0.625 0.428,1.031 0.427,0.407 1.01,0.407 z m 0,4.041 q -1.854,0 -3.458,-0.687 Q 3.771,16.291 2.573,15.093 1.375,13.895 0.687,12.291 0,10.687 0,8.833 0,6.958 0.687,5.364 1.375,3.771 2.573,2.573 3.771,1.375 5.375,0.687 6.979,0 8.833,0 q 1.875,0 3.469,0.687 1.593,0.688 2.791,1.886 1.198,1.198 1.886,2.791 0.687,1.594 0.687,3.469 0,1.854 -0.687,3.458 -0.688,1.604 -1.886,2.802 -1.198,1.198 -2.791,1.886 -1.594,0.687 -3.469,0.687 z"
id="path6749"
style="fill:#46c0e1;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d4ddhrw2xriwo"
path="res://.godot/imported/failer.svg-9a62b840e1eacc0437e7a67b14a302e4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/failer.svg"
dest_files=["res://.godot/imported/failer.svg-9a62b840e1eacc0437e7a67b14a302e4.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="13.907205"
width="18.437"
version="1.1"
id="svg68"
sodipodi:docname="inverter.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs72" />
<sodipodi:namedview
id="namedview70"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="29.521708"
inkscape:cx="6.3343218"
inkscape:cy="7.8078138"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg68" />
<path
d="m 12.937,8.48 q -0.458,-0.459 -0.458,-1.032 0,-0.572 0.458,-1.031 L 13.416,5.938 H 9.812 Q 9.229,5.938 8.791,5.5 8.354,5.063 8.354,4.48 8.354,3.876 8.791,3.448 9.229,3.021 9.812,3.021 h 3.604 l -0.5,-0.5 Q 12.458,2.063 12.468,1.49 12.479,0.917 12.937,0.48 13.396,0 13.979,0 14.562,0 15,0.459 l 2.979,2.979 q 0.229,0.229 0.344,0.5 0.114,0.271 0.114,0.521 0,0.271 -0.114,0.541 -0.115,0.271 -0.344,0.5 l -3,3 Q 14.52,8.959 13.958,8.948 13.396,8.938 12.937,8.48 Z M 3.416,13.438 0.437,10.459 Q 0.229,10.25 0.114,9.98 0,9.709 0,9.438 0,9.167 0.114,8.896 0.229,8.626 0.437,8.396 l 3.021,-3 Q 3.916,4.938 4.468,4.948 5.02,4.959 5.479,5.417 5.937,5.876 5.937,6.459 5.937,7.042 5.479,7.48 L 5,7.959 h 3.604 q 0.583,0 1.021,0.437 0.437,0.438 0.437,1.021 0,0.604 -0.437,1.031 -0.438,0.428 -1.021,0.428 H 5 l 0.5,0.52 q 0.458,0.459 0.448,1.021 -0.011,0.563 -0.469,1.021 -0.459,0.458 -1.021,0.469 -0.562,0.01 -1.042,-0.469 z"
id="path66"
style="fill:#46c0e1;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://biyjvaibehsld"
path="res://.godot/imported/inverter.svg-1f1b976d95de42c4ad99a92fa9a6c5d0.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/inverter.svg"
dest_files=["res://.godot/imported/inverter.svg-1f1b976d95de42c4ad99a92fa9a6c5d0.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="14.75"
width="14.916"
version="1.1"
id="svg3323"
sodipodi:docname="repeater.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs3327" />
<sodipodi:namedview
id="namedview3325"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="7.6367532"
inkscape:cx="-37.057633"
inkscape:cy="27.56407"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg3323" />
<path
d="M 7.375,14.75 Q 4.312,14.75 2.156,12.594 0,10.437 0,7.375 0,4.313 2.156,2.156 4.312,0 7.375,0 8.979,0 10.385,0.646 11.791,1.292 12.833,2.5 V 1.042 q 0,-0.417 0.313,-0.729 Q 13.458,0 13.875,0 q 0.416,0 0.729,0.313 0.312,0.312 0.312,0.729 v 4.583 q 0,0.542 -0.406,0.948 -0.406,0.406 -0.948,0.406 H 8.958 Q 8.541,6.979 8.229,6.677 7.916,6.375 7.916,5.958 7.916,5.542 8.229,5.229 8.541,4.917 8.958,4.917 h 2.354 Q 10.646,3.917 9.614,3.313 8.583,2.708 7.375,2.708 q -1.938,0 -3.302,1.365 -1.365,1.364 -1.365,3.302 0,1.938 1.365,3.302 1.364,1.365 3.302,1.365 1.25,0 2.291,-0.615 1.042,-0.614 1.688,-1.677 0.208,-0.313 0.562,-0.521 0.354,-0.208 0.688,-0.208 0.854,0 1.239,0.573 0.386,0.573 0.011,1.26 -0.979,1.771 -2.698,2.833 -1.719,1.063 -3.781,1.063 z"
id="path3321"
style="fill:#46c0e1;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b23nxd75yrbwm"
path="res://.godot/imported/limiter.svg-b4c7646605c46f53c5e403fe21d8f584.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/limiter.svg"
dest_files=["res://.godot/imported/limiter.svg-b4c7646605c46f53c5e403fe21d8f584.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="13.906356"
width="17.791"
version="1.1"
id="svg5037"
sodipodi:docname="selector.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs5041" />
<sodipodi:namedview
id="namedview5039"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="7.6367532"
inkscape:cx="61.282588"
inkscape:cy="33.849463"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg5037" />
<path
d="M 4.52,13.645 Q 4.25,13.374 4.25,13 4.25,12.624 4.5,12.354 L 5.208,11.666 Q 2.979,11.395 1.489,9.802 0,8.208 0,5.895 0,3.437 1.718,1.718 3.437,0 5.895,0 H 7.562 Q 7.916,0 8.187,0.27 8.458,0.541 8.458,0.895 8.458,1.27 8.187,1.541 7.916,1.812 7.562,1.812 H 5.895 Q 4.187,1.812 3,3 1.812,4.187 1.812,5.895 1.812,7.437 2.76,8.52 3.708,9.604 5.187,9.916 L 4.5,9.229 Q 4.229,8.937 4.239,8.583 4.25,8.229 4.52,7.958 4.77,7.687 5.145,7.687 q 0.375,0 0.646,0.271 l 2.167,2.187 q 0.125,0.125 0.208,0.302 0.084,0.177 0.084,0.365 0,0.167 -0.084,0.344 -0.083,0.177 -0.208,0.323 L 5.75,13.645 Q 5.479,13.916 5.124,13.906 4.77,13.895 4.52,13.645 Z M 12.041,5.958 Q 11.25,5.958 10.708,5.416 10.166,4.874 10.166,4.083 V 1.874 q 0,-0.77 0.542,-1.322 Q 11.25,0 12.041,0 h 3.875 q 0.771,0 1.323,0.552 0.552,0.552 0.552,1.322 v 2.209 q 0,0.791 -0.552,1.333 -0.552,0.542 -1.323,0.542 z m 0,-1.875 h 3.875 V 1.874 h -3.875 z m 0,9.541 q -0.791,0 -1.333,-0.552 Q 10.166,12.52 10.166,11.75 V 9.541 q 0,-0.791 0.542,-1.333 0.542,-0.542 1.333,-0.542 h 3.875 q 0.771,0 1.323,0.542 0.552,0.542 0.552,1.333 v 2.209 q 0,0.77 -0.552,1.322 -0.552,0.552 -1.323,0.552 z"
id="path5035"
style="fill:#40d29f;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://fxbfp8glftm"
path="res://.godot/imported/selector.svg-78bccfc448bd1676b5a29bfde4b08e5b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/selector.svg"
dest_files=["res://.godot/imported/selector.svg-78bccfc448bd1676b5a29bfde4b08e5b.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.3 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ca3k3maygmx7r"
path="res://.godot/imported/selector_random.svg-d52fea1352c24483ecd9dc8609cf00f3.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/selector_random.svg"
dest_files=["res://.godot/imported/selector_random.svg-d52fea1352c24483ecd9dc8609cf00f3.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="14.543364"
width="17.791"
version="1.1"
id="svg5037"
sodipodi:docname="selector_reactive.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs5041" /><sodipodi:namedview
id="namedview5039"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="15.273506"
inkscape:cx="18.59429"
inkscape:cy="12.014268"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg5037" /><path
style="fill:#40d29f;fill-opacity:1;color:#000000;stroke-width:1.7354;-inkscape-stroke:none;paint-order:markers stroke fill"
d="m 15.63424,18.518399 q -0.218955,0 -0.373746,-0.150649 -0.155154,-0.150651 -0.155154,-0.363746 v -1.161448 l -0.860595,0.49993 q -0.196481,0.103026 -0.400572,0.05152 -0.20373,-0.05152 -0.309582,-0.227915 l -0.181253,-0.308712 q -0.105496,-0.183813 -0.05655,-0.385972 0.04894,-0.202159 0.237805,-0.312236 l 0.868569,-0.492527 -0.883432,-0.485112 q -0.188867,-0.110433 -0.241793,-0.312236 -0.05292,-0.202161 0.05292,-0.385974 l 0.196116,-0.301299 q 0.113465,-0.183814 0.321183,-0.238852 0.207716,-0.05539 0.388971,0.05504 l 0.868208,0.492523 v -1.161448 q 0,-0.213096 0.155154,-0.363747 0.154791,-0.150648 0.373746,-0.150648 h 0.347283 q 0.218955,0 0.373747,0.150648 0.155152,0.150651 0.155152,0.363747 v 1.161448 l 0.868209,-0.499931 q 0.188867,-0.103026 0.388973,-0.04763 0.200104,0.05504 0.313568,0.224034 l 0.211343,0.323526 q 0.113466,0.183813 0.05655,0.38562 -0.05655,0.20216 -0.253031,0.312589 l -0.87582,0.477704 0.86857,0.492521 q 0.181255,0.09561 0.245418,0.304826 0.06416,0.209569 -0.0493,0.385974 l -0.196119,0.316117 q -0.113464,0.176404 -0.313569,0.235323 -0.200105,0.05857 -0.388972,-0.05151 l -0.875822,-0.50734 v 1.161449 q 0,0.213096 -0.155152,0.363745 -0.154792,0.15065 -0.373747,0.15065 z"
id="path5291"
transform="matrix(1.2719165,0,0,1.2110461,-6.1455464,-7.8832739)"
inkscape:label="asterisk" /><path
style="fill:#40d29f;fill-opacity:1"
d="M 12.041,5.958 Q 11.25,5.958 10.708,5.416 10.166,4.874 10.166,4.083 V 1.874 q 0,-0.77 0.542,-1.322 Q 11.25,0 12.041,0 h 3.875 q 0.771,0 1.323,0.552 0.552,0.552 0.552,1.322 v 2.209 q 0,0.791 -0.552,1.333 -0.552,0.542 -1.323,0.542 z m 0,-1.875 h 3.875 V 1.874 h -3.875 z"
id="path923"
inkscape:label="square" /><path
style="fill:#40d29f;fill-opacity:1"
d="M 4.52,13.645 Q 4.25,13.374 4.25,13 4.25,12.624 4.5,12.354 L 5.208,11.666 Q 2.979,11.395 1.489,9.802 0,8.208 0,5.895 0,3.437 1.718,1.718 3.437,0 5.895,0 H 7.562 Q 7.916,0 8.187,0.27 8.458,0.541 8.458,0.895 8.458,1.27 8.187,1.541 7.916,1.812 7.562,1.812 H 5.895 Q 4.187,1.812 3,3 1.812,4.187 1.812,5.895 1.812,7.437 2.76,8.52 3.708,9.604 5.187,9.916 L 4.5,9.229 Q 4.229,8.937 4.239,8.583 4.25,8.229 4.52,7.958 4.77,7.687 5.145,7.687 q 0.375,0 0.646,0.271 l 2.167,2.187 q 0.125,0.125 0.208,0.302 0.084,0.177 0.084,0.365 0,0.167 -0.084,0.344 -0.083,0.177 -0.208,0.323 L 5.75,13.645 Q 5.479,13.916 5.124,13.906 4.77,13.895 4.52,13.645 Z"
id="path5035"
inkscape:label="arrow" /></svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bwxolb55cox2v"
path="res://.godot/imported/selector_reactive.svg-dd3b8fb8cd2ffe331605aaad1e021cc0.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/selector_reactive.svg"
dest_files=["res://.godot/imported/selector_reactive.svg-dd3b8fb8cd2ffe331605aaad1e021cc0.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="13.187"
width="16.583"
version="1.1"
id="svg5894"
sodipodi:docname="sequence.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs5898" />
<sodipodi:namedview
id="namedview5896"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="43.2"
inkscape:cx="13.449074"
inkscape:cy="7.974537"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg5894" />
<path
d="M 1.709,8.313 Q 1,8.313 0.5,7.823 0,7.333 0,6.604 0,5.896 0.5,5.396 q 0.5,-0.5 1.209,-0.5 0.708,0 1.208,0.5 0.5,0.5 0.5,1.229 0,0.708 -0.5,1.198 -0.5,0.49 -1.208,0.49 z m 0,-4.896 Q 1,3.417 0.5,2.927 0,2.437 0,1.708 0,1 0.5,0.5 1,0 1.709,0 q 0.708,0 1.208,0.5 0.5,0.5 0.5,1.229 0,0.708 -0.5,1.198 -0.5,0.49 -1.208,0.49 z m 0,9.77 Q 1,13.187 0.5,12.698 0,12.208 0,11.479 q 0,-0.708 0.5,-1.208 0.5,-0.5 1.209,-0.5 0.708,0 1.208,0.5 0.5,0.5 0.5,1.229 0,0.708 -0.5,1.198 -0.5,0.489 -1.208,0.489 z m 4.604,-0.395 q -0.5,0 -0.896,-0.396 Q 5.021,12 5.021,11.479 q 0,-0.5 0.396,-0.896 0.396,-0.396 0.896,-0.396 h 8.979 q 0.5,0 0.896,0.396 0.395,0.396 0.395,0.896 0,0.521 -0.395,0.917 -0.396,0.396 -0.896,0.396 z m 0,-4.875 q -0.5,0 -0.896,-0.396 -0.396,-0.396 -0.396,-0.917 0,-0.5 0.396,-0.896 0.396,-0.395 0.896,-0.395 h 8.979 q 0.5,0 0.896,0.395 0.395,0.396 0.395,0.896 0,0.521 -0.395,0.917 -0.396,0.396 -0.896,0.396 z m 0,-4.896 q -0.5,0 -0.896,-0.396 -0.396,-0.396 -0.396,-0.917 0,-0.5 0.396,-0.895 0.396,-0.396 0.896,-0.396 h 8.979 q 0.5,0 0.896,0.396 0.395,0.395 0.395,0.895 0,0.521 -0.395,0.917 -0.396,0.396 -0.896,0.396 z"
id="path5892"
style="fill:#40d29f;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dxub8qg1o201c"
path="res://.godot/imported/sequence.svg-76e5600611900cc81e9ec286977b8c6a.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/sequence.svg"
dest_files=["res://.godot/imported/sequence.svg-76e5600611900cc81e9ec286977b8c6a.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="12.183594"
width="16.582031"
version="1.1"
id="svg5894"
sodipodi:docname="sequence_random.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs5898" />
<sodipodi:namedview
id="namedview5896"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="32"
inkscape:cx="-1.59375"
inkscape:cy="7.828125"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg5894" />
<path
id="path414"
style="color:#000000;fill:#40d29f;stroke-width:0.4;stroke-linecap:round;paint-order:fill markers stroke"
d="M 1.7089844,0 C 1.2363186,0 0.83333267,0.16666733 0.5,0.5 0.16666733,0.83333267 0,1.2350322 0,1.7070312 c 0,0.4859992 0.16666733,0.8940371 0.5,1.2207032 0.33333267,0.3266659 0.7363186,0.4902343 1.2089844,0.4902344 0.471999,0 0.8756516,-0.1635685 1.2089844,-0.4902344 0.3333326,-0.3266661 0.4999999,-0.7272196 0.5,-1.1992188 0,-0.485999 -0.1666674,-0.89518293 -0.5,-1.2285156 C 2.5846361,0.16666733 2.1809834,0 1.7089844,0 Z M 6.3125,0.41796875 c -0.3333327,0 -0.6305318,0.13053177 -0.8945312,0.39453125 -0.2639996,0.2633328 -0.3964844,0.5611987 -0.3964844,0.8945312 0,0.3473327 0.1324848,0.6539694 0.3964844,0.9179688 C 5.6819682,2.8889994 5.9791673,3.0214844 6.3125,3.0214844 h 8.980469 C 15.626301,3.0214844 15.9235,2.8889994 16.1875,2.625 16.450833,2.3610006 16.582031,2.0543639 16.582031,1.7070312 16.582031,1.3736987 16.450833,1.0758328 16.1875,0.8125 15.9235,0.54850052 15.626301,0.41796875 15.292969,0.41796875 Z M 9.0878906,4.3007812 c -0.2828275,0 -0.5270087,0.1055895 -0.7324218,0.3164063 -0.2050874,0.2104687 -0.3066407,0.4597345 -0.3066407,0.75 v 5.7480465 c 0,0.290266 0.101563,0.543098 0.3066407,0.753907 0.2054131,0.210442 0.4495943,0.314453 0.7324218,0.314453 h 5.6015624 c 0.282829,0 0.527005,-0.103984 0.732422,-0.314453 0.20504,-0.210809 0.310547,-0.463641 0.310547,-0.753907 V 5.3671875 c 0,-0.2902655 -0.105468,-0.5395313 -0.310547,-0.75 -0.192576,-0.1976408 -0.419166,-0.3040153 -0.679687,-0.3164063 -0.01736,-8.263e-4 -0.03505,0 -0.05274,0 z M 1.9257812,5.3535156 C 1.6720837,5.3535062 1.4450691,5.4533683 1.2441406,5.6542969 1.043212,5.8547182 0.94335938,6.0822399 0.94335938,6.3359375 c 0,0.2643529 0.0998526,0.4982903 0.30078122,0.6992187 0.2009285,0.2009285 0.4279431,0.3007813 0.6816406,0.3007813 H 6.8886719 V 5.7070312 c 0,-0.1182132 0.017785,-0.2363441 0.048828,-0.3515624 z m 11.7246098,0.041016 c 0.275278,0 0.533582,0.1086393 0.724609,0.3046876 0.191017,0.1960469 0.294922,0.4582008 0.294922,0.7363281 0,0.2781252 -0.10358,0.53844 -0.294922,0.734375 -0.191911,0.1969508 -0.45114,0.3027343 -0.724609,0.3027343 -0.273472,0 -0.532817,-0.1054585 -0.72461,-0.3027343 -0.192232,-0.1968435 -0.291015,-0.4579656 -0.291015,-0.734375 0,-0.2764085 0.09911,-0.5393695 0.291015,-0.7363281 0.190906,-0.1963654 0.449327,-0.3046876 0.72461,-0.3046876 z M 11.888672,7.203125 c 0.0068,0 0.0244,-0.00128 0.05273,0 0.256852,0.011652 0.493747,0.1218832 0.671875,0.3046875 0.191019,0.1960465 0.296876,0.4562463 0.296875,0.734375 10e-7,0.2781245 -0.105853,0.5402803 -0.296875,0.7363281 C 12.42137,9.1754773 12.162139,9.28125 11.888672,9.28125 c -0.273461,0 -0.532699,-0.1057738 -0.72461,-0.3027344 -0.191923,-0.1969574 -0.291015,-0.4599183 -0.291015,-0.7363281 0,-0.2764094 0.0991,-0.5374165 0.291015,-0.734375 0.191024,-0.1960448 0.449335,-0.3046875 0.72461,-0.3046875 z m -1.75,1.8066406 c 0.0068,0 0.0244,6.732e-4 0.05273,0.00195 0.256848,0.011652 0.49375,0.1238283 0.671875,0.3066406 0.189491,0.1948428 0.294922,0.4530828 0.294922,0.7304688 0,0.278128 -0.103894,0.538327 -0.294922,0.734375 -0.191019,0.196026 -0.449319,0.306641 -0.724609,0.306641 -0.2750313,0 -0.5324,-0.111387 -0.7226564,-0.306641 C 9.2249894,10.587155 9.1210938,10.326955 9.1210938,10.048828 9.1210937,9.7714422 9.2265406,9.5132022 9.4160156,9.3183594 9.6062729,9.1231217 9.8636398,9.0097656 10.138672,9.0097656 Z m -8.216797,0.1875 c -0.2536975,-9.4e-6 -0.478759,0.0979 -0.6796875,0.2988282 -0.2009286,0.2004212 -0.30273437,0.4279427 -0.30273438,0.6816402 0,0.264353 0.10180578,0.498291 0.30273438,0.699219 0.2009285,0.200929 0.42599,0.300781 0.6796875,0.300781 H 6.8867188 V 9.5488281 c 0,-0.1182133 0.017785,-0.2363441 0.048828,-0.3515625 z" />
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b6ub4t6q50iou"
path="res://.godot/imported/sequence_random.svg-58cee9098c622ef87db941279206422a.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/sequence_random.svg"
dest_files=["res://.godot/imported/sequence_random.svg-58cee9098c622ef87db941279206422a.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="14.874624"
width="16.583984"
version="1.1"
id="svg5894"
sodipodi:docname="sequence_reactive.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs5898"><linearGradient
id="linearGradient3589"
inkscape:swatch="solid"><stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop3587" /></linearGradient><inkscape:path-effect
effect="fill_between_many"
method="originald"
linkedpaths="#path5892,0,1|#path3531,0,1"
id="path-effect3720"
is_visible="true"
lpeversion="0"
join="true"
close="true"
autoreverse="true" /></defs><sodipodi:namedview
id="namedview5896"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="21.599999"
inkscape:cx="16.805556"
inkscape:cy="4.3287039"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg5894" /><path
d="M 0,0"
id="path3722"
class="UnoptimicedTransforms"
inkscape:original-d="M 0,0"
inkscape:path-effect="#path-effect3720"
transform="translate(-1.7285156,-3.375)" /><path
id="path5892"
style="fill:#40d29f;fill-opacity:1"
class="UnoptimicedTransforms"
d="m 3.4375,3.375 c -0.4726662,0 -0.8756514,0.166667 -1.2089844,0.5 -0.333333,0.333333 -0.5,0.7350317 -0.5,1.2070312 0,0.4859996 0.166667,0.8940368 0.5,1.2207032 0.333333,0.3266663 0.7363182,0.4902344 1.2089844,0.4902344 0.4719995,-10e-8 0.8756514,-0.1635681 1.2089844,-0.4902344 0.333333,-0.3266664 0.5,-0.7272192 0.5,-1.1992188 0,-0.4859995 -0.166667,-0.8951826 -0.5,-1.2285156 C 4.3131514,3.541667 3.9094995,3.375 3.4375,3.375 Z M 8.0429688,3.7929688 C 7.7096358,3.7929687 7.4104841,3.9235003 7.1464844,4.1875 6.8824846,4.4508331 6.75,4.7486983 6.75,5.0820312 6.75,5.4293642 6.8824846,5.7360003 7.1464844,6 7.4104841,6.2639997 7.7096358,6.3964844 8.0429688,6.3964844 H 17.021484 C 17.354817,6.3964844 17.653969,6.2639997 17.917969,6 18.181302,5.7360003 18.3125,5.4293642 18.3125,5.0820312 18.3125,4.7486983 18.181302,4.4508331 17.917969,4.1875 17.653969,3.9235003 17.354817,3.7929688 17.021484,3.7929688 Z M 3.4375,8.2714844 c -0.4726662,0 -0.8756514,0.166667 -1.2089844,0.5 -0.333333,0.333333 -0.5,0.7350317 -0.5,1.2070312 0,0.4859994 0.166667,0.8920834 0.5,1.2187504 0.333333,0.326666 0.7363182,0.490234 1.2089844,0.490234 0.4719995,0 0.8756514,-0.163568 1.2089844,-0.490234 0.333333,-0.326667 0.5,-0.725266 0.5,-1.197266 0,-0.4859995 -0.166667,-0.8951826 -0.5,-1.2285156 -0.333333,-0.333333 -0.7369849,-0.5 -1.2089844,-0.5 z M 8.0429688,8.6875 c -0.333333,0 -0.6324847,0.1311982 -0.8964844,0.3945312 C 6.8824846,9.346031 6.75,9.6451826 6.75,9.9785156 c 0,0.3473334 0.1324846,0.6539694 0.3964844,0.9179684 0.2639997,0.264 0.5631514,0.396485 0.8964844,0.396485 H 12.582031 C 12.667811,11.029759 12.810428,10.78417 13,10.615234 c 0.314158,-0.280616 0.764135,-0.439453 1.171875,-0.439453 h 0.46875 c 0.407741,0 0.855765,0.158985 1.169922,0.439453 0.189414,0.168797 0.331535,0.414214 0.416015,0.677735 h 0.794922 c 0.333333,0 0.632485,-0.132485 0.896485,-0.396485 0.263333,-0.263999 0.394531,-0.570635 0.394531,-0.9179684 0,-0.333333 -0.131198,-0.6324846 -0.394531,-0.8964844 C 17.653969,8.8186982 17.354817,8.6875 17.021484,8.6875 Z M 3.4375,13.146484 c -0.4726662,0 -0.8756514,0.166667 -1.2089844,0.5 -0.333333,0.333333 -0.5,0.735032 -0.5,1.207032 0,0.485999 0.166667,0.892083 0.5,1.21875 0.333333,0.325999 0.7363182,0.490234 1.2089844,0.490234 0.4719995,0 0.8756514,-0.164235 1.2089844,-0.490234 0.333333,-0.326667 0.5,-0.725266 0.5,-1.197266 0,-0.486 -0.166667,-0.895183 -0.5,-1.228516 -0.333333,-0.333333 -0.7369849,-0.5 -1.2089844,-0.5 z M 8.0429688,13.5625 c -0.333333,0 -0.6324847,0.130532 -0.8964844,0.394531 C 6.8824846,14.221031 6.75,14.520183 6.75,14.853516 c 0,0.347333 0.1324846,0.653969 0.3964844,0.917968 0.2639997,0.264 0.5631514,0.396485 0.8964844,0.396485 H 10.03125 C 9.985538,15.92966 9.986027,15.678358 10.04492,15.455078 10.12589,15.148084 10.389523,14.941651 10.626951,14.732422 10.380403,14.52554 10.114609,14.310259 10.025389,13.998047 9.986266,13.860916 9.972079,13.712745 9.974607,13.5625 Z m 10.2500002,1.083984 c -0.03323,0.02743 -0.06681,0.05476 -0.09961,0.08203 0.03855,0.03356 0.07792,0.06815 0.117188,0.101562 -0.0012,-0.06277 -0.007,-0.123307 -0.01758,-0.183594 z"
transform="translate(-1.7285156,-3.375)" /><path
style="color:#000000;fill:#40d29f;stroke-width:2;paint-order:stroke markers fill;fill-opacity:1"
d="m 12.442996,14.874619 q -0.295876,0 -0.505045,-0.186837 -0.209662,-0.186838 -0.209662,-0.451125 v -1.440446 l -1.16293,0.620023 q -0.265504,0.127774 -0.541296,0.06389 -0.2753016,-0.06389 -0.4183406,-0.282664 l -0.244932,-0.382867 q -0.142553,-0.227969 -0.07642,-0.47869 0.06613,-0.250722 0.321348,-0.387241 L 10.779424,11.33783 9.5856344,10.736185 q -0.255217,-0.136961 -0.326737,-0.38724 -0.07153,-0.250722 0.07153,-0.478691 l 0.265013,-0.373676 q 0.153328,-0.227967 0.4340186,-0.296228 0.28069,-0.0687 0.52562,0.06827 l 1.173216,0.610833 V 8.439007 q 0,-0.264287 0.209662,-0.451125 0.209169,-0.186837 0.505047,-0.186837 h 0.469287 q 0.295874,0 0.505047,0.186837 0.209659,0.186838 0.209659,0.451125 v 1.440446 l 1.173216,-0.620024 q 0.255218,-0.127774 0.525622,-0.05908 0.270404,0.06827 0.423731,0.277851 l 0.285589,0.401242 q 0.153327,0.227969 0.07642,0.478254 -0.07642,0.25072 -0.341923,0.387677 l -1.183505,0.592455 1.173707,0.610833 q 0.24493,0.118573 0.331635,0.378052 0.08671,0.259911 -0.06662,0.478692 l -0.265012,0.392052 q -0.153326,0.21878 -0.423729,0.291853 -0.270405,0.07263 -0.525621,-0.06389 l -1.183504,-0.629204 v 1.440446 q 0,0.264286 -0.20966,0.451125 -0.209172,0.186837 -0.505048,0.186837 z"
id="path6912" /></svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://yqyy2jgimlgp"
path="res://.godot/imported/sequence_reactive.svg-7d384ca290f7934adb9e17d9e7116b6c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/sequence_reactive.svg"
dest_files=["res://.godot/imported/sequence_reactive.svg-7d384ca290f7934adb9e17d9e7116b6c.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="17.666"
width="17.666"
version="1.1"
id="svg7608"
sodipodi:docname="succedeer.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs7612" />
<sodipodi:namedview
id="namedview7610"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="10.8"
inkscape:cx="-0.092592592"
inkscape:cy="15.509259"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg7608" />
<path
d="M 7.666,9.479 6.479,8.271 Q 6.104,7.916 5.604,7.916 q -0.5,0 -0.875,0.375 -0.375,0.375 -0.375,0.865 0,0.489 0.375,0.865 l 2,2.02 q 0.416,0.396 0.927,0.396 0.51,0 0.906,-0.396 L 12.833,7.75 Q 13.166,7.416 13.052,6.958 12.937,6.5 12.833,6.041 12.458,5.666 11.968,5.666 q -0.489,0 -0.864,0.375 z m 1.167,8.187 q -1.854,0 -3.458,-0.687 Q 3.771,16.291 2.573,15.093 1.375,13.895 0.687,12.291 0,10.687 0,8.833 0,6.958 0.687,5.364 1.375,3.771 2.573,2.573 3.771,1.375 5.375,0.687 6.979,0 8.833,0 q 1.875,0 3.469,0.687 1.593,0.688 2.791,1.886 1.198,1.198 1.886,2.791 0.687,1.594 0.687,3.469 0,1.854 -0.687,3.458 -0.688,1.604 -1.886,2.802 -1.198,1.198 -2.791,1.886 -1.594,0.687 -3.469,0.687 z"
id="path7606"
style="fill:#46c0e1;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bfwgqq5jr46jm"
path="res://.godot/imported/succeeder.svg-e5cf6f6e04b9b862b82fd2cb479272aa.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/succeeder.svg"
dest_files=["res://.godot/imported/succeeder.svg-e5cf6f6e04b9b862b82fd2cb479272aa.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="15.063"
width="16.875999"
version="1.1"
id="svg12"
sodipodi:docname="tree.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs16" />
<sodipodi:namedview
id="namedview14"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="10.8"
inkscape:cx="-26.481481"
inkscape:cy="9.3981481"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg12" />
<path
d="m 12.709,15.063 q -0.771,0 -1.313,-0.521 -0.541,-0.521 -0.562,-1.271 V 12.604 H 9.376 q -0.792,0 -1.344,-0.552 Q 7.48,11.5 7.48,10.73 V 4.313 H 6.021 V 4.938 Q 6.021,5.709 5.469,6.23 4.917,6.75 4.146,6.75 H 1.876 Q 1.105,6.75 0.553,6.198 0,5.646 0,4.875 v -3 Q 0,1.104 0.553,0.552 1.105,0 1.876,0 h 2.27 q 0.771,0 1.323,0.521 0.552,0.521 0.552,1.271 v 0.646 h 4.813 V 1.792 q 0,-0.75 0.552,-1.271 Q 11.938,0 12.709,0 H 15 q 0.771,0 1.323,0.552 0.553,0.552 0.553,1.323 v 3 q 0,0.771 -0.553,1.323 Q 15.771,6.75 15,6.75 H 12.709 Q 11.938,6.75 11.386,6.23 10.834,5.709 10.834,4.938 V 4.313 H 9.376 v 6.417 h 1.458 v -0.626 q 0,-0.77 0.552,-1.291 0.552,-0.521 1.323,-0.521 H 15 q 0.771,0 1.323,0.552 0.553,0.552 0.553,1.323 v 3.021 q 0,0.771 -0.553,1.323 -0.552,0.552 -1.323,0.552 z"
id="path10"
style="fill:#c689ff;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dl3sb2gdybwgs"
path="res://.godot/imported/tree.svg-c0b20ed88b2fe300c0296f7236049076.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/beehave/icons/tree.svg"
dest_files=["res://.godot/imported/tree.svg-c0b20ed88b2fe300c0296f7236049076.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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

View file

@ -0,0 +1,14 @@
## A node in the behaviour tree. Every node must return `SUCCESS`, `FAILURE` or
## `RUNNING` when ticked.
@tool
class_name BeehaveNode extends BeehaveTree
## Executes this node and returns a status code.
## This method must be overwritten.
func tick(actor: Node, blackboard: Blackboard) -> int:
return SUCCESS
## Called when this node needs to be interrupted before it can return FAILURE or SUCCESS.
func interrupt(actor: Node, blackboard: Blackboard) -> void:
pass

View file

@ -0,0 +1,93 @@
## Controls the flow of execution of the entire behaviour tree.
@tool
class_name BeehaveRoot extends BeehaveTree
@icon("../icons/tree.svg")
@export var enabled: bool = true:
set(value):
enabled = value
set_physics_process(enabled)
get:
return enabled
@export_node_path var actor_node_path : NodePath
var actor : Node
@onready var blackboard: Blackboard = Blackboard.new()
func _ready() -> void:
if Engine.is_editor_hint():
return
if self.get_child_count() != 1:
push_error("Beehave error: Root %s should have one child (NodePath: %s)" % [self.name, self.get_path()])
disable()
return
actor = get_parent()
if actor_node_path:
actor = get_node(actor_node_path)
set_physics_process(enabled)
func _physics_process(delta: float) -> void:
if Engine.is_editor_hint():
return
blackboard.set_value("delta", delta)
var status = self.get_child(0).tick(actor, blackboard)
# Updates the current running action.
var running_action = get_running_action() if status == RUNNING else null
blackboard.set_value("running_action", running_action)
func _get_configuration_warnings() -> PackedStringArray:
var warnings: PackedStringArray = super._get_configuration_warnings()
if get_child_count() != 1:
warnings.append("BeehaveRoot should have exactly one child node.")
return warnings
func get_running_action() -> ActionLeaf:
var node = get_child(0)
while node != null:
if node is Composite:
node = (node as Composite).running_child
elif node is ActionLeaf:
return node
push_error("Beehave error: Could not find running action in tree root '%s'." % name)
return null
func get_last_condition() -> Variant:
if blackboard.has_value("last_condition"):
return blackboard.get_value("last_condition")
return null
func get_last_condition_status() -> String:
if blackboard.has_value("last_condition_status"):
var status = blackboard.get_value("last_condition_status")
if status == SUCCESS:
return "SUCCESS"
elif status == FAILURE:
return "FAILURE"
else:
return "RUNNING"
return ""
func enable() -> void:
enabled = true
func disable() -> void:
enabled = false

View file

@ -0,0 +1,20 @@
## Base class for all parts of the behaviour tree.
@tool
class_name BeehaveTree extends Node
@icon("../icons/category_bt.svg")
enum {
SUCCESS,
FAILURE,
RUNNING
}
func _get_configuration_warnings() -> PackedStringArray:
var warnings: PackedStringArray = []
if get_children().any(func(x): return not (x is BeehaveNode)):
warnings.append("All children of this node should inherit from BeehaveNode class.")
return warnings

View file

@ -0,0 +1,30 @@
## A Composite node controls the flow of execution of its children in a specific manner.
@tool
class_name Composite extends BeehaveNode
@icon("../../icons/category_composite.svg")
var running_child: BeehaveNode = null
func _ready():
if Engine.is_editor_hint():
return
if self.get_child_count() < 1:
push_error("BehaviorTree Error: Composite %s should have at least one child (NodePath: %s)" % [self.name, self.get_path()])
func _get_configuration_warnings() -> PackedStringArray:
var warnings: PackedStringArray = super._get_configuration_warnings()
if get_children().filter(func(x): return x is BeehaveNode).size() < 2:
warnings.append("Any composite node should have at least two children. Otherwise it is not useful.")
return warnings
func interrupt(actor: Node, blackboard: Blackboard) -> void:
if running_child != null:
running_child.interrupt(actor, blackboard)
running_child = null

View file

@ -0,0 +1,25 @@
## Selector nodes will attempt to execute each of its children until one of
## them return `SUCCESS`. If all children return `FAILURE`, this node will also
## return `FAILURE`. This node will attempt to process all its children every
## single tick, even if one of them is currently `RUNNING` already.
@tool
class_name SelectorComposite extends Composite
@icon("../../icons/selector.svg")
func tick(actor: Node, blackboard: Blackboard) -> int:
for c in get_children():
var response = c.tick(actor, blackboard)
if c is ConditionLeaf:
blackboard.set_value("last_condition", c)
blackboard.set_value("last_condition_status", response)
if response != FAILURE:
if response == SUCCESS:
# Interrupt any child that was RUNNING before.
interrupt(actor, blackboard)
else: # RUNNING
running_child = c
return response
return FAILURE

View file

@ -0,0 +1,54 @@
## This node will attempt to execute all of its children just like a
## [code]SelectorStar[/code] would, with the exception that the children
## will be executed in a random order.
@tool
class_name SelectorRandomComposite extends Composite
@icon("../../icons/selector_random.svg")
## A shuffled list of the children that will be executed in reverse order.
var _children_bag: Array[Node] = []
var c: Node
func tick(actor: Node, blackboard: Blackboard) -> int:
if _children_bag.is_empty():
_reset()
# We need to traverse the array in reverse since we will be manipulating it.
for i in _get_reversed_indexes():
c = _children_bag[i]
var response = c.tick(actor, blackboard)
if c is ConditionLeaf:
blackboard.set_value("last_condition", c)
blackboard.set_value("last_condition_status", response)
if response == RUNNING:
running_child = c
else:
_children_bag.erase(c)
if response != FAILURE:
if response == SUCCESS:
_reset()
return response
return FAILURE
func interrupt(actor: Node, blackboard: Blackboard) -> void:
_reset()
super(actor, blackboard)
func _get_reversed_indexes() -> Array[int]:
var reversed = range(_children_bag.size())
reversed.reverse()
return reversed
## Generates a new shuffled list of the children.
func _reset() -> void:
_children_bag = get_children().duplicate()
_children_bag.shuffle()

View file

@ -0,0 +1,32 @@
## Selector Star nodes will attempt to execute each of its children until one of
## them return `SUCCESS`. If all children return `FAILURE`, this node will also
## return `FAILURE`. This node will skip all previous child nodes that were
## executed prior, in case one of the children is currently in `RUNNING` state.
@tool
class_name SelectorStarComposite extends Composite
@icon("../../icons/selector_reactive.svg")
var last_execution_index = 0
func tick(actor: Node, blackboard: Blackboard) -> int:
for c in get_children():
if c.get_index() < last_execution_index:
continue
var response = c.tick(actor, blackboard)
if c is ConditionLeaf:
blackboard.set_value("last_condition", c)
blackboard.set_value("last_condition_status", response)
if response != FAILURE:
if response == SUCCESS:
last_execution_index = 0
else: # RUNNING
running_child = c
return response
else:
last_execution_index += 1
last_execution_index = 0
return FAILURE

View file

@ -0,0 +1,26 @@
## Sequence nodes will attempt to execute all of its children and report
## `SUCCESS` in case all of the children report a `SUCCESS` status code.
## If at least one child reports a `FAILURE` status code, this node will also
## return `FAILURE`. This node will attempt to process all its children every
## single tick, even if one of them is currently `RUNNING` already.
@tool
class_name SequenceComposite extends Composite
@icon("../../icons/sequence.svg")
func tick(actor: Node, blackboard: Blackboard) -> int:
for c in get_children():
var response = c.tick(actor, blackboard)
if c is ConditionLeaf:
blackboard.set_value("last_condition", c)
blackboard.set_value("last_condition_status", response)
if response != SUCCESS:
if response == FAILURE:
# Interrupt any child that was RUNNING before.
interrupt(actor, blackboard)
else: # RUNNING
running_child = c
return response
return SUCCESS

View file

@ -0,0 +1,60 @@
## This node will attempt to execute all of its children just like a
## [code]SequenceStar[/code] would, with the exception that the children
## will be executed in a random order.
@tool
class_name SequenceRandomComposite extends Composite
@icon("../../icons/sequence_random.svg")
## Whether the sequence should start where it left off after a previous failure.
@export var resume_on_failure: bool = false
## Whether the sequence should start where it left off after a previous interruption.
@export var resume_on_interrupt: bool = false
## A shuffled list of the children that will be executed in reverse order.
var _children_bag: Array[Node] = []
var c: Node
func tick(actor: Node, blackboard: Blackboard) -> int:
if _children_bag.is_empty():
_reset()
# We need to traverse the array in reverse since we will be manipulating it.
for i in _get_reversed_indexes():
c = _children_bag[i]
var response = c.tick(actor, blackboard)
if c is ConditionLeaf:
blackboard.set_value("last_condition", c)
blackboard.set_value("last_condition_status", response)
if response == RUNNING:
running_child = c
else:
_children_bag.erase(c)
if response != SUCCESS:
if not resume_on_failure and response == FAILURE:
_reset()
return response
return SUCCESS
func interrupt(actor: Node, blackboard: Blackboard) -> void:
if not resume_on_interrupt:
_reset()
super(actor, blackboard)
func _get_reversed_indexes() -> Array[int]:
var reversed = range(_children_bag.size())
reversed.reverse()
return reversed
## Generates a new shuffled list of the children.
func _reset() -> void:
_children_bag = get_children().duplicate()
_children_bag.shuffle()

View file

@ -0,0 +1,37 @@
## Sequence Star nodes will attempt to execute all of its children and report
## `SUCCESS` in case all of the children report a `SUCCESS` status code.
## If at least one child reports a `FAILURE` status code, this node will also
## return `FAILURE`. This node will skip all previous child nodes that succeeded
## prior, in case one of the children is currently in `RUNNING` state
@tool
class_name SequenceStarComposite extends Composite
@icon("../../icons/sequence_reactive.svg")
var successful_index = 0
func tick(actor: Node, blackboard: Blackboard) -> int:
for c in get_children():
if c.get_index() < successful_index:
continue
var response = c.tick(actor, blackboard)
if c is ConditionLeaf:
blackboard.set_value("last_condition", c)
blackboard.set_value("last_condition_status", response)
if response != SUCCESS:
if response == FAILURE:
successful_index = 0
else: # RUNNING
running_child = c
return response
else:
successful_index += 1
if successful_index == get_child_count():
successful_index = 0
return SUCCESS
else:
successful_index = 0
return FAILURE

View file

@ -0,0 +1,22 @@
## Decorator nodes are used to transform the result received by its child.
## Must only have one child.
@tool
class_name Decorator extends BeehaveNode
@icon("../../icons/category_decorator.svg")
func _ready():
if Engine.is_editor_hint():
return
if self.get_child_count() != 1:
push_error("Beehave Error: Decorator %s should have only one child (NodePath: %s)" % [self.name, self.get_path()])
func _get_configuration_warnings() -> PackedStringArray:
var warnings: PackedStringArray = super._get_configuration_warnings()
if get_child_count() != 1:
warnings.append("Decorator should have exactly one child node.")
return warnings

View file

@ -0,0 +1,12 @@
## A Failer node will always return a `FAILURE` status code.
@tool
class_name AlwaysFailDecorator extends Decorator
@icon("../../icons/failer.svg")
func tick(actor: Node, blackboard: Blackboard) -> int:
for c in get_children():
var response = c.tick(actor, blackboard)
if response == RUNNING:
return RUNNING
return FAILURE

View file

@ -0,0 +1,21 @@
## An inverter will return `FAILURE` in case it's child returns a `SUCCESS` status
## code or `SUCCESS` in case its child returns a `FAILURE` status code.
@tool
class_name InverterDecorator extends Decorator
@icon("../../icons/inverter.svg")
func tick(actor: Node, blackboard: Blackboard) -> int:
for c in get_children():
var response = c.tick(actor, blackboard)
if response == SUCCESS:
return FAILURE
if response == FAILURE:
return SUCCESS
if c is Leaf and response == RUNNING:
blackboard.set_value("running_action", c)
return RUNNING
# Decorators must have a child. This should be unreachable code.
return FAILURE

View file

@ -0,0 +1,21 @@
## The limiter will execute its child `x` amount of times. When the number of
## maximum ticks is reached, it will return a `FAILURE` status code.
@tool
class_name LimiterDecorator extends Decorator
@icon("../../icons/limiter.svg")
@onready var cache_key = 'limiter_%s' % self.get_instance_id()
@export var max_count : float = 0
func tick(actor: Node, blackboard: Blackboard) -> int:
var current_count = blackboard.get_value(cache_key)
if current_count == null:
current_count = 0
if current_count <= max_count:
blackboard.set_value(cache_key, current_count + 1)
return self.get_child(0).tick(actor, blackboard)
else:
return FAILURE

View file

@ -0,0 +1,12 @@
## A succeeder node will always return a `SUCCESS` status code.
@tool
class_name AlwaysSucceedDecorator extends Decorator
@icon("../../icons/succeeder.svg")
func tick(actor: Node, blackboard: Blackboard) -> int:
for c in get_children():
var response = c.tick(actor, blackboard)
if response == RUNNING:
return RUNNING
return SUCCESS

View file

@ -0,0 +1,7 @@
## Actions are leaf nodes that define a task to be performed by an actor.
## Their execution can be long running, potentially being called across multiple
## frame executions. In this case, the node should return `RUNNING` until the
## action is completed.
@tool
class_name ActionLeaf extends Leaf
@icon("../../icons/action.svg")

View file

@ -0,0 +1,5 @@
## Conditions are leaf nodes that either return SUCCESS or FAILURE depending on
## a single simple condition. They should never return `RUNNING`.
@tool
class_name ConditionLeaf extends Leaf
@icon("../../icons/condition.svg")

View file

@ -0,0 +1,15 @@
## Base class for all leaf nodes of the tree.
@tool
class_name Leaf extends BeehaveNode
@icon("../../icons/category_leaf.svg")
func _get_configuration_warnings() -> PackedStringArray:
var warnings: PackedStringArray = []
var children: Array[Node] = get_children()
if children.any(func(x): return x is BeehaveNode):
warnings.append("Leaf nodes should not have any child nodes. They won't be ticked.")
return warnings

View file

@ -0,0 +1,7 @@
[plugin]
name="Beehave"
description="🐝 Behaviour Tree addon for Godot Engine"
author="bitbrain"
version="2.5.0"
script="plugin.gd"

View file

@ -0,0 +1,5 @@
@tool
extends EditorPlugin
func _init():
print("Beehave initialized!")

View file

@ -0,0 +1,511 @@
@tool
extends DialogicEditor
## Editor for editing character resources.
signal character_loaded(resource_path:String)
signal portrait_selected()
# Enums
enum PreviewModes {Full, Real}
# Current state
var current_preview_mode = PreviewModes.Full
var loading = false
var current_previewed_scene = null
# References
var selected_item: TreeItem
##############################################################################
## RESOURCE LOGIC
##############################################################################
# Method is called once editors manager is ready to accept registers.
func _register() -> void:
# Makes the editor open this when a .dch file is selected.
# Then _open_resource() is called.
editors_manager.register_resource_editor("dch", self)
# Add an "add character" button
var add_character_button = editors_manager.add_icon_button(
load("res://addons/dialogic/Editor/Images/Toolbar/add-character.svg"),
'Add Character',
self)
add_character_button.pressed.connect(
editors_manager.show_add_resource_dialog.bind(
new_character,
'*.dch; DialogicCharacter',
'Create new character',
'character',
))
# Called when a character is opened somehow
func _open_resource(resource:Resource) -> void:
# update resource
current_resource = (resource as DialogicCharacter)
# make sure changes in the ui won't trigger saving
loading = true
## Load other main tabs
for main_edit in %MainEditTabs.get_children():
main_edit._load_character(current_resource)
%DefaultPortraitPicker.set_value(resource.default_portrait)
%MainScale.value = 100*resource.scale
%MainOffsetX.value = resource.offset.x
%MainOffsetY.value = resource.offset.y
%MainMirror.button_pressed = resource.mirror
# Portrait section
%PortraitSearch.text = ""
load_portrait_tree()
loading = false
character_loaded.emit(resource.resource_path)
func _save_resource() -> void:
if ! visible or not current_resource:
return
# Portrait list
current_resource.portraits = get_updated_portrait_dict()
# Portrait settings
if %DefaultPortraitPicker.current_value in current_resource.portraits.keys():
current_resource.default_portrait = %DefaultPortraitPicker.current_value
elif !current_resource.portraits.is_empty():
current_resource.default_portrait = current_resource.portraits.keys()[0]
else:
current_resource.default_portrait = ""
current_resource.scale = %MainScale.value/100.0
current_resource.offset = Vector2(%MainOffsetX.value, %MainOffsetY.value)
current_resource.mirror = %MainMirror.button_pressed
# Main tabs
for main_edit in %MainEditTabs.get_children():
current_resource = main_edit._save_changes(current_resource)
ResourceSaver.save(current_resource, current_resource.resource_path)
current_resource_state = ResourceStates.Saved
editors_manager.resource_helper.rebuild_character_directory()
# Saves a new empty character to the given path
func new_character(path: String) -> void:
var resource := DialogicCharacter.new()
resource.resource_path = path
resource.display_name = path.get_file().trim_suffix("."+path.get_extension())
resource.color = Color(1,1,1,1)
resource.default_portrait = ""
resource.custom_info = {}
ResourceSaver.save(resource, path)
editors_manager.edit_resource(resource)
##############################################################################
## INTERFACE
##############################################################################
func _ready() -> void:
setup_portrait_list_tab()
setup_portrait_settings_tab()
%PreviewMode.item_selected.connect(_on_PreviewMode_item_selected)
%PreviewMode.select(DialogicUtil.get_project_setting('dialogic/editor/character_preview_mode', 0))
_on_PreviewMode_item_selected(%PreviewMode.selected)
## General Styling
var panel_style = DCSS.inline({
'border-radius': 5,
'border': 1,
'border_color':get_theme_color("dark_color_3", "Editor"),
'background': get_theme_color("base_color", "Editor"),
'padding': [10, 10],
})
var tab_panel :StyleBoxFlat = get_theme_stylebox('tab_selected', 'TabContainer').duplicate()
tab_panel.bg_color = get_theme_color("base_color", "Editor")
%MainEditTabs.add_theme_stylebox_override('panel', panel_style)
%MainEditTabs.add_theme_stylebox_override('tab_selected', tab_panel)
%MainEditTabs.add_theme_constant_override('side_margin', 5)
%PortraitListSection.add_theme_stylebox_override('panel', panel_style)
%PortraitListSection.add_theme_stylebox_override('tab_selected', tab_panel)
%PortraitListSection.add_theme_constant_override('side_margin', 5)
%PortraitPreviewSection.add_theme_stylebox_override('panel', panel_style)
%PortraitSettingsSection.add_theme_stylebox_override('panel', panel_style)
%PortraitSettingsSection.add_theme_stylebox_override('tab_selected', tab_panel)
%PortraitSettingsSection.add_theme_constant_override('side_margin', 5)
%RealPreviewPivot.texture = get_theme_icon("EditorPivot", "EditorIcons")
# Add general tab
add_main_tab("res://addons/dialogic/Editor/CharacterEditor/character_editor_tab_general.tscn")
# Load main tabs from subsystems/events
for indexer in DialogicUtil.get_indexers():
for main_tab in indexer._get_character_editor_tabs():
add_main_tab(main_tab)
for child in %PortraitSettingsSection.get_children():
if !child is DialogicCharacterEditorPortraitSettingsTab:
printerr("[Dialogic Editor] Portrait settings tabs should extend the right class!")
else:
child.character_editor = self
child.changed.connect(something_changed)
child.update_preview.connect(update_preview)
func add_main_tab(scene_path:String) -> void:
var edit: DialogicCharacterEditorMainTab = load(scene_path).instantiate()
edit.changed.connect(something_changed)
edit.character_editor = self
%MainEditTabs.add_child(edit)
func something_changed(fake_argument = "", fake_arg2 = null) -> void:
if not loading:
current_resource_state = ResourceStates.Unsaved
editors_manager.save_current_resource() #TODO, should this happen?
##############################################################################
## PORTRAIT SECTION
##############################################################################
func setup_portrait_list_tab() -> void:
%PortraitTree.editor = self
## Portrait section styling/connections
%AddPortraitButton.icon = get_theme_icon("Add", "EditorIcons")
%AddPortraitButton.pressed.connect(add_portrait)
%AddPortraitGroupButton.icon = get_theme_icon("Groups", "EditorIcons")
%AddPortraitGroupButton.pressed.connect(add_portrait_group)
%ImportPortraitsButton.icon = get_theme_icon("Folder", "EditorIcons")
%ImportPortraitsButton.pressed.connect(open_portrait_folder_select)
%PortraitSearch.right_icon = get_theme_icon("Search", "EditorIcons")
%PortraitSearch.text_changed.connect(filter_portrait_list)
%PortraitTree.item_selected.connect(load_selected_portrait)
%PortraitTree.item_edited.connect(_on_item_edited)
func open_portrait_folder_select() -> void:
find_parent("EditorView").godot_file_dialog(
import_portraits_from_folder, "*",
EditorFileDialog.FILE_MODE_OPEN_DIR)
func import_portraits_from_folder(path:String) -> void:
var parent: TreeItem = %PortraitTree.get_root()
if %PortraitTree.get_selected() and %PortraitTree.get_selected().get_metadata(0).has('group'):
parent = %PortraitTree.get_selected()
var dir := DirAccess.open(path)
dir.list_dir_begin()
var file_name :String = dir.get_next()
while file_name != "":
if not dir.current_is_dir():
var file_lower = file_name.to_lower()
if '.svg' in file_lower or '.png' in file_lower:
if not '.import' in file_lower:
var final_name: String= path+ "/" + file_name
%PortraitTree.add_portrait_item(file_name.trim_suffix('.'+file_name.get_extension()),
{'scene':"",'image':final_name, 'scale':1, 'offset':Vector2(), 'mirror':false}, parent)
file_name = dir.get_next()
something_changed()
func add_portrait(portrait_name:String='New portrait', portrait_data:Dictionary={'scene':"", 'image':'', 'scale':1, 'offset':Vector2(), 'mirror':false}) -> void:
var parent: TreeItem = %PortraitTree.get_root()
if %PortraitTree.get_selected():
if %PortraitTree.get_selected().get_metadata(0).has('group'):
parent = %PortraitTree.get_selected()
else:
parent = %PortraitTree.get_selected().get_parent()
%PortraitTree.add_portrait_item(portrait_name, portrait_data, parent).select(0)
something_changed()
func add_portrait_group() -> void:
var parent_item :TreeItem = %PortraitTree.get_root()
if %PortraitTree.get_selected() and %PortraitTree.get_selected().get_metadata(0).has('group'):
parent_item = %PortraitTree.get_selected()
%PortraitTree.add_portrait_group("Group", parent_item)
func load_portrait_tree() -> void:
%PortraitTree.clear_tree()
var root:TreeItem = %PortraitTree.create_item()
for portrait in current_resource.portraits.keys():
var portrait_label = portrait
var parent = %PortraitTree.get_root()
if '/' in portrait:
parent = %PortraitTree.create_necessary_group_items(portrait)
portrait_label = portrait.split('/')[-1]
%PortraitTree.add_portrait_item(portrait_label, current_resource.portraits[portrait], parent)
update_default_portrait_star(current_resource.default_portrait)
if root.get_child_count():
root.get_first_child().select(0)
else:
# Call anyways to clear preview and hide portrait settings section
load_selected_portrait()
func filter_portrait_list(filter_term:String = '') -> void:
filter_branch(%PortraitTree.get_root(), filter_term)
func filter_branch(parent:TreeItem, filter_term:String) -> bool:
var anything_visible := false
for item in parent.get_children():
if item.get_metadata(0).has('group'):
item.visible = filter_branch(item, filter_term)
anything_visible = item.visible
elif filter_term.is_empty() or filter_term.to_lower() in item.get_text(0).to_lower():
item.visible = true
anything_visible = true
else:
item.visible = false
return anything_visible
# this is used to save the portrait data
func get_updated_portrait_dict() -> Dictionary:
return list_portraits(%PortraitTree.get_root().get_children())
func list_portraits(tree_items:Array[TreeItem], dict:Dictionary = {}, path_prefix = "") -> Dictionary:
for item in tree_items:
if item.get_metadata(0).has('group'):
dict = list_portraits(item.get_children(), dict, path_prefix+item.get_text(0)+"/")
else:
dict[path_prefix +item.get_text(0)] = item.get_metadata(0)
return dict
func load_selected_portrait():
if selected_item and is_instance_valid(selected_item):
selected_item.set_editable(0, false)
selected_item = %PortraitTree.get_selected()
if selected_item and !selected_item.get_metadata(0).has('group'):
%PortraitSettingsSection.show()
var current_portrait_data :Dictionary = selected_item.get_metadata(0)
portrait_selected.emit(%PortraitTree.get_full_item_name(selected_item), current_portrait_data)
update_preview()
for tab in %PortraitSettingsSection.get_children():
if !tab is DialogicCharacterEditorPortraitSettingsTab:
printerr("[Dialogic Editor] Portrait settings tabs should extend the right class!")
else:
tab.selected_item = selected_item
tab._load_portrait_data(current_portrait_data)
# switch tabs if the current one is hidden (until the next not hidden tab)
for i in range(%PortraitSettingsSection.get_tab_count()):
if %PortraitSettingsSection.is_tab_hidden(%PortraitSettingsSection.current_tab):
if %PortraitSettingsSection.current_tab == %PortraitSettingsSection.get_tab_count()-1:
%PortraitSettingsSection.current_tab = 0
else:
%PortraitSettingsSection.current_tab += 1
else:
break
else:
%PortraitSettingsSection.hide()
update_preview()
if selected_item:
await get_tree().create_timer(0.01).timeout
selected_item.set_editable(0, true)
func delete_portrait_item(item:TreeItem) -> void:
item.free()
something_changed()
func duplicate_item(item:TreeItem) -> void:
%PortraitTree.add_portrait_item(item.get_text(0)+'_duplicated', item.get_metadata(0), item.get_parent()).select(0)
func _on_portrait_tree_button_clicked(item:TreeItem, column:int, id:int, mouse_button_index:int):
# DELETE BUTTON
if id == 1:
delete_portrait_item(item)
# DUPLICATE ITEM
if id == 3:
duplicate_item(item)
# this removes/and adds the DEFAULT star on the portrait list
func update_default_portrait_star(default_portrait_name:String) -> void:
var item_list : Array = %PortraitTree.get_root().get_children()
while true:
var item = item_list.pop_back()
if item.get_button_by_id(0, 2) != -1:
item.erase_button(0, item.get_button_by_id(0, 2))
if %PortraitTree.get_full_item_name(item) == default_portrait_name:
item.erase_button(0, item.get_button_by_id(0, 1))
item.erase_button(0, item.get_button_by_id(0, 3))
item.add_button(0, get_theme_icon('Favorites', 'EditorIcons'), 2, true, 'Default')
item.add_button(0, get_theme_icon('Duplicate', 'EditorIcons'), 3, false, 'Duplicate')
item.add_button(0, get_theme_icon('Remove', 'EditorIcons'), 1, false, 'Remove')
item_list.append_array(item.get_children())
if item_list.is_empty():
break
func _on_item_edited():
selected_item = %PortraitTree.get_selected()
something_changed()
if selected_item:
if %PreviewLabel.text.trim_prefix('Preview of "').trim_suffix('"') == current_resource.default_portrait:
current_resource.default_portrait = %PortraitTree.get_full_item_name(selected_item)
update_preview()
##############################################################################
## PORTRAIT SETTINGS TAB
##############################################################################
func setup_portrait_settings_tab() -> void:
%DefaultPortraitPicker.value_changed.connect(default_portrait_changed)
%MainScale.value_changed.connect(main_portrait_settings_update)
%MainOffsetX.value_changed.connect(main_portrait_settings_update)
%MainOffsetY.value_changed.connect(main_portrait_settings_update)
%MainMirror.toggled.connect(main_portrait_settings_update)
# Setting up Default Portrait Picker
%DefaultPortraitPicker.resource_icon = load("res://addons/dialogic/Editor/Images/Resources/portrait.svg")
%DefaultPortraitPicker.get_suggestions_func = suggest_portraits
%DefaultPortraitPicker.set_left_text("")
# Make sure preview get's updated when portrait settings change
func main_portrait_settings_update(value = null) -> void:
current_resource.scale = %MainScale.value/100.0
current_resource.offset = Vector2(%MainOffsetX.value, %MainOffsetY.value)
current_resource.mirror = %MainMirror.button_pressed
update_preview()
something_changed()
func default_portrait_changed(property:String, value:String) -> void:
current_resource.default_portrait = value
update_default_portrait_star(value)
# Get suggestions for DefaultPortraitPicker
func suggest_portraits(search:String) -> Dictionary:
var suggestions := {}
for portrait in get_updated_portrait_dict().keys():
suggestions[portrait] = {'value':portrait}
return suggestions
##############################################################################
## PREVIEW
##############################################################################
func update_preview() -> void:
%ScenePreviewWarning.hide()
if selected_item and is_instance_valid(selected_item) and !selected_item.get_metadata(0).has('group'):
%PreviewLabel.text = 'Preview of "'+%PortraitTree.get_full_item_name(selected_item)+'"'
var current_portrait_data: Dictionary = selected_item.get_metadata(0)
var mirror:bool = current_portrait_data.get('mirror', false) != current_resource.mirror
var scale:float = current_portrait_data.get('scale', 1) * current_resource.scale
var offset:Vector2 =current_portrait_data.get('offset', Vector2()) + current_resource.offset
if current_previewed_scene != null \
and current_previewed_scene.get_meta('path', null) == current_portrait_data.get('scene') \
and current_previewed_scene.has_method('_should_do_portrait_update') \
and is_instance_valid(current_previewed_scene.get_script()) \
and current_previewed_scene._should_do_portrait_update(current_resource, selected_item.get_text(0)):
pass # we keep the same scene
else:
for node in %RealPreviewPivot.get_children():
node.queue_free()
current_previewed_scene = null
if current_portrait_data.get('scene', '').is_empty():
if FileAccess.file_exists("res://addons/dialogic/Events/Character/default_portrait.tscn"):
current_previewed_scene = load("res://addons/dialogic/Events/Character/default_portrait.tscn").instantiate()
current_previewed_scene.set_meta('path', '')
else:
if FileAccess.file_exists(current_portrait_data.get('scene')):
current_previewed_scene = load(current_portrait_data.get('scene')).instantiate()
current_previewed_scene.set_meta('path', current_portrait_data.get('scene'))
if current_previewed_scene:
%RealPreviewPivot.add_child(current_previewed_scene)
if current_previewed_scene != null:
var scene = current_previewed_scene
scene.show_behind_parent = true
for prop in current_portrait_data.get('export_overrides', {}).keys():
scene.set(prop, str_to_var(current_portrait_data['export_overrides'][prop]))
if is_instance_valid(scene.get_script()) and scene.script.is_tool():
if scene.has_method('_update_portrait'):
scene._update_portrait(current_resource, %PortraitTree.get_full_item_name(selected_item))
if scene.has_method('_set_mirror'):
scene._set_mirror(mirror)
if current_preview_mode == PreviewModes.Real:
scene.position = Vector2() + offset
if current_portrait_data.get('scene', '').is_empty() or !current_portrait_data.get('ignore_char_scale', false):
scene.scale = Vector2(1,1)*scale
else:
if is_instance_valid(scene.get_script()) and scene.script.is_tool() and scene.has_method('_get_covered_rect'):
var rect :Rect2= scene._get_covered_rect()
var available_rect:Rect2 = %FullPreviewAvailableRect.get_rect()
scene.scale = Vector2(1,1) * min(available_rect.size.x/rect.size.x, available_rect.size.y/rect.size.y)
%RealPreviewPivot.position = (rect.position)*-1*scene.scale
%RealPreviewPivot.position.x = %FullPreviewAvailableRect.size.x/2
scene.position = Vector2()
else:
%ScenePreviewWarning.show()
else:
%PreviewRealRect.texture = null
%PreviewFullRect.texture = null
%PreviewLabel.text = 'Nothing to preview'
else:
%PreviewLabel.text = 'No portrait to preview.'
for node in %RealPreviewPivot.get_children():
node.queue_free()
current_previewed_scene = null
func _on_PreviewMode_item_selected(index:int) -> void:
current_preview_mode = index
# FULL VIEW
if index == PreviewModes.Full:
%RealSizeRemotePivotTransform.update_position = false
# REAL SIZE
if index == PreviewModes.Real or index == null:
%RealSizeRemotePivotTransform.update_position = true
update_preview()
ProjectSettings.set_setting('dialogic/editor/character_preview_mode', index)
ProjectSettings.save()
func _on_full_preview_available_rect_resized():
if current_preview_mode == PreviewModes.Full:
update_preview()

View file

@ -0,0 +1,493 @@
[gd_scene load_steps=15 format=3 uid="uid://dlskc36c5hrwv"]
[ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/character_editor.gd" id="2"]
[ext_resource type="PackedScene" uid="uid://dpwhshre1n4t6" path="res://addons/dialogic/Editor/Events/Fields/ComplexPicker.tscn" id="2_01va3"]
[ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/character_editor_ptab_image.gd" id="2_cusvj"]
[ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/character_editor_portrait_tree.gd" id="2_vad0i"]
[ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/character_editor_ptab_scene.gd" id="4_6tltp"]
[ext_resource type="PackedScene" uid="uid://7mvxuaulctcq" path="res://addons/dialogic/Editor/Events/Fields/FilePicker.tscn" id="4_a4bcu"]
[ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/character_editor_ptab_layout.gd" id="5_eicvp"]
[ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/character_editor_ptab_exports.gd" id="7_4enie"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kxihh"]
content_margin_left = 10.0
content_margin_top = 4.0
content_margin_right = 10.0
content_margin_bottom = 4.0
bg_color = Color(0, 0, 0, 1)
border_width_top = 2
border_color = Color(1, 1, 1, 0.75)
corner_detail = 1
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1pdx5"]
content_margin_left = 7.5
content_margin_top = 7.5
content_margin_right = 7.5
content_margin_bottom = 7.5
bg_color = Color(0, 0, 0, 1)
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
[sub_resource type="Image" id="Image_8q5e2"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 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, 93, 93, 131, 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, 93, 93, 131, 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, 93, 93, 131, 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, 252, 255, 93, 93, 252, 255, 93, 93, 252, 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, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 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, 93, 93, 55, 255, 97, 97, 58, 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, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 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, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 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, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 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_uf4x7"]
image = SubResource("Image_8q5e2")
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_4xgdx"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_81xk6"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
bg_color = Color(1, 0.365, 0.365, 1)
draw_center = false
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
corner_detail = 1
[node name="CharacterEditor" type="MarginContainer"]
self_modulate = Color(0, 0, 0, 1)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 0
theme_override_constants/margin_top = 0
theme_override_constants/margin_right = 0
theme_override_constants/margin_bottom = 0
script = ExtResource("2")
[node name="Split" type="HSplitContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Editor" type="VSplitContainer" parent="Split"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 11
[node name="EditorScroll" type="ScrollContainer" parent="Split/Editor"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.0
vertical_scroll_mode = 0
[node name="MainEditTabs" type="TabContainer" parent="Split/Editor/EditorScroll"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/side_margin = 5
theme_override_styles/tab_selected = SubResource("StyleBoxFlat_kxihh")
theme_override_styles/panel = SubResource("StyleBoxFlat_1pdx5")
[node name="PortraitListSection" type="TabContainer" parent="Split/Editor"]
unique_name_in_owner = true
layout_mode = 2
theme_override_constants/side_margin = 5
theme_override_styles/tab_selected = SubResource("StyleBoxFlat_kxihh")
theme_override_styles/panel = SubResource("StyleBoxFlat_1pdx5")
[node name="Portraits" type="VBoxContainer" parent="Split/Editor/PortraitListSection"]
layout_mode = 2
[node name="PortraitListTools" type="HBoxContainer" parent="Split/Editor/PortraitListSection/Portraits"]
layout_mode = 2
[node name="AddPortraitButton" type="Button" parent="Split/Editor/PortraitListSection/Portraits/PortraitListTools"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Add portrait"
icon = SubResource("ImageTexture_uf4x7")
[node name="AddPortraitGroupButton" type="Button" parent="Split/Editor/PortraitListSection/Portraits/PortraitListTools"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Add Group"
icon = SubResource("ImageTexture_uf4x7")
[node name="ImportPortraitsButton" type="Button" parent="Split/Editor/PortraitListSection/Portraits/PortraitListTools"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Import images from folder"
icon = SubResource("ImageTexture_uf4x7")
[node name="PortraitSearch" type="LineEdit" parent="Split/Editor/PortraitListSection/Portraits/PortraitListTools"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 4
placeholder_text = "Search"
expand_to_text_length = true
clear_button_enabled = true
right_icon = SubResource("ImageTexture_uf4x7")
caret_blink = true
caret_blink_interval = 0.5
[node name="Panel" type="PanelContainer" parent="Split/Editor/PortraitListSection/Portraits"]
layout_mode = 2
size_flags_vertical = 3
theme_override_styles/panel = SubResource("StyleBoxEmpty_4xgdx")
[node name="PortraitTree" type="Tree" parent="Split/Editor/PortraitListSection/Portraits/Panel"]
unique_name_in_owner = true
layout_mode = 2
allow_rmb_select = true
hide_root = true
drop_mode_flags = 1
script = ExtResource("2_vad0i")
[node name="P Settings" type="VBoxContainer" parent="Split/Editor/PortraitListSection"]
visible = false
layout_mode = 2
[node name="DefaultPortrait" type="HBoxContainer" parent="Split/Editor/PortraitListSection/P Settings"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Label5" type="Label" parent="Split/Editor/PortraitListSection/P Settings/DefaultPortrait"]
layout_mode = 2
text = "Default:"
[node name="DefaultPortraitPicker" parent="Split/Editor/PortraitListSection/P Settings/DefaultPortrait" instance=ExtResource("2_01va3")]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Select Portrait"
[node name="HSeparator" type="HSeparator" parent="Split/Editor/PortraitListSection/P Settings"]
layout_mode = 2
[node name="PortraitMainSettings" type="HFlowContainer" parent="Split/Editor/PortraitListSection/P Settings"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Label" type="Label" parent="Split/Editor/PortraitListSection/P Settings/PortraitMainSettings"]
layout_mode = 2
text = "Scale"
[node name="MainScale" type="SpinBox" parent="Split/Editor/PortraitListSection/P Settings/PortraitMainSettings"]
unique_name_in_owner = true
layout_mode = 2
value = 100.0
allow_greater = true
suffix = "%"
[node name="Offset" type="HBoxContainer" parent="Split/Editor/PortraitListSection/P Settings/PortraitMainSettings"]
layout_mode = 2
[node name="Label2" type="Label" parent="Split/Editor/PortraitListSection/P Settings/PortraitMainSettings/Offset"]
layout_mode = 2
text = "Offset"
[node name="MainOffsetX" type="SpinBox" parent="Split/Editor/PortraitListSection/P Settings/PortraitMainSettings/Offset"]
unique_name_in_owner = true
layout_mode = 2
allow_greater = true
allow_lesser = true
suffix = "X"
[node name="MainOffsetY" type="SpinBox" parent="Split/Editor/PortraitListSection/P Settings/PortraitMainSettings/Offset"]
unique_name_in_owner = true
layout_mode = 2
allow_greater = true
allow_lesser = true
suffix = "Y"
[node name="MirrorOption" type="HBoxContainer" parent="Split/Editor/PortraitListSection/P Settings/PortraitMainSettings"]
layout_mode = 2
[node name="Label" type="Label" parent="Split/Editor/PortraitListSection/P Settings/PortraitMainSettings/MirrorOption"]
layout_mode = 2
text = "Mirror"
[node name="MainMirror" type="CheckBox" parent="Split/Editor/PortraitListSection/P Settings/PortraitMainSettings/MirrorOption"]
unique_name_in_owner = true
layout_mode = 2
[node name="RightSection" type="VSplitContainer" parent="Split"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
size_flags_stretch_ratio = 1.5
[node name="PortraitPreviewSection" type="Panel" parent="Split/RightSection"]
unique_name_in_owner = true
clip_contents = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_1pdx5")
[node name="Node2D" type="Node2D" parent="Split/RightSection/PortraitPreviewSection"]
position = Vector2(13, 17)
[node name="RealPreviewPivot" type="Sprite2D" parent="Split/RightSection/PortraitPreviewSection/Node2D"]
unique_name_in_owner = true
position = Vector2(330, 405)
texture = SubResource("ImageTexture_uf4x7")
[node name="ScenePreviewWarning" type="Label" parent="Split/RightSection/PortraitPreviewSection"]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -143.0
offset_top = -44.5
offset_right = 143.0
offset_bottom = 85.5
grow_horizontal = 2
grow_vertical = 2
text = "Custom scenes can only be viewed in \"Full mode\" if they are in @tool mode and override _get_covered_rect"
horizontal_alignment = 1
vertical_alignment = 1
autowrap_mode = 3
metadata/_edit_layout_mode = 1
[node name="PreviewReal" type="CenterContainer" parent="Split/RightSection/PortraitPreviewSection"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -302.0
offset_top = -139.0
offset_right = 302.0
grow_horizontal = 2
grow_vertical = 0
metadata/_edit_layout_mode = 1
[node name="Control" type="Control" parent="Split/RightSection/PortraitPreviewSection/PreviewReal"]
layout_mode = 2
[node name="RealSizeRemotePivotTransform" type="RemoteTransform2D" parent="Split/RightSection/PortraitPreviewSection/PreviewReal/Control"]
unique_name_in_owner = true
remote_path = NodePath("../../../Node2D/RealPreviewPivot")
update_position = false
update_rotation = false
update_scale = false
[node name="FullPreviewAvailableRect" type="Control" parent="Split/RightSection/PortraitPreviewSection"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 10.0
offset_top = 13.0
offset_right = -10.0
offset_bottom = -16.0
grow_horizontal = 2
grow_vertical = 2
metadata/_edit_layout_mode = 1
[node name="PreviewLabel" type="Label" parent="Split/RightSection/PortraitPreviewSection"]
unique_name_in_owner = true
layout_mode = 0
offset_right = 40.0
offset_bottom = 23.0
text = "No portrait to preview."
[node name="PreviewMode" type="OptionButton" parent="Split/RightSection/PortraitPreviewSection"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -121.0
offset_top = 9.0
offset_right = -9.0
offset_bottom = 40.0
grow_horizontal = 0
item_count = 2
selected = 0
popup/item_0/text = "Full View"
popup/item_0/id = 0
popup/item_1/text = "Real Size"
popup/item_1/id = 1
metadata/_edit_layout_mode = 1
[node name="TextureRect" type="TextureRect" parent="Split/RightSection/PortraitPreviewSection"]
layout_mode = 0
offset_top = -12.0
offset_right = 597.0
offset_bottom = -12.0
[node name="PortraitSettingsSection" type="TabContainer" parent="Split/RightSection"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
size_flags_stretch_ratio = 0.3
theme_override_constants/side_margin = 5
theme_override_styles/tab_selected = SubResource("StyleBoxFlat_kxihh")
theme_override_styles/panel = SubResource("StyleBoxFlat_1pdx5")
[node name="Image" type="ScrollContainer" parent="Split/RightSection/PortraitSettingsSection"]
custom_minimum_size = Vector2(0, 35)
layout_mode = 2
horizontal_scroll_mode = 0
script = ExtResource("2_cusvj")
[node name="Flow" type="HFlowContainer" parent="Split/RightSection/PortraitSettingsSection/Image"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="GridContainer" type="GridContainer" parent="Split/RightSection/PortraitSettingsSection/Image/Flow"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
columns = 2
[node name="Label" type="Label" parent="Split/RightSection/PortraitSettingsSection/Image/Flow/GridContainer"]
layout_mode = 2
text = "Image: "
[node name="ImagePicker" parent="Split/RightSection/PortraitSettingsSection/Image/Flow/GridContainer" instance=ExtResource("4_a4bcu")]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
file_filter = "*.png, *.svg"
resource_icon = SubResource("ImageTexture_uf4x7")
[node name="Scene" type="ScrollContainer" parent="Split/RightSection/PortraitSettingsSection"]
visible = false
custom_minimum_size = Vector2(0, 35)
layout_mode = 2
horizontal_scroll_mode = 0
script = ExtResource("4_6tltp")
[node name="Flow" type="HFlowContainer" parent="Split/RightSection/PortraitSettingsSection/Scene"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="GridContainer" type="GridContainer" parent="Split/RightSection/PortraitSettingsSection/Scene/Flow"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
columns = 2
[node name="Label" type="Label" parent="Split/RightSection/PortraitSettingsSection/Scene/Flow/GridContainer"]
layout_mode = 2
text = "Scene: "
[node name="ScenePicker" parent="Split/RightSection/PortraitSettingsSection/Scene/Flow/GridContainer" instance=ExtResource("4_a4bcu")]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
file_filter = "*.tscn"
placeholder = "Default scene"
resource_icon = SubResource("ImageTexture_uf4x7")
[node name="Label2" type="Label" parent="Split/RightSection/PortraitSettingsSection/Scene/Flow/GridContainer"]
layout_mode = 2
text = "Ignore scale: "
[node name="IgnoreScale" type="CheckBox" parent="Split/RightSection/PortraitSettingsSection/Scene/Flow/GridContainer"]
unique_name_in_owner = true
layout_mode = 2
[node name="Layout" type="ScrollContainer" parent="Split/RightSection/PortraitSettingsSection"]
visible = false
custom_minimum_size = Vector2(0, 35)
layout_mode = 2
horizontal_scroll_mode = 0
script = ExtResource("5_eicvp")
[node name="Flow" type="HFlowContainer" parent="Split/RightSection/PortraitSettingsSection/Layout"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="HBoxContainer" type="HBoxContainer" parent="Split/RightSection/PortraitSettingsSection/Layout/Flow"]
layout_mode = 2
[node name="Label" type="Label" parent="Split/RightSection/PortraitSettingsSection/Layout/Flow/HBoxContainer"]
layout_mode = 2
text = "Scale:"
[node name="PortraitScale" type="SpinBox" parent="Split/RightSection/PortraitSettingsSection/Layout/Flow/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
value = 100.0
allow_greater = true
suffix = "%"
[node name="HBoxContainer2" type="HBoxContainer" parent="Split/RightSection/PortraitSettingsSection/Layout/Flow"]
layout_mode = 2
[node name="Label2" type="Label" parent="Split/RightSection/PortraitSettingsSection/Layout/Flow/HBoxContainer2"]
layout_mode = 2
text = "Offset:"
[node name="PortraitOffsetX" type="SpinBox" parent="Split/RightSection/PortraitSettingsSection/Layout/Flow/HBoxContainer2"]
unique_name_in_owner = true
layout_mode = 2
allow_greater = true
allow_lesser = true
suffix = "X"
[node name="PortraitOffsetY" type="SpinBox" parent="Split/RightSection/PortraitSettingsSection/Layout/Flow/HBoxContainer2"]
unique_name_in_owner = true
layout_mode = 2
allow_greater = true
allow_lesser = true
suffix = "Y"
[node name="MirrorOption" type="HBoxContainer" parent="Split/RightSection/PortraitSettingsSection/Layout/Flow"]
layout_mode = 2
[node name="Label" type="Label" parent="Split/RightSection/PortraitSettingsSection/Layout/Flow/MirrorOption"]
layout_mode = 2
text = "Mirror:"
[node name="PortraitMirror" type="CheckBox" parent="Split/RightSection/PortraitSettingsSection/Layout/Flow/MirrorOption"]
unique_name_in_owner = true
layout_mode = 2
[node name="Exports" type="ScrollContainer" parent="Split/RightSection/PortraitSettingsSection"]
visible = false
custom_minimum_size = Vector2(0, 35)
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_81xk6")
horizontal_scroll_mode = 0
script = ExtResource("7_4enie")
[node name="Grid" type="GridContainer" parent="Split/RightSection/PortraitSettingsSection/Exports"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/h_separation = 10
columns = 2
[connection signal="button_clicked" from="Split/Editor/PortraitListSection/Portraits/Panel/PortraitTree" to="." method="_on_portrait_tree_button_clicked"]
[connection signal="resized" from="Split/RightSection/PortraitPreviewSection/FullPreviewAvailableRect" to="." method="_on_full_preview_available_rect_resized"]
[connection signal="value_changed" from="Split/RightSection/PortraitSettingsSection/Image/Flow/GridContainer/ImagePicker" to="Split/RightSection/PortraitSettingsSection/Image" method="_on_image_picker_value_changed"]
[connection signal="value_changed" from="Split/RightSection/PortraitSettingsSection/Scene/Flow/GridContainer/ScenePicker" to="Split/RightSection/PortraitSettingsSection/Scene" method="_on_scene_picker_value_changed"]
[connection signal="toggled" from="Split/RightSection/PortraitSettingsSection/Scene/Flow/GridContainer/IgnoreScale" to="Split/RightSection/PortraitSettingsSection/Scene" method="_on_ignore_scale_toggled"]
[connection signal="value_changed" from="Split/RightSection/PortraitSettingsSection/Layout/Flow/HBoxContainer/PortraitScale" to="Split/RightSection/PortraitSettingsSection/Layout" method="_on_portrait_scale_value_changed"]
[connection signal="value_changed" from="Split/RightSection/PortraitSettingsSection/Layout/Flow/HBoxContainer2/PortraitOffsetX" to="Split/RightSection/PortraitSettingsSection/Layout" method="_on_portrait_offset_x_value_changed"]
[connection signal="value_changed" from="Split/RightSection/PortraitSettingsSection/Layout/Flow/HBoxContainer2/PortraitOffsetY" to="Split/RightSection/PortraitSettingsSection/Layout" method="_on_portrait_offset_y_value_changed"]
[connection signal="toggled" from="Split/RightSection/PortraitSettingsSection/Layout/Flow/MirrorOption/PortraitMirror" to="Split/RightSection/PortraitSettingsSection/Layout" method="_on_portrait_mirror_toggled"]

View file

@ -0,0 +1,20 @@
@tool
class_name DialogicCharacterEditorMainTab
extends Control
## Base class for all character editor main tabs. Methods should be overriden.
# Emit this, if something changed
signal changed
var character_editor:Control
func _load_character(resource:DialogicCharacter) -> void:
pass
func _save_changes(resource:DialogicCharacter) -> DialogicCharacter:
return resource

View file

@ -0,0 +1,16 @@
@tool
class_name DialogicCharacterEditorPortraitSettingsTab
extends ScrollContainer
## Base class for all portrait settings tabs. Methods should be overriden.
# Emit this, if something changed
signal changed
signal update_preview
var character_editor:Control
var selected_item :TreeItem = null
func _load_portrait_data(data:Dictionary) -> void:
pass

View file

@ -0,0 +1,115 @@
@tool
extends Tree
## Tree that displays the portrait list as a hirarchy
var editor := find_parent('Character Editor')
var current_group_nodes := {}
func clear_tree() -> void:
clear()
current_group_nodes = {}
func add_portrait_item(portrait_name:String, portrait_data:Dictionary, parent_item:TreeItem) -> TreeItem:
var item :TreeItem = %PortraitTree.create_item(parent_item)
item.set_text(0, portrait_name)
item.set_metadata(0, portrait_data)
if portrait_name == editor.current_resource.default_portrait:
item.add_button(0, get_theme_icon('Favorites', 'EditorIcons'), 2, true, 'Default')
item.add_button(0, get_theme_icon('Duplicate', 'EditorIcons'), 3, false, 'Duplicate')
item.add_button(0, get_theme_icon('Remove', 'EditorIcons'), 1, false, 'Remove')
return item
func add_portrait_group(goup_name:String = "Group", parent_item:TreeItem = get_root()):
var item :TreeItem = %PortraitTree.create_item(parent_item)
item.set_icon(0, get_theme_icon("Groups", "EditorIcons"))
item.set_text(0, goup_name)
item.set_metadata(0, {'group':true})
item.add_button(0, get_theme_icon('Remove', 'EditorIcons'), 1, false, 'Remove')
return item
func get_full_item_name(item:TreeItem) -> String:
var item_name := item.get_text(0)
while item.get_parent() != get_root() and item != get_root():
item_name = item.get_parent().get_text(0)+"/"+item_name
item = item.get_parent()
return item_name
# Will create all not yet existing folders in the given path.
# Returns the last folder (the parent of the portrait item of this path).
func create_necessary_group_items(path:String) -> void:
var last_item := get_root()
var item_path := ""
for i in Array(path.split('/')).slice(0, -1):
item_path += "/"+i
item_path = item_path.trim_prefix('/')
if current_group_nodes.has(item_path+"/"+i):
last_item = current_group_nodes[item_path+"/"+i]
else:
var new_item := add_portrait_group(i, last_item)
current_group_nodes[item_path+"/"+i] = new_item
last_item = new_item
return last_item
################################################################################
## DRAG AND DROP
################################################################################
func _get_drag_data(position:Vector2) -> Variant:
set_drop_mode_flags(DROP_MODE_ON_ITEM)
var preview := Label.new()
preview.text = " "+get_selected().get_text(0)
preview.add_theme_stylebox_override('normal', get_theme_stylebox("Background", "EditorStyles"))
set_drag_preview(preview)
return get_selected()
func _can_drop_data(position:Vector2, data:Variant) -> bool:
return data is TreeItem
func _drop_data(position:Vector2, item:Variant) -> void:
var to_item := get_item_at_position(position)
if to_item:
var test_item:= to_item
while true:
if test_item == item:
return
test_item = test_item.get_parent()
if test_item == get_root():
break
var parent := get_root()
if to_item:
parent = to_item.get_parent()
if to_item and to_item.get_metadata(0).has('group'):
parent = to_item
var new_item := copy_branch_or_item(item, parent)
if to_item and !to_item.get_metadata(0).has('group'):
new_item.move_after(to_item)
item.free()
func copy_branch_or_item(item:TreeItem, new_parent:TreeItem) -> TreeItem:
var new_item :TreeItem = null
if item.get_metadata(0).has('group'):
new_item = add_portrait_group(item.get_text(0), new_parent)
else:
new_item = add_portrait_item(item.get_text(0), item.get_metadata(0), new_parent)
for child in item.get_children():
copy_branch_or_item(child, new_item)
return new_item

View file

@ -0,0 +1,102 @@
@tool
extends DialogicCharacterEditorPortraitSettingsTab
## Tab that allows setting values of exported scene variables
## for custom portrait scenes
var current_portrait_data := {}
func _ready() -> void:
get_parent().set_tab_icon(get_index(), get_theme_icon("EditInternal", "EditorIcons"))
add_theme_stylebox_override('panel', get_theme_stylebox("Background", "EditorStyles"))
func _load_portrait_data(data:Dictionary) -> void:
if data.get('scene', '').is_empty():
get_parent().set_tab_hidden(get_index(), true)
else:
get_parent().set_tab_hidden(get_index(), false)
current_portrait_data = data
load_portrait_scene_export_variables()
func load_portrait_scene_export_variables():
var scene = null
if !current_portrait_data.get('scene', '').is_empty():
scene = load(current_portrait_data.get('scene'))
if !scene:
return
for child in $Grid.get_children():
child.queue_free()
scene = scene.instantiate()
for i in scene.script.get_script_property_list():
if i['usage'] & PROPERTY_USAGE_EDITOR:
var label = Label.new()
label.text = i['name']
label.add_theme_stylebox_override('normal', get_theme_stylebox("CanvasItemInfoOverlay", "EditorStyles"))
label.size_flags_horizontal = SIZE_EXPAND_FILL
$Grid.add_child(label)
var input = null
var current_value :String = ""
if current_portrait_data.has('export_overrides') and current_portrait_data['export_overrides'].has(i['name']):
current_value = current_portrait_data['export_overrides'][i['name']]
match typeof(scene.get(i['name'])):
TYPE_BOOL:
input = CheckBox.new()
if current_value:
input.button_pressed = current_value == "true"
input.toggled.connect(_on_export_bool_submitted.bind(i['name']))
TYPE_COLOR:
input = ColorPickerButton.new()
if current_value:
input.color = str_to_var(current_value)
input.color_changed.connect(_on_export_color_submitted.bind(i['name']))
input.custom_minimum_size.x = DialogicUtil.get_editor_scale()*50
TYPE_INT:
if i['hint'] & PROPERTY_HINT_ENUM:
input = OptionButton.new()
for x in i['hint_string'].split(','):
input.add_item(x)
input.item_selected.connect(_on_export_int_enum_submitted.bind(i['name']))
_:
input = LineEdit.new()
if current_value:
input.text = current_value
input.text_submitted.connect(_on_export_input_text_submitted.bind(i['name']))
input.size_flags_horizontal = SIZE_EXPAND_FILL
$Grid.add_child(input)
if i['usage'] & PROPERTY_USAGE_GROUP:
var title := Label.new()
title.text = i['name']
title.add_theme_stylebox_override('normal', get_theme_stylebox("ContextualToolbar", "EditorStyles"))
$Grid.add_child(title)
$Grid.add_child(Control.new())
func set_export_override(property_name:String, value:String = "") -> void:
var data:Dictionary = selected_item.get_metadata(0)
if !data.has('export_overrides'):
data['export_overrides'] = {}
if !value.is_empty():
data['export_overrides'][property_name] = value
else:
data['export_overrides'].erase(property_name)
changed.emit()
update_preview.emit()
func _on_export_input_text_submitted(text:String, property_name:String) -> void:
set_export_override(property_name, var_to_str(text))
func _on_export_bool_submitted(value:bool, property_name:String) -> void:
set_export_override(property_name, var_to_str(value))
func _on_export_color_submitted(color:Color, property_name:String) -> void:
set_export_override(property_name, var_to_str(color))
func _on_export_int_enum_submitted(item:int, property_name:String) -> void:
set_export_override(property_name, var_to_str(item))

View file

@ -0,0 +1,25 @@
@tool
extends DialogicCharacterEditorPortraitSettingsTab
## Tab that allows setting an image file on a portrait.
func _ready() -> void:
get_parent().set_tab_icon(get_index(), get_theme_icon('Image', 'EditorIcons'))
%ImagePicker.file_filter = "*.png, *.svg"
%ImagePicker.resource_icon = get_theme_icon('Image', 'EditorIcons')
func _load_portrait_data(data:Dictionary) -> void:
# hides/shows this tab based on the scene value of this portrait
# (only shown if the default scene is used)
get_parent().set_tab_hidden(get_index(), !data.get('scene', '').is_empty())
%ImagePicker.set_value(data.get('image', ''))
func _on_image_picker_value_changed(prop_name:String, value:String):
var data:Dictionary = selected_item.get_metadata(0)
data['image'] = value
changed.emit()
update_preview.emit()

View file

@ -0,0 +1,43 @@
@tool
extends DialogicCharacterEditorPortraitSettingsTab
## Tab that allows setting size, offset and mirror of a portrait.
func _ready():
get_parent().set_tab_icon(get_index(), get_theme_icon('EditorPivot', 'EditorIcons'))
func _load_portrait_data(data:Dictionary) -> void:
%PortraitScale.value = data.get('scale', 1.0)*100
%PortraitOffsetX.value = data.get('offset', Vector2()).x
%PortraitOffsetY.value = data.get('offset', Vector2()).y
%PortraitMirror.button_pressed = data.get('mirror', false)
func _on_portrait_scale_value_changed(value) -> void:
var data:Dictionary = selected_item.get_metadata(0)
data['scale'] = value/100.0
update_preview.emit()
changed.emit()
func _on_portrait_offset_x_value_changed(value) -> void:
var data:Dictionary = selected_item.get_metadata(0)
data['offset'] = data.get('offset', Vector2())
data['offset'].x = value
update_preview.emit()
changed.emit()
func _on_portrait_offset_y_value_changed(value)-> void:
var data:Dictionary = selected_item.get_metadata(0)
data['offset'] = data.get('offset', Vector2())
data['offset'].y = value
update_preview.emit()
changed.emit()
func _on_portrait_mirror_toggled(button_pressed:bool)-> void:
var data:Dictionary = selected_item.get_metadata(0)
data['mirror'] = button_pressed
update_preview.emit()
changed.emit()

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