diff --git a/addons/anim_player_refactor/lib/anim_player_refactor.gd b/addons/anim_player_refactor/lib/anim_player_refactor.gd new file mode 100644 index 0000000..9e533e4 --- /dev/null +++ b/addons/anim_player_refactor/lib/anim_player_refactor.gd @@ -0,0 +1,207 @@ +## Core utility class to handle all refactoring logic + +const EditorUtil := preload("res://addons/anim_player_refactor/lib/editor_util.gd") + +var _editor_plugin: EditorPlugin +var _undo_redo: EditorUndoRedoManager + +func _init(editor_plugin: EditorPlugin) -> void: + _editor_plugin = editor_plugin + _undo_redo = editor_plugin.get_undo_redo() + +# Nodes +func rename_node_path(anim_player: AnimationPlayer, old: NodePath, new: NodePath): + if old == new: + return + + _undo_redo.create_action("Refactor node tracks", UndoRedo.MERGE_ALL, anim_player) + + _foreach_animation(anim_player, func(animation: Animation): + for i in animation.get_track_count(): + var path := animation.track_get_path(i) + var node_path := path.get_concatenated_names() + + if node_path == old.get_concatenated_names(): + var new_path := new.get_concatenated_names() + ":" + path.get_concatenated_subnames() + animation.track_set_path(i, NodePath(new_path)) + + _undo_redo.add_do_property(animation, "tracks/%d/path" % i, new_path) + _undo_redo.add_undo_property(animation, "tracks/%d/path" % i, path) + ) + + _undo_redo.commit_action() + + +func remove_node_path(anim_player: AnimationPlayer, node_path: NodePath): + _undo_redo.create_action("Remove node tracks", UndoRedo.MERGE_ALL, anim_player) + + _foreach_animation_restore(anim_player, _undo_redo, func(animation: Animation): + var removed_tracks = 0 + + for i in range(animation.get_track_count() - 1, -1, -1): + var path = animation.track_get_path(i) + + if NodePath(path.get_concatenated_names()) == node_path: + removed_tracks += 1 + _undo_redo.add_do_method(animation, &'remove_track', i) + + return removed_tracks + ) + + _undo_redo.commit_action() + + +# Tracks +func rename_track_path(anim_player: AnimationPlayer, old: NodePath, new: NodePath): + if old == new: + return + + _undo_redo.create_action("Refactor track paths", UndoRedo.MERGE_ALL, anim_player) + + _foreach_animation(anim_player, func(animation: Animation): + for i in animation.get_track_count(): + var path = animation.track_get_path(i) + + if path == old: + animation.track_set_path(i, new) + + _undo_redo.add_do_property(animation, "tracks/%d/path" % i, new) + _undo_redo.add_undo_property(animation, "tracks/%d/path" % i, old) + ) + + _undo_redo.commit_action() + + +func remove_track_path(anim_player: AnimationPlayer, property_path: NodePath): + _undo_redo.create_action("Remove tracks", UndoRedo.MERGE_ALL, anim_player) + + _foreach_animation_restore(anim_player, _undo_redo, func(animation: Animation): + var removed_tracks = 0 + + for i in range(animation.get_track_count() - 1, -1, -1): + var path = animation.track_get_path(i) + + if path == property_path: + removed_tracks += 1 + _undo_redo.add_do_method(animation, &'remove_track', i) + + return removed_tracks + ) + + _undo_redo.commit_action() + +# Method tracks +func rename_method(anim_player, old: NodePath, new: NodePath): + if old == new: + return + + var node_path := NodePath(old.get_concatenated_names()) + var old_method := old.get_concatenated_subnames() + var new_method := new.get_concatenated_subnames() + + _undo_redo.create_action("Rename method keys", UndoRedo.MERGE_ALL, anim_player) + + _foreach_animation(anim_player, func(animation: Animation): + for i in animation.get_track_count(): + if (animation.track_get_type(i) == Animation.TYPE_METHOD and animation.track_get_path(i) == node_path): + for j in animation.track_get_key_count(i): + var name := animation.method_track_get_name(i, j) + if name == old_method: + var old_method_params := { + "method": old_method, + "args": animation.method_track_get_params(i, j) + } + + var method_params := { + "method": new_method, + "args": animation.method_track_get_params(i, j) + } + + _undo_redo.add_do_method(animation, &'track_set_key_value', i, j, method_params) + _undo_redo.add_undo_method(animation, &'track_set_key_value', i, j, old_method_params) + ) + + _undo_redo.commit_action() + + +func remove_method(anim_player: AnimationPlayer, method_path: NodePath): + _undo_redo.create_action("Remove method keys", UndoRedo.MERGE_ALL, anim_player) + + _foreach_animation_restore(anim_player, _undo_redo, func(animation: Animation): + for i in animation.get_track_count(): + if ( + animation.track_get_type(i) == Animation.TYPE_METHOD + and StringName(animation.track_get_path(i)) == method_path.get_concatenated_names() + ): + for j in range(animation.track_get_key_count(i) - 1, -1, -1): + var name := animation.method_track_get_name(i, j) + if name == method_path.get_concatenated_subnames(): + _undo_redo.add_do_method(animation, &'track_remove_key', i, j) + return 0 + ) + + _undo_redo.commit_action() + + +# Root +func change_root(anim_player: AnimationPlayer, new_path: NodePath): + var current_root: Node = anim_player.get_node(anim_player.root_node) + var new_root: Node = anim_player.get_node_or_null(new_path) + + if new_root == null: + return + + _undo_redo.create_action("Change animation player root", UndoRedo.MERGE_ALL, anim_player) + + _foreach_animation(anim_player, func(animation: Animation): + for i in animation.get_track_count(): + var path := animation.track_get_path(i) + var node := current_root.get_node_or_null(NodePath(path.get_concatenated_names())) + + if node == null: + push_warning("Invalid path: %s. Skipping root change." % path) + continue + + var updated_path = str(new_root.get_path_to(node)) + ":" + path.get_concatenated_subnames() + + _undo_redo.add_do_property(animation, "tracks/%d/path" % i, updated_path) + _undo_redo.add_undo_property(animation, "tracks/%d/path" % i, path) + ) + + _undo_redo.add_do_property(anim_player, "root_node", new_path) + _undo_redo.add_undo_property(anim_player, "root_node", anim_player.root_node) + + _undo_redo.commit_action() + + + +# Helper methods + +## Iterates over all animations in the animation player +static func _foreach_animation(anim_player: AnimationPlayer, callback: Callable): + for lib_name in anim_player.get_animation_library_list(): + var lib := anim_player.get_animation_library(lib_name) + for animation_name in lib.get_animation_list(): + var animation := lib.get_animation(animation_name) + callback.call(animation) + + +## Iterates over all animations in the animation player and adds a full revert to the undo stack +## Useful for do actions that remove tracks +static func _foreach_animation_restore(anim_player: AnimationPlayer, undo_redo: EditorUndoRedoManager, callback: Callable): + for lib_name in anim_player.get_animation_library_list(): + var lib := anim_player.get_animation_library(lib_name) + for animation_name in lib.get_animation_list(): + var animation := lib.get_animation(animation_name) + + var old_anim := animation.duplicate(true) + + var removed_tracked = callback.call(animation) + + for i in range(animation.get_track_count() - 1 - removed_tracked, -1, -1): + undo_redo.add_undo_method(animation, &'remove_track', i) + + for i in range(old_anim.get_track_count()): + undo_redo.add_undo_method(old_anim, &'copy_track', i, animation) + + undo_redo.add_undo_reference(old_anim) diff --git a/addons/anim_player_refactor/lib/editor_util.gd b/addons/anim_player_refactor/lib/editor_util.gd new file mode 100644 index 0000000..bbce3d6 --- /dev/null +++ b/addons/anim_player_refactor/lib/editor_util.gd @@ -0,0 +1,64 @@ +# Utility class for parsing and hacking the editor + +## Find menu button to add option to +static func find_animation_menu_button(node: Node) -> MenuButton: + var animation_editor := find_editor_control_with_class(node, "AnimationPlayerEditor") + if animation_editor: + return find_editor_control_with_class( + animation_editor, + "MenuButton", + func(node): return node.text == "Animation" + ) + + return null + + +## General utility to find a control in the editor using an iterative search +static func find_editor_control_with_class( + base: Control, + p_class_name: StringName, + condition := func(node: Node): return true + ) -> Node: + if base.get_class() == p_class_name and condition.call(base): + return base + + for child in base.get_children(): + if not child is Control: + continue + + var found = find_editor_control_with_class(child, p_class_name) + if found: + return found + + return null + + + +# Finds the active animation player (either pinned or selected) +static func find_active_anim_player(base_control: Control, scene_tree: Tree) -> AnimationPlayer: + var find_anim_player_recursive: Callable + + var pin_icon := scene_tree.get_theme_icon("Pin", "EditorIcons") + + var stack: Array[TreeItem] = [] + stack.append(scene_tree.get_root()) + + while not stack.is_empty(): + var current := stack.pop_back() as TreeItem + + # Check for pin icon + for i in current.get_button_count(0): + if current.get_button(0, i) == pin_icon: + var node := base_control.get_node_or_null(current.get_metadata(0)) + if node is AnimationPlayer: + return node + + if current.is_selected(0): + var node := base_control.get_node_or_null(current.get_metadata(0)) + if node is AnimationPlayer: + return node + + for i in range(current.get_child_count() - 1, -1, -1): + stack.push_back(current.get_child(i)) + + return null diff --git a/addons/anim_player_refactor/plugin.cfg b/addons/anim_player_refactor/plugin.cfg new file mode 100644 index 0000000..cfbfe9f --- /dev/null +++ b/addons/anim_player_refactor/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Animation Player Refactor" +description="Refactoring tools for AnimationPlayer libraries. Adds \"Refactor\" menu option under AnimationPlayer > Animation." +author="poohcom1" +version="0.1.4" +script="plugin.gd" diff --git a/addons/anim_player_refactor/plugin.gd b/addons/anim_player_refactor/plugin.gd new file mode 100644 index 0000000..d29481f --- /dev/null +++ b/addons/anim_player_refactor/plugin.gd @@ -0,0 +1,144 @@ +@tool +extends EditorPlugin + +const RefactorDialogue := preload("scenes/refactor_dialogue/refactor_dialogue.gd") + +const AnimPlayerInspectorButton := preload("scenes/inspector_button/inspector_button.gd") + +const EditorUtil := preload("lib/editor_util.gd") + +var activate_button: AnimPlayerInspectorButton +var refactor_dialogue: RefactorDialogue + +var anim_menu_button: MenuButton + +var _last_anim_player: AnimationPlayer +const SCENE_TREE_IDX := 0 +var _scene_tree: Tree + +func _enter_tree() -> void: + # Create dialogue + refactor_dialogue = load("res://addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.tscn").instantiate() + get_editor_interface().get_base_control().add_child(refactor_dialogue) + refactor_dialogue.init(self) + # Create menu button + _add_refactor_option(func(): + refactor_dialogue.popup_centered() + refactor_dialogue.reset_size() + ) + + +func _exit_tree() -> void: + if refactor_dialogue and refactor_dialogue.is_inside_tree(): + get_editor_interface().get_base_control().remove_child(refactor_dialogue) + refactor_dialogue.queue_free() + + _remove_refactor_option() + + +func _handles(object: Object) -> bool: + if object is AnimationPlayer: + _last_anim_player = object + return false + + +# Editor methods +func get_anim_player() -> AnimationPlayer: + # Check for pinned animation + if not _scene_tree: + var _scene_tree_editor = EditorUtil.find_editor_control_with_class( + get_editor_interface().get_base_control(), + "SceneTreeEditor" + ) + + if not _scene_tree_editor: + push_error("[Animation Refactor] Could not find scene tree editor. Please report this.") + return null + + _scene_tree = _scene_tree_editor.get_child(SCENE_TREE_IDX) + + if not _scene_tree: + push_error("[Animation Refactor] Could not find scene tree editor. Please report this.") + return null + + var found_anim := EditorUtil.find_active_anim_player( + get_editor_interface().get_base_control(), + _scene_tree + ) + + if found_anim: + return found_anim + + # Get latest edited + return _last_anim_player + + +# Plugin buttons + +const TOOL_REFACTOR := 999 +const TOOL_ANIM_LIBRARY := 1 + +func _add_refactor_option(on_pressed: Callable): + var base_control := get_editor_interface().get_base_control() + if not anim_menu_button: + anim_menu_button = EditorUtil.find_animation_menu_button(base_control) + if not anim_menu_button: + push_error("Could not find Animation menu button. Please report this issue.") + return + + # Remove item up to "Manage Animations..." + var menu_popup := anim_menu_button.get_popup() + var items := [] + var count := menu_popup.item_count - 1 + + while count >= 0 and menu_popup.get_item_id(count) != TOOL_ANIM_LIBRARY: + if menu_popup.is_item_separator(count): + items.append({}) + else: + items.append({ + "shortcut": menu_popup.get_item_shortcut(count), + "id": menu_popup.get_item_id(count), + "icon": menu_popup.get_item_icon(count) + }) + + menu_popup.remove_item(count) + count -= 1 + + # Add refactor item + menu_popup.add_icon_item( + base_control.get_theme_icon(&"Reload", &"EditorIcons"), + "Refactor", + TOOL_REFACTOR, + ) + + # Re-add items + for i in range(items.size() - 1, -1, -1): + var item: Dictionary = items[i] + + if not item.is_empty(): + menu_popup.add_shortcut(item.shortcut, item.id) + menu_popup.set_item_icon(menu_popup.get_item_index(item.id), item.icon) + else: + menu_popup.add_separator() + + menu_popup.notification(NOTIFICATION_TRANSLATION_CHANGED) + + menu_popup.id_pressed.connect(_on_menu_button_pressed) + + +func _remove_refactor_option(): + if not anim_menu_button: + return + + var base_control := get_editor_interface().get_base_control() + + var menu_popup := anim_menu_button.get_popup() + menu_popup.remove_item(menu_popup.get_item_index(TOOL_REFACTOR)) + + menu_popup.id_pressed.disconnect(_on_menu_button_pressed) + + +func _on_menu_button_pressed(id: int): + if id == TOOL_REFACTOR: + refactor_dialogue.popup_centered() + diff --git a/addons/anim_player_refactor/scenes/autocomplete/autocomplete.gd b/addons/anim_player_refactor/scenes/autocomplete/autocomplete.gd new file mode 100644 index 0000000..9fafb2b --- /dev/null +++ b/addons/anim_player_refactor/scenes/autocomplete/autocomplete.gd @@ -0,0 +1,17 @@ +extends CodeEdit + + +func _ready() -> void: + code_completion_enabled = true + add_code_completion_option(CodeEdit.KIND_MEMBER, "Test", "test") + add_code_completion_option(CodeEdit.KIND_MEMBER, "Boo", "boo") + code_completion_prefixes = ["t", "b"] + + code_completion_requested.connect(func(): + add_code_completion_option(CodeEdit.KIND_MEMBER, "Test", "test") + add_code_completion_option(CodeEdit.KIND_MEMBER, "Boo", "boo") + update_code_completion_options(true) + ) + + text_changed.connect(func(): request_code_completion(true)) + diff --git a/addons/anim_player_refactor/scenes/autocomplete/autocomplete_option.gd b/addons/anim_player_refactor/scenes/autocomplete/autocomplete_option.gd new file mode 100644 index 0000000..7bd41c5 --- /dev/null +++ b/addons/anim_player_refactor/scenes/autocomplete/autocomplete_option.gd @@ -0,0 +1,50 @@ +## Autocomplete Class +extends OptionButton + +## LineEdit.text_change_rejected +signal text_change_rejected(rejected_substring: String) +## LineEdit.text_changed +signal text_changed(new_text: String) +## LineEdit.text_submitted +signal text_submitted(new_text: String) + +## LineEdit component +var edit: LineEdit = LineEdit.new() + +@export var get_autocomplete_options: Callable = func(text: String): return [] + +func _ready() -> void: + focus_mode = Control.FOCUS_NONE + edit.custom_minimum_size = size + get_popup().unfocusable = true + + add_child(edit) + edit.reset_size() + + edit.text_change_rejected.connect(func(arg): text_change_rejected.emit(arg)) + edit.text_changed.connect(func(arg): text_changed.emit(arg)) + edit.text_submitted.connect(func(arg): text_submitted.emit(arg)) + + edit.text_changed.connect(_update_options) + + edit.focus_entered.connect(_update_options) + edit.focus_exited.connect(clear) + + get_autocomplete_options = func(text: String): + return [ + "test", + "ashina", + "hello" + ].filter(func(el: String): return el.contains(text)) + +func _update_options(text: String = edit.text): + clear() + var options = get_autocomplete_options.call(text) + + for option in options: + if typeof(option) == TYPE_STRING: + add_item(option) + + show_popup() + + diff --git a/addons/anim_player_refactor/scenes/inspector_button/inspector_button.gd b/addons/anim_player_refactor/scenes/inspector_button/inspector_button.gd new file mode 100644 index 0000000..50b8a17 --- /dev/null +++ b/addons/anim_player_refactor/scenes/inspector_button/inspector_button.gd @@ -0,0 +1,37 @@ +extends EditorInspectorPlugin +signal button_clicked + +var button: Button +var button_text: String + +func _init(text: String) -> void: + button_text = text + +func _can_handle(object: Object) -> bool: + return object is AnimationPlayer + +func _parse_end(object: Object) -> void: + button = Button.new() + button.text = button_text + button.pressed.connect(func(): button_clicked.emit()) + + var margins := MarginContainer.new() + margins.add_theme_constant_override("margin_top", 8) + margins.add_theme_constant_override("margin_left", 16) + margins.add_theme_constant_override("margin_bottom", 8) + margins.add_theme_constant_override("margin_right", 16) + margins.add_child(button) + + var container = VBoxContainer.new() + container.add_theme_constant_override("separation", 0) + container.add_child(HSeparator.new()) + container.add_child(margins) + + var container_margins := MarginContainer.new() + container_margins.add_theme_constant_override("margin_top", 8) + container_margins.add_theme_constant_override("margin_left", 4) + container_margins.add_theme_constant_override("margin_bottom", 8) + container_margins.add_theme_constant_override("margin_right", 4) + container_margins.add_child(container) + + add_custom_control(container_margins) diff --git a/addons/anim_player_refactor/scenes/refactor_dialogue/components/anim_player_tree.gd b/addons/anim_player_refactor/scenes/refactor_dialogue/components/anim_player_tree.gd new file mode 100644 index 0000000..f09d13a --- /dev/null +++ b/addons/anim_player_refactor/scenes/refactor_dialogue/components/anim_player_tree.gd @@ -0,0 +1,176 @@ +@tool +extends Tree + +signal rendered + +const EditInfo := preload("edit_info.gd") + +@export var edittable_items := false + + +func _ready() -> void: + reset_size() + + +func render(editor_plugin: EditorPlugin, anim_player: AnimationPlayer) -> void: + clear() + + # Get paths + var animations = anim_player.get_animation_list() + var root_node := anim_player.get_node(anim_player.root_node) + + var track_paths := {} # Dictionary[NodePath, Dictionary[NodePath, EditInfo]] + + # Get EditInfo data + for anim_name in animations: + var animation := anim_player.get_animation(anim_name) + + for i in animation.get_track_count(): + var path := animation.track_get_path(i) + var type := animation.track_get_type(i) + + var node_path := NodePath(path.get_concatenated_names()) + var property_path := path.get_concatenated_subnames() + var node := root_node.get_node_or_null(node_path) + + var edit_infos: Array[EditInfo] = [] + + if not node_path in track_paths: + track_paths[node_path] = {} + match type: + Animation.TYPE_METHOD: + for j in animation.track_get_key_count(i): + var method_path = NodePath( + ( + path.get_concatenated_names() + + ":" + + animation.method_track_get_name(i, j) + ) + ) + + var edit_info = EditInfo.new( + EditInfo.Type.METHOD_TRACK, method_path, node_path, node, [anim_name] + ) + + edit_infos.append(edit_info) + _: + if not property_path.is_empty(): + var edit_info = EditInfo.new( + EditInfo.Type.VALUE_TRACK, path, node_path, node, [anim_name] + ) + + edit_infos.append(edit_info) + + # Combine + for info in edit_infos: + if not StringName(info.path) in track_paths[node_path]: + track_paths[node_path][StringName(info.path)] = info + else: + for name in info.animation_names: + if name in track_paths[node_path][StringName(info.path)].animation_names: continue + track_paths[node_path][StringName(info.path)].animation_names.append(name) + + # Sort + var paths := track_paths.keys() + paths.sort() + + var tree_root: TreeItem = create_item() + hide_root = true + + # Get icons + var gui := editor_plugin.get_editor_interface().get_base_control() + + # Render + for path in paths: + var node := root_node.get_node_or_null(path) + var icon := gui.get_theme_icon(node.get_class() if node != null else "", "EditorIcons") + + var path_item = create_item(tree_root) + path_item.set_editable(0, edittable_items) + if edittable_items: + path_item.set_text(0, path) + if path.get_concatenated_names() == ".." and node: + path_item.set_suffix(0, "(" + node.name + ")") + else: + path_item.set_text(0, node.name if node else path) + path_item.set_icon(0, icon) + path_item.set_metadata(0, EditInfo.new(EditInfo.Type.NODE, path, path, node, [])) + path_item.add_button(0, gui.get_theme_icon("Edit", "EditorIcons")) + path_item.add_button(0, gui.get_theme_icon("Remove", "EditorIcons")) + + var property_paths: Array = track_paths[path].keys() + property_paths.sort() + + for property_path in property_paths: + var info: EditInfo = track_paths[path][property_path] + var edit_type = EditInfo.Type.VALUE_TRACK + var icon_type = "KeyValue" + var invalid = false + var property := info.path.get_concatenated_subnames() + if node == null: + invalid = true + icon_type = "" + elif node.has_method(StringName(property)): + icon_type = "KeyCall" + elif str(info.path) in node or node.get_indexed(NodePath(property)) != null: + pass + else: + invalid = true + icon_type = "" + + var property_item = create_item(path_item) + property_item.set_editable(0, edittable_items) + property_item.set_text(0, property) + property_item.set_icon(0, gui.get_theme_icon(icon_type, "EditorIcons")) + property_item.set_metadata(0, info) + property_item.add_button(0, gui.get_theme_icon("Edit", "EditorIcons")) + property_item.add_button(0, gui.get_theme_icon("Remove", "EditorIcons")) + + if invalid: + property_item.set_custom_color(0, Color.RED) + property_item.set_tooltip_text(0, "Possibly invalid value: %s" % info.path) + rendered.emit() + + +func set_filter(filter: String): + var item_stack := [] + var visited := [] + + item_stack.append(get_root()) + + # Post-order traversal + while not item_stack.is_empty(): + var current: TreeItem = item_stack[item_stack.size() - 1] + var children = current.get_children() if current else [] + + var children_all_visited := true + var child_visible := false + + for child in children: + children_all_visited = children_all_visited and child in visited + child_visible = child_visible or child.visible + + if children_all_visited: + item_stack.pop_back() + if current: + if current == get_root() or filter.is_empty() or child_visible: + current.visible = true + else: + current.visible = current.get_text(0).to_lower().contains(filter.to_lower()) + visited.append(current) + else: + item_stack += children + + +## Class to cache heirarchy of nodes +## Unused +class TreeNode: + var node: Node + var path: String + var children: Dictionary + var parent: TreeNode + + func debug(level = 0): + print(" - ".repeat(level) + node.name) + for name in children: + children[name].debug(level + 1) diff --git a/addons/anim_player_refactor/scenes/refactor_dialogue/components/edit_info.gd b/addons/anim_player_refactor/scenes/refactor_dialogue/components/edit_info.gd new file mode 100644 index 0000000..c738759 --- /dev/null +++ b/addons/anim_player_refactor/scenes/refactor_dialogue/components/edit_info.gd @@ -0,0 +1,26 @@ +## Data class for storing information on tracks +extends Object + +enum Type { VALUE_TRACK, METHOD_TRACK, NODE } + +## Type of info being edited +var type: Type + +## Full path to property. Same as node_path if type is NODE +var path: NodePath + +## Full path to node +var node_path: NodePath + +## Cached node +var node: Node + +## Animations the track is used in +var animation_names: Array[String] = [] + +func _init(type: Type, path: NodePath, node_path: NodePath, node: Node, animation_names: Array[String]) -> void: + self.type = type + self.path = path + self.node = node + self.node_path = node_path + self.animation_names = animation_names diff --git a/addons/anim_player_refactor/scenes/refactor_dialogue/components/node_select.gd b/addons/anim_player_refactor/scenes/refactor_dialogue/components/node_select.gd new file mode 100644 index 0000000..c3655a9 --- /dev/null +++ b/addons/anim_player_refactor/scenes/refactor_dialogue/components/node_select.gd @@ -0,0 +1,30 @@ +@tool +extends Tree + +var _editor_plugin: EditorPlugin +var _gui: Control + +func init(editor_plugin: EditorPlugin): + _editor_plugin = editor_plugin + _gui = editor_plugin.get_editor_interface().get_base_control() + +func render(anim_player: AnimationPlayer): + clear() + + _create_items(null, anim_player, anim_player.owner) + + +func _create_items(parent: TreeItem, anim_player: AnimationPlayer, node: Node): + var icon := _gui.get_theme_icon(node.get_class(), "EditorIcons") + + var item := create_item(parent) + item.set_text(0, node.name) + item.set_icon(0, icon) + item.set_metadata(0, anim_player.get_path_to(node)) + + if anim_player.get_path_to(node) == anim_player.root_node: + item.select(0) + scroll_to_item(item) + + for child in node.get_children(): + _create_items(item, anim_player, child) diff --git a/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.gd b/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.gd new file mode 100644 index 0000000..6f5f265 --- /dev/null +++ b/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.gd @@ -0,0 +1,235 @@ +@tool +extends AcceptDialog + +const CustomEditorPlugin := preload("res://addons/anim_player_refactor/plugin.gd") +const EditorUtil := preload("res://addons/anim_player_refactor/lib/editor_util.gd") + +const AnimPlayerRefactor = preload("res://addons/anim_player_refactor/lib/anim_player_refactor.gd") +const AnimPlayerTree := preload("components/anim_player_tree.gd") +const EditInfo := preload("components/edit_info.gd") + +const NodeSelect := preload("components/node_select.gd") + +var _editor_plugin: CustomEditorPlugin +var _editor_interface: EditorInterface +var _anim_player_refactor: AnimPlayerRefactor +var _anim_player: AnimationPlayer + +@onready var anim_player_tree: AnimPlayerTree = $%AnimPlayerTree + +@onready var change_root: Button = $%ChangeRoot + +@onready var edit_dialogue: ConfirmationDialog = $%EditDialogue +@onready var edit_dialogue_input: LineEdit = $%EditInput +@onready var edit_dialogue_button: Button = $%EditDialogueButton # Just for pretty icon display +@onready var edit_full_path_toggle: CheckButton = $%EditFullPathToggle +@onready var edit_anim_list: Label = $%EditAnimationList + +@onready var node_select_dialogue: ConfirmationDialog = $%NodeSelectDialogue +@onready var node_select: NodeSelect = $%NodeSelect + +@onready var confirmation_dialogue: ConfirmationDialog = $%ConfirmationDialog + + +var is_full_path: bool: + set(val): edit_full_path_toggle.button_pressed = val + get: return edit_full_path_toggle.button_pressed + + +func init(editor_plugin: CustomEditorPlugin) -> void: + _editor_plugin = editor_plugin + _editor_interface = editor_plugin.get_editor_interface() + _anim_player_refactor = AnimPlayerRefactor.new(_editor_plugin) + node_select.init(_editor_plugin) + + +func _ready() -> void: + wrap_controls = true + about_to_popup.connect(render) + + +func render(): + _anim_player = _editor_plugin.get_anim_player() + + if not _anim_player or not _anim_player is AnimationPlayer: + push_error("AnimationPlayer is null or invalid") + return + + # Render track tree + anim_player_tree.render(_editor_plugin, _anim_player) + + # Render root node button + var root_node: Node = _anim_player.get_node(_anim_player.root_node) + var node_path := str(_anim_player.owner.get_path_to(root_node)) + if node_path == ".": + node_path = _anim_player.owner.name + else: + node_path = _anim_player.owner.name + "/" + node_path + + change_root.text = "%s (Change)" % node_path + change_root.icon = _editor_interface.get_base_control().get_theme_icon( + root_node.get_class(), "EditorIcons" + ) + + +# Rename +enum Action { Rename, Delete } +var _current_info: EditInfo + +func _on_tree_activated(): + _current_info = anim_player_tree.get_selected().get_metadata(0) + _on_action(Action.Rename) + + +func _on_tree_button_clicked(item: TreeItem, column: int, id: int, mouse_button_index: int): + _current_info = item.get_metadata(column) + if id == 0: + _on_action(Action.Rename) + elif id == 1: + _on_action(Action.Delete) + + +func _on_action(action: Action): + if action == Action.Rename: + _render_edit_dialogue() + edit_dialogue.popup_centered() + edit_dialogue_input.grab_focus() + edit_dialogue_input.caret_column = edit_dialogue_input.text.length() + elif action == Action.Delete: + var track_path = _current_info.path + var anim_player = _editor_plugin.get_anim_player() + var node = anim_player\ + .get_node(anim_player.root_node)\ + .get_node_or_null(_current_info.path) + + if node: + track_path = node.name + var property := _current_info.path.get_concatenated_subnames() + if not property.is_empty(): + track_path += ":" + property + + var msg = 'Delete all tracks with the path "%s"?' % track_path + + if _current_info.type == EditInfo.Type.NODE: + msg = 'Delete tracks belonging to the node "%s"?' % track_path + + _show_confirmation(msg, _remove) + + +func _render_edit_dialogue(): + var info := _current_info + + if info.type == EditInfo.Type.METHOD_TRACK: + is_full_path = false + edit_full_path_toggle.disabled = true + edit_full_path_toggle.visible = false + else: + edit_full_path_toggle.disabled = false + edit_full_path_toggle.visible = true + + if is_full_path: + edit_dialogue_input.text = info.path + else: + if info.type == EditInfo.Type.NODE: + edit_dialogue.title = "Rename node" + edit_dialogue_input.text = info.path.get_name(info.path.get_name_count() - 1) + elif info.type == EditInfo.Type.VALUE_TRACK: + edit_dialogue.title = "Rename Value" + edit_dialogue_input.text = info.path.get_concatenated_subnames() + elif info.type == EditInfo.Type.METHOD_TRACK: + edit_dialogue.title = "Rename method" + edit_dialogue_input.text = info.path.get_concatenated_subnames() + edit_dialogue_button.text = info.node_path + if str(info.node_path) in [".", ".."] and info.node: + # Show name for relatives paths + edit_dialogue_button.text = info.node.name + if info.node: + # Show icon + edit_dialogue_button.icon = _editor_interface.get_base_control().get_theme_icon( + info.node.get_class(), "EditorIcons" + ) + edit_anim_list.text = "" + for name in _current_info.animation_names: + edit_anim_list.text += " • " + name + "\n" + + +## Toggle full path and re-render edit dialogue +func _on_full_path_toggled(pressed: bool): + _render_edit_dialogue() + + +## Callback on rename +func _on_rename_confirmed(_arg0 = null): + var new := edit_dialogue_input.text + + edit_dialogue.hide() + if not _anim_player or not _anim_player is AnimationPlayer: + push_error("AnimationPlayer is null or invalid") + return + + if new.is_empty(): + return + + var info: EditInfo = _current_info + if info.type == EditInfo.Type.NODE: + var old := info.path + var new_path = new + if not is_full_path: + new_path = "" + for i in range(info.path.get_name_count() - 1): + new_path += info.path.get_name(i) + "/" + new_path += new + _anim_player_refactor.rename_node_path(_anim_player, old, NodePath(new)) + elif info.type == EditInfo.Type.VALUE_TRACK: + var old_path := info.path + var new_path := NodePath(new) + if not is_full_path: + new_path = info.node_path.get_concatenated_names() + ":" + new + _anim_player_refactor.rename_track_path(_anim_player, old_path, new_path) + elif info.type == EditInfo.Type.METHOD_TRACK: + var old_method := info.path + var new_method := NodePath( + info.path.get_concatenated_names() + ":" + new + ) + _anim_player_refactor.rename_method(_anim_player, old_method, new_method) + await get_tree().create_timer(0.1).timeout + render() + + +func _remove(): + var info: EditInfo = _current_info + match info.type: + EditInfo.Type.NODE: + _anim_player_refactor.remove_node_path(_anim_player, info.node_path) + EditInfo.Type.VALUE_TRACK: + _anim_player_refactor.remove_track_path(_anim_player, info.path) + EditInfo.Type.METHOD_TRACK: + _anim_player_refactor.remove_method(_anim_player, info.path) + await get_tree().create_timer(0.1).timeout + render() + + +# Change root +func _on_change_root_pressed(): + node_select_dialogue.popup_centered() + node_select.render(_editor_plugin.get_anim_player()) + + +func _on_node_select_confirmed(): + var path: NodePath = node_select.get_selected().get_metadata(0) + + _anim_player_refactor.change_root(_editor_plugin.get_anim_player(), path) + + await get_tree().create_timer(0.1).timeout + render() + + +# Confirmation +func _show_confirmation(text: String, on_confirmed: Callable): + for c in confirmation_dialogue.confirmed.get_connections(): + confirmation_dialogue.confirmed.disconnect(c.callable) + + confirmation_dialogue.confirmed.connect(on_confirmed, CONNECT_ONE_SHOT) + confirmation_dialogue.popup_centered() + confirmation_dialogue.dialog_text = text + diff --git a/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.tscn b/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.tscn new file mode 100644 index 0000000..cc1dae8 --- /dev/null +++ b/addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.tscn @@ -0,0 +1,157 @@ +[gd_scene load_steps=4 format=3 uid="uid://cyfxysds8uhnx"] + +[ext_resource type="Script" path="res://addons/anim_player_refactor/scenes/refactor_dialogue/refactor_dialogue.gd" id="1_nkqdl"] +[ext_resource type="Script" path="res://addons/anim_player_refactor/scenes/refactor_dialogue/components/anim_player_tree.gd" id="2_7pqfs"] +[ext_resource type="Script" path="res://addons/anim_player_refactor/scenes/refactor_dialogue/components/node_select.gd" id="3_87x4i"] + +[node name="RefactorDialogue" type="AcceptDialog"] +title = "Refactor Animations" +size = Vector2i(400, 599) +ok_button_text = "Close" +script = ExtResource("1_nkqdl") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +offset_left = 8.0 +offset_top = 8.0 +offset_right = 392.0 +offset_bottom = 550.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 8 + +[node name="TreeContainer" type="VBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/TreeContainer"] +layout_mode = 2 +text = "Properties:" + +[node name="FilterInput" type="LineEdit" parent="VBoxContainer/TreeContainer"] +layout_mode = 2 +placeholder_text = "Filter..." +caret_blink = true +caret_blink_interval = 0.5 + +[node name="AnimPlayerTree" type="Tree" parent="VBoxContainer/TreeContainer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(300, 400) +layout_mode = 2 +size_flags_vertical = 3 +hide_root = true +scroll_horizontal_enabled = false +script = ExtResource("2_7pqfs") + +[node name="RootNodeContainer" type="VBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/RootNodeContainer"] +layout_mode = 2 +text = "Root Node" + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/RootNodeContainer"] +layout_mode = 2 + +[node name="ChangeRoot" type="Button" parent="VBoxContainer/RootNodeContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "Change Root" + +[node name="EditDialogue" type="ConfirmationDialog" parent="."] +unique_name_in_owner = true +title = "Renaming" +position = Vector2i(0, 36) +size = Vector2i(230, 239) + +[node name="VBoxContainer" type="VBoxContainer" parent="EditDialogue"] +offset_left = 8.0 +offset_top = 8.0 +offset_right = 222.0 +offset_bottom = 190.0 + +[node name="HBoxContainer" type="HBoxContainer" parent="EditDialogue/VBoxContainer"] +layout_mode = 2 + +[node name="EditDialogueButton" type="Button" parent="EditDialogue/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +focus_mode = 0 + +[node name="EditInput" type="LineEdit" parent="EditDialogue/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 0 + +[node name="HBoxContainer2" type="HBoxContainer" parent="EditDialogue/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="EditDialogue/VBoxContainer/HBoxContainer2"] +layout_mode = 2 +text = "Used in:" + +[node name="EditFullPathToggle" type="CheckButton" parent="EditDialogue/VBoxContainer/HBoxContainer2"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 10 +text = "Edit full path" + +[node name="MarginContainer" type="MarginContainer" parent="EditDialogue/VBoxContainer"] +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 + +[node name="ColorRect" type="ColorRect" parent="EditDialogue/VBoxContainer/MarginContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +color = Color(0, 0, 0, 0.211765) + +[node name="ScrollContainer" type="ScrollContainer" parent="EditDialogue/VBoxContainer/MarginContainer"] +layout_mode = 2 +size_flags_vertical = 3 +horizontal_scroll_mode = 0 + +[node name="MarginContainer" type="MarginContainer" parent="EditDialogue/VBoxContainer/MarginContainer/ScrollContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 2 +theme_override_constants/margin_top = 2 +theme_override_constants/margin_right = 2 +theme_override_constants/margin_bottom = 2 + +[node name="EditAnimationList" type="Label" parent="EditDialogue/VBoxContainer/MarginContainer/ScrollContainer/MarginContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Test +Test 2" + +[node name="NodeSelectDialogue" type="ConfirmationDialog" parent="."] +unique_name_in_owner = true +title = "Select a node..." +size = Vector2i(616, 557) +ok_button_text = "Change" + +[node name="NodeSelect" type="Tree" parent="NodeSelectDialogue"] +unique_name_in_owner = true +custom_minimum_size = Vector2(600, 500) +offset_left = 8.0 +offset_top = 8.0 +offset_right = 608.0 +offset_bottom = 508.0 +scroll_horizontal_enabled = false +script = ExtResource("3_87x4i") + +[node name="ConfirmationDialog" type="ConfirmationDialog" parent="."] +unique_name_in_owner = true +size = Vector2i(300, 200) +ok_button_text = "Delete" +dialog_autowrap = true + +[connection signal="about_to_popup" from="." to="VBoxContainer/TreeContainer/FilterInput" method="clear"] +[connection signal="text_changed" from="VBoxContainer/TreeContainer/FilterInput" to="VBoxContainer/TreeContainer/AnimPlayerTree" method="set_filter"] +[connection signal="button_clicked" from="VBoxContainer/TreeContainer/AnimPlayerTree" to="." method="_on_tree_button_clicked"] +[connection signal="item_activated" from="VBoxContainer/TreeContainer/AnimPlayerTree" to="." method="_on_tree_activated"] +[connection signal="rendered" from="VBoxContainer/TreeContainer/AnimPlayerTree" to="VBoxContainer/TreeContainer/FilterInput" method="clear"] +[connection signal="pressed" from="VBoxContainer/RootNodeContainer/HBoxContainer/ChangeRoot" to="." method="_on_change_root_pressed"] +[connection signal="confirmed" from="EditDialogue" to="." method="_on_rename_confirmed"] +[connection signal="text_submitted" from="EditDialogue/VBoxContainer/EditInput" to="." method="_on_rename_confirmed"] +[connection signal="toggled" from="EditDialogue/VBoxContainer/HBoxContainer2/EditFullPathToggle" to="." method="_on_full_path_toggled"] +[connection signal="confirmed" from="NodeSelectDialogue" to="." method="_on_node_select_confirmed"] diff --git a/entities/Bee.tscn b/entities/Bee.tscn index c574285..a24716c 100644 --- a/entities/Bee.tscn +++ b/entities/Bee.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=14 format=3 uid="uid://deek6uv574xas"] +[gd_scene load_steps=18 format=3 uid="uid://deek6uv574xas"] [ext_resource type="Script" path="res://entities/scripts/bee.gd" id="1_pnu7x"] [ext_resource type="Script" path="res://entities/scripts/finite_state_machine.gd" id="1_t3s5d"] @@ -8,23 +8,25 @@ [ext_resource type="Script" path="res://entities/bee/states/bee_travelling.gd" id="5_qtx0r"] [ext_resource type="Script" path="res://entities/bee/states/bee_sleeping.gd" id="7_6qlbu"] [ext_resource type="Script" path="res://entities/bee/states/bee_returning.gd" id="8_dptvu"] +[ext_resource type="Texture2D" uid="uid://ch3qalaaky8ng" path="res://resources/textures/bee_body.png" id="10_yi42o"] +[ext_resource type="Texture2D" uid="uid://bsskcrayofs8n" path="res://resources/textures/bee_wings.png" id="11_utbwk"] [sub_resource type="Animation" id="Animation_iys4n"] resource_name = "Flying" length = 5.0 loop_mode = 1 -step = 0.5 +step = 0.25 tracks/0/type = "value" tracks/0/imported = false tracks/0/enabled = true -tracks/0/path = NodePath("Polygon2D:position") +tracks/0/path = NodePath("BeeBody:position") tracks/0/interp = 1 tracks/0/loop_wrap = true tracks/0/keys = { -"times": PackedFloat32Array(0, 1.5, 2.5, 3.5, 5), -"transitions": PackedFloat32Array(1, 1, 1, 1, 1), +"times": PackedFloat32Array(0, 1, 2, 3, 4, 5), +"transitions": PackedFloat32Array(-2, -2, -2, -2, -2, -2), "update": 0, -"values": [Vector2(0, 0), Vector2(0, 10), Vector2(0, -10), Vector2(0, 10), Vector2(0, 0)] +"values": [Vector2(0, 0), Vector2(0, 10), Vector2(0, 5), Vector2(0, -5), Vector2(0, 10), Vector2(0, 0)] } [sub_resource type="Animation" id="Animation_t75ra"] @@ -32,18 +34,6 @@ resource_name = "Idle" [sub_resource type="Animation" id="Animation_0encb"] length = 0.001 -tracks/0/type = "value" -tracks/0/imported = false -tracks/0/enabled = true -tracks/0/path = NodePath("Polygon2D:position") -tracks/0/interp = 1 -tracks/0/loop_wrap = true -tracks/0/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 0, -"values": [Vector2(0, 0)] -} [sub_resource type="AnimationLibrary" id="AnimationLibrary_m27po"] _data = { @@ -52,6 +42,29 @@ _data = { "RESET": SubResource("Animation_0encb") } +[sub_resource type="Animation" id="Animation_muxdj"] +resource_name = "Fly" +length = 0.5 +loop_mode = 1 +step = 0.05 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("BeeBody/BeeWings:scale") +tracks/0/interp = 1 +tracks/0/loop_wrap = false +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45), +"transitions": PackedFloat32Array(-2, -2, -2, -2, -2, -2, -2, -2, -2, -2), +"update": 0, +"values": [Vector2(1, 1), Vector2(1, 0.1), Vector2(1, 1), Vector2(1, 0.1), Vector2(1, 1), Vector2(1, 0.1), Vector2(1, 1), Vector2(1, 0.1), Vector2(1, 1), Vector2(1, 0.1)] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_yvewf"] +_data = { +"Fly": SubResource("Animation_muxdj") +} + [sub_resource type="CircleShape2D" id="CircleShape2D_86nxf"] radius = 22.0907 @@ -60,11 +73,16 @@ z_index = 99 collision_mask = 0 script = ExtResource("1_pnu7x") -[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +[node name="BeePositionAnimation" type="AnimationPlayer" parent="."] libraries = { "": SubResource("AnimationLibrary_m27po") } +[node name="WingAnimation" type="AnimationPlayer" parent="."] +libraries = { +"": SubResource("AnimationLibrary_yvewf") +} + [node name="CollisionShape2D" type="CollisionShape2D" parent="."] light_mask = 0 shape = SubResource("CircleShape2D_86nxf") @@ -97,52 +115,21 @@ script = ExtResource("7_6qlbu") [node name="Returning" type="Node" parent="StateMachine"] script = ExtResource("8_dptvu") -[node name="Polygon2D" type="Polygon2D" parent="."] -scale = Vector2(0.152885, 0.15978) -color = Color(1, 1, 0.0745098, 1) -polygon = PackedVector2Array(-18, -11, -6, -21, 17, -19, 23, 1, 3, 12, -18, 7) +[node name="BeeBody" type="Sprite2D" parent="."] +scale = Vector2(0.1, 0.1) +texture = ExtResource("10_yi42o") -[node name="CPUParticles2D" type="CPUParticles2D" parent="Polygon2D"] -position = Vector2(0, 2.46663) +[node name="BeeWings" type="Sprite2D" parent="BeeBody"] +scale = Vector2(1, 0.1) +texture = ExtResource("11_utbwk") + +[node name="CPUParticles2D" type="CPUParticles2D" parent="BeeBody"] +position = Vector2(0, 3.94118) +scale = Vector2(1.52885, 1.5978) lifetime_randomness = 0.33 gravity = Vector2(0, 0) linear_accel_min = 5.0 linear_accel_max = 10.0 -[node name="Polygon2D" type="Polygon2D" parent="Polygon2D"] -position = Vector2(-13.0817, -6.25862) -scale = Vector2(0.5, 0.5) -color = Color(0, 0, 0, 1) -offset = Vector2(-9.53674e-07, -9.53674e-07) -polygon = PackedVector2Array(32.7043, -12.5172, 39.2451, -18.7759, 45.786, -12.5172, 39.2451, -6.25862) - -[node name="Polygon2D5" type="Polygon2D" parent="Polygon2D"] -position = Vector2(-13.0817, 18.7759) -scale = Vector2(0.5, 0.5) -color = Color(0, 0, 0, 1) -offset = Vector2(-9.53674e-07, -9.53674e-07) -polygon = PackedVector2Array(32.7043, -12.5172, 39.2451, -18.7759, 45.786, -12.5172, 39.2451, -6.25862) - -[node name="Polygon2D4" type="Polygon2D" parent="Polygon2D"] -position = Vector2(-26.1634, 18.7759) -scale = Vector2(0.5, 0.5) -color = Color(0, 0, 0, 1) -offset = Vector2(-9.53674e-07, -9.53674e-07) -polygon = PackedVector2Array(32.7043, -12.5172, 39.2451, -18.7759, 45.786, -12.5172, 39.2451, -6.25862) - -[node name="Polygon2D3" type="Polygon2D" parent="Polygon2D"] -position = Vector2(-26.1634, -6.25862) -scale = Vector2(0.5, 0.5) -color = Color(0.30032, 0.200213, 0.0690995, 1) -offset = Vector2(-9.53674e-07, -9.53674e-07) -polygon = PackedVector2Array(32.7043, -12.5172, 39.2451, -18.7759, 78.4903, 12.5172, 65.4086, 25.0345) - -[node name="Polygon2D2" type="Polygon2D" parent="Polygon2D"] -position = Vector2(-6.54086, 0) -scale = Vector2(0.5, 0.5) -color = Color(0, 0, 0, 1) -offset = Vector2(-9.53674e-07, -9.53674e-07) -polygon = PackedVector2Array(32.7043, -12.5172, 39.2451, -18.7759, 45.786, -12.5172, 39.2451, -6.25862) - [connection signal="area_entered" from="HitBox" to="HitBox" method="_on_area_entered"] [connection signal="area_exited" from="HitBox" to="HitBox" method="_on_area_exited"] diff --git a/entities/bee/states/bee_gather.gd b/entities/bee/states/bee_gather.gd index 214a0c9..7a13210 100644 --- a/entities/bee/states/bee_gather.gd +++ b/entities/bee/states/bee_gather.gd @@ -6,10 +6,17 @@ class_name BeeGathering var time_at_patch : float = 0.0 +var target : Vector2 = Vector2.ZERO + func enter(_msg := {}): bee.just_gathering = true Log.pr("Gathering now...") + + randomize() + target = bee.get_global_position() + Vector2(randi_range(-100, 100), randi_range(-100, 100)) + + #animator.play("Idle") #if !bee.in_range_of_flowers: @@ -30,3 +37,9 @@ func update(_delta : float): else: state_transition.emit(self, "Idle") +func physics_update(delta : float) -> void: + if target: + if bee.position.distance_to(target) > 2: + bee.velocity = (target - bee.position).normalized() * bee.speed / 2 * delta + bee.move_and_collide(bee.velocity) + bee.look_at(target) diff --git a/entities/bee/states/bee_idle.gd b/entities/bee/states/bee_idle.gd index d901ef0..e5318c5 100644 --- a/entities/bee/states/bee_idle.gd +++ b/entities/bee/states/bee_idle.gd @@ -5,12 +5,25 @@ class_name BeeIdle var idle_time : float = 0.0 +var target : Vector2 = Vector2.ZERO + +var acquire_new_target : bool = false + + func enter(_msg := {}): - #animator.play("Idle") - pass + if acquire_new_target: + target = bee.get_global_position() + Vector2(randi_range(-60, 60), randi_range(-60, 60)) + +func exit(): + acquire_new_target = true func update(delta : float): + if target == Vector2.ZERO: + randomize() + target = bee.get_global_position() + Vector2(randi_range(-60, 60), randi_range(-60, 60)) + + idle_time += delta if idle_time > 2.0: @@ -18,6 +31,14 @@ func update(delta : float): idle_time = 0.0 pass +func physics_update(delta : float) -> void: + if target: + if bee.position.distance_to(target) > 3: + + bee.velocity = (target - bee.position).normalized() * bee.speed / 2 * delta + bee.move_and_collide(bee.velocity) + bee.look_at(target) + func find_something_to_do(): if bee.nectar > 0: Log.pr("I have pollen, time to move..") diff --git a/entities/bee/states/bee_returning.gd b/entities/bee/states/bee_returning.gd index 4e06551..0c166ce 100644 --- a/entities/bee/states/bee_returning.gd +++ b/entities/bee/states/bee_returning.gd @@ -6,10 +6,13 @@ class_name BeeReturning func enter(_msg := {}): Log.pr("Returning to the hive") - bee.animation_player.play("Flying") + bee.bee_position_animation.play("Flying") -func update(delta : float) -> void: +func update(_delta : float) -> void: + pass + +func physics_update(delta : float) -> void: if target: if bee.position.distance_to(target.position) > 3: @@ -20,4 +23,4 @@ func update(delta : float) -> void: else: # Deposit the nectar and return it idle state bee.deposit_nectar() - state_transition.emit(self, "Idle") + state_transition.emit(self, "Idle") \ No newline at end of file diff --git a/entities/bee/states/bee_sleeping.gd b/entities/bee/states/bee_sleeping.gd index 044fc4d..64e0783 100644 --- a/entities/bee/states/bee_sleeping.gd +++ b/entities/bee/states/bee_sleeping.gd @@ -1,12 +1,15 @@ extends State class_name BeeSleeping -@onready var bee = get_parent().get_parent() as Bee # I think this is bad but I dont care it works +@onready var bee : Bee = get_parent().get_parent() as Bee # I think this is bad but I dont care it works var time_at_patch : float = 0.0 -func enter(_msg := {}): +func enter(_msg := {}) -> void: pass -func update(_delta): +func update(_delta) -> void: + pass + +func physics_update(_delta : float) -> void: pass \ No newline at end of file diff --git a/entities/bee/states/bee_travelling.gd b/entities/bee/states/bee_travelling.gd index 491d29a..483d1e3 100644 --- a/entities/bee/states/bee_travelling.gd +++ b/entities/bee/states/bee_travelling.gd @@ -6,6 +6,7 @@ class_name BeeTravelling @onready var bee = get_parent().get_parent() as Bee # I think this is bad but I dont care it works var return_to_hive : bool = false +var moving_to : Vector2 = Vector2(0,0) func enter(_msg := {}): return_to_hive = false @@ -23,20 +24,22 @@ func enter(_msg := {}): # If we have no target, we are returning to the hive if !target: return_to_hive = true + else: + moving_to = target.get_global_position() - bee.animation_player.play("Flying") + bee.bee_position_animation.play("Flying") -func update(delta : float) -> void: - +func update(_delta : float) -> void: if return_to_hive: state_transition.emit(self, "Returning") return +func physics_update(delta : float) -> void: if target: if bee.position.distance_to(target.position) > 3: - bee.velocity = (target.get_global_position() - bee.position).normalized() * bee.speed * delta + bee.velocity = (moving_to - bee.position).normalized() * bee.speed * delta bee.move_and_collide(bee.velocity) bee.look_at(target.position) diff --git a/entities/scripts/bee.gd b/entities/scripts/bee.gd index a9970ae..810bf78 100644 --- a/entities/scripts/bee.gd +++ b/entities/scripts/bee.gd @@ -3,7 +3,8 @@ class_name Bee @onready var fsm = $StateMachine as FiniteStateMachine @onready var drone_manager = get_tree().get_first_node_in_group("dronemanager") as DroneManager -@onready var animation_player = $AnimationPlayer as AnimationPlayer +@onready var bee_position_animation = $BeePositionAnimation as AnimationPlayer +@onready var bee_wing_animation = $WingAnimation as AnimationPlayer @export var nectar : int = 0 @export var speed : int = 30 @@ -16,6 +17,7 @@ var just_gathering : bool = false # Used to check if the bee has just been gathe func _ready(): speed = randi_range(35,55) # Randomise the bee speed a bit + bee_wing_animation.play("Fly") func get_current_director(): return drone_manager.get_director(latest_target_director) @@ -51,10 +53,11 @@ func get_next_target(): return null func deposit_nectar(): - GameState.add_nectar() - nectar = 0 - latest_target_director = 0 - just_gathering = false + if nectar > 0: + GameState.add_nectar() + nectar = 0 + latest_target_director = 0 + just_gathering = false func die(): # Move to the death state diff --git a/entities/scripts/finite_state_machine.gd b/entities/scripts/finite_state_machine.gd index 514105f..173f404 100644 --- a/entities/scripts/finite_state_machine.gd +++ b/entities/scripts/finite_state_machine.gd @@ -24,6 +24,10 @@ func _process(delta): if current_state: current_state.update(delta) +func _physics_process(delta): + if current_state: + current_state.physics_update(delta) + # Use force_change_state cautiously, it immediately switches to a state regardless of any transitions. # This is used to force us into a 'death state' when killed func force_change_state(new_state : String): diff --git a/project.godot b/project.godot index afae17e..1e61079 100644 --- a/project.godot +++ b/project.godot @@ -23,6 +23,7 @@ GameState="*res://utility/game_state.gd" window/size/viewport_width=1280 window/size/viewport_height=720 +window/vsync/vsync_mode=0 [editor_plugins] @@ -37,3 +38,7 @@ theme/custom="res://resources/theme/game_theme.tres" 2d_physics/layer_1="bees" 2d_physics/layer_2="hazards" 2d_physics/layer_3="flowers" + +[physics] + +2d/run_on_separate_thread=true diff --git a/resources/concept art/scene design.kra b/resources/concept art/scene design.kra index 6f61be6..4e01554 100644 Binary files a/resources/concept art/scene design.kra and b/resources/concept art/scene design.kra differ diff --git a/resources/concept art/scene design.kra~ b/resources/concept art/scene design.kra~ index 76a9afe..28e5ab9 100644 Binary files a/resources/concept art/scene design.kra~ and b/resources/concept art/scene design.kra~ differ diff --git a/resources/textures/bee.png b/resources/textures/bee.png new file mode 100644 index 0000000..e4608bb Binary files /dev/null and b/resources/textures/bee.png differ diff --git a/resources/textures/bee.png.import b/resources/textures/bee.png.import new file mode 100644 index 0000000..bb87c50 --- /dev/null +++ b/resources/textures/bee.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bmy5d0vl0l2bj" +path="res://.godot/imported/bee.png-e6e2f710a80ac28411d91cb428bfbb46.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/textures/bee.png" +dest_files=["res://.godot/imported/bee.png-e6e2f710a80ac28411d91cb428bfbb46.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/resources/textures/bee.png~ b/resources/textures/bee.png~ new file mode 100644 index 0000000..e731489 Binary files /dev/null and b/resources/textures/bee.png~ differ diff --git a/resources/textures/bee_body.png b/resources/textures/bee_body.png new file mode 100644 index 0000000..e9d1a12 Binary files /dev/null and b/resources/textures/bee_body.png differ diff --git a/resources/textures/bee_body.png.import b/resources/textures/bee_body.png.import new file mode 100644 index 0000000..1a1f1c6 --- /dev/null +++ b/resources/textures/bee_body.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ch3qalaaky8ng" +path="res://.godot/imported/bee_body.png-a901a812be76582d296d2da284fea011.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/textures/bee_body.png" +dest_files=["res://.godot/imported/bee_body.png-a901a812be76582d296d2da284fea011.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/resources/textures/bee_wings.png b/resources/textures/bee_wings.png new file mode 100644 index 0000000..627abbf Binary files /dev/null and b/resources/textures/bee_wings.png differ diff --git a/resources/textures/bee_wings.png.import b/resources/textures/bee_wings.png.import new file mode 100644 index 0000000..2da49a9 --- /dev/null +++ b/resources/textures/bee_wings.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bsskcrayofs8n" +path="res://.godot/imported/bee_wings.png-40259da7bb67a8bea5d5368d118220ff.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/textures/bee_wings.png" +dest_files=["res://.godot/imported/bee_wings.png-40259da7bb67a8bea5d5368d118220ff.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/scenes/scripts/bee_spawner.gd b/scenes/scripts/bee_spawner.gd index 60c7f4e..a92121e 100644 --- a/scenes/scripts/bee_spawner.gd +++ b/scenes/scripts/bee_spawner.gd @@ -8,7 +8,7 @@ var bee = preload("res://entities/Bee.tscn") @onready var small_bee_sound = get_node("BeeSound") var bee_count = 0 -var max_bees = 10 +var max_bees = 100 var spawn_interval = 0.5 var spawn_timer = 0.0 var bee_sound_timer = 0.0