feat: updated engine version to 4.4-rc1

This commit is contained in:
Sara 2025-02-23 14:38:14 +01:00
parent ee00efde1f
commit 21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")

View file

@ -374,21 +374,20 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(const AnimationM
}
if (new_closest != cur_closest && new_closest != -1) {
NodeTimeInfo from;
if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
//for ping-pong loop
NodeTimeInfo from;
// 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()) {
if (na_c.is_valid() && na_n.is_valid()) {
na_n->set_backward(na_c->is_backward());
}
//see how much animation remains
// See how much animation remains.
pi.seeked = false;
pi.weight = 0;
from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, true);
pi.time = from.position;
}
pi.time = from.position;
pi.seeked = true;
pi.weight = 1.0;
mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only);

View file

@ -316,7 +316,7 @@ void AnimationNodeBlendSpace2D::_set_triangles(const Vector<int> &p_triangles) {
Vector<int> AnimationNodeBlendSpace2D::_get_triangles() const {
Vector<int> t;
if (auto_triangles && trianges_dirty) {
if (auto_triangles && triangles_dirty) {
return t;
}
@ -330,20 +330,20 @@ Vector<int> AnimationNodeBlendSpace2D::_get_triangles() const {
}
void AnimationNodeBlendSpace2D::_queue_auto_triangles() {
if (!auto_triangles || trianges_dirty) {
if (!auto_triangles || triangles_dirty) {
return;
}
trianges_dirty = true;
triangles_dirty = true;
callable_mp(this, &AnimationNodeBlendSpace2D::_update_triangles).call_deferred();
}
void AnimationNodeBlendSpace2D::_update_triangles() {
if (!auto_triangles || !trianges_dirty) {
if (!auto_triangles || !triangles_dirty) {
return;
}
trianges_dirty = false;
triangles_dirty = false;
triangles.clear();
if (blend_points_used < 3) {
emit_signal(SNAME("triangles_updated"));
@ -551,21 +551,20 @@ AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(const AnimationM
}
if (new_closest != cur_closest && new_closest != -1) {
NodeTimeInfo from;
if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
//for ping-pong loop
NodeTimeInfo from;
// 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()) {
if (na_c.is_valid() && na_n.is_valid()) {
na_n->set_backward(na_c->is_backward());
}
//see how much animation remains
// See how much animation remains.
pi.seeked = false;
pi.weight = 0;
from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, true);
pi.time = from.position;
}
pi.time = from.position;
pi.seeked = true;
pi.weight = 1.0;
mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only);

View file

@ -79,7 +79,7 @@ protected:
void _blend_triangle(const Vector2 &p_pos, const Vector2 *p_points, float *r_weights);
bool auto_triangles = true;
bool trianges_dirty = false;
bool triangles_dirty = false;
void _update_triangles();
void _queue_auto_triangles();

View file

@ -44,6 +44,18 @@ Vector<String> (*AnimationNodeAnimation::get_editable_animation_list)() = nullpt
void AnimationNodeAnimation::get_parameter_list(List<PropertyInfo> *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::BOOL, backward, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
}
Variant AnimationNodeAnimation::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 == backward) {
return false;
}
return 0;
}
AnimationNode::NodeTimeInfo AnimationNodeAnimation::get_node_time_info() const {
@ -93,9 +105,11 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::process(const AnimationMixer
AnimationMixer::PlaybackInfo pi = p_playback_info;
if (p_playback_info.seeked) {
pi.delta = get_node_time_info().position - p_playback_info.time;
if (p_playback_info.is_external_seeking) {
pi.delta = get_node_time_info().position - p_playback_info.time;
}
} else {
pi.time = get_node_time_info().position + (backward ? -p_playback_info.delta : p_playback_info.delta);
pi.time = get_node_time_info().position + (get_parameter(backward) ? -p_playback_info.delta : p_playback_info.delta);
}
NodeTimeInfo nti = _process(pi, p_test_only);
@ -128,6 +142,7 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe
double cur_len = cur_nti.length;
double cur_time = p_playback_info.time;
double cur_delta = p_playback_info.delta;
bool cur_backward = get_parameter(backward);
Animation::LoopMode cur_loop_mode = cur_nti.loop_mode;
double prev_time = cur_nti.position;
@ -140,18 +155,24 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe
// 1. Progress for AnimationNode.
bool will_end = Animation::is_greater_or_equal_approx(cur_time + cur_delta, cur_len);
bool is_started = p_seek && !p_is_external_seeking && Math::is_zero_approx(cur_time);
// 1. Progress for AnimationNode.
if (is_started && advance_on_start) {
cur_time = cur_delta;
}
if (cur_loop_mode != Animation::LOOP_NONE) {
if (cur_loop_mode == Animation::LOOP_LINEAR) {
if (!Math::is_zero_approx(cur_len)) {
cur_time = Math::fposmod(cur_time, cur_len);
}
backward = false;
cur_backward = false;
} else {
if (!Math::is_zero_approx(cur_len)) {
if (Animation::is_greater_or_equal_approx(prev_time, 0) && Animation::is_less_approx(cur_time, 0)) {
backward = !backward;
cur_backward = !cur_backward;
} else if (Animation::is_less_or_equal_approx(prev_time, cur_len) && Animation::is_greater_approx(cur_time, cur_len)) {
backward = !backward;
cur_backward = !cur_backward;
}
cur_time = Math::pingpong(cur_time, cur_len);
}
@ -164,7 +185,7 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe
cur_delta += cur_time - cur_len;
cur_time = cur_len;
}
backward = false;
cur_backward = false;
// If ended, don't progress AnimationNode. So set delta to 0.
if (!Math::is_zero_approx(cur_delta)) {
if (play_mode == PLAY_MODE_FORWARD) {
@ -232,7 +253,7 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe
// We should use call_deferred since the track keys are still being processed.
if (process_state->tree && !p_test_only) {
// AnimationTree uses seek to 0 "internally" to process the first key of the animation, which is used as the start detection.
if (p_seek && !p_is_external_seeking && Math::is_zero_approx(cur_playback_time)) {
if (is_started) {
process_state->tree->call_deferred(SNAME("emit_signal"), SceneStringName(animation_started), animation);
}
// Finished.
@ -245,6 +266,8 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe
if (!p_test_only) {
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.start = 0.0;
pi.end = cur_len;
if (play_mode == PLAY_MODE_FORWARD) {
pi.time = cur_playback_time;
pi.delta = cur_delta;
@ -257,6 +280,8 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe
blend_animation(animation, pi);
}
set_parameter(backward, cur_backward);
return nti;
}
@ -273,11 +298,19 @@ AnimationNodeAnimation::PlayMode AnimationNodeAnimation::get_play_mode() const {
}
void AnimationNodeAnimation::set_backward(bool p_backward) {
backward = p_backward;
set_parameter(backward, p_backward);
}
bool AnimationNodeAnimation::is_backward() const {
return backward;
return get_parameter(backward);
}
void AnimationNodeAnimation::set_advance_on_start(bool p_advance_on_start) {
advance_on_start = p_advance_on_start;
}
bool AnimationNodeAnimation::is_advance_on_start() const {
return advance_on_start;
}
void AnimationNodeAnimation::set_use_custom_timeline(bool p_use_custom_timeline) {
@ -297,8 +330,8 @@ double AnimationNodeAnimation::get_timeline_length() const {
return timeline_length;
}
void AnimationNodeAnimation::set_stretch_time_scale(bool p_strech_time_scale) {
stretch_time_scale = p_strech_time_scale;
void AnimationNodeAnimation::set_stretch_time_scale(bool p_stretch_time_scale) {
stretch_time_scale = p_stretch_time_scale;
notify_property_list_changed();
}
@ -329,6 +362,9 @@ void AnimationNodeAnimation::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_play_mode", "mode"), &AnimationNodeAnimation::set_play_mode);
ClassDB::bind_method(D_METHOD("get_play_mode"), &AnimationNodeAnimation::get_play_mode);
ClassDB::bind_method(D_METHOD("set_advance_on_start", "advance_on_start"), &AnimationNodeAnimation::set_advance_on_start);
ClassDB::bind_method(D_METHOD("is_advance_on_start"), &AnimationNodeAnimation::is_advance_on_start);
ClassDB::bind_method(D_METHOD("set_use_custom_timeline", "use_custom_timeline"), &AnimationNodeAnimation::set_use_custom_timeline);
ClassDB::bind_method(D_METHOD("is_using_custom_timeline"), &AnimationNodeAnimation::is_using_custom_timeline);
@ -346,6 +382,7 @@ void AnimationNodeAnimation::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation"), "set_animation", "get_animation");
ADD_PROPERTY(PropertyInfo(Variant::INT, "play_mode", PROPERTY_HINT_ENUM, "Forward,Backward"), "set_play_mode", "get_play_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "advance_on_start"), "set_advance_on_start", "is_advance_on_start");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_custom_timeline"), "set_use_custom_timeline", "is_using_custom_timeline");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "timeline_length", PROPERTY_HINT_RANGE, "0.001,60,0.001,or_greater,or_less,hide_slider,suffix:s"), "set_timeline_length", "get_timeline_length");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stretch_time_scale"), "set_stretch_time_scale", "is_stretching_time_scale");
@ -754,9 +791,6 @@ AnimationNode::NodeTimeInfo AnimationNodeAdd2::_process(const AnimationMixer::Pl
return nti;
}
void AnimationNodeAdd2::_bind_methods() {
}
AnimationNodeAdd2::AnimationNodeAdd2() {
add_input("in");
add_input("add");
@ -800,9 +834,6 @@ AnimationNode::NodeTimeInfo AnimationNodeAdd3::_process(const AnimationMixer::Pl
return nti;
}
void AnimationNodeAdd3::_bind_methods() {
}
AnimationNodeAdd3::AnimationNodeAdd3() {
add_input("-add");
add_input("in");
@ -845,9 +876,6 @@ bool AnimationNodeBlend2::has_filter() const {
return true;
}
void AnimationNodeBlend2::_bind_methods() {
}
AnimationNodeBlend2::AnimationNodeBlend2() {
add_input("in");
add_input("blend");
@ -887,9 +915,6 @@ AnimationNode::NodeTimeInfo AnimationNodeBlend3::_process(const AnimationMixer::
return amount > 0.5 ? nti2 : (amount < -0.5 ? nti0 : nti1); // Hacky but good enough.
}
void AnimationNodeBlend3::_bind_methods() {
}
AnimationNodeBlend3::AnimationNodeBlend3() {
add_input("-blend");
add_input("in");
@ -932,9 +957,6 @@ AnimationNode::NodeTimeInfo AnimationNodeSub2::_process(const AnimationMixer::Pl
return blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
}
void AnimationNodeSub2::_bind_methods() {
}
AnimationNodeSub2::AnimationNodeSub2() {
add_input("in");
add_input("sub");
@ -972,9 +994,6 @@ AnimationNode::NodeTimeInfo AnimationNodeTimeScale::_process(const AnimationMixe
return blend_input(0, pi, FILTER_IGNORE, true, p_test_only);
}
void AnimationNodeTimeScale::_bind_methods() {
}
AnimationNodeTimeScale::AnimationNodeTimeScale() {
add_input("in");
}
@ -999,6 +1018,14 @@ String AnimationNodeTimeSeek::get_caption() const {
return "TimeSeek";
}
void AnimationNodeTimeSeek::set_explicit_elapse(bool p_enable) {
explicit_elapse = p_enable;
}
bool AnimationNodeTimeSeek::is_explicit_elapse() const {
return explicit_elapse;
}
AnimationNode::NodeTimeInfo AnimationNodeTimeSeek::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double cur_seek_pos = get_parameter(seek_pos_request);
@ -1007,20 +1034,23 @@ AnimationNode::NodeTimeInfo AnimationNodeTimeSeek::_process(const AnimationMixer
if (Animation::is_greater_or_equal_approx(cur_seek_pos, 0)) {
pi.time = cur_seek_pos;
pi.seeked = true;
pi.is_external_seeking = true;
pi.is_external_seeking = explicit_elapse;
set_parameter(seek_pos_request, -1.0); // Reset.
}
return blend_input(0, pi, FILTER_IGNORE, true, p_test_only);
}
void AnimationNodeTimeSeek::_bind_methods() {
}
AnimationNodeTimeSeek::AnimationNodeTimeSeek() {
add_input("in");
}
void AnimationNodeTimeSeek::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_explicit_elapse", "enable"), &AnimationNodeTimeSeek::set_explicit_elapse);
ClassDB::bind_method(D_METHOD("is_explicit_elapse"), &AnimationNodeTimeSeek::is_explicit_elapse);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "explicit_elapse"), "set_explicit_elapse", "is_explicit_elapse");
}
/////////////////////////////////////////////////
bool AnimationNodeTransition::_set(const StringName &p_path, const Variant &p_value) {
@ -1160,12 +1190,16 @@ void AnimationNodeTransition::remove_input(int p_index) {
bool AnimationNodeTransition::set_input_name(int p_input, const String &p_name) {
pending_update = true;
return AnimationNode::set_input_name(p_input, p_name);
if (!AnimationNode::set_input_name(p_input, p_name)) {
return false;
}
emit_signal(SNAME("tree_changed")); // For updating enum options.
return true;
}
void AnimationNodeTransition::set_input_as_auto_advance(int p_input, bool p_enable) {
ERR_FAIL_INDEX(p_input, get_input_count());
input_data.write[p_input].auto_advance = p_enable;
input_data[p_input].auto_advance = p_enable;
}
bool AnimationNodeTransition::is_input_set_as_auto_advance(int p_input) const {
@ -1175,7 +1209,7 @@ bool AnimationNodeTransition::is_input_set_as_auto_advance(int p_input) const {
void AnimationNodeTransition::set_input_break_loop_at_end(int p_input, bool p_enable) {
ERR_FAIL_INDEX(p_input, get_input_count());
input_data.write[p_input].break_loop_at_end = p_enable;
input_data[p_input].break_loop_at_end = p_enable;
}
bool AnimationNodeTransition::is_input_loop_broken_at_end(int p_input) const {
@ -1185,7 +1219,7 @@ bool AnimationNodeTransition::is_input_loop_broken_at_end(int p_input) const {
void AnimationNodeTransition::set_input_reset(int p_input, bool p_enable) {
ERR_FAIL_INDEX(p_input, get_input_count());
input_data.write[p_input].reset = p_enable;
input_data[p_input].reset = p_enable;
}
bool AnimationNodeTransition::is_input_reset(int p_input) const {
@ -1418,7 +1452,7 @@ void AnimationNodeBlendTree::add_node(const StringName &p_name, Ref<AnimationNod
ERR_FAIL_COND(nodes.has(p_name));
ERR_FAIL_COND(p_node.is_null());
ERR_FAIL_COND(p_name == SceneStringName(output));
ERR_FAIL_COND(String(p_name).contains("/"));
ERR_FAIL_COND(String(p_name).contains_char('/'));
Node n;
n.node = p_node;

View file

@ -36,8 +36,12 @@
class AnimationNodeAnimation : public AnimationRootNode {
GDCLASS(AnimationNodeAnimation, AnimationRootNode);
StringName backward = "backward"; // Only used by pingpong animation.
StringName animation;
bool advance_on_start = false;
bool use_custom_timeline = false;
double timeline_length = 1.0;
Animation::LoopMode loop_mode = Animation::LOOP_NONE;
@ -54,6 +58,7 @@ public:
};
void get_parameter_list(List<PropertyInfo> *r_list) const override;
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
virtual NodeTimeInfo get_node_time_info() const override; // Wrapper of get_parameter().
@ -72,13 +77,16 @@ public:
void set_backward(bool p_backward);
bool is_backward() const;
void set_advance_on_start(bool p_advance_on_start);
bool is_advance_on_start() const;
void set_use_custom_timeline(bool p_use_custom_timeline);
bool is_using_custom_timeline() const;
void set_timeline_length(double p_length);
double get_timeline_length() const;
void set_stretch_time_scale(bool p_strech_time_scale);
void set_stretch_time_scale(bool p_stretch_time_scale);
bool is_stretching_time_scale() const;
void set_start_offset(double p_offset);
@ -95,7 +103,6 @@ protected:
private:
PlayMode play_mode = PLAY_MODE_FORWARD;
bool backward = false; // Only used by pingpong animation.
};
VARIANT_ENUM_CAST(AnimationNodeAnimation::PlayMode)
@ -200,9 +207,6 @@ class AnimationNodeAdd2 : public AnimationNodeSync {
StringName add_amount = PNAME("add_amount");
protected:
static void _bind_methods();
public:
void get_parameter_list(List<PropertyInfo> *r_list) const override;
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
@ -220,9 +224,6 @@ class AnimationNodeAdd3 : public AnimationNodeSync {
StringName add_amount = PNAME("add_amount");
protected:
static void _bind_methods();
public:
void get_parameter_list(List<PropertyInfo> *r_list) const override;
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
@ -240,9 +241,6 @@ class AnimationNodeBlend2 : public AnimationNodeSync {
StringName blend_amount = PNAME("blend_amount");
protected:
static void _bind_methods();
public:
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
@ -259,9 +257,6 @@ class AnimationNodeBlend3 : public AnimationNodeSync {
StringName blend_amount = PNAME("blend_amount");
protected:
static void _bind_methods();
public:
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
@ -277,9 +272,6 @@ class AnimationNodeSub2 : public AnimationNodeSync {
StringName sub_amount = PNAME("sub_amount");
protected:
static void _bind_methods();
public:
void get_parameter_list(List<PropertyInfo> *r_list) const override;
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
@ -297,9 +289,6 @@ class AnimationNodeTimeScale : public AnimationNode {
StringName scale = PNAME("scale");
protected:
static void _bind_methods();
public:
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
@ -315,6 +304,7 @@ class AnimationNodeTimeSeek : public AnimationNode {
GDCLASS(AnimationNodeTimeSeek, AnimationNode);
StringName seek_pos_request = PNAME("seek_request");
bool explicit_elapse = true;
protected:
static void _bind_methods();
@ -327,6 +317,9 @@ public:
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
void set_explicit_elapse(bool p_enable);
bool is_explicit_elapse() const;
AnimationNodeTimeSeek();
};
@ -338,7 +331,7 @@ class AnimationNodeTransition : public AnimationNodeSync {
bool break_loop_at_end = false;
bool reset = true;
};
Vector<InputData> input_data;
LocalVector<InputData> input_data;
StringName prev_xfading = "prev_xfading";
StringName prev_index = "prev_index";

View file

@ -33,6 +33,7 @@
#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "core/string/string_name.h"
#include "scene/2d/audio_stream_player_2d.h"
#include "scene/animation/animation_player.h"
#include "scene/audio/audio_stream_player.h"
@ -45,11 +46,9 @@
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/node_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/3d/skeleton_modifier_3d.h"
#endif // _3D_DISABLED
#ifdef TOOLS_ENABLED
#include "editor/editor_node.h"
#include "editor/editor_undo_redo_manager.h"
#endif // TOOLS_ENABLED
@ -81,9 +80,8 @@ bool AnimationMixer::_set(const StringName &p_name, const Variant &p_value) {
List<Variant> keys;
d.get_key_list(&keys);
for (const Variant &K : keys) {
StringName lib_name = K;
Ref<AnimationLibrary> lib = d[lib_name];
add_animation_library(lib_name, lib);
Ref<AnimationLibrary> lib = d[K];
add_animation_library(K, lib);
}
emit_signal(SNAME("animation_libraries_updated"));
@ -128,6 +126,9 @@ void AnimationMixer::_validate_property(PropertyInfo &p_property) const {
p_property.usage |= PROPERTY_USAGE_READ_ONLY;
}
#endif // TOOLS_ENABLED
if (root_motion_track.is_empty() && p_property.name == "root_motion_local") {
p_property.usage = PROPERTY_USAGE_NONE;
}
}
/* -------------------------------------------- */
@ -268,6 +269,16 @@ bool AnimationMixer::has_animation_library(const StringName &p_name) const {
return false;
}
StringName AnimationMixer::get_animation_library_name(const Ref<AnimationLibrary> &p_animation_library) const {
ERR_FAIL_COND_V(p_animation_library.is_null(), StringName());
for (const AnimationLibraryData &lib : animation_libraries) {
if (lib.library == p_animation_library) {
return lib.name;
}
}
return StringName();
}
StringName AnimationMixer::find_animation_library(const Ref<Animation> &p_animation) const {
for (const KeyValue<StringName, AnimationData> &E : animation_set) {
if (E.value.animation == p_animation) {
@ -280,7 +291,7 @@ StringName AnimationMixer::find_animation_library(const Ref<Animation> &p_animat
Error AnimationMixer::add_animation_library(const StringName &p_name, const Ref<AnimationLibrary> &p_animation_library) {
ERR_FAIL_COND_V(p_animation_library.is_null(), ERR_INVALID_PARAMETER);
#ifdef DEBUG_ENABLED
ERR_FAIL_COND_V_MSG(String(p_name).contains("/") || String(p_name).contains(":") || String(p_name).contains(",") || String(p_name).contains("["), ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + ".");
ERR_FAIL_COND_V_MSG(String(p_name).contains_char('/') || String(p_name).contains_char(':') || String(p_name).contains_char(',') || String(p_name).contains_char('['), ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + ".");
#endif
int insert_pos = 0;
@ -342,7 +353,7 @@ void AnimationMixer::rename_animation_library(const StringName &p_name, const St
return;
}
#ifdef DEBUG_ENABLED
ERR_FAIL_COND_MSG(String(p_new_name).contains("/") || String(p_new_name).contains(":") || String(p_new_name).contains(",") || String(p_new_name).contains("["), "Invalid animation library name: " + String(p_new_name) + ".");
ERR_FAIL_COND_MSG(String(p_new_name).contains_char('/') || String(p_new_name).contains_char(':') || String(p_new_name).contains_char(',') || String(p_new_name).contains_char('['), "Invalid animation library name: " + String(p_new_name) + ".");
#endif
bool found = false;
@ -564,6 +575,7 @@ void AnimationMixer::_clear_caches() {
memdelete(K.value);
}
track_cache.clear();
animation_track_num_to_track_cache.clear();
cache_valid = false;
capture_cache.clear();
@ -600,6 +612,22 @@ void AnimationMixer::_init_root_motion_cache() {
root_motion_scale_accumulator = Vector3(1, 1, 1);
}
void AnimationMixer::_create_track_num_to_track_cache_for_animation(Ref<Animation> &p_animation) {
ERR_FAIL_COND(animation_track_num_to_track_cache.has(p_animation));
LocalVector<TrackCache *> &track_num_to_track_cache = animation_track_num_to_track_cache.insert_new(p_animation, LocalVector<TrackCache *>())->value;
const Vector<Animation::Track *> &tracks = p_animation->get_tracks();
track_num_to_track_cache.resize(tracks.size());
for (int i = 0; i < tracks.size(); i++) {
TrackCache **track_ptr = track_cache.getptr(tracks[i]->thash);
if (track_ptr == nullptr) {
track_num_to_track_cache[i] = nullptr;
} else {
track_num_to_track_cache[i] = *track_ptr;
}
}
}
bool AnimationMixer::_update_caches() {
setup_pass++;
@ -717,6 +745,15 @@ bool AnimationMixer::_update_caches() {
}
}
if (is_value && callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS) {
if (child) {
PropertyInfo prop_info;
ClassDB::get_property_info(child->get_class_name(), path.get_concatenated_subnames(), &prop_info);
if (prop_info.hint == PROPERTY_HINT_ONESHOT) {
WARN_PRINT_ED(vformat("%s: '%s', Value Track: '%s' is oneshot property, but will be continuously updated. Consider setting a value other than ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS to AnimationMixer.callback_mode_dominant.", mixer_name, String(E), String(path)));
}
}
}
} break;
case Animation::TYPE_POSITION_3D:
case Animation::TYPE_ROTATION_3D:
@ -923,6 +960,16 @@ bool AnimationMixer::_update_caches() {
idx++;
}
for (KeyValue<Animation::TypeHash, TrackCache *> &K : track_cache) {
K.value->blend_idx = track_map[K.value->path];
}
animation_track_num_to_track_cache.clear();
for (const StringName &E : sname_list) {
Ref<Animation> anim = get_animation(E);
_create_track_num_to_track_cache_for_animation(anim);
}
track_count = idx;
cache_valid = true;
@ -947,15 +994,7 @@ void AnimationMixer::_process_animation(double p_delta, bool p_update_only) {
clear_animation_instances();
}
Variant AnimationMixer::post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx) {
Variant res;
if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, p_object_id, p_object_sub_idx, res)) {
return res;
}
return _post_process_key_value(p_anim, p_track, p_value, p_object_id, p_object_sub_idx);
}
Variant AnimationMixer::_post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx) {
Variant AnimationMixer::_post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant &p_value, ObjectID p_object_id, int p_object_sub_idx) {
#ifndef _3D_DISABLED
switch (p_anim->track_get_type(p_track)) {
case Animation::TYPE_POSITION_3D: {
@ -974,6 +1013,17 @@ Variant AnimationMixer::_post_process_key_value(const Ref<Animation> &p_anim, in
return p_value;
}
Variant AnimationMixer::post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx) {
if (is_GDVIRTUAL_CALL_post_process_key_value) {
Variant res;
if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, p_object_id, p_object_sub_idx, res)) {
return res;
}
is_GDVIRTUAL_CALL_post_process_key_value = false;
}
return _post_process_key_value(p_anim, p_track, p_value, p_object_id, p_object_sub_idx);
}
void AnimationMixer::_blend_init() {
// Check all tracks, see if they need modification.
root_motion_position = Vector3(0, 0, 0);
@ -1031,7 +1081,7 @@ void AnimationMixer::_blend_init() {
}
}
bool AnimationMixer::_blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) {
bool AnimationMixer::_blend_pre_process(double p_delta, int p_track_count, const AHashMap<NodePath, int> &p_track_map) {
return true;
}
@ -1050,6 +1100,9 @@ void AnimationMixer::blend_capture(double p_delta) {
capture_cache.remain -= p_delta * capture_cache.step;
if (Animation::is_less_or_equal_approx(capture_cache.remain, 0)) {
if (capture_cache.animation.is_valid()) {
animation_track_num_to_track_cache.erase(capture_cache.animation);
}
capture_cache.clear();
return;
}
@ -1080,24 +1133,32 @@ void AnimationMixer::_blend_calc_total_weight() {
for (const AnimationInstance &ai : animation_instances) {
Ref<Animation> a = ai.animation_data.animation;
real_t weight = ai.playback_info.weight;
Vector<real_t> track_weights = ai.playback_info.track_weights;
Vector<Animation::TypeHash> processed_hashes;
for (int i = 0; i < a->get_track_count(); i++) {
if (!a->track_is_enabled(i)) {
const real_t *track_weights_ptr = ai.playback_info.track_weights.ptr();
int track_weights_count = ai.playback_info.track_weights.size();
ERR_CONTINUE_EDMSG(!animation_track_num_to_track_cache.has(a), "No animation in cache.");
LocalVector<TrackCache *> &track_num_to_track_cache = animation_track_num_to_track_cache[a];
thread_local HashSet<Animation::TypeHash, HashHasher> processed_hashes;
processed_hashes.clear();
const Vector<Animation::Track *> tracks = a->get_tracks();
Animation::Track *const *tracks_ptr = tracks.ptr();
int count = tracks.size();
for (int i = 0; i < count; i++) {
Animation::Track *animation_track = tracks_ptr[i];
if (!animation_track->enabled) {
continue;
}
Animation::TypeHash thash = a->track_get_type_hash(i);
if (!track_cache.has(thash) || processed_hashes.has(thash)) {
Animation::TypeHash thash = animation_track->thash;
TrackCache *track = track_num_to_track_cache[i];
if (track == nullptr || processed_hashes.has(thash)) {
// No path, but avoid error spamming.
// Or, there is the case different track type with same path; These can be distinguished by hash. So don't add the weight doubly.
continue;
}
TrackCache *track = track_cache[thash];
int blend_idx = track_map[track->path];
int blend_idx = track->blend_idx;
ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count);
real_t blend = blend_idx < track_weights.size() ? track_weights[blend_idx] * weight : weight;
real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight;
track->total_weight += blend;
processed_hashes.push_back(thash);
processed_hashes.insert(thash);
}
}
}
@ -1111,30 +1172,37 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
Ref<Animation> a = ai.animation_data.animation;
double time = ai.playback_info.time;
double delta = ai.playback_info.delta;
double start = ai.playback_info.start;
double end = ai.playback_info.end;
bool seeked = ai.playback_info.seeked;
Animation::LoopedFlag looped_flag = ai.playback_info.looped_flag;
bool is_external_seeking = ai.playback_info.is_external_seeking;
real_t weight = ai.playback_info.weight;
Vector<real_t> track_weights = ai.playback_info.track_weights;
const real_t *track_weights_ptr = ai.playback_info.track_weights.ptr();
int track_weights_count = ai.playback_info.track_weights.size();
bool backward = signbit(delta); // This flag is used by the root motion calculates or detecting the end of audio stream.
bool seeked_backward = signbit(p_delta);
#ifndef _3D_DISABLED
bool calc_root = !seeked || is_external_seeking;
#endif // _3D_DISABLED
for (int i = 0; i < a->get_track_count(); i++) {
if (!a->track_is_enabled(i)) {
ERR_CONTINUE_EDMSG(!animation_track_num_to_track_cache.has(a), "No animation in cache.");
LocalVector<TrackCache *> &track_num_to_track_cache = animation_track_num_to_track_cache[a];
const Vector<Animation::Track *> tracks = a->get_tracks();
Animation::Track *const *tracks_ptr = tracks.ptr();
real_t a_length = a->get_length();
int count = tracks.size();
for (int i = 0; i < count; i++) {
const Animation::Track *animation_track = tracks_ptr[i];
if (!animation_track->enabled) {
continue;
}
Animation::TypeHash thash = a->track_get_type_hash(i);
if (!track_cache.has(thash)) {
TrackCache *track = track_num_to_track_cache[i];
if (track == nullptr) {
continue; // No path, but avoid error spamming.
}
TrackCache *track = track_cache[thash];
ERR_CONTINUE(!track_map.has(track->path));
int blend_idx = track_map[track->path];
int blend_idx = track->blend_idx;
ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count);
real_t blend = blend_idx < track_weights.size() ? track_weights[blend_idx] * weight : weight;
real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight;
if (!deterministic) {
// If non-deterministic, do normalization.
// It would be better to make this if statement outside the for loop, but come here since too much code...
@ -1143,8 +1211,8 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
blend = blend / track->total_weight;
}
Animation::TrackType ttype = a->track_get_type(i);
track->root_motion = root_motion_track == a->track_get_path(i);
Animation::TrackType ttype = animation_track->type;
track->root_motion = root_motion_track == animation_track->path;
switch (ttype) {
case Animation::TYPE_POSITION_3D: {
#ifndef _3D_DISABLED
@ -1153,75 +1221,130 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
if (track->root_motion && calc_root) {
int rot_track = -1;
if (root_motion_local) {
rot_track = a->find_track(a->track_get_path(i), Animation::TYPE_ROTATION_3D);
}
double prev_time = time - delta;
if (!backward) {
if (Animation::is_less_approx(prev_time, 0)) {
if (Animation::is_less_approx(prev_time, start)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
prev_time = 0;
prev_time = start;
} break;
case Animation::LOOP_LINEAR: {
prev_time = Math::fposmod(prev_time, (double)a->get_length());
prev_time = Math::fposmod(prev_time - start, end - start) + start;
} break;
case Animation::LOOP_PINGPONG: {
prev_time = Math::pingpong(prev_time, (double)a->get_length());
prev_time = Math::pingpong(prev_time - start, end - start) + start;
} break;
default:
break;
}
}
} else {
if (Animation::is_greater_approx(prev_time, (double)a->get_length())) {
if (Animation::is_greater_approx(prev_time, end)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
prev_time = (double)a->get_length();
prev_time = end;
} break;
case Animation::LOOP_LINEAR: {
prev_time = Math::fposmod(prev_time, (double)a->get_length());
prev_time = Math::fposmod(prev_time - start, end - start) + start;
} break;
case Animation::LOOP_PINGPONG: {
prev_time = Math::pingpong(prev_time, (double)a->get_length());
prev_time = Math::pingpong(prev_time - start, end - start) + start;
} break;
default:
break;
}
}
}
Vector3 loc[2];
if (!backward) {
if (Animation::is_greater_approx(prev_time, time)) {
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
if (rot_track >= 0) {
Vector3 loc[2];
Quaternion rot;
if (!backward) {
if (Animation::is_greater_approx(prev_time, time)) {
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, end, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
a->try_rotation_track_interpolate(rot_track, end, &rot);
rot = post_process_key_value(a, rot_track, rot, t->object_id, t->bone_idx);
root_motion_cache.loc += rot.xform_inv(loc[1] - loc[0]) * blend;
prev_time = start;
}
} else {
if (Animation::is_less_approx(prev_time, time)) {
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, start, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
a->try_rotation_track_interpolate(rot_track, start, &rot);
rot = post_process_key_value(a, rot_track, rot, t->object_id, t->bone_idx);
root_motion_cache.loc += rot.xform_inv(loc[1] - loc[0]) * blend;
prev_time = end;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, (double)a->get_length(), &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
prev_time = 0;
}
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, time, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
a->try_rotation_track_interpolate(rot_track, time, &rot);
rot = post_process_key_value(a, rot_track, rot, t->object_id, t->bone_idx);
root_motion_cache.loc += rot.xform_inv(loc[1] - loc[0]) * blend;
prev_time = !backward ? start : end;
} else {
if (Animation::is_less_approx(prev_time, time)) {
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
Vector3 loc[2];
if (!backward) {
if (Animation::is_greater_approx(prev_time, time)) {
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, end, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
prev_time = start;
}
} else {
if (Animation::is_less_approx(prev_time, time)) {
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, start, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
prev_time = end;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, 0, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
prev_time = (double)a->get_length();
}
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, time, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
prev_time = !backward ? start : end;
}
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, time, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
prev_time = !backward ? 0 : (double)a->get_length();
}
{
Vector3 loc;
@ -1243,32 +1366,32 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
if (track->root_motion && calc_root) {
double prev_time = time - delta;
if (!backward) {
if (Animation::is_less_approx(prev_time, 0)) {
if (Animation::is_less_approx(prev_time, start)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
prev_time = 0;
prev_time = start;
} break;
case Animation::LOOP_LINEAR: {
prev_time = Math::fposmod(prev_time, (double)a->get_length());
prev_time = Math::fposmod(prev_time - start, end - start) + start;
} break;
case Animation::LOOP_PINGPONG: {
prev_time = Math::pingpong(prev_time, (double)a->get_length());
prev_time = Math::pingpong(prev_time - start, end - start) + start;
} break;
default:
break;
}
}
} else {
if (Animation::is_greater_approx(prev_time, (double)a->get_length())) {
if (Animation::is_greater_approx(prev_time, end)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
prev_time = (double)a->get_length();
prev_time = end;
} break;
case Animation::LOOP_LINEAR: {
prev_time = Math::fposmod(prev_time, (double)a->get_length());
prev_time = Math::fposmod(prev_time - start, end - start) + start;
} break;
case Animation::LOOP_PINGPONG: {
prev_time = Math::pingpong(prev_time, (double)a->get_length());
prev_time = Math::pingpong(prev_time - start, end - start) + start;
} break;
default:
break;
@ -1283,10 +1406,10 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx);
a->try_rotation_track_interpolate(i, (double)a->get_length(), &rot[1]);
a->try_rotation_track_interpolate(i, end, &rot[1]);
rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx);
root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
prev_time = 0;
prev_time = start;
}
} else {
if (Animation::is_less_approx(prev_time, time)) {
@ -1295,9 +1418,10 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx);
a->try_rotation_track_interpolate(i, 0, &rot[1]);
a->try_rotation_track_interpolate(i, start, &rot[1]);
rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx);
root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
prev_time = (double)a->get_length();
prev_time = end;
}
}
Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0]);
@ -1308,7 +1432,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_rotation_track_interpolate(i, time, &rot[1]);
rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx);
root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
prev_time = !backward ? 0 : (double)a->get_length();
prev_time = !backward ? start : end;
}
{
Quaternion rot;
@ -1330,32 +1454,32 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
if (track->root_motion && calc_root) {
double prev_time = time - delta;
if (!backward) {
if (Animation::is_less_approx(prev_time, 0)) {
if (Animation::is_less_approx(prev_time, start)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
prev_time = 0;
prev_time = start;
} break;
case Animation::LOOP_LINEAR: {
prev_time = Math::fposmod(prev_time, (double)a->get_length());
prev_time = Math::fposmod(prev_time - start, end - start) + start;
} break;
case Animation::LOOP_PINGPONG: {
prev_time = Math::pingpong(prev_time, (double)a->get_length());
prev_time = Math::pingpong(prev_time - start, end - start) + start;
} break;
default:
break;
}
}
} else {
if (Animation::is_greater_approx(prev_time, (double)a->get_length())) {
if (Animation::is_greater_approx(prev_time, end)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
prev_time = (double)a->get_length();
prev_time = end;
} break;
case Animation::LOOP_LINEAR: {
prev_time = Math::fposmod(prev_time, (double)a->get_length());
prev_time = Math::fposmod(prev_time - start, end - start) + start;
} break;
case Animation::LOOP_PINGPONG: {
prev_time = Math::pingpong(prev_time, (double)a->get_length());
prev_time = Math::pingpong(prev_time - start, end - start) + start;
} break;
default:
break;
@ -1370,10 +1494,10 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
scale[0] = post_process_key_value(a, i, scale[0], t->object_id, t->bone_idx);
a->try_scale_track_interpolate(i, (double)a->get_length(), &scale[1]);
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
a->try_scale_track_interpolate(i, end, &scale[1]);
scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx);
prev_time = 0;
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
prev_time = start;
}
} else {
if (Animation::is_less_approx(prev_time, time)) {
@ -1382,10 +1506,10 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
scale[0] = post_process_key_value(a, i, scale[0], t->object_id, t->bone_idx);
a->try_scale_track_interpolate(i, 0, &scale[1]);
a->try_scale_track_interpolate(i, start, &scale[1]);
scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx);
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
prev_time = (double)a->get_length();
prev_time = end;
}
}
Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0]);
@ -1396,7 +1520,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_scale_track_interpolate(i, time, &scale[1]);
scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx);
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
prev_time = !backward ? 0 : (double)a->get_length();
prev_time = !backward ? start : end;
}
{
Vector3 scale;
@ -1560,13 +1684,13 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
PlayingAudioTrackInfo &track_info = t->playing_streams[oid];
track_info.length = a->get_length();
track_info.length = a_length;
track_info.time = time;
track_info.volume += blend;
track_info.loop = a->get_loop_mode() != Animation::LOOP_NONE;
track_info.backward = backward;
track_info.use_blend = a->audio_track_is_use_blend(i);
HashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info;
AHashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info;
// Main process to fire key is started from here.
if (p_update_only) {
@ -1623,6 +1747,9 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
if (t_obj->call(SNAME("get_is_sample"))) {
if (t->audio_stream_playback->get_sample_playback().is_valid()) {
AudioServer::get_singleton()->stop_sample_playback(t->audio_stream_playback->get_sample_playback());
}
Ref<AudioSamplePlayback> sample_playback;
sample_playback.instantiate();
sample_playback->stream = stream;
@ -1655,6 +1782,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
if (!player2) {
continue;
}
// TODO: Make it possible to embed section info in animation track keys.
if (seeked) {
// Seek.
int idx = a->track_find_key(i, time, Animation::FIND_MODE_NEAREST, true);
@ -1667,19 +1795,19 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
Ref<Animation> anim = player2->get_animation(anim_name);
double at_anim_pos = 0.0;
double at_anim_pos = start;
switch (anim->get_loop_mode()) {
case Animation::LOOP_NONE: {
if (!is_external_seeking && ((!backward && Animation::is_greater_or_equal_approx(time, pos + (double)anim->get_length())) || (backward && Animation::is_less_or_equal_approx(time, pos)))) {
if (!is_external_seeking && ((!backward && Animation::is_greater_or_equal_approx(time, pos + end)) || (backward && Animation::is_less_or_equal_approx(time, pos + start)))) {
continue; // Do nothing if current time is outside of length when started.
}
at_anim_pos = MIN((double)anim->get_length(), time - pos); // Seek to end.
at_anim_pos = MIN(end, time - pos); // Seek to end.
} break;
case Animation::LOOP_LINEAR: {
at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); // Seek to loop.
at_anim_pos = Math::fposmod(time - pos - start, end - start) + start; // Seek to loop.
} break;
case Animation::LOOP_PINGPONG: {
at_anim_pos = Math::pingpong(time - pos, (double)a->get_length());
at_anim_pos = Math::pingpong(time - pos - start, end - start) + start;
} break;
default:
break;
@ -1717,6 +1845,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
}
}
is_GDVIRTUAL_CALL_post_process_key_value = true;
}
void AnimationMixer::_blend_apply() {
@ -1830,7 +1959,7 @@ void AnimationMixer::_blend_apply() {
PlayingAudioTrackInfo &track_info = L.value;
float db = Math::linear_to_db(track_info.use_blend ? track_info.volume : 1.0);
LocalVector<int> erase_streams;
HashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info;
AHashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info;
for (const KeyValue<int, PlayingAudioStreamInfo> &M : map) {
PlayingAudioStreamInfo pasi = M.value;
@ -1938,12 +2067,21 @@ void AnimationMixer::clear_caches() {
void AnimationMixer::set_root_motion_track(const NodePath &p_track) {
root_motion_track = p_track;
notify_property_list_changed();
}
NodePath AnimationMixer::get_root_motion_track() const {
return root_motion_track;
}
void AnimationMixer::set_root_motion_local(bool p_enabled) {
root_motion_local = p_enabled;
}
bool AnimationMixer::is_root_motion_local() const {
return root_motion_local;
}
Vector3 AnimationMixer::get_root_motion_position() const {
return root_motion_position;
}
@ -2075,6 +2213,8 @@ Ref<AnimatedValuesBackup> AnimationMixer::make_backup() {
PlaybackInfo pi;
pi.time = 0;
pi.delta = 0;
pi.start = 0;
pi.end = reset_anim->get_length();
pi.seeked = true;
pi.weight = 1.0;
make_animation_instance(SceneStringName(RESET), pi);
@ -2112,7 +2252,7 @@ void AnimationMixer::restore(const Ref<AnimatedValuesBackup> &p_backup) {
ERR_FAIL_COND(p_backup.is_null());
track_cache = p_backup->get_data();
_blend_apply();
track_cache = HashMap<Animation::TypeHash, AnimationMixer::TrackCache *>();
track_cache = AHashMap<Animation::TypeHash, AnimationMixer::TrackCache *, HashHasher>();
cache_valid = false;
}
@ -2159,6 +2299,9 @@ void AnimationMixer::capture(const StringName &p_name, double p_duration, Tween:
capture_cache.step = 1.0 / p_duration;
capture_cache.trans_type = p_trans_type;
capture_cache.ease_type = p_ease_type;
if (capture_cache.animation.is_valid()) {
animation_track_num_to_track_cache.erase(capture_cache.animation);
}
capture_cache.animation.instantiate();
bool is_valid = false;
@ -2182,6 +2325,8 @@ void AnimationMixer::capture(const StringName &p_name, double p_duration, Tween:
}
if (!is_valid) {
capture_cache.clear();
} else {
_create_track_num_to_track_cache_for_animation(capture_cache.animation);
}
}
@ -2282,6 +2427,8 @@ void AnimationMixer::_bind_methods() {
/* ---- Root motion accumulator for Skeleton3D ---- */
ClassDB::bind_method(D_METHOD("set_root_motion_track", "path"), &AnimationMixer::set_root_motion_track);
ClassDB::bind_method(D_METHOD("get_root_motion_track"), &AnimationMixer::get_root_motion_track);
ClassDB::bind_method(D_METHOD("set_root_motion_local", "enabled"), &AnimationMixer::set_root_motion_local);
ClassDB::bind_method(D_METHOD("is_root_motion_local"), &AnimationMixer::is_root_motion_local);
ClassDB::bind_method(D_METHOD("get_root_motion_position"), &AnimationMixer::get_root_motion_position);
ClassDB::bind_method(D_METHOD("get_root_motion_rotation"), &AnimationMixer::get_root_motion_rotation);
@ -2309,6 +2456,7 @@ void AnimationMixer::_bind_methods() {
ADD_GROUP("Root Motion", "root_motion_");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_motion_track"), "set_root_motion_track", "get_root_motion_track");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "root_motion_local"), "set_root_motion_local", "is_root_motion_local");
ADD_GROUP("Audio", "audio_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_max_polyphony", PROPERTY_HINT_RANGE, "1,127,1"), "set_audio_max_polyphony", "get_audio_max_polyphony");
@ -2348,7 +2496,7 @@ AnimationMixer::AnimationMixer() {
AnimationMixer::~AnimationMixer() {
}
void AnimatedValuesBackup::set_data(const HashMap<Animation::TypeHash, AnimationMixer::TrackCache *> p_data) {
void AnimatedValuesBackup::set_data(const AHashMap<Animation::TypeHash, AnimationMixer::TrackCache *, HashHasher> p_data) {
clear_data();
for (const KeyValue<Animation::TypeHash, AnimationMixer::TrackCache *> &E : p_data) {
@ -2361,7 +2509,7 @@ void AnimatedValuesBackup::set_data(const HashMap<Animation::TypeHash, Animation
}
}
HashMap<Animation::TypeHash, AnimationMixer::TrackCache *> AnimatedValuesBackup::get_data() const {
AHashMap<Animation::TypeHash, AnimationMixer::TrackCache *, HashHasher> AnimatedValuesBackup::get_data() const {
HashMap<Animation::TypeHash, AnimationMixer::TrackCache *> ret;
for (const KeyValue<Animation::TypeHash, AnimationMixer::TrackCache *> &E : data) {
AnimationMixer::TrackCache *track = get_cache_copy(E.value);

View file

@ -31,6 +31,7 @@
#ifndef ANIMATION_MIXER_H
#define ANIMATION_MIXER_H
#include "core/templates/a_hash_map.h"
#include "scene/animation/tween.h"
#include "scene/main/node.h"
#include "scene/resources/animation.h"
@ -48,6 +49,7 @@ class AnimationMixer : public Node {
#endif // TOOLS_ENABLED
bool reset_on_save = true;
bool is_GDVIRTUAL_CALL_post_process_key_value = true;
public:
enum AnimationCallbackModeProcess {
@ -84,6 +86,8 @@ public:
struct PlaybackInfo {
double time = 0.0;
double delta = 0.0;
double start = 0.0;
double end = 0.0;
bool seeked = false;
bool is_external_seeking = false;
Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
@ -99,7 +103,7 @@ public:
protected:
/* ---- Data lists ---- */
LocalVector<AnimationLibraryData> animation_libraries;
HashMap<StringName, AnimationData> animation_set; // HashMap<Library name + Animation name, AnimationData>
AHashMap<StringName, AnimationData> animation_set; // HashMap<Library name + Animation name, AnimationData>
TypedArray<StringName> _get_animation_library_list() const;
Vector<String> _get_animation_list() const {
@ -145,6 +149,7 @@ protected:
uint64_t setup_pass = 0;
Animation::TrackType type = Animation::TrackType::TYPE_ANIMATION;
NodePath path;
int blend_idx = -1;
ObjectID object_id;
real_t total_weight = 0.0;
@ -266,7 +271,7 @@ protected:
// Audio track information for mixng and ending.
struct PlayingAudioTrackInfo {
HashMap<int, PlayingAudioStreamInfo> stream_info;
AHashMap<int, PlayingAudioStreamInfo> stream_info;
double length = 0.0;
double time = 0.0;
real_t volume = 0.0;
@ -305,7 +310,8 @@ protected:
};
RootMotionCache root_motion_cache;
HashMap<Animation::TypeHash, TrackCache *> track_cache;
AHashMap<Animation::TypeHash, TrackCache *, HashHasher> track_cache;
AHashMap<Ref<Animation>, LocalVector<TrackCache *>> animation_track_num_to_track_cache;
HashSet<TrackCache *> playing_caches;
Vector<Node *> playing_audio_stream_players;
@ -315,18 +321,20 @@ protected:
void _clear_playing_caches();
void _init_root_motion_cache();
bool _update_caches();
void _create_track_num_to_track_cache_for_animation(Ref<Animation> &p_animation);
/* ---- Audio ---- */
AudioServer::PlaybackType playback_type;
/* ---- Blending processor ---- */
LocalVector<AnimationInstance> animation_instances;
HashMap<NodePath, int> track_map;
AHashMap<NodePath, int> track_map;
int track_count = 0;
bool deterministic = false;
/* ---- Root motion accumulator for Skeleton3D ---- */
NodePath root_motion_track;
bool root_motion_local = false;
Vector3 root_motion_position = Vector3(0, 0, 0);
Quaternion root_motion_rotation = Quaternion(0, 0, 0, 1);
Vector3 root_motion_scale = Vector3(0, 0, 0);
@ -356,14 +364,14 @@ protected:
virtual void _process_animation(double p_delta, bool p_update_only = false);
// For post process with retrieved key value during blending.
virtual Variant _post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx = -1);
virtual Variant _post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant &p_value, ObjectID p_object_id, int p_object_sub_idx = -1);
Variant post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx = -1);
GDVIRTUAL5RC(Variant, _post_process_key_value, Ref<Animation>, int, Variant, ObjectID, int);
void _blend_init();
virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map);
virtual bool _blend_pre_process(double p_delta, int p_track_count, const AHashMap<NodePath, int> &p_track_map);
virtual void _blend_capture(double p_delta);
void _blend_calc_total_weight(); // For undeterministic blending.
void _blend_calc_total_weight(); // For indeterministic blending.
void _blend_process(double p_delta, bool p_update_only = false);
void _blend_apply();
virtual void _blend_post_process();
@ -402,6 +410,7 @@ public:
void get_animation_library_list(List<StringName> *p_animations) const;
Ref<AnimationLibrary> get_animation_library(const StringName &p_name) const;
bool has_animation_library(const StringName &p_name) const;
StringName get_animation_library_name(const Ref<AnimationLibrary> &p_animation_library) const;
StringName find_animation_library(const Ref<Animation> &p_animation) const;
Error add_animation_library(const StringName &p_name, const Ref<AnimationLibrary> &p_animation_library);
void remove_animation_library(const StringName &p_name);
@ -439,6 +448,9 @@ public:
void set_root_motion_track(const NodePath &p_track);
NodePath get_root_motion_track() const;
void set_root_motion_local(bool p_enabled);
bool is_root_motion_local() const;
Vector3 get_root_motion_position() const;
Quaternion get_root_motion_rotation() const;
Vector3 get_root_motion_scale() const;
@ -482,11 +494,11 @@ public:
class AnimatedValuesBackup : public RefCounted {
GDCLASS(AnimatedValuesBackup, RefCounted);
HashMap<Animation::TypeHash, AnimationMixer::TrackCache *> data;
AHashMap<Animation::TypeHash, AnimationMixer::TrackCache *, HashHasher> data;
public:
void set_data(const HashMap<Animation::TypeHash, AnimationMixer::TrackCache *> p_data);
HashMap<Animation::TypeHash, AnimationMixer::TrackCache *> get_data() const;
void set_data(const AHashMap<Animation::TypeHash, AnimationMixer::TrackCache *, HashHasher> p_data);
AHashMap<Animation::TypeHash, AnimationMixer::TrackCache *, HashHasher> get_data() const;
void clear_data();
AnimationMixer::TrackCache *get_cache_copy(AnimationMixer::TrackCache *p_cache) const;

View file

@ -0,0 +1,83 @@
/**************************************************************************/
/* animation_node_extension.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "animation_node_extension.h"
AnimationNode::NodeTimeInfo AnimationNodeExtension::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
PackedFloat32Array r_ret;
GDVIRTUAL_CALL(
_process_animation_node,
_playback_info_to_array(p_playback_info),
p_test_only,
r_ret);
return _array_to_node_time_info(r_ret);
}
bool AnimationNodeExtension::is_looping(const PackedFloat32Array &p_node_info) {
return _array_to_node_time_info(p_node_info).is_looping();
}
double AnimationNodeExtension::get_remaining_time(const PackedFloat32Array &p_node_info, bool p_break_loop) {
return _array_to_node_time_info(p_node_info).get_remain(p_break_loop);
}
void AnimationNodeExtension::_bind_methods() {
ClassDB::bind_static_method("AnimationNodeExtension", D_METHOD("is_looping", "node_info"), &AnimationNodeExtension::is_looping);
ClassDB::bind_static_method("AnimationNodeExtension", D_METHOD("get_remaining_time", "node_info", "break_loop"), &AnimationNodeExtension::get_remaining_time);
GDVIRTUAL_BIND(_process_animation_node, "playback_info", "test_only");
}
AnimationNode::NodeTimeInfo AnimationNodeExtension::_array_to_node_time_info(const PackedFloat32Array &p_node_info) {
ERR_FAIL_COND_V_MSG(p_node_info.size() != 6, AnimationNode::NodeTimeInfo(), "Invalid node info.");
AnimationNode::NodeTimeInfo ret_val;
ret_val.length = p_node_info[0];
ret_val.position = p_node_info[1];
ret_val.delta = p_node_info[2];
ret_val.loop_mode = static_cast<Animation::LoopMode>(p_node_info[3]);
ret_val.will_end = p_node_info[4] > 0.0;
ret_val.is_infinity = p_node_info[5] > 0.0;
return ret_val;
}
PackedFloat64Array AnimationNodeExtension::_playback_info_to_array(const AnimationMixer::PlaybackInfo &p_playback_info) {
PackedFloat64Array playback_info_array;
playback_info_array.push_back(p_playback_info.time);
playback_info_array.push_back(p_playback_info.delta);
playback_info_array.push_back(p_playback_info.start);
playback_info_array.push_back(p_playback_info.end);
playback_info_array.push_back(p_playback_info.seeked);
playback_info_array.push_back(p_playback_info.is_external_seeking);
playback_info_array.push_back(p_playback_info.looped_flag);
playback_info_array.push_back(p_playback_info.weight);
return playback_info_array;
}

View file

@ -0,0 +1,55 @@
/**************************************************************************/
/* animation_node_extension.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef ANIMATION_NODE_EXTENSION_H
#define ANIMATION_NODE_EXTENSION_H
#include "scene/animation/animation_tree.h"
class AnimationNodeExtension : public AnimationNode {
GDCLASS(AnimationNodeExtension, AnimationNode);
public:
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
static bool is_looping(const PackedFloat32Array &p_node_info);
static double get_remaining_time(const PackedFloat32Array &p_node_info, bool p_break_loop = false);
protected:
static void _bind_methods();
GDVIRTUAL2R_REQUIRED(PackedFloat32Array, _process_animation_node, PackedFloat64Array, bool);
private:
static AnimationNode::NodeTimeInfo _array_to_node_time_info(const PackedFloat32Array &p_array);
static PackedFloat64Array _playback_info_to_array(const AnimationMixer::PlaybackInfo &p_playback_info);
};
#endif // ANIMATION_NODE_EXTENSION_H

View file

@ -29,10 +29,6 @@
/**************************************************************************/
#include "animation_node_state_machine.h"
#include "scene/main/window.h"
StringName AnimationNodeStateMachine::START_NODE;
StringName AnimationNodeStateMachine::END_NODE;
/////////////////////////////////////////////////
@ -54,7 +50,7 @@ AnimationNodeStateMachineTransition::AdvanceMode AnimationNodeStateMachineTransi
void AnimationNodeStateMachineTransition::set_advance_condition(const StringName &p_condition) {
String cs = p_condition;
ERR_FAIL_COND(cs.contains("/") || cs.contains(":"));
ERR_FAIL_COND(cs.contains_char('/') || cs.contains_char(':'));
advance_condition = p_condition;
if (!cs.is_empty()) {
advance_condition_name = "conditions/" + cs;
@ -205,7 +201,7 @@ void AnimationNodeStateMachinePlayback::_set_current(AnimationNodeStateMachine *
}
Ref<AnimationNodeStateMachine> anodesm = p_state_machine->find_node_by_path(current);
if (!anodesm.is_valid()) {
if (anodesm.is_null()) {
group_start_transition = Ref<AnimationNodeStateMachineTransition>();
group_end_transition = Ref<AnimationNodeStateMachineTransition>();
return;
@ -229,9 +225,9 @@ void AnimationNodeStateMachinePlayback::_set_current(AnimationNodeStateMachine *
// Validation.
if (anodesm->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) {
indices = anodesm->find_transition_from(AnimationNodeStateMachine::START_NODE);
indices = anodesm->find_transition_from(SceneStringName(Start));
int anodesm_start_size = indices.size();
indices = anodesm->find_transition_to(AnimationNodeStateMachine::END_NODE);
indices = anodesm->find_transition_to(SceneStringName(End));
int anodesm_end_size = indices.size();
if (group_start_size > 1) {
WARN_PRINT_ED("There are two or more transitions to the Grouped AnimationNodeStateMachine in AnimationNodeStateMachine: " + base_path + ", which may result in unintended transitions.");
@ -307,7 +303,7 @@ bool AnimationNodeStateMachinePlayback::is_playing() const {
}
bool AnimationNodeStateMachinePlayback::is_end() const {
return current == AnimationNodeStateMachine::END_NODE && fading_from == StringName();
return current == SceneStringName(End) && fading_from == StringName();
}
StringName AnimationNodeStateMachinePlayback::get_current_node() const {
@ -357,7 +353,7 @@ void AnimationNodeStateMachinePlayback::_clear_path_children(AnimationTree *p_tr
Ref<AnimationNodeStateMachine> anodesm = child_node.node;
if (anodesm.is_valid() && anodesm->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) {
Ref<AnimationNodeStateMachinePlayback> playback = p_tree->get(base_path + child_node.name + "/playback");
ERR_FAIL_COND(!playback.is_valid());
ERR_FAIL_COND(playback.is_null());
playback->_set_base_path(base_path + child_node.name + "/");
if (p_test_only) {
playback = playback->duplicate();
@ -387,7 +383,7 @@ void AnimationNodeStateMachinePlayback::_start_children(AnimationTree *p_tree, A
ERR_FAIL_MSG("Root/Nested AnimationNodeStateMachine can't have path from parent AnimationNodeStateMachine.");
}
Ref<AnimationNodeStateMachinePlayback> playback = p_tree->get(base_path + concatenated + "/playback");
ERR_FAIL_COND(!playback.is_valid());
ERR_FAIL_COND(playback.is_null());
playback->_set_base_path(base_path + concatenated + "/");
if (p_test_only) {
playback = playback->duplicate();
@ -419,7 +415,7 @@ bool AnimationNodeStateMachinePlayback::_travel_children(AnimationTree *p_tree,
ERR_FAIL_V_MSG(false, "Root/Nested AnimationNodeStateMachine can't have path from parent AnimationNodeStateMachine.");
}
Ref<AnimationNodeStateMachinePlayback> playback = p_tree->get(base_path + concatenated + "/playback");
ERR_FAIL_COND_V(!playback.is_valid(), false);
ERR_FAIL_COND_V(playback.is_null(), false);
playback->_set_base_path(base_path + concatenated + "/");
if (p_test_only) {
playback = playback->duplicate();
@ -438,16 +434,16 @@ bool AnimationNodeStateMachinePlayback::_travel_children(AnimationTree *p_tree,
String child_path = "/" + playback->get_current_node();
while (true) {
Ref<AnimationNodeStateMachine> child_anodesm = p_state_machine->find_node_by_path(concatenated + child_path);
if (!child_anodesm.is_valid() || child_anodesm->get_state_machine_type() != AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) {
if (child_anodesm.is_null() || child_anodesm->get_state_machine_type() != AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) {
break;
}
Ref<AnimationNodeStateMachinePlayback> child_playback = p_tree->get(base_path + concatenated + child_path + "/playback");
ERR_FAIL_COND_V(!child_playback.is_valid(), false);
ERR_FAIL_COND_V(child_playback.is_null(), false);
child_playback->_set_base_path(base_path + concatenated + "/");
if (p_test_only) {
child_playback = child_playback->duplicate();
}
child_playback->_travel_main(AnimationNodeStateMachine::END_NODE);
child_playback->_travel_main(SceneStringName(End));
child_found_route &= child_playback->_travel(p_tree, child_anodesm.ptr(), false, p_test_only);
child_path += "/" + child_playback->get_current_node();
}
@ -490,7 +486,7 @@ bool AnimationNodeStateMachinePlayback::_travel_children(AnimationTree *p_tree,
void AnimationNodeStateMachinePlayback::_start(AnimationNodeStateMachine *p_state_machine) {
playing = true;
_set_current(p_state_machine, start_request != StringName() ? start_request : AnimationNodeStateMachine::START_NODE);
_set_current(p_state_machine, start_request != StringName() ? start_request : SceneStringName(Start));
teleport_request = true;
stop_request = false;
start_request = StringName();
@ -502,12 +498,12 @@ bool AnimationNodeStateMachinePlayback::_travel(AnimationTree *p_tree, Animation
String AnimationNodeStateMachinePlayback::_validate_path(AnimationNodeStateMachine *p_state_machine, const String &p_path) {
if (p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) {
return p_path; // Grouped state machine doesn't allow validat-able request.
return p_path; // Grouped state machine doesn't allow validate-able request.
}
String target = p_path;
Ref<AnimationNodeStateMachine> anodesm = p_state_machine->find_node_by_path(target);
while (anodesm.is_valid() && anodesm->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) {
Vector<int> indices = anodesm->find_transition_from(AnimationNodeStateMachine::START_NODE);
Vector<int> indices = anodesm->find_transition_from(SceneStringName(Start));
if (indices.size()) {
target = target + "/" + anodesm->get_transition_to(indices[0]); // Find next state of Start.
} else {
@ -649,7 +645,7 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree,
Ref<AnimationNodeStateMachine> anodesm = p_state_machine->find_node_by_path(current_path);
if (anodesm.is_valid() && anodesm->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) {
Ref<AnimationNodeStateMachinePlayback> playback = p_tree->get(base_path + current_path + "/playback");
ERR_FAIL_COND_V(!playback.is_valid(), false);
ERR_FAIL_COND_V(playback.is_null(), false);
playback->_set_base_path(base_path + current_path + "/");
if (p_test_only) {
playback = playback->duplicate();
@ -660,7 +656,7 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree,
if (i >= new_path.size()) {
break; // Tracing has been finished, needs to break.
}
playback->_travel_main(AnimationNodeStateMachine::END_NODE);
playback->_travel_main(SceneStringName(End));
if (!playback->_travel(p_tree, anodesm.ptr(), false, p_test_only)) {
found_route = false;
break;
@ -803,7 +799,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St
pi.is_external_seeking = false;
pi.weight = 0;
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.
// Don't process first node if not necessary, instead process next node.
_transition_to_next_recursive(tree, p_state_machine, p_delta, p_test_only);
}
@ -818,8 +814,8 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St
bool is_start_of_group = false;
bool is_end_of_group = false;
if (!p_state_machine->are_ends_reset() || p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) {
is_start_of_group = fading_from == AnimationNodeStateMachine::START_NODE;
is_end_of_group = current == AnimationNodeStateMachine::END_NODE;
is_start_of_group = fading_from == SceneStringName(Start);
is_end_of_group = current == SceneStringName(End);
}
// Calc blend amount by cross-fade.
@ -881,7 +877,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St
}
// Find next and see when to transition.
bool will_end = _transition_to_next_recursive(tree, p_state_machine, p_delta, p_test_only) || current == AnimationNodeStateMachine::END_NODE;
bool will_end = _transition_to_next_recursive(tree, p_state_machine, p_delta, p_test_only) || current == SceneStringName(End);
// Predict remaining time.
if (will_end || ((p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_NESTED) && !p_state_machine->has_transition_from(current))) {
@ -951,7 +947,7 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT
_set_current(p_state_machine, next.node);
current_curve = next.curve;
if (current == AnimationNodeStateMachine::END_NODE) {
if (current == SceneStringName(End)) {
break;
}
@ -981,7 +977,7 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT
}
}
return next.node == AnimationNodeStateMachine::END_NODE;
return next.node == SceneStringName(End);
}
bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, NextInfo p_next, bool p_test_only) {
@ -996,7 +992,7 @@ bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p
Ref<AnimationNodeStateMachine> anodesm = p_state_machine->find_node_by_path(current);
if (anodesm.is_valid() && anodesm->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) {
Ref<AnimationNodeStateMachinePlayback> playback = p_tree->get(base_path + current + "/playback");
ERR_FAIL_COND_V(!playback.is_valid(), false);
ERR_FAIL_COND_V(playback.is_null(), false);
playback->_set_base_path(base_path + current + "/");
if (p_test_only) {
playback = playback->duplicate();
@ -1015,7 +1011,7 @@ bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p
return false;
}
if (current != AnimationNodeStateMachine::START_NODE && p_next.switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_AT_END) {
if (current != SceneStringName(Start) && p_next.switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_AT_END) {
return Animation::is_less_or_equal_approx(current_nti.get_remain(p_next.break_loop_at_end), p_next.xfade);
}
return true;
@ -1025,13 +1021,13 @@ Ref<AnimationNodeStateMachineTransition> AnimationNodeStateMachinePlayback::_che
Ref<AnimationNodeStateMachineTransition> temp_transition;
Ref<AnimationNodeStateMachinePlayback> parent_playback;
if (r_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) {
if (p_transition.from == AnimationNodeStateMachine::START_NODE) {
if (p_transition.from == SceneStringName(Start)) {
parent_playback = _get_parent_playback(p_tree);
if (parent_playback.is_valid()) {
r_bypass = true;
temp_transition = parent_playback->_get_group_start_transition();
}
} else if (p_transition.to == AnimationNodeStateMachine::END_NODE) {
} else if (p_transition.to == SceneStringName(End)) {
parent_playback = _get_parent_playback(p_tree);
if (parent_playback.is_valid()) {
temp_transition = parent_playback->_get_group_end_transition();
@ -1152,7 +1148,7 @@ Ref<AnimationNodeStateMachinePlayback> AnimationNodeStateMachinePlayback::_get_p
split.remove_at(split.size() - 2);
String playback_path = String("/").join(split) + "playback";
Ref<AnimationNodeStateMachinePlayback> playback = p_tree->get(playback_path);
if (!playback.is_valid()) {
if (playback.is_null()) {
ERR_PRINT_ONCE("Can't get parent AnimationNodeStateMachinePlayback with path: " + playback_path + ". Maybe there is no Root/Nested AnimationNodeStateMachine in the parent of the Grouped AnimationNodeStateMachine.");
return Ref<AnimationNodeStateMachinePlayback>();
}
@ -1257,7 +1253,7 @@ bool AnimationNodeStateMachine::is_parameter_read_only(const StringName &p_param
void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref<AnimationNode> p_node, const Vector2 &p_position) {
ERR_FAIL_COND(states.has(p_name));
ERR_FAIL_COND(p_node.is_null());
ERR_FAIL_COND(String(p_name).contains("/"));
ERR_FAIL_COND(String(p_name).contains_char('/'));
State state_new;
state_new.node = p_node;
@ -1276,7 +1272,7 @@ void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref<Animation
void AnimationNodeStateMachine::replace_node(const StringName &p_name, Ref<AnimationNode> p_node) {
ERR_FAIL_COND(states.has(p_name) == false);
ERR_FAIL_COND(p_node.is_null());
ERR_FAIL_COND(String(p_name).contains("/"));
ERR_FAIL_COND(String(p_name).contains_char('/'));
{
Ref<AnimationNode> node = states[p_name].node;
@ -1326,7 +1322,8 @@ bool AnimationNodeStateMachine::are_ends_reset() const {
bool AnimationNodeStateMachine::can_edit_node(const StringName &p_name) const {
if (states.has(p_name)) {
return !(states[p_name].node->is_class("AnimationNodeStartState") || states[p_name].node->is_class("AnimationNodeEndState"));
const AnimationNode *anode = states[p_name].node.ptr();
return !(Object::cast_to<AnimationNodeStartState>(anode) || Object::cast_to<AnimationNodeEndState>(anode));
}
return true;
@ -1518,7 +1515,7 @@ void AnimationNodeStateMachine::add_transition(const StringName &p_from, const S
return;
}
ERR_FAIL_COND(p_from == END_NODE || p_to == START_NODE);
ERR_FAIL_COND(p_from == SceneStringName(End) || p_to == SceneStringName(Start));
ERR_FAIL_COND(p_from == p_to);
ERR_FAIL_COND(!_can_connect(p_from));
ERR_FAIL_COND(!_can_connect(p_to));
@ -1560,7 +1557,7 @@ StringName AnimationNodeStateMachine::get_transition_to(int p_transition) const
bool AnimationNodeStateMachine::is_transition_across_group(int p_transition) const {
ERR_FAIL_INDEX_V(p_transition, transitions.size(), false);
if (get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) {
if (transitions[p_transition].from == START_NODE || transitions[p_transition].to == END_NODE) {
if (transitions[p_transition].from == SceneStringName(Start) || transitions[p_transition].to == SceneStringName(End)) {
return true;
}
}
@ -1582,16 +1579,8 @@ void AnimationNodeStateMachine::remove_transition(const StringName &p_from, cons
void AnimationNodeStateMachine::remove_transition_by_index(const int p_transition) {
ERR_FAIL_INDEX(p_transition, transitions.size());
Transition tr = transitions[p_transition];
transitions.write[p_transition].transition->disconnect("advance_condition_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed));
transitions.remove_at(p_transition);
Vector<String> path_from = String(tr.from).split("/");
Vector<String> path_to = String(tr.to).split("/");
List<Vector<String>> paths;
paths.push_back(path_from);
paths.push_back(path_to);
}
void AnimationNodeStateMachine::_remove_transition(const Ref<AnimationNodeStateMachineTransition> p_transition) {
@ -1619,7 +1608,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachine::_process(const AnimationM
playback_new = playback_new->duplicate(); // Don't process original when testing.
}
return playback_new->process(node_state.base_path, this, p_playback_info, p_test_only);
return playback_new->process(node_state.get_base_path(), this, p_playback_info, p_test_only);
}
String AnimationNodeStateMachine::get_caption() const {
@ -1745,14 +1734,14 @@ void AnimationNodeStateMachine::reset_state() {
State start;
start.node = s;
start.position = Vector2(200, 100);
states[START_NODE] = start;
states[SceneStringName(Start)] = start;
Ref<AnimationNodeEndState> e;
e.instantiate();
State end;
end.node = e;
end.position = Vector2(900, 100);
states[END_NODE] = end;
states[SceneStringName(End)] = end;
emit_changed();
emit_signal(SNAME("tree_changed"));
@ -1841,21 +1830,38 @@ void AnimationNodeStateMachine::_bind_methods() {
BIND_ENUM_CONSTANT(STATE_MACHINE_TYPE_GROUPED);
}
AnimationNodeStateMachine::AnimationNodeStateMachine() {
START_NODE = "Start";
END_NODE = "End";
Vector<StringName> AnimationNodeStateMachine::get_nodes_with_transitions_from(const StringName &p_node) const {
Vector<StringName> result;
for (const Transition &transition : transitions) {
if (transition.from == p_node) {
result.push_back(transition.to);
}
}
return result;
}
Vector<StringName> AnimationNodeStateMachine::get_nodes_with_transitions_to(const StringName &p_node) const {
Vector<StringName> result;
for (const Transition &transition : transitions) {
if (transition.to == p_node) {
result.push_back(transition.from);
}
}
return result;
}
AnimationNodeStateMachine::AnimationNodeStateMachine() {
Ref<AnimationNodeStartState> s;
s.instantiate();
State start;
start.node = s;
start.position = Vector2(200, 100);
states[START_NODE] = start;
states[SceneStringName(Start)] = start;
Ref<AnimationNodeEndState> e;
e.instantiate();
State end;
end.node = e;
end.position = Vector2(900, 100);
states[END_NODE] = end;
states[SceneStringName(End)] = end;
}

View file

@ -110,9 +110,6 @@ class AnimationNodeStateMachine : public AnimationRootNode {
GDCLASS(AnimationNodeStateMachine, AnimationRootNode);
public:
static StringName START_NODE;
static StringName END_NODE;
enum StateMachineType {
STATE_MACHINE_TYPE_ROOT,
STATE_MACHINE_TYPE_NESTED,
@ -223,6 +220,9 @@ public:
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
#endif
Vector<StringName> get_nodes_with_transitions_from(const StringName &p_node) const;
Vector<StringName> get_nodes_with_transitions_to(const StringName &p_node) const;
AnimationNodeStateMachine();
};

View file

@ -58,14 +58,6 @@ void AnimationPlayer::_seek_bind_compat_80813(double p_time, bool p_update) {
seek(p_time, p_update, false);
}
void AnimationPlayer::_play_compat_84906(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) {
play(p_name, p_custom_blend, p_custom_scale, p_from_end);
}
void AnimationPlayer::_play_backwards_compat_84906(const StringName &p_name, double p_custom_blend) {
play_backwards(p_name, p_custom_blend);
}
void AnimationPlayer::_bind_compatibility_methods() {
ClassDB::bind_method(D_METHOD("set_process_callback", "mode"), &AnimationPlayer::_set_process_callback_bind_compat_80813);
ClassDB::bind_method(D_METHOD("get_process_callback"), &AnimationPlayer::_get_process_callback_bind_compat_80813);
@ -74,8 +66,6 @@ void AnimationPlayer::_bind_compatibility_methods() {
ClassDB::bind_method(D_METHOD("set_root", "path"), &AnimationPlayer::_set_root_bind_compat_80813);
ClassDB::bind_method(D_METHOD("get_root"), &AnimationPlayer::_get_root_bind_compat_80813);
ClassDB::bind_compatibility_method(D_METHOD("seek", "seconds", "update"), &AnimationPlayer::_seek_bind_compat_80813, DEFVAL(false));
ClassDB::bind_compatibility_method(D_METHOD("play", "name", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::_play_compat_84906, DEFVAL(""), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false));
ClassDB::bind_compatibility_method(D_METHOD("play_backwards", "name", "custom_blend"), &AnimationPlayer::_play_backwards_compat_84906, DEFVAL(""), DEFVAL(-1));
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS);
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE);
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_MANUAL);

View file

@ -133,7 +133,7 @@ void AnimationPlayer::_get_property_list(List<PropertyInfo> *p_list) const {
List<PropertyInfo> anim_names;
for (const KeyValue<StringName, AnimationData> &E : animation_set) {
HashMap<StringName, StringName>::ConstIterator F = animation_next_set.find(E.key);
AHashMap<StringName, StringName>::ConstIterator F = animation_next_set.find(E.key);
if (F && F->value != StringName()) {
anim_names.push_back(PropertyInfo(Variant::STRING, "next/" + String(E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
}
@ -164,39 +164,41 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f
double delta = p_started ? 0 : p_delta * speed;
double next_pos = cd.pos + delta;
double len = cd.from->animation->get_length();
double start = cd.get_start_time();
double end = cd.get_end_time();
Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
switch (cd.from->animation->get_loop_mode()) {
case Animation::LOOP_NONE: {
if (Animation::is_less_approx(next_pos, 0)) {
next_pos = 0;
} else if (Animation::is_greater_approx(next_pos, len)) {
next_pos = len;
if (Animation::is_less_approx(next_pos, start)) {
next_pos = start;
} else if (Animation::is_greater_approx(next_pos, end)) {
next_pos = end;
}
delta = next_pos - cd.pos; // Fix delta (after determination of backwards because negative zero is lost here).
} break;
case Animation::LOOP_LINEAR: {
if (Animation::is_less_approx(next_pos, 0) && Animation::is_greater_or_equal_approx(cd.pos, 0)) {
if (Animation::is_less_approx(next_pos, start) && Animation::is_greater_or_equal_approx(cd.pos, start)) {
looped_flag = Animation::LOOPED_FLAG_START;
}
if (Animation::is_greater_approx(next_pos, len) && Animation::is_less_or_equal_approx(cd.pos, len)) {
if (Animation::is_greater_approx(next_pos, end) && Animation::is_less_or_equal_approx(cd.pos, end)) {
looped_flag = Animation::LOOPED_FLAG_END;
}
next_pos = Math::fposmod(next_pos, (double)len);
next_pos = Math::fposmod(next_pos - start, end - start) + start;
} break;
case Animation::LOOP_PINGPONG: {
if (Animation::is_less_approx(next_pos, 0) && Animation::is_greater_or_equal_approx(cd.pos, 0)) {
if (Animation::is_less_approx(next_pos, start) && Animation::is_greater_or_equal_approx(cd.pos, start)) {
cd.speed_scale *= -1.0;
looped_flag = Animation::LOOPED_FLAG_START;
}
if (Animation::is_greater_approx(next_pos, len) && Animation::is_less_or_equal_approx(cd.pos, len)) {
if (Animation::is_greater_approx(next_pos, end) && Animation::is_less_or_equal_approx(cd.pos, end)) {
cd.speed_scale *= -1.0;
looped_flag = Animation::LOOPED_FLAG_END;
}
next_pos = Math::pingpong(next_pos, (double)len);
next_pos = Math::pingpong(next_pos - start, end - start) + start;
} break;
default:
@ -208,18 +210,18 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f
// End detection.
if (p_is_current) {
if (cd.from->animation->get_loop_mode() == Animation::LOOP_NONE) {
if (!backwards && Animation::is_less_or_equal_approx(prev_pos, len) && Math::is_equal_approx(next_pos, len)) {
if (!backwards && Animation::is_less_or_equal_approx(prev_pos, end) && Math::is_equal_approx(next_pos, end)) {
// Playback finished.
next_pos = len; // Snap to the edge.
next_pos = end; // Snap to the edge.
end_reached = true;
end_notify = Animation::is_less_approx(prev_pos, len); // Notify only if not already at the end.
end_notify = Animation::is_less_approx(prev_pos, end); // Notify only if not already at the end.
p_blend = 1.0;
}
if (backwards && Animation::is_greater_or_equal_approx(prev_pos, 0) && Math::is_equal_approx(next_pos, 0)) {
if (backwards && Animation::is_greater_or_equal_approx(prev_pos, start) && Math::is_equal_approx(next_pos, start)) {
// Playback finished.
next_pos = 0; // Snap to the edge.
next_pos = start; // Snap to the edge.
end_reached = true;
end_notify = Animation::is_greater_approx(prev_pos, 0); // Notify only if not already at the beginning.
end_notify = Animation::is_greater_approx(prev_pos, start); // Notify only if not already at the beginning.
p_blend = 1.0;
}
}
@ -231,10 +233,14 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f
if (p_started) {
pi.time = prev_pos;
pi.delta = 0;
pi.start = start;
pi.end = end;
pi.seeked = true;
} else {
pi.time = next_pos;
pi.delta = delta;
pi.start = start;
pi.end = end;
pi.seeked = p_seeked;
}
if (Math::is_zero_approx(pi.delta) && backwards) {
@ -293,7 +299,7 @@ void AnimationPlayer::_blend_playback_data(double p_delta, bool p_started) {
}
}
bool AnimationPlayer::_blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) {
bool AnimationPlayer::_blend_pre_process(double p_delta, int p_track_count, const AHashMap<NodePath, int> &p_track_map) {
if (!playback.current.from) {
_set_process(false);
return false;
@ -378,6 +384,14 @@ void AnimationPlayer::play_backwards(const StringName &p_name, double p_custom_b
play(p_name, p_custom_blend, -1, true);
}
void AnimationPlayer::play_section_with_markers_backwards(const StringName &p_name, const StringName &p_start_marker, const StringName &p_end_marker, double p_custom_blend) {
play_section_with_markers(p_name, p_start_marker, p_end_marker, p_custom_blend, -1, true);
}
void AnimationPlayer::play_section_backwards(const StringName &p_name, double p_start_time, double p_end_time, double p_custom_blend) {
play_section(p_name, p_start_time, p_end_time, p_custom_blend, -1, true);
}
void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) {
if (auto_capture) {
play_with_capture(p_name, auto_capture_duration, p_custom_blend, p_custom_scale, p_from_end, auto_capture_transition_type, auto_capture_ease_type);
@ -387,6 +401,10 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa
}
void AnimationPlayer::_play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) {
play_section_with_markers(p_name, StringName(), StringName(), p_custom_blend, p_custom_scale, p_from_end);
}
void AnimationPlayer::play_section_with_markers(const StringName &p_name, const StringName &p_start_marker, const StringName &p_end_marker, double p_custom_blend, float p_custom_scale, bool p_from_end) {
StringName name = p_name;
if (name == StringName()) {
@ -395,6 +413,38 @@ void AnimationPlayer::_play(const StringName &p_name, double p_custom_blend, flo
ERR_FAIL_COND_MSG(!animation_set.has(name), vformat("Animation not found: %s.", name));
Ref<Animation> animation = animation_set[name].animation;
ERR_FAIL_COND_MSG(p_start_marker == p_end_marker && p_start_marker, vformat("Start marker and end marker cannot be the same marker: %s.", p_start_marker));
ERR_FAIL_COND_MSG(p_start_marker && !animation->has_marker(p_start_marker), vformat("Marker %s not found in animation: %s.", p_start_marker, name));
ERR_FAIL_COND_MSG(p_end_marker && !animation->has_marker(p_end_marker), vformat("Marker %s not found in animation: %s.", p_end_marker, name));
double start_time = p_start_marker ? animation->get_marker_time(p_start_marker) : -1;
double end_time = p_end_marker ? animation->get_marker_time(p_end_marker) : -1;
ERR_FAIL_COND_MSG(p_start_marker && p_end_marker && Animation::is_greater_approx(start_time, end_time), vformat("End marker %s is placed earlier than start marker %s in animation: %s.", p_end_marker, p_start_marker, name));
if (p_start_marker && Animation::is_less_approx(start_time, 0)) {
WARN_PRINT_ED(vformat("Negative time start marker: %s is invalid in the section, so the start of the animation: %s is used instead.", p_start_marker, playback.current.from->animation->get_name()));
}
if (p_end_marker && Animation::is_less_approx(end_time, 0)) {
WARN_PRINT_ED(vformat("Negative time end marker: %s is invalid in the section, so the end of the animation: %s is used instead.", p_end_marker, playback.current.from->animation->get_name()));
}
play_section(name, start_time, end_time, p_custom_blend, p_custom_scale, p_from_end);
}
void AnimationPlayer::play_section(const StringName &p_name, double p_start_time, double p_end_time, double p_custom_blend, float p_custom_scale, bool p_from_end) {
StringName name = p_name;
if (name == StringName()) {
name = playback.assigned;
}
ERR_FAIL_COND_MSG(!animation_set.has(name), vformat("Animation not found: %s.", name));
ERR_FAIL_COND_MSG(p_start_time >= 0 && p_end_time >= 0 && Math::is_equal_approx(p_start_time, p_end_time), "Start time and end time must not equal to each other.");
ERR_FAIL_COND_MSG(p_start_time >= 0 && p_end_time >= 0 && Animation::is_greater_approx(p_start_time, p_end_time), vformat("Start time %f is greater than end time %f.", p_start_time, p_end_time));
Playback &c = playback;
if (c.current.from) {
@ -442,22 +492,27 @@ void AnimationPlayer::_play(const StringName &p_name, double p_custom_blend, flo
c.current.from = &animation_set[name];
c.current.speed_scale = p_custom_scale;
c.current.start_time = p_start_time;
c.current.end_time = p_end_time;
double start = playback.current.get_start_time();
double end = playback.current.get_end_time();
if (!end_reached) {
playback_queue.clear();
}
if (c.assigned != name) { // Reset.
c.current.pos = p_from_end ? c.current.from->animation->get_length() : 0;
c.current.pos = p_from_end ? end : start;
c.assigned = name;
emit_signal(SNAME("current_animation_changed"), c.assigned);
} else {
if (p_from_end && Math::is_zero_approx(c.current.pos)) {
if (p_from_end && Math::is_equal_approx(c.current.pos, start)) {
// Animation reset but played backwards, set position to the end.
seek_internal(c.current.from->animation->get_length(), true, true, true);
} else if (!p_from_end && Math::is_equal_approx(c.current.pos, (double)c.current.from->animation->get_length())) {
seek_internal(end, true, true, true);
} else if (!p_from_end && Math::is_equal_approx(c.current.pos, end)) {
// Animation resumed but already ended, set position to the beginning.
seek_internal(0, true, true, true);
seek_internal(start, true, true, true);
} else if (playing) {
return;
}
@ -551,6 +606,8 @@ void AnimationPlayer::set_assigned_animation(const String &p_animation) {
ERR_FAIL_COND_MSG(!animation_set.has(p_animation), vformat("Animation not found: %s.", p_animation));
playback.current.pos = 0;
playback.current.from = &animation_set[p_animation];
playback.current.start_time = -1;
playback.current.end_time = -1;
playback.assigned = p_animation;
emit_signal(SNAME("current_animation_changed"), playback.assigned);
}
@ -603,6 +660,12 @@ void AnimationPlayer::seek_internal(double p_time, bool p_update, bool p_update_
}
}
double start = playback.current.get_start_time();
double end = playback.current.get_end_time();
// Clamp the seek position.
p_time = CLAMP(p_time, start, end);
playback.seeked = true;
playback.internal_seeked = p_is_internal_seek;
@ -641,6 +704,49 @@ double AnimationPlayer::get_current_animation_length() const {
return playback.current.from->animation->get_length();
}
void AnimationPlayer::set_section_with_markers(const StringName &p_start_marker, const StringName &p_end_marker) {
ERR_FAIL_NULL_MSG(playback.current.from, "AnimationPlayer has no current animation.");
ERR_FAIL_COND_MSG(p_start_marker == p_end_marker && p_start_marker, vformat("Start marker and end marker cannot be the same marker: %s.", p_start_marker));
ERR_FAIL_COND_MSG(p_start_marker && !playback.current.from->animation->has_marker(p_start_marker), vformat("Marker %s not found in animation: %s.", p_start_marker, playback.current.from->animation->get_name()));
ERR_FAIL_COND_MSG(p_end_marker && !playback.current.from->animation->has_marker(p_end_marker), vformat("Marker %s not found in animation: %s.", p_end_marker, playback.current.from->animation->get_name()));
double start_time = p_start_marker ? playback.current.from->animation->get_marker_time(p_start_marker) : -1;
double end_time = p_end_marker ? playback.current.from->animation->get_marker_time(p_end_marker) : -1;
if (p_start_marker && Animation::is_less_approx(start_time, 0)) {
WARN_PRINT_ONCE_ED(vformat("Marker %s time must be positive in animation: %s.", p_start_marker, playback.current.from->animation->get_name()));
}
if (p_end_marker && Animation::is_less_approx(end_time, 0)) {
WARN_PRINT_ONCE_ED(vformat("Marker %s time must be positive in animation: %s.", p_end_marker, playback.current.from->animation->get_name()));
}
set_section(start_time, end_time);
}
void AnimationPlayer::set_section(double p_start_time, double p_end_time) {
ERR_FAIL_NULL_MSG(playback.current.from, "AnimationPlayer has no current animation.");
ERR_FAIL_COND_MSG(Animation::is_greater_or_equal_approx(p_start_time, 0) && Animation::is_greater_or_equal_approx(p_end_time, 0) && Animation::is_greater_or_equal_approx(p_start_time, p_end_time), vformat("Start time %f is greater than end time %f.", p_start_time, p_end_time));
playback.current.start_time = p_start_time;
playback.current.end_time = p_end_time;
playback.current.pos = CLAMP(playback.current.pos, playback.current.get_start_time(), playback.current.get_end_time());
}
void AnimationPlayer::reset_section() {
playback.current.start_time = -1;
playback.current.end_time = -1;
}
double AnimationPlayer::get_section_start_time() const {
ERR_FAIL_NULL_V_MSG(playback.current.from, playback.current.start_time, "AnimationPlayer has no current animation.");
return playback.current.get_start_time();
}
double AnimationPlayer::get_section_end_time() const {
ERR_FAIL_NULL_V_MSG(playback.current.from, playback.current.end_time, "AnimationPlayer has no current animation.");
return playback.current.get_end_time();
}
bool AnimationPlayer::has_section() const {
return Animation::is_greater_or_equal_approx(playback.current.start_time, 0) || Animation::is_greater_or_equal_approx(playback.current.end_time, 0);
}
void AnimationPlayer::set_autoplay(const String &p_name) {
if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) {
WARN_PRINT("Setting autoplay after the node has been added to the scene has no effect.");
@ -665,13 +771,14 @@ void AnimationPlayer::_stop_internal(bool p_reset, bool p_keep_state) {
_clear_caches();
Playback &c = playback;
// c.blend.clear();
double start = c.current.from ? playback.current.get_start_time() : 0;
if (p_reset) {
c.blend.clear();
if (p_keep_state) {
c.current.pos = 0;
c.current.pos = start;
} else {
is_stopping = true;
seek_internal(0, true, true, true);
seek_internal(start, true, true, true);
is_stopping = false;
}
c.current.from = nullptr;
@ -863,7 +970,11 @@ void AnimationPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_auto_capture_ease_type"), &AnimationPlayer::get_auto_capture_ease_type);
ClassDB::bind_method(D_METHOD("play", "name", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::play, DEFVAL(StringName()), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false));
ClassDB::bind_method(D_METHOD("play_section_with_markers", "name", "start_marker", "end_marker", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::play_section_with_markers, DEFVAL(StringName()), DEFVAL(StringName()), DEFVAL(StringName()), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false));
ClassDB::bind_method(D_METHOD("play_section", "name", "start_time", "end_time", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::play_section, DEFVAL(StringName()), DEFVAL(-1), DEFVAL(-1), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false));
ClassDB::bind_method(D_METHOD("play_backwards", "name", "custom_blend"), &AnimationPlayer::play_backwards, DEFVAL(StringName()), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("play_section_with_markers_backwards", "name", "start_marker", "end_marker", "custom_blend"), &AnimationPlayer::play_section_with_markers_backwards, DEFVAL(StringName()), DEFVAL(StringName()), DEFVAL(StringName()), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("play_section_backwards", "name", "start_time", "end_time", "custom_blend"), &AnimationPlayer::play_section_backwards, DEFVAL(StringName()), DEFVAL(-1), DEFVAL(-1), 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(StringName()), 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));
@ -893,6 +1004,14 @@ void AnimationPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_current_animation_position"), &AnimationPlayer::get_current_animation_position);
ClassDB::bind_method(D_METHOD("get_current_animation_length"), &AnimationPlayer::get_current_animation_length);
ClassDB::bind_method(D_METHOD("set_section_with_markers", "start_marker", "end_marker"), &AnimationPlayer::set_section_with_markers, DEFVAL(StringName()), DEFVAL(StringName()));
ClassDB::bind_method(D_METHOD("set_section", "start_time", "end_time"), &AnimationPlayer::set_section, DEFVAL(-1), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("reset_section"), &AnimationPlayer::reset_section);
ClassDB::bind_method(D_METHOD("get_section_start_time"), &AnimationPlayer::get_section_start_time);
ClassDB::bind_method(D_METHOD("get_section_end_time"), &AnimationPlayer::get_section_end_time);
ClassDB::bind_method(D_METHOD("has_section"), &AnimationPlayer::has_section);
ClassDB::bind_method(D_METHOD("seek", "seconds", "update", "update_only"), &AnimationPlayer::seek, DEFVAL(false), DEFVAL(false));
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "current_animation", PROPERTY_HINT_ENUM, "", PROPERTY_USAGE_EDITOR), "set_current_animation", "get_current_animation");
@ -904,7 +1023,7 @@ void AnimationPlayer::_bind_methods() {
ADD_GROUP("Playback Options", "playback_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playback_auto_capture"), "set_auto_capture", "is_auto_capture");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_auto_capture_duration", PROPERTY_HINT_NONE, "suffix:s"), "set_auto_capture_duration", "get_auto_capture_duration");
ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_auto_capture_transition_type", PROPERTY_HINT_ENUM, "Linear,Sine,Quint,Quart,Expo,Elastic,Cubic,Circ,Bounce,Back,Spring"), "set_auto_capture_transition_type", "get_auto_capture_transition_type");
ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_auto_capture_transition_type", PROPERTY_HINT_ENUM, "Linear,Sine,Quint,Quart,Quad,Expo,Elastic,Cubic,Circ,Bounce,Back,Spring"), "set_auto_capture_transition_type", "get_auto_capture_transition_type");
ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_auto_capture_ease_type", PROPERTY_HINT_ENUM, "In,Out,InOut,OutIn"), "set_auto_capture_ease_type", "get_auto_capture_ease_type");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_default_blend_time", PROPERTY_HINT_RANGE, "0,4096,0.01,suffix:s"), "set_default_blend_time", "get_default_blend_time");

View file

@ -32,7 +32,6 @@
#define ANIMATION_PLAYER_H
#include "animation_mixer.h"
#include "scene/2d/node_2d.h"
#include "scene/resources/animation.h"
class AnimationPlayer : public AnimationMixer {
@ -52,7 +51,7 @@ public:
#endif // DISABLE_DEPRECATED
private:
HashMap<StringName, StringName> animation_next_set; // For auto advance.
AHashMap<StringName, StringName> animation_next_set; // For auto advance.
float speed_scale = 1.0;
double default_blend_time = 0.0;
@ -68,6 +67,20 @@ private:
AnimationData *from = nullptr;
double pos = 0.0;
float speed_scale = 1.0;
double start_time = 0.0;
double end_time = 0.0;
double get_start_time() const {
if (from && (Animation::is_less_approx(start_time, 0) || Animation::is_greater_approx(start_time, from->animation->get_length()))) {
return 0;
}
return start_time;
}
double get_end_time() const {
if (from && (Animation::is_less_approx(end_time, 0) || Animation::is_greater_approx(end_time, from->animation->get_length()))) {
return from->animation->get_length();
}
return end_time;
}
};
struct Blend {
@ -136,7 +149,7 @@ protected:
static void _bind_methods();
// Make animation instances.
virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) override;
virtual bool _blend_pre_process(double p_delta, int p_track_count, const AHashMap<NodePath, int> &p_track_map) override;
virtual void _blend_capture(double p_delta) override;
virtual void _blend_post_process() override;
@ -176,8 +189,16 @@ public:
void set_auto_capture_ease_type(Tween::EaseType p_auto_capture_ease_type);
Tween::EaseType get_auto_capture_ease_type() const;
#ifdef TOOLS_ENABLED
void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
#endif
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_section_with_markers(const StringName &p_name = StringName(), const StringName &p_start_marker = StringName(), const StringName &p_end_marker = StringName(), double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false);
void play_section(const StringName &p_name = StringName(), double p_start_time = -1, double p_end_time = -1, 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_section_with_markers_backwards(const StringName &p_name = StringName(), const StringName &p_start_marker = StringName(), const StringName &p_end_marker = StringName(), double p_custom_blend = -1);
void play_section_backwards(const StringName &p_name = StringName(), double p_start_time = -1, double p_end_time = -1, double p_custom_blend = -1);
void play_with_capture(const StringName &p_name = StringName(), 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();
@ -207,9 +228,13 @@ public:
double get_current_animation_position() const;
double get_current_animation_length() const;
#ifdef TOOLS_ENABLED
void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
#endif
void set_section_with_markers(const StringName &p_start_marker = StringName(), const StringName &p_end_marker = StringName());
void set_section(double p_start_time = -1, double p_end_time = -1);
void reset_section();
double get_section_start_time() const;
double get_section_end_time() const;
bool has_section() const;
virtual void advance(double p_time) override;

View file

@ -32,7 +32,6 @@
#include "animation_tree.compat.inc"
#include "animation_blend_tree.h"
#include "core/config/engine.h"
#include "scene/animation/animation_player.h"
void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const {
@ -75,20 +74,34 @@ void AnimationNode::set_parameter(const StringName &p_name, const Variant &p_val
if (process_state->is_testing) {
return;
}
const AHashMap<StringName, int>::Iterator it = property_cache.find(p_name);
if (it) {
process_state->tree->property_map.get_by_index(it->value).value.first = p_value;
return;
}
ERR_FAIL_COND(!process_state->tree->property_parent_map.has(node_state.base_path));
ERR_FAIL_COND(!process_state->tree->property_parent_map[node_state.base_path].has(p_name));
StringName path = process_state->tree->property_parent_map[node_state.base_path][p_name];
process_state->tree->property_map[path].first = p_value;
int idx = process_state->tree->property_map.get_index(path);
property_cache.insert_new(p_name, idx);
process_state->tree->property_map.get_by_index(idx).value.first = p_value;
}
Variant AnimationNode::get_parameter(const StringName &p_name) const {
ERR_FAIL_NULL_V(process_state, Variant());
const AHashMap<StringName, int>::ConstIterator it = property_cache.find(p_name);
if (it) {
return process_state->tree->property_map.get_by_index(it->value).value.first;
}
ERR_FAIL_COND_V(!process_state->tree->property_parent_map.has(node_state.base_path), Variant());
ERR_FAIL_COND_V(!process_state->tree->property_parent_map[node_state.base_path].has(p_name), Variant());
StringName path = process_state->tree->property_parent_map[node_state.base_path][p_name];
return process_state->tree->property_map[path].first;
int idx = process_state->tree->property_map.get_index(path);
property_cache.insert_new(p_name, idx);
return process_state->tree->property_map.get_by_index(idx).value.first;
}
void AnimationNode::set_node_time_info(const NodeTimeInfo &p_node_time_info) {
@ -147,7 +160,7 @@ AnimationTree *AnimationNode::get_animation_tree() const {
}
AnimationNode::NodeTimeInfo AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) {
ERR_FAIL_INDEX_V(p_input, inputs.size(), NodeTimeInfo());
ERR_FAIL_INDEX_V(p_input, (int64_t)inputs.size(), NodeTimeInfo());
AnimationNodeBlendTree *blend_tree = Object::cast_to<AnimationNodeBlendTree>(node_state.parent);
ERR_FAIL_NULL_V(blend_tree, NodeTimeInfo());
@ -167,12 +180,12 @@ AnimationNode::NodeTimeInfo AnimationNode::blend_input(int p_input, AnimationMix
ERR_FAIL_COND_V(node.is_null(), NodeTimeInfo());
real_t activity = 0.0;
Vector<AnimationTree::Activity> *activity_ptr = process_state->tree->input_activity_map.getptr(node_state.base_path);
LocalVector<AnimationTree::Activity> *activity_ptr = process_state->tree->input_activity_map.getptr(node_state.base_path);
NodeTimeInfo nti = _blend_node(node, node_name, nullptr, p_playback_info, p_filter, p_sync, p_test_only, &activity);
if (activity_ptr && p_input < activity_ptr->size()) {
activity_ptr->write[p_input].last_pass = process_state->last_pass;
activity_ptr->write[p_input].activity = activity;
if (activity_ptr && p_input < (int64_t)activity_ptr->size()) {
(*activity_ptr)[p_input].last_pass = process_state->last_pass;
(*activity_ptr)[p_input].activity = activity;
}
return nti;
}
@ -188,11 +201,11 @@ AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref<AnimationNode> p_node
int blend_count = node_state.track_weights.size();
if (p_node->node_state.track_weights.size() != blend_count) {
if ((int64_t)p_node->node_state.track_weights.size() != blend_count) {
p_node->node_state.track_weights.resize(blend_count);
}
real_t *blendw = p_node->node_state.track_weights.ptrw();
real_t *blendw = p_node->node_state.track_weights.ptr();
const real_t *blendr = node_state.track_weights.ptr();
bool any_valid = false;
@ -203,10 +216,11 @@ AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref<AnimationNode> p_node
}
for (const KeyValue<NodePath, bool> &E : filter) {
if (!process_state->track_map.has(E.key)) {
const AHashMap<NodePath, int> &map = *process_state->track_map;
if (!map.has(E.key)) {
continue;
}
int idx = process_state->track_map[E.key];
int idx = map[E.key];
blendw[idx] = 1.0; // Filtered goes to one.
}
@ -291,7 +305,7 @@ AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref<AnimationNode> p_node
// This process, which depends on p_sync is needed to process sync correctly in the case of
// that a synced AnimationNodeSync exists under the un-synced AnimationNodeSync.
p_node->node_state.base_path = new_path;
p_node->set_node_state_base_path(new_path);
p_node->node_state.parent = new_parent;
if (!p_playback_info.seeked && !p_sync && !any_valid) {
p_playback_info.delta = 0.0;
@ -310,7 +324,7 @@ bool AnimationNode::add_input(const String &p_name) {
// Root nodes can't add inputs.
ERR_FAIL_COND_V(Object::cast_to<AnimationRootNode>(this) != nullptr, false);
Input input;
ERR_FAIL_COND_V(p_name.contains(".") || p_name.contains("/"), false);
ERR_FAIL_COND_V(p_name.contains_char('.') || p_name.contains_char('/'), false);
input.name = p_name;
inputs.push_back(input);
emit_changed();
@ -318,21 +332,21 @@ bool AnimationNode::add_input(const String &p_name) {
}
void AnimationNode::remove_input(int p_index) {
ERR_FAIL_INDEX(p_index, inputs.size());
ERR_FAIL_INDEX(p_index, (int64_t)inputs.size());
inputs.remove_at(p_index);
emit_changed();
}
bool AnimationNode::set_input_name(int p_input, const String &p_name) {
ERR_FAIL_INDEX_V(p_input, inputs.size(), false);
ERR_FAIL_COND_V(p_name.contains(".") || p_name.contains("/"), false);
inputs.write[p_input].name = p_name;
ERR_FAIL_INDEX_V(p_input, (int64_t)inputs.size(), false);
ERR_FAIL_COND_V(p_name.contains_char('.') || p_name.contains_char('/'), false);
inputs[p_input].name = p_name;
emit_changed();
return true;
}
String AnimationNode::get_input_name(int p_input) const {
ERR_FAIL_INDEX_V(p_input, inputs.size(), String());
ERR_FAIL_INDEX_V(p_input, (int64_t)inputs.size(), String());
return inputs[p_input].name;
}
@ -342,7 +356,7 @@ int AnimationNode::get_input_count() const {
int AnimationNode::find_input(const String &p_name) const {
int idx = -1;
for (int i = 0; i < inputs.size(); i++) {
for (int i = 0; i < (int64_t)inputs.size(); i++) {
if (inputs[i].name == p_name) {
idx = i;
break;
@ -356,7 +370,9 @@ AnimationNode::NodeTimeInfo AnimationNode::process(const AnimationMixer::Playbac
AnimationMixer::PlaybackInfo pi = p_playback_info;
if (p_playback_info.seeked) {
pi.delta = get_node_time_info().position - p_playback_info.time;
if (p_playback_info.is_external_seeking) {
pi.delta = get_node_time_info().position - p_playback_info.time;
}
} else {
pi.time = get_node_time_info().position + p_playback_info.delta;
}
@ -402,6 +418,16 @@ bool AnimationNode::is_deletable() const {
return closable;
}
ObjectID AnimationNode::get_processing_animation_tree_instance_id() const {
ERR_FAIL_NULL_V(process_state, ObjectID());
return process_state->tree->get_instance_id();
}
bool AnimationNode::is_process_testing() const {
ERR_FAIL_NULL_V(process_state, false);
return process_state->is_testing;
}
bool AnimationNode::is_path_filtered(const NodePath &p_path) const {
return filter.has(p_path);
}
@ -447,7 +473,7 @@ Ref<AnimationNode> AnimationNode::find_node_by_path(const String &p_name) const
Ref<AnimationNode> ret = const_cast<AnimationNode *>(this);
for (int i = 0; i < split.size(); i++) {
ret = ret->get_child_by_name(split[i]);
if (!ret.is_valid()) {
if (ret.is_null()) {
break;
}
}
@ -527,6 +553,10 @@ void AnimationNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_filter_enabled", "enable"), &AnimationNode::set_filter_enabled);
ClassDB::bind_method(D_METHOD("is_filter_enabled"), &AnimationNode::is_filter_enabled);
ClassDB::bind_method(D_METHOD("get_processing_animation_tree_instance_id"), &AnimationNode::get_processing_animation_tree_instance_id);
ClassDB::bind_method(D_METHOD("is_process_testing"), &AnimationNode::is_process_testing);
ClassDB::bind_method(D_METHOD("_set_filters", "filters"), &AnimationNode::_set_filters);
ClassDB::bind_method(D_METHOD("_get_filters"), &AnimationNode::_get_filters);
@ -602,10 +632,10 @@ Ref<AnimationRootNode> AnimationTree::get_root_animation_node() const {
return root_animation_node;
}
bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) {
bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const AHashMap<NodePath, int> &p_track_map) {
_update_properties(); // If properties need updating, update them.
if (!root_animation_node.is_valid()) {
if (root_animation_node.is_null()) {
return false;
}
@ -618,15 +648,15 @@ bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const
process_state.valid = true;
process_state.invalid_reasons = "";
process_state.last_pass = process_pass;
process_state.track_map = p_track_map;
process_state.track_map = &p_track_map;
// Init node state for root AnimationNode.
root_animation_node->node_state.track_weights.resize(p_track_count);
real_t *src_blendsw = root_animation_node->node_state.track_weights.ptrw();
real_t *src_blendsw = root_animation_node->node_state.track_weights.ptr();
for (int i = 0; i < p_track_count; i++) {
src_blendsw[i] = 1.0; // By default all go to 1 for the root input.
}
root_animation_node->node_state.base_path = SNAME(Animation::PARAMETERS_BASE_PATH.ascii().get_data());
root_animation_node->set_node_state_base_path(SNAME(Animation::PARAMETERS_BASE_PATH.ascii().get_data()));
root_animation_node->node_state.parent = nullptr;
}
@ -680,8 +710,8 @@ uint64_t AnimationTree::get_last_process_pass() const {
}
PackedStringArray AnimationTree::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (!root_animation_node.is_valid()) {
PackedStringArray warnings = AnimationMixer::get_configuration_warnings();
if (root_animation_node.is_null()) {
warnings.push_back(RTR("No root AnimationNode for the graph is set."));
}
return warnings;
@ -728,17 +758,17 @@ void AnimationTree::_animation_node_removed(const ObjectID &p_oid, const StringN
_update_properties();
}
void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref<AnimationNode> p_node) {
void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref<AnimationNode> p_node) const {
ERR_FAIL_COND(p_node.is_null());
if (!property_parent_map.has(p_base_path)) {
property_parent_map[p_base_path] = HashMap<StringName, StringName>();
property_parent_map[p_base_path] = AHashMap<StringName, StringName>();
}
if (!property_reference_map.has(p_node->get_instance_id())) {
property_reference_map[p_node->get_instance_id()] = p_base_path;
}
if (p_node->get_input_count() && !input_activity_map.has(p_base_path)) {
Vector<Activity> activity;
LocalVector<Activity> activity;
for (int i = 0; i < p_node->get_input_count(); i++) {
Activity a;
a.activity = 0;
@ -766,7 +796,7 @@ void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref<A
pinfo.name = p_base_path + key;
properties.push_back(pinfo);
}
p_node->make_cache_dirty();
List<AnimationNode::ChildNode> children;
p_node->get_child_nodes(&children);
@ -775,7 +805,7 @@ void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref<A
}
}
void AnimationTree::_update_properties() {
void AnimationTree::_update_properties() const {
if (!properties_dirty) {
return;
}
@ -792,7 +822,7 @@ void AnimationTree::_update_properties() {
properties_dirty = false;
notify_property_list_changed();
const_cast<AnimationTree *>(this)->notify_property_list_changed();
}
void AnimationTree::_notification(int p_what) {
@ -908,7 +938,7 @@ bool AnimationTree::_get(const StringName &p_name, Variant &r_ret) const {
}
#endif // DISABLE_DEPRECATED
if (properties_dirty) {
const_cast<AnimationTree *>(this)->_update_properties();
_update_properties();
}
if (property_map.has(p_name)) {
@ -921,7 +951,7 @@ bool AnimationTree::_get(const StringName &p_name, Variant &r_ret) const {
void AnimationTree::_get_property_list(List<PropertyInfo> *p_list) const {
if (properties_dirty) {
const_cast<AnimationTree *>(this)->_update_properties();
_update_properties();
}
for (const PropertyInfo &E : properties) {
@ -933,9 +963,9 @@ real_t AnimationTree::get_connection_activity(const StringName &p_path, int p_co
if (!input_activity_map_get.has(p_path)) {
return 0;
}
const Vector<Activity> *activity = input_activity_map_get[p_path];
const LocalVector<Activity> *activity = input_activity_map_get[p_path];
if (!activity || p_connection < 0 || p_connection >= activity->size()) {
if (!activity || p_connection < 0 || p_connection >= (int64_t)activity->size()) {
return 0;
}

View file

@ -59,8 +59,8 @@ public:
};
bool closable = false;
Vector<Input> inputs;
HashMap<NodePath, bool> filter;
LocalVector<Input> inputs;
AHashMap<NodePath, bool> filter;
bool filter_enabled = false;
// To propagate information from upstream for use in estimation of playback progress.
@ -91,28 +91,63 @@ public:
if (Math::is_zero_approx(remain)) {
return 0;
}
return length - position;
return remain;
}
};
// Temporary state for blending process which needs to be stored in each AnimationNodes.
struct NodeState {
friend AnimationNode;
private:
StringName base_path;
public:
AnimationNode *parent = nullptr;
Vector<StringName> connections;
Vector<real_t> track_weights;
LocalVector<real_t> track_weights;
const StringName get_base_path() const {
return base_path;
}
} node_state;
// Temporary state for blending process which needs to be started in the AnimationTree, pass through the AnimationNodes, and then return to the AnimationTree.
struct ProcessState {
AnimationTree *tree = nullptr;
HashMap<NodePath, int> track_map; // TODO: Is there a better way to manage filter/tracks?
const AHashMap<NodePath, int> *track_map; // TODO: Is there a better way to manage filter/tracks?
bool is_testing = false;
bool valid = false;
String invalid_reasons;
uint64_t last_pass = 0;
} *process_state = nullptr;
private:
mutable AHashMap<StringName, int> property_cache;
public:
void set_node_state_base_path(const StringName p_base_path) {
if (p_base_path != node_state.base_path) {
node_state.base_path = p_base_path;
make_cache_dirty();
}
}
void set_node_state_base_path(const String p_base_path) {
if (p_base_path != node_state.base_path) {
node_state.base_path = p_base_path;
make_cache_dirty();
}
}
const StringName get_node_state_base_path() const {
return node_state.get_base_path();
}
void make_cache_dirty() {
property_cache.clear();
}
Array _get_filters() const;
void _set_filters(const Array &p_filters);
friend class AnimationNodeBlendTree;
@ -151,7 +186,7 @@ protected:
GDVIRTUAL1RC(Ref<AnimationNode>, _get_child_by_name, StringName)
GDVIRTUAL1RC(Variant, _get_parameter_default_value, StringName)
GDVIRTUAL1RC(bool, _is_parameter_read_only, StringName)
GDVIRTUAL4RC(double, _process, double, bool, bool, bool)
GDVIRTUAL4R(double, _process, double, bool, bool, bool)
GDVIRTUAL0RC(String, _get_caption)
GDVIRTUAL0RC(bool, _has_filter)
@ -191,6 +226,10 @@ public:
void set_deletable(bool p_closable);
bool is_deletable() const;
ObjectID get_processing_animation_tree_instance_id() const;
bool is_process_testing() const;
virtual bool has_filter() const;
#ifdef TOOLS_ENABLED
@ -249,15 +288,15 @@ private:
friend class AnimationNode;
List<PropertyInfo> properties;
HashMap<StringName, HashMap<StringName, StringName>> property_parent_map;
HashMap<ObjectID, StringName> property_reference_map;
HashMap<StringName, Pair<Variant, bool>> property_map; // Property value and read-only flag.
mutable List<PropertyInfo> properties;
mutable AHashMap<StringName, AHashMap<StringName, StringName>> property_parent_map;
mutable AHashMap<ObjectID, StringName> property_reference_map;
mutable AHashMap<StringName, Pair<Variant, bool>> property_map; // Property value and read-only flag.
bool properties_dirty = true;
mutable bool properties_dirty = true;
void _update_properties();
void _update_properties_for_node(const String &p_base_path, Ref<AnimationNode> p_node);
void _update_properties() const;
void _update_properties_for_node(const String &p_base_path, Ref<AnimationNode> p_node) const;
void _tree_changed();
void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name);
@ -267,8 +306,8 @@ private:
uint64_t last_pass = 0;
real_t activity = 0.0;
};
HashMap<StringName, Vector<Activity>> input_activity_map;
HashMap<StringName, Vector<Activity> *> input_activity_map_get;
mutable HashMap<StringName, LocalVector<Activity>> input_activity_map;
mutable HashMap<StringName, LocalVector<Activity> *> input_activity_map_get;
NodePath animation_player;
@ -286,7 +325,7 @@ private:
virtual void _set_active(bool p_active) override;
// Make animation instances.
virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) override;
virtual bool _blend_pre_process(double p_delta, int p_track_count, const AHashMap<NodePath, int> &p_track_map) override;
#ifndef DISABLE_DEPRECATED
void _set_process_callback_bind_compat_80813(AnimationProcessCallback p_mode);

View file

@ -32,7 +32,7 @@
#include "root_motion_view.h"
#include "scene/animation/animation_tree.h"
#include "scene/animation/animation_mixer.h"
#include "scene/resources/material.h"
void RootMotionView::set_animation_mixer(const NodePath &p_path) {
@ -94,7 +94,6 @@ void RootMotionView::_notification(int p_what) {
if (has_node(path)) {
Node *node = get_node(path);
AnimationMixer *mixer = Object::cast_to<AnimationMixer>(node);
if (mixer && mixer->is_active() && mixer->get_root_motion_track() != NodePath()) {
if (is_processing_internal() && mixer->get_callback_mode_process() == AnimationMixer::ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS) {
@ -106,12 +105,12 @@ void RootMotionView::_notification(int p_what) {
set_process_internal(true);
set_physics_process_internal(false);
}
transform.origin = mixer->get_root_motion_position();
transform.basis = mixer->get_root_motion_rotation(); // Scale is meaningless.
diff = mixer->get_root_motion_rotation_accumulator();
diff = mixer->is_root_motion_local() ? Quaternion() : mixer->get_root_motion_rotation_accumulator();
}
}
if (!first && transform == Transform3D()) {
return;
}

View file

@ -57,10 +57,20 @@ void Tweener::set_tween(const Ref<Tween> &p_tween) {
tween_id = p_tween->get_instance_id();
}
void Tweener::start() {
elapsed_time = 0;
finished = false;
}
Ref<Tween> Tweener::_get_tween() {
return Ref<Tween>(ObjectDB::get_instance(tween_id));
}
void Tweener::_finish() {
finished = true;
emit_signal(SceneStringName(finished));
}
void Tweener::_bind_methods() {
ADD_SIGNAL(MethodInfo("finished"));
}
@ -116,7 +126,8 @@ Ref<PropertyTweener> Tween::tween_property(const Object *p_target, const NodePat
return nullptr;
}
Ref<PropertyTweener> tweener = memnew(PropertyTweener(p_target, property_subnames, p_to, p_duration));
Ref<PropertyTweener> tweener;
tweener.instantiate(p_target, property_subnames, p_to, p_duration);
append(tweener);
return tweener;
}
@ -124,7 +135,8 @@ Ref<PropertyTweener> Tween::tween_property(const Object *p_target, const NodePat
Ref<IntervalTweener> Tween::tween_interval(double p_time) {
CHECK_VALID();
Ref<IntervalTweener> tweener = memnew(IntervalTweener(p_time));
Ref<IntervalTweener> tweener;
tweener.instantiate(p_time);
append(tweener);
return tweener;
}
@ -132,7 +144,8 @@ Ref<IntervalTweener> Tween::tween_interval(double p_time) {
Ref<CallbackTweener> Tween::tween_callback(const Callable &p_callback) {
CHECK_VALID();
Ref<CallbackTweener> tweener = memnew(CallbackTweener(p_callback));
Ref<CallbackTweener> tweener;
tweener.instantiate(p_callback);
append(tweener);
return tweener;
}
@ -144,7 +157,27 @@ Ref<MethodTweener> Tween::tween_method(const Callable &p_callback, const Variant
return nullptr;
}
Ref<MethodTweener> tweener = memnew(MethodTweener(p_callback, p_from, p_to, p_duration));
Ref<MethodTweener> tweener;
tweener.instantiate(p_callback, p_from, p_to, p_duration);
append(tweener);
return tweener;
}
Ref<SubtweenTweener> Tween::tween_subtween(const Ref<Tween> &p_subtween) {
CHECK_VALID();
// Ensure that the subtween being added is not null.
ERR_FAIL_COND_V(p_subtween.is_null(), nullptr);
Ref<SubtweenTweener> tweener;
tweener.instantiate(p_subtween);
// Remove the tween from its parent tree, if it has one.
// If the user created this tween without a parent tree attached,
// then this step isn't necessary.
if (tweener->subtween->parent_tree != nullptr) {
tweener->subtween->parent_tree->remove_tween(tweener->subtween);
}
append(tweener);
return tweener;
}
@ -208,7 +241,7 @@ Ref<Tween> Tween::set_process_mode(TweenProcessMode p_mode) {
return this;
}
Tween::TweenProcessMode Tween::get_process_mode() {
Tween::TweenProcessMode Tween::get_process_mode() const {
return process_mode;
}
@ -217,10 +250,19 @@ Ref<Tween> Tween::set_pause_mode(TweenPauseMode p_mode) {
return this;
}
Tween::TweenPauseMode Tween::get_pause_mode() {
Tween::TweenPauseMode Tween::get_pause_mode() const {
return pause_mode;
}
Ref<Tween> Tween::set_ignore_time_scale(bool p_ignore) {
ignore_time_scale = p_ignore;
return this;
}
bool Tween::is_ignoring_time_scale() const {
return ignore_time_scale;
}
Ref<Tween> Tween::set_parallel(bool p_parallel) {
default_parallel = p_parallel;
parallel_enabled = p_parallel;
@ -250,7 +292,7 @@ Ref<Tween> Tween::set_trans(TransitionType p_trans) {
return this;
}
Tween::TransitionType Tween::get_trans() {
Tween::TransitionType Tween::get_trans() const {
return default_transition;
}
@ -259,7 +301,7 @@ Ref<Tween> Tween::set_ease(EaseType p_ease) {
return this;
}
Tween::EaseType Tween::get_ease() {
Tween::EaseType Tween::get_ease() const {
return default_ease;
}
@ -433,6 +475,7 @@ void Tween::_bind_methods() {
ClassDB::bind_method(D_METHOD("tween_interval", "time"), &Tween::tween_interval);
ClassDB::bind_method(D_METHOD("tween_callback", "callback"), &Tween::tween_callback);
ClassDB::bind_method(D_METHOD("tween_method", "method", "from", "to", "duration"), &Tween::tween_method);
ClassDB::bind_method(D_METHOD("tween_subtween", "subtween"), &Tween::tween_subtween);
ClassDB::bind_method(D_METHOD("custom_step", "delta"), &Tween::custom_step);
ClassDB::bind_method(D_METHOD("stop"), &Tween::stop);
@ -446,6 +489,7 @@ void Tween::_bind_methods() {
ClassDB::bind_method(D_METHOD("bind_node", "node"), &Tween::bind_node);
ClassDB::bind_method(D_METHOD("set_process_mode", "mode"), &Tween::set_process_mode);
ClassDB::bind_method(D_METHOD("set_pause_mode", "mode"), &Tween::set_pause_mode);
ClassDB::bind_method(D_METHOD("set_ignore_time_scale", "ignore"), &Tween::set_ignore_time_scale, DEFVAL(true));
ClassDB::bind_method(D_METHOD("set_parallel", "parallel"), &Tween::set_parallel, DEFVAL(true));
ClassDB::bind_method(D_METHOD("set_loops", "loops"), &Tween::set_loops, DEFVAL(0));
@ -493,8 +537,9 @@ Tween::Tween() {
ERR_FAIL_MSG("Tween can't be created directly. Use create_tween() method.");
}
Tween::Tween(bool p_valid) {
valid = p_valid;
Tween::Tween(SceneTree *p_parent_tree) {
parent_tree = p_parent_tree;
valid = true;
}
Ref<PropertyTweener> PropertyTweener::from(const Variant &p_value) {
@ -542,8 +587,7 @@ Ref<PropertyTweener> PropertyTweener::set_delay(double p_delay) {
}
void PropertyTweener::start() {
elapsed_time = 0;
finished = false;
Tweener::start();
Object *target_instance = ObjectDB::get_instance(target);
if (!target_instance) {
@ -574,6 +618,7 @@ bool PropertyTweener::step(double &r_delta) {
Object *target_instance = ObjectDB::get_instance(target);
if (!target_instance) {
_finish();
return false;
}
elapsed_time += r_delta;
@ -612,9 +657,8 @@ bool PropertyTweener::step(double &r_delta) {
return true;
} else {
target_instance->set_indexed(property, final_val);
finished = true;
r_delta = elapsed_time - delay - duration;
emit_signal(SceneStringName(finished));
_finish();
return false;
}
}
@ -656,11 +700,6 @@ PropertyTweener::PropertyTweener() {
ERR_FAIL_MSG("PropertyTweener can't be created directly. Use the tween_property() method in Tween.");
}
void IntervalTweener::start() {
elapsed_time = 0;
finished = false;
}
bool IntervalTweener::step(double &r_delta) {
if (finished) {
return false;
@ -672,9 +711,8 @@ bool IntervalTweener::step(double &r_delta) {
r_delta = 0;
return true;
} else {
finished = true;
r_delta = elapsed_time - duration;
emit_signal(SceneStringName(finished));
_finish();
return false;
}
}
@ -692,17 +730,13 @@ Ref<CallbackTweener> CallbackTweener::set_delay(double p_delay) {
return this;
}
void CallbackTweener::start() {
elapsed_time = 0;
finished = false;
}
bool CallbackTweener::step(double &r_delta) {
if (finished) {
return false;
}
if (!callback.is_valid()) {
_finish();
return false;
}
@ -715,9 +749,8 @@ bool CallbackTweener::step(double &r_delta) {
ERR_FAIL_V_MSG(false, "Error calling method from CallbackTweener: " + Variant::get_callable_error_text(callback, nullptr, 0, ce) + ".");
}
finished = true;
r_delta = elapsed_time - delay;
emit_signal(SceneStringName(finished));
_finish();
return false;
}
@ -757,17 +790,13 @@ Ref<MethodTweener> MethodTweener::set_ease(Tween::EaseType p_ease) {
return this;
}
void MethodTweener::start() {
elapsed_time = 0;
finished = false;
}
bool MethodTweener::step(double &r_delta) {
if (finished) {
return false;
}
if (!callback.is_valid()) {
_finish();
return false;
}
@ -801,9 +830,8 @@ bool MethodTweener::step(double &r_delta) {
r_delta = 0;
return true;
} else {
finished = true;
r_delta = elapsed_time - delay - duration;
emit_signal(SceneStringName(finished));
_finish();
return false;
}
}
@ -840,3 +868,50 @@ MethodTweener::MethodTweener(const Callable &p_callback, const Variant &p_from,
MethodTweener::MethodTweener() {
ERR_FAIL_MSG("MethodTweener can't be created directly. Use the tween_method() method in Tween.");
}
void SubtweenTweener::start() {
Tweener::start();
// Reset the subtween.
subtween->stop();
subtween->play();
}
bool SubtweenTweener::step(double &r_delta) {
if (finished) {
return false;
}
elapsed_time += r_delta;
if (elapsed_time < delay) {
r_delta = 0;
return true;
}
if (!subtween->step(r_delta)) {
r_delta = elapsed_time - delay - subtween->get_total_time();
_finish();
return false;
}
r_delta = 0;
return true;
}
Ref<SubtweenTweener> SubtweenTweener::set_delay(double p_delay) {
delay = p_delay;
return this;
}
void SubtweenTweener::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_delay", "delay"), &SubtweenTweener::set_delay);
}
SubtweenTweener::SubtweenTweener(const Ref<Tween> &p_subtween) {
subtween = p_subtween;
}
SubtweenTweener::SubtweenTweener() {
ERR_FAIL_MSG("SubtweenTweener can't be created directly. Use the tween_subtween() method in Tween.");
}

View file

@ -35,6 +35,7 @@
class Tween;
class Node;
class SceneTree;
class Tweener : public RefCounted {
GDCLASS(Tweener, RefCounted);
@ -43,13 +44,14 @@ class Tweener : public RefCounted {
public:
virtual void set_tween(const Ref<Tween> &p_tween);
virtual void start() = 0;
virtual void start();
virtual bool step(double &r_delta) = 0;
protected:
static void _bind_methods();
Ref<Tween> _get_tween();
void _finish();
double elapsed_time = 0;
bool finished = false;
@ -59,6 +61,7 @@ class PropertyTweener;
class IntervalTweener;
class CallbackTweener;
class MethodTweener;
class SubtweenTweener;
class Tween : public RefCounted {
GDCLASS(Tween, RefCounted);
@ -108,12 +111,14 @@ private:
EaseType default_ease = EaseType::EASE_IN_OUT;
ObjectID bound_node;
SceneTree *parent_tree = nullptr;
Vector<List<Ref<Tweener>>> tweeners;
double total_time = 0;
int current_step = -1;
int loops = 1;
int loops_done = 0;
float speed_scale = 1;
bool ignore_time_scale = false;
bool is_bound = false;
bool started = false;
@ -143,6 +148,7 @@ public:
Ref<IntervalTweener> tween_interval(double p_time);
Ref<CallbackTweener> tween_callback(const Callable &p_callback);
Ref<MethodTweener> tween_method(const Callable &p_callback, const Variant p_from, Variant p_to, double p_duration);
Ref<SubtweenTweener> tween_subtween(const Ref<Tween> &p_subtween);
void append(Ref<Tweener> p_tweener);
bool custom_step(double p_delta);
@ -157,18 +163,20 @@ public:
Ref<Tween> bind_node(const Node *p_node);
Ref<Tween> set_process_mode(TweenProcessMode p_mode);
TweenProcessMode get_process_mode();
TweenProcessMode get_process_mode() const;
Ref<Tween> set_pause_mode(TweenPauseMode p_mode);
TweenPauseMode get_pause_mode();
TweenPauseMode get_pause_mode() const;
Ref<Tween> set_ignore_time_scale(bool p_ignore = true);
bool is_ignoring_time_scale() const;
Ref<Tween> set_parallel(bool p_parallel);
Ref<Tween> set_loops(int p_loops);
int get_loops_left() const;
Ref<Tween> set_speed_scale(float p_speed);
Ref<Tween> set_trans(TransitionType p_trans);
TransitionType get_trans();
TransitionType get_trans() const;
Ref<Tween> set_ease(EaseType p_ease);
EaseType get_ease();
EaseType get_ease() const;
Ref<Tween> parallel();
Ref<Tween> chain();
@ -182,7 +190,7 @@ public:
double get_total_time() const;
Tween();
Tween(bool p_valid);
Tween(SceneTree *p_parent_tree);
};
VARIANT_ENUM_CAST(Tween::TweenPauseMode);
@ -237,7 +245,6 @@ class IntervalTweener : public Tweener {
GDCLASS(IntervalTweener, Tweener);
public:
void start() override;
bool step(double &r_delta) override;
IntervalTweener(double p_time);
@ -253,7 +260,6 @@ class CallbackTweener : public Tweener {
public:
Ref<CallbackTweener> set_delay(double p_delay);
void start() override;
bool step(double &r_delta) override;
CallbackTweener(const Callable &p_callback);
@ -278,7 +284,6 @@ public:
Ref<MethodTweener> set_delay(double p_delay);
void set_tween(const Ref<Tween> &p_tween) override;
void start() override;
bool step(double &r_delta) override;
MethodTweener(const Callable &p_callback, const Variant &p_from, const Variant &p_to, double p_duration);
@ -301,4 +306,24 @@ private:
Ref<RefCounted> ref_copy;
};
class SubtweenTweener : public Tweener {
GDCLASS(SubtweenTweener, Tweener);
public:
Ref<Tween> subtween;
void start() override;
bool step(double &r_delta) override;
Ref<SubtweenTweener> set_delay(double p_delay);
SubtweenTweener(const Ref<Tween> &p_subtween);
SubtweenTweener();
protected:
static void _bind_methods();
private:
double delay = 0;
};
#endif // TWEEN_H