Przeglądaj źródła

Merge pull request #71264 from TokageItLab/improve-statemachine

Add next/reset function to `AnimationStateMachine`
Rémi Verschelde 2 lat temu
rodzic
commit
a202f5104f

+ 11 - 0
doc/classes/AnimationNodeStateMachinePlayback.xml

@@ -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>

+ 3 - 0
doc/classes/AnimationNodeStateMachineTransition.xml

@@ -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>

+ 1 - 1
doc/classes/AnimationNodeTransition.xml

@@ -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">

+ 8 - 8
scene/animation/animation_blend_tree.cpp

@@ -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);

+ 3 - 3
scene/animation/animation_blend_tree.h

@@ -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;
 

+ 88 - 44
scene/animation/animation_node_state_machine.cpp

@@ -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,18 +179,27 @@ AnimationNodeStateMachineTransition::AnimationNodeStateMachineTransition() {
 
 ////////////////////////////////////////////////////////
 
-void AnimationNodeStateMachinePlayback::travel(const StringName &p_state) {
-	start_request_travel = true;
-	start_request = p_state;
+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) {
-	start_request_travel = 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::next() {
+	next_request = true;
+}
+
 void AnimationNodeStateMachinePlayback::stop() {
 	stop_request = true;
 }
@@ -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);

+ 13 - 3
scene/animation/animation_node_state_machine.h

@@ -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;