Merge pull request #44558 from RandomShaper/reset_track_3.2
[3.x] Add animation reset track feature
This commit is contained in:
commit
4730856264
13 changed files with 316 additions and 99 deletions
|
@ -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<Resource>(p_object)) {
|
||||
do_op.resref = Ref<Resource>(Object::cast_to<Resource>(p_object));
|
||||
if (Object::cast_to<Reference>(p_object)) {
|
||||
do_op.ref = Ref<Reference>(Object::cast_to<Reference>(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<Resource>(p_object)) {
|
||||
undo_op.resref = Ref<Resource>(Object::cast_to<Resource>(p_object));
|
||||
if (Object::cast_to<Reference>(p_object)) {
|
||||
undo_op.ref = Ref<Reference>(Object::cast_to<Reference>(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<Resource>(p_object)) {
|
||||
do_op.resref = Ref<Resource>(Object::cast_to<Resource>(p_object));
|
||||
if (Object::cast_to<Reference>(p_object)) {
|
||||
do_op.ref = Ref<Reference>(Object::cast_to<Reference>(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<Resource>(p_object)) {
|
||||
undo_op.resref = Ref<Resource>(Object::cast_to<Resource>(p_object));
|
||||
if (Object::cast_to<Reference>(p_object)) {
|
||||
undo_op.ref = Ref<Reference>(Object::cast_to<Reference>(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<Resource>(p_object)) {
|
||||
do_op.resref = Ref<Resource>(Object::cast_to<Resource>(p_object));
|
||||
if (Object::cast_to<Reference>(p_object)) {
|
||||
do_op.ref = Ref<Reference>(Object::cast_to<Reference>(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<Resource>(p_object)) {
|
||||
undo_op.resref = Ref<Resource>(Object::cast_to<Resource>(p_object));
|
||||
if (Object::cast_to<Reference>(p_object)) {
|
||||
undo_op.ref = Ref<Reference>(Object::cast_to<Reference>(p_object));
|
||||
}
|
||||
|
||||
undo_op.type = Operation::TYPE_REFERENCE;
|
||||
|
|
|
@ -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<Resource> resref;
|
||||
Ref<Reference> ref;
|
||||
ObjectID object;
|
||||
String name;
|
||||
Variant args[VARIANT_ARG_MAX];
|
||||
|
|
|
@ -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("..")">
|
||||
The node from which node path references will travel.
|
||||
</member>
|
||||
|
|
|
@ -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,15 +3337,31 @@ 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) {
|
||||
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;
|
||||
|
@ -3360,6 +3387,7 @@ void AnimationTrackEditor::_query_insert(const InsertData &p_id) {
|
|||
}
|
||||
}
|
||||
|
||||
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<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, 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<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, reset_anim, insert_confirm_bezier->is_pressed());
|
||||
insert_data.pop_front();
|
||||
}
|
||||
|
||||
|
@ -3813,11 +3871,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, Ref<Animation> 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;
|
||||
|
|
|
@ -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<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, Ref<Animation> 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;
|
||||
|
||||
|
|
|
@ -1445,6 +1445,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);
|
||||
|
||||
|
@ -1459,6 +1470,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);
|
||||
|
@ -1506,6 +1519,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()) {
|
||||
|
|
|
@ -627,6 +627,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));
|
||||
|
||||
|
|
|
@ -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<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));
|
||||
Ref<ImageTexture> 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<StringName>::Element *E = animlist.front(); E; E = E->next()) {
|
||||
if (player->get_autoplay() == E->get()) {
|
||||
animation->add_icon_item(autoplay_icon, E->get());
|
||||
Ref<Texture> icon;
|
||||
if (E->get() == player->get_autoplay()) {
|
||||
if (E->get() == "RESET") {
|
||||
icon = autoplay_reset_icon;
|
||||
} else {
|
||||
animation->add_item(E->get());
|
||||
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<AnimatedValuesBackup> 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()) {
|
||||
|
|
|
@ -105,6 +105,8 @@ class AnimationPlayerEditor : public VBoxContainer {
|
|||
Label *name_title;
|
||||
UndoRedo *undo_redo;
|
||||
Ref<Texture> autoplay_icon;
|
||||
Ref<Texture> reset_icon;
|
||||
Ref<Texture> autoplay_reset_icon;
|
||||
bool last_active;
|
||||
float timeline_position;
|
||||
|
||||
|
|
|
@ -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<Skeleton>(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<AnimatedValuesBackup> AnimationPlayer::backup_animated_values(Node *p_root_override) {
|
||||
Ref<AnimatedValuesBackup> 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<StringName, TrackNodeCache::PropertyAnim>::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<Skeleton>(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;
|
||||
|
||||
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->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<AnimatedValuesBackup> old_values = aux_player->backup_animated_values(root);
|
||||
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
|
||||
|
||||
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -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<String> *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<AnimatedValuesBackup> backup_animated_values(Node *p_root_override = NULL);
|
||||
Ref<AnimatedValuesBackup> apply_reset(bool p_user_initiated = false);
|
||||
bool can_apply_reset() const;
|
||||
#endif
|
||||
|
||||
AnimationPlayer();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in a new issue