Add next/reset function to AnimationStateMachine

This commit is contained in:
Silc Renew 2023-01-10 18:15:16 +09:00
parent 7c2768c2f8
commit 8bfaf098c7
7 changed files with 128 additions and 60 deletions

View file

@ -50,11 +50,19 @@
Returns [code]true[/code] if an animation is playing.
</description>
</method>
<method name="next">
<return type="void" />
<description>
If there is a next path by travel or auto advance, immediately transitions from the current state to the next state.
</description>
</method>
<method name="start">
<return type="void" />
<param index="0" name="node" type="StringName" />
<param index="1" name="reset" type="bool" default="true" />
<description>
Starts playing the given animation.
If [param reset] is [code]true[/code], the animation is played from the beginning.
</description>
</method>
<method name="stop">
@ -66,8 +74,11 @@
<method name="travel">
<return type="void" />
<param index="0" name="to_node" type="StringName" />
<param index="1" name="reset_on_teleport" type="bool" default="true" />
<description>
Transitions from the current state to another one, following the shortest path.
If the path does not connect from the current state, the animation will play after the state teleports.
If [param reset_on_teleport] is [code]true[/code], the animation is played from the beginning when the travel cause a teleportation.
</description>
</method>
</methods>

View file

@ -28,6 +28,9 @@
<member name="priority" type="int" setter="set_priority" getter="get_priority" default="1">
Lower priority transitions are preferred when travelling through the tree via [method AnimationNodeStateMachinePlayback.travel] or [member advance_mode] is set to [constant ADVANCE_MODE_AUTO].
</member>
<member name="reset" type="bool" setter="set_reset" getter="is_reset" default="true">
If [code]true[/code], the destination animation is played back from the beginning when switched.
</member>
<member name="switch_mode" type="int" setter="set_switch_mode" getter="get_switch_mode" enum="AnimationNodeStateMachineTransition.SwitchMode" default="0">
The transition type.
</member>

View file

@ -43,7 +43,7 @@
<member name="enabled_inputs" type="int" setter="set_enabled_inputs" getter="get_enabled_inputs" default="0">
The number of enabled input ports for this node.
</member>
<member name="from_start" type="bool" setter="set_from_start" getter="is_from_start" default="true">
<member name="reset" type="bool" setter="set_reset" getter="is_reset" default="true">
If [code]true[/code], the destination animation is played back from the beginning when switched.
</member>
<member name="xfade_curve" type="Curve" setter="set_xfade_curve" getter="get_xfade_curve">

View file

@ -718,12 +718,12 @@ Ref<Curve> AnimationNodeTransition::get_xfade_curve() const {
return xfade_curve;
}
void AnimationNodeTransition::set_from_start(bool p_from_start) {
from_start = p_from_start;
void AnimationNodeTransition::set_reset(bool p_reset) {
reset = p_reset;
}
bool AnimationNodeTransition::is_from_start() const {
return from_start;
bool AnimationNodeTransition::is_reset() const {
return reset;
}
double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_external_seeking) {
@ -783,7 +783,7 @@ 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.
real_t blend_inv = 1.0 - blend;
if (from_start && !p_seek && switched) { //just switched, seek to start of current
if (reset && !p_seek && switched) { //just switched, seek to start of current
rem = blend_input(cur_current, 0, true, p_is_external_seeking, Math::is_zero_approx(blend_inv) ? CMP_EPSILON : blend_inv, FILTER_IGNORE, true);
} else {
rem = blend_input(cur_current, p_time, p_seek, p_is_external_seeking, Math::is_zero_approx(blend_inv) ? CMP_EPSILON : blend_inv, FILTER_IGNORE, true);
@ -836,13 +836,13 @@ void AnimationNodeTransition::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_xfade_curve", "curve"), &AnimationNodeTransition::set_xfade_curve);
ClassDB::bind_method(D_METHOD("get_xfade_curve"), &AnimationNodeTransition::get_xfade_curve);
ClassDB::bind_method(D_METHOD("set_from_start", "from_start"), &AnimationNodeTransition::set_from_start);
ClassDB::bind_method(D_METHOD("is_from_start"), &AnimationNodeTransition::is_from_start);
ClassDB::bind_method(D_METHOD("set_reset", "reset"), &AnimationNodeTransition::set_reset);
ClassDB::bind_method(D_METHOD("is_reset"), &AnimationNodeTransition::is_reset);
ADD_PROPERTY(PropertyInfo(Variant::INT, "enabled_inputs", PROPERTY_HINT_RANGE, "0,64,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_enabled_inputs", "get_enabled_inputs");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "xfade_time", PROPERTY_HINT_RANGE, "0,120,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, "from_start"), "set_from_start", "is_from_start");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset"), "set_reset", "is_reset");
for (int i = 0; i < MAX_INPUTS; i++) {
ADD_PROPERTYI(PropertyInfo(Variant::STRING, "input_" + itos(i) + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_input_caption", "get_input_caption", i);

View file

@ -299,7 +299,7 @@ class AnimationNodeTransition : public AnimationNodeSync {
double xfade_time = 0.0;
Ref<Curve> xfade_curve;
bool from_start = true;
bool reset = true;
void _update_inputs();
@ -328,8 +328,8 @@ public:
void set_xfade_curve(const Ref<Curve> &p_curve);
Ref<Curve> get_xfade_curve() const;
void set_from_start(bool p_from_start);
bool is_from_start() const;
void set_reset(bool p_reset);
bool is_reset() const;
double process(double p_time, bool p_seek, bool p_is_external_seeking) override;

View file

@ -107,6 +107,15 @@ Ref<Curve> AnimationNodeStateMachineTransition::get_xfade_curve() const {
return xfade_curve;
}
void AnimationNodeStateMachineTransition::set_reset(bool p_reset) {
reset = p_reset;
emit_changed();
}
bool AnimationNodeStateMachineTransition::is_reset() const {
return reset;
}
void AnimationNodeStateMachineTransition::set_priority(int p_priority) {
priority = p_priority;
emit_changed();
@ -132,6 +141,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_reset", "reset"), &AnimationNodeStateMachineTransition::set_reset);
ClassDB::bind_method(D_METHOD("is_reset"), &AnimationNodeStateMachineTransition::is_reset);
ClassDB::bind_method(D_METHOD("set_priority", "priority"), &AnimationNodeStateMachineTransition::set_priority);
ClassDB::bind_method(D_METHOD("get_priority"), &AnimationNodeStateMachineTransition::get_priority);
@ -140,6 +152,9 @@ 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, "reset"), "set_reset", "is_reset");
ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority");
ADD_GROUP("Switch", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "switch_mode", PROPERTY_HINT_ENUM, "Immediate,Sync,At End"), "set_switch_mode", "get_switch_mode");
@ -164,16 +179,25 @@ AnimationNodeStateMachineTransition::AnimationNodeStateMachineTransition() {
////////////////////////////////////////////////////////
void AnimationNodeStateMachinePlayback::travel(const StringName &p_state) {
start_request_travel = true;
void AnimationNodeStateMachinePlayback::travel(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) {
travel_request = StringName();
reset_request = p_reset;
_start(p_state);
}
void AnimationNodeStateMachinePlayback::_start(const StringName &p_state) {
start_request = p_state;
stop_request = false;
}
void AnimationNodeStateMachinePlayback::start(const StringName &p_state) {
start_request_travel = false;
start_request = p_state;
stop_request = false;
void AnimationNodeStateMachinePlayback::next() {
next_request = true;
}
void AnimationNodeStateMachinePlayback::stop() {
@ -323,6 +347,15 @@ bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *p_sta
}
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);
start_request = StringName();
next_request = false;
stop_request = false;
reset_request_on_teleport = false;
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<AnimationNodeStateMachine> anodesm = p_state_machine->states[current].node;
if (anodesm.is_valid()) {
@ -335,14 +368,13 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s
//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);
_start(p_state_machine->start_node);
} else {
return 0;
}
}
if (playing && stop_request) {
stop_request = false;
playing = false;
return 0;
}
@ -350,42 +382,45 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s
bool play_start = false;
if (start_request != StringName()) {
if (start_request_travel) {
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;
} else {
// stopped, invalid state
String node_name = start_request;
start_request = StringName(); //clear start request
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?");
}
} else {
if (!_travel(p_state_machine, start_request)) {
// can't travel, then teleport
path.clear();
current = start_request;
play_start = true;
}
start_request = StringName(); //clear start request
}
// teleport to start
if (p_state_machine->states.has(start_request)) {
path.clear();
current = start_request;
playing = true;
play_start = true;
} else {
// teleport to start
if (p_state_machine->states.has(start_request)) {
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 = start_request;
current = p_state_machine->start_node;
playing = true;
play_start = true;
start_request = StringName(); //clear start request
} else {
StringName node = start_request;
start_request = StringName(); //clear start request
ERR_FAIL_V_MSG(0, "No such node: '" + node + "'");
// 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?");
}
} else {
if (!_travel(p_state_machine, travel_request)) {
// can't travel, then teleport
if (p_state_machine->states.has(travel_request)) {
path.clear();
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 + "'");
}
}
travel_request = StringName();
}
}
@ -396,8 +431,11 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s
current = p_state_machine->start_node;
}
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;
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 (!p_state_machine->states.has(current)) {
@ -421,7 +459,8 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s
if (current_curve.is_valid()) {
fade_blend = current_curve->sample(fade_blend);
}
double rem = 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.
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.
if (fading_from != StringName()) {
double fade_blend_inv = 1.0 - fade_blend;
@ -457,6 +496,7 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s
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];
}
}
@ -513,6 +553,7 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s
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();
}
}
@ -567,7 +608,7 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s
goto_next = fading_from == StringName();
}
if (goto_next) { //end_loop should be used because fade time may be too small or zero and animation may have looped
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;
@ -591,7 +632,9 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s
current = next;
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 (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);
@ -652,8 +695,9 @@ bool AnimationNodeStateMachinePlayback::_check_advance_condition(const Ref<Anima
}
void AnimationNodeStateMachinePlayback::_bind_methods() {
ClassDB::bind_method(D_METHOD("travel", "to_node"), &AnimationNodeStateMachinePlayback::travel);
ClassDB::bind_method(D_METHOD("start", "node"), &AnimationNodeStateMachinePlayback::start);
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));
ClassDB::bind_method(D_METHOD("next"), &AnimationNodeStateMachinePlayback::next);
ClassDB::bind_method(D_METHOD("stop"), &AnimationNodeStateMachinePlayback::stop);
ClassDB::bind_method(D_METHOD("is_playing"), &AnimationNodeStateMachinePlayback::is_playing);
ClassDB::bind_method(D_METHOD("get_current_node"), &AnimationNodeStateMachinePlayback::get_current_node);

View file

@ -57,6 +57,7 @@ private:
StringName advance_condition_name;
float xfade_time = 0.0;
Ref<Curve> xfade_curve;
bool reset = true;
int priority = 1;
String advance_expression;
@ -84,6 +85,9 @@ public:
void set_xfade_time(float p_xfade);
float get_xfade_time() const;
void set_reset(bool p_reset);
bool is_reset() const;
void set_xfade_curve(const Ref<Curve> &p_curve);
Ref<Curve> get_xfade_curve() const;
@ -131,10 +135,15 @@ class AnimationNodeStateMachinePlayback : public Resource {
bool playing = false;
StringName start_request;
bool start_request_travel = false;
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);
@ -144,8 +153,9 @@ protected:
static void _bind_methods();
public:
void travel(const StringName &p_state);
void start(const StringName &p_state);
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;