Pārlūkot izejas kodu

Merge pull request #72396 from TokageItLab/bs1dconsist

Consistent with NodeBlendSpace1D option NodeBlendSpace2D
Rémi Verschelde 2 gadi atpakaļ
vecāks
revīzija
92a6586fb6

+ 14 - 0
doc/classes/AnimationNodeBlendSpace1D.xml

@@ -67,6 +67,9 @@
 		</method>
 	</methods>
 	<members>
+		<member name="blend_mode" type="int" setter="set_blend_mode" getter="get_blend_mode" enum="AnimationNodeBlendSpace1D.BlendMode" default="0">
+			Controls the interpolation between animations. See [enum BlendMode] constants.
+		</member>
 		<member name="max_space" type="float" setter="set_max_space" getter="get_max_space" default="1.0">
 			The blend space's axis's upper limit for the points' position. See [method add_blend_point].
 		</member>
@@ -84,4 +87,15 @@
 			Label of the virtual axis of the blend space.
 		</member>
 	</members>
+	<constants>
+		<constant name="BLEND_MODE_INTERPOLATED" value="0" enum="BlendMode">
+			The interpolation between animations is linear.
+		</constant>
+		<constant name="BLEND_MODE_DISCRETE" value="1" enum="BlendMode">
+			The blend space plays the animation of the node the blending position is closest to. Useful for frame-by-frame 2D animations.
+		</constant>
+		<constant name="BLEND_MODE_DISCRETE_CARRY" value="2" enum="BlendMode">
+			Similar to [constant BLEND_MODE_DISCRETE], but starts the new animation at the last animation's playback position.
+		</constant>
+	</constants>
 </class>

+ 16 - 0
editor/plugins/animation_blend_space_1d_editor.cpp

@@ -38,6 +38,7 @@
 #include "editor/editor_undo_redo_manager.h"
 #include "scene/animation/animation_blend_tree.h"
 #include "scene/gui/check_box.h"
+#include "scene/gui/option_button.h"
 #include "scene/gui/panel_container.h"
 
 StringName AnimationNodeBlendSpace1DEditor::get_blend_position_path() const {
@@ -335,6 +336,7 @@ void AnimationNodeBlendSpace1DEditor::_update_space() {
 	min_value->set_value(blend_space->get_min_space());
 
 	sync->set_pressed(blend_space->is_using_sync());
+	interpolation->select(blend_space->get_blend_mode());
 
 	label_value->set_text(blend_space->get_value_label());
 
@@ -361,6 +363,8 @@ void AnimationNodeBlendSpace1DEditor::_config_changed(double) {
 	undo_redo->add_undo_method(blend_space.ptr(), "set_snap", blend_space->get_snap());
 	undo_redo->add_do_method(blend_space.ptr(), "set_use_sync", sync->is_pressed());
 	undo_redo->add_undo_method(blend_space.ptr(), "set_use_sync", blend_space->is_using_sync());
+	undo_redo->add_do_method(blend_space.ptr(), "set_blend_mode", interpolation->get_selected());
+	undo_redo->add_undo_method(blend_space.ptr(), "set_blend_mode", blend_space->get_blend_mode());
 	undo_redo->add_do_method(this, "_update_space");
 	undo_redo->add_undo_method(this, "_update_space");
 	undo_redo->commit_action();
@@ -579,6 +583,10 @@ void AnimationNodeBlendSpace1DEditor::_notification(int p_what) {
 			tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
 			snap->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons")));
 			open_editor->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")));
+			interpolation->clear();
+			interpolation->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), "", 0);
+			interpolation->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), "", 1);
+			interpolation->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), "", 2);
 		} break;
 
 		case NOTIFICATION_PROCESS: {
@@ -639,6 +647,7 @@ void AnimationNodeBlendSpace1DEditor::edit(const Ref<AnimationNode> &p_node) {
 	min_value->set_editable(!read_only);
 	max_value->set_editable(!read_only);
 	sync->set_disabled(read_only);
+	interpolation->set_disabled(read_only);
 }
 
 AnimationNodeBlendSpace1DEditor *AnimationNodeBlendSpace1DEditor::singleton = nullptr;
@@ -707,6 +716,13 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() {
 	top_hb->add_child(sync);
 	sync->connect("toggled", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
 
+	top_hb->add_child(memnew(VSeparator));
+
+	top_hb->add_child(memnew(Label(TTR("Blend:"))));
+	interpolation = memnew(OptionButton);
+	top_hb->add_child(interpolation);
+	interpolation->connect("item_selected", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
+
 	edit_hb = memnew(HBoxContainer);
 	top_hb->add_child(edit_hb);
 	edit_hb->add_child(memnew(VSeparator));

+ 2 - 0
editor/plugins/animation_blend_space_1d_editor.h

@@ -41,6 +41,7 @@
 #include "scene/gui/tree.h"
 
 class CheckBox;
+class OptionButton;
 class PanelContainer;
 
 class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin {
@@ -66,6 +67,7 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin {
 	SpinBox *min_value = nullptr;
 
 	CheckBox *sync = nullptr;
+	OptionButton *interpolation = nullptr;
 
 	HBoxContainer *edit_hb = nullptr;
 	SpinBox *edit_value = nullptr;

+ 125 - 55
scene/animation/animation_blend_space_1d.cpp

@@ -30,12 +30,20 @@
 
 #include "animation_blend_space_1d.h"
 
+#include "animation_blend_tree.h"
+
 void AnimationNodeBlendSpace1D::get_parameter_list(List<PropertyInfo> *r_list) const {
 	r_list->push_back(PropertyInfo(Variant::FLOAT, blend_position));
+	r_list->push_back(PropertyInfo(Variant::INT, closest, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
+	r_list->push_back(PropertyInfo(Variant::FLOAT, length_internal, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
 }
 
 Variant AnimationNodeBlendSpace1D::get_parameter_default_value(const StringName &p_parameter) const {
-	return 0;
+	if (p_parameter == closest) {
+		return -1;
+	} else {
+		return 0;
+	}
 }
 
 Ref<AnimationNode> AnimationNodeBlendSpace1D::get_child_by_name(const StringName &p_name) {
@@ -77,6 +85,9 @@ void AnimationNodeBlendSpace1D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_value_label", "text"), &AnimationNodeBlendSpace1D::set_value_label);
 	ClassDB::bind_method(D_METHOD("get_value_label"), &AnimationNodeBlendSpace1D::get_value_label);
 
+	ClassDB::bind_method(D_METHOD("set_blend_mode", "mode"), &AnimationNodeBlendSpace1D::set_blend_mode);
+	ClassDB::bind_method(D_METHOD("get_blend_mode"), &AnimationNodeBlendSpace1D::get_blend_mode);
+
 	ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlendSpace1D::set_use_sync);
 	ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlendSpace1D::is_using_sync);
 
@@ -91,7 +102,12 @@ void AnimationNodeBlendSpace1D::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_max_space", "get_max_space");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_snap", "get_snap");
 	ADD_PROPERTY(PropertyInfo(Variant::STRING, "value_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_value_label", "get_value_label");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Interpolated,Discrete,Carry", PROPERTY_USAGE_NO_EDITOR), "set_blend_mode", "get_blend_mode");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_use_sync", "is_using_sync");
+
+	BIND_ENUM_CONSTANT(BLEND_MODE_INTERPOLATED);
+	BIND_ENUM_CONSTANT(BLEND_MODE_DISCRETE);
+	BIND_ENUM_CONSTANT(BLEND_MODE_DISCRETE_CARRY);
 }
 
 void AnimationNodeBlendSpace1D::get_child_nodes(List<ChildNode> *r_child_nodes) {
@@ -214,6 +230,14 @@ String AnimationNodeBlendSpace1D::get_value_label() const {
 	return value_label;
 }
 
+void AnimationNodeBlendSpace1D::set_blend_mode(BlendMode p_blend_mode) {
+	blend_mode = p_blend_mode;
+}
+
+AnimationNodeBlendSpace1D::BlendMode AnimationNodeBlendSpace1D::get_blend_mode() const {
+	return blend_mode;
+}
+
 void AnimationNodeBlendSpace1D::set_use_sync(bool p_sync) {
 	sync = p_sync;
 }
@@ -241,79 +265,125 @@ double AnimationNodeBlendSpace1D::process(double p_time, bool p_seek, bool p_is_
 	}
 
 	double blend_pos = get_parameter(blend_position);
+	int cur_closest = get_parameter(closest);
+	double cur_length_internal = get_parameter(length_internal);
+	double max_time_remaining = 0.0;
 
-	float weights[MAX_BLEND_POINTS] = {};
+	if (blend_mode == BLEND_MODE_INTERPOLATED) {
+		float weights[MAX_BLEND_POINTS] = {};
+
+		int point_lower = -1;
+		float pos_lower = 0.0;
+		int point_higher = -1;
+		float pos_higher = 0.0;
+
+		// find the closest two points to blend between
+		for (int i = 0; i < blend_points_used; i++) {
+			float pos = blend_points[i].position;
+
+			if (pos <= blend_pos) {
+				if (point_lower == -1) {
+					point_lower = i;
+					pos_lower = pos;
+				} else if ((blend_pos - pos) < (blend_pos - pos_lower)) {
+					point_lower = i;
+					pos_lower = pos;
+				}
+			} else {
+				if (point_higher == -1) {
+					point_higher = i;
+					pos_higher = pos;
+				} else if ((pos - blend_pos) < (pos_higher - blend_pos)) {
+					point_higher = i;
+					pos_higher = pos;
+				}
+			}
+		}
 
-	int point_lower = -1;
-	float pos_lower = 0.0;
-	int point_higher = -1;
-	float pos_higher = 0.0;
+		// fill in weights
 
-	// find the closest two points to blend between
-	for (int i = 0; i < blend_points_used; i++) {
-		float pos = blend_points[i].position;
-
-		if (pos <= blend_pos) {
-			if (point_lower == -1) {
-				point_lower = i;
-				pos_lower = pos;
-			} else if ((blend_pos - pos) < (blend_pos - pos_lower)) {
-				point_lower = i;
-				pos_lower = pos;
-			}
+		if (point_lower == -1 && point_higher != -1) {
+			// we are on the left side, no other point to the left
+			// we just play the next point.
+
+			weights[point_higher] = 1.0;
+		} else if (point_higher == -1) {
+			// we are on the right side, no other point to the right
+			// we just play the previous point
+
+			weights[point_lower] = 1.0;
 		} else {
-			if (point_higher == -1) {
-				point_higher = i;
-				pos_higher = pos;
-			} else if ((pos - blend_pos) < (pos_higher - blend_pos)) {
-				point_higher = i;
-				pos_higher = pos;
-			}
-		}
-	}
+			// we are between two points.
+			// figure out weights, then blend the animations
 
-	// fill in weights
+			float distance_between_points = pos_higher - pos_lower;
 
-	if (point_lower == -1 && point_higher != -1) {
-		// we are on the left side, no other point to the left
-		// we just play the next point.
+			float current_pos_inbetween = blend_pos - pos_lower;
 
-		weights[point_higher] = 1.0;
-	} else if (point_higher == -1) {
-		// we are on the right side, no other point to the right
-		// we just play the previous point
+			float blend_percentage = current_pos_inbetween / distance_between_points;
 
-		weights[point_lower] = 1.0;
-	} else {
-		// we are between two points.
-		// figure out weights, then blend the animations
+			float blend_lower = 1.0 - blend_percentage;
+			float blend_higher = blend_percentage;
 
-		float distance_between_points = pos_higher - pos_lower;
+			weights[point_lower] = blend_lower;
+			weights[point_higher] = blend_higher;
+		}
 
-		float current_pos_inbetween = blend_pos - pos_lower;
+		// actually blend the animations now
 
-		float blend_percentage = current_pos_inbetween / distance_between_points;
+		for (int i = 0; i < blend_points_used; i++) {
+			if (i == point_lower || i == point_higher) {
+				double remaining = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, weights[i], FILTER_IGNORE, true);
+				max_time_remaining = MAX(max_time_remaining, remaining);
+			} else if (sync) {
+				blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true);
+			}
+		}
+	} else {
+		int new_closest = -1;
+		double new_closest_dist = 1e20;
+
+		for (int i = 0; i < blend_points_used; i++) {
+			double d = abs(blend_points[i].position - blend_pos);
+			if (d < new_closest_dist) {
+				new_closest = i;
+				new_closest_dist = d;
+			}
+		}
 
-		float blend_lower = 1.0 - blend_percentage;
-		float blend_higher = blend_percentage;
+		if (new_closest != cur_closest && new_closest != -1) {
+			double from = 0.0;
+			if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
+				//for ping-pong loop
+				Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node);
+				Ref<AnimationNodeAnimation> na_n = static_cast<Ref<AnimationNodeAnimation>>(blend_points[new_closest].node);
+				if (!na_c.is_null() && !na_n.is_null()) {
+					na_n->set_backward(na_c->is_backward());
+				}
+				//see how much animation remains
+				from = cur_length_internal - blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, false, p_is_external_seeking, 0.0, FILTER_IGNORE, true);
+			}
 
-		weights[point_lower] = blend_lower;
-		weights[point_higher] = blend_higher;
-	}
+			max_time_remaining = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true);
+			cur_length_internal = from + max_time_remaining;
 
-	// actually blend the animations now
+			cur_closest = new_closest;
 
-	double max_time_remaining = 0.0;
+		} else {
+			max_time_remaining = blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true);
+		}
 
-	for (int i = 0; i < blend_points_used; i++) {
-		if (i == point_lower || i == point_higher) {
-			double remaining = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, weights[i], FILTER_IGNORE, true);
-			max_time_remaining = MAX(max_time_remaining, remaining);
-		} else if (sync) {
-			blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true);
+		if (sync) {
+			for (int i = 0; i < blend_points_used; i++) {
+				if (i != cur_closest) {
+					blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true);
+				}
+			}
 		}
 	}
 
+	set_parameter(this->closest, cur_closest);
+	set_parameter(this->length_internal, cur_length_internal);
 	return max_time_remaining;
 }
 

+ 17 - 0
scene/animation/animation_blend_space_1d.h

@@ -36,6 +36,14 @@
 class AnimationNodeBlendSpace1D : public AnimationRootNode {
 	GDCLASS(AnimationNodeBlendSpace1D, AnimationRootNode);
 
+public:
+	enum BlendMode {
+		BLEND_MODE_INTERPOLATED,
+		BLEND_MODE_DISCRETE,
+		BLEND_MODE_DISCRETE_CARRY,
+	};
+
+protected:
 	enum {
 		MAX_BLEND_POINTS = 64
 	};
@@ -61,6 +69,10 @@ class AnimationNodeBlendSpace1D : public AnimationRootNode {
 	void _tree_changed();
 
 	StringName blend_position = "blend_position";
+	StringName closest = "closest";
+	StringName length_internal = "length_internal";
+
+	BlendMode blend_mode = BLEND_MODE_INTERPOLATED;
 
 protected:
 	bool sync = false;
@@ -95,6 +107,9 @@ public:
 	void set_value_label(const String &p_label);
 	String get_value_label() const;
 
+	void set_blend_mode(BlendMode p_blend_mode);
+	BlendMode get_blend_mode() const;
+
 	void set_use_sync(bool p_sync);
 	bool is_using_sync() const;
 
@@ -107,4 +122,6 @@ public:
 	~AnimationNodeBlendSpace1D();
 };
 
+VARIANT_ENUM_CAST(AnimationNodeBlendSpace1D::BlendMode)
+
 #endif // ANIMATION_BLEND_SPACE_1D_H