|
@@ -101,12 +101,22 @@ float AnimationNodeStateMachineTransition::get_xfade_time() const {
|
|
|
|
|
|
void AnimationNodeStateMachineTransition::set_xfade_curve(const Ref<Curve> &p_curve) {
|
|
|
xfade_curve = p_curve;
|
|
|
+ emit_changed();
|
|
|
}
|
|
|
|
|
|
Ref<Curve> AnimationNodeStateMachineTransition::get_xfade_curve() const {
|
|
|
return xfade_curve;
|
|
|
}
|
|
|
|
|
|
+void AnimationNodeStateMachineTransition::set_break_loop_at_end(bool p_enable) {
|
|
|
+ break_loop_at_end = p_enable;
|
|
|
+ emit_changed();
|
|
|
+}
|
|
|
+
|
|
|
+bool AnimationNodeStateMachineTransition::is_loop_broken_at_end() const {
|
|
|
+ return break_loop_at_end;
|
|
|
+}
|
|
|
+
|
|
|
void AnimationNodeStateMachineTransition::set_reset(bool p_reset) {
|
|
|
reset = p_reset;
|
|
|
emit_changed();
|
|
@@ -141,6 +151,9 @@ void AnimationNodeStateMachineTransition::_bind_methods() {
|
|
|
ClassDB::bind_method(D_METHOD("set_xfade_curve", "curve"), &AnimationNodeStateMachineTransition::set_xfade_curve);
|
|
|
ClassDB::bind_method(D_METHOD("get_xfade_curve"), &AnimationNodeStateMachineTransition::get_xfade_curve);
|
|
|
|
|
|
+ ClassDB::bind_method(D_METHOD("set_break_loop_at_end", "enable"), &AnimationNodeStateMachineTransition::set_break_loop_at_end);
|
|
|
+ ClassDB::bind_method(D_METHOD("is_loop_broken_at_end"), &AnimationNodeStateMachineTransition::is_loop_broken_at_end);
|
|
|
+
|
|
|
ClassDB::bind_method(D_METHOD("set_reset", "reset"), &AnimationNodeStateMachineTransition::set_reset);
|
|
|
ClassDB::bind_method(D_METHOD("is_reset"), &AnimationNodeStateMachineTransition::is_reset);
|
|
|
|
|
@@ -153,6 +166,7 @@ void AnimationNodeStateMachineTransition::_bind_methods() {
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "xfade_time", PROPERTY_HINT_RANGE, "0,240,0.01,suffix:s"), "set_xfade_time", "get_xfade_time");
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "xfade_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_xfade_curve", "get_xfade_curve");
|
|
|
|
|
|
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "break_loop_at_end"), "set_break_loop_at_end", "is_loop_broken_at_end");
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset"), "set_reset", "is_reset");
|
|
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority");
|
|
@@ -310,19 +324,19 @@ TypedArray<StringName> AnimationNodeStateMachinePlayback::_get_travel_path() con
|
|
|
}
|
|
|
|
|
|
float AnimationNodeStateMachinePlayback::get_current_play_pos() const {
|
|
|
- return pos_current;
|
|
|
+ return current_nti.position;
|
|
|
}
|
|
|
|
|
|
float AnimationNodeStateMachinePlayback::get_current_length() const {
|
|
|
- return len_current;
|
|
|
+ return current_nti.length;
|
|
|
}
|
|
|
|
|
|
float AnimationNodeStateMachinePlayback::get_fade_from_play_pos() const {
|
|
|
- return pos_fade_from;
|
|
|
+ return fadeing_from_nti.position;
|
|
|
}
|
|
|
|
|
|
float AnimationNodeStateMachinePlayback::get_fade_from_length() const {
|
|
|
- return len_fade_from;
|
|
|
+ return fadeing_from_nti.length;
|
|
|
}
|
|
|
|
|
|
float AnimationNodeStateMachinePlayback::get_fading_time() const {
|
|
@@ -665,21 +679,22 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-double AnimationNodeStateMachinePlayback::process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
|
|
|
- double rem = _process(p_base_path, p_state_machine, p_playback_info, p_test_only);
|
|
|
+AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
|
|
|
+ AnimationNode::NodeTimeInfo nti = _process(p_base_path, p_state_machine, p_playback_info, p_test_only);
|
|
|
start_request = StringName();
|
|
|
next_request = false;
|
|
|
stop_request = false;
|
|
|
reset_request_on_teleport = false;
|
|
|
- return rem;
|
|
|
+ return nti;
|
|
|
}
|
|
|
|
|
|
-double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
|
|
|
+AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
|
|
|
_set_base_path(p_base_path);
|
|
|
|
|
|
AnimationTree *tree = p_state_machine->process_state->tree;
|
|
|
|
|
|
double p_time = p_playback_info.time;
|
|
|
+ double p_delta = p_playback_info.delta;
|
|
|
bool p_seek = p_playback_info.seeked;
|
|
|
bool p_is_external_seeking = p_playback_info.is_external_seeking;
|
|
|
|
|
@@ -690,8 +705,8 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
|
|
|
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);
|
|
|
- _start(p_state_machine);
|
|
|
}
|
|
|
+ _start(p_state_machine);
|
|
|
reset_request = true;
|
|
|
} else {
|
|
|
// Reset current state.
|
|
@@ -705,11 +720,11 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
|
|
|
travel_request = StringName();
|
|
|
path.clear();
|
|
|
playing = false;
|
|
|
- return 0;
|
|
|
+ return AnimationNode::NodeTimeInfo();
|
|
|
}
|
|
|
|
|
|
if (!playing && start_request != StringName() && travel_request != StringName()) {
|
|
|
- return 0;
|
|
|
+ return AnimationNode::NodeTimeInfo();
|
|
|
}
|
|
|
|
|
|
// Process start/travel request.
|
|
@@ -732,7 +747,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
|
|
|
_start(p_state_machine);
|
|
|
} else {
|
|
|
StringName node = start_request;
|
|
|
- ERR_FAIL_V_MSG(0, "No such node: '" + node + "'");
|
|
|
+ ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + node + "'");
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -766,7 +781,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
|
|
|
teleport_request = true;
|
|
|
}
|
|
|
} else {
|
|
|
- ERR_FAIL_V_MSG(0, "No such node: '" + temp_travel_request + "'");
|
|
|
+ ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + temp_travel_request + "'");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -777,16 +792,14 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
|
|
|
teleport_request = false;
|
|
|
// Clear fadeing on teleport.
|
|
|
fading_from = StringName();
|
|
|
+ fadeing_from_nti = AnimationNode::NodeTimeInfo();
|
|
|
fading_pos = 0;
|
|
|
// Init current length.
|
|
|
- pos_current = 0; // Overwritten suddenly in main process.
|
|
|
-
|
|
|
pi.time = 0;
|
|
|
pi.seeked = true;
|
|
|
pi.is_external_seeking = false;
|
|
|
pi.weight = 0;
|
|
|
-
|
|
|
- len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true);
|
|
|
+ current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, 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);
|
|
|
}
|
|
@@ -795,7 +808,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
|
|
|
if (!p_state_machine->states.has(current)) {
|
|
|
playing = false; // Current does not exist.
|
|
|
_set_current(p_state_machine, StringName());
|
|
|
- return 0;
|
|
|
+ return AnimationNode::NodeTimeInfo();
|
|
|
}
|
|
|
|
|
|
// 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).
|
|
@@ -813,7 +826,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
|
|
|
fading_from = StringName();
|
|
|
} else {
|
|
|
if (!p_seek) {
|
|
|
- fading_pos += p_time;
|
|
|
+ fading_pos += Math::abs(p_delta);
|
|
|
}
|
|
|
fade_blend = MIN(1.0, fading_pos / fading_time);
|
|
|
}
|
|
@@ -829,18 +842,14 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
|
|
|
}
|
|
|
|
|
|
// Main process.
|
|
|
- double rem = 0.0;
|
|
|
pi = p_playback_info;
|
|
|
pi.weight = fade_blend;
|
|
|
if (reset_request) {
|
|
|
reset_request = false;
|
|
|
pi.time = 0;
|
|
|
pi.seeked = true;
|
|
|
- len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only);
|
|
|
- rem = len_current;
|
|
|
- } else {
|
|
|
- rem = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
|
|
|
}
|
|
|
+ current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, 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()) {
|
|
@@ -852,7 +861,6 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
|
|
|
fade_blend_inv = 1.0;
|
|
|
}
|
|
|
|
|
|
- float fading_from_rem = 0.0;
|
|
|
pi = p_playback_info;
|
|
|
pi.weight = fade_blend_inv;
|
|
|
if (_reset_request_for_fading_from) {
|
|
@@ -860,57 +868,41 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
|
|
|
pi.time = 0;
|
|
|
pi.seeked = true;
|
|
|
}
|
|
|
- fading_from_rem = p_state_machine->blend_node(p_state_machine->states[fading_from].node, fading_from, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // 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;
|
|
|
+ fadeing_from_nti = p_state_machine->blend_node(p_state_machine->states[fading_from].node, fading_from, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
|
|
|
|
|
|
if (fading_pos >= fading_time) {
|
|
|
- fading_from = StringName(); // Finish fading.
|
|
|
+ // Finish fading.
|
|
|
+ fading_from = StringName();
|
|
|
+ fadeing_from_nti = AnimationNode::NodeTimeInfo();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Guess playback position.
|
|
|
- if (rem > len_current) { // Weird but ok.
|
|
|
- len_current = rem;
|
|
|
- }
|
|
|
- pos_current = len_current - rem;
|
|
|
-
|
|
|
// Find next and see when to transition.
|
|
|
_transition_to_next_recursive(tree, p_state_machine, p_test_only);
|
|
|
|
|
|
// Predict remaining 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 current_nti.get_remain() > fadeing_from_nti.get_remain() ? current_nti : fadeing_from_nti;
|
|
|
}
|
|
|
- return remain;
|
|
|
+ return current_nti;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (current == p_state_machine->end_node) {
|
|
|
- if (fading_from != StringName()) {
|
|
|
- remain = MAX(0, fading_time - fading_pos);
|
|
|
- } else {
|
|
|
- remain = 0;
|
|
|
+ if (fading_from != StringName() && fadeing_from_nti.get_remain() > 0) {
|
|
|
+ return fadeing_from_nti;
|
|
|
}
|
|
|
- return remain;
|
|
|
+ return AnimationNode::NodeTimeInfo();
|
|
|
}
|
|
|
|
|
|
if (!is_end()) {
|
|
|
- return HUGE_LENGTH;
|
|
|
+ current_nti.is_infinity = true;
|
|
|
}
|
|
|
|
|
|
- return remain;
|
|
|
+ return current_nti;
|
|
|
}
|
|
|
|
|
|
bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only) {
|
|
@@ -952,6 +944,7 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT
|
|
|
p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only);
|
|
|
}
|
|
|
fading_from = StringName();
|
|
|
+ fadeing_from_nti = AnimationNode::NodeTimeInfo();
|
|
|
fading_time = 0;
|
|
|
fading_pos = 0;
|
|
|
}
|
|
@@ -968,11 +961,10 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT
|
|
|
_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;
|
|
|
+ fadeing_from_nti = current_nti;
|
|
|
|
|
|
if (next.switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) {
|
|
|
- pi.time = MIN(pos_current, len_current);
|
|
|
+ pi.time = current_nti.position;
|
|
|
pi.seeked = true;
|
|
|
pi.is_external_seeking = false;
|
|
|
pi.weight = 0;
|
|
@@ -980,24 +972,11 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT
|
|
|
}
|
|
|
|
|
|
// Just get length to find next recursive.
|
|
|
- double rem = 0.0;
|
|
|
pi.time = 0;
|
|
|
pi.is_external_seeking = false;
|
|
|
pi.weight = 0;
|
|
|
- if (next.is_reset) {
|
|
|
- pi.seeked = true;
|
|
|
- len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process.
|
|
|
- rem = len_current;
|
|
|
- } else {
|
|
|
- pi.seeked = false;
|
|
|
- rem = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, 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;
|
|
|
+ pi.seeked = next.is_reset;
|
|
|
+ current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process.
|
|
|
|
|
|
// Fading must be processed.
|
|
|
if (fading_time) {
|
|
@@ -1028,6 +1007,7 @@ bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p
|
|
|
playback->_next_main();
|
|
|
// Then, fadeing should be end.
|
|
|
fading_from = StringName();
|
|
|
+ fadeing_from_nti = AnimationNode::NodeTimeInfo();
|
|
|
fading_pos = 0;
|
|
|
} else {
|
|
|
return true;
|
|
@@ -1039,7 +1019,7 @@ bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p
|
|
|
}
|
|
|
|
|
|
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 current_nti.get_remain(p_next.break_loop_at_end) <= p_next.xfade;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
@@ -1084,6 +1064,7 @@ AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_
|
|
|
next.curve = ref_transition->get_xfade_curve();
|
|
|
next.switch_mode = ref_transition->get_switch_mode();
|
|
|
next.is_reset = ref_transition->is_reset();
|
|
|
+ next.break_loop_at_end = ref_transition->is_loop_broken_at_end();
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
@@ -1113,6 +1094,7 @@ AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_
|
|
|
next.curve = ref_transition->get_xfade_curve();
|
|
|
next.switch_mode = ref_transition->get_switch_mode();
|
|
|
next.is_reset = ref_transition->is_reset();
|
|
|
+ next.break_loop_at_end = ref_transition->is_loop_broken_at_end();
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1233,6 +1215,7 @@ AnimationNodeStateMachinePlayback::AnimationNodeStateMachinePlayback() {
|
|
|
///////////////////////////////////////////////////////
|
|
|
|
|
|
void AnimationNodeStateMachine::get_parameter_list(List<PropertyInfo> *r_list) const {
|
|
|
+ AnimationNode::get_parameter_list(r_list);
|
|
|
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<StringName> advance_conditions;
|
|
|
for (int i = 0; i < transitions.size(); i++) {
|
|
@@ -1249,6 +1232,11 @@ void AnimationNodeStateMachine::get_parameter_list(List<PropertyInfo> *r_list) c
|
|
|
}
|
|
|
|
|
|
Variant AnimationNodeStateMachine::get_parameter_default_value(const StringName &p_parameter) const {
|
|
|
+ Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
|
|
|
+ if (ret != Variant()) {
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
if (p_parameter == playback) {
|
|
|
Ref<AnimationNodeStateMachinePlayback> p;
|
|
|
p.instantiate();
|
|
@@ -1259,6 +1247,10 @@ Variant AnimationNodeStateMachine::get_parameter_default_value(const StringName
|
|
|
}
|
|
|
|
|
|
bool AnimationNodeStateMachine::is_parameter_read_only(const StringName &p_parameter) const {
|
|
|
+ if (AnimationNode::is_parameter_read_only(p_parameter)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
if (p_parameter == playback) {
|
|
|
return true;
|
|
|
}
|
|
@@ -1622,9 +1614,9 @@ Vector2 AnimationNodeStateMachine::get_graph_offset() const {
|
|
|
return graph_offset;
|
|
|
}
|
|
|
|
|
|
-double AnimationNodeStateMachine::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
|
|
|
+AnimationNode::NodeTimeInfo AnimationNodeStateMachine::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
|
|
|
Ref<AnimationNodeStateMachinePlayback> playback_new = get_parameter(playback);
|
|
|
- ERR_FAIL_COND_V(playback_new.is_null(), 0.0);
|
|
|
+ ERR_FAIL_COND_V(playback_new.is_null(), AnimationNode::NodeTimeInfo());
|
|
|
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.
|