1
0
Fork 0
This commit is contained in:
Adam Johnston 2025-02-28 01:36:15 +01:00 committed by GitHub
commit 4e6e7ce89a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 118 additions and 12 deletions

View File

@ -106,5 +106,17 @@
<constant name="MIX_MODE_ADD" value="1" enum="MixMode">
Blends two animations additively. See also [AnimationNodeAdd2].
</constant>
<constant name="ANIMATION_NODE_NOTIFICATION_ONESHOT_STARTED" value="1000">
The oneshot node has started playback.
</constant>
<constant name="ANIMATION_NODE_NOTIFICATION_ONESHOT_FINISHED" value="1001">
The oneshot node has finished playback.
</constant>
<constant name="ANIMATION_NODE_NOTIFICATION_ONESHOT_FADEIN_FINISHED" value="1002">
The oneshot node has finished fading in. Only emitted if fade-in is enabled.
</constant>
<constant name="ANIMATION_NODE_NOTIFICATION_ONESHOT_FADEOUT_STARTED" value="1003">
The oneshot node has started fading out. Only emitted if fade-out is enabled.
</constant>
</constants>
</class>

View File

@ -183,5 +183,11 @@
<constant name="STATE_MACHINE_TYPE_GROUPED" value="2" enum="StateMachineType">
This is a grouped state machine that can be controlled from a parent state machine. It does not work independently. 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.
</constant>
<constant name="ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_STARTED" value="2000">
The state machine has started playback.
</constant>
<constant name="ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_FINISHED" value="2001">
The state machine has finished playback. This is emitted if the terminal state is reached or if playback is manually stopped, but not if the state machine is stuck in a non-terminal state.
</constant>
</constants>
</class>

View File

@ -103,4 +103,12 @@
[b]Note:[/b] [AnimationNodeTransition] transitions the current state immediately after the start of the fading. The precise remaining time can only be inferred from the main animation. When [AnimationNodeOutput] is considered as the most upstream, so the [member xfade_time] is not scaled depending on the downstream delta. See also [member AnimationNodeOneShot.fadeout_time].
</member>
</members>
<constants>
<constant name="ANIMATION_NODE_NOTIFICATION_TRANSITION_STARTED" value="3000">
The node has started transitioning to the next state. This is emitted immediately before the current state changes to the requested state.
</constant>
<constant name="ANIMATION_NODE_NOTIFICATION_TRANSITION_FINISHED" value="3001">
The node has finished transitioning to the next state.
</constant>
</constants>
</class>

View File

@ -40,6 +40,13 @@
</member>
</members>
<signals>
<signal name="animation_node_notification">
<param index="0" name="animation_node_path" type="StringName" />
<param index="1" name="what" type="int" />
<description>
Emitted when the animation node located at [param animation_node_path] state changes during processing or playback.
</description>
</signal>
<signal name="animation_player_changed">
<description>
Emitted when the [member anim_player] is changed.

View File

@ -535,6 +535,18 @@ bool AnimationNodeOneShot::has_filter() const {
return true;
}
void AnimationNodeOneShot::_check_and_notify_state_changes(bool p_active, bool p_internal_active, double p_fade_in_remaining) {
if (Animation::is_greater_approx(p_fade_in_remaining, 0) && Math::is_zero_approx((double)get_parameter(fade_in_remaining))) {
_animation_tree_notify(ANIMATION_NODE_NOTIFICATION_ONESHOT_FADEIN_FINISHED);
}
if (get_fade_out_time() > 0 && p_internal_active && p_internal_active != (bool)get_parameter(internal_active)) {
_animation_tree_notify(ANIMATION_NODE_NOTIFICATION_ONESHOT_FADEOUT_STARTED);
}
if (p_active != (bool)get_parameter(active)) {
_animation_tree_notify(p_active ? ANIMATION_NODE_NOTIFICATION_ONESHOT_FINISHED : ANIMATION_NODE_NOTIFICATION_ONESHOT_STARTED);
}
}
AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
OneShotRequest cur_request = static_cast<OneShotRequest>((int)get_parameter(request));
bool cur_active = get_parameter(active);
@ -555,6 +567,7 @@ AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer:
double abs_delta = Math::abs(p_delta);
bool p_seek = p_playback_info.seeked;
bool p_is_external_seeking = p_playback_info.is_external_seeking;
double prev_fade_in_remaining = cur_fade_in_remaining;
if (Math::is_zero_approx(p_time) && p_seek && !p_is_external_seeking) {
clear_remaining_fade = true; // Reset occurs.
@ -605,6 +618,7 @@ AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer:
}
if (!is_shooting) {
_check_and_notify_state_changes(cur_active, cur_internal_active, prev_fade_in_remaining);
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = 1.0;
return blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
@ -696,6 +710,7 @@ AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer:
set_parameter(fade_in_remaining, cur_fade_in_remaining);
set_parameter(fade_out_remaining, cur_fade_out_remaining);
_check_and_notify_state_changes(cur_active, cur_internal_active, prev_fade_in_remaining);
return cur_internal_active ? os_nti : main_nti;
}
@ -748,6 +763,11 @@ void AnimationNodeOneShot::_bind_methods() {
BIND_ENUM_CONSTANT(MIX_MODE_BLEND);
BIND_ENUM_CONSTANT(MIX_MODE_ADD);
BIND_CONSTANT(ANIMATION_NODE_NOTIFICATION_ONESHOT_STARTED);
BIND_CONSTANT(ANIMATION_NODE_NOTIFICATION_ONESHOT_FINISHED);
BIND_CONSTANT(ANIMATION_NODE_NOTIFICATION_ONESHOT_FADEIN_FINISHED);
BIND_CONSTANT(ANIMATION_NODE_NOTIFICATION_ONESHOT_FADEOUT_STARTED);
}
AnimationNodeOneShot::AnimationNodeOneShot() {
@ -1297,6 +1317,7 @@ AnimationNode::NodeTimeInfo AnimationNodeTransition::_process(const AnimationMix
clear_remaining_fade = true;
}
} else {
_animation_tree_notify(ANIMATION_NODE_NOTIFICATION_TRANSITION_STARTED);
switched = true;
cur_prev_index = cur_current_index;
set_parameter(prev_index, cur_current_index);
@ -1309,6 +1330,9 @@ AnimationNode::NodeTimeInfo AnimationNodeTransition::_process(const AnimationMix
}
cur_transition_request = String();
set_parameter(transition_request, cur_transition_request);
if (switched && Animation::is_less_or_equal_approx(xfade_time, 0)) {
_animation_tree_notify(ANIMATION_NODE_NOTIFICATION_TRANSITION_FINISHED);
}
}
if (clear_remaining_fade) {
@ -1381,6 +1405,9 @@ AnimationNode::NodeTimeInfo AnimationNodeTransition::_process(const AnimationMix
blend_input(cur_prev_index, pi, FILTER_IGNORE, true, p_test_only);
if (!p_seek) {
if (Animation::is_less_or_equal_approx(cur_prev_xfading, 0)) {
if (xfade_time > 0) {
_animation_tree_notify(ANIMATION_NODE_NOTIFICATION_TRANSITION_FINISHED);
}
set_parameter(prev_index, -1);
}
cur_prev_xfading -= Math::abs(p_playback_info.delta);
@ -1426,6 +1453,9 @@ void AnimationNodeTransition::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "xfade_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_xfade_curve", "get_xfade_curve");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_transition_to_self"), "set_allow_transition_to_self", "is_allow_transition_to_self");
ADD_PROPERTY(PropertyInfo(Variant::INT, "input_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED, "Inputs,input_"), "set_input_count", "get_input_count");
BIND_CONSTANT(ANIMATION_NODE_NOTIFICATION_TRANSITION_STARTED);
BIND_CONSTANT(ANIMATION_NODE_NOTIFICATION_TRANSITION_FINISHED);
}
AnimationNodeTransition::AnimationNodeTransition() {
@ -1554,7 +1584,6 @@ 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];
nodes.erase(p_name);
@ -1853,7 +1882,7 @@ void AnimationNodeBlendTree::_initialize_node_tree() {
n.node = output;
n.position = Vector2(300, 150);
n.connections.resize(1);
nodes["output"] = n;
nodes[SceneStringName(output)] = n;
}
AnimationNodeBlendTree::AnimationNodeBlendTree() {

View File

@ -138,6 +138,13 @@ public:
MIX_MODE_ADD
};
enum {
ANIMATION_NODE_NOTIFICATION_ONESHOT_STARTED = 1000,
ANIMATION_NODE_NOTIFICATION_ONESHOT_FINISHED = 1001,
ANIMATION_NODE_NOTIFICATION_ONESHOT_FADEIN_FINISHED = 1002,
ANIMATION_NODE_NOTIFICATION_ONESHOT_FADEOUT_STARTED = 1003
};
private:
double fade_in = 0.0;
Ref<Curve> fade_in_curve;
@ -157,6 +164,8 @@ private:
StringName fade_out_remaining = "fade_out_remaining";
StringName time_to_restart = "time_to_restart";
void _check_and_notify_state_changes(bool p_active, bool p_internal_active, double p_fade_in_remaining);
protected:
static void _bind_methods();
@ -355,6 +364,11 @@ protected:
void _get_property_list(List<PropertyInfo> *p_list) const;
public:
enum {
ANIMATION_NODE_NOTIFICATION_TRANSITION_STARTED = 3000,
ANIMATION_NODE_NOTIFICATION_TRANSITION_FINISHED = 3001
};
virtual void get_parameter_list(List<PropertyInfo> *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;

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>();
@ -361,7 +362,7 @@ void AnimationNodeStateMachinePlayback::_clear_path_children(AnimationTree *p_tr
playback->path.clear();
playback->_clear_path_children(p_tree, anodesm.ptr(), p_test_only);
if (current != child_node.name) {
playback->_start(anodesm.ptr()); // Can restart.
playback->_start(anodesm.ptr(), p_test_only); // Can restart.
}
}
}
@ -421,7 +422,7 @@ bool AnimationNodeStateMachinePlayback::_travel_children(AnimationTree *p_tree,
playback = playback->duplicate();
}
if (!playback->is_playing()) {
playback->_start(anodesm.ptr());
playback->_start(anodesm.ptr(), p_test_only);
}
ChildStateMachineInfo child_info;
child_info.playback = playback;
@ -448,7 +449,7 @@ bool AnimationNodeStateMachinePlayback::_travel_children(AnimationTree *p_tree,
child_path += "/" + child_playback->get_current_node();
}
// Force restart target state machine.
playback->_start(anodesm.ptr());
playback->_start(anodesm.ptr(), p_test_only);
}
is_parent_same_state = is_current_same_state;
@ -484,12 +485,16 @@ bool AnimationNodeStateMachinePlayback::_travel_children(AnimationTree *p_tree,
return found_route;
}
void AnimationNodeStateMachinePlayback::_start(AnimationNodeStateMachine *p_state_machine) {
void AnimationNodeStateMachinePlayback::_start(AnimationNodeStateMachine *p_state_machine, bool p_test_only) {
playing = true;
_set_current(p_state_machine, start_request != StringName() ? start_request : SceneStringName(Start));
teleport_request = true;
stop_request = false;
start_request = StringName();
// Only emit the started signal if currently configured for playback
if (!p_test_only && p_state_machine->process_state) {
p_state_machine->_animation_tree_notify(AnimationNodeStateMachine::ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_STARTED);
}
}
bool AnimationNodeStateMachinePlayback::_travel(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, bool p_test_only) {
@ -519,7 +524,7 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree,
travel_request = StringName();
if (!playing) {
_start(p_state_machine);
_start(p_state_machine, p_test_only);
}
ERR_FAIL_COND_V(!p_state_machine->states.has(travel), false);
@ -651,7 +656,7 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree,
playback = playback->duplicate();
}
if (i > 0) {
playback->_start(anodesm.ptr());
playback->_start(anodesm.ptr(), p_test_only);
}
if (i >= new_path.size()) {
break; // Tracing has been finished, needs to break.
@ -705,7 +710,8 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St
path.clear();
_clear_path_children(tree, p_state_machine, p_test_only);
}
_start(p_state_machine);
playing = false;
_start(p_state_machine, p_test_only);
reset_request = true;
} else {
// Reset current state.
@ -718,7 +724,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St
start_request = StringName();
travel_request = StringName();
path.clear();
playing = false;
p_state_machine->_animation_tree_notify(AnimationNodeStateMachine::ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_FINISHED);
return AnimationNode::NodeTimeInfo();
}
@ -743,7 +749,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St
}
// Teleport to start.
if (p_state_machine->states.has(start_request)) {
_start(p_state_machine);
_start(p_state_machine, p_test_only);
} else {
StringName node = start_request;
ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + node + "'");
@ -883,8 +889,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->_animation_tree_notify(AnimationNodeStateMachine::ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_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->_animation_tree_notify(AnimationNodeStateMachine::ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_FINISHED);
}
return current_nti;
}
@ -1828,6 +1840,9 @@ void AnimationNodeStateMachine::_bind_methods() {
BIND_ENUM_CONSTANT(STATE_MACHINE_TYPE_ROOT);
BIND_ENUM_CONSTANT(STATE_MACHINE_TYPE_NESTED);
BIND_ENUM_CONSTANT(STATE_MACHINE_TYPE_GROUPED);
BIND_CONSTANT(ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_STARTED);
BIND_CONSTANT(ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_FINISHED);
}
Vector<StringName> AnimationNodeStateMachine::get_nodes_with_transitions_from(const StringName &p_node) const {

View File

@ -116,6 +116,11 @@ public:
STATE_MACHINE_TYPE_GROUPED,
};
enum {
ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_STARTED = 2000,
ANIMATION_NODE_NOTIFICATION_STATE_MACHINE_FINISHED = 2001
};
private:
friend class AnimationNodeStateMachinePlayback;
@ -296,7 +301,7 @@ class AnimationNodeStateMachinePlayback : public Resource {
bool _make_travel_path(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, Vector<StringName> &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 _start(AnimationNodeStateMachine *p_state_machine, bool p_test_only);
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);

View File

@ -589,6 +589,12 @@ void AnimationNode::_bind_methods() {
BIND_ENUM_CONSTANT(FILTER_BLEND);
}
void AnimationNode::_animation_tree_notify(int p_what) {
ERR_FAIL_NULL(process_state);
ERR_FAIL_NULL(process_state->tree);
process_state->tree->emit_signal(SceneStringName(animation_node_notification), process_state->tree->property_reference_map[get_instance_id()], p_what);
}
AnimationNode::AnimationNode() {
}
@ -991,6 +997,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(animation_node_notification), PropertyInfo(Variant::STRING_NAME, "animation_node_path"), PropertyInfo(Variant::INT, "what")));
}
AnimationTree::AnimationTree() {

View File

@ -181,6 +181,8 @@ protected:
void _validate_property(PropertyInfo &p_property) const;
void _animation_tree_notify(int p_what);
GDVIRTUAL0RC(Dictionary, _get_child_nodes)
GDVIRTUAL0RC(Array, _get_parameter_list)
GDVIRTUAL1RC(Ref<AnimationNode>, _get_child_by_name, StringName)

View File

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