summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPedro J. Estébanez <pedrojrulez@gmail.com>2020-12-20 11:46:44 +0100
committerPedro J. Estébanez <pedrojrulez@gmail.com>2020-12-20 12:45:08 +0100
commitb7367ac426659e02facc7c828e57aa687cce957c (patch)
tree09fe174ea963c2745f8b268454acb5a39dab7de9
parent1f52782bbb417e559be9317cd56acb37b6d15a9a (diff)
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.
-rw-r--r--core/object/undo_redo.cpp2
-rw-r--r--doc/classes/AnimationPlayer.xml4
-rw-r--r--editor/animation_track_editor.cpp186
-rw-r--r--editor/animation_track_editor.h19
-rw-r--r--editor/editor_node.cpp18
-rw-r--r--editor/editor_settings.cpp2
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp33
-rw-r--r--editor/plugins/animation_player_editor_plugin.h2
-rw-r--r--scene/animation/animation_player.cpp84
-rw-r--r--scene/animation/animation_player.h19
-rw-r--r--scene/resources/animation.cpp2
-rw-r--r--scene/resources/animation.h2
12 files changed, 296 insertions, 77 deletions
diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp
index c5b66cda8c..65bae6f6e8 100644
--- a/core/object/undo_redo.cpp
+++ b/core/object/undo_redo.cpp
@@ -30,8 +30,8 @@
#include "undo_redo.h"
+#include "core/io/resource.h"
#include "core/os/os.h"
-#include "core/resource.h"
void UndoRedo::_discard_redo() {
if (current_action == actions.size() - 1) {
diff --git a/doc/classes/AnimationPlayer.xml b/doc/classes/AnimationPlayer.xml
index de2087d930..bebff61671 100644
--- a/doc/classes/AnimationPlayer.xml
+++ b/doc/classes/AnimationPlayer.xml
@@ -260,6 +260,10 @@
<member name="playback_speed" type="float" setter="set_speed_scale" getter="get_speed_scale" default="1.0">
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.
</member>
+ <member name="reset_on_save" type="bool" setter="set_reset_on_save_enabled" getter="is_reset_on_save_enabled" default="true">
+ 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.
+ </member>
<member name="root_node" type="NodePath" setter="set_root" getter="get_root" default="NodePath(&quot;..&quot;)">
The node from which node path references will travel.
</member>
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 492a9f2a2f..ec411c6415 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -37,6 +37,7 @@
#include "editor/plugins/animation_player_editor_plugin.h"
#include "editor_node.h"
#include "editor_scale.h"
+#include "scene/animation/animation_player.h"
#include "scene/main/window.h"
#include "servers/audio/audio_stream.h"
@@ -3299,6 +3300,19 @@ 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:
+ [[fallthrough]];
+ case Animation::TYPE_BEZIER:
+ [[fallthrough]];
+ 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
@@ -3319,40 +3333,58 @@ 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;
- }
+ 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].track_idx == -1) {
+ ++num_tracks;
+ }
- if (insert_data[i].type != Animation::TYPE_VALUE) {
- continue;
- }
+ if (insert_data[i].type != Animation::TYPE_VALUE) {
+ continue;
+ }
- switch (insert_data[i].value.get_type()) {
- case Variant::INT:
- case Variant::FLOAT:
- case Variant::VECTOR2:
- case Variant::VECTOR3:
- case Variant::QUAT:
- case Variant::PLANE:
- case Variant::COLOR: {
- // Valid.
- } break;
- default: {
- all_bezier = false;
- }
+ switch (insert_data[i].value.get_type()) {
+ case Variant::INT:
+ case Variant::FLOAT:
+ 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))) {
+ //potential new key, does not exist
if (num_tracks == 1) {
insert_confirm_text->set_text(vformat(TTR("Create new track for %s and insert key?"), p_id.query));
} else {
@@ -3360,23 +3392,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_button()->set_text(TTR("Create"));
insert_confirm->popup_centered();
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;
@@ -3385,13 +3420,18 @@ void AnimationTrackEditor::_insert_delay() {
undo_redo->create_action(TTR("Anim Insert"));
- int last_track = animation->get_track_count();
+ Ref<Animation> 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, p_create_beziers);
insert_data.pop_front();
}
@@ -3682,12 +3722,34 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari
}
}
+Ref<Animation> AnimationTrackEditor::_create_and_get_reset_animation() {
+ AnimationPlayer *player = AnimationPlayerEditor::singleton->get_player();
+ if (player->has_animation("RESET")) {
+ return player->get_animation("RESET");
+ } else {
+ Ref<Animation> 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<Animation> 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, insert_confirm_bezier->is_pressed());
insert_data.pop_front();
}
@@ -3807,11 +3869,7 @@ static Vector<String> _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, bool p_create_beziers) {
bool created = false;
if (p_id.track_idx < 0) {
if (p_create_beziers) {
@@ -3823,10 +3881,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, false);
}
- return p_last_track + subindices.size();
+ return p_next_tracks;
}
}
created = true;
@@ -3863,7 +3921,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);
@@ -3915,7 +3973,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);
@@ -3926,9 +3984,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 = AnimationPlayerEditor::singleton->get_player()->get_animation("RESET").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) {
@@ -4224,6 +4300,7 @@ void AnimationTrackEditor::_notification(int p_what) {
selected_filter->set_icon(get_theme_icon("AnimationFilter", "EditorIcons"));
imported_anim_warning->set_icon(get_theme_icon("NodeWarning", "EditorIcons"));
main_panel->add_theme_style_override("panel", get_theme_stylebox("bg", "Tree"));
+ edit->get_popup()->set_item_icon(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), get_theme_icon("Reload", "EditorIcons"));
}
if (p_what == NOTIFICATION_READY) {
@@ -5056,6 +5133,11 @@ void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) {
}
}
+void AnimationTrackEditor::_edit_menu_about_to_popup() {
+ 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) {
@@ -5378,6 +5460,10 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
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);
@@ -5710,10 +5796,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", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed));
+ edit->get_popup()->connect("about_to_popup", callable_mp(this, &AnimationTrackEditor::_edit_menu_about_to_popup));
pick_track = memnew(SceneTreeDialog);
add_child(pick_track);
@@ -5739,9 +5828,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 3cb31fe21e..7006959187 100644
--- a/editor/animation_track_editor.h
+++ b/editor/animation_track_editor.h
@@ -47,6 +47,8 @@
#include "scene/resources/animation.h"
#include "scene_tree_editor.h"
+class AnimationPlayer;
+
class AnimationTimelineEdit : public Range {
GDCLASS(AnimationTimelineEdit, Range);
@@ -285,6 +287,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,
@@ -361,6 +364,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;
@@ -369,9 +373,19 @@ class AnimationTrackEditor : public VBoxContainer {
uint64_t insert_frame;
void _query_insert(const InsertData &p_id);
+ Ref<Animation> _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, bool p_create_beziers);
+ void _insert_delay(bool p_create_reset, bool p_create_beziers);
void _root_removed(Node *p_root);
@@ -447,6 +461,7 @@ class AnimationTrackEditor : public VBoxContainer {
void _select_all_tracks_for_copy();
+ void _edit_menu_about_to_popup();
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 dfe5d64784..a8dc14427d 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -1419,6 +1419,17 @@ int EditorNode::_save_external_resources() {
return saved;
}
+static void _reset_animation_players(Node *p_node, List<Ref<AnimatedValuesBackup>> *r_anim_backups) {
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(p_node->get_child(i));
+ if (player && player->is_reset_on_save_enabled() && player->can_apply_reset()) {
+ Ref<AnimatedValuesBackup> 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);
@@ -1433,6 +1444,8 @@ void EditorNode::_save_scene(String p_file, int idx) {
}
editor_data.apply_changes_in_editors();
+ List<Ref<AnimatedValuesBackup>> anim_backups;
+ _reset_animation_players(scene, &anim_backups);
_save_default_environment();
_set_scene_metadata(p_file, idx);
@@ -1480,6 +1493,11 @@ void EditorNode::_save_scene(String p_file, int idx) {
_save_external_resources();
editor_data.save_editor_external_data();
+
+ for (List<Ref<AnimatedValuesBackup>>::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 e88f27c857..73a191d317 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -628,6 +628,8 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> 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 7e376eee57..4b97dacbc1 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -116,6 +116,19 @@ void AnimationPlayerEditor::_notification(int p_what) {
play_bw_from->set_icon(get_theme_icon("PlayBackwards", "EditorIcons"));
autoplay_icon = get_theme_icon("AutoPlay", "EditorIcons");
+ reset_icon = get_theme_icon("Reload", "EditorIcons");
+ {
+ Ref<Image> autoplay_img = autoplay_icon->get_data();
+ Ref<Image> reset_img = reset_icon->get_data();
+ Ref<Image> 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));
+ autoplay_reset_icon.instance();
+ autoplay_reset_icon->create_from_image(autoplay_reset_img);
+ }
stop->set_icon(get_theme_icon("Stop", "EditorIcons"));
onion_toggle->set_icon(get_theme_icon("Onion", "EditorIcons"));
@@ -817,11 +830,17 @@ void AnimationPlayerEditor::_update_player() {
int active_idx = -1;
for (List<StringName>::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<Texture2D> 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;
@@ -1375,7 +1394,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
}
// Backup current animation state.
- AnimatedValuesBackup values_backup = player->backup_animated_values();
+ Ref<AnimatedValuesBackup> values_backup = player->backup_animated_values();
float cpos = player->get_current_animation_position();
// Render every past/future step with the capture shader.
@@ -1405,7 +1424,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
if (valid) {
player->seek(pos, true);
get_tree()->flush_transform_notifications(); // Needed for transforms of Node3Ds.
- values_backup.update_skeletons(); // Needed for Skeletons (2D & 3D).
+ values_backup->update_skeletons(); // Needed for Skeletons (2D & 3D).
RS::get_singleton()->viewport_set_active(onion.captures[cidx], true);
RS::get_singleton()->viewport_set_parent_viewport(root_vp, onion.captures[cidx]);
@@ -1425,7 +1444,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 (Node3DEditor::get_singleton()->is_visible()) {
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index 17e554ee0d..ab3feb115f 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<Texture2D> autoplay_icon;
+ Ref<Texture2D> reset_icon;
+ Ref<ImageTexture> autoplay_reset_icon;
bool last_active;
float timeline_position;
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index e9e17148d6..159ccae130 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<Skeleton3D>(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) {
@@ -1379,6 +1395,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;
@@ -1473,15 +1497,15 @@ void AnimationPlayer::get_argument_options(const StringName &p_function, int p_i
}
#ifdef TOOLS_ENABLED
-AnimatedValuesBackup AnimationPlayer::backup_animated_values() {
+Ref<AnimatedValuesBackup> AnimationPlayer::backup_animated_values() {
+ Ref<AnimatedValuesBackup> backup;
if (!playback.current.from) {
- return AnimatedValuesBackup();
+ return backup;
}
_ensure_node_caches(playback.current.from);
- AnimatedValuesBackup backup;
-
+ backup.instance();
for (int i = 0; i < playback.current.from->node_cache.size(); i++) {
TrackNodeCache *nc = playback.current.from->node_cache[i];
if (!nc) {
@@ -1497,7 +1521,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;
@@ -1505,7 +1529,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<StringName, TrackNodeCache::PropertyAnim>::Element *E = nc->property_anim.front(); E; E = E->next()) {
AnimatedValuesBackup::Entry entry;
@@ -1515,7 +1539,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);
}
}
}
@@ -1525,15 +1549,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<Skeleton3D>(entry->object)->set_bone_pose(entry->bone_idx, entry->value);
- }
+Ref<AnimatedValuesBackup> AnimationPlayer::apply_reset(bool p_user_initiated) {
+ ERR_FAIL_COND_V(!can_apply_reset(), Ref<AnimatedValuesBackup>());
+
+ Ref<Animation> reset_anim = animation_set["RESET"].animation;
+ ERR_FAIL_COND_V(reset_anim.is_null(), Ref<AnimatedValuesBackup>());
+
+ Node *root_node = get_node_or_null(root);
+ ERR_FAIL_COND_V(!root_node, Ref<AnimatedValuesBackup>());
+
+ AnimationPlayer *aux_player = memnew(AnimationPlayer);
+ EditorNode::get_singleton()->add_child(aux_player);
+ aux_player->set_root(aux_player->get_path_to(root_node));
+ aux_player->add_animation("RESET", reset_anim);
+ aux_player->set_assigned_animation("RESET");
+ Ref<AnimatedValuesBackup> old_values = aux_player->backup_animated_values();
+ aux_player->seek(0.0f, true);
+ aux_player->queue_delete();
+
+ if (p_user_initiated) {
+ Ref<AnimatedValuesBackup> 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
@@ -1577,6 +1626,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);
@@ -1600,6 +1652,7 @@ void AnimationPlayer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "current_animation", PROPERTY_HINT_ENUM, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ANIMATE_AS_TRIGGER), "set_current_animation", "get_current_animation");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "assigned_animation", PROPERTY_HINT_NONE, "", 0), "set_assigned_animation", "get_assigned_animation");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "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::FLOAT, "current_animation_length", PROPERTY_HINT_NONE, "", 0), "", "get_current_animation_length");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "current_animation_position", PROPERTY_HINT_NONE, "", 0), "", "get_current_animation_position");
@@ -1631,6 +1684,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 dbce5643c7..7f0d5630e1 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<StringName> 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
@@ -215,6 +220,7 @@ private:
bool end_notify;
String autoplay;
+ bool reset_on_save;
AnimationProcessMode animation_process_mode;
AnimationMethodCallMode method_call_mode;
bool processing;
@@ -304,6 +310,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;
@@ -325,9 +334,9 @@ public:
void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
#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<AnimatedValuesBackup> backup_animated_values();
+ Ref<AnimatedValuesBackup> 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 a7a02361a9..f4670ca850 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -33,8 +33,6 @@
#include "core/math/geometry_3d.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 060d1fe2d9..650a54ebfc 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -33,6 +33,8 @@
#include "core/io/resource.h"
+#define ANIM_MIN_LENGTH 0.001
+
class Animation : public Resource {
GDCLASS(Animation, Resource);
RES_BASE_EXTENSION("anim");