diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index a90cf6e361a..b2aae2316f4 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1416,6 +1416,9 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true); GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor", false); + GLOBAL_DEF("animation/warnings/check_invalid_track_paths", true); + GLOBAL_DEF("animation/warnings/check_angle_interpolation_type_conflicting", true); + GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "audio/buses/default_bus_layout", PROPERTY_HINT_FILE, "*.tres"), "res://default_bus_layout.tres"); GLOBAL_DEF_RST("audio/general/text_to_speech", false); GLOBAL_DEF_RST(PropertyInfo(Variant::FLOAT, "audio/general/2d_panning_strength", PROPERTY_HINT_RANGE, "0,2,0.01"), 0.5f); diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index b23d605d9ec..a0bf0e0c7fb 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -238,6 +238,12 @@ + + If [code]true[/code], [AnimationMixer] prints the warning of interpolation being forced to choose the shortest rotation path due to multiple angle interpolation types being mixed in the [AnimationMixer] cache. + + + If [code]true[/code], [AnimationMixer] prints the warning of no matching object of the track path in the scene. + Background color for the boot splash. diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 8b59abc7130..bb403d3e3e6 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -77,10 +77,10 @@ void AnimationTrackKeyEdit::_fix_node_path(Variant &value) { Node *root = EditorNode::get_singleton()->get_tree()->get_root(); - Node *np_node = root->get_node(np); + Node *np_node = root->get_node_or_null(np); ERR_FAIL_NULL(np_node); - Node *edited_node = root->get_node(base); + Node *edited_node = root->get_node_or_null(base); ERR_FAIL_NULL(edited_node); value = edited_node->get_path_to(np_node); @@ -601,8 +601,8 @@ void AnimationTrackKeyEdit::_get_property_list(List *p_list) const case Animation::TYPE_ANIMATION: { String animations; - if (root_path && root_path->has_node(animation->track_get_path(track))) { - AnimationPlayer *ap = Object::cast_to(root_path->get_node(animation->track_get_path(track))); + if (root_path) { + AnimationPlayer *ap = Object::cast_to(root_path->get_node_or_null(animation->track_get_path(track))); if (ap) { List anims; ap->get_animation_list(&anims); @@ -663,10 +663,10 @@ void AnimationMultiTrackKeyEdit::_fix_node_path(Variant &value, NodePath &base) Node *root = EditorNode::get_singleton()->get_tree()->get_root(); - Node *np_node = root->get_node(np); + Node *np_node = root->get_node_or_null(np); ERR_FAIL_NULL(np_node); - Node *edited_node = root->get_node(base); + Node *edited_node = root->get_node_or_null(base); ERR_FAIL_NULL(edited_node); value = edited_node->get_path_to(np_node); @@ -1207,8 +1207,8 @@ void AnimationMultiTrackKeyEdit::_get_property_list(List *p_list) String animations; - if (root_path && root_path->has_node(animation->track_get_path(first_track))) { - AnimationPlayer *ap = Object::cast_to(root_path->get_node(animation->track_get_path(first_track))); + if (root_path) { + AnimationPlayer *ap = Object::cast_to(root_path->get_node_or_null(animation->track_get_path(first_track))); if (ap) { List anims; ap->get_animation_list(&anims); @@ -1940,8 +1940,8 @@ void AnimationTrackEdit::_notification(int p_what) { NodePath anim_path = animation->track_get_path(track); Node *node = nullptr; - if (root && root->has_node(anim_path)) { - node = root->get_node(anim_path); + if (root) { + node = root->get_node_or_null(anim_path); } String text; @@ -2481,10 +2481,9 @@ void AnimationTrackEdit::_path_submitted(const String &p_text) { } bool AnimationTrackEdit::_is_value_key_valid(const Variant &p_key_value, Variant::Type &r_valid_type) const { - if (root == nullptr) { + if (root == nullptr || !root->has_node_and_resource(animation->track_get_path(track))) { return false; } - Ref res; Vector leftover_path; Node *node = root->get_node_and_resource(animation->track_get_path(track), res, leftover_path); @@ -2774,11 +2773,11 @@ void AnimationTrackEdit::gui_input(const Ref &p_event) { AnimationPlayer *ap = ape->get_player(); if (ap) { NodePath npath = animation->track_get_path(track); - Node *a_ap_root_node = ap->get_node(ap->get_root_node()); + Node *a_ap_root_node = ap->get_node_or_null(ap->get_root_node()); Node *nd = nullptr; // We must test that we have a valid a_ap_root_node before trying to access its content to init the nd Node. if (a_ap_root_node) { - nd = a_ap_root_node->get_node(NodePath(npath.get_concatenated_names())); + nd = a_ap_root_node->get_node_or_null(NodePath(npath.get_concatenated_names())); } if (nd) { StringName prop = npath.get_concatenated_subnames(); @@ -3310,8 +3309,8 @@ void AnimationTrackEditGroup::_notification(int p_what) { int separation = get_theme_constant(SNAME("h_separation"), SNAME("ItemList")); Color color = get_theme_color(SNAME("font_color"), SNAME("Label")); - if (root && root->has_node(node)) { - Node *n = root->get_node(node); + if (root) { + Node *n = root->get_node_or_null(node); if (n && EditorNode::get_singleton()->get_editor_selection()->is_selected(n)) { color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor)); } @@ -3353,7 +3352,10 @@ void AnimationTrackEditGroup::gui_input(const Ref &p_event) { if (node_name_rect.has_point(pos)) { EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection(); editor_selection->clear(); - editor_selection->add_node(root->get_node(node)); + Node *n = root->get_node_or_null(node); + if (n) { + editor_selection->add_node(n); + } } } } @@ -4474,8 +4476,8 @@ void AnimationTrackEditor::_update_tracks() { if (use_filter) { NodePath path = animation->track_get_path(i); - if (root && root->has_node(path)) { - Node *node = root->get_node(path); + if (root) { + Node *node = root->get_node_or_null(path); if (!node) { continue; // No node, no filter. } @@ -4527,8 +4529,8 @@ void AnimationTrackEditor::_update_tracks() { NodePath path = animation->track_get_path(i); Node *node = nullptr; - if (root && root->has_node(path)) { - node = root->get_node(path); + if (root) { + node = root->get_node_or_null(path); } if (node && Object::cast_to(node)) { @@ -4557,8 +4559,8 @@ void AnimationTrackEditor::_update_tracks() { Ref icon = get_editor_theme_icon(SNAME("Node")); String name = base_path; String tooltip; - if (root && root->has_node(base_path)) { - Node *n = root->get_node(base_path); + if (root) { + Node *n = root->get_node_or_null(base_path); if (n) { icon = EditorNode::get_singleton()->get_object_icon(n, "Node"); name = n->get_name(); @@ -4808,7 +4810,7 @@ void AnimationTrackEditor::_dropped_track(int p_from_track, int p_to_track) { void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) { ERR_FAIL_NULL(root); - Node *node = get_node(p_path); + Node *node = get_node_or_null(p_path); ERR_FAIL_NULL(node); NodePath path_to = root->get_path_to(node, true); @@ -5129,7 +5131,8 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a method key.")); return; } - Node *base = root->get_node(animation->track_get_path(p_track)); + Node *base = root->get_node_or_null(animation->track_get_path(p_track)); + ERR_FAIL_NULL(base); method_selector->select_method_from_instance(base); @@ -5182,7 +5185,8 @@ void AnimationTrackEditor::_add_method_key(const String &p_method) { EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a method key.")); return; } - Node *base = root->get_node(animation->track_get_path(insert_key_from_track_call_track)); + Node *base = root->get_node_or_null(animation->track_get_path(insert_key_from_track_call_track)); + ERR_FAIL_NULL(base); List minfo; base->get_method_list(&minfo); @@ -5963,8 +5967,8 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { NodePath path = animation->track_get_path(i); Node *node = nullptr; - if (root && root->has_node(path)) { - node = root->get_node(path); + if (root) { + node = root->get_node_or_null(path); } String text; @@ -6085,7 +6089,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { if (root) { NodePath np = track_clipboard[i].full_path; - exists = root->get_node(np); + exists = root->get_node_or_null(np); if (exists) { path = NodePath(root->get_path_to(exists).get_names(), track_clipboard[i].full_path.get_subnames(), false); } @@ -6587,15 +6591,17 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { void AnimationTrackEditor::_cleanup_animation(Ref p_animation) { for (int i = 0; i < p_animation->get_track_count(); i++) { + if (!root->has_node_and_resource(p_animation->track_get_path(i))) { + continue; + } + Ref res; + Vector leftover_path; + Node *node = root->get_node_and_resource(p_animation->track_get_path(i), res, leftover_path); + bool prop_exists = false; Variant::Type valid_type = Variant::NIL; Object *obj = nullptr; - Ref res; - Vector leftover_path; - - Node *node = root->get_node_and_resource(p_animation->track_get_path(i), res, leftover_path); - if (res.is_valid()) { obj = res.ptr(); } else if (node) { @@ -6772,9 +6778,9 @@ void AnimationTrackEditor::_pick_track_select_recursive(TreeItem *p_item, const } NodePath np = p_item->get_metadata(0); - Node *node = get_node(np); + Node *node = get_node_or_null(np); - if (!p_filter.is_empty() && ((String)node->get_name()).findn(p_filter) != -1) { + if (node && !p_filter.is_empty() && ((String)node->get_name()).findn(p_filter) != -1) { p_select_candidates.push_back(node); } diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 9f954fd6c0c..2f99bdda405 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -32,6 +32,7 @@ #include "animation_mixer.compat.inc" #include "core/config/engine.h" +#include "core/config/project_settings.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/node_3d.h" #include "scene/3d/skeleton_3d.h" @@ -597,6 +598,9 @@ bool AnimationMixer::_update_caches() { List sname; get_animation_list(&sname); + bool check_path = GLOBAL_GET("animation/warnings/check_invalid_track_paths"); + bool check_angle_interpolation = GLOBAL_GET("animation/warnings/check_angle_interpolation_type_conflicting"); + Node *parent = get_node_or_null(root_node); if (!parent) { cache_valid = false; @@ -645,10 +649,19 @@ bool AnimationMixer::_update_caches() { if (!track) { Ref resource; Vector leftover_path; - Node *child = parent->get_node_and_resource(path, resource, leftover_path); + if (!parent->has_node_and_resource(path)) { + if (check_path) { + WARN_PRINT_ED(mixer_name + ": '" + String(E) + "', couldn't resolve track: '" + String(path) + "'. This warning can be disabled in Project Settings."); + } + continue; + } + + Node *child = parent->get_node_and_resource(path, resource, leftover_path); if (!child) { - ERR_PRINT("AnimationMixer: '" + String(E) + "', couldn't resolve track: '" + String(path) + "'."); + if (check_path) { + WARN_PRINT_ED(mixer_name + ": '" + String(E) + "', couldn't resolve track: '" + String(path) + "'. This warning can be disabled in Project Settings."); + } continue; } @@ -657,7 +670,7 @@ bool AnimationMixer::_update_caches() { case Animation::TYPE_VALUE: { // If a value track without a key is cached first, the initial value cannot be determined. // It is a corner case, but which may cause problems with blending. - ERR_CONTINUE_MSG(anim->track_get_key_count(i) == 0, "AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' must have at least one key to cache for blending."); + ERR_CONTINUE_MSG(anim->track_get_key_count(i) == 0, mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' must have at least one key to cache for blending."); TrackCacheValue *track_value = memnew(TrackCacheValue); @@ -698,7 +711,7 @@ bool AnimationMixer::_update_caches() { Node3D *node_3d = Object::cast_to(child); if (!node_3d) { - ERR_PRINT("AnimationMixer: '" + String(E) + "', transform track does not point to Node3D: '" + String(path) + "'."); + ERR_PRINT(mixer_name + ": '" + String(E) + "', transform track does not point to Node3D: '" + String(path) + "'."); continue; } @@ -764,20 +777,20 @@ bool AnimationMixer::_update_caches() { case Animation::TYPE_BLEND_SHAPE: { #ifndef _3D_DISABLED if (path.get_subname_count() != 1) { - ERR_PRINT("AnimationMixer: '" + String(E) + "', blend shape track does not contain a blend shape subname: '" + String(path) + "'."); + ERR_PRINT(mixer_name + ": '" + String(E) + "', blend shape track does not contain a blend shape subname: '" + String(path) + "'."); continue; } MeshInstance3D *mesh_3d = Object::cast_to(child); if (!mesh_3d) { - ERR_PRINT("AnimationMixer: '" + String(E) + "', blend shape track does not point to MeshInstance3D: '" + String(path) + "'."); + ERR_PRINT(mixer_name + ": '" + String(E) + "', blend shape track does not point to MeshInstance3D: '" + String(path) + "'."); continue; } StringName blend_shape_name = path.get_subname(0); int blend_shape_idx = mesh_3d->find_blend_shape_by_name(blend_shape_name); if (blend_shape_idx == -1) { - ERR_PRINT("AnimationMixer: '" + String(E) + "', blend shape track points to a non-existing name: '" + String(blend_shape_name) + "'."); + ERR_PRINT(mixer_name + ": '" + String(E) + "', blend shape track points to a non-existing name: '" + String(blend_shape_name) + "'."); continue; } @@ -855,7 +868,6 @@ bool AnimationMixer::_update_caches() { } else if (track_cache_type == Animation::TYPE_VALUE) { // If it has at least one angle interpolation, it also uses angle interpolation for blending. TrackCacheValue *track_value = static_cast(track); - bool was_continuous = track_value->is_continuous; bool was_using_angle = track_value->is_using_angle; if (track_src_type == Animation::TYPE_VALUE) { @@ -868,23 +880,17 @@ bool AnimationMixer::_update_caches() { // TODO: Currently, misc type cannot be blended. // In the future, it should have a separate blend weight, just as bool is converted to 0 and 1. // Then, it should provide the correct precedence value. - bool skip_update_mode_warning = false; if (track_value->is_continuous) { if (!Animation::is_variant_interpolatable(track_value->init_value)) { WARN_PRINT_ONCE_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' uses a non-numeric type as key value with UpdateMode.UPDATE_CONTINUOUS. This will not be blended correctly, so it is forced to UpdateMode.UPDATE_DISCRETE."); track_value->is_continuous = false; - skip_update_mode_warning = true; } if (track_value->init_value.is_string()) { WARN_PRINT_ONCE_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' blends String types. This is an experimental algorithm."); } } - - if (!skip_update_mode_warning && was_continuous != track_value->is_continuous) { - WARN_PRINT_ONCE_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' has different update modes between some animations which may be blended together. Blending prioritizes UpdateMode.UPDATE_CONTINUOUS, so the process treats UpdateMode.UPDATE_DISCRETE as UpdateMode.UPDATE_CONTINUOUS with InterpolationType.INTERPOLATION_NEAREST."); - } - if (was_using_angle != track_value->is_using_angle) { - WARN_PRINT_ONCE_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' has different interpolation types for rotation between some animations which may be blended together. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value."); + if (check_angle_interpolation && (was_using_angle != track_value->is_using_angle)) { + WARN_PRINT_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' has different interpolation types for rotation between some animations which may be blended together. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value. If you do not want further warnings, you can turn off the checking for the angle interpolation type conflicting in Project Settings."); } }