Browse Source

Add capture() and play_with_capture() as substitute of update capture

Silc Lizard (Tokage) Renew 1 năm trước cách đây
mục cha
commit
85d66cb4e8

+ 1 - 1
doc/classes/Animation.xml

@@ -651,7 +651,7 @@
 			Update at the keyframes.
 		</constant>
 		<constant name="UPDATE_CAPTURE" value="2" enum="UpdateMode">
-			Same as linear interpolation, but also interpolates from the current value (i.e. dynamically at runtime) if the first key isn't at 0 seconds.
+			Same as [constant UPDATE_CONTINUOUS] but works as a flag to capture the value of the current object and perform interpolation in some methods. See also [method AnimationMixer.capture] and [method AnimationPlayer.play_with_capture].
 		</constant>
 		<constant name="LOOP_NONE" value="0" enum="LoopMode">
 			At both ends of the animation, the animation will stop playing.

+ 12 - 0
doc/classes/AnimationMixer.xml

@@ -36,6 +36,18 @@
 				Manually advance the animations by the specified time (in seconds).
 			</description>
 		</method>
+		<method name="capture">
+			<return type="void" />
+			<param index="0" name="name" type="StringName" />
+			<param index="1" name="duration" type="float" />
+			<param index="2" name="trans_type" type="int" enum="Tween.TransitionType" default="0" />
+			<param index="3" name="ease_type" type="int" enum="Tween.EaseType" default="0" />
+			<description>
+				If the animation track specified by [param name] has an option [constant Animation.UPDATE_CAPTURE], stores current values of the objects indicated by the track path as a cache. If there is already a captured cache, the old cache is discarded.
+				After this it will interpolate with current animation blending result during the playback process for the time specified by [param duration], working like a crossfade.
+				You can specify [param trans_type] as the curve for the interpolation. For better results, it may be appropriate to specify [constant Tween.TRANS_LINEAR] for cases where the first key of the track begins with a non-zero value or where the key value does not change, and [constant Tween.TRANS_QUAD] for cases where the key value changes linearly.
+			</description>
+		</method>
 		<method name="clear_caches">
 			<return type="void" />
 			<description>

+ 21 - 1
doc/classes/AnimationPlayer.xml

@@ -110,6 +110,26 @@
 				This method is a shorthand for [method play] with [code]custom_speed = -1.0[/code] and [code]from_end = true[/code], so see its description for more information.
 			</description>
 		</method>
+		<method name="play_with_capture">
+			<return type="void" />
+			<param index="0" name="name" type="StringName" />
+			<param index="1" name="duration" type="float" default="-1.0" />
+			<param index="2" name="custom_blend" type="float" default="-1" />
+			<param index="3" name="custom_speed" type="float" default="1.0" />
+			<param index="4" name="from_end" type="bool" default="false" />
+			<param index="5" name="trans_type" type="int" enum="Tween.TransitionType" default="0" />
+			<param index="6" name="ease_type" type="int" enum="Tween.EaseType" default="0" />
+			<description>
+				See [method AnimationMixer.capture]. It is almost the same as the following:
+				[codeblock]
+				capture(name, duration, trans_type, ease_type)
+				play(name, custom_blend, custom_speed, from_end)
+				[/codeblock]
+				If name is blank, it specifies [member assigned_animation].
+				If [param duration] is a negative value, the duration is set to the interval between the current position and the first key, when [param from_end] is [code]true[/code], uses the interval between the current position and the last key instead.
+				[b]Note:[/b] The [param duration] takes [member speed_scale] into account, but [param custom_speed] does not, because the capture cache is interpolated with the blend result and the result may contain multiple animations.
+			</description>
+		</method>
 		<method name="queue">
 			<return type="void" />
 			<param index="0" name="name" type="StringName" />
@@ -125,7 +145,7 @@
 			<param index="2" name="update_only" type="bool" default="false" />
 			<description>
 				Seeks the animation to the [param seconds] point in time (in seconds). If [param update] is [code]true[/code], the animation updates too, otherwise it updates at process time. Events between the current frame and [param seconds] are skipped.
-				If [param update_only] is true, the method / audio / animation playback tracks will not be processed.
+				If [param update_only] is [code]true[/code], the method / audio / animation playback tracks will not be processed.
 				[b]Note:[/b] Seeking to the end of the animation doesn't emit [signal AnimationMixer.animation_finished]. If you want to skip animation and emit the signal, use [method AnimationMixer.advance].
 			</description>
 		</method>

+ 92 - 0
scene/animation/animation_mixer.cpp

@@ -552,6 +552,7 @@ void AnimationMixer::_clear_caches() {
 	}
 	track_cache.clear();
 	cache_valid = false;
+	capture_cache.clear();
 
 	emit_signal(SNAME("caches_cleared"));
 }
@@ -915,6 +916,7 @@ bool AnimationMixer::_update_caches() {
 void AnimationMixer::_process_animation(double p_delta, bool p_update_only) {
 	_blend_init();
 	if (_blend_pre_process(p_delta, track_count, track_map)) {
+		_blend_capture(p_delta);
 		_blend_calc_total_weight();
 		_blend_process(p_delta, p_update_only);
 		_blend_apply();
@@ -1013,6 +1015,43 @@ void AnimationMixer::_blend_post_process() {
 	//
 }
 
+void AnimationMixer::_blend_capture(double p_delta) {
+	blend_capture(p_delta);
+}
+
+void AnimationMixer::blend_capture(double p_delta) {
+	if (capture_cache.animation.is_null()) {
+		return;
+	}
+
+	capture_cache.remain -= p_delta * capture_cache.step;
+	if (capture_cache.remain <= 0.0) {
+		capture_cache.clear();
+		return;
+	}
+
+	real_t weight = Tween::run_equation(capture_cache.trans_type, capture_cache.ease_type, capture_cache.remain, 0.0, 1.0, 1.0);
+
+	// Blend with other animations.
+	real_t inv = 1.0 - weight;
+	for (AnimationInstance &ai : animation_instances) {
+		ai.playback_info.weight *= inv;
+	}
+
+	// Build capture animation instance.
+	AnimationData ad;
+	ad.animation = capture_cache.animation;
+
+	PlaybackInfo pi;
+	pi.weight = weight;
+
+	AnimationInstance ai;
+	ai.animation_data = ad;
+	ai.playback_info = pi;
+
+	animation_instances.push_back(ai);
+}
+
 void AnimationMixer::_blend_calc_total_weight() {
 	for (const AnimationInstance &ai : animation_instances) {
 		Ref<Animation> a = ai.animation_data.animation;
@@ -1848,6 +1887,10 @@ Vector3 AnimationMixer::get_root_motion_scale_accumulator() const {
 	return root_motion_scale_accumulator;
 }
 
+/* -------------------------------------------- */
+/* -- Reset on save --------------------------- */
+/* -------------------------------------------- */
+
 void AnimationMixer::set_reset_on_save_enabled(bool p_enabled) {
 	reset_on_save = p_enabled;
 }
@@ -2011,6 +2054,50 @@ Ref<AnimatedValuesBackup> AnimationMixer::apply_reset(bool p_user_initiated) {
 }
 #endif // TOOLS_ENABLED
 
+/* -------------------------------------------- */
+/* -- Capture feature ------------------------- */
+/* -------------------------------------------- */
+
+void AnimationMixer::capture(const StringName &p_name, double p_duration, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) {
+	ERR_FAIL_COND(!active);
+	ERR_FAIL_COND(!has_animation(p_name));
+	ERR_FAIL_COND(Math::is_zero_approx(p_duration));
+	Ref<Animation> reference_animation = get_animation(p_name);
+
+	if (!cache_valid) {
+		_update_caches(); // Need to retrieve object id.
+	}
+
+	capture_cache.remain = 1.0;
+	capture_cache.step = 1.0 / p_duration;
+	capture_cache.trans_type = p_trans_type;
+	capture_cache.ease_type = p_ease_type;
+	capture_cache.animation.instantiate();
+
+	bool is_valid = false;
+	for (int i = 0; i < reference_animation->get_track_count(); i++) {
+		if (!reference_animation->track_is_enabled(i)) {
+			continue;
+		}
+		if (reference_animation->track_get_type(i) == Animation::TYPE_VALUE && reference_animation->value_track_get_update_mode(i) == Animation::UPDATE_CAPTURE) {
+			TrackCacheValue *t = static_cast<TrackCacheValue *>(track_cache[reference_animation->track_get_type_hash(i)]);
+			Object *t_obj = ObjectDB::get_instance(t->object_id);
+			if (t_obj) {
+				Variant value = t_obj->get_indexed(t->subpath);
+				int inserted_idx = capture_cache.animation->add_track(Animation::TYPE_VALUE);
+				capture_cache.animation->track_set_path(inserted_idx, reference_animation->track_get_path(i));
+				capture_cache.animation->track_insert_key(inserted_idx, 0, value);
+				capture_cache.animation->value_track_set_update_mode(inserted_idx, Animation::UPDATE_CONTINUOUS);
+				capture_cache.animation->track_set_interpolation_type(inserted_idx, Animation::INTERPOLATION_LINEAR);
+				is_valid = true;
+			}
+		}
+	}
+	if (!is_valid) {
+		capture_cache.clear();
+	}
+}
+
 /* -------------------------------------------- */
 /* -- General functions ----------------------- */
 /* -------------------------------------------- */
@@ -2118,9 +2205,14 @@ void AnimationMixer::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deterministic"), "set_deterministic", "is_deterministic");
 
+	/* ---- Reset on save ---- */
 	ClassDB::bind_method(D_METHOD("set_reset_on_save_enabled", "enabled"), &AnimationMixer::set_reset_on_save_enabled);
 	ClassDB::bind_method(D_METHOD("is_reset_on_save_enabled"), &AnimationMixer::is_reset_on_save_enabled);
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset_on_save", PROPERTY_HINT_NONE, ""), "set_reset_on_save_enabled", "is_reset_on_save_enabled");
+
+	/* ---- Capture feature ---- */
+	ClassDB::bind_method(D_METHOD("capture", "name", "duration", "trans_type", "ease_type"), &AnimationMixer::capture, DEFVAL(Tween::TRANS_LINEAR), DEFVAL(Tween::EASE_IN));
+
 	ADD_SIGNAL(MethodInfo("mixer_updated")); // For updating dummy player.
 
 	ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_node"), "set_root_node", "get_root_node");

+ 27 - 1
scene/animation/animation_mixer.h

@@ -31,6 +31,7 @@
 #ifndef ANIMATION_MIXER_H
 #define ANIMATION_MIXER_H
 
+#include "scene/animation/tween.h"
 #include "scene/main/node.h"
 #include "scene/resources/animation.h"
 #include "scene/resources/animation_library.h"
@@ -334,12 +335,34 @@ protected:
 
 	void _blend_init();
 	virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map);
+	virtual void _blend_capture(double p_delta);
 	void _blend_calc_total_weight(); // For undeterministic blending.
 	void _blend_process(double p_delta, bool p_update_only = false);
 	void _blend_apply();
 	virtual void _blend_post_process();
 	void _call_object(ObjectID p_object_id, const StringName &p_method, const Vector<Variant> &p_params, bool p_deferred);
 
+	/* ---- Capture feature ---- */
+	struct CaptureCache {
+		Ref<Animation> animation;
+		double remain = 0.0;
+		double step = 0.0;
+		Tween::TransitionType trans_type = Tween::TRANS_LINEAR;
+		Tween::EaseType ease_type = Tween::EASE_IN;
+
+		void clear() {
+			animation.unref();
+			remain = 0.0;
+			step = 0.0;
+		}
+
+		CaptureCache() {}
+		~CaptureCache() {
+			clear();
+		}
+	} capture_cache;
+	void blend_capture(double p_delta); // To blend capture track with all other animations.
+
 #ifndef DISABLE_DEPRECATED
 	virtual Variant _post_process_key_value_bind_compat_86687(const Ref<Animation> &p_anim, int p_track, Variant p_value, Object *p_object, int p_object_idx = -1);
 
@@ -400,9 +423,12 @@ public:
 	virtual void advance(double p_time);
 	virtual void clear_caches(); ///< must be called by hand if an animation was modified after added
 
+	/* ---- Capture feature ---- */
+	void capture(const StringName &p_name, double p_duration, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN);
+
+	/* ---- Reset on save ---- */
 	void set_reset_on_save_enabled(bool p_enabled);
 	bool is_reset_on_save_enabled() const;
-
 	bool can_apply_reset() const;
 	void _build_backup_track_cache();
 	Ref<AnimatedValuesBackup> make_backup();

+ 67 - 2
scene/animation/animation_player.cpp

@@ -310,6 +310,10 @@ bool AnimationPlayer::_blend_pre_process(double p_delta, int p_track_count, cons
 	return true;
 }
 
+void AnimationPlayer::_blend_capture(double p_delta) {
+	blend_capture(p_delta * Math::abs(speed_scale));
+}
+
 void AnimationPlayer::_blend_post_process() {
 	if (end_reached) {
 		// If the method track changes current animation, the animation is not finished.
@@ -366,13 +370,73 @@ void AnimationPlayer::play_backwards(const StringName &p_name, double p_custom_b
 	play(p_name, p_custom_blend, -1, true);
 }
 
+void AnimationPlayer::play_with_capture(const StringName &p_name, double p_duration, double p_custom_blend, float p_custom_scale, bool p_from_end, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) {
+	StringName name = p_name;
+	if (name == StringName()) {
+		name = playback.assigned;
+	}
+
+	if (signbit(p_duration)) {
+		double max_dur = 0;
+		Ref<Animation> anim = get_animation(name);
+		if (anim.is_valid()) {
+			double current_pos = playback.current.pos;
+			if (playback.assigned != name) {
+				current_pos = p_from_end ? anim->get_length() : 0;
+			}
+			for (int i = 0; i < anim->get_track_count(); i++) {
+				if (anim->track_get_type(i) != Animation::TYPE_VALUE) {
+					continue;
+				}
+				if (anim->value_track_get_update_mode(i) != Animation::UPDATE_CAPTURE) {
+					continue;
+				}
+				if (anim->track_get_key_count(i) == 0) {
+					continue;
+				}
+				max_dur = MAX(max_dur, p_from_end ? current_pos - anim->track_get_key_time(i, anim->track_get_key_count(i) - 1) : anim->track_get_key_time(i, 0) - current_pos);
+			}
+		}
+		p_duration = max_dur;
+	}
+
+	capture(name, p_duration, p_trans_type, p_ease_type);
+	play(name, p_custom_blend, p_custom_scale, p_from_end);
+}
+
 void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) {
 	StringName name = p_name;
 
-	if (String(name) == "") {
+	if (name == StringName()) {
 		name = playback.assigned;
 	}
 
+#ifdef TOOLS_ENABLED
+	if (!Engine::get_singleton()->is_editor_hint()) {
+		bool warn_enabled = false;
+		if (capture_cache.animation.is_null()) {
+			Ref<Animation> anim = get_animation(name);
+			if (anim.is_valid()) {
+				for (int i = 0; i < anim->get_track_count(); i++) {
+					if (anim->track_get_type(i) != Animation::TYPE_VALUE) {
+						continue;
+					}
+					if (anim->value_track_get_update_mode(i) != Animation::UPDATE_CAPTURE) {
+						continue;
+					}
+					if (anim->track_get_key_count(i) == 0) {
+						continue;
+					}
+					warn_enabled = true;
+				}
+			}
+		}
+		if (warn_enabled) {
+			WARN_PRINT_ONCE_ED("Capture track found. If you want to interpolate animation with captured frame, you can use play_with_capture() instead of play().");
+		}
+	}
+#endif
+
 	ERR_FAIL_COND_MSG(!animation_set.has(name), vformat("Animation not found: %s.", name));
 
 	Playback &c = playback;
@@ -417,7 +481,7 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa
 	}
 
 	if (get_current_animation() != p_name) {
-		_clear_caches();
+		_clear_playing_caches();
 	}
 
 	c.current.from = &animation_set[name];
@@ -751,6 +815,7 @@ void AnimationPlayer::_bind_methods() {
 
 	ClassDB::bind_method(D_METHOD("play", "name", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::play, DEFVAL(""), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false));
 	ClassDB::bind_method(D_METHOD("play_backwards", "name", "custom_blend"), &AnimationPlayer::play_backwards, DEFVAL(""), DEFVAL(-1));
+	ClassDB::bind_method(D_METHOD("play_with_capture", "name", "duration", "custom_blend", "custom_speed", "from_end", "trans_type", "ease_type"), &AnimationPlayer::play_with_capture, DEFVAL(-1.0), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false), DEFVAL(Tween::TRANS_LINEAR), DEFVAL(Tween::EASE_IN));
 	ClassDB::bind_method(D_METHOD("pause"), &AnimationPlayer::pause);
 	ClassDB::bind_method(D_METHOD("stop", "keep_state"), &AnimationPlayer::stop, DEFVAL(false));
 	ClassDB::bind_method(D_METHOD("is_playing"), &AnimationPlayer::is_playing);

+ 2 - 0
scene/animation/animation_player.h

@@ -128,6 +128,7 @@ protected:
 
 	// Make animation instances.
 	virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) override;
+	virtual void _blend_capture(double p_delta) override;
 	virtual void _blend_post_process() override;
 
 	virtual void _animation_removed(const StringName &p_name, const StringName &p_library) override;
@@ -157,6 +158,7 @@ public:
 
 	void play(const StringName &p_name = StringName(), double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false);
 	void play_backwards(const StringName &p_name = StringName(), double p_custom_blend = -1);
+	void play_with_capture(const StringName &p_name, double p_duration = -1.0, double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN);
 	void queue(const StringName &p_name);
 	Vector<String> get_queue();
 	void clear_queue();