Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add node_notify signal for animation trees #102165

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

a-johnston
Copy link
Contributor

@a-johnston a-johnston commented Jan 29, 2025

Closes godotengine/godot-proposals#10253.

Related to #102398

This adds node_notify(StringName, NotifyReason) to AnimationTree as a generic signal used for node state changes during processing/playback. Currently NotifyReason can be either NOTIFY_STARTED or NOTIFY_FINISHED and is implemented for one shot and state machine nodes. For example in the following blend tree:

Screenshot 2025-02-02 at 4 53 04 PM

The new signal fires with the following values:

Node notify: MyStateMachine Started
Node notify: OneShot Started
Node notify: MyStateMachine Finished
Node notify: OneShot Finished

@TokageItLab
Copy link
Member

TokageItLab commented Jan 30, 2025

See also:

As I have pointed out in the past with those, it should be implemented so that the ancestor StateMachine returns the nested paths when in Grouped mode (https://godotengine.org/article/migrating-animations-from-godot-4-0-to-4-3/#grouped). In other words, it must be implemented in such a way that the signal is propagated to the ancestor State as special case for the Grouped mode.

@a-johnston
Copy link
Contributor Author

Thanks for the links, I hadn't seen those PRs. Do you think it would be worth adding fading_from_state_changed to this pr? From the comments, a few things jumped out at me

  • Regarding performance concerns, is it more in the sense that adding new signals adds XYZ overhead even if there are no listeners, or have there been related performance issues in the past related to signal use which should be avoided? I can set up a synthetic case that triggers the new emit calls a ton if we want that measured.
  • I hadn't considered the option of saving a state machine node for reuse; I'll change the code to not use Resource.name and instead opt for a reverse node -> name map on the tree. Also I don't know if or when this would be useful, but there is an existing odd edge case where the same node resource could be added twice to a tree which would cause get_node_name to return the wrong name / cause the wrong node to be updated on a rename callback within the editor. This could be detected; it might be worth adding a warning message or something I'm not sure.

As I have pointed out in the past with those, it should be implemented so that the ancestor StateMachine returns the nested paths when in Grouped mode [..]. In other words, it must be implemented in such a way that the signal is propagated to the ancestor State as special case for the Grouped mode.

I'll work on adding this. Just to be sure I understand correctly, the changes I'll make would change the example root sm node_changed values, assuming InternalStateMachine is set to grouped, to be the following:

Start
InternalStateMachine/Anim_Knight_Attack01
InternalStateMachine/Anim_Knight_Attack02
Anim_Knight_Hit01
End

Also regarding a comment you left on another PR the user cannot use the child StateMachine's StateMachinePlayback directly, it is possible right now to grab the playback for the grouped state machine and listen to the signal from within user code, even if control is not allowed. I'll filter events in the root sm to skip the grouped Start/End node but it'll still be possible for users to directly listen to the grouped sm to detect those states.

Copy link
Member

@TokageItLab TokageItLab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the links, I hadn't seen those PRs. Do you think it would be worth adding fading_from_state_changed to this pr?

I think so.


As for the StateMachinePlayback object, it should be a unique resource for each AnimationTree, so there should be no problem to fire a signal there.

However, if you want to fire a signal in the AnimationNode, signals should not be registered with AnimationNode as explained in the review below.

The simplest implementation would be to fire signal from the AnimationTree with its own BlendTree path. In this case, you can refer to AnimationTree::set_parameter() to get the path. At this point, it may be better to separate the method for obtaining its own path from it and make it reusable.

But then, my concern is that each time we have more signals that we want to fire in the AnimationNode, we need to register all of them in the AnimationTree.

So, instead of adding a signal called oneshot_finished() to the AnimationTree, we should add a generic abstract signal such as node_notified(animation_node_path, signal_name, signal_variants).

However, that PR should be a separate PR from the StateMachinePlayback signal addition.

scene/animation/animation_blend_tree.cpp Outdated Show resolved Hide resolved
@a-johnston a-johnston force-pushed the animation_node_signals branch from 06a3c9d to 89dbc1c Compare February 3, 2025 00:36
@a-johnston
Copy link
Contributor Author

a-johnston commented Feb 3, 2025

I've updated the pr to have node_started/node_finished for state machine nodes rather than node_changed so that end of crossfades can be detected via signal. I've also added support for grouped state machines to pass up their signals to the containing playback, including when nested, although I still need to test a few more cases like when state traversal is interrupted by certain calls. In a minute I'll update the op to reflect the current state of the code.

Edit: One thing I'd like feedback on with the current code is if the signal emission should be put behind a call_deferred in all cases? I noticed it was like that for animation_started/animation_finished but wasn't sure if it should also be the case for the new signals. Easy change, just unsure what the preferred behavior would be.

@a-johnston a-johnston changed the title Add node changed/started/finished signals for animation trees Add node started/finished signals for animation trees and state machines Feb 3, 2025
@a-johnston a-johnston force-pushed the animation_node_signals branch from 89dbc1c to 4b9850a Compare February 3, 2025 01:13
@a-johnston
Copy link
Contributor Author

However, that PR should be a separate PR from the StateMachinePlayback signal addition.

This pr is currently sitting at +124 -11 which imo is a good size but if you'd prefer I can break this up into two PRs (maybe a third to rename fadeing_from_nti to fading_from_nti since that's totally unrelated but was bugging me lol)

doc/classes/AnimationTree.xml Outdated Show resolved Hide resolved
@a-johnston a-johnston force-pushed the animation_node_signals branch from 4b9850a to e09355a Compare February 4, 2025 07:07
@a-johnston a-johnston changed the title Add node started/finished signals for animation trees and state machines Add node_notify signal for animation trees Feb 4, 2025
@a-johnston a-johnston force-pushed the animation_node_signals branch 2 times, most recently from 2c7c814 to 4059858 Compare February 4, 2025 22:49
@a-johnston a-johnston force-pushed the animation_node_signals branch from 4059858 to df98559 Compare February 5, 2025 21:07
@fire fire requested a review from a team February 5, 2025 21:09
doc/classes/AnimationNode.xml Outdated Show resolved Hide resolved
scene/animation/animation_blend_tree.h Outdated Show resolved Hide resolved
scene/animation/animation_blend_tree.cpp Outdated Show resolved Hide resolved
@a-johnston a-johnston force-pushed the animation_node_signals branch from df98559 to e13e26a Compare February 8, 2025 23:15
@a-johnston a-johnston force-pushed the animation_node_signals branch from e13e26a to dcdd47a Compare February 10, 2025 21:18
Comment on lines +57 to +62
enum AnimationNodeNotification {
ANIMATION_NODE_NOTIFICATION_STARTED,
ANIMATION_NODE_NOTIFICATION_FINISHED,
ANIMATION_NODE_NOTIFICATION_FADEOUT_STARTED
};

Copy link
Member

@TokageItLab TokageItLab Feb 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the overall implementation is headed in the right direction.

My concern is where to define the enum for signal, maybe it should be better that defined separately for each AnimationNode and returned as an int and AnimationNode class name in the AnimationTree like:

animation_tree.cpp

ADD_SIGNAL(MethodInfo(SceneStringName(animation_node_notification), PropertyInfo(Variant::STRING_NAME, "animation_node_path"), PropertyInfo(Variant::STRING_NAME, "animation_node_class_name"), PropertyInfo(Variant::INT, "what")));

animation_blend_tree.h

	enum AnimationNodeNotificationOneShot {
		ANIMATION_NODE_NOTIFICATION_ONESHOT_STARTED,
		ANIMATION_NODE_NOTIFICATION_ONESHOT_FINISHED,
		ANIMATION_NODE_NOTIFICATION_FADEOUT_FINISHED_INTERNAL, // Fadeout started.
	};
~~~~~~
	enum AnimationNodeTransitionNotification {
		ANIMATION_NODE_NOTIFICATION_TRANSITION_STARTED,
		ANIMATION_NODE_NOTIFICATION_TRANSITION_FINISHED,
	};

This way, you can write enum documentation for each AnimationNode.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was thinking about this. The meaning of some of the notifications are frustratingly similar but I agree it's probably best to separate them for clarity and ease of documentation.

@@ -991,6 +1001,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_notification), PropertyInfo(Variant::STRING_NAME, "node"), PropertyInfo(Variant::INT, "what")));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ADD_SIGNAL(MethodInfo(SceneStringName(node_notification), PropertyInfo(Variant::STRING_NAME, "node"), PropertyInfo(Variant::INT, "what")));
ADD_SIGNAL(MethodInfo(SceneStringName(animation_node_notification), PropertyInfo(Variant::STRING_NAME, "animation_node_path"), PropertyInfo(Variant::INT, "what")));

"node" should be avoided because it is confusing with Node/Node2D/Node3D classes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

Add node_started and node_finished signals to AnimationTree
3 participants