Merge pull request #86608 from TokageItLab/add-warn-setting-anim

Add some options to Project Settings to silence warnings in AnimationMixer caching
This commit is contained in:
Rémi Verschelde 2024-02-17 00:22:52 +01:00
commit b6ef996eb7
No known key found for this signature in database
GPG key ID: C3336907360768E1
4 changed files with 73 additions and 52 deletions

View file

@ -1430,6 +1430,9 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true); GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true);
GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor", false); 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_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("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); GLOBAL_DEF_RST(PropertyInfo(Variant::FLOAT, "audio/general/2d_panning_strength", PROPERTY_HINT_RANGE, "0,2,0.01"), 0.5f);

View file

@ -238,6 +238,12 @@
</method> </method>
</methods> </methods>
<members> <members>
<member name="animation/warnings/check_angle_interpolation_type_conflicting" type="bool" setter="" getter="" default="true">
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.
</member>
<member name="animation/warnings/check_invalid_track_paths" type="bool" setter="" getter="" default="true">
If [code]true[/code], [AnimationMixer] prints the warning of no matching object of the track path in the scene.
</member>
<member name="application/boot_splash/bg_color" type="Color" setter="" getter="" default="Color(0.14, 0.14, 0.14, 1)"> <member name="application/boot_splash/bg_color" type="Color" setter="" getter="" default="Color(0.14, 0.14, 0.14, 1)">
Background color for the boot splash. Background color for the boot splash.
</member> </member>

View file

@ -77,10 +77,10 @@ void AnimationTrackKeyEdit::_fix_node_path(Variant &value) {
Node *root = EditorNode::get_singleton()->get_tree()->get_root(); 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); 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); ERR_FAIL_NULL(edited_node);
value = edited_node->get_path_to(np_node); value = edited_node->get_path_to(np_node);
@ -601,8 +601,8 @@ void AnimationTrackKeyEdit::_get_property_list(List<PropertyInfo> *p_list) const
case Animation::TYPE_ANIMATION: { case Animation::TYPE_ANIMATION: {
String animations; String animations;
if (root_path && root_path->has_node(animation->track_get_path(track))) { if (root_path) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node(animation->track_get_path(track))); AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node_or_null(animation->track_get_path(track)));
if (ap) { if (ap) {
List<StringName> anims; List<StringName> anims;
ap->get_animation_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 *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); 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); ERR_FAIL_NULL(edited_node);
value = edited_node->get_path_to(np_node); value = edited_node->get_path_to(np_node);
@ -1207,8 +1207,8 @@ void AnimationMultiTrackKeyEdit::_get_property_list(List<PropertyInfo> *p_list)
String animations; String animations;
if (root_path && root_path->has_node(animation->track_get_path(first_track))) { if (root_path) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node(animation->track_get_path(first_track))); AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node_or_null(animation->track_get_path(first_track)));
if (ap) { if (ap) {
List<StringName> anims; List<StringName> anims;
ap->get_animation_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); NodePath anim_path = animation->track_get_path(track);
Node *node = nullptr; Node *node = nullptr;
if (root && root->has_node(anim_path)) { if (root) {
node = root->get_node(anim_path); node = root->get_node_or_null(anim_path);
} }
String text; 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 { 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; return false;
} }
Ref<Resource> res; Ref<Resource> res;
Vector<StringName> leftover_path; Vector<StringName> leftover_path;
Node *node = root->get_node_and_resource(animation->track_get_path(track), res, 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<InputEvent> &p_event) {
AnimationPlayer *ap = ape->get_player(); AnimationPlayer *ap = ape->get_player();
if (ap) { if (ap) {
NodePath npath = animation->track_get_path(track); 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; 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. // 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) { 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) { if (nd) {
StringName prop = npath.get_concatenated_subnames(); 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")); int separation = get_theme_constant(SNAME("h_separation"), SNAME("ItemList"));
Color color = get_theme_color(SNAME("font_color"), SNAME("Label")); Color color = get_theme_color(SNAME("font_color"), SNAME("Label"));
if (root && root->has_node(node)) { if (root) {
Node *n = root->get_node(node); Node *n = root->get_node_or_null(node);
if (n && EditorNode::get_singleton()->get_editor_selection()->is_selected(n)) { if (n && EditorNode::get_singleton()->get_editor_selection()->is_selected(n)) {
color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor)); color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
} }
@ -3353,7 +3352,10 @@ void AnimationTrackEditGroup::gui_input(const Ref<InputEvent> &p_event) {
if (node_name_rect.has_point(pos)) { if (node_name_rect.has_point(pos)) {
EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection(); EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();
editor_selection->clear(); 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) { if (use_filter) {
NodePath path = animation->track_get_path(i); NodePath path = animation->track_get_path(i);
if (root && root->has_node(path)) { if (root) {
Node *node = root->get_node(path); Node *node = root->get_node_or_null(path);
if (!node) { if (!node) {
continue; // No node, no filter. continue; // No node, no filter.
} }
@ -4527,8 +4529,8 @@ void AnimationTrackEditor::_update_tracks() {
NodePath path = animation->track_get_path(i); NodePath path = animation->track_get_path(i);
Node *node = nullptr; Node *node = nullptr;
if (root && root->has_node(path)) { if (root) {
node = root->get_node(path); node = root->get_node_or_null(path);
} }
if (node && Object::cast_to<AnimationPlayer>(node)) { if (node && Object::cast_to<AnimationPlayer>(node)) {
@ -4557,8 +4559,8 @@ void AnimationTrackEditor::_update_tracks() {
Ref<Texture2D> icon = get_editor_theme_icon(SNAME("Node")); Ref<Texture2D> icon = get_editor_theme_icon(SNAME("Node"));
String name = base_path; String name = base_path;
String tooltip; String tooltip;
if (root && root->has_node(base_path)) { if (root) {
Node *n = root->get_node(base_path); Node *n = root->get_node_or_null(base_path);
if (n) { if (n) {
icon = EditorNode::get_singleton()->get_object_icon(n, "Node"); icon = EditorNode::get_singleton()->get_object_icon(n, "Node");
name = n->get_name(); 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) { void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) {
ERR_FAIL_NULL(root); ERR_FAIL_NULL(root);
Node *node = get_node(p_path); Node *node = get_node_or_null(p_path);
ERR_FAIL_NULL(node); ERR_FAIL_NULL(node);
NodePath path_to = root->get_path_to(node, true); 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.")); EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a method key."));
return; 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); 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.")); EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a method key."));
return; 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<MethodInfo> minfo; List<MethodInfo> minfo;
base->get_method_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); NodePath path = animation->track_get_path(i);
Node *node = nullptr; Node *node = nullptr;
if (root && root->has_node(path)) { if (root) {
node = root->get_node(path); node = root->get_node_or_null(path);
} }
String text; String text;
@ -6085,7 +6089,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
if (root) { if (root) {
NodePath np = track_clipboard[i].full_path; NodePath np = track_clipboard[i].full_path;
exists = root->get_node(np); exists = root->get_node_or_null(np);
if (exists) { if (exists) {
path = NodePath(root->get_path_to(exists).get_names(), track_clipboard[i].full_path.get_subnames(), false); 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<Animation> p_animation) { void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) {
for (int i = 0; i < p_animation->get_track_count(); i++) { 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<Resource> res;
Vector<StringName> leftover_path;
Node *node = root->get_node_and_resource(p_animation->track_get_path(i), res, leftover_path);
bool prop_exists = false; bool prop_exists = false;
Variant::Type valid_type = Variant::NIL; Variant::Type valid_type = Variant::NIL;
Object *obj = nullptr; Object *obj = nullptr;
Ref<Resource> res;
Vector<StringName> leftover_path;
Node *node = root->get_node_and_resource(p_animation->track_get_path(i), res, leftover_path);
if (res.is_valid()) { if (res.is_valid()) {
obj = res.ptr(); obj = res.ptr();
} else if (node) { } else if (node) {
@ -6772,9 +6778,9 @@ void AnimationTrackEditor::_pick_track_select_recursive(TreeItem *p_item, const
} }
NodePath np = p_item->get_metadata(0); 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); p_select_candidates.push_back(node);
} }

View file

@ -32,6 +32,7 @@
#include "animation_mixer.compat.inc" #include "animation_mixer.compat.inc"
#include "core/config/engine.h" #include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "scene/3d/mesh_instance_3d.h" #include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/node_3d.h" #include "scene/3d/node_3d.h"
#include "scene/3d/skeleton_3d.h" #include "scene/3d/skeleton_3d.h"
@ -597,6 +598,9 @@ bool AnimationMixer::_update_caches() {
List<StringName> sname; List<StringName> sname;
get_animation_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); Node *parent = get_node_or_null(root_node);
if (!parent) { if (!parent) {
cache_valid = false; cache_valid = false;
@ -645,10 +649,19 @@ bool AnimationMixer::_update_caches() {
if (!track) { if (!track) {
Ref<Resource> resource; Ref<Resource> resource;
Vector<StringName> leftover_path; Vector<StringName> 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) { 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; continue;
} }
@ -657,7 +670,7 @@ bool AnimationMixer::_update_caches() {
case Animation::TYPE_VALUE: { case Animation::TYPE_VALUE: {
// If a value track without a key is cached first, the initial value cannot be determined. // 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. // 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); TrackCacheValue *track_value = memnew(TrackCacheValue);
@ -698,7 +711,7 @@ bool AnimationMixer::_update_caches() {
Node3D *node_3d = Object::cast_to<Node3D>(child); Node3D *node_3d = Object::cast_to<Node3D>(child);
if (!node_3d) { 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; continue;
} }
@ -764,20 +777,20 @@ bool AnimationMixer::_update_caches() {
case Animation::TYPE_BLEND_SHAPE: { case Animation::TYPE_BLEND_SHAPE: {
#ifndef _3D_DISABLED #ifndef _3D_DISABLED
if (path.get_subname_count() != 1) { 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; continue;
} }
MeshInstance3D *mesh_3d = Object::cast_to<MeshInstance3D>(child); MeshInstance3D *mesh_3d = Object::cast_to<MeshInstance3D>(child);
if (!mesh_3d) { 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; continue;
} }
StringName blend_shape_name = path.get_subname(0); StringName blend_shape_name = path.get_subname(0);
int blend_shape_idx = mesh_3d->find_blend_shape_by_name(blend_shape_name); int blend_shape_idx = mesh_3d->find_blend_shape_by_name(blend_shape_name);
if (blend_shape_idx == -1) { 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; continue;
} }
@ -855,7 +868,6 @@ bool AnimationMixer::_update_caches() {
} else if (track_cache_type == Animation::TYPE_VALUE) { } else if (track_cache_type == Animation::TYPE_VALUE) {
// If it has at least one angle interpolation, it also uses angle interpolation for blending. // If it has at least one angle interpolation, it also uses angle interpolation for blending.
TrackCacheValue *track_value = static_cast<TrackCacheValue *>(track); TrackCacheValue *track_value = static_cast<TrackCacheValue *>(track);
bool was_continuous = track_value->is_continuous;
bool was_using_angle = track_value->is_using_angle; bool was_using_angle = track_value->is_using_angle;
if (track_src_type == Animation::TYPE_VALUE) { if (track_src_type == Animation::TYPE_VALUE) {
@ -868,23 +880,17 @@ bool AnimationMixer::_update_caches() {
// TODO: Currently, misc type cannot be blended. // 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. // 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. // Then, it should provide the correct precedence value.
bool skip_update_mode_warning = false;
if (track_value->is_continuous) { if (track_value->is_continuous) {
if (!Animation::is_variant_interpolatable(track_value->init_value)) { 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."); 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; track_value->is_continuous = false;
skip_update_mode_warning = true;
} }
if (track_value->init_value.is_string()) { 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."); WARN_PRINT_ONCE_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' blends String types. This is an experimental algorithm.");
} }
} }
if (check_angle_interpolation && (was_using_angle != track_value->is_using_angle)) {
if (!skip_update_mode_warning && was_continuous != track_value->is_continuous) { 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.");
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.");
} }
} }