From 90f13520ddb75a7ae64c35d61dc4b71ef9e4e41d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20J=2E=20Est=C3=A9banez?= Date: Tue, 27 Oct 2020 01:44:30 +0100 Subject: [PATCH 1/2] Extend UndoRedo handling of Resource to every Reference --- core/undo_redo.cpp | 25 +++++++++++++------------ core/undo_redo.h | 5 ++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/core/undo_redo.cpp b/core/undo_redo.cpp index e1beea1a15..013a6465a7 100644 --- a/core/undo_redo.cpp +++ b/core/undo_redo.cpp @@ -31,6 +31,7 @@ #include "undo_redo.h" #include "core/os/os.h" +#include "core/resource.h" void UndoRedo::_discard_redo() { if (current_action == actions.size() - 1) { @@ -104,8 +105,8 @@ void UndoRedo::add_do_method(Object *p_object, const String &p_method, VARIANT_A ERR_FAIL_COND((current_action + 1) >= actions.size()); Operation do_op; do_op.object = p_object->get_instance_id(); - if (Object::cast_to(p_object)) { - do_op.resref = Ref(Object::cast_to(p_object)); + if (Object::cast_to(p_object)) { + do_op.ref = Ref(Object::cast_to(p_object)); } do_op.type = Operation::TYPE_METHOD; @@ -130,8 +131,8 @@ void UndoRedo::add_undo_method(Object *p_object, const String &p_method, VARIANT Operation undo_op; undo_op.object = p_object->get_instance_id(); - if (Object::cast_to(p_object)) { - undo_op.resref = Ref(Object::cast_to(p_object)); + if (Object::cast_to(p_object)) { + undo_op.ref = Ref(Object::cast_to(p_object)); } undo_op.type = Operation::TYPE_METHOD; @@ -148,8 +149,8 @@ void UndoRedo::add_do_property(Object *p_object, const String &p_property, const ERR_FAIL_COND((current_action + 1) >= actions.size()); Operation do_op; do_op.object = p_object->get_instance_id(); - if (Object::cast_to(p_object)) { - do_op.resref = Ref(Object::cast_to(p_object)); + if (Object::cast_to(p_object)) { + do_op.ref = Ref(Object::cast_to(p_object)); } do_op.type = Operation::TYPE_PROPERTY; @@ -169,8 +170,8 @@ void UndoRedo::add_undo_property(Object *p_object, const String &p_property, con Operation undo_op; undo_op.object = p_object->get_instance_id(); - if (Object::cast_to(p_object)) { - undo_op.resref = Ref(Object::cast_to(p_object)); + if (Object::cast_to(p_object)) { + undo_op.ref = Ref(Object::cast_to(p_object)); } undo_op.type = Operation::TYPE_PROPERTY; @@ -184,8 +185,8 @@ void UndoRedo::add_do_reference(Object *p_object) { ERR_FAIL_COND((current_action + 1) >= actions.size()); Operation do_op; do_op.object = p_object->get_instance_id(); - if (Object::cast_to(p_object)) { - do_op.resref = Ref(Object::cast_to(p_object)); + if (Object::cast_to(p_object)) { + do_op.ref = Ref(Object::cast_to(p_object)); } do_op.type = Operation::TYPE_REFERENCE; @@ -203,8 +204,8 @@ void UndoRedo::add_undo_reference(Object *p_object) { Operation undo_op; undo_op.object = p_object->get_instance_id(); - if (Object::cast_to(p_object)) { - undo_op.resref = Ref(Object::cast_to(p_object)); + if (Object::cast_to(p_object)) { + undo_op.ref = Ref(Object::cast_to(p_object)); } undo_op.type = Operation::TYPE_REFERENCE; diff --git a/core/undo_redo.h b/core/undo_redo.h index 93791f6e57..c71a51848b 100644 --- a/core/undo_redo.h +++ b/core/undo_redo.h @@ -31,8 +31,7 @@ #ifndef UNDO_REDO_H #define UNDO_REDO_H -#include "core/object.h" -#include "core/resource.h" +#include "core/reference.h" class UndoRedo : public Object { GDCLASS(UndoRedo, Object); @@ -61,7 +60,7 @@ private: }; Type type; - Ref resref; + Ref ref; ObjectID object; String name; Variant args[VARIANT_ARG_MAX]; From 4da9a501f67247b227330f666cbb1690a3ab644e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20J=2E=20Est=C3=A9banez?= Date: Sun, 20 Dec 2020 11:46:44 +0100 Subject: [PATCH 2/2] Add animation reset track feature As a bonus, to have consistency between use Beziers and create insert tracks, use Beziers also gets a default via editor settings that is used when the confirmation dialog is disabled, instead of just falling back to creating non-Bezier tracks. --- doc/classes/AnimationPlayer.xml | 4 + editor/animation_track_editor.cpp | 190 +++++++++++++----- editor/animation_track_editor.h | 17 +- editor/editor_node.cpp | 18 ++ editor/editor_settings.cpp | 2 + .../animation_player_editor_plugin.cpp | 37 +++- .../plugins/animation_player_editor_plugin.h | 2 + scene/animation/animation_player.cpp | 90 +++++++-- scene/animation/animation_player.h | 21 +- scene/resources/animation.cpp | 2 - scene/resources/animation.h | 2 + 11 files changed, 301 insertions(+), 84 deletions(-) diff --git a/doc/classes/AnimationPlayer.xml b/doc/classes/AnimationPlayer.xml index c0f6890fd7..a884f1a84d 100644 --- a/doc/classes/AnimationPlayer.xml +++ b/doc/classes/AnimationPlayer.xml @@ -260,6 +260,10 @@ The speed scaling ratio. For instance, if this value is 1, then the animation plays at normal speed. If it's 0.5, then it plays at half speed. If it's 2, then it plays at double speed. + + This is used by the editor. If set to [code]true[/code], the scene will be saved with the effects of the reset animation applied (as if it had been seeked to time 0), then reverted after saving. + In other words, the saved scene file will contain the "default pose", as defined by the reset animation, if any, with the editor keeping the values that the nodes had before saving. + The node from which node path references will travel. diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 339ffa1766..dc179c41b6 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -3306,6 +3306,17 @@ void AnimationTrackEditor::set_anim_pos(float p_pos) { bezier_edit->set_play_position(p_pos); } +static bool track_type_is_resettable(Animation::TrackType p_type) { + switch (p_type) { + case Animation::TYPE_VALUE: + case Animation::TYPE_BEZIER: + case Animation::TYPE_TRANSFORM: + return true; + default: + return false; + } +} + void AnimationTrackEditor::_query_insert(const InsertData &p_id) { if (insert_frame != Engine::get_singleton()->get_frames_drawn()) { //clear insert list for the frame if frame changed @@ -3326,40 +3337,57 @@ void AnimationTrackEditor::_query_insert(const InsertData &p_id) { insert_data.push_back(p_id); + bool reset_allowed = true; + AnimationPlayer *player = AnimationPlayerEditor::singleton->get_player(); + if (player->has_animation("RESET") && player->get_animation("RESET") == animation) { + // Avoid messing with the reset animation itself + reset_allowed = false; + } else { + bool some_resettable = false; + for (int i = 0; i < insert_data.size(); i++) { + if (track_type_is_resettable(insert_data[i].type)) { + some_resettable = true; + break; + } + } + if (!some_resettable) { + reset_allowed = false; + } + } + if (p_id.track_idx == -1) { - if (bool(EDITOR_DEF("editors/animation/confirm_insert_track", true))) { - //potential new key, does not exist - int num_tracks = 0; - bool all_bezier = true; - for (int i = 0; i < insert_data.size(); i++) { - if (insert_data[i].type != Animation::TYPE_VALUE && insert_data[i].type != Animation::TYPE_BEZIER) { - all_bezier = false; - } + //potential new key, does not exist + int num_tracks = 0; + bool all_bezier = true; + for (int i = 0; i < insert_data.size(); i++) { + if (insert_data[i].type != Animation::TYPE_VALUE && insert_data[i].type != Animation::TYPE_BEZIER) + all_bezier = false; - if (insert_data[i].track_idx == -1) { - ++num_tracks; - } - - if (insert_data[i].type != Animation::TYPE_VALUE) { - continue; - } - - switch (insert_data[i].value.get_type()) { - case Variant::INT: - case Variant::REAL: - case Variant::VECTOR2: - case Variant::VECTOR3: - case Variant::QUAT: - case Variant::PLANE: - case Variant::COLOR: { - // Valid. - } break; - default: { - all_bezier = false; - } - } + if (insert_data[i].track_idx == -1) { + ++num_tracks; } + if (insert_data[i].type != Animation::TYPE_VALUE) { + continue; + } + + switch (insert_data[i].value.get_type()) { + case Variant::INT: + case Variant::REAL: + case Variant::VECTOR2: + case Variant::VECTOR3: + case Variant::QUAT: + case Variant::PLANE: + case Variant::COLOR: { + // Valid. + } break; + default: { + all_bezier = false; + } + } + } + + if (bool(EDITOR_DEF("editors/animation/confirm_insert_track", true))) { if (num_tracks == 1) { insert_confirm_text->set_text(vformat(TTR("Create NEW track for %s and insert key?"), p_id.query)); } else { @@ -3367,23 +3395,26 @@ void AnimationTrackEditor::_query_insert(const InsertData &p_id) { } insert_confirm_bezier->set_visible(all_bezier); + insert_confirm_reset->set_visible(reset_allowed); + insert_confirm->get_ok()->set_text(TTR("Create")); insert_confirm->popup_centered_minsize(); insert_query = true; } else { - call_deferred("_insert_delay"); + call_deferred("_insert_delay", reset_allowed && EDITOR_GET("editors/animation/default_create_reset_tracks"), all_bezier && EDITOR_GET("editors/animation/default_create_bezier_tracks")); insert_queue = true; } } else { if (!insert_query && !insert_queue) { - call_deferred("_insert_delay"); + // Create Beziers wouldn't make sense in this case, where no tracks are being created + call_deferred("_insert_delay", reset_allowed && EDITOR_GET("editors/animation/default_create_reset_tracks"), false); insert_queue = true; } } } -void AnimationTrackEditor::_insert_delay() { +void AnimationTrackEditor::_insert_delay(bool p_create_reset, bool p_create_beziers) { if (insert_query) { //discard since it's entered into query mode insert_queue = false; @@ -3392,13 +3423,18 @@ void AnimationTrackEditor::_insert_delay() { undo_redo->create_action(TTR("Anim Insert")); - int last_track = animation->get_track_count(); + Ref reset_anim; + if (p_create_reset) { + reset_anim = _create_and_get_reset_animation(); + } + + TrackIndices next_tracks(animation.ptr(), reset_anim.ptr()); bool advance = false; while (insert_data.size()) { if (insert_data.front()->get().advance) { advance = true; } - last_track = _confirm_insert(insert_data.front()->get(), last_track); + next_tracks = _confirm_insert(insert_data.front()->get(), next_tracks, p_create_reset, reset_anim, p_create_beziers); insert_data.pop_front(); } @@ -3689,12 +3725,34 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari } } +Ref AnimationTrackEditor::_create_and_get_reset_animation() { + AnimationPlayer *player = AnimationPlayerEditor::singleton->get_player(); + if (player->has_animation("RESET")) { + return player->get_animation("RESET"); + } else { + Ref reset_anim; + reset_anim.instance(); + reset_anim->set_length(ANIM_MIN_LENGTH); + undo_redo->add_do_method(player, "add_animation", "RESET", reset_anim); + undo_redo->add_do_method(AnimationPlayerEditor::singleton, "_animation_player_changed", player); + undo_redo->add_undo_method(player, "remove_animation", "RESET"); + undo_redo->add_undo_method(AnimationPlayerEditor::singleton, "_animation_player_changed", player); + return reset_anim; + } +} + void AnimationTrackEditor::_confirm_insert_list() { undo_redo->create_action(TTR("Anim Create & Insert")); - int last_track = animation->get_track_count(); + bool create_reset = insert_confirm_reset->is_visible() && insert_confirm_reset->is_pressed(); + Ref reset_anim; + if (create_reset) { + reset_anim = _create_and_get_reset_animation(); + } + + TrackIndices next_tracks(animation.ptr(), reset_anim.ptr()); while (insert_data.size()) { - last_track = _confirm_insert(insert_data.front()->get(), last_track, insert_confirm_bezier->is_pressed()); + next_tracks = _confirm_insert(insert_data.front()->get(), next_tracks, create_reset, reset_anim, insert_confirm_bezier->is_pressed()); insert_data.pop_front(); } @@ -3813,11 +3871,7 @@ static Vector _get_bezier_subindices_for_type(Variant::Type p_type, bool return subindices; } -int AnimationTrackEditor::_confirm_insert(InsertData p_id, int p_last_track, bool p_create_beziers) { - if (p_last_track == -1) { - p_last_track = animation->get_track_count(); - } - +AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertData p_id, TrackIndices p_next_tracks, bool p_create_reset, Ref p_reset_anim, bool p_create_beziers) { bool created = false; if (p_id.track_idx < 0) { if (p_create_beziers) { @@ -3829,10 +3883,10 @@ int AnimationTrackEditor::_confirm_insert(InsertData p_id, int p_last_track, boo id.type = Animation::TYPE_BEZIER; id.value = p_id.value.get(subindices[i].substr(1, subindices[i].length())); id.path = String(p_id.path) + subindices[i]; - _confirm_insert(id, p_last_track + i); + p_next_tracks = _confirm_insert(id, p_next_tracks, p_create_reset, p_reset_anim, false); } - return p_last_track + subindices.size(); + return p_next_tracks; } } created = true; @@ -3869,7 +3923,7 @@ int AnimationTrackEditor::_confirm_insert(InsertData p_id, int p_last_track, boo } } - p_id.track_idx = p_last_track; + p_id.track_idx = p_next_tracks.normal; undo_redo->add_do_method(animation.ptr(), "add_track", p_id.type); undo_redo->add_do_method(animation.ptr(), "track_set_path", p_id.track_idx, p_id.path); @@ -3921,7 +3975,7 @@ int AnimationTrackEditor::_confirm_insert(InsertData p_id, int p_last_track, boo // Just remove the track. undo_redo->add_undo_method(this, "_clear_selection", false); undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count()); - p_last_track++; + p_next_tracks.normal++; } else { undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_id.track_idx, time); int existing = animation->track_find_key(p_id.track_idx, time, true); @@ -3932,9 +3986,27 @@ int AnimationTrackEditor::_confirm_insert(InsertData p_id, int p_last_track, boo } } + if (p_create_reset && track_type_is_resettable(p_id.type)) { + bool create_reset_track = true; + Animation *reset_anim = p_reset_anim.ptr(); + for (int i = 0; i < reset_anim->get_track_count(); i++) { + if (reset_anim->track_get_path(i) == p_id.path) { + create_reset_track = false; + break; + } + } + if (create_reset_track) { + undo_redo->add_do_method(reset_anim, "add_track", p_id.type); + undo_redo->add_do_method(reset_anim, "track_set_path", p_next_tracks.reset, p_id.path); + undo_redo->add_do_method(reset_anim, "track_insert_key", p_next_tracks.reset, 0.0f, value); + undo_redo->add_undo_method(reset_anim, "remove_track", reset_anim->get_track_count()); + p_next_tracks.reset++; + } + } + undo_redo->commit_action(); - return p_last_track; + return p_next_tracks; } void AnimationTrackEditor::show_select_node_warning(bool p_show) { @@ -4229,6 +4301,7 @@ void AnimationTrackEditor::_notification(int p_what) { selected_filter->set_icon(get_icon("AnimationFilter", "EditorIcons")); imported_anim_warning->set_icon(get_icon("NodeWarning", "EditorIcons")); main_panel->add_style_override("panel", get_stylebox("bg", "Tree")); + edit->get_popup()->set_item_icon(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), get_icon("Reload", "EditorIcons")); } if (p_what == NOTIFICATION_READY) { @@ -5062,6 +5135,12 @@ void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) { _update_key_edit(); } } + +void AnimationTrackEditor::_edit_menu_about_to_show() { + AnimationPlayer *player = AnimationPlayerEditor::singleton->get_player(); + edit->get_popup()->set_item_disabled(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), !player->can_apply_reset()); +} + void AnimationTrackEditor::_edit_menu_pressed(int p_option) { last_menu_track_opt = p_option; switch (p_option) { @@ -5383,6 +5462,10 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { set_anim_pos(pos); emit_signal("timeline_changed", pos, true); + } break; + case EDIT_APPLY_RESET: { + AnimationPlayerEditor::singleton->get_player()->apply_reset(true); + } break; case EDIT_OPTIMIZE_ANIMATION: { optimize_dialog->popup_centered(Size2(250, 180) * EDSCALE); @@ -5573,6 +5656,7 @@ void AnimationTrackEditor::_bind_methods() { ClassDB::bind_method("_bezier_edit", &AnimationTrackEditor::_bezier_edit); ClassDB::bind_method("_cancel_bezier_edit", &AnimationTrackEditor::_cancel_bezier_edit); ClassDB::bind_method("_edit_menu_pressed", &AnimationTrackEditor::_edit_menu_pressed); + ClassDB::bind_method("_edit_menu_about_to_show", &AnimationTrackEditor::_edit_menu_about_to_show); ClassDB::bind_method("_view_group_toggle", &AnimationTrackEditor::_view_group_toggle); ClassDB::bind_method("_selection_changed", &AnimationTrackEditor::_selection_changed); ClassDB::bind_method("_snap_mode_changed", &AnimationTrackEditor::_snap_mode_changed); @@ -5744,10 +5828,13 @@ AnimationTrackEditor::AnimationTrackEditor() { edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/goto_next_step", TTR("Go to Next Step"), KEY_MASK_CMD | KEY_RIGHT), EDIT_GOTO_NEXT_STEP); edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/goto_prev_step", TTR("Go to Previous Step"), KEY_MASK_CMD | KEY_LEFT), EDIT_GOTO_PREV_STEP); edit->get_popup()->add_separator(); + edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/apply_reset", TTR("Apply Reset")), EDIT_APPLY_RESET); + edit->get_popup()->add_separator(); edit->get_popup()->add_item(TTR("Optimize Animation"), EDIT_OPTIMIZE_ANIMATION); edit->get_popup()->add_item(TTR("Clean-Up Animation"), EDIT_CLEAN_UP_ANIMATION); edit->get_popup()->connect("id_pressed", this, "_edit_menu_pressed"); + edit->get_popup()->connect("about_to_show", this, "_edit_menu_about_to_show"); pick_track = memnew(SceneTreeDialog); add_child(pick_track); @@ -5773,9 +5860,16 @@ AnimationTrackEditor::AnimationTrackEditor() { insert_confirm->add_child(icvb); insert_confirm_text = memnew(Label); icvb->add_child(insert_confirm_text); + HBoxContainer *ichb = memnew(HBoxContainer); + icvb->add_child(ichb); insert_confirm_bezier = memnew(CheckBox); insert_confirm_bezier->set_text(TTR("Use Bezier Curves")); - icvb->add_child(insert_confirm_bezier); + insert_confirm_bezier->set_pressed(EDITOR_GET("editors/animation/default_create_bezier_tracks")); + ichb->add_child(insert_confirm_bezier); + insert_confirm_reset = memnew(CheckBox); + insert_confirm_reset->set_text(TTR("Create RESET Track(s)")); + insert_confirm_reset->set_pressed(EDITOR_GET("editors/animation/default_create_reset_tracks")); + ichb->add_child(insert_confirm_reset); keying = false; moving_selection = false; key_edit = nullptr; diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index bc907d1d36..2af400946f 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -286,6 +286,7 @@ class AnimationTrackEditor : public VBoxContainer { EDIT_DELETE_SELECTION, EDIT_GOTO_NEXT_STEP, EDIT_GOTO_PREV_STEP, + EDIT_APPLY_RESET, EDIT_OPTIMIZE_ANIMATION, EDIT_OPTIMIZE_ANIMATION_CONFIRM, EDIT_CLEAN_UP_ANIMATION, @@ -362,6 +363,7 @@ class AnimationTrackEditor : public VBoxContainer { Label *insert_confirm_text; CheckBox *insert_confirm_bezier; + CheckBox *insert_confirm_reset; ConfirmationDialog *insert_confirm; bool insert_queue; bool inserting; @@ -370,9 +372,19 @@ class AnimationTrackEditor : public VBoxContainer { uint64_t insert_frame; void _query_insert(const InsertData &p_id); + Ref _create_and_get_reset_animation(); void _confirm_insert_list(); - int _confirm_insert(InsertData p_id, int p_last_track, bool p_create_beziers = false); - void _insert_delay(); + struct TrackIndices { + int normal; + int reset; + + TrackIndices(const Animation *p_anim = nullptr, const Animation *p_reset_anim = nullptr) { + normal = p_anim ? p_anim->get_track_count() : 0; + reset = p_reset_anim ? p_reset_anim->get_track_count() : 0; + } + }; + TrackIndices _confirm_insert(InsertData p_id, TrackIndices p_next_tracks, bool p_create_reset, Ref p_reset_anim, bool p_create_beziers); + void _insert_delay(bool p_create_reset, bool p_create_beziers); void _root_removed(Node *p_root); @@ -448,6 +460,7 @@ class AnimationTrackEditor : public VBoxContainer { void _select_all_tracks_for_copy(); + void _edit_menu_about_to_show(); void _edit_menu_pressed(int p_option); int last_menu_track_opt; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 9a4c89486c..3de6651c9b 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1445,6 +1445,17 @@ int EditorNode::_save_external_resources() { return saved; } +static void _reset_animation_players(Node *p_node, List> *r_anim_backups) { + for (int i = 0; i < p_node->get_child_count(); i++) { + AnimationPlayer *player = Object::cast_to(p_node->get_child(i)); + if (player && player->is_reset_on_save_enabled() && player->can_apply_reset()) { + Ref old_values = player->apply_reset(); + r_anim_backups->push_back(old_values); + } + _reset_animation_players(p_node->get_child(i), r_anim_backups); + } +} + void EditorNode::_save_scene(String p_file, int idx) { Node *scene = editor_data.get_edited_scene_root(idx); @@ -1459,6 +1470,8 @@ void EditorNode::_save_scene(String p_file, int idx) { } editor_data.apply_changes_in_editors(); + List> anim_backups; + _reset_animation_players(scene, &anim_backups); _save_default_environment(); _set_scene_metadata(p_file, idx); @@ -1506,6 +1519,11 @@ void EditorNode::_save_scene(String p_file, int idx) { _save_external_resources(); editor_data.save_editor_external_data(); + + for (List>::Element *E = anim_backups.front(); E; E = E->next()) { + E->get()->restore(); + } + if (err == OK) { scene->set_filename(ProjectSettings::get_singleton()->localize_path(p_file)); if (idx < 0 || idx == editor_data.get_edited_scene()) { diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 889546f2fa..8b8dbb3524 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -627,6 +627,8 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { // Animation _initial_set("editors/animation/autorename_animation_tracks", true); _initial_set("editors/animation/confirm_insert_track", true); + _initial_set("editors/animation/default_create_bezier_tracks", false); + _initial_set("editors/animation/default_create_reset_tracks", true); _initial_set("editors/animation/onion_layers_past_color", Color(1, 0, 0)); _initial_set("editors/animation/onion_layers_future_color", Color(0, 1, 0)); diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 3e095ab7e6..926ef76041 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -116,6 +116,21 @@ void AnimationPlayerEditor::_notification(int p_what) { play_bw_from->set_icon(get_icon("PlayBackwards", "EditorIcons")); autoplay_icon = get_icon("AutoPlay", "EditorIcons"); + reset_icon = get_icon("Reload", "EditorIcons"); + { + Ref autoplay_img = autoplay_icon->get_data(); + Ref reset_img = reset_icon->get_data(); + Ref autoplay_reset_img; + Size2 icon_size = Size2(autoplay_img->get_width(), autoplay_img->get_height()); + autoplay_reset_img.instance(); + autoplay_reset_img->create(icon_size.x * 2, icon_size.y, false, autoplay_img->get_format()); + autoplay_reset_img->blit_rect(autoplay_img, Rect2(Point2(), icon_size), Point2()); + autoplay_reset_img->blit_rect(reset_img, Rect2(Point2(), icon_size), Point2(icon_size.x, 0)); + Ref temp_icon; + temp_icon.instance(); + temp_icon->create_from_image(autoplay_reset_img); + autoplay_reset_icon = temp_icon; + } stop->set_icon(get_icon("Stop", "EditorIcons")); onion_toggle->set_icon(get_icon("Onion", "EditorIcons")); @@ -816,11 +831,17 @@ void AnimationPlayerEditor::_update_player() { int active_idx = -1; for (List::Element *E = animlist.front(); E; E = E->next()) { - if (player->get_autoplay() == E->get()) { - animation->add_icon_item(autoplay_icon, E->get()); - } else { - animation->add_item(E->get()); + Ref icon; + if (E->get() == player->get_autoplay()) { + if (E->get() == "RESET") { + icon = autoplay_reset_icon; + } else { + icon = autoplay_icon; + } + } else if (E->get() == "RESET") { + icon = reset_icon; } + animation->add_icon_item(icon, E->get()); if (player->get_assigned_animation() == E->get()) { active_idx = animation->get_item_count() - 1; @@ -1368,7 +1389,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() { } // Backup current animation state. - AnimatedValuesBackup values_backup = player->backup_animated_values(); + Ref values_backup = player->backup_animated_values(); float cpos = player->get_current_animation_position(); // Render every past/future step with the capture shader. @@ -1397,8 +1418,8 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() { onion.captures_valid.write[cidx] = valid; if (valid) { player->seek(pos, true); - get_tree()->flush_transform_notifications(); // Needed for transforms of Spatials. - values_backup.update_skeletons(); // Needed for Skeletons (2D & 3D). + get_tree()->flush_transform_notifications(); // Needed for transforms of Node3Ds. + values_backup->update_skeletons(); // Needed for Skeletons (2D & 3D). VS::get_singleton()->viewport_set_active(onion.captures[cidx], true); VS::get_singleton()->viewport_set_parent_viewport(root_vp, onion.captures[cidx]); @@ -1418,7 +1439,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() { // (Seeking with update=true wouldn't do the trick because the current value of the properties // may not match their value for the current point in the animation). player->seek(cpos, false); - player->restore_animated_values(values_backup); + values_backup->restore(); // Restore state of main editors. if (SpatialEditor::get_singleton()->is_visible()) { diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index adb4b9fd85..d1da881014 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -105,6 +105,8 @@ class AnimationPlayerEditor : public VBoxContainer { Label *name_title; UndoRedo *undo_redo; Ref autoplay_icon; + Ref reset_icon; + Ref autoplay_reset_icon; bool last_active; float timeline_position; diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 6d73b15a3f..590a3cc3ef 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -36,6 +36,7 @@ #include "servers/audio/audio_stream.h" #ifdef TOOLS_ENABLED +#include "editor/editor_node.h" #include "editor/editor_settings.h" #include "scene/2d/skeleton_2d.h" @@ -53,6 +54,21 @@ void AnimatedValuesBackup::update_skeletons() { } } } + +void AnimatedValuesBackup::restore() const { + for (int i = 0; i < entries.size(); i++) { + const AnimatedValuesBackup::Entry *entry = &entries[i]; + if (entry->bone_idx == -1) { + entry->object->set_indexed(entry->subpath, entry->value); + } else { + Object::cast_to(entry->object)->set_bone_pose(entry->bone_idx, entry->value); + } + } +} + +void AnimatedValuesBackup::_bind_methods() { + ClassDB::bind_method(D_METHOD("restore"), &AnimatedValuesBackup::restore); +} #endif bool AnimationPlayer::_set(const StringName &p_name, const Variant &p_value) { @@ -213,13 +229,13 @@ void AnimationPlayer::_notification(int p_what) { } } -void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim) { +void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_override) { // Already cached? if (p_anim->node_cache.size() == p_anim->animation->get_track_count()) { return; } - Node *parent = get_node(root); + Node *parent = p_root_override ? p_root_override : get_node(root); ERR_FAIL_COND(!parent); @@ -1386,6 +1402,14 @@ String AnimationPlayer::get_autoplay() const { return autoplay; } +void AnimationPlayer::set_reset_on_save_enabled(bool p_enabled) { + reset_on_save = p_enabled; +} + +bool AnimationPlayer::is_reset_on_save_enabled() const { + return reset_on_save; +} + void AnimationPlayer::set_animation_process_mode(AnimationProcessMode p_mode) { if (animation_process_mode == p_mode) { return; @@ -1480,15 +1504,15 @@ void AnimationPlayer::get_argument_options(const StringName &p_function, int p_i } #ifdef TOOLS_ENABLED -AnimatedValuesBackup AnimationPlayer::backup_animated_values() { +Ref AnimationPlayer::backup_animated_values(Node *p_root_override) { + Ref backup; if (!playback.current.from) { - return AnimatedValuesBackup(); + return backup; } - _ensure_node_caches(playback.current.from); - - AnimatedValuesBackup backup; + _ensure_node_caches(playback.current.from, p_root_override); + backup.instance(); for (int i = 0; i < playback.current.from->node_cache.size(); i++) { TrackNodeCache *nc = playback.current.from->node_cache[i]; if (!nc) { @@ -1504,7 +1528,7 @@ AnimatedValuesBackup AnimationPlayer::backup_animated_values() { entry.object = nc->skeleton; entry.bone_idx = nc->bone_idx; entry.value = nc->skeleton->get_bone_pose(nc->bone_idx); - backup.entries.push_back(entry); + backup->entries.push_back(entry); } else { if (nc->spatial) { AnimatedValuesBackup::Entry entry; @@ -1512,7 +1536,7 @@ AnimatedValuesBackup AnimationPlayer::backup_animated_values() { entry.subpath.push_back("transform"); entry.value = nc->spatial->get_transform(); entry.bone_idx = -1; - backup.entries.push_back(entry); + backup->entries.push_back(entry); } else { for (Map::Element *E = nc->property_anim.front(); E; E = E->next()) { AnimatedValuesBackup::Entry entry; @@ -1522,7 +1546,7 @@ AnimatedValuesBackup AnimationPlayer::backup_animated_values() { entry.value = E->value().object->get_indexed(E->value().subpath, &valid); entry.bone_idx = -1; if (valid) { - backup.entries.push_back(entry); + backup->entries.push_back(entry); } } } @@ -1532,15 +1556,40 @@ AnimatedValuesBackup AnimationPlayer::backup_animated_values() { return backup; } -void AnimationPlayer::restore_animated_values(const AnimatedValuesBackup &p_backup) { - for (int i = 0; i < p_backup.entries.size(); i++) { - const AnimatedValuesBackup::Entry *entry = &p_backup.entries[i]; - if (entry->bone_idx == -1) { - entry->object->set_indexed(entry->subpath, entry->value); - } else { - Object::cast_to(entry->object)->set_bone_pose(entry->bone_idx, entry->value); - } +Ref AnimationPlayer::apply_reset(bool p_user_initiated) { + ERR_FAIL_COND_V(!can_apply_reset(), Ref()); + + Ref reset_anim = animation_set["RESET"].animation; + + Node *root_node = get_node_or_null(root); + ERR_FAIL_COND_V(!root_node, Ref()); + + AnimationPlayer *aux_player = memnew(AnimationPlayer); + EditorNode::get_singleton()->add_child(aux_player); + aux_player->add_animation("RESET", reset_anim); + aux_player->set_assigned_animation("RESET"); + // Forcing the use of the original root because the scene where original player belongs may be not the active one + Node *root = get_node(get_root()); + Ref old_values = aux_player->backup_animated_values(root); + aux_player->seek(0.0f, true); + aux_player->queue_delete(); + + if (p_user_initiated) { + Ref new_values = aux_player->backup_animated_values(); + old_values->restore(); + + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Anim Apply Reset")); + ur->add_do_method(new_values.ptr(), "restore"); + ur->add_undo_method(old_values.ptr(), "restore"); + ur->commit_action(); } + + return old_values; +} + +bool AnimationPlayer::can_apply_reset() const { + return has_animation("RESET") && playback.assigned != StringName("RESET"); } #endif @@ -1587,6 +1636,9 @@ void AnimationPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_autoplay", "name"), &AnimationPlayer::set_autoplay); ClassDB::bind_method(D_METHOD("get_autoplay"), &AnimationPlayer::get_autoplay); + ClassDB::bind_method(D_METHOD("set_reset_on_save_enabled", "enabled"), &AnimationPlayer::set_reset_on_save_enabled); + ClassDB::bind_method(D_METHOD("is_reset_on_save_enabled"), &AnimationPlayer::is_reset_on_save_enabled); + ClassDB::bind_method(D_METHOD("set_root", "path"), &AnimationPlayer::set_root); ClassDB::bind_method(D_METHOD("get_root"), &AnimationPlayer::get_root); @@ -1610,6 +1662,7 @@ void AnimationPlayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_animation", PROPERTY_HINT_ENUM, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ANIMATE_AS_TRIGGER), "set_current_animation", "get_current_animation"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "assigned_animation", PROPERTY_HINT_NONE, "", 0), "set_assigned_animation", "get_assigned_animation"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "autoplay", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_autoplay", "get_autoplay"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset_on_save", PROPERTY_HINT_NONE, ""), "set_reset_on_save_enabled", "is_reset_on_save_enabled"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "current_animation_length", PROPERTY_HINT_NONE, "", 0), "", "get_current_animation_length"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "current_animation_position", PROPERTY_HINT_NONE, "", 0), "", "get_current_animation_position"); @@ -1641,6 +1694,7 @@ AnimationPlayer::AnimationPlayer() { speed_scale = 1; end_reached = false; end_notify = false; + reset_on_save = true; animation_process_mode = ANIMATION_PROCESS_IDLE; method_call_mode = ANIMATION_METHOD_CALL_DEFERRED; processing = false; diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index f7a6dc66c1..e558efc1b7 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -37,8 +37,9 @@ #include "scene/resources/animation.h" #ifdef TOOLS_ENABLED -// To save/restore animated values -class AnimatedValuesBackup { +class AnimatedValuesBackup : public Reference { + GDCLASS(AnimatedValuesBackup, Reference); + struct Entry { Object *object; Vector subpath; // Unused if bone @@ -49,8 +50,12 @@ class AnimatedValuesBackup { friend class AnimationPlayer; +protected: + static void _bind_methods(); + public: void update_skeletons(); + void restore() const; }; #endif @@ -237,6 +242,7 @@ private: bool end_notify; String autoplay; + bool reset_on_save; AnimationProcessMode animation_process_mode; AnimationMethodCallMode method_call_mode; bool processing; @@ -246,7 +252,7 @@ private: void _animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false); - void _ensure_node_caches(AnimationData *p_anim); + void _ensure_node_caches(AnimationData *p_anim, Node *p_root_override = NULL); void _animation_process_data(PlaybackData &cd, float p_delta, float p_blend, bool p_seeked, bool p_started); void _animation_process2(float p_delta, bool p_started); void _animation_update_transforms(); @@ -326,6 +332,9 @@ public: void set_autoplay(const String &p_name); String get_autoplay() const; + void set_reset_on_save_enabled(bool p_enabled); + bool is_reset_on_save_enabled() const; + void set_animation_process_mode(AnimationProcessMode p_mode); AnimationProcessMode get_animation_process_mode() const; @@ -347,9 +356,9 @@ public: void get_argument_options(const StringName &p_function, int p_idx, List *r_options) const; #ifdef TOOLS_ENABLED - // These may be interesting for games, but are too dangerous for general use - AnimatedValuesBackup backup_animated_values(); - void restore_animated_values(const AnimatedValuesBackup &p_backup); + Ref backup_animated_values(Node *p_root_override = NULL); + Ref apply_reset(bool p_user_initiated = false); + bool can_apply_reset() const; #endif AnimationPlayer(); diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 70bf1ce8b0..df6a3a8887 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -33,8 +33,6 @@ #include "core/math/geometry.h" -#define ANIM_MIN_LENGTH 0.001 - bool Animation::_set(const StringName &p_name, const Variant &p_value) { String name = p_name; diff --git a/scene/resources/animation.h b/scene/resources/animation.h index d0ae706068..108471e06e 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -33,6 +33,8 @@ #include "core/resource.h" +#define ANIM_MIN_LENGTH 0.001 + class Animation : public Resource { GDCLASS(Animation, Resource); RES_BASE_EXTENSION("anim");