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.
This commit is contained in:
Pedro J. Estébanez 2020-12-20 11:46:44 +01:00
parent 1f52782bbb
commit b7367ac426
12 changed files with 300 additions and 81 deletions

View file

@ -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) {

View file

@ -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>

View file

@ -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;
}
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::FLOAT:
case Variant::VECTOR2:
case Variant::VECTOR3:
case Variant::QUAT:
case Variant::PLANE:
case Variant::COLOR: {
// Valid.
} break;
default: {
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].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;
}
}
}
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) {
@ -5377,6 +5459,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);
@ -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;

View file

@ -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;

View file

@ -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()) {

View file

@ -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));

View file

@ -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()) {

View file

@ -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;

View file

@ -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;

View file

@ -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();

View file

@ -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;

View file

@ -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");