Add node_notify signal for animation trees

This commit is contained in:
Adam Johnston 2025-02-03 15:50:06 -08:00
parent eee39f004b
commit 2c7c814a60
8 changed files with 59 additions and 7 deletions

View File

@ -244,5 +244,11 @@
<constant name="FILTER_BLEND" value="3" enum="FilterAction">
Paths matching the filter will be blended (by the blend value).
</constant>
<constant name="NOTIFY_STARTED" value="0" enum="NotifyReason">
The node has started playback.
</constant>
<constant name="NOTIFY_FINISHED" value="1" enum="NotifyReason">
The node has finished playback.
</constant>
</constants>
</class>

View File

@ -45,6 +45,13 @@
Emitted when the [member anim_player] is changed.
</description>
</signal>
<signal name="node_notify">
<param index="0" name="node" type="StringName" />
<param index="1" name="reason" type="int" />
<description>
Emitted when the [param node] state changes during processing or playback.
</description>
</signal>
</signals>
<constants>
<constant name="ANIMATION_PROCESS_PHYSICS" value="0" enum="AnimationProcessCallback" deprecated="See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS].">

View File

@ -696,6 +696,10 @@ AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer:
set_parameter(fade_in_remaining, cur_fade_in_remaining);
set_parameter(fade_out_remaining, cur_fade_out_remaining);
if (cur_active != (bool)get_parameter(active)) {
_notify_tree(cur_active ? NOTIFY_FINISHED : NOTIFY_STARTED);
}
return cur_internal_active ? os_nti : main_nti;
}
@ -1451,6 +1455,7 @@ AnimationNodeOutput::AnimationNodeOutput() {
void AnimationNodeBlendTree::add_node(const StringName &p_name, Ref<AnimationNode> p_node, const Vector2 &p_position) {
ERR_FAIL_COND(nodes.has(p_name));
ERR_FAIL_COND(p_node.is_null());
ERR_FAIL_COND(node_to_name.has(*p_node));
ERR_FAIL_COND(p_name == SceneStringName(output));
ERR_FAIL_COND(String(p_name).contains_char('/'));
@ -1459,6 +1464,7 @@ void AnimationNodeBlendTree::add_node(const StringName &p_name, Ref<AnimationNod
n.position = p_position;
n.connections.resize(n.node->get_input_count());
nodes[p_name] = n;
node_to_name[*p_node] = p_name;
emit_changed();
emit_signal(SNAME("tree_changed"));
@ -1476,12 +1482,10 @@ Ref<AnimationNode> AnimationNodeBlendTree::get_node(const StringName &p_name) co
}
StringName AnimationNodeBlendTree::get_node_name(const Ref<AnimationNode> &p_node) const {
for (const KeyValue<StringName, Node> &E : nodes) {
if (E.value.node == p_node) {
return E.key;
}
AHashMap<AnimationNode *, StringName>::ConstIterator E = node_to_name.find(*p_node);
if (E) {
return E->value;
}
ERR_FAIL_V(StringName());
}
@ -1525,6 +1529,7 @@ void AnimationNodeBlendTree::remove_node(const StringName &p_name) {
{
Ref<AnimationNode> node = nodes[p_name].node;
node_to_name.erase(*node);
node->disconnect(SNAME("tree_changed"), callable_mp(this, &AnimationNodeBlendTree::_tree_changed));
node->disconnect(SNAME("animation_node_renamed"), callable_mp(this, &AnimationNodeBlendTree::_animation_node_renamed));
node->disconnect(SNAME("animation_node_removed"), callable_mp(this, &AnimationNodeBlendTree::_animation_node_removed));
@ -1554,8 +1559,8 @@ void AnimationNodeBlendTree::rename_node(const StringName &p_name, const StringN
ERR_FAIL_COND(p_new_name == SceneStringName(output));
nodes[p_name].node->disconnect_changed(callable_mp(this, &AnimationNodeBlendTree::_node_changed));
nodes[p_new_name] = nodes[p_name];
node_to_name[*(nodes[p_new_name].node)] = p_new_name;
nodes.erase(p_name);
// Rename connections.
@ -1853,7 +1858,8 @@ void AnimationNodeBlendTree::_initialize_node_tree() {
n.node = output;
n.position = Vector2(300, 150);
n.connections.resize(1);
nodes["output"] = n;
nodes[SceneStringName(output)] = n;
node_to_name[*n.node] = SceneStringName(output);
}
AnimationNodeBlendTree::AnimationNodeBlendTree() {

View File

@ -411,6 +411,7 @@ class AnimationNodeBlendTree : public AnimationRootNode {
};
AHashMap<StringName, Node> nodes;
AHashMap<AnimationNode *, StringName> node_to_name;
Vector2 graph_offset;

View File

@ -194,6 +194,7 @@ AnimationNodeStateMachineTransition::AnimationNodeStateMachineTransition() {
void AnimationNodeStateMachinePlayback::_set_current(AnimationNodeStateMachine *p_state_machine, const StringName &p_state) {
current = p_state;
if (current == StringName()) {
group_start_transition = Ref<AnimationNodeStateMachineTransition>();
group_end_transition = Ref<AnimationNodeStateMachineTransition>();
@ -490,6 +491,7 @@ void AnimationNodeStateMachinePlayback::_start(AnimationNodeStateMachine *p_stat
teleport_request = true;
stop_request = false;
start_request = StringName();
p_state_machine->_notify_tree(AnimationNode::NOTIFY_STARTED);
}
bool AnimationNodeStateMachinePlayback::_travel(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, bool p_test_only) {
@ -719,6 +721,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St
travel_request = StringName();
path.clear();
playing = false;
p_state_machine->_notify_tree(AnimationNode::NOTIFY_FINISHED);
return AnimationNode::NodeTimeInfo();
}
@ -883,8 +886,14 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St
if (will_end || ((p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_NESTED) && !p_state_machine->has_transition_from(current))) {
// There is no next transition.
if (fading_from != StringName()) {
if (MAX(current_nti.get_remain(), fadeing_from_nti.get_remain()) <= 0) {
p_state_machine->_notify_tree(AnimationNode::NOTIFY_FINISHED);
}
return Animation::is_greater_approx(current_nti.get_remain(), fadeing_from_nti.get_remain()) ? current_nti : fadeing_from_nti;
}
if (current_nti.get_remain() <= 0) {
p_state_machine->_notify_tree(AnimationNode::NOTIFY_FINISHED);
}
return current_nti;
}

View File

@ -587,6 +587,19 @@ void AnimationNode::_bind_methods() {
BIND_ENUM_CONSTANT(FILTER_PASS);
BIND_ENUM_CONSTANT(FILTER_STOP);
BIND_ENUM_CONSTANT(FILTER_BLEND);
BIND_ENUM_CONSTANT(NOTIFY_STARTED);
BIND_ENUM_CONSTANT(NOTIFY_FINISHED);
}
void AnimationNode::_notify_tree(AnimationNode::NotifyReason p_reason) {
AnimationNodeBlendTree *blend_tree = Object::cast_to<AnimationNodeBlendTree>(node_state.parent);
if (blend_tree) {
StringName node_name = blend_tree->get_node_name(this);
if (!node_name.is_empty()) {
process_state->tree->emit_signal(SceneStringName(node_notify), node_name, p_reason);
}
}
}
AnimationNode::AnimationNode() {
@ -991,6 +1004,7 @@ void AnimationTree::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_animation_player", "get_animation_player");
ADD_SIGNAL(MethodInfo(SNAME("animation_player_changed")));
ADD_SIGNAL(MethodInfo(SceneStringName(node_notify), PropertyInfo(Variant::STRING_NAME, "node"), PropertyInfo(Variant::INT, "reason")));
}
AnimationTree::AnimationTree() {

View File

@ -54,6 +54,11 @@ public:
FILTER_BLEND
};
enum NotifyReason {
NOTIFY_STARTED,
NOTIFY_FINISHED
};
struct Input {
String name;
};
@ -181,6 +186,8 @@ protected:
void _validate_property(PropertyInfo &p_property) const;
void _notify_tree(AnimationNode::NotifyReason p_reason);
GDVIRTUAL0RC(Dictionary, _get_child_nodes)
GDVIRTUAL0RC(Array, _get_parameter_list)
GDVIRTUAL1RC(Ref<AnimationNode>, _get_child_by_name, StringName)
@ -243,6 +250,7 @@ public:
};
VARIANT_ENUM_CAST(AnimationNode::FilterAction)
VARIANT_ENUM_CAST(AnimationNode::NotifyReason)
// Root node does not allow inputs.
class AnimationRootNode : public AnimationNode {

View File

@ -85,6 +85,7 @@ public:
const StringName sort_children = StaticCString::create("sort_children");
const StringName finished = StaticCString::create("finished");
const StringName node_notify = StaticCString::create("node_notify");
const StringName animation_finished = StaticCString::create("animation_finished");
const StringName animation_changed = StaticCString::create("animation_changed");
const StringName animation_started = StaticCString::create("animation_started");