diff --git a/doc/classes/AnimationNode.xml b/doc/classes/AnimationNode.xml index 389c2c790ba..80f9fd08039 100644 --- a/doc/classes/AnimationNode.xml +++ b/doc/classes/AnimationNode.xml @@ -61,6 +61,7 @@ + When inheriting from [AnimationRootNode], implement this virtual method to run some code when this node is processed. The [param time] parameter is a relative delta, unless [param seek] is [code]true[/code], in which case it is absolute. Here, call the [method blend_input], [method blend_node] or [method blend_animation] functions. You can also use [method get_parameter] and [method set_parameter] to modify local memory. @@ -97,6 +98,7 @@ + Blend an input. This is only useful for nodes created for an [AnimationNodeBlendTree]. The [param time] parameter is a relative delta, unless [param seek] is [code]true[/code], in which case it is absolute. A filter mode may be optionally passed (see [enum FilterAction] for options). @@ -111,6 +113,7 @@ + Blend another animation node (in case this node contains children animation nodes). This function is only useful if you inherit from [AnimationRootNode] instead, else editors will not display your node for addition. diff --git a/doc/classes/AnimationNodeStateMachine.xml b/doc/classes/AnimationNodeStateMachine.xml index 2587cea7292..13f88f79ef6 100644 --- a/doc/classes/AnimationNodeStateMachine.xml +++ b/doc/classes/AnimationNodeStateMachine.xml @@ -165,5 +165,23 @@ If [code]true[/code], allows teleport to the self state with [method AnimationNodeStateMachinePlayback.travel]. When the reset option is enabled in [method AnimationNodeStateMachinePlayback.travel], the animation is restarted. If [code]false[/code], nothing happens on the teleportation to the self state. + + If [code]true[/code], treat the cross-fade to the start and end nodes as a blend with the RESET animation. + In most cases, when additional cross-fades are performed in the parent [AnimationNode] of the state machine, setting this property to [code]false[/code] and matching the cross-fade time of the parent [AnimationNode] and the state machine's start node and end node gives good results. + + + This property can define the process of transitions for different use cases. See also [enum AnimationNodeStateMachine.StateMachineType]. + + + + Seeking to the beginning is treated as playing from the start state. Transition to the end state is treated as exiting the state machine. + + + Seeking to the beginning is treated as seeking to the beginning of the animation in the current state. Transition to the end state, or the absence of transitions in each state, is treated as exiting the state machine. + + + This is a grouped state machine that can be controlled from a parent state machine. It does not work on standalone. There must be a state machine with [member state_machine_type] of [constant STATE_MACHINE_TYPE_ROOT] or [constant STATE_MACHINE_TYPE_NESTED] in the parent or ancestor. + + diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp index 9632670658d..7a07b34375c 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -50,6 +50,7 @@ #include "scene/gui/tree.h" #include "scene/main/viewport.h" #include "scene/main/window.h" +#include "scene/scene_string_names.h" bool AnimationNodeStateMachineEditor::can_edit(const Ref &p_node) { Ref ansm = p_node; @@ -67,7 +68,6 @@ void AnimationNodeStateMachineEditor::edit(const Ref &p_node) { selected_transition_from = StringName(); selected_transition_to = StringName(); selected_transition_index = -1; - selected_multi_transition = TransitionLine(); selected_node = StringName(); selected_nodes.clear(); _update_mode(); @@ -78,14 +78,60 @@ void AnimationNodeStateMachineEditor::edit(const Ref &p_node) { tool_connect->set_disabled(read_only); } +String AnimationNodeStateMachineEditor::_get_root_playback_path(String &r_node_directory) { + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + Vector edited_path = AnimationTreeEditor::get_singleton()->get_edited_path(); + + String base_path; + Vector node_directory_path; + + bool is_playable_anodesm_found = false; + + if (edited_path.size()) { + while (!is_playable_anodesm_found) { + base_path = String("/").join(edited_path); + Ref anodesm = !edited_path.size() ? tree->get_tree_root() : tree->get_tree_root()->find_node_by_path(base_path); + if (!anodesm.is_valid()) { + break; + } else { + if (anodesm->get_state_machine_type() != AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + is_playable_anodesm_found = true; + } else { + int idx = edited_path.size() - 1; + node_directory_path.push_back(edited_path[idx]); + edited_path.remove_at(idx); + } + } + } + } + + if (is_playable_anodesm_found) { + // Return Root/Nested state machine playback. + node_directory_path.reverse(); + r_node_directory = String("/").join(node_directory_path); + if (node_directory_path.size()) { + r_node_directory += "/"; + } + base_path = !edited_path.size() ? String(SceneStringNames::get_singleton()->parameters_base_path) + "playback" : String(SceneStringNames::get_singleton()->parameters_base_path) + base_path + "/playback"; + } else { + // Hmmm, we have to return Grouped state machine playback... + // It will give the user the error that Root/Nested state machine should be retrieved, that would be kind :-) + r_node_directory = String(); + base_path = AnimationTreeEditor::get_singleton()->get_base_path() + "playback"; + } + + return base_path; +} + void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref &p_event) { AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); if (!tree) { return; } - Ref playback = tree->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); - if (playback.is_null()) { + String node_directory; + Ref playback = tree->get(_get_root_playback_path(node_directory)); + if (!playback.is_valid()) { return; } @@ -99,16 +145,6 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Refis_pressed() && k.is_valid() && k->is_pressed() && k->is_ctrl_pressed() && !k->is_shift_pressed() && k->get_keycode() == Key::G && !k->is_echo()) { - _group_selected_nodes(); - } - - // Ungroup state machine - if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->is_ctrl_pressed() && k->is_shift_pressed() && k->get_keycode() == Key::G && !k->is_echo()) { - _ungroup_selected_nodes(); - } - Ref mb = p_event; // Add new node @@ -124,17 +160,16 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref= 0; i--) { //inverse to draw order if (node_rects[i].play.has_point(mb->get_position())) { //edit name if (play_mode->get_selected() == 1 || !playback->is_playing()) { - //start - playback->start(node_rects[i].node_name); + // Start + playback->start(node_directory + String(node_rects[i].node_name)); } else { - //travel - playback->travel(node_rects[i].node_name); + // Travel + playback->travel(node_directory + String(node_rects[i].node_name)); } state_machine_draw->queue_redraw(); return; @@ -213,26 +248,11 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref tr = state_machine->get_transition(closest); - EditorNode::get_singleton()->push_item(tr.ptr(), "", true); - - if (!transition_lines[closest].multi_transitions.is_empty()) { - selected_transition_index = -1; - selected_multi_transition = transition_lines[closest]; - - Ref multi; - multi.instantiate(); - multi->add_transition(selected_transition_from, selected_transition_to, tr); - - for (int i = 0; i < transition_lines[closest].multi_transitions.size(); i++) { - int index = transition_lines[closest].multi_transitions[i].transition_index; - - Ref transition = state_machine->get_transition(index); - StringName from = transition_lines[closest].multi_transitions[i].from_node; - StringName to = transition_lines[closest].multi_transitions[i].to_node; - - multi->add_transition(from, to, transition); - } - EditorNode::get_singleton()->push_item(multi.ptr(), "", true); + if (!state_machine->is_transition_across_group(closest)) { + EditorNode::get_singleton()->push_item(tr.ptr(), "", true); + } else { + EditorNode::get_singleton()->push_item(tr.ptr(), "", true); + EditorNode::get_singleton()->push_item(nullptr, "", true); } } @@ -296,11 +316,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Refshow_warning(TTR("Transition exists!")); connecting = false; } else { - if (anodesm.is_valid() || end_node.is_valid()) { - _open_connect_menu(mb->get_position()); - } else { - _add_transition(); - } + _add_transition(); } } else { _open_menu(mb->get_position()); @@ -481,12 +497,6 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref " + to; - - for (int i = 0; i < transition_lines[closest].multi_transitions.size(); i++) { - from = String(transition_lines[closest].multi_transitions[i].from_node); - to = String(transition_lines[closest].multi_transitions[i].to_node); - tooltip += "\n" + from + " -> " + to; - } state_machine_draw->set_tooltip_text(tooltip); } else { state_machine_draw->set_tooltip_text(""); @@ -522,224 +532,6 @@ Control::CursorShape AnimationNodeStateMachineEditor::get_cursor_shape(const Poi return cursor_shape; } -void AnimationNodeStateMachineEditor::_group_selected_nodes() { - if (!selected_nodes.is_empty()) { - if (selected_nodes.size() == 1 && (*selected_nodes.begin() == state_machine->start_node || *selected_nodes.begin() == state_machine->end_node)) - return; - - Ref group_sm = memnew(AnimationNodeStateMachine); - Vector2 group_position; - - Vector nodes_ur; - Vector transitions_ur; - - int base = 1; - String base_name = group_sm->get_caption(); - String group_name = base_name; - - while (state_machine->has_node(group_name) && !selected_nodes.has(group_name)) { - base++; - group_name = base_name + " " + itos(base); - } - - updating = true; - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action("Group"); - - // Move selected nodes to the new state machine - for (const StringName &E : selected_nodes) { - if (!state_machine->can_edit_node(E)) { - continue; - } - - Ref node = state_machine->get_node(E); - Vector2 node_position = state_machine->get_node_position(E); - group_position += node_position; - - NodeUR new_node; - new_node.name = E; - new_node.node = node; - new_node.position = node_position; - - nodes_ur.push_back(new_node); - } - - // Add the transitions to the new state machine - for (int i = 0; i < state_machine->get_transition_count(); i++) { - String from = state_machine->get_transition_from(i); - String to = state_machine->get_transition_to(i); - - String local_from = from.get_slicec('/', 0); - String local_to = to.get_slicec('/', 0); - - String old_from = from; - String old_to = to; - - bool from_selected = false; - bool to_selected = false; - - if (selected_nodes.has(local_from) && local_from != state_machine->start_node) { - from_selected = true; - } - if (selected_nodes.has(local_to) && local_to != state_machine->end_node) { - to_selected = true; - } - if (!from_selected && !to_selected) { - continue; - } - - Ref tr = state_machine->get_transition(i); - - if (!from_selected) { - from = "../" + old_from; - } - if (!to_selected) { - to = "../" + old_to; - } - - TransitionUR new_tr; - new_tr.new_from = from; - new_tr.new_to = to; - new_tr.old_from = old_from; - new_tr.old_to = old_to; - new_tr.transition = tr; - - transitions_ur.push_back(new_tr); - } - - for (int i = 0; i < nodes_ur.size(); i++) { - undo_redo->add_do_method(state_machine.ptr(), "remove_node", nodes_ur[i].name); - undo_redo->add_undo_method(group_sm.ptr(), "remove_node", nodes_ur[i].name); - } - - undo_redo->add_do_method(state_machine.ptr(), "add_node", group_name, group_sm, group_position / nodes_ur.size()); - undo_redo->add_undo_method(state_machine.ptr(), "remove_node", group_name); - - for (int i = 0; i < nodes_ur.size(); i++) { - undo_redo->add_do_method(group_sm.ptr(), "add_node", nodes_ur[i].name, nodes_ur[i].node, nodes_ur[i].position); - undo_redo->add_undo_method(state_machine.ptr(), "add_node", nodes_ur[i].name, nodes_ur[i].node, nodes_ur[i].position); - } - - for (int i = 0; i < transitions_ur.size(); i++) { - undo_redo->add_do_method(group_sm.ptr(), "add_transition", transitions_ur[i].new_from, transitions_ur[i].new_to, transitions_ur[i].transition); - undo_redo->add_undo_method(state_machine.ptr(), "add_transition", transitions_ur[i].old_from, transitions_ur[i].old_to, transitions_ur[i].transition); - } - - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); - undo_redo->commit_action(); - updating = false; - - selected_nodes.clear(); - selected_nodes.insert(group_name); - state_machine_draw->queue_redraw(); - accept_event(); - _update_mode(); - } -} - -void AnimationNodeStateMachineEditor::_ungroup_selected_nodes() { - bool find = false; - HashSet new_selected_nodes; - - for (const StringName &E : selected_nodes) { - Ref group_sm = state_machine->get_node(E); - - if (group_sm.is_valid()) { - find = true; - - Vector2 group_position = state_machine->get_node_position(E); - StringName group_name = E; - - List nodes; - group_sm->get_child_nodes(&nodes); - - Vector nodes_ur; - Vector transitions_ur; - - updating = true; - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action("Ungroup"); - - // Move all child nodes to current state machine - for (int i = 0; i < nodes.size(); i++) { - if (!group_sm->can_edit_node(nodes[i].name)) { - continue; - } - - Vector2 node_position = group_sm->get_node_position(nodes[i].name); - - NodeUR new_node; - new_node.name = nodes[i].name; - new_node.position = node_position; - new_node.node = nodes[i].node; - - nodes_ur.push_back(new_node); - } - - for (int i = 0; i < group_sm->get_transition_count(); i++) { - String from = group_sm->get_transition_from(i); - String to = group_sm->get_transition_to(i); - Ref tr = group_sm->get_transition(i); - - TransitionUR new_tr; - new_tr.new_from = from.replace_first("../", ""); - new_tr.new_to = to.replace_first("../", ""); - new_tr.old_from = from; - new_tr.old_to = to; - new_tr.transition = tr; - - transitions_ur.push_back(new_tr); - } - - for (int i = 0; i < nodes_ur.size(); i++) { - undo_redo->add_do_method(group_sm.ptr(), "remove_node", nodes_ur[i].name); - undo_redo->add_undo_method(state_machine.ptr(), "remove_node", nodes_ur[i].name); - } - - undo_redo->add_do_method(state_machine.ptr(), "remove_node", group_name); - undo_redo->add_undo_method(state_machine.ptr(), "add_node", group_name, group_sm, group_position); - - for (int i = 0; i < nodes_ur.size(); i++) { - new_selected_nodes.insert(nodes_ur[i].name); - undo_redo->add_do_method(state_machine.ptr(), "add_node", nodes_ur[i].name, nodes_ur[i].node, nodes_ur[i].position); - undo_redo->add_undo_method(group_sm.ptr(), "add_node", nodes_ur[i].name, nodes_ur[i].node, nodes_ur[i].position); - } - - for (int i = 0; i < transitions_ur.size(); i++) { - if (transitions_ur[i].old_from != state_machine->start_node && transitions_ur[i].old_to != state_machine->end_node) { - undo_redo->add_do_method(state_machine.ptr(), "add_transition", transitions_ur[i].new_from, transitions_ur[i].new_to, transitions_ur[i].transition); - } - - undo_redo->add_undo_method(group_sm.ptr(), "add_transition", transitions_ur[i].old_from, transitions_ur[i].old_to, transitions_ur[i].transition); - } - - for (int i = 0; i < state_machine->get_transition_count(); i++) { - String from = state_machine->get_transition_from(i); - String to = state_machine->get_transition_to(i); - Ref tr = state_machine->get_transition(i); - - if (from == group_name || to == group_name) { - undo_redo->add_undo_method(state_machine.ptr(), "add_transition", from, to, tr); - } - } - - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); - undo_redo->commit_action(); - updating = false; - } - } - - if (find) { - selected_nodes = new_selected_nodes; - selected_node = StringName(); - state_machine_draw->queue_redraw(); - accept_event(); - _update_mode(); - } -} - void AnimationNodeStateMachineEditor::_open_menu(const Vector2 &p_position) { AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); if (!tree) { @@ -790,83 +582,8 @@ void AnimationNodeStateMachineEditor::_open_menu(const Vector2 &p_position) { add_node_pos = p_position / EDSCALE + state_machine->get_graph_offset(); } -void AnimationNodeStateMachineEditor::_open_connect_menu(const Vector2 &p_position) { - ERR_FAIL_COND(connecting_to_node == StringName()); - - Ref node = state_machine->get_node(connecting_to_node); - Ref anodesm = node; - Ref end_node = node; - ERR_FAIL_COND(!anodesm.is_valid() && !end_node.is_valid()); - - connect_menu->clear(); - state_machine_menu->clear(); - end_menu->clear(); - nodes_to_connect.clear(); - - for (int i = connect_menu->get_child_count() - 1; i >= 0; i--) { - Node *child = connect_menu->get_child(i); - - if (child->is_class("PopupMenu")) { - connect_menu->remove_child(child); - } - } - - connect_menu->reset_size(); - state_machine_menu->reset_size(); - end_menu->reset_size(); - - if (anodesm.is_valid()) { - _create_submenu(connect_menu, anodesm, connecting_to_node, connecting_to_node); - } else { - _create_submenu(connect_menu, state_machine, connecting_to_node, connecting_to_node, true); - } - - connect_menu->add_submenu_item(TTR("To") + " Animation", connecting_to_node); - - if (state_machine_menu->get_item_count() > 0 || !end_node.is_valid()) { - connect_menu->add_submenu_item(TTR("To") + " StateMachine", "state_machines"); - connect_menu->add_child(state_machine_menu); - } - - if (end_node.is_valid()) { - connect_menu->add_submenu_item(TTR("To") + " End", "end_nodes"); - connect_menu->add_child(end_menu); - } else { - state_machine_menu->add_item(connecting_to_node, nodes_to_connect.size()); - } - - nodes_to_connect.push_back(connecting_to_node); - - if (nodes_to_connect.size() == 1) { - _add_transition(); - return; - } - - connect_menu->set_position(state_machine_draw->get_screen_transform().xform(p_position)); - connect_menu->popup(); -} - -bool AnimationNodeStateMachineEditor::_create_submenu(PopupMenu *p_menu, Ref p_nodesm, const StringName &p_name, const StringName &p_path, bool from_root, Vector> p_parents) { +bool AnimationNodeStateMachineEditor::_create_submenu(PopupMenu *p_menu, Ref p_nodesm, const StringName &p_name, const StringName &p_path) { String prev_path; - Vector> parents = p_parents; - - if (from_root && p_nodesm->get_prev_state_machine() == nullptr) { - return false; - } - - if (from_root) { - AnimationNodeStateMachine *prev = p_nodesm->get_prev_state_machine(); - - while (prev != nullptr) { - parents.push_back(prev); - p_nodesm = Ref(prev); - prev_path += "../"; - prev = prev->get_prev_state_machine(); - } - end_menu->add_item("Root", nodes_to_connect.size()); - nodes_to_connect.push_back(prev_path + state_machine->end_node); - prev_path.remove_at(prev_path.size() - 1); - } List nodes; p_nodesm->get_node_list(&nodes); @@ -881,12 +598,7 @@ bool AnimationNodeStateMachineEditor::_create_submenu(PopupMenu *p_menu, Refcan_edit_node(E)) { Ref ansm = p_nodesm->get_node(E); - String path; - if (from_root) { - path = prev_path + "/" + E; - } else { - path = String(p_path) + "/" + E; - } + String path = String(p_path) + "/" + E; if (ansm == state_machine) { end_menu->add_item(E, nodes_to_connect.size()); @@ -895,25 +607,10 @@ bool AnimationNodeStateMachineEditor::_create_submenu(PopupMenu *p_menu, Refadd_item(E, nodes_to_connect.size()); + nodes_to_connect.push_back(path); - for (int i = 0; i < parents.size(); i++) { - if (parents[i] == ansm) { - path = path.replace_first("/../" + E, ""); - parent_found = true; - break; - } - } - - if (parent_found) { - end_menu->add_item(E, nodes_to_connect.size()); - nodes_to_connect.push_back(path + "/" + state_machine->end_node); - } else { - state_machine_menu->add_item(E, nodes_to_connect.size()); - nodes_to_connect.push_back(path); - } - - if (_create_submenu(nodes_menu, ansm, E, path, false, parents)) { + if (_create_submenu(nodes_menu, ansm, E, path)) { nodes_menu->add_submenu_item(E, E); node_added = true; } @@ -939,7 +636,6 @@ void AnimationNodeStateMachineEditor::_delete_selected() { while (item) { if (!updating) { updating = true; - selected_multi_transition = TransitionLine(); undo_redo->create_action("Transition(s) Removed"); } @@ -959,18 +655,10 @@ void AnimationNodeStateMachineEditor::_delete_selected() { } void AnimationNodeStateMachineEditor::_delete_all() { - Vector multi_transitions = selected_multi_transition.multi_transitions; - selected_multi_transition = TransitionLine(); - updating = true; EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action("Transition(s) Removed"); _erase_selected(true); - for (int i = 0; i < multi_transitions.size(); i++) { - selected_transition_from = multi_transitions[i].from_node; - selected_transition_to = multi_transitions[i].to_node; - _erase_selected(true); - } undo_redo->commit_action(); updating = false; @@ -1120,14 +808,19 @@ void AnimationNodeStateMachineEditor::_add_transition(const bool p_nested_action selected_transition_to = connecting_to_node; selected_transition_index = transition_lines.size(); - EditorNode::get_singleton()->push_item(tr.ptr(), "", true); + if (!state_machine->is_transition_across_group(selected_transition_index)) { + EditorNode::get_singleton()->push_item(tr.ptr(), "", true); + } else { + EditorNode::get_singleton()->push_item(tr.ptr(), "", true); + EditorNode::get_singleton()->push_item(nullptr, "", true); + } _update_mode(); } connecting = false; } -void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_multi_transitions) { +void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_is_across_group) { Color linecolor = get_theme_color(SNAME("font_color"), SNAME("Label")); Color icon_color(1, 1, 1); Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor")); @@ -1173,11 +866,7 @@ void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, co xf.columns[2] = (p_from + p_to) * 0.5 - xf.columns[1] * icon->get_height() * 0.5 - xf.columns[0] * icon->get_height() * 0.5; state_machine_draw->draw_set_transform_matrix(xf); - if (p_multi_transitions) { - state_machine_draw->draw_texture(icons[0], Vector2(-icon->get_width(), 0), icon_color); - state_machine_draw->draw_texture(icons[0], Vector2(), icon_color); - state_machine_draw->draw_texture(icons[0], Vector2(icon->get_width(), 0), icon_color); - } else { + if (!p_is_across_group) { state_machine_draw->draw_texture(icon, Vector2(), icon_color); } state_machine_draw->draw_set_transform_matrix(Transform2D()); @@ -1344,17 +1033,14 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { for (int i = 0; i < state_machine->get_transition_count(); i++) { TransitionLine tl; tl.transition_index = i; + tl.from_node = state_machine->get_transition_from(i); - StringName local_from = String(tl.from_node).get_slicec('/', 0); - local_from = local_from == ".." ? state_machine->start_node : local_from; - Vector2 ofs_from = (dragging_selected && selected_nodes.has(local_from)) ? drag_ofs : Vector2(); - tl.from = (state_machine->get_node_position(local_from) * EDSCALE) + ofs_from - state_machine->get_graph_offset() * EDSCALE; + Vector2 ofs_from = (dragging_selected && selected_nodes.has(tl.from_node)) ? drag_ofs : Vector2(); + tl.from = (state_machine->get_node_position(tl.from_node) * EDSCALE) + ofs_from - state_machine->get_graph_offset() * EDSCALE; tl.to_node = state_machine->get_transition_to(i); - StringName local_to = String(tl.to_node).get_slicec('/', 0); - local_to = local_to == ".." ? state_machine->end_node : local_to; - Vector2 ofs_to = (dragging_selected && selected_nodes.has(local_to)) ? drag_ofs : Vector2(); - tl.to = (state_machine->get_node_position(local_to) * EDSCALE) + ofs_to - state_machine->get_graph_offset() * EDSCALE; + Vector2 ofs_to = (dragging_selected && selected_nodes.has(tl.to_node)) ? drag_ofs : Vector2(); + tl.to = (state_machine->get_node_position(tl.to_node) * EDSCALE) + ofs_to - state_machine->get_graph_offset() * EDSCALE; Ref tr = state_machine->get_transition(i); tl.disabled = bool(tr->get_advance_mode() == AnimationNodeStateMachineTransition::ADVANCE_MODE_DISABLED); @@ -1366,35 +1052,36 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { tl.travel = false; tl.fade_ratio = 0.0; tl.hidden = false; + tl.is_across_group = state_machine->is_transition_across_group(i); - if (state_machine->has_local_transition(local_to, local_from)) { //offset if same exists + if (state_machine->has_transition(tl.to_node, tl.from_node)) { //offset if same exists Vector2 offset = -(tl.from - tl.to).normalized().orthogonal() * tr_bidi_offset; tl.from += offset; tl.to += offset; } for (int j = 0; j < node_rects.size(); j++) { - if (node_rects[j].node_name == local_from) { + if (node_rects[j].node_name == tl.from_node) { _clip_src_line_to_rect(tl.from, tl.to, node_rects[j].node); } - if (node_rects[j].node_name == local_to) { + if (node_rects[j].node_name == tl.to_node) { _clip_dst_line_to_rect(tl.from, tl.to, node_rects[j].node); } } tl.selected = selected_transition_from == tl.from_node && selected_transition_to == tl.to_node; - if (blend_from == local_from && current == local_to) { + if (blend_from == tl.from_node && current == tl.to_node) { tl.travel = true; tl.fade_ratio = MIN(1.0, fading_pos / fading_time); } if (travel_path.size()) { - if (current == local_from && travel_path[0] == local_to) { + if (current == tl.from_node && travel_path[0] == tl.to_node) { tl.travel = true; } else { for (int j = 0; j < travel_path.size() - 1; j++) { - if (travel_path[j] == local_from && travel_path[j + 1] == local_to) { + if (travel_path[j] == tl.from_node && travel_path[j + 1] == tl.to_node) { tl.travel = true; break; } @@ -1408,17 +1095,11 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { tl.auto_advance = true; } - // check if already have this local transition + // check if already have this transition for (int j = 0; j < transition_lines.size(); j++) { - StringName from = String(transition_lines[j].from_node).get_slicec('/', 0); - StringName to = String(transition_lines[j].to_node).get_slicec('/', 0); - from = from == ".." ? state_machine->start_node : from; - to = to == ".." ? state_machine->end_node : to; - - if (from == local_from && to == local_to) { + if (transition_lines[j].from_node == tl.from_node && transition_lines[j].to_node == tl.to_node) { tl.hidden = true; transition_lines.write[j].disabled = transition_lines[j].disabled && tl.disabled; - transition_lines.write[j].multi_transitions.push_back(tl); } } transition_lines.push_back(tl); @@ -1427,7 +1108,7 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { for (int i = 0; i < transition_lines.size(); i++) { TransitionLine tl = transition_lines[i]; if (!tl.hidden) { - _connection_draw(tl.from, tl.to, tl.mode, !tl.disabled, tl.selected, tl.travel, tl.fade_ratio, tl.auto_advance, !tl.multi_transitions.is_empty()); + _connection_draw(tl.from, tl.to, tl.mode, !tl.disabled, tl.selected, tl.travel, tl.fade_ratio, tl.auto_advance, tl.is_across_group); } } @@ -1481,6 +1162,7 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { state_machine_draw->draw_string(font, nr.name.position + Vector2(0, font->get_ascent(font_size)), name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); offset.x += strsize + sep; + nr.can_edit = needs_editor; if (needs_editor) { nr.edit.position = offset + Vector2(0, (h - edit->get_height()) / 2).floor(); nr.edit.size = edit->get_size(); @@ -1546,6 +1228,10 @@ void AnimationNodeStateMachineEditor::_state_machine_pos_draw_individual(String const NodeRect &nr = node_rects[idx]; + if (nr.can_edit) { + return; // It is not AnimationNodeAnimation. + } + Vector2 from; from.x = nr.play.position.x; from.y = (nr.play.position.y + nr.play.size.y + nr.node.position.y + nr.node.size.y) * 0.5; @@ -1584,14 +1270,14 @@ void AnimationNodeStateMachineEditor::_state_machine_pos_draw_all() { { float len = MAX(0.0001, current_length); float pos = CLAMP(current_play_pos, 0, len); - float c = pos / len; + float c = current_length == HUGE_LENGTH ? 1 : (pos / len); _state_machine_pos_draw_individual(playback->get_current_node(), c); } { float len = MAX(0.0001, fade_from_length); float pos = CLAMP(fade_from_current_play_pos, 0, len); - float c = pos / len; + float c = fade_from_length == HUGE_LENGTH ? 1 : (pos / len); _state_machine_pos_draw_individual(playback->get_fading_from_node(), c); } } @@ -1630,8 +1316,6 @@ void AnimationNodeStateMachineEditor::_notification(int p_what) { auto_advance->set_icon(get_theme_icon(SNAME("AutoPlay"), SNAME("EditorIcons"))); tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - tool_group->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons"))); - tool_ungroup->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons"))); play_mode->clear(); play_mode->add_icon_item(get_theme_icon(SNAME("PlayTravel"), SNAME("EditorIcons")), TTR("Travel")); @@ -1655,10 +1339,6 @@ void AnimationNodeStateMachineEditor::_notification(int p_what) { error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); } else if (tree->is_state_invalid()) { error = tree->get_invalid_state_reason(); - /*} else if (state_machine->get_parent().is_valid() && state_machine->get_parent()->is_class("AnimationNodeStateMachine")) { - if (state_machine->get_start_node() == StringName() || state_machine->get_end_node() == StringName()) { - error = TTR("Start and end nodes are needed for a sub-transition."); - }*/ } else if (playback.is_null()) { error = vformat(TTR("No playback resource set at path: %s."), AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); } @@ -1780,8 +1460,12 @@ void AnimationNodeStateMachineEditor::_notification(int p_what) { while (anodesm.is_valid()) { current_node_playback = tree->get(AnimationTreeEditor::get_singleton()->get_base_path() + next + "/playback"); - next += "/" + current_node_playback->get_current_node(); - anodesm = anodesm->get_node(current_node_playback->get_current_node()); + StringName cnode = current_node_playback->get_current_node(); + next += "/" + cnode; + if (!anodesm->has_node(cnode)) { + break; + } + anodesm = anodesm->get_node(cnode); } // when current_node is a state machine, use playback of current_node to set play_pos @@ -1883,10 +1567,7 @@ void AnimationNodeStateMachineEditor::_erase_selected(const bool p_nested_action for (int j = 0; j < state_machine->get_transition_count(); j++) { String from = state_machine->get_transition_from(j); String to = state_machine->get_transition_to(j); - String local_from = from.get_slicec('/', 0); - String local_to = to.get_slicec('/', 0); - - if (local_from == node_rects[i].node_name || local_to == node_rects[i].node_name) { + if (from == node_rects[i].node_name || to == node_rects[i].node_name) { undo_redo->add_undo_method(state_machine.ptr(), "add_transition", from, to, state_machine->get_transition(j)); } } @@ -1903,30 +1584,6 @@ void AnimationNodeStateMachineEditor::_erase_selected(const bool p_nested_action selected_nodes.clear(); } - if (!selected_multi_transition.multi_transitions.is_empty()) { - delete_tree->clear(); - - TreeItem *root = delete_tree->create_item(); - - TreeItem *item = delete_tree->create_item(root); - item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); - item->set_text(0, String(selected_transition_from) + " -> " + selected_transition_to); - item->set_editable(0, true); - - for (int i = 0; i < selected_multi_transition.multi_transitions.size(); i++) { - String from = selected_multi_transition.multi_transitions[i].from_node; - String to = selected_multi_transition.multi_transitions[i].to_node; - - item = delete_tree->create_item(root); - item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); - item->set_text(0, from + " -> " + to); - item->set_editable(0, true); - } - - delete_window->popup_centered(Vector2(400, 200)); - return; - } - if (selected_transition_to != StringName() && selected_transition_from != StringName() && state_machine->has_transition(selected_transition_from, selected_transition_to)) { Ref tr = state_machine->get_transition(state_machine->find_transition(selected_transition_from, selected_transition_to)); if (!p_nested_action) { @@ -1945,7 +1602,6 @@ void AnimationNodeStateMachineEditor::_erase_selected(const bool p_nested_action selected_transition_from = StringName(); selected_transition_to = StringName(); selected_transition_index = -1; - selected_multi_transition = TransitionLine(); } state_machine_draw->queue_redraw(); @@ -1957,24 +1613,6 @@ void AnimationNodeStateMachineEditor::_update_mode() { bool nothing_selected = selected_nodes.is_empty() && selected_transition_from == StringName() && selected_transition_to == StringName(); bool start_end_selected = selected_nodes.size() == 1 && (*selected_nodes.begin() == state_machine->start_node || *selected_nodes.begin() == state_machine->end_node); tool_erase->set_disabled(nothing_selected || start_end_selected || read_only); - - if (selected_nodes.is_empty() || start_end_selected || read_only) { - tool_group->set_disabled(true); - tool_group->set_visible(true); - tool_ungroup->set_visible(false); - } else { - Ref ansm = state_machine->get_node(*selected_nodes.begin()); - - if (selected_nodes.size() == 1 && ansm.is_valid()) { - tool_group->set_disabled(true); - tool_group->set_visible(false); - tool_ungroup->set_visible(true); - } else { - tool_group->set_disabled(false); - tool_group->set_visible(true); - tool_ungroup->set_visible(false); - } - } } else { selection_tools_hb->hide(); } @@ -2037,20 +1675,6 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { top_hb->add_child(selection_tools_hb); selection_tools_hb->add_child(memnew(VSeparator)); - tool_group = memnew(Button); - tool_group->set_flat(true); - tool_group->set_tooltip_text(TTR("Group Selected Node(s)") + " (Ctrl+G)"); - tool_group->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_group_selected_nodes)); - tool_group->set_disabled(true); - selection_tools_hb->add_child(tool_group); - - tool_ungroup = memnew(Button); - tool_ungroup->set_flat(true); - tool_ungroup->set_tooltip_text(TTR("Ungroup Selected Node") + " (Ctrl+Shift+G)"); - tool_ungroup->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_ungroup_selected_nodes)); - tool_ungroup->set_visible(false); - selection_tools_hb->add_child(tool_ungroup); - tool_erase = memnew(Button); tool_erase->set_flat(true); tool_erase->set_tooltip_text(TTR("Remove selected node or transition.")); diff --git a/editor/plugins/animation_state_machine_editor.h b/editor/plugins/animation_state_machine_editor.h index 66338c820e8..e289ddd5928 100644 --- a/editor/plugins/animation_state_machine_editor.h +++ b/editor/plugins/animation_state_machine_editor.h @@ -56,8 +56,6 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { LineEdit *name_edit = nullptr; HBoxContainer *selection_tools_hb = nullptr; - Button *tool_group = nullptr; - Button *tool_ungroup = nullptr; Button *tool_erase = nullptr; HBoxContainer *transition_tools_hb = nullptr; @@ -85,7 +83,7 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { static AnimationNodeStateMachineEditor *singleton; void _state_machine_gui_input(const Ref &p_event); - void _connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_multi_transitions); + void _connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_is_across_group); void _state_machine_draw(); @@ -136,6 +134,7 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { Rect2 play; Rect2 name; Rect2 edit; + bool can_edit; }; Vector node_rects; @@ -156,7 +155,7 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { float fade_ratio; bool hidden; int transition_index; - Vector multi_transitions; + bool is_across_group = false; }; Vector transition_lines; @@ -178,7 +177,6 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { StringName selected_transition_from; StringName selected_transition_to; int selected_transition_index; - TransitionLine selected_multi_transition; void _add_transition(const bool p_nested_action = false); StringName over_node; @@ -190,19 +188,17 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { void _open_editor(const String &p_name); void _scroll_changed(double); + String _get_root_playback_path(String &r_node_directory); + void _clip_src_line_to_rect(Vector2 &r_from, const Vector2 &p_to, const Rect2 &p_rect); void _clip_dst_line_to_rect(const Vector2 &p_from, Vector2 &r_to, const Rect2 &p_rect); void _erase_selected(const bool p_nested_action = false); void _update_mode(); void _open_menu(const Vector2 &p_position); - void _open_connect_menu(const Vector2 &p_position); - bool _create_submenu(PopupMenu *p_menu, Ref p_nodesm, const StringName &p_name, const StringName &p_path, bool from_root = false, Vector> p_parents = Vector>()); + bool _create_submenu(PopupMenu *p_menu, Ref p_nodesm, const StringName &p_name, const StringName &p_path); void _stop_connecting(); - void _group_selected_nodes(); - void _ungroup_selected_nodes(); - void _delete_selected(); void _delete_all(); void _delete_tree_draw(); diff --git a/editor/plugins/animation_tree_editor_plugin.cpp b/editor/plugins/animation_tree_editor_plugin.cpp index ab46e8f04a8..9f808a7d963 100644 --- a/editor/plugins/animation_tree_editor_plugin.cpp +++ b/editor/plugins/animation_tree_editor_plugin.cpp @@ -66,11 +66,6 @@ void AnimationTreeEditor::edit(AnimationTree *p_tree) { Vector path; if (tree) { - if (tree->has_meta("_tree_edit_path")) { - path = tree->get_meta("_tree_edit_path"); - } else { - current_root = ObjectID(); - } edit_path(path); } } diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp index 0e9e02f2479..b252ccbd275 100644 --- a/scene/animation/animation_blend_space_1d.cpp +++ b/scene/animation/animation_blend_space_1d.cpp @@ -46,7 +46,7 @@ Variant AnimationNodeBlendSpace1D::get_parameter_default_value(const StringName } } -Ref AnimationNodeBlendSpace1D::get_child_by_name(const StringName &p_name) { +Ref AnimationNodeBlendSpace1D::get_child_by_name(const StringName &p_name) const { return get_blend_point_node(p_name.operator String().to_int()); } @@ -272,14 +272,14 @@ void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Refset_backward(na_c->is_backward()); } //see how much animation remains - from = cur_length_internal - blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, false, p_is_external_seeking, 0.0, FILTER_IGNORE, true); + from = cur_length_internal - blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, false, p_is_external_seeking, 0.0, FILTER_IGNORE, true, p_test_only); } - max_time_remaining = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true); + max_time_remaining = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); cur_length_internal = from + max_time_remaining; cur_closest = new_closest; } else { - max_time_remaining = blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true); + max_time_remaining = blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); } if (sync) { for (int i = 0; i < blend_points_used; i++) { if (i != cur_closest) { - blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true); + blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only); } } } diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h index 4007df0dede..db2e0045a61 100644 --- a/scene/animation/animation_blend_space_1d.h +++ b/scene/animation/animation_blend_space_1d.h @@ -114,10 +114,10 @@ public: void set_use_sync(bool p_sync); bool is_using_sync() const; - double process(double p_time, bool p_seek, bool p_is_external_seeking) override; + virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; String get_caption() const override; - Ref get_child_by_name(const StringName &p_name) override; + Ref get_child_by_name(const StringName &p_name) const override; AnimationNodeBlendSpace1D(); ~AnimationNodeBlendSpace1D(); diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp index ae5b0d5779a..5715516a7f0 100644 --- a/scene/animation/animation_blend_space_2d.cpp +++ b/scene/animation/animation_blend_space_2d.cpp @@ -442,7 +442,7 @@ void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vect r_weights[2] = w; } -double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek, bool p_is_external_seeking) { +double AnimationNodeBlendSpace2D::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { _update_triangles(); Vector2 blend_pos = get_parameter(blend_position); @@ -512,7 +512,7 @@ double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek, bool p_is_ for (int j = 0; j < 3; j++) { if (i == triangle_points[j]) { //blend with the given weight - double t = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, blend_weights[j], FILTER_IGNORE, true); + double t = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, blend_weights[j], FILTER_IGNORE, true, p_test_only); if (first || t < mind) { mind = t; first = false; @@ -523,7 +523,7 @@ double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek, bool p_is_ } if (sync && !found) { - blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true); + blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only); } } } else { @@ -548,22 +548,22 @@ double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek, bool p_is_ na_n->set_backward(na_c->is_backward()); } //see how much animation remains - from = cur_length_internal - blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, false, p_is_external_seeking, 0.0, FILTER_IGNORE, true); + from = cur_length_internal - blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, false, p_is_external_seeking, 0.0, FILTER_IGNORE, true, p_test_only); } - mind = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true); + mind = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); cur_length_internal = from + mind; cur_closest = new_closest; } else { - mind = blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true); + mind = blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); } if (sync) { for (int i = 0; i < blend_points_used; i++) { if (i != cur_closest) { - blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true); + blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only); } } } @@ -604,7 +604,7 @@ bool AnimationNodeBlendSpace2D::get_auto_triangles() const { return auto_triangles; } -Ref AnimationNodeBlendSpace2D::get_child_by_name(const StringName &p_name) { +Ref AnimationNodeBlendSpace2D::get_child_by_name(const StringName &p_name) const { return get_blend_point_node(p_name.operator String().to_int()); } diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h index a770bf01eeb..a18ae3b2586 100644 --- a/scene/animation/animation_blend_space_2d.h +++ b/scene/animation/animation_blend_space_2d.h @@ -129,7 +129,7 @@ public: void set_y_label(const String &p_label); String get_y_label() const; - virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override; + virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; virtual String get_caption() const override; Vector2 get_closest_point(const Vector2 &p_point); @@ -143,7 +143,7 @@ public: void set_use_sync(bool p_sync); bool is_using_sync() const; - virtual Ref get_child_by_name(const StringName &p_name) override; + virtual Ref get_child_by_name(const StringName &p_name) const override; AnimationNodeBlendSpace2D(); ~AnimationNodeBlendSpace2D(); diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index d3207c1a3db..09eb2beba20 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -64,7 +64,7 @@ void AnimationNodeAnimation::_validate_property(PropertyInfo &p_property) const } } -double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_is_external_seeking) { +double AnimationNodeAnimation::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { AnimationPlayer *ap = state->player; ERR_FAIL_COND_V(!ap, 0); @@ -99,6 +99,7 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_is_ext step = p_time; } + bool is_looping = false; if (anim->get_loop_mode() == Animation::LOOP_PINGPONG) { if (!Math::is_zero_approx(anim_size)) { if (prev_time >= 0 && cur_time < 0) { @@ -111,6 +112,7 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_is_ext } cur_time = Math::pingpong(cur_time, anim_size); } + is_looping = true; } else if (anim->get_loop_mode() == Animation::LOOP_LINEAR) { if (!Math::is_zero_approx(anim_size)) { if (prev_time >= 0 && cur_time < 0) { @@ -122,6 +124,7 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_is_ext cur_time = Math::fposmod(cur_time, anim_size); } backward = false; + is_looping = true; } else { if (cur_time < 0) { step += cur_time; @@ -159,14 +162,16 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_is_ext } } - if (play_mode == PLAY_MODE_FORWARD) { - blend_animation(animation, cur_time, step, p_seek, p_is_external_seeking, 1.0, looped_flag); - } else { - blend_animation(animation, anim_size - cur_time, -step, p_seek, p_is_external_seeking, 1.0, looped_flag); + if (!p_test_only) { + if (play_mode == PLAY_MODE_FORWARD) { + blend_animation(animation, cur_time, step, p_seek, p_is_external_seeking, 1.0, looped_flag); + } else { + blend_animation(animation, anim_size - cur_time, -step, p_seek, p_is_external_seeking, 1.0, looped_flag); + } } set_parameter(time, cur_time); - return anim_size - cur_time; + return is_looping ? HUGE_LENGTH : anim_size - cur_time; } String AnimationNodeAnimation::get_caption() const { @@ -311,7 +316,7 @@ bool AnimationNodeOneShot::has_filter() const { return true; } -double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_is_external_seeking) { +double AnimationNodeOneShot::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { OneShotRequest cur_request = static_cast((int)get_parameter(request)); bool cur_active = get_parameter(active); double cur_time = get_parameter(time); @@ -324,7 +329,7 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_is_exter if (cur_request == ONE_SHOT_REQUEST_ABORT) { set_parameter(active, false); set_parameter(time_to_restart, -1); - return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync); + return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only); } else if (!do_start && !cur_active) { if (cur_time_to_restart >= 0.0 && !p_seek) { cur_time_to_restart -= p_time; @@ -334,7 +339,7 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_is_exter set_parameter(time_to_restart, cur_time_to_restart); } if (!do_start) { - return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync); + return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only); } } @@ -370,11 +375,11 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_is_exter double main_rem = 0.0; if (mix == MIX_MODE_ADD) { - main_rem = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync); + main_rem = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only); } else { - main_rem = blend_input(0, p_time, use_blend && p_seek, p_is_external_seeking, 1.0 - blend, FILTER_BLEND, sync); // Unlike below, processing this edge is a corner case. + main_rem = blend_input(0, p_time, use_blend && p_seek, p_is_external_seeking, 1.0 - blend, FILTER_BLEND, sync, p_test_only); // Unlike below, processing this edge is a corner case. } - double os_rem = blend_input(1, os_seek ? cur_time : p_time, os_seek, p_is_external_seeking, Math::is_zero_approx(blend) ? CMP_EPSILON : blend, FILTER_PASS, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. + double os_rem = blend_input(1, os_seek ? cur_time : p_time, os_seek, p_is_external_seeking, Math::is_zero_approx(blend) ? CMP_EPSILON : blend, FILTER_PASS, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. if (do_start) { cur_remaining = os_rem; @@ -459,10 +464,10 @@ bool AnimationNodeAdd2::has_filter() const { return true; } -double AnimationNodeAdd2::process(double p_time, bool p_seek, bool p_is_external_seeking) { +double AnimationNodeAdd2::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { double amount = get_parameter(add_amount); - double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync); - blend_input(1, p_time, p_seek, p_is_external_seeking, amount, FILTER_PASS, sync); + double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only); + blend_input(1, p_time, p_seek, p_is_external_seeking, amount, FILTER_PASS, sync, p_test_only); return rem0; } @@ -493,11 +498,11 @@ bool AnimationNodeAdd3::has_filter() const { return true; } -double AnimationNodeAdd3::process(double p_time, bool p_seek, bool p_is_external_seeking) { +double AnimationNodeAdd3::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { double amount = get_parameter(add_amount); - blend_input(0, p_time, p_seek, p_is_external_seeking, MAX(0, -amount), FILTER_PASS, sync); - double rem0 = blend_input(1, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync); - blend_input(2, p_time, p_seek, p_is_external_seeking, MAX(0, amount), FILTER_PASS, sync); + blend_input(0, p_time, p_seek, p_is_external_seeking, MAX(0, -amount), FILTER_PASS, sync, p_test_only); + double rem0 = blend_input(1, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only); + blend_input(2, p_time, p_seek, p_is_external_seeking, MAX(0, amount), FILTER_PASS, sync, p_test_only); return rem0; } @@ -525,11 +530,11 @@ String AnimationNodeBlend2::get_caption() const { return "Blend2"; } -double AnimationNodeBlend2::process(double p_time, bool p_seek, bool p_is_external_seeking) { +double AnimationNodeBlend2::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { double amount = get_parameter(blend_amount); - double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0 - amount, FILTER_BLEND, sync); - double rem1 = blend_input(1, p_time, p_seek, p_is_external_seeking, amount, FILTER_PASS, sync); + double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0 - amount, FILTER_BLEND, sync, p_test_only); + double rem1 = blend_input(1, p_time, p_seek, p_is_external_seeking, amount, FILTER_PASS, sync, p_test_only); return amount > 0.5 ? rem1 : rem0; // Hacky but good enough. } @@ -560,11 +565,11 @@ String AnimationNodeBlend3::get_caption() const { return "Blend3"; } -double AnimationNodeBlend3::process(double p_time, bool p_seek, bool p_is_external_seeking) { +double AnimationNodeBlend3::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { double amount = get_parameter(blend_amount); - double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, MAX(0, -amount), FILTER_IGNORE, sync); - double rem1 = blend_input(1, p_time, p_seek, p_is_external_seeking, 1.0 - ABS(amount), FILTER_IGNORE, sync); - double rem2 = blend_input(2, p_time, p_seek, p_is_external_seeking, MAX(0, amount), FILTER_IGNORE, sync); + double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, MAX(0, -amount), FILTER_IGNORE, sync, p_test_only); + double rem1 = blend_input(1, p_time, p_seek, p_is_external_seeking, 1.0 - ABS(amount), FILTER_IGNORE, sync, p_test_only); + double rem2 = blend_input(2, p_time, p_seek, p_is_external_seeking, MAX(0, amount), FILTER_IGNORE, sync, p_test_only); return amount > 0.5 ? rem2 : (amount < -0.5 ? rem0 : rem1); // Hacky but good enough. } @@ -592,12 +597,12 @@ String AnimationNodeTimeScale::get_caption() const { return "TimeScale"; } -double AnimationNodeTimeScale::process(double p_time, bool p_seek, bool p_is_external_seeking) { +double AnimationNodeTimeScale::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { double cur_scale = get_parameter(scale); if (p_seek) { - return blend_input(0, p_time, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true); + return blend_input(0, p_time, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); } else { - return blend_input(0, p_time * cur_scale, false, p_is_external_seeking, 1.0, FILTER_IGNORE, true); + return blend_input(0, p_time * cur_scale, false, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); } } @@ -622,16 +627,16 @@ String AnimationNodeTimeSeek::get_caption() const { return "TimeSeek"; } -double AnimationNodeTimeSeek::process(double p_time, bool p_seek, bool p_is_external_seeking) { +double AnimationNodeTimeSeek::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { double cur_seek_pos = get_parameter(seek_pos_request); if (p_seek) { - return blend_input(0, p_time, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true); + return blend_input(0, p_time, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); } else if (cur_seek_pos >= 0) { - double ret = blend_input(0, cur_seek_pos, true, true, 1.0, FILTER_IGNORE, true); + double ret = blend_input(0, cur_seek_pos, true, true, 1.0, FILTER_IGNORE, true, p_test_only); set_parameter(seek_pos_request, -1.0); // Reset. return ret; } else { - return blend_input(0, p_time, false, p_is_external_seeking, 1.0, FILTER_IGNORE, true); + return blend_input(0, p_time, false, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); } } @@ -815,7 +820,7 @@ bool AnimationNodeTransition::is_allow_transition_to_self() const { return allow_transition_to_self; } -double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_external_seeking) { +double AnimationNodeTransition::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { String cur_transition_request = get_parameter(transition_request); int cur_current_index = get_parameter(current_index); int cur_prev_index = get_parameter(prev_index); @@ -881,7 +886,7 @@ double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_ex // Special case for restart. if (restart) { set_parameter(time, 0); - return blend_input(cur_current_index, 0, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true); + return blend_input(cur_current_index, 0, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); } if (switched) { @@ -898,14 +903,14 @@ double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_ex if (sync) { for (int i = 0; i < get_input_count(); i++) { if (i != cur_current_index && i != cur_prev_index) { - blend_input(i, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true); + blend_input(i, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only); } } } if (cur_prev_index < 0) { // Process current animation, check for transition. - rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true); + rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); if (p_seek) { cur_time = p_time; @@ -935,12 +940,12 @@ double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_ex // Blend values must be more than CMP_EPSILON to process discrete keys in edge. if (input_data[cur_current_index].reset && !p_seek && switched) { // Just switched, seek to start of current. - rem = blend_input(cur_current_index, 0, true, p_is_external_seeking, blend_inv, FILTER_IGNORE, true); + rem = blend_input(cur_current_index, 0, true, p_is_external_seeking, blend_inv, FILTER_IGNORE, true, p_test_only); } else { - rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, blend_inv, FILTER_IGNORE, true); + rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, blend_inv, FILTER_IGNORE, true, p_test_only); } - blend_input(cur_prev_index, p_time, use_blend && p_seek, p_is_external_seeking, blend, FILTER_IGNORE, true); + blend_input(cur_prev_index, p_time, use_blend && p_seek, p_is_external_seeking, blend, FILTER_IGNORE, true, p_test_only); if (p_seek) { cur_time = p_time; } else { @@ -999,8 +1004,8 @@ String AnimationNodeOutput::get_caption() const { return "Output"; } -double AnimationNodeOutput::process(double p_time, bool p_seek, bool p_is_external_seeking) { - return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true); +double AnimationNodeOutput::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { + return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); } AnimationNodeOutput::AnimationNodeOutput() { @@ -1218,9 +1223,9 @@ String AnimationNodeBlendTree::get_caption() const { return "BlendTree"; } -double AnimationNodeBlendTree::process(double p_time, bool p_seek, bool p_is_external_seeking) { +double AnimationNodeBlendTree::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { Ref output = nodes[SceneStringNames::get_singleton()->output].node; - return _blend_node("output", nodes[SceneStringNames::get_singleton()->output].connections, this, output, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true); + return _blend_node("output", nodes[SceneStringNames::get_singleton()->output].connections, this, output, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, nullptr, p_test_only); } void AnimationNodeBlendTree::get_node_list(List *r_list) { @@ -1237,7 +1242,7 @@ Vector2 AnimationNodeBlendTree::get_graph_offset() const { return graph_offset; } -Ref AnimationNodeBlendTree::get_child_by_name(const StringName &p_name) { +Ref AnimationNodeBlendTree::get_child_by_name(const StringName &p_name) const { return get_node(p_name); } diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index d4827180bbd..e1cba60d7b0 100644 --- a/scene/animation/animation_blend_tree.h +++ b/scene/animation/animation_blend_tree.h @@ -53,7 +53,7 @@ public: static Vector (*get_editable_animation_list)(); virtual String get_caption() const override; - virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override; + virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; void set_animation(const StringName &p_name); StringName get_animation() const; @@ -150,7 +150,7 @@ public: MixMode get_mix_mode() const; virtual bool has_filter() const override; - virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override; + virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; AnimationNodeOneShot(); }; @@ -173,7 +173,7 @@ public: virtual String get_caption() const override; virtual bool has_filter() const override; - virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override; + virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; AnimationNodeAdd2(); }; @@ -193,7 +193,7 @@ public: virtual String get_caption() const override; virtual bool has_filter() const override; - virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override; + virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; AnimationNodeAdd3(); }; @@ -211,7 +211,7 @@ public: virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; virtual String get_caption() const override; - virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override; + virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; virtual bool has_filter() const override; AnimationNodeBlend2(); @@ -231,7 +231,7 @@ public: virtual String get_caption() const override; - double process(double p_time, bool p_seek, bool p_is_external_seeking) override; + virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; AnimationNodeBlend3(); }; @@ -249,7 +249,7 @@ public: virtual String get_caption() const override; - double process(double p_time, bool p_seek, bool p_is_external_seeking) override; + virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; AnimationNodeTimeScale(); }; @@ -268,7 +268,7 @@ public: virtual String get_caption() const override; - double process(double p_time, bool p_seek, bool p_is_external_seeking) override; + virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; AnimationNodeTimeSeek(); }; @@ -332,7 +332,7 @@ public: void set_allow_transition_to_self(bool p_enable); bool is_allow_transition_to_self() const; - double process(double p_time, bool p_seek, bool p_is_external_seeking) override; + virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; AnimationNodeTransition(); }; @@ -342,7 +342,7 @@ class AnimationNodeOutput : public AnimationNode { public: virtual String get_caption() const override; - virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override; + virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; AnimationNodeOutput(); }; @@ -414,14 +414,14 @@ public: void get_node_connections(List *r_connections) const; virtual String get_caption() const override; - virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override; + virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; void get_node_list(List *r_list); void set_graph_offset(const Vector2 &p_graph_offset); Vector2 get_graph_offset() const; - virtual Ref get_child_by_name(const StringName &p_name) override; + virtual Ref get_child_by_name(const StringName &p_name) const override; AnimationNodeBlendTree(); ~AnimationNodeBlendTree(); diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index d19d3cc7a35..7f98bc1c98e 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -179,28 +179,109 @@ AnimationNodeStateMachineTransition::AnimationNodeStateMachineTransition() { //////////////////////////////////////////////////////// +void AnimationNodeStateMachinePlayback::_set_current(AnimationNodeStateMachine *p_state_machine, const StringName &p_state) { + current = p_state; + if (current == StringName()) { + group_start_transition = Ref(); + group_end_transition = Ref(); + return; + } + + Ref anodesm = p_state_machine->find_node_by_path(current); + if (!anodesm.is_valid()) { + group_start_transition = Ref(); + group_end_transition = Ref(); + return; + } + + Vector indices = p_state_machine->find_transition_to(current); + int group_start_size = indices.size(); + if (group_start_size) { + group_start_transition = p_state_machine->get_transition(indices[0]); + } else { + group_start_transition = Ref(); + } + + indices = p_state_machine->find_transition_from(current); + int group_end_size = indices.size(); + if (group_end_size) { + group_end_transition = p_state_machine->get_transition(indices[0]); + } else { + group_end_transition = Ref(); + } + + // Validation. + if (anodesm->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + indices = anodesm->find_transition_from(anodesm->start_node); + int anodesm_start_size = indices.size(); + indices = anodesm->find_transition_to(anodesm->end_node); + int anodesm_end_size = indices.size(); + if (group_start_size > 1) { + WARN_PRINT_ED("There are two or more transitions to the Grouped AnimationNodeStateMachine in AnimationNodeStateMachine: " + base_path + ", which may result in unintended transitions."); + } + if (group_end_size > 1) { + WARN_PRINT_ED("There are two or more transitions from the Grouped AnimationNodeStateMachine in AnimationNodeStateMachine: " + base_path + ", which may result in unintended transitions."); + } + if (anodesm_start_size > 1) { + WARN_PRINT_ED("There are two or more transitions from the Start of Grouped AnimationNodeStateMachine in AnimationNodeStateMachine: " + base_path + current + ", which may result in unintended transitions."); + } + if (anodesm_end_size > 1) { + WARN_PRINT_ED("There are two or more transitions to the End of Grouped AnimationNodeStateMachine in AnimationNodeStateMachine: " + base_path + current + ", which may result in unintended transitions."); + } + if (anodesm_start_size != group_start_size) { + ERR_PRINT_ED("There is a mismatch in the number of start transitions in and out of the Grouped AnimationNodeStateMachine on AnimationNodeStateMachine: " + base_path + current + "."); + } + if (anodesm_end_size != group_end_size) { + ERR_PRINT_ED("There is a mismatch in the number of end transitions in and out of the Grouped AnimationNodeStateMachine on AnimationNodeStateMachine: " + base_path + current + "."); + } + } +} + +void AnimationNodeStateMachinePlayback::_set_grouped(bool p_is_grouped) { + is_grouped = p_is_grouped; +} + void AnimationNodeStateMachinePlayback::travel(const StringName &p_state, bool p_reset_on_teleport) { + ERR_FAIL_COND_EDMSG(is_grouped, "Grouped AnimationNodeStateMachinePlayback must be handled by parent AnimationNodeStateMachinePlayback. You need to retrieve the parent Root/Nested AnimationNodeStateMachine."); + ERR_FAIL_COND_EDMSG(String(p_state).contains("/Start") || String(p_state).contains("/End"), "Grouped AnimationNodeStateMachinePlayback doesn't allow to play Start/End directly. Instead, play the prev or next state of group in the parent AnimationNodeStateMachine."); + _travel_main(p_state, p_reset_on_teleport); +} + +void AnimationNodeStateMachinePlayback::start(const StringName &p_state, bool p_reset) { + ERR_FAIL_COND_EDMSG(is_grouped, "Grouped AnimationNodeStateMachinePlayback must be handled by parent AnimationNodeStateMachinePlayback. You need to retrieve the parent Root/Nested AnimationNodeStateMachine."); + ERR_FAIL_COND_EDMSG(String(p_state).contains("/Start") || String(p_state).contains("/End"), "Grouped AnimationNodeStateMachinePlayback doesn't allow to play Start/End directly. Instead, play the prev or next state of group in the parent AnimationNodeStateMachine."); + _start_main(p_state, p_reset); +} + +void AnimationNodeStateMachinePlayback::next() { + ERR_FAIL_COND_EDMSG(is_grouped, "Grouped AnimationNodeStateMachinePlayback must be handled by parent AnimationNodeStateMachinePlayback. You need to retrieve the parent Root/Nested AnimationNodeStateMachine."); + _next_main(); +} + +void AnimationNodeStateMachinePlayback::stop() { + ERR_FAIL_COND_EDMSG(is_grouped, "Grouped AnimationNodeStateMachinePlayback must be handled by parent AnimationNodeStateMachinePlayback. You need to retrieve the parent Root/Nested AnimationNodeStateMachine."); + _stop_main(); +} + +void AnimationNodeStateMachinePlayback::_travel_main(const StringName &p_state, bool p_reset_on_teleport) { travel_request = p_state; reset_request_on_teleport = p_reset_on_teleport; stop_request = false; } -void AnimationNodeStateMachinePlayback::start(const StringName &p_state, bool p_reset) { +void AnimationNodeStateMachinePlayback::_start_main(const StringName &p_state, bool p_reset) { travel_request = StringName(); + path.clear(); reset_request = p_reset; - _start(p_state); -} - -void AnimationNodeStateMachinePlayback::_start(const StringName &p_state) { start_request = p_state; stop_request = false; } -void AnimationNodeStateMachinePlayback::next() { +void AnimationNodeStateMachinePlayback::_next_main() { next_request = true; } -void AnimationNodeStateMachinePlayback::stop() { +void AnimationNodeStateMachinePlayback::_stop_main() { stop_request = true; } @@ -208,6 +289,10 @@ bool AnimationNodeStateMachinePlayback::is_playing() const { return playing; } +bool AnimationNodeStateMachinePlayback::is_end() const { + return current == "End" && fading_from == StringName(); +} + StringName AnimationNodeStateMachinePlayback::get_current_node() const { return current; } @@ -244,60 +329,234 @@ float AnimationNodeStateMachinePlayback::get_fading_pos() const { return fading_pos; } -bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *p_state_machine, const StringName &p_travel) { - ERR_FAIL_COND_V(!playing, false); - ERR_FAIL_COND_V(!p_state_machine->states.has(p_travel), false); - ERR_FAIL_COND_V(!p_state_machine->states.has(current), false); +void AnimationNodeStateMachinePlayback::_clear_path_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only) { + List child_nodes; + p_state_machine->get_child_nodes(&child_nodes); + for (int i = 0; i < child_nodes.size(); i++) { + Ref anodesm = child_nodes[i].node; + if (anodesm.is_valid() && anodesm->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + Ref playback = p_tree->get(base_path + child_nodes[i].name + "/playback"); + ERR_FAIL_COND(!playback.is_valid()); + playback->_set_base_path(base_path + child_nodes[i].name + "/"); + if (p_test_only) { + playback = playback->duplicate(); + } + playback->path.clear(); + playback->_clear_path_children(p_tree, anodesm.ptr(), p_test_only); + if (current != child_nodes[i].name) { + playback->_start(anodesm.ptr()); // Can restart. + } + } + } +} - path.clear(); //a new one will be needed +void AnimationNodeStateMachinePlayback::_start_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_test_only) { + if (p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + return; // This function must be fired only by the top state machine, do nothing in child state machine. + } + Vector temp_path = p_path.split("/"); + if (temp_path.size() > 1) { + for (int i = 1; i < temp_path.size(); i++) { + String concatenated; + for (int j = 0; j < i; j++) { + concatenated += temp_path[j] + (j == i - 1 ? "" : "/"); + } + Ref anodesm = p_state_machine->find_node_by_path(concatenated); + if (anodesm.is_valid() && anodesm->get_state_machine_type() != AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + ERR_FAIL_MSG("Root/Nested AnimationNodeStateMachine can't have path from parent AnimationNodeStateMachine."); + } + Ref playback = p_tree->get(base_path + concatenated + "/playback"); + ERR_FAIL_COND(!playback.is_valid()); + playback->_set_base_path(base_path + concatenated + "/"); + if (p_test_only) { + playback = playback->duplicate(); + } + playback->_start_main(temp_path[i], i == temp_path.size() - 1 ? reset_request : false); + } + reset_request = false; + } +} - if (current == p_travel) { - return !p_state_machine->is_allow_transition_to_self(); +bool AnimationNodeStateMachinePlayback::_travel_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_is_allow_transition_to_self, bool p_is_parent_same_state, bool p_test_only) { + if (p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + return false; // This function must be fired only by the top state machine, do nothing in child state machine. + } + Vector temp_path = p_path.split("/"); + Vector children; + + bool found_route = true; + bool is_parent_same_state = p_is_parent_same_state; + if (temp_path.size() > 1) { + for (int i = 1; i < temp_path.size(); i++) { + String concatenated; + for (int j = 0; j < i; j++) { + concatenated += temp_path[j] + (j == i - 1 ? "" : "/"); + } + + Ref anodesm = p_state_machine->find_node_by_path(concatenated); + if (anodesm.is_valid() && anodesm->get_state_machine_type() != AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + ERR_FAIL_V_MSG(false, "Root/Nested AnimationNodeStateMachine can't have path from parent AnimationNodeStateMachine."); + } + Ref playback = p_tree->get(base_path + concatenated + "/playback"); + ERR_FAIL_COND_V(!playback.is_valid(), false); + playback->_set_base_path(base_path + concatenated + "/"); + if (p_test_only) { + playback = playback->duplicate(); + } + if (!playback->is_playing()) { + playback->_start(anodesm.ptr()); + } + ChildStateMachineInfo child_info; + child_info.playback = playback; + + // Process for the case that parent state is changed. + bool child_found_route = true; + bool is_current_same_state = temp_path[i] == playback->get_current_node(); + if (!is_parent_same_state) { + // Force travel to end current child state machine. + String child_path = "/" + playback->get_current_node(); + while (true) { + Ref child_anodesm = p_state_machine->find_node_by_path(concatenated + child_path); + if (!child_anodesm.is_valid() || child_anodesm->get_state_machine_type() != AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + break; + } + Ref child_playback = p_tree->get(base_path + concatenated + child_path + "/playback"); + ERR_FAIL_COND_V(!child_playback.is_valid(), false); + child_playback->_set_base_path(base_path + concatenated + "/"); + if (p_test_only) { + child_playback = child_playback->duplicate(); + } + child_playback->_travel_main("End"); + child_found_route &= child_playback->_travel(p_tree, child_anodesm.ptr(), false, p_test_only); + child_path += "/" + child_playback->get_current_node(); + } + // Force restart target state machine. + playback->_start(anodesm.ptr()); + } + is_parent_same_state = is_current_same_state; + + bool is_deepest_state = i == temp_path.size() - 1; + child_info.is_reset = is_deepest_state ? reset_request_on_teleport : false; + playback->_travel_main(temp_path[i], child_info.is_reset); + if (playback->_make_travel_path(p_tree, anodesm.ptr(), is_deepest_state ? p_is_allow_transition_to_self : false, child_info.path, p_test_only)) { + found_route &= child_found_route; + } else { + child_info.path.push_back(temp_path[i]); + found_route = false; + } + children.push_back(child_info); + } + reset_request_on_teleport = false; } - Vector2 current_pos = p_state_machine->states[current].position; - Vector2 target_pos = p_state_machine->states[p_travel].position; + if (found_route) { + for (int i = 0; i < children.size(); i++) { + children.write[i].playback->clear_path(); + for (int j = 0; j < children[i].path.size(); j++) { + children.write[i].playback->push_path(children[i].path[j]); + } + } + } else { + for (int i = 0; i < children.size(); i++) { + children.write[i].playback->_travel_main(StringName(), children[i].is_reset); // Clear travel. + if (children[i].path.size()) { + children.write[i].playback->_start_main(children[i].path[children[i].path.size() - 1], children[i].is_reset); + } + } + } + return found_route; +} +void AnimationNodeStateMachinePlayback::_start(AnimationNodeStateMachine *p_state_machine) { + playing = true; + _set_current(p_state_machine, start_request != StringName() ? start_request : p_state_machine->start_node); + teleport_request = true; + stop_request = false; + start_request = StringName(); +} + +bool AnimationNodeStateMachinePlayback::_travel(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, bool p_test_only) { + return _make_travel_path(p_tree, p_state_machine, p_is_allow_transition_to_self, path, p_test_only); +} + +String AnimationNodeStateMachinePlayback::_validate_path(AnimationNodeStateMachine *p_state_machine, const String &p_path) { + if (p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + return p_path; // Grouped state machine doesn't allow validat-able request. + } + String target = p_path; + Ref anodesm = p_state_machine->find_node_by_path(target); + while (anodesm.is_valid() && anodesm->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + Vector indices = anodesm->find_transition_from(anodesm->start_node); + if (indices.size()) { + target = target + "/" + anodesm->get_transition_to(indices[0]); // Find next state of Start. + } else { + break; // There is no transition in Start state of grouped state machine. + } + anodesm = p_state_machine->find_node_by_path(target); + } + return target; +} + +bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, Vector &r_path, bool p_test_only) { + StringName travel = travel_request; + travel_request = StringName(); + + if (!playing) { + _start(p_state_machine); + } + + ERR_FAIL_COND_V(!p_state_machine->states.has(travel), false); + ERR_FAIL_COND_V(!p_state_machine->states.has(current), false); + + if (current == travel) { + return !p_is_allow_transition_to_self; + } + + Vector new_path; + + Vector2 current_pos = p_state_machine->states[current].position; + Vector2 target_pos = p_state_machine->states[travel].position; + + bool found_route = false; HashMap cost_map; List open_list; - //build open list + // Build open list. for (int i = 0; i < p_state_machine->transitions.size(); i++) { if (p_state_machine->transitions[i].transition->get_advance_mode() == AnimationNodeStateMachineTransition::ADVANCE_MODE_DISABLED) { continue; } - if (p_state_machine->transitions[i].local_from == current) { + if (p_state_machine->transitions[i].from == current) { open_list.push_back(i); - float cost = p_state_machine->states[p_state_machine->transitions[i].local_to].position.distance_to(current_pos); + float cost = p_state_machine->states[p_state_machine->transitions[i].to].position.distance_to(current_pos); cost *= p_state_machine->transitions[i].transition->get_priority(); AStarCost ap; ap.prev = current; ap.distance = cost; - cost_map[p_state_machine->transitions[i].local_to] = ap; + cost_map[p_state_machine->transitions[i].to] = ap; - if (p_state_machine->transitions[i].local_to == p_travel) { //prematurely found it! :D - path.push_back(p_travel); - return true; + if (p_state_machine->transitions[i].to == travel) { // Prematurely found it! :D + found_route = true; + break; } } } - //begin astar - bool found_route = false; + // Begin astar. while (!found_route) { if (open_list.size() == 0) { - return false; //no path found + break; // No path found. } - //find the last cost transition + // Find the last cost transition. List::Element *least_cost_transition = nullptr; float least_cost = 1e20; for (List::Element *E = open_list.front(); E; E = E->next()) { - float cost = cost_map[p_state_machine->transitions[E->get()].local_to].distance; - cost += p_state_machine->states[p_state_machine->transitions[E->get()].local_to].position.distance_to(target_pos); + float cost = cost_map[p_state_machine->transitions[E->get()].to].distance; + cost += p_state_machine->states[p_state_machine->transitions[E->get()].to].position.distance_to(target_pos); if (cost < least_cost) { least_cost_transition = E; @@ -305,38 +564,38 @@ bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *p_sta } } - StringName transition_prev = p_state_machine->transitions[least_cost_transition->get()].local_from; - StringName transition = p_state_machine->transitions[least_cost_transition->get()].local_to; + StringName transition_prev = p_state_machine->transitions[least_cost_transition->get()].from; + StringName transition = p_state_machine->transitions[least_cost_transition->get()].to; for (int i = 0; i < p_state_machine->transitions.size(); i++) { if (p_state_machine->transitions[i].transition->get_advance_mode() == AnimationNodeStateMachineTransition::ADVANCE_MODE_DISABLED) { continue; } - if (p_state_machine->transitions[i].local_from != transition || p_state_machine->transitions[i].local_to == transition_prev) { - continue; //not interested on those + if (p_state_machine->transitions[i].from != transition || p_state_machine->transitions[i].to == transition_prev) { + continue; // Not interested on those. } - float distance = p_state_machine->states[p_state_machine->transitions[i].local_from].position.distance_to(p_state_machine->states[p_state_machine->transitions[i].local_to].position); + float distance = p_state_machine->states[p_state_machine->transitions[i].from].position.distance_to(p_state_machine->states[p_state_machine->transitions[i].to].position); distance *= p_state_machine->transitions[i].transition->get_priority(); - distance += cost_map[p_state_machine->transitions[i].local_from].distance; + distance += cost_map[p_state_machine->transitions[i].from].distance; - if (cost_map.has(p_state_machine->transitions[i].local_to)) { - //oh this was visited already, can we win the cost? - if (distance < cost_map[p_state_machine->transitions[i].local_to].distance) { - cost_map[p_state_machine->transitions[i].local_to].distance = distance; - cost_map[p_state_machine->transitions[i].local_to].prev = p_state_machine->transitions[i].local_from; + if (cost_map.has(p_state_machine->transitions[i].to)) { + // Oh this was visited already, can we win the cost? + if (distance < cost_map[p_state_machine->transitions[i].to].distance) { + cost_map[p_state_machine->transitions[i].to].distance = distance; + cost_map[p_state_machine->transitions[i].to].prev = p_state_machine->transitions[i].from; } } else { - //add to open list + // Add to open list. AStarCost ac; - ac.prev = p_state_machine->transitions[i].local_from; + ac.prev = p_state_machine->transitions[i].from; ac.distance = distance; - cost_map[p_state_machine->transitions[i].local_to] = ac; + cost_map[p_state_machine->transitions[i].to] = ac; open_list.push_back(i); - if (p_state_machine->transitions[i].local_to == p_travel) { + if (p_state_machine->transitions[i].to == travel) { found_route = true; break; } @@ -350,20 +609,60 @@ bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *p_sta open_list.erase(least_cost_transition); } - //make path - StringName at = p_travel; - while (at != current) { - path.push_back(at); - at = cost_map[at].prev; + // Check child grouped state machine. + if (found_route) { + // Make path. + StringName at = travel; + while (at != current) { + new_path.push_back(at); + at = cost_map[at].prev; + } + new_path.reverse(); + + // Check internal paths of child grouped state machine. + // For example: + // [current - End] - [Start - End] - [Start - End] - [Start - target] + String current_path = current; + int len = new_path.size() + 1; + for (int i = 0; i < len; i++) { + Ref anodesm = p_state_machine->find_node_by_path(current_path); + if (anodesm.is_valid() && anodesm->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + Ref playback = p_tree->get(base_path + current_path + "/playback"); + ERR_FAIL_COND_V(!playback.is_valid(), false); + playback->_set_base_path(base_path + current_path + "/"); + if (p_test_only) { + playback = playback->duplicate(); + } + if (i > 0) { + playback->_start(anodesm.ptr()); + } + if (i >= new_path.size()) { + break; // Tracing has been finished, needs to break. + } + playback->_travel_main("End"); + if (!playback->_travel(p_tree, anodesm.ptr(), false, p_test_only)) { + found_route = false; + break; + } + } + if (i >= new_path.size()) { + break; // Tracing has been finished, needs to break. + } + current_path = new_path[i]; + } } - path.reverse(); - - return true; + // Finally, rewrite path if route is found. + if (found_route) { + r_path = new_path; + return true; + } else { + return false; + } } -double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking) { - double rem = _process(p_state_machine, p_time, p_seek, p_is_external_seeking); +double AnimationNodeStateMachinePlayback::process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { + double rem = _process(p_base_path, p_state_machine, p_time, p_seek, p_is_external_seeking, p_test_only); start_request = StringName(); next_request = false; stop_request = false; @@ -371,99 +670,129 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s return rem; } -double AnimationNodeStateMachinePlayback::_process(AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking) { - if (p_time == -1) { - Ref anodesm = p_state_machine->states[current].node; - if (anodesm.is_valid()) { - p_state_machine->blend_node(current, p_state_machine->states[current].node, -1, p_seek, p_is_external_seeking, 0, AnimationNode::FILTER_IGNORE, true); - } - playing = false; - return 0; - } +double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { + _set_base_path(p_base_path); - //if not playing and it can restart, then restart - if (!playing && start_request == StringName()) { - if (!stop_request && p_state_machine->start_node) { - _start(p_state_machine->start_node); + AnimationTree *tree = p_state_machine->state->tree; + + // Check seek to 0 (means reset) by parent AnimationNode. + if (p_time == 0 && p_seek && !p_is_external_seeking) { + if (p_state_machine->state_machine_type != AnimationNodeStateMachine::STATE_MACHINE_TYPE_NESTED || is_end() || !playing) { + // Restart state machine. + if (p_state_machine->get_state_machine_type() != AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + path.clear(); + _clear_path_children(tree, p_state_machine, p_test_only); + } + reset_request = true; + _start(p_state_machine); } else { - return 0; + // Reset current state. + reset_request = true; + teleport_request = true; } } - if (playing && stop_request) { + if (stop_request) { + start_request = StringName(); + travel_request = StringName(); + path.clear(); playing = false; return 0; } - bool play_start = false; + if (!playing && start_request != StringName() && travel_request != StringName()) { + return 0; + } + + // Process start/travel request. + if (start_request != StringName() || travel_request != StringName()) { + if (p_state_machine->get_state_machine_type() != AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + _clear_path_children(tree, p_state_machine, p_test_only); + } + } if (start_request != StringName()) { - // teleport to start + path.clear(); + String start_target = _validate_path(p_state_machine, start_request); + Vector start_path = String(start_target).split("/"); + start_request = start_path[0]; + if (start_path.size()) { + _start_children(tree, p_state_machine, start_target, p_test_only); + } + // Teleport to start. if (p_state_machine->states.has(start_request)) { - path.clear(); - current = start_request; - playing = true; - play_start = true; + _start(p_state_machine); } else { StringName node = start_request; ERR_FAIL_V_MSG(0, "No such node: '" + node + "'"); } - } else if (travel_request != StringName()) { - if (!playing) { - if (!stop_request && p_state_machine->start_node) { - // can restart, just postpone traveling - path.clear(); - current = p_state_machine->start_node; - playing = true; - play_start = true; + } + + if (travel_request != StringName()) { + // Fix path. + String travel_target = _validate_path(p_state_machine, travel_request); + Vector travel_path = travel_target.split("/"); + travel_request = travel_path[0]; + StringName temp_travel_request = travel_request; // For the case that can't travel. + // Process children. + Vector new_path; + bool can_travel = _make_travel_path(tree, p_state_machine, travel_path.size() <= 1 ? p_state_machine->is_allow_transition_to_self() : false, new_path, p_test_only); + if (travel_path.size()) { + if (can_travel) { + can_travel = _travel_children(tree, p_state_machine, travel_target, p_state_machine->is_allow_transition_to_self(), travel_path[0] == current, p_test_only); } else { - // stopped, invalid state - String node_name = travel_request; - travel_request = StringName(); - ERR_FAIL_V_MSG(0, "Can't travel to '" + node_name + "' if state machine is not playing. Maybe you need to enable Autoplay on Load for one of the nodes in your state machine or call .start() first?"); + _start_children(tree, p_state_machine, travel_target, p_test_only); } + } + + // Process to travel. + if (can_travel) { + path = new_path; } else { - if (!_travel(p_state_machine, travel_request)) { - // can't travel, then teleport - if (p_state_machine->states.has(travel_request)) { - path.clear(); - if (current != travel_request || reset_request_on_teleport) { - current = travel_request; - play_start = true; - reset_request = reset_request_on_teleport; - } - } else { - StringName node = travel_request; - travel_request = StringName(); - ERR_FAIL_V_MSG(0, "No such node: '" + node + "'"); + // Can't travel, then teleport. + if (p_state_machine->states.has(temp_travel_request)) { + path.clear(); + if (current != temp_travel_request || reset_request_on_teleport) { + _set_current(p_state_machine, temp_travel_request); + reset_request = reset_request_on_teleport; + teleport_request = true; } + } else { + ERR_FAIL_V_MSG(0, "No such node: '" + temp_travel_request + "'"); } - travel_request = StringName(); } } - bool do_start = (p_seek && p_time == 0) || play_start || current == StringName(); - - if (do_start) { - if (p_state_machine->start_node != StringName() && p_seek && p_time == 0 && current == StringName()) { - current = p_state_machine->start_node; - } - - if (reset_request) { - len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_is_external_seeking, 1.0, AnimationNode::FILTER_IGNORE, true); - pos_current = 0; - reset_request = false; - } + if (teleport_request) { + teleport_request = false; + // Clear fadeing on teleport. + fading_from = StringName(); + fading_pos = 0; + // Init current length. + pos_current = 0; // Overwritten suddenly in main process. + len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, false, 0, AnimationNode::FILTER_IGNORE, true, true); + // Don't process first node if not necessary, insteads process next node. + _transition_to_next_recursive(tree, p_state_machine, p_test_only); } + // Check current node existence. if (!p_state_machine->states.has(current)) { - playing = false; //current does not exist - current = StringName(); + playing = false; // Current does not exist. + _set_current(p_state_machine, StringName()); return 0; } - float fade_blend = 1.0; - if (fading_from != StringName()) { + // Special case for grouped state machine Start/End to make priority with parent blend (means don't treat Start and End states as RESET animations). + bool is_start_of_group = false; + bool is_end_of_group = false; + if (!p_state_machine->are_ends_reset() || p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + is_start_of_group = fading_from == p_state_machine->start_node; + is_end_of_group = current == p_state_machine->end_node; + } + + // Calc blend amount by cross-fade. + float fade_blend = 1.0; + if (fading_time && fading_from != StringName()) { if (!p_state_machine->states.has(fading_from)) { fading_from = StringName(); } else { @@ -473,223 +802,283 @@ double AnimationNodeStateMachinePlayback::_process(AnimationNodeStateMachine *p_ fade_blend = MIN(1.0, fading_pos / fading_time); } } - if (current_curve.is_valid()) { fade_blend = current_curve->sample(fade_blend); } + fade_blend = Math::is_zero_approx(fade_blend) ? CMP_EPSILON : fade_blend; + if (is_start_of_group) { + fade_blend = 1.0; + } else if (is_end_of_group) { + fade_blend = 0.0; + } - double rem = do_start ? len_current : p_state_machine->blend_node(current, p_state_machine->states[current].node, p_time, p_seek, p_is_external_seeking, Math::is_zero_approx(fade_blend) ? CMP_EPSILON : fade_blend, AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. + // Main process. + double rem = 0.0; + if (reset_request) { + reset_request = false; + len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_is_external_seeking, fade_blend, AnimationNode::FILTER_IGNORE, true, p_test_only); + rem = len_current; + } else { + rem = p_state_machine->blend_node(current, p_state_machine->states[current].node, p_time, p_seek, p_is_external_seeking, fade_blend, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. + } + // Cross-fade process. if (fading_from != StringName()) { double fade_blend_inv = 1.0 - fade_blend; + fade_blend_inv = Math::is_zero_approx(fade_blend_inv) ? CMP_EPSILON : fade_blend_inv; + if (is_start_of_group) { + fade_blend_inv = 0.0; + } else if (is_end_of_group) { + fade_blend_inv = 1.0; + } + float fading_from_rem = 0.0; - fading_from_rem = p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, p_time, p_seek, p_is_external_seeking, Math::is_zero_approx(fade_blend_inv) ? CMP_EPSILON : fade_blend_inv, AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. - //guess playback position - if (fading_from_rem > len_fade_from) { // weird but ok + if (_reset_request_for_fading_from) { + _reset_request_for_fading_from = false; + fading_from_rem = p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, 0, true, p_is_external_seeking, fade_blend_inv, AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. + } else { + fading_from_rem = p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, p_time, p_seek, p_is_external_seeking, fade_blend_inv, AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. + } + + // Guess playback position. + if (fading_from_rem > len_fade_from) { /// Weird but ok. len_fade_from = fading_from_rem; } + pos_fade_from = len_fade_from - fading_from_rem; - { //advance and loop check - float next_pos = len_fade_from - fading_from_rem; - pos_fade_from = next_pos; //looped - } - if (fade_blend >= 1.0) { - fading_from = StringName(); + if (fading_pos >= fading_time) { + fading_from = StringName(); // Finish fading. } } - //guess playback position - if (rem > len_current) { // weird but ok + // Guess playback position. + if (rem > len_current) { // Weird but ok. len_current = rem; } + pos_current = len_current - rem; - { //advance and loop check - double next_pos = len_current - rem; - end_loop = next_pos < pos_current; - pos_current = next_pos; //looped + // Find next and see when to transition. + _transition_to_next_recursive(tree, p_state_machine, p_test_only); + + // Predict reamin time. + double remain = rem; // If we can't predict the end of state machine, the time remaining must be INFINITY. + + if (p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_NESTED) { + // There is no next transition. + if (!p_state_machine->has_transition_from(current)) { + if (fading_from != StringName()) { + remain = MAX(rem, fading_time - fading_pos); + } else { + remain = rem; + } + return remain; + } } - //find next - StringName next; - double next_xfade = 0.0; - AnimationNodeStateMachineTransition::SwitchMode switch_mode = AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE; + if (current == p_state_machine->end_node) { + if (fading_from != StringName()) { + remain = MAX(0, fading_time - fading_pos); + } else { + remain = 0; + } + return remain; + } + if (!is_end()) { + return HUGE_LENGTH; + } + + return remain; +} + +bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only) { + _reset_request_for_fading_from = false; + + bool is_state_changed = false; + + NextInfo next; + StringName transition_start = current; + while (true) { + next = _find_next(p_tree, p_state_machine); + if (next.node == transition_start) { + is_state_changed = false; + break; // Maybe infinity loop, do noting more. + } + + if (!_can_transition_to_next(p_tree, p_state_machine, next, p_test_only)) { + break; // Finish transition. + } + + is_state_changed = true; + + // Setting for fading. + if (next.xfade) { + // Time to fade. + fading_from = current; + fading_time = next.xfade; + fading_pos = 0; + } else { + if (reset_request) { + // There is no possibility of processing doubly. Now we can apply reset actually in here. + p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, false, 0, AnimationNode::FILTER_IGNORE, true, p_test_only); + } + fading_from = StringName(); + fading_time = 0; + fading_pos = 0; + } + + // If it came from path, remove path. + if (path.size()) { + path.remove_at(0); + } + + // Update current status. + _set_current(p_state_machine, next.node); + current_curve = next.curve; + + _reset_request_for_fading_from = reset_request; // To avoid processing doubly, it must be reset in the fading process within _process(). + reset_request = next.is_reset; + + pos_fade_from = pos_current; + len_fade_from = len_current; + + if (next.switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) { + p_state_machine->blend_node(current, p_state_machine->states[current].node, MIN(pos_current, len_current), true, false, 0, AnimationNode::FILTER_IGNORE, true); + } + + // Just get length to find next recursive. + double rem = 0.0; + if (next.is_reset) { + len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, false, 0, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process. + rem = len_current; + } else { + rem = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, false, false, 0, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process. + } + + // Guess playback position. + if (rem > len_current) { // Weird but ok. + len_current = rem; + } + pos_current = len_current - rem; + + // Fading must be processed. + if (fading_time) { + break; + } + } + + return is_state_changed; +} + +bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, NextInfo p_next, bool p_test_only) { + if (p_next.node == StringName()) { + return false; + } + + if (next_request) { + // Process request only once. + next_request = false; + // Next request must be applied to only deepest state machine. + Ref anodesm = p_state_machine->find_node_by_path(current); + if (anodesm.is_valid() && anodesm->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + Ref playback = p_tree->get(base_path + current + "/playback"); + ERR_FAIL_COND_V(!playback.is_valid(), false); + playback->_set_base_path(base_path + current + "/"); + if (p_test_only) { + playback = playback->duplicate(); + } + playback->_next_main(); + // Then, fadeing should be end. + fading_from = StringName(); + fading_pos = 0; + } else { + return true; + } + } + + if (fading_from != StringName()) { + return false; + } + + if (current != p_state_machine->start_node && p_next.switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_AT_END) { + return pos_current >= len_current - p_next.xfade; + } + return true; +} + +Ref AnimationNodeStateMachinePlayback::_check_group_transition(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const AnimationNodeStateMachine::Transition &p_transition, Ref &r_state_machine, bool &r_bypass) const { + Ref temp_transition; + Ref parent_playback; + if (r_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + if (p_transition.from == "Start") { + parent_playback = _get_parent_playback(p_tree); + if (parent_playback.is_valid()) { + r_bypass = true; + temp_transition = parent_playback->_get_group_start_transition(); + } + } else if (p_transition.to == "End") { + parent_playback = _get_parent_playback(p_tree); + if (parent_playback.is_valid()) { + temp_transition = parent_playback->_get_group_end_transition(); + } + } + if (temp_transition.is_valid()) { + r_state_machine = _get_parent_state_machine(p_tree); + return temp_transition; + } + } + return p_transition.transition; +} + +AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_find_next(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine) const { + NextInfo next; if (path.size()) { for (int i = 0; i < p_state_machine->transitions.size(); i++) { - if (p_state_machine->transitions[i].transition->get_advance_mode() == AnimationNodeStateMachineTransition::ADVANCE_MODE_DISABLED) { + Ref anodesm = p_state_machine; + bool bypass = false; + Ref ref_transition = _check_group_transition(p_tree, p_state_machine, p_state_machine->transitions[i], anodesm, bypass); + if (ref_transition->get_advance_mode() == AnimationNodeStateMachineTransition::ADVANCE_MODE_DISABLED) { continue; } - - if (p_state_machine->transitions[i].local_from == current && p_state_machine->transitions[i].local_to == path[0]) { - next_xfade = p_state_machine->transitions[i].transition->get_xfade_time(); - current_curve = p_state_machine->transitions[i].transition->get_xfade_curve(); - switch_mode = p_state_machine->transitions[i].transition->get_switch_mode(); - reset_request = p_state_machine->transitions[i].transition->is_reset(); - next = path[0]; + if (p_state_machine->transitions[i].from == current && p_state_machine->transitions[i].to == path[0]) { + next.node = path[0]; + next.xfade = ref_transition->get_xfade_time(); + next.curve = ref_transition->get_xfade_curve(); + next.switch_mode = ref_transition->get_switch_mode(); + next.is_reset = ref_transition->is_reset(); } } } else { - float priority_best = 1e20; int auto_advance_to = -1; - + float priority_best = 1e20; for (int i = 0; i < p_state_machine->transitions.size(); i++) { - if (p_state_machine->transitions[i].transition->get_advance_mode() == AnimationNodeStateMachineTransition::ADVANCE_MODE_DISABLED) { + Ref anodesm = p_state_machine; + bool bypass = false; + Ref ref_transition = _check_group_transition(p_tree, p_state_machine, p_state_machine->transitions[i], anodesm, bypass); + if (ref_transition->get_advance_mode() == AnimationNodeStateMachineTransition::ADVANCE_MODE_DISABLED) { continue; } - - // handles end_node: when end_node is reached in a sub state machine, find and activate the current_transition - if (force_auto_advance) { - if (p_state_machine->transitions[i].from == current_transition.from && p_state_machine->transitions[i].to == current_transition.to) { - auto_advance_to = i; - force_auto_advance = false; - break; - } - } - - // handles start_node: if previous state machine is pointing to a node inside the current state machine, starts the current machine from start_node to prev_local_to - if (p_state_machine->start_node == current && p_state_machine->transitions[i].local_from == current) { - if (p_state_machine->prev_state_machine != nullptr) { - Ref prev_playback = p_state_machine->prev_state_machine->get_parameter(p_state_machine->playback); - - if (prev_playback.is_valid()) { - StringName prev_local_to = String(prev_playback->current_transition.next).replace_first(String(p_state_machine->state_machine_name) + "/", ""); - - if (p_state_machine->transitions[i].to == prev_local_to) { - auto_advance_to = i; - prev_playback->current_transition.next = StringName(); - break; - } - } - } - } - - if (p_state_machine->transitions[i].from == current && _check_advance_condition(p_state_machine, p_state_machine->transitions[i].transition)) { - if (p_state_machine->transitions[i].transition->get_priority() <= priority_best) { - priority_best = p_state_machine->transitions[i].transition->get_priority(); + if (p_state_machine->transitions[i].from == current && (_check_advance_condition(anodesm, ref_transition) || bypass)) { + if (ref_transition->get_priority() <= priority_best) { + priority_best = ref_transition->get_priority(); auto_advance_to = i; } } } if (auto_advance_to != -1) { - next = p_state_machine->transitions[auto_advance_to].local_to; - Transition tr; - tr.from = String(p_state_machine->state_machine_name) + "/" + String(p_state_machine->transitions[auto_advance_to].from); - tr.to = String(p_state_machine->transitions[auto_advance_to].to).replace_first("../", ""); - tr.next = p_state_machine->transitions[auto_advance_to].to; - current_transition = tr; - current_curve = p_state_machine->transitions[auto_advance_to].transition->get_xfade_curve(); - next_xfade = p_state_machine->transitions[auto_advance_to].transition->get_xfade_time(); - switch_mode = p_state_machine->transitions[auto_advance_to].transition->get_switch_mode(); - reset_request = p_state_machine->transitions[auto_advance_to].transition->is_reset(); + next.node = p_state_machine->transitions[auto_advance_to].to; + Ref anodesm = p_state_machine; + bool bypass = false; + Ref ref_transition = _check_group_transition(p_tree, p_state_machine, p_state_machine->transitions[auto_advance_to], anodesm, bypass); + next.xfade = ref_transition->get_xfade_time(); + next.curve = ref_transition->get_xfade_curve(); + next.switch_mode = ref_transition->get_switch_mode(); + next.is_reset = ref_transition->is_reset(); } } - if (next == p_state_machine->end_node) { - AnimationNodeStateMachine *prev_state_machine = p_state_machine->prev_state_machine; - - if (prev_state_machine != nullptr) { - Ref prev_playback = prev_state_machine->get_parameter(p_state_machine->playback); - - if (prev_playback.is_valid()) { - if (next_xfade) { - prev_playback->current_transition = current_transition; - prev_playback->force_auto_advance = true; - - return rem; - } - float priority_best = 1e20; - int auto_advance_to = -1; - - for (int i = 0; i < prev_state_machine->transitions.size(); i++) { - if (prev_state_machine->transitions[i].transition->get_advance_mode() == AnimationNodeStateMachineTransition::ADVANCE_MODE_DISABLED) { - continue; - } - - if (current_transition.next == prev_state_machine->end_node && _check_advance_condition(prev_state_machine, prev_state_machine->transitions[i].transition)) { - if (prev_state_machine->transitions[i].transition->get_priority() <= priority_best) { - priority_best = prev_state_machine->transitions[i].transition->get_priority(); - auto_advance_to = i; - } - } - } - - if (auto_advance_to != -1) { - if (prev_state_machine->transitions[auto_advance_to].transition->get_xfade_time()) { - return rem; - } - } - } - } - } - - //if next, see when to transition - if (next != StringName()) { - bool goto_next = false; - - if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_AT_END) { - goto_next = next_xfade >= (len_current - pos_current) || end_loop; - if (end_loop) { - next_xfade = 0; - } - } else { - goto_next = fading_from == StringName(); - } - - if (next_request || goto_next) { //end_loop should be used because fade time may be too small or zero and animation may have looped - if (next_xfade) { - //time to fade, baby - fading_from = current; - fading_time = next_xfade; - fading_pos = 0; - } else { - fading_from = StringName(); - fading_pos = 0; - } - - if (path.size()) { //if it came from path, remove path - path.remove_at(0); - } - - { // if the current node is a state machine, update the "playing" variable to false by passing -1 in p_time - Ref anodesm = p_state_machine->states[current].node; - if (anodesm.is_valid()) { - p_state_machine->blend_node(current, p_state_machine->states[current].node, -1, p_seek, p_is_external_seeking, 0, AnimationNode::FILTER_IGNORE, true); - } - } - - current = next; - pos_fade_from = pos_current; - len_fade_from = len_current; - - if (reset_request) { - len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_is_external_seeking, CMP_EPSILON, AnimationNode::FILTER_IGNORE, true); // Process next node's first key in here. - } - if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) { - pos_current = MIN(pos_current, len_current); - p_state_machine->blend_node(current, p_state_machine->states[current].node, pos_current, true, p_is_external_seeking, 0, AnimationNode::FILTER_IGNORE, true); - } else { - pos_current = 0; - } - - rem = len_current; //so it does not show 0 on transition - } - } - - if (current != p_state_machine->end_node) { - rem = 1; // the time remaining must always be 1 because there is no way to predict how long it takes for the entire state machine to complete - } else { - if (p_state_machine->prev_state_machine != nullptr) { - Ref prev_playback = p_state_machine->prev_state_machine->get_parameter(p_state_machine->playback); - - if (prev_playback.is_valid()) { - prev_playback->current_transition = current_transition; - prev_playback->force_auto_advance = true; - } - } - } - - return rem; + return next; } bool AnimationNodeStateMachinePlayback::_check_advance_condition(const Ref state_machine, const Ref transition) const { @@ -724,6 +1113,63 @@ bool AnimationNodeStateMachinePlayback::_check_advance_condition(const Ref AnimationNodeStateMachinePlayback::_get_parent_playback(AnimationTree *p_tree) const { + if (base_path.is_empty()) { + return Ref(); + } + Vector split = base_path.split("/"); + ERR_FAIL_COND_V_MSG(split.size() < 2, Ref(), "Path is too short."); + StringName self_path = split[split.size() - 2]; + split.remove_at(split.size() - 2); + String playback_path = String("/").join(split) + "playback"; + Ref playback = p_tree->get(playback_path); + if (!playback.is_valid()) { + ERR_PRINT_ONCE("Can't get parent AnimationNodeStateMachinePlayback with path: " + playback_path + ". Maybe there is no Root/Nested AnimationNodeStateMachine in the parent of the Grouped AnimationNodeStateMachine."); + return Ref(); + } + if (playback->get_current_node() != self_path) { + return Ref(); + } + return playback; +} + +Ref AnimationNodeStateMachinePlayback::_get_parent_state_machine(AnimationTree *p_tree) const { + if (base_path.is_empty()) { + return Ref(); + } + Vector split = base_path.split("/"); + ERR_FAIL_COND_V_MSG(split.size() < 3, Ref(), "Path is too short."); + split = split.slice(1, split.size() - 2); + Ref root = p_tree->get_tree_root(); + ERR_FAIL_COND_V_MSG(root.is_null(), Ref(), "There is no root AnimationNode in AnimationTree: " + String(p_tree->get_name())); + String anodesm_path = String("/").join(split); + Ref anodesm = !anodesm_path.size() ? root : root->find_node_by_path(anodesm_path); + ERR_FAIL_COND_V_MSG(anodesm.is_null(), Ref(), "Can't get state machine with path: " + anodesm_path); + return anodesm; +} + +Ref AnimationNodeStateMachinePlayback::_get_group_start_transition() const { + ERR_FAIL_COND_V_MSG(group_start_transition.is_null(), Ref(), "Group start transition is null."); + return group_start_transition; +} + +Ref AnimationNodeStateMachinePlayback::_get_group_end_transition() const { + ERR_FAIL_COND_V_MSG(group_end_transition.is_null(), Ref(), "Group end transition is null."); + return group_end_transition; +} + void AnimationNodeStateMachinePlayback::_bind_methods() { ClassDB::bind_method(D_METHOD("travel", "to_node", "reset_on_teleport"), &AnimationNodeStateMachinePlayback::travel, DEFVAL(true)); ClassDB::bind_method(D_METHOD("start", "node", "reset"), &AnimationNodeStateMachinePlayback::start, DEFVAL(true)); @@ -738,13 +1184,18 @@ void AnimationNodeStateMachinePlayback::_bind_methods() { } AnimationNodeStateMachinePlayback::AnimationNodeStateMachinePlayback() { - set_local_to_scene(true); //only one per instantiated scene + set_local_to_scene(true); // Only one per instantiated scene. + default_transition.instantiate(); + default_transition->set_xfade_time(0); + default_transition->set_reset(true); + default_transition->set_advance_mode(AnimationNodeStateMachineTransition::ADVANCE_MODE_AUTO); + default_transition->set_switch_mode(AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE); } /////////////////////////////////////////////////////// void AnimationNodeStateMachine::get_parameter_list(List *r_list) const { - r_list->push_back(PropertyInfo(Variant::OBJECT, playback, PROPERTY_HINT_RESOURCE_TYPE, "AnimationNodeStateMachinePlayback", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE)); + r_list->push_back(PropertyInfo(Variant::OBJECT, playback, PROPERTY_HINT_RESOURCE_TYPE, "AnimationNodeStateMachinePlayback", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ALWAYS_DUPLICATE)); // Don't store this object in .tres, it always needs to be made as unique object. List advance_conditions; for (int i = 0; i < transitions.size(); i++) { StringName ac = transitions[i].transition->get_advance_condition_name(); @@ -765,10 +1216,17 @@ Variant AnimationNodeStateMachine::get_parameter_default_value(const StringName p.instantiate(); return p; } else { - return false; //advance condition + return false; // Advance condition. } } +bool AnimationNodeStateMachine::is_parameter_read_only(const StringName &p_parameter) const { + if (p_parameter == playback) { + return true; + } + return false; +} + void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref p_node, const Vector2 &p_position) { ERR_FAIL_COND(states.has(p_name)); ERR_FAIL_COND(p_node.is_null()); @@ -780,13 +1238,6 @@ void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref anodesm = p_node; - - if (anodesm.is_valid()) { - anodesm->state_machine_name = p_name; - anodesm->prev_state_machine = this; - } - emit_changed(); emit_signal(SNAME("tree_changed")); @@ -819,6 +1270,17 @@ void AnimationNodeStateMachine::replace_node(const StringName &p_name, Refconnect("animation_node_removed", callable_mp(this, &AnimationNodeStateMachine::_animation_node_removed), CONNECT_REFERENCE_COUNTED); } +void AnimationNodeStateMachine::set_state_machine_type(StateMachineType p_state_machine_type) { + state_machine_type = p_state_machine_type; + emit_changed(); + emit_signal(SNAME("tree_changed")); + notify_property_list_changed(); +} + +AnimationNodeStateMachine::StateMachineType AnimationNodeStateMachine::get_state_machine_type() const { + return state_machine_type; +} + void AnimationNodeStateMachine::set_allow_transition_to_self(bool p_enable) { allow_transition_to_self = p_enable; } @@ -827,6 +1289,14 @@ bool AnimationNodeStateMachine::is_allow_transition_to_self() const { return allow_transition_to_self; } +void AnimationNodeStateMachine::set_reset_ends(bool p_enable) { + reset_ends = p_enable; +} + +bool AnimationNodeStateMachine::are_ends_reset() const { + return reset_ends; +} + bool AnimationNodeStateMachine::can_edit_node(const StringName &p_name) const { if (states.has(p_name)) { return !(states[p_name].node->is_class("AnimationNodeStartState") || states[p_name].node->is_class("AnimationNodeEndState")); @@ -836,7 +1306,7 @@ bool AnimationNodeStateMachine::can_edit_node(const StringName &p_name) const { } Ref AnimationNodeStateMachine::get_node(const StringName &p_name) const { - ERR_FAIL_COND_V(!states.has(p_name), Ref()); + ERR_FAIL_COND_V_EDMSG(!states.has(p_name), Ref(), String(p_name) + " is not found current state."); return states[p_name].node; } @@ -880,7 +1350,7 @@ void AnimationNodeStateMachine::remove_node(const StringName &p_name) { } for (int i = 0; i < transitions.size(); i++) { - if (transitions[i].local_from == p_name || transitions[i].local_to == p_name) { + if (transitions[i].from == p_name || transitions[i].to == p_name) { remove_transition_by_index(i); i--; } @@ -909,11 +1379,6 @@ void AnimationNodeStateMachine::rename_node(const StringName &p_name, const Stri states[p_new_name] = states[p_name]; states.erase(p_name); - Ref anodesm = states[p_new_name].node; - if (anodesm.is_valid()) { - anodesm->state_machine_name = p_new_name; - } - _rename_transitions(p_name, p_new_name); emit_signal(SNAME("animation_node_renamed"), get_instance_id(), p_name, p_new_name); @@ -929,36 +1394,9 @@ void AnimationNodeStateMachine::_rename_transitions(const StringName &p_name, co updating_transitions = true; for (int i = 0; i < transitions.size(); i++) { if (transitions[i].from == p_name) { - Vector path = String(transitions[i].to).split("/"); - if (path.size() > 1) { - if (path[0] == "..") { - prev_state_machine->_rename_transitions(String(state_machine_name) + "/" + p_name, String(state_machine_name) + "/" + p_new_name); - } else { - ((Ref)states[transitions[i].local_to].node)->_rename_transitions("../" + p_name, "../" + p_new_name); - } - } - - if (transitions[i].local_from == p_name) { - transitions.write[i].local_from = p_new_name; - } - transitions.write[i].from = p_new_name; } - if (transitions[i].to == p_name) { - Vector path = String(transitions[i].from).split("/"); - if (path.size() > 1) { - if (path[0] == "..") { - prev_state_machine->_rename_transitions(String(state_machine_name) + "/" + p_name, String(state_machine_name) + "/" + p_new_name); - } else { - ((Ref)states[transitions[i].local_from].node)->_rename_transitions("../" + p_name, "../" + p_new_name); - } - } - - if (transitions[i].local_to == p_name) { - transitions.write[i].local_to = p_new_name; - } - transitions.write[i].to = p_new_name; } } @@ -977,16 +1415,27 @@ void AnimationNodeStateMachine::get_node_list(List *r_nodes) const { } } -AnimationNodeStateMachine *AnimationNodeStateMachine::get_prev_state_machine() const { - return prev_state_machine; +bool AnimationNodeStateMachine::has_transition(const StringName &p_from, const StringName &p_to) const { + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_from && transitions[i].to == p_to) { + return true; + } + } + return false; } -bool AnimationNodeStateMachine::has_transition(const StringName &p_from, const StringName &p_to) const { - StringName from = _get_shortest_path(p_from); - StringName to = _get_shortest_path(p_to); - +bool AnimationNodeStateMachine::has_transition_from(const StringName &p_from) const { for (int i = 0; i < transitions.size(); i++) { - if (transitions[i].from == from && transitions[i].to == to) { + if (transitions[i].from == p_from) { + return true; + } + } + return false; +} + +bool AnimationNodeStateMachine::has_transition_to(const StringName &p_to) const { + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].to == p_to) { return true; } } @@ -994,33 +1443,36 @@ bool AnimationNodeStateMachine::has_transition(const StringName &p_from, const S } int AnimationNodeStateMachine::find_transition(const StringName &p_from, const StringName &p_to) const { - StringName from = _get_shortest_path(p_from); - StringName to = _get_shortest_path(p_to); - for (int i = 0; i < transitions.size(); i++) { - if (transitions[i].from == from && transitions[i].to == to) { + if (transitions[i].from == p_from && transitions[i].to == p_to) { return i; } } return -1; } -bool AnimationNodeStateMachine::_can_connect(const StringName &p_name, Vector p_parents) { - if (p_parents.is_empty()) { - AnimationNodeStateMachine *prev = this; - while (prev != nullptr) { - p_parents.push_back(prev); - prev = prev->prev_state_machine; +Vector AnimationNodeStateMachine::find_transition_from(const StringName &p_from) const { + Vector ret; + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_from) { + ret.push_back(i); } } + return ret; +} - if (states.has(p_name)) { - Ref anodesm = states[p_name].node; - - if (anodesm.is_valid() && p_parents.find(anodesm.ptr()) != -1) { - return false; +Vector AnimationNodeStateMachine::find_transition_to(const StringName &p_to) const { + Vector ret; + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].to == p_to) { + ret.push_back(i); } + } + return ret; +} +bool AnimationNodeStateMachine::_can_connect(const StringName &p_name) { + if (states.has(p_name)) { return true; } @@ -1031,110 +1483,35 @@ bool AnimationNodeStateMachine::_can_connect(const StringName &p_name, Vector_can_connect(node_name.replace_first("../", ""), p_parents); - } - } else if (states.has(path[0])) { - Ref anodesm = states[path[0]].node; - if (anodesm.is_valid()) { - return anodesm->_can_connect(node_name.replace_first(path[0] + "/", ""), p_parents); - } - } - return false; } -StringName AnimationNodeStateMachine::_get_shortest_path(const StringName &p_path) const { - // If p_path is something like StateMachine/../StateMachine2/State1, - // the result will be StateMachine2/State1. This avoid duplicate - // transitions when using add_transition. eg, this two calls is the same: - // - // add_transition("State1", "StateMachine/../State2", tr) - // add_transition("State1", "State2", tr) - // - // but the second call must be invalid because the transition already exists - - Vector path = String(p_path).split("/"); - Vector new_path; - - for (int i = 0; i < path.size(); i++) { - if (i > 0 && path[i] == ".." && new_path[i - 1] != "..") { - new_path.remove_at(i - 1); - } else { - new_path.push_back(path[i]); - } - } - - String result; - for (int i = 0; i < new_path.size(); i++) { - result += new_path[i] + "/"; - } - result.remove_at(result.length() - 1); - - return result; -} - void AnimationNodeStateMachine::add_transition(const StringName &p_from, const StringName &p_to, const Ref &p_transition) { if (updating_transitions) { return; } - StringName from = _get_shortest_path(p_from); - StringName to = _get_shortest_path(p_to); - Vector path_from = String(from).split("/"); - Vector path_to = String(to).split("/"); - - ERR_FAIL_COND(from == end_node || to == start_node); - ERR_FAIL_COND(from == to); - ERR_FAIL_COND(!_can_connect(from)); - ERR_FAIL_COND(!_can_connect(to)); + ERR_FAIL_COND(p_from == end_node || p_to == start_node); + ERR_FAIL_COND(p_from == p_to); + ERR_FAIL_COND(!_can_connect(p_from)); + ERR_FAIL_COND(!_can_connect(p_to)); ERR_FAIL_COND(p_transition.is_null()); for (int i = 0; i < transitions.size(); i++) { - ERR_FAIL_COND(transitions[i].from == from && transitions[i].to == to); - } - - if (path_from.size() > 1 || path_to.size() > 1) { - ERR_FAIL_COND(path_from[0] == path_to[0]); + ERR_FAIL_COND(transitions[i].from == p_from && transitions[i].to == p_to); } updating_transitions = true; - StringName local_from = String(from).get_slicec('/', 0); - StringName local_to = String(to).get_slicec('/', 0); - local_from = local_from == ".." ? "Start" : local_from; - local_to = local_to == ".." ? "End" : local_to; - Transition tr; - tr.from = from; - tr.to = to; - tr.local_from = local_from; - tr.local_to = local_to; + tr.from = p_from; + tr.to = p_to; tr.transition = p_transition; tr.transition->connect("advance_condition_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed), CONNECT_REFERENCE_COUNTED); transitions.push_back(tr); - // do recursive - if (path_from.size() > 1) { - StringName local_path = String(from).replace_first(path_from[0] + "/", ""); - if (path_from[0] == "..") { - prev_state_machine->add_transition(local_path, String(state_machine_name) + "/" + to, p_transition); - } else { - ((Ref)states[path_from[0]].node)->add_transition(local_path, "../" + to, p_transition); - } - } - if (path_to.size() > 1) { - StringName local_path = String(to).replace_first(path_to[0] + "/", ""); - if (path_to[0] == "..") { - prev_state_machine->add_transition(String(state_machine_name) + "/" + from, local_path, p_transition); - } else { - ((Ref)states[path_to[0]].node)->add_transition("../" + from, local_path, p_transition); - } - } - updating_transitions = false; } @@ -1153,16 +1530,23 @@ StringName AnimationNodeStateMachine::get_transition_to(int p_transition) const return transitions[p_transition].to; } +bool AnimationNodeStateMachine::is_transition_across_group(int p_transition) const { + ERR_FAIL_INDEX_V(p_transition, transitions.size(), false); + if (get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { + if (transitions[p_transition].from == "Start" || transitions[p_transition].to == "End") { + return true; + } + } + return false; +} + int AnimationNodeStateMachine::get_transition_count() const { return transitions.size(); } void AnimationNodeStateMachine::remove_transition(const StringName &p_from, const StringName &p_to) { - StringName from = _get_shortest_path(p_from); - StringName to = _get_shortest_path(p_to); - for (int i = 0; i < transitions.size(); i++) { - if (transitions[i].from == from && transitions[i].to == to) { + if (transitions[i].from == p_from && transitions[i].to == p_to) { remove_transition_by_index(i); return; } @@ -1181,20 +1565,6 @@ void AnimationNodeStateMachine::remove_transition_by_index(const int p_transitio List> paths; paths.push_back(path_from); paths.push_back(path_to); - - for (List>::Element *E = paths.front(); E; E = E->next()) { - if (E->get()[0].size() > 1) { - if (E->get()[0] == "..") { - prev_state_machine->_remove_transition(tr.transition); - } else if (states.has(E->get()[0])) { - Ref anodesm = states[E->get()[0]].node; - - if (anodesm.is_valid()) { - anodesm->_remove_transition(tr.transition); - } - } - } - } } void AnimationNodeStateMachine::_remove_transition(const Ref p_transition) { @@ -1214,30 +1584,21 @@ Vector2 AnimationNodeStateMachine::get_graph_offset() const { return graph_offset; } -double AnimationNodeStateMachine::process(double p_time, bool p_seek, bool p_is_external_seeking) { +double AnimationNodeStateMachine::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { Ref playback_new = get_parameter(playback); ERR_FAIL_COND_V(playback_new.is_null(), 0.0); - - return playback_new->process(this, p_time, p_seek, p_is_external_seeking); + playback_new->_set_grouped(state_machine_type == STATE_MACHINE_TYPE_GROUPED); + if (p_test_only) { + playback_new = playback_new->duplicate(); // Don't process original when testing. + } + return playback_new->process(base_path, this, p_time, p_seek, p_is_external_seeking, p_test_only); } String AnimationNodeStateMachine::get_caption() const { return "StateMachine"; } -bool AnimationNodeStateMachine::has_local_transition(const StringName &p_from, const StringName &p_to) const { - StringName from = _get_shortest_path(p_from); - StringName to = _get_shortest_path(p_to); - - for (int i = 0; i < transitions.size(); i++) { - if (transitions[i].local_from == from && transitions[i].local_to == to) { - return true; - } - } - return false; -} - -Ref AnimationNodeStateMachine::get_child_by_name(const StringName &p_name) { +Ref AnimationNodeStateMachine::get_child_by_name(const StringName &p_name) const { return get_node(p_name); } @@ -1302,10 +1663,6 @@ bool AnimationNodeStateMachine::_get(const StringName &p_name, Variant &r_ret) c String from = transitions[i].from; String to = transitions[i].to; - if (from.get_slicec('/', 0) == ".." || to.get_slicec('/', 0) == "..") { - continue; - } - trans.push_back(from); trans.push_back(to); trans.push_back(transitions[i].transition); @@ -1335,6 +1692,18 @@ void AnimationNodeStateMachine::_get_property_list(List *p_list) c p_list->push_back(PropertyInfo(Variant::ARRAY, "transitions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); p_list->push_back(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + + for (PropertyInfo &E : *p_list) { + _validate_property(E); + } +} + +void AnimationNodeStateMachine::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "allow_transition_to_self" || p_property.name == "reset_ends") { + if (state_machine_type == STATE_MACHINE_TYPE_GROUPED) { + p_property.usage = PROPERTY_USAGE_NONE; + } + } } void AnimationNodeStateMachine::reset_state() { @@ -1410,10 +1779,22 @@ void AnimationNodeStateMachine::_bind_methods() { ClassDB::bind_method(D_METHOD("set_graph_offset", "offset"), &AnimationNodeStateMachine::set_graph_offset); ClassDB::bind_method(D_METHOD("get_graph_offset"), &AnimationNodeStateMachine::get_graph_offset); + ClassDB::bind_method(D_METHOD("set_state_machine_type", "state_machine_type"), &AnimationNodeStateMachine::set_state_machine_type); + ClassDB::bind_method(D_METHOD("get_state_machine_type"), &AnimationNodeStateMachine::get_state_machine_type); + ClassDB::bind_method(D_METHOD("set_allow_transition_to_self", "enable"), &AnimationNodeStateMachine::set_allow_transition_to_self); ClassDB::bind_method(D_METHOD("is_allow_transition_to_self"), &AnimationNodeStateMachine::is_allow_transition_to_self); + ClassDB::bind_method(D_METHOD("set_reset_ends", "enable"), &AnimationNodeStateMachine::set_reset_ends); + ClassDB::bind_method(D_METHOD("are_ends_reset"), &AnimationNodeStateMachine::are_ends_reset); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "state_machine_type", PROPERTY_HINT_ENUM, "Root,Nested,Grouped"), "set_state_machine_type", "get_state_machine_type"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_transition_to_self"), "set_allow_transition_to_self", "is_allow_transition_to_self"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset_ends"), "set_reset_ends", "are_ends_reset"); + + BIND_ENUM_CONSTANT(STATE_MACHINE_TYPE_ROOT); + BIND_ENUM_CONSTANT(STATE_MACHINE_TYPE_NESTED); + BIND_ENUM_CONSTANT(STATE_MACHINE_TYPE_GROUPED); } AnimationNodeStateMachine::AnimationNodeStateMachine() { diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index 5867b6c65ac..757c6ddc68b 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -100,88 +100,23 @@ public: VARIANT_ENUM_CAST(AnimationNodeStateMachineTransition::SwitchMode) VARIANT_ENUM_CAST(AnimationNodeStateMachineTransition::AdvanceMode) -class AnimationNodeStateMachine; - -class AnimationNodeStateMachinePlayback : public Resource { - GDCLASS(AnimationNodeStateMachinePlayback, Resource); - - friend class AnimationNodeStateMachine; - - struct AStarCost { - float distance = 0.0; - StringName prev; - }; - - struct Transition { - StringName from; - StringName to; - StringName next; - }; - - double len_fade_from = 0.0; - double pos_fade_from = 0.0; - - double len_current = 0.0; - double pos_current = 0.0; - bool end_loop = false; - - StringName current; - Transition current_transition; - Ref current_curve; - bool force_auto_advance = false; - - StringName fading_from; - float fading_time = 0.0; - float fading_pos = 0.0; - - Vector path; - bool playing = false; - - StringName start_request; - StringName travel_request; - bool reset_request = false; - bool reset_request_on_teleport = false; - bool next_request = false; - bool stop_request = false; - - bool _travel(AnimationNodeStateMachine *p_state_machine, const StringName &p_travel); - void _start(const StringName &p_state); - double _process(AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking); - - double process(AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking); - - bool _check_advance_condition(const Ref p_state_machine, const Ref p_transition) const; - -protected: - static void _bind_methods(); - -public: - void travel(const StringName &p_state, bool p_reset_on_teleport = true); - void start(const StringName &p_state, bool p_reset = true); - void next(); - void stop(); - bool is_playing() const; - StringName get_current_node() const; - StringName get_fading_from_node() const; - Vector get_travel_path() const; - float get_current_play_pos() const; - float get_current_length() const; - - float get_fade_from_play_pos() const; - float get_fade_from_length() const; - - float get_fading_time() const; - float get_fading_pos() const; - - AnimationNodeStateMachinePlayback(); -}; +class AnimationNodeStateMachinePlayback; class AnimationNodeStateMachine : public AnimationRootNode { GDCLASS(AnimationNodeStateMachine, AnimationRootNode); +public: + enum StateMachineType { + STATE_MACHINE_TYPE_ROOT, + STATE_MACHINE_TYPE_NESTED, + STATE_MACHINE_TYPE_GROUPED, + }; + private: friend class AnimationNodeStateMachinePlayback; + StateMachineType state_machine_type = STATE_MACHINE_TYPE_ROOT; + struct State { Ref node; Vector2 position; @@ -189,28 +124,24 @@ private: HashMap states; bool allow_transition_to_self = false; + bool reset_ends = false; struct Transition { StringName from; StringName to; - StringName local_from; - StringName local_to; Ref transition; }; Vector transitions; StringName playback = "playback"; - StringName state_machine_name; - AnimationNodeStateMachine *prev_state_machine = nullptr; bool updating_transitions = false; Vector2 graph_offset; void _remove_transition(const Ref p_transition); void _rename_transitions(const StringName &p_name, const StringName &p_new_name); - bool _can_connect(const StringName &p_name, Vector p_parents = Vector()); - StringName _get_shortest_path(const StringName &p_path) const; + bool _can_connect(const StringName &p_name); protected: static void _bind_methods(); @@ -218,6 +149,8 @@ protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List *p_list) const; + void _validate_property(PropertyInfo &p_property) const; + bool _check_advance_condition(const Ref p_state_machine, const Ref p_transition) const; virtual void _tree_changed() override; @@ -232,6 +165,7 @@ public: virtual void get_parameter_list(List *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; + virtual bool is_parameter_read_only(const StringName &p_parameter) const override; void add_node(const StringName &p_name, Ref p_node, const Vector2 &p_position = Vector2()); void replace_node(const StringName &p_name, Ref p_node); @@ -248,32 +182,164 @@ public: virtual void get_child_nodes(List *r_child_nodes) override; bool has_transition(const StringName &p_from, const StringName &p_to) const; - bool has_local_transition(const StringName &p_from, const StringName &p_to) const; + bool has_transition_from(const StringName &p_from) const; + bool has_transition_to(const StringName &p_to) const; int find_transition(const StringName &p_from, const StringName &p_to) const; + Vector find_transition_from(const StringName &p_from) const; + Vector find_transition_to(const StringName &p_to) const; void add_transition(const StringName &p_from, const StringName &p_to, const Ref &p_transition); Ref get_transition(int p_transition) const; StringName get_transition_from(int p_transition) const; StringName get_transition_to(int p_transition) const; int get_transition_count() const; + bool is_transition_across_group(int p_transition) const; void remove_transition_by_index(const int p_transition); void remove_transition(const StringName &p_from, const StringName &p_to); + void set_state_machine_type(StateMachineType p_state_machine_type); + StateMachineType get_state_machine_type() const; + void set_allow_transition_to_self(bool p_enable); bool is_allow_transition_to_self() const; - bool can_edit_node(const StringName &p_name) const; + void set_reset_ends(bool p_enable); + bool are_ends_reset() const; - AnimationNodeStateMachine *get_prev_state_machine() const; + bool can_edit_node(const StringName &p_name) const; void set_graph_offset(const Vector2 &p_offset); Vector2 get_graph_offset() const; - virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override; + virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; virtual String get_caption() const override; - virtual Ref get_child_by_name(const StringName &p_name) override; + virtual Ref get_child_by_name(const StringName &p_name) const override; AnimationNodeStateMachine(); }; +VARIANT_ENUM_CAST(AnimationNodeStateMachine::StateMachineType); + +class AnimationNodeStateMachinePlayback : public Resource { + GDCLASS(AnimationNodeStateMachinePlayback, Resource); + + friend class AnimationNodeStateMachine; + + struct AStarCost { + float distance = 0.0; + StringName prev; + }; + + struct TransitionInfo { + StringName from; + StringName to; + StringName next; + }; + + struct NextInfo { + StringName node; + double xfade; + Ref curve; + AnimationNodeStateMachineTransition::SwitchMode switch_mode; + bool is_reset; + }; + + struct ChildStateMachineInfo { + Ref playback; + Vector path; + bool is_reset = false; + }; + + Ref default_transition; + String base_path; + + double len_fade_from = 0.0; + double pos_fade_from = 0.0; + + double len_current = 0.0; + double pos_current = 0.0; + + StringName current; + Ref current_curve; + + Ref group_start_transition; + Ref group_end_transition; + + StringName fading_from; + float fading_time = 0.0; + float fading_pos = 0.0; + + Vector path; + bool playing = false; + + StringName start_request; + StringName travel_request; + bool reset_request = false; + bool reset_request_on_teleport = false; + bool _reset_request_for_fading_from = false; + bool next_request = false; + bool stop_request = false; + bool teleport_request = false; + + bool is_grouped = false; + + void _travel_main(const StringName &p_state, bool p_reset_on_teleport = true); + void _start_main(const StringName &p_state, bool p_reset = true); + void _next_main(); + void _stop_main(); + + bool _make_travel_path(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, Vector &r_path, bool p_test_only); + String _validate_path(AnimationNodeStateMachine *p_state_machine, const String &p_path); + bool _travel(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, bool p_test_only); + void _start(AnimationNodeStateMachine *p_state_machine); + + void _clear_path_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only); + bool _travel_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_is_allow_transition_to_self, bool p_is_parent_same_state, bool p_test_only); + void _start_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_test_only); + + double process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only); + double _process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only); + + bool _check_advance_condition(const Ref p_state_machine, const Ref p_transition) const; + bool _transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only); + NextInfo _find_next(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine) const; + Ref _check_group_transition(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const AnimationNodeStateMachine::Transition &p_transition, Ref &r_state_machine, bool &r_bypass) const; + bool _can_transition_to_next(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, NextInfo p_next, bool p_test_only); + + void _set_current(AnimationNodeStateMachine *p_state_machine, const StringName &p_state); + void _set_grouped(bool p_is_grouped); + void _set_base_path(const String &p_base_path); + Ref _get_parent_playback(AnimationTree *p_tree) const; + Ref _get_parent_state_machine(AnimationTree *p_tree) const; + Ref _get_group_start_transition() const; + Ref _get_group_end_transition() const; + +protected: + static void _bind_methods(); + +public: + void travel(const StringName &p_state, bool p_reset_on_teleport = true); + void start(const StringName &p_state, bool p_reset = true); + void next(); + void stop(); + bool is_playing() const; + bool is_end() const; + StringName get_current_node() const; + StringName get_fading_from_node() const; + Vector get_travel_path() const; + float get_current_play_pos() const; + float get_current_length() const; + + float get_fade_from_play_pos() const; + float get_fade_from_length() const; + + float get_fading_time() const; + float get_fading_pos() const; + + void clear_path(); + void push_path(const StringName &p_state); + + AnimationNodeStateMachinePlayback(); +}; + #endif // ANIMATION_NODE_STATE_MACHINE_H diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 9f9916c1c62..de9824a5156 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -61,6 +61,9 @@ bool AnimationNode::is_parameter_read_only(const StringName &p_parameter) const } void AnimationNode::set_parameter(const StringName &p_name, const Variant &p_value) { + if (is_testing) { + return; + } ERR_FAIL_COND(!state); ERR_FAIL_COND(!state->tree->property_parent_map.has(base_path)); ERR_FAIL_COND(!state->tree->property_parent_map[base_path].has(p_name)); @@ -124,13 +127,13 @@ void AnimationNode::blend_animation(const StringName &p_animation, double p_time state->animation_states.push_back(anim_state); } -double AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, bool p_is_external_seeking, const Vector &p_connections) { +double AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, bool p_is_external_seeking, const Vector &p_connections, bool p_test_only) { base_path = p_base_path; parent = p_parent; connections = p_connections; state = p_state; - double t = process(p_time, p_seek, p_is_external_seeking); + double t = process(p_time, p_seek, p_is_external_seeking, p_test_only); state = nullptr; parent = nullptr; @@ -154,7 +157,7 @@ void AnimationNode::make_invalid(const String &p_reason) { state->invalid_reasons += String::utf8("• ") + p_reason; } -double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync) { +double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, bool p_test_only) { ERR_FAIL_INDEX_V(p_input, inputs.size(), 0); ERR_FAIL_COND_V(!state, 0); @@ -173,7 +176,7 @@ double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, bool //inputs.write[p_input].last_pass = state->last_pass; real_t activity = 0.0; - double ret = _blend_node(node_name, blend_tree->get_node_connection_array(node_name), nullptr, node, p_time, p_seek, p_is_external_seeking, p_blend, p_filter, p_sync, &activity); + double ret = _blend_node(node_name, blend_tree->get_node_connection_array(node_name), nullptr, node, p_time, p_seek, p_is_external_seeking, p_blend, p_filter, p_sync, &activity, p_test_only); Vector *activity_ptr = state->tree->input_activity_map.getptr(base_path); @@ -184,11 +187,11 @@ double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, bool return ret; } -double AnimationNode::blend_node(const StringName &p_sub_path, Ref p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync) { - return _blend_node(p_sub_path, Vector(), this, p_node, p_time, p_seek, p_is_external_seeking, p_blend, p_filter, p_sync); +double AnimationNode::blend_node(const StringName &p_sub_path, Ref p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, bool p_test_only) { + return _blend_node(p_sub_path, Vector(), this, p_node, p_time, p_seek, p_is_external_seeking, p_blend, p_filter, p_sync, nullptr, p_test_only); } -double AnimationNode::_blend_node(const StringName &p_subpath, const Vector &p_connections, AnimationNode *p_new_parent, Ref p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, real_t *r_max) { +double AnimationNode::_blend_node(const StringName &p_subpath, const Vector &p_connections, AnimationNode *p_new_parent, Ref p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, real_t *r_max, bool p_test_only) { ERR_FAIL_COND_V(!p_node.is_valid(), 0); ERR_FAIL_COND_V(!state, 0); @@ -298,9 +301,9 @@ double AnimationNode::_blend_node(const StringName &p_subpath, const Vector_pre_process(new_path, new_parent, state, 0, p_seek, p_is_external_seeking, p_connections); + return p_node->_pre_process(new_path, new_parent, state, 0, p_seek, p_is_external_seeking, p_connections, p_test_only); } - return p_node->_pre_process(new_path, new_parent, state, p_time, p_seek, p_is_external_seeking, p_connections); + return p_node->_pre_process(new_path, new_parent, state, p_time, p_seek, p_is_external_seeking, p_connections, p_test_only); } String AnimationNode::get_caption() const { @@ -354,9 +357,14 @@ int AnimationNode::find_input(const String &p_name) const { return idx; } -double AnimationNode::process(double p_time, bool p_seek, bool p_is_external_seeking) { +double AnimationNode::process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { + is_testing = p_test_only; + return _process(p_time, p_seek, p_is_external_seeking, p_test_only); +} + +double AnimationNode::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { double ret = 0; - GDVIRTUAL_CALL(_process, p_time, p_seek, p_is_external_seeking, ret); + GDVIRTUAL_CALL(_process, p_time, p_seek, p_is_external_seeking, p_test_only, ret); return ret; } @@ -410,12 +418,24 @@ void AnimationNode::_validate_property(PropertyInfo &p_property) const { } } -Ref AnimationNode::get_child_by_name(const StringName &p_name) { +Ref AnimationNode::get_child_by_name(const StringName &p_name) const { Ref ret; GDVIRTUAL_CALL(_get_child_by_name, p_name, ret); return ret; } +Ref AnimationNode::find_node_by_path(const String &p_name) const { + Vector split = p_name.split("/"); + Ref ret = const_cast(this); + for (int i = 0; i < split.size(); i++) { + ret = ret->get_child_by_name(split[i]); + if (!ret.is_valid()) { + break; + } + } + return ret; +} + void AnimationNode::_bind_methods() { ClassDB::bind_method(D_METHOD("add_input", "name"), &AnimationNode::add_input); ClassDB::bind_method(D_METHOD("remove_input", "index"), &AnimationNode::remove_input); @@ -434,8 +454,8 @@ void AnimationNode::_bind_methods() { ClassDB::bind_method(D_METHOD("_get_filters"), &AnimationNode::_get_filters); ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "is_external_seeking", "blend", "looped_flag"), &AnimationNode::blend_animation, DEFVAL(Animation::LOOPED_FLAG_NONE)); - ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "is_external_seeking", "blend", "filter", "sync"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true)); - ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "is_external_seeking", "blend", "filter", "sync"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "is_external_seeking", "blend", "filter", "sync", "test_only"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "is_external_seeking", "blend", "filter", "sync", "test_only"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true), DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_parameter", "name", "value"), &AnimationNode::set_parameter); ClassDB::bind_method(D_METHOD("get_parameter", "name"), &AnimationNode::get_parameter); @@ -448,7 +468,7 @@ void AnimationNode::_bind_methods() { GDVIRTUAL_BIND(_get_child_by_name, "name"); GDVIRTUAL_BIND(_get_parameter_default_value, "parameter"); GDVIRTUAL_BIND(_is_parameter_read_only, "parameter"); - GDVIRTUAL_BIND(_process, "time", "seek", "is_external_seeking"); + GDVIRTUAL_BIND(_process, "time", "seek", "is_external_seeking", "test_only"); GDVIRTUAL_BIND(_get_caption); GDVIRTUAL_BIND(_has_filter); diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index c68cae56eaf..8ee8d300142 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -37,6 +37,8 @@ #include "scene/resources/animation.h" #include "scene/resources/audio_stream_polyphonic.h" +#define HUGE_LENGTH 31540000 // 31540000 seconds mean 1 year... is it too long? It must be longer than any Animation length and Transition xfade time to prevent time inversion. + class AnimationNodeBlendTree; class AnimationNodeStartState; class AnimationNodeEndState; @@ -87,7 +89,9 @@ public: Vector blends; State *state = nullptr; - double _pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, bool p_is_external_seeking, const Vector &p_connections); + bool is_testing = false; + + double _pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, bool p_is_external_seeking, const Vector &p_connections, bool p_test_only = false); //all this is temporary StringName base_path; @@ -100,12 +104,15 @@ public: Array _get_filters() const; void _set_filters(const Array &p_filters); friend class AnimationNodeBlendTree; - double _blend_node(const StringName &p_subpath, const Vector &p_connections, AnimationNode *p_new_parent, Ref p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, real_t *r_max = nullptr); + double _blend_node(const StringName &p_subpath, const Vector &p_connections, AnimationNode *p_new_parent, Ref p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, real_t *r_max = nullptr, bool p_test_only = false); protected: + virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false); + double process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false); + void blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE); - double blend_node(const StringName &p_sub_path, Ref p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true); - double blend_input(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true); + double blend_node(const StringName &p_sub_path, Ref p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); + double blend_input(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); void make_invalid(const String &p_reason); AnimationTree *get_animation_tree() const; @@ -119,7 +126,7 @@ protected: GDVIRTUAL1RC(Ref, _get_child_by_name, StringName) GDVIRTUAL1RC(Variant, _get_parameter_default_value, StringName) GDVIRTUAL1RC(bool, _is_parameter_read_only, StringName) - GDVIRTUAL3RC(double, _process, double, bool, bool) + GDVIRTUAL4RC(double, _process, double, bool, bool, bool) GDVIRTUAL0RC(String, _get_caption) GDVIRTUAL0RC(bool, _has_filter) @@ -138,7 +145,6 @@ public: virtual void get_child_nodes(List *r_child_nodes); - virtual double process(double p_time, bool p_seek, bool p_is_external_seeking); virtual String get_caption() const; virtual bool add_input(const String &p_name); @@ -156,7 +162,8 @@ public: virtual bool has_filter() const; - virtual Ref get_child_by_name(const StringName &p_name); + virtual Ref get_child_by_name(const StringName &p_name) const; + Ref find_node_by_path(const String &p_name) const; AnimationNode(); };