From 17bf6238fc3dca1b2ec6c1a0bbe11d5e7d7a8113 Mon Sep 17 00:00:00 2001 From: Silc Renew Date: Sat, 21 Jan 2023 14:51:03 +0900 Subject: Make AnimatedSprite's playback API consistent with AnimationPlayer --- doc/classes/AnimatedSprite2D.xml | 102 ++++- doc/classes/AnimatedSprite3D.xml | 104 ++++- doc/classes/AnimationPlayer.xml | 18 +- editor/plugins/animation_player_editor_plugin.cpp | 10 +- editor/plugins/sprite_frames_editor_plugin.cpp | 500 +++++++++++++++++----- editor/plugins/sprite_frames_editor_plugin.h | 43 +- editor/project_converter_3_to_4.cpp | 2 +- scene/2d/animated_sprite_2d.cpp | 307 +++++++++---- scene/2d/animated_sprite_2d.h | 32 +- scene/3d/sprite_3d.cpp | 307 +++++++++---- scene/3d/sprite_3d.h | 31 +- scene/animation/animation_player.cpp | 10 +- scene/resources/sprite_frames.cpp | 6 +- scene/resources/sprite_frames.h | 8 +- 14 files changed, 1113 insertions(+), 367 deletions(-) diff --git a/doc/classes/AnimatedSprite2D.xml b/doc/classes/AnimatedSprite2D.xml index cd9c0cee98..9872c59990 100644 --- a/doc/classes/AnimatedSprite2D.xml +++ b/doc/classes/AnimatedSprite2D.xml @@ -5,34 +5,82 @@ [AnimatedSprite2D] is similar to the [Sprite2D] node, except it carries multiple textures as animation frames. Animations are created using a [SpriteFrames] resource, which allows you to import image files (or a folder containing said files) to provide the animation frames for the sprite. The [SpriteFrames] resource can be configured in the editor via the SpriteFrames bottom panel. - After setting up [member frames], [method play] may be called. It's also possible to select an [member animation] and toggle [member playing], even within the editor. - To pause the current animation, set [member playing] to [code]false[/code]. Alternatively, setting [member speed_scale] to [code]0[/code] also preserves the current frame's elapsed time. $DOCS_URL/tutorials/2d/2d_sprite_animation.html https://godotengine.org/asset-library/asset/515 + + + + Returns the actual playing speed of current animation or [code]0[/code] if not playing. This speed is the [member speed_scale] property multiplied by [code]custom_speed[/code] argument specified when calling the [method play] method. + Returns a negative value if the current animation is playing backwards. + + + + + + Returns [code]true[/code] if an animation is currently playing (even if [member speed_scale] and/or [code]custom_speed[/code] are [code]0[/code]). + + + + + + Pauses the currently playing animation. The [member frame] and [member frame_progress] will be kept and calling [method play] or [method play_backwards] without arguments will resume the animation from the current playback position. + See also [method stop]. + + - - + + + + + Plays the animation with key [param name]. If [param custom_speed] is negative and [param from_end] is [code]true[/code], the animation will play backwards (which is equivalent to calling [method play_backwards]). + If this method is called with that same animation [param name], or with no [param name] parameter, the assigned animation will resume playing if it was paused. + + + + + + + Plays the animation with key [param name] in reverse. + This method is a shorthand for [method play] with [code]custom_speed = -1.0[/code] and [code]from_end = true[/code], so see its description for more information. + + + + + + - Plays the animation named [param anim]. If no [param anim] is provided, the current animation is played. If [param backwards] is [code]true[/code], the animation is played in reverse. - [b]Note:[/b] If [member speed_scale] is negative, the animation direction specified by [param backwards] will be inverted. + The setter of [member frame] resets the [member frame_progress] to [code]0.0[/code] implicitly, but this method avoids that. + This is useful when you want to carry over the current [member frame_progress] to another [member frame]. + [b]Example:[/b] + [codeblocks] + [gdscript] + # Change the animation with keeping the frame index and progress. + var current_frame = animated_sprite.get_frame() + var current_progress = animated_sprite.get_frame_progress() + animated_sprite.play("walk_another_skin") + animated_sprite.set_frame_and_progress(current_frame, current_progress) + [/gdscript] + [/codeblocks] - Stops the current [member animation] at the current [member frame]. - [b]Note:[/b] This method resets the current frame's elapsed time and removes the [code]backwards[/code] flag from the current [member animation] (if it was previously set by [method play]). If this behavior is undesired, set [member playing] to [code]false[/code] instead. + Stops the currently playing animation. The animation position is reset to [code]0[/code] and the [code]custom_speed[/code] is reset to [code]1.0[/code]. See also [method pause]. - The current animation from the [member frames] resource. If this value changes, the [code]frame[/code] counter is reset. + The current animation from the [member sprite_frames] resource. If this value is changed, the [member frame] counter and the [member frame_progress] are reset. + + + The key of the animation to play when the scene loads. If [code]true[/code], texture will be centered. @@ -44,32 +92,46 @@ If [code]true[/code], texture is flipped vertically. - The displayed animation frame's index. + The displayed animation frame's index. Setting this property also resets [member frame_progress]. If this is not desired, use [method set_frame_and_progress]. - - The [SpriteFrames] resource containing the animation(s). Allows you the option to load, edit, clear, make unique and save the states of the [SpriteFrames] resource. + + The progress value between [code]0.0[/code] and [code]1.0[/code] until the current frame transitions to the next frame. If the animation is playing backwards, the value transitions from [code]1.0[/code] to [code]0.0[/code]. The texture's drawing offset. - - If [code]true[/code], the [member animation] is currently playing. Setting this property to [code]false[/code] pauses the current animation. Use [method stop] to stop the animation at the current frame instead. - [b]Note:[/b] Unlike [method stop], changing this property to [code]false[/code] preserves the current frame's elapsed time and the [code]backwards[/code] flag of the current [member animation] (if it was previously set by [method play]). - [b]Note:[/b] After a non-looping animation finishes, the property still remains [code]true[/code]. - - The animation speed is multiplied by this value. If set to a negative value, the animation is played in reverse. If set to [code]0[/code], the animation is paused, preserving the current frame's elapsed time. + The speed scaling ratio. For example, if this value is [code]1[/code], then the animation plays at normal speed. If it's [code]0.5[/code], then it plays at half speed. If it's [code]2[/code], then it plays at double speed. + If set to a negative value, the animation is played in reverse. If set to [code]0[/code], the animation will not advance. + + + The [SpriteFrames] resource containing the animation(s). Allows you the option to load, edit, clear, make unique and save the states of the [SpriteFrames] resource. + + + Emitted when [member animation] changes. + + - Emitted when the animation reaches the end, or the start if it is played in reverse. If the animation is looping, this signal is emitted at the end of each loop. + Emitted when the animation reaches the end, or the start if it is played in reverse. When the animation finishes, it pauses the playback. + + + + + Emitted when the animation loops. - Emitted when [member frame] changed. + Emitted when [member frame] changes. + + + + + Emitted when [member sprite_frames] changes. diff --git a/doc/classes/AnimatedSprite3D.xml b/doc/classes/AnimatedSprite3D.xml index 4837ae715f..c39bb99827 100644 --- a/doc/classes/AnimatedSprite3D.xml +++ b/doc/classes/AnimatedSprite3D.xml @@ -4,59 +4,121 @@ 2D sprite node in 3D world, that can use multiple 2D textures for animation. - [AnimatedSprite3D] is similar to the [Sprite3D] node, except it carries multiple textures as animation [member frames]. Animations are created using a [SpriteFrames] resource, which allows you to import image files (or a folder containing said files) to provide the animation frames for the sprite. The [SpriteFrames] resource can be configured in the editor via the SpriteFrames bottom panel. - After setting up [member frames], [method play] may be called. It's also possible to select an [member animation] and toggle [member playing], even within the editor. - To pause the current animation, set [member playing] to [code]false[/code]. Alternatively, setting [member speed_scale] to [code]0[/code] also preserves the current frame's elapsed time. + [AnimatedSprite3D] is similar to the [Sprite3D] node, except it carries multiple textures as animation [member sprite_frames]. Animations are created using a [SpriteFrames] resource, which allows you to import image files (or a folder containing said files) to provide the animation frames for the sprite. The [SpriteFrames] resource can be configured in the editor via the SpriteFrames bottom panel. $DOCS_URL/tutorials/2d/2d_sprite_animation.html + + + + Returns the actual playing speed of current animation or [code]0[/code] if not playing. This speed is the [member speed_scale] property multiplied by [code]custom_speed[/code] argument specified when calling the [method play] method. + Returns a negative value if the current animation is playing backwards. + + + + + + Returns [code]true[/code] if an animation is currently playing (even if [member speed_scale] and/or [code]custom_speed[/code] are [code]0[/code]). + + + + + + Pauses the currently playing animation. The [member frame] and [member frame_progress] will be kept and calling [method play] or [method play_backwards] without arguments will resume the animation from the current playback position. + See also [method stop]. + + - - + + + + + Plays the animation with key [param name]. If [param custom_speed] is negative and [param from_end] is [code]true[/code], the animation will play backwards (which is equivalent to calling [method play_backwards]). + If this method is called with that same animation [param name], or with no [param name] parameter, the assigned animation will resume playing if it was paused. + + + + + + + Plays the animation with key [param name] in reverse. + This method is a shorthand for [method play] with [code]custom_speed = -1.0[/code] and [code]from_end = true[/code], so see its description for more information. + + + + + + - Plays the animation named [param anim]. If no [param anim] is provided, the current animation is played. If [param backwards] is [code]true[/code], the animation is played in reverse. - [b]Note:[/b] If [member speed_scale] is negative, the animation direction specified by [param backwards] will be inverted. + The setter of [member frame] resets the [member frame_progress] to [code]0.0[/code] implicitly, but this method avoids that. + This is useful when you want to carry over the current [member frame_progress] to another [member frame]. + [b]Example:[/b] + [codeblocks] + [gdscript] + # Change the animation with keeping the frame index and progress. + var current_frame = animated_sprite.get_frame() + var current_progress = animated_sprite.get_frame_progress() + animated_sprite.play("walk_another_skin") + animated_sprite.set_frame_and_progress(current_frame, current_progress) + [/gdscript] + [/codeblocks] - Stops the current [member animation] at the current [member frame]. - [b]Note:[/b] This method resets the current frame's elapsed time and removes the [code]backwards[/code] flag from the current [member animation] (if it was previously set by [method play]). If this behavior is undesired, set [member playing] to [code]false[/code] instead. + Stops the currently playing animation. The animation position is reset to [code]0[/code] and the [code]custom_speed[/code] is reset to [code]1.0[/code]. See also [method pause]. - The current animation from the [code]frames[/code] resource. If this value changes, the [code]frame[/code] counter is reset. + The current animation from the [member sprite_frames] resource. If this value is changed, the [member frame] counter and the [member frame_progress] are reset. - - The displayed animation frame's index. + + The key of the animation to play when the scene loads. - - The [SpriteFrames] resource containing the animation(s). + + The displayed animation frame's index. Setting this property also resets [member frame_progress]. If this is not desired, use [method set_frame_and_progress]. - - If [code]true[/code], the [member animation] is currently playing. Setting this property to [code]false[/code] pauses the current animation. Use [method stop] to stop the animation at the current frame instead. - [b]Note:[/b] Unlike [method stop], changing this property to [code]false[/code] preserves the current frame's elapsed time and the [code]backwards[/code] flag of the current [member animation] (if it was previously set by [method play]). - [b]Note:[/b] After a non-looping animation finishes, the property still remains [code]true[/code]. + + The progress value between [code]0.0[/code] and [code]1.0[/code] until the current frame transitions to the next frame. If the animation is playing backwards, the value transitions from [code]1.0[/code] to [code]0.0[/code]. - The animation speed is multiplied by this value. If set to a negative value, the animation is played in reverse. If set to [code]0[/code], the animation is paused, preserving the current frame's elapsed time. + The speed scaling ratio. For example, if this value is [code]1[/code], then the animation plays at normal speed. If it's [code]0.5[/code], then it plays at half speed. If it's [code]2[/code], then it plays at double speed. + If set to a negative value, the animation is played in reverse. If set to [code]0[/code], the animation will not advance. + + + The [SpriteFrames] resource containing the animation(s). Allows you the option to load, edit, clear, make unique and save the states of the [SpriteFrames] resource. + + + Emitted when [member animation] changes. + + - Emitted when the animation reaches the end, or the start if it is played in reverse. If the animation is looping, this signal is emitted at the end of each loop. + Emitted when the animation reaches the end, or the start if it is played in reverse. When the animation finishes, it pauses the playback. + + + + + Emitted when the animation loops. - Emitted when [member frame] changed. + Emitted when [member frame] changes. + + + + + Emitted when [member sprite_frames] changes. diff --git a/doc/classes/AnimationPlayer.xml b/doc/classes/AnimationPlayer.xml index 304caeef43..e41e35814f 100644 --- a/doc/classes/AnimationPlayer.xml +++ b/doc/classes/AnimationPlayer.xml @@ -102,13 +102,14 @@ - Gets the blend time (in seconds) between two animations, referenced by their keys. + Returns the blend time (in seconds) between two animations, referenced by their keys. - Gets the actual playing speed of current animation or 0 if not playing. This speed is the [member playback_speed] property multiplied by [code]custom_speed[/code] argument specified when calling the [method play] method. + Returns the actual playing speed of current animation or [code]0[/code] if not playing. This speed is the [member speed_scale] property multiplied by [code]custom_speed[/code] argument specified when calling the [method play] method. + Returns a negative value if the current animation is playing backwards. @@ -134,7 +135,7 @@ - Returns [code]true[/code] if playing an animation. + Returns [code]true[/code] if an animation is currently playing (even if [member speed_scale] and/or [code]custom_speed[/code] are [code]0[/code]). @@ -152,7 +153,7 @@ Plays the animation with key [param name]. Custom blend times and speed can be set. If [param custom_speed] is negative and [param from_end] is [code]true[/code], the animation will play backwards (which is equivalent to calling [method play_backwards]). - The [AnimationPlayer] keeps track of its current or last played animation with [member assigned_animation]. If this method is called with that same animation [param name], or with no [param name] parameter, the assigned animation will resume playing if it was paused, or restart if it was stopped (see [method stop] for both pause and stop). If the animation was already playing, it will keep playing. + The [AnimationPlayer] keeps track of its current or last played animation with [member assigned_animation]. If this method is called with that same animation [param name], or with no [param name] parameter, the assigned animation will resume playing if it was paused. [b]Note:[/b] The animation will be updated the next time the [AnimationPlayer] is processed. If other variables are updated at the same time this is called, they may be updated too early. To perform the update immediately, call [code]advance(0)[/code]. @@ -210,7 +211,7 @@ - Stops the currently playing animation. The animation position is reset to [code]0[/code] and the playback speed is reset to [code]1.0[/code]. See also [method pause]. + Stops the currently playing animation. The animation position is reset to [code]0[/code] and the [code]custom_speed[/code] is reset to [code]1.0[/code]. See also [method pause]. If [param keep_state] is [code]true[/code], the animation state is not updated visually. [b]Note:[/b] The method / audio / animation playback tracks will not be processed by this method. @@ -249,9 +250,6 @@ The process notification in which to update animations. - - The speed scaling ratio. For example, if this value is 1, then the animation plays at normal speed. If it's 0.5, then it plays at half speed. If it's 2, then it plays at double speed. - This is used by the editor. If set to [code]true[/code], the scene will be saved with the effects of the reset animation (the animation with the key [code]"RESET"[/code]) applied as if it had been seeked to time 0, with the editor keeping the values that the scene had before saving. This makes it more convenient to preview and edit animations in the editor, as changes to the scene will not be saved as long as they are set in the reset animation. @@ -259,6 +257,10 @@ The node from which node path references will travel. + + The speed scaling ratio. For example, if this value is [code]1[/code], then the animation plays at normal speed. If it's [code]0.5[/code], then it plays at half speed. If it's [code]2[/code], then it plays at double speed. + If set to a negative value, the animation is played in reverse. If set to [code]0[/code], the animation will not advance. + diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 06241d07cf..30e06bfcf4 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -245,7 +245,7 @@ void AnimationPlayerEditor::_play_bw_pressed() { player->stop(); //so it won't blend with itself } ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing."); - player->play(current, -1, -1, true); + player->play_backwards(current); } //unstop @@ -262,7 +262,7 @@ void AnimationPlayerEditor::_play_bw_from_pressed() { } ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing."); player->seek(time); - player->play(current, -1, -1, true); + player->play_backwards(current); } //unstop @@ -452,7 +452,9 @@ float AnimationPlayerEditor::_get_editor_step() const { } void AnimationPlayerEditor::_animation_name_edited() { - player->stop(); + if (player->is_playing()) { + player->stop(); + } String new_name = name->get_text(); if (!AnimationLibrary::is_valid_animation_name(new_name)) { @@ -1675,7 +1677,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug stop = memnew(Button); stop->set_flat(true); hb->add_child(stop); - stop->set_tooltip_text(TTR("Stop animation playback. (S)")); + stop->set_tooltip_text(TTR("Pause/stop animation playback. (S)")); play = memnew(Button); play->set_flat(true); diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index 74c9286325..a7b32ce0c3 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -40,7 +40,6 @@ #include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" #include "editor/scene_tree_dock.h" -#include "scene/3d/sprite_3d.h" #include "scene/gui/center_container.h" #include "scene/gui/margin_container.h" #include "scene/gui/panel_container.h" @@ -252,8 +251,7 @@ void SpriteFramesEditor::_sheet_add_frames() { const Size2i separation = _get_separation(); EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Add Frame")); - + undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); int fc = frames->get_frame_count(edited_anim); for (const int &E : frames_selected) { @@ -265,8 +263,8 @@ void SpriteFramesEditor::_sheet_add_frames() { at->set_atlas(split_sheet_preview->get_texture()); at->set_region(Rect2(offset + frame_coords * (frame_size + separation), frame_size)); - undo_redo->add_do_method(frames, "add_frame", edited_anim, at, 1.0, -1); - undo_redo->add_undo_method(frames, "remove_frame", edited_anim, fc); + undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, at, 1.0, -1); + undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, fc); } undo_redo->add_do_method(this, "_update_library"); @@ -415,8 +413,23 @@ void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) { void SpriteFramesEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_ENTER_TREE: { + get_tree()->connect("node_removed", callable_mp(this, &SpriteFramesEditor::_node_removed)); + + [[fallthrough]]; + } case NOTIFICATION_THEME_CHANGED: { + autoplay_icon = get_theme_icon(SNAME("AutoPlay"), SNAME("EditorIcons")); + stop_icon = get_theme_icon(SNAME("Stop"), SNAME("EditorIcons")); + pause_icon = get_theme_icon(SNAME("Pause"), SNAME("EditorIcons")); + _update_stop_icon(); + + autoplay->set_icon(get_theme_icon(SNAME("AutoPlay"), SNAME("EditorIcons"))); + play->set_icon(get_theme_icon(SNAME("PlayStart"), SNAME("EditorIcons"))); + play_from->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); + play_bw->set_icon(get_theme_icon(SNAME("PlayStartBackwards"), SNAME("EditorIcons"))); + play_bw_from->set_icon(get_theme_icon(SNAME("PlayBackwards"), SNAME("EditorIcons"))); + load->set_icon(get_theme_icon(SNAME("Load"), SNAME("EditorIcons"))); load_sheet->set_icon(get_theme_icon(SNAME("SpriteSheet"), SNAME("EditorIcons"))); copy->set_icon(get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons"))); @@ -441,6 +454,10 @@ void SpriteFramesEditor::_notification(int p_what) { case NOTIFICATION_READY: { add_theme_constant_override("autohide", 1); // Fixes the dragger always showing up. } break; + + case NOTIFICATION_EXIT_TREE: { + get_tree()->disconnect("node_removed", callable_mp(this, &SpriteFramesEditor::_node_removed)); + } break; } } @@ -471,14 +488,14 @@ void SpriteFramesEditor::_file_load_request(const Vector &p_path, int p_ } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Add Frame")); + undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); int fc = frames->get_frame_count(edited_anim); int count = 0; for (const Ref &E : resources) { - undo_redo->add_do_method(frames, "add_frame", edited_anim, E, 1.0, p_at_pos == -1 ? -1 : p_at_pos + count); - undo_redo->add_undo_method(frames, "remove_frame", edited_anim, p_at_pos == -1 ? fc : p_at_pos); + undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, E, 1.0, p_at_pos == -1 ? -1 : p_at_pos + count); + undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, p_at_pos == -1 ? fc : p_at_pos); count++; } undo_redo->add_do_method(this, "_update_library"); @@ -542,9 +559,9 @@ void SpriteFramesEditor::_paste_pressed() { } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Paste Frame")); - undo_redo->add_do_method(frames, "add_frame", edited_anim, texture, duration); - undo_redo->add_undo_method(frames, "remove_frame", edited_anim, frames->get_frame_count(edited_anim)); + undo_redo->create_action(TTR("Paste Frame"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, duration); + undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, frames->get_frame_count(edited_anim)); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); undo_redo->commit_action(); @@ -585,9 +602,9 @@ void SpriteFramesEditor::_empty_pressed() { Ref texture; EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Add Empty")); - undo_redo->add_do_method(frames, "add_frame", edited_anim, texture, 1.0, from); - undo_redo->add_undo_method(frames, "remove_frame", edited_anim, from); + undo_redo->create_action(TTR("Add Empty"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, 1.0, from); + undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, from); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); undo_redo->commit_action(); @@ -609,9 +626,9 @@ void SpriteFramesEditor::_empty2_pressed() { Ref texture; EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Add Empty")); - undo_redo->add_do_method(frames, "add_frame", edited_anim, texture, 1.0, from + 1); - undo_redo->add_undo_method(frames, "remove_frame", edited_anim, from + 1); + undo_redo->create_action(TTR("Add Empty"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, 1.0, from + 1); + undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, from + 1); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); undo_redo->commit_action(); @@ -633,11 +650,11 @@ void SpriteFramesEditor::_up_pressed() { sel -= 1; EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Move Frame")); - undo_redo->add_do_method(frames, "set_frame", edited_anim, to_move, frames->get_frame_texture(edited_anim, to_move - 1), frames->get_frame_duration(edited_anim, to_move - 1)); - undo_redo->add_do_method(frames, "set_frame", edited_anim, to_move - 1, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move)); - undo_redo->add_undo_method(frames, "set_frame", edited_anim, to_move, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move)); - undo_redo->add_undo_method(frames, "set_frame", edited_anim, to_move - 1, frames->get_frame_texture(edited_anim, to_move - 1), frames->get_frame_duration(edited_anim, to_move - 1)); + undo_redo->create_action(TTR("Move Frame"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, to_move, frames->get_frame_texture(edited_anim, to_move - 1), frames->get_frame_duration(edited_anim, to_move - 1)); + undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, to_move - 1, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move)); + undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, to_move, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move)); + undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, to_move - 1, frames->get_frame_texture(edited_anim, to_move - 1), frames->get_frame_duration(edited_anim, to_move - 1)); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); undo_redo->commit_action(); @@ -659,11 +676,11 @@ void SpriteFramesEditor::_down_pressed() { sel += 1; EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Move Frame")); - undo_redo->add_do_method(frames, "set_frame", edited_anim, to_move, frames->get_frame_texture(edited_anim, to_move + 1), frames->get_frame_duration(edited_anim, to_move + 1)); - undo_redo->add_do_method(frames, "set_frame", edited_anim, to_move + 1, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move)); - undo_redo->add_undo_method(frames, "set_frame", edited_anim, to_move, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move)); - undo_redo->add_undo_method(frames, "set_frame", edited_anim, to_move + 1, frames->get_frame_texture(edited_anim, to_move + 1), frames->get_frame_duration(edited_anim, to_move + 1)); + undo_redo->create_action(TTR("Move Frame"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, to_move, frames->get_frame_texture(edited_anim, to_move + 1), frames->get_frame_duration(edited_anim, to_move + 1)); + undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, to_move + 1, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move)); + undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, to_move, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move)); + undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, to_move + 1, frames->get_frame_texture(edited_anim, to_move + 1), frames->get_frame_duration(edited_anim, to_move + 1)); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); undo_redo->commit_action(); @@ -682,15 +699,15 @@ void SpriteFramesEditor::_delete_pressed() { } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Delete Resource")); - undo_redo->add_do_method(frames, "remove_frame", edited_anim, to_delete); - undo_redo->add_undo_method(frames, "add_frame", edited_anim, frames->get_frame_texture(edited_anim, to_delete), frames->get_frame_duration(edited_anim, to_delete), to_delete); + undo_redo->create_action(TTR("Delete Resource"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "remove_frame", edited_anim, to_delete); + undo_redo->add_undo_method(frames.ptr(), "add_frame", edited_anim, frames->get_frame_texture(edited_anim, to_delete), frames->get_frame_duration(edited_anim, to_delete), to_delete); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); undo_redo->commit_action(); } -void SpriteFramesEditor::_animation_select() { +void SpriteFramesEditor::_animation_selected() { if (updating) { return; } @@ -705,9 +722,42 @@ void SpriteFramesEditor::_animation_select() { TreeItem *selected = animations->get_selected(); ERR_FAIL_COND(!selected); edited_anim = selected->get_text(0); + + if (animated_sprite) { + sprite_node_updating = true; + animated_sprite->call("set_animation", edited_anim); + sprite_node_updating = false; + } + _update_library(true); } +void SpriteFramesEditor::_sync_animation() { + if (!animated_sprite || sprite_node_updating) { + return; + } + _select_animation(animated_sprite->call("get_animation"), false); + _update_stop_icon(); +} + +void SpriteFramesEditor::_select_animation(const String &p_name, bool p_update_node) { + TreeItem *selected = nullptr; + selected = animations->get_item_with_text(p_name); + if (!selected) { + return; + }; + + edited_anim = selected->get_text(0); + + if (animated_sprite) { + if (p_update_node) { + animated_sprite->call("set_animation", edited_anim); + } + } + + _update_library(); +} + static void _find_anim_sprites(Node *p_node, List *r_nodes, Ref p_sfames) { Node *edited = EditorNode::get_singleton()->get_edited_scene(); if (!edited) { @@ -765,26 +815,48 @@ void SpriteFramesEditor::_animation_name_edited() { name = new_name + " " + itos(counter); } - List nodes; - _find_anim_sprites(EditorNode::get_singleton()->get_edited_scene(), &nodes, Ref(frames)); - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Rename Animation")); - undo_redo->add_do_method(frames, "rename_animation", edited_anim, name); - undo_redo->add_undo_method(frames, "rename_animation", name, edited_anim); - - for (Node *E : nodes) { - String current = E->call("get_animation"); - undo_redo->add_do_method(E, "set_animation", name); - undo_redo->add_undo_method(E, "set_animation", edited_anim); - } - + undo_redo->create_action(TTR("Rename Animation"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + _rename_node_animation(undo_redo, false, edited_anim, "", ""); + undo_redo->add_do_method(frames.ptr(), "rename_animation", edited_anim, name); + undo_redo->add_undo_method(frames.ptr(), "rename_animation", name, edited_anim); + _rename_node_animation(undo_redo, false, edited_anim, name, name); + _rename_node_animation(undo_redo, true, edited_anim, edited_anim, edited_anim); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); + undo_redo->commit_action(); - edited_anim = name; + _select_animation(name); + animations->grab_focus(); +} - undo_redo->commit_action(); +void SpriteFramesEditor::_rename_node_animation(EditorUndoRedoManager *undo_redo, bool is_undo, const String &p_filter, const String &p_new_animation, const String &p_new_autoplay) { + List nodes; + _find_anim_sprites(EditorNode::get_singleton()->get_edited_scene(), &nodes, Ref(frames)); + + if (is_undo) { + for (Node *E : nodes) { + String current_name = E->call("get_animation"); + if (current_name == p_filter) { + undo_redo->add_undo_method(E, "set_animation", p_new_animation); + } + String autoplay_name = E->call("get_autoplay"); + if (autoplay_name == p_filter) { + undo_redo->add_undo_method(E, "set_autoplay", p_new_autoplay); + } + } + } else { + for (Node *E : nodes) { + String current_name = E->call("get_animation"); + if (current_name == p_filter) { + undo_redo->add_do_method(E, "set_animation", p_new_animation); + } + String autoplay_name = E->call("get_autoplay"); + if (autoplay_name == p_filter) { + undo_redo->add_do_method(E, "set_autoplay", p_new_autoplay); + } + } + } } void SpriteFramesEditor::_animation_add() { @@ -795,25 +867,15 @@ void SpriteFramesEditor::_animation_add() { name = vformat("new_animation_%d", counter); } - List nodes; - _find_anim_sprites(EditorNode::get_singleton()->get_edited_scene(), &nodes, Ref(frames)); - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Add Animation")); - undo_redo->add_do_method(frames, "add_animation", name); - undo_redo->add_undo_method(frames, "remove_animation", name); + undo_redo->create_action(TTR("Add Animation"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "add_animation", name); + undo_redo->add_undo_method(frames.ptr(), "remove_animation", name); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); - - for (Node *E : nodes) { - String current = E->call("get_animation"); - undo_redo->add_do_method(E, "set_animation", name); - undo_redo->add_undo_method(E, "set_animation", current); - } - - edited_anim = name; - undo_redo->commit_action(); + + _select_animation(name); animations->grab_focus(); } @@ -831,24 +893,39 @@ void SpriteFramesEditor::_animation_remove() { } void SpriteFramesEditor::_animation_remove_confirmed() { + StringName new_edited; + List anim_names; + frames->get_animation_list(&anim_names); + anim_names.sort_custom(); + if (anim_names.size() >= 2) { + if (edited_anim == anim_names[0]) { + new_edited = anim_names[1]; + } else { + new_edited = anim_names[0]; + } + } else { + new_edited = StringName(); + } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Remove Animation")); - undo_redo->add_do_method(frames, "remove_animation", edited_anim); - undo_redo->add_undo_method(frames, "add_animation", edited_anim); - undo_redo->add_undo_method(frames, "set_animation_speed", edited_anim, frames->get_animation_speed(edited_anim)); - undo_redo->add_undo_method(frames, "set_animation_loop", edited_anim, frames->get_animation_loop(edited_anim)); + undo_redo->create_action(TTR("Remove Animation"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + _rename_node_animation(undo_redo, false, edited_anim, new_edited, ""); + undo_redo->add_do_method(frames.ptr(), "remove_animation", edited_anim); + undo_redo->add_undo_method(frames.ptr(), "add_animation", edited_anim); + _rename_node_animation(undo_redo, true, edited_anim, edited_anim, edited_anim); + undo_redo->add_undo_method(frames.ptr(), "set_animation_speed", edited_anim, frames->get_animation_speed(edited_anim)); + undo_redo->add_undo_method(frames.ptr(), "set_animation_loop", edited_anim, frames->get_animation_loop(edited_anim)); int fc = frames->get_frame_count(edited_anim); for (int i = 0; i < fc; i++) { Ref texture = frames->get_frame_texture(edited_anim, i); float duration = frames->get_frame_duration(edited_anim, i); - undo_redo->add_undo_method(frames, "add_frame", edited_anim, texture, duration); + undo_redo->add_undo_method(frames.ptr(), "add_frame", edited_anim, texture, duration); } undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); - - edited_anim = StringName(); - undo_redo->commit_action(); + + _select_animation(new_edited); } void SpriteFramesEditor::_animation_search_text_changed(const String &p_text) { @@ -861,9 +938,9 @@ void SpriteFramesEditor::_animation_loop_changed() { } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Change Animation Loop")); - undo_redo->add_do_method(frames, "set_animation_loop", edited_anim, anim_loop->is_pressed()); - undo_redo->add_undo_method(frames, "set_animation_loop", edited_anim, frames->get_animation_loop(edited_anim)); + undo_redo->create_action(TTR("Change Animation Loop"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "set_animation_loop", edited_anim, anim_loop->is_pressed()); + undo_redo->add_undo_method(frames.ptr(), "set_animation_loop", edited_anim, frames->get_animation_loop(edited_anim)); undo_redo->add_do_method(this, "_update_library", true); undo_redo->add_undo_method(this, "_update_library", true); undo_redo->commit_action(); @@ -875,9 +952,9 @@ void SpriteFramesEditor::_animation_speed_changed(double p_value) { } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Change Animation FPS"), UndoRedo::MERGE_ENDS); - undo_redo->add_do_method(frames, "set_animation_speed", edited_anim, p_value); - undo_redo->add_undo_method(frames, "set_animation_speed", edited_anim, frames->get_animation_speed(edited_anim)); + undo_redo->create_action(TTR("Change Animation FPS"), UndoRedo::MERGE_ENDS, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "set_animation_speed", edited_anim, p_value); + undo_redo->add_undo_method(frames.ptr(), "set_animation_speed", edited_anim, frames->get_animation_speed(edited_anim)); undo_redo->add_do_method(this, "_update_library", true); undo_redo->add_undo_method(this, "_update_library", true); undo_redo->commit_action(); @@ -927,9 +1004,9 @@ void SpriteFramesEditor::_frame_duration_changed(double p_value) { float old_duration = frames->get_frame_duration(edited_anim, index); EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Set Frame Duration")); - undo_redo->add_do_method(frames, "set_frame", edited_anim, index, texture, p_value); - undo_redo->add_undo_method(frames, "set_frame", edited_anim, index, texture, old_duration); + undo_redo->create_action(TTR("Set Frame Duration"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, index, texture, p_value); + undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, index, texture, old_duration); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); undo_redo->commit_action(); @@ -968,6 +1045,10 @@ void SpriteFramesEditor::_zoom_reset() { } void SpriteFramesEditor::_update_library(bool p_skip_selector) { + if (frames.is_null()) { + return; + } + updating = true; frame_duration->set_value(1.0); // Default. @@ -998,12 +1079,27 @@ void SpriteFramesEditor::_update_library(bool p_skip_selector) { it->set_text(0, name); it->set_editable(0, true); + if (animated_sprite) { + if (name == String(animated_sprite->call("get_autoplay"))) { + it->set_icon(0, autoplay_icon); + } + } + if (E == edited_anim) { it->select(0); } } } + if (animated_sprite) { + String autoplay_name = animated_sprite->call("get_autoplay"); + if (autoplay_name.is_empty()) { + autoplay->set_pressed(false); + } else { + autoplay->set_pressed(String(edited_anim) == autoplay_name); + } + } + frame_list->clear(); if (!frames->has_animation(edited_anim)) { @@ -1061,20 +1157,25 @@ void SpriteFramesEditor::_update_library(bool p_skip_selector) { updating = false; } -void SpriteFramesEditor::edit(SpriteFrames *p_frames) { - bool new_read_only_state = false; - if (p_frames) { - new_read_only_state = EditorNode::get_singleton()->is_resource_read_only(p_frames); +void SpriteFramesEditor::_edit() { + if (!animated_sprite) { + return; } + edit(animated_sprite->call("get_sprite_frames")); +} + +void SpriteFramesEditor::edit(Ref p_frames) { + _update_stop_icon(); - if (frames == p_frames && new_read_only_state == read_only) { + if (!p_frames.is_valid()) { + frames.unref(); return; } frames = p_frames; - read_only = new_read_only_state; + read_only = EditorNode::get_singleton()->is_resource_read_only(p_frames); - if (p_frames) { + if (p_frames.is_valid()) { if (!p_frames->has_animation(edited_anim)) { List anim_names; frames->get_animation_list(&anim_names); @@ -1107,6 +1208,8 @@ void SpriteFramesEditor::edit(SpriteFrames *p_frames) { move_up->set_disabled(read_only); move_down->set_disabled(read_only); delete_frame->set_disabled(read_only); + + _fetch_sprite_node(); // Fetch node after set frames. } Variant SpriteFramesEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { @@ -1215,18 +1318,18 @@ void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da duration = frames->get_frame_duration(edited_anim, from_frame); } - undo_redo->create_action(TTR("Move Frame")); - undo_redo->add_do_method(frames, "remove_frame", edited_anim, from_frame == -1 ? frames->get_frame_count(edited_anim) : from_frame); - undo_redo->add_do_method(frames, "add_frame", edited_anim, texture, duration, at_pos == -1 ? -1 : at_pos); - undo_redo->add_undo_method(frames, "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) - 1 : at_pos); - undo_redo->add_undo_method(frames, "add_frame", edited_anim, texture, duration, from_frame); + undo_redo->create_action(TTR("Move Frame"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "remove_frame", edited_anim, from_frame == -1 ? frames->get_frame_count(edited_anim) : from_frame); + undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, duration, at_pos == -1 ? -1 : at_pos); + undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) - 1 : at_pos); + undo_redo->add_undo_method(frames.ptr(), "add_frame", edited_anim, texture, duration, from_frame); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); undo_redo->commit_action(); } else { - undo_redo->create_action(TTR("Add Frame")); - undo_redo->add_do_method(frames, "add_frame", edited_anim, texture, 1.0, at_pos == -1 ? -1 : at_pos); - undo_redo->add_undo_method(frames, "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) : at_pos); + undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, 1.0, at_pos == -1 ? -1 : at_pos); + undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) : at_pos); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); undo_redo->commit_action(); @@ -1245,10 +1348,156 @@ void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da } } +void SpriteFramesEditor::_update_stop_icon() { + bool is_playing = false; + if (animated_sprite) { + is_playing = animated_sprite->call("is_playing"); + } + if (is_playing) { + stop->set_icon(pause_icon); + } else { + stop->set_icon(stop_icon); + } +} + +void SpriteFramesEditor::_remove_sprite_node() { + if (!animated_sprite) { + return; + } + if (animated_sprite->is_connected("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit))) { + animated_sprite->disconnect("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit)); + } + if (animated_sprite->is_connected("animation_changed", callable_mp(this, &SpriteFramesEditor::_sync_animation))) { + animated_sprite->disconnect("animation_changed", callable_mp(this, &SpriteFramesEditor::_sync_animation)); + } + if (animated_sprite->is_connected("animation_finished", callable_mp(this, &SpriteFramesEditor::_update_stop_icon))) { + animated_sprite->disconnect("animation_finished", callable_mp(this, &SpriteFramesEditor::_update_stop_icon)); + } + animated_sprite = nullptr; +} + +void SpriteFramesEditor::_fetch_sprite_node() { + Node *selected = nullptr; + EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection(); + if (editor_selection->get_selected_node_list().size() == 1) { + selected = editor_selection->get_selected_node_list()[0]; + } + + bool show_node_edit = false; + AnimatedSprite2D *as2d = Object::cast_to(selected); + AnimatedSprite3D *as3d = Object::cast_to(selected); + if (as2d || as3d) { + if (frames != selected->call("get_sprite_frames")) { + _remove_sprite_node(); + } else { + animated_sprite = selected; + if (!animated_sprite->is_connected("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit))) { + animated_sprite->connect("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit)); + } + if (!animated_sprite->is_connected("animation_changed", callable_mp(this, &SpriteFramesEditor::_sync_animation))) { + animated_sprite->connect("animation_changed", callable_mp(this, &SpriteFramesEditor::_sync_animation), CONNECT_DEFERRED); + } + if (!animated_sprite->is_connected("animation_finished", callable_mp(this, &SpriteFramesEditor::_update_stop_icon))) { + animated_sprite->connect("animation_finished", callable_mp(this, &SpriteFramesEditor::_update_stop_icon)); + } + show_node_edit = true; + } + } else { + _remove_sprite_node(); + } + + if (show_node_edit) { + _sync_animation(); + autoplay_container->show(); + playback_container->show(); + } else { + _update_library(); // To init autoplay icon. + autoplay_container->hide(); + playback_container->hide(); + } +} + +void SpriteFramesEditor::_play_pressed() { + if (animated_sprite) { + animated_sprite->call("stop"); + animated_sprite->call("play", animated_sprite->call("get_animation")); + } + _update_stop_icon(); +} + +void SpriteFramesEditor::_play_from_pressed() { + if (animated_sprite) { + animated_sprite->call("play", animated_sprite->call("get_animation")); + } + _update_stop_icon(); +} + +void SpriteFramesEditor::_play_bw_pressed() { + if (animated_sprite) { + animated_sprite->call("stop"); + animated_sprite->call("play_backwards", animated_sprite->call("get_animation")); + } + _update_stop_icon(); +} + +void SpriteFramesEditor::_play_bw_from_pressed() { + if (animated_sprite) { + animated_sprite->call("play_backwards", animated_sprite->call("get_animation")); + } + _update_stop_icon(); +} + +void SpriteFramesEditor::_stop_pressed() { + if (animated_sprite) { + if (animated_sprite->call("is_playing")) { + animated_sprite->call("pause"); + } else { + animated_sprite->call("stop"); + } + } + _update_stop_icon(); +} + +void SpriteFramesEditor::_autoplay_pressed() { + if (updating) { + return; + } + + if (animated_sprite) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Toggle Autoplay"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + String current = animated_sprite->call("get_animation"); + String current_auto = animated_sprite->call("get_autoplay"); + if (current == current_auto) { + //unset + undo_redo->add_do_method(animated_sprite, "set_autoplay", ""); + undo_redo->add_undo_method(animated_sprite, "set_autoplay", current_auto); + } else { + //set + undo_redo->add_do_method(animated_sprite, "set_autoplay", current); + undo_redo->add_undo_method(animated_sprite, "set_autoplay", current_auto); + } + undo_redo->add_do_method(this, "_update_library"); + undo_redo->add_undo_method(this, "_update_library"); + undo_redo->commit_action(); + } + + _update_library(); +} + void SpriteFramesEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_library", "skipsel"), &SpriteFramesEditor::_update_library, DEFVAL(false)); } +void SpriteFramesEditor::_node_removed(Node *p_node) { + if (animated_sprite) { + if (animated_sprite != p_node) { + return; + } + _remove_sprite_node(); + } +} + SpriteFramesEditor::SpriteFramesEditor() { VBoxContainer *vbc_animlist = memnew(VBoxContainer); add_child(vbc_animlist); @@ -1272,8 +1521,16 @@ SpriteFramesEditor::SpriteFramesEditor() { delete_anim->set_disabled(true); delete_anim->connect("pressed", callable_mp(this, &SpriteFramesEditor::_animation_remove)); + autoplay_container = memnew(HBoxContainer); + hbc_animlist->add_child(autoplay_container); + autoplay_container->add_child(memnew(VSeparator)); + autoplay = memnew(Button); + autoplay->set_flat(true); + autoplay->set_tooltip_text(TTR("Autoplay on Load")); + autoplay_container->add_child(autoplay); + anim_search_box = memnew(LineEdit); - hbc_animlist->add_child(anim_search_box); + sub_vb->add_child(anim_search_box); anim_search_box->set_h_size_flags(SIZE_EXPAND_FILL); anim_search_box->set_placeholder(TTR("Filter Animations")); anim_search_box->set_clear_button_enabled(true); @@ -1283,7 +1540,7 @@ SpriteFramesEditor::SpriteFramesEditor() { sub_vb->add_child(animations); animations->set_v_size_flags(SIZE_EXPAND_FILL); animations->set_hide_root(true); - animations->connect("cell_selected", callable_mp(this, &SpriteFramesEditor::_animation_select)); + animations->connect("cell_selected", callable_mp(this, &SpriteFramesEditor::_animation_selected)); animations->connect("item_edited", callable_mp(this, &SpriteFramesEditor::_animation_name_edited)); animations->set_allow_reselect(true); @@ -1319,6 +1576,44 @@ SpriteFramesEditor::SpriteFramesEditor() { HBoxContainer *hbc = memnew(HBoxContainer); sub_vb->add_child(hbc); + playback_container = memnew(HBoxContainer); + hbc->add_child(playback_container); + + play_bw_from = memnew(Button); + play_bw_from->set_flat(true); + play_bw_from->set_tooltip_text(TTR("Play selected animation backwards from current pos. (A)")); + playback_container->add_child(play_bw_from); + + play_bw = memnew(Button); + play_bw->set_flat(true); + play_bw->set_tooltip_text(TTR("Play selected animation backwards from end. (Shift+A)")); + playback_container->add_child(play_bw); + + stop = memnew(Button); + stop->set_flat(true); + stop->set_tooltip_text(TTR("Pause/stop animation playback. (S)")); + playback_container->add_child(stop); + + play = memnew(Button); + play->set_flat(true); + play->set_tooltip_text(TTR("Play selected animation from start. (Shift+D)")); + playback_container->add_child(play); + + play_from = memnew(Button); + play_from->set_flat(true); + play_from->set_tooltip_text(TTR("Play selected animation from current pos. (D)")); + playback_container->add_child(play_from); + + playback_container->add_child(memnew(VSeparator)); + + autoplay->connect("pressed", callable_mp(this, &SpriteFramesEditor::_autoplay_pressed)); + autoplay->set_toggle_mode(true); + play->connect("pressed", callable_mp(this, &SpriteFramesEditor::_play_pressed)); + play_from->connect("pressed", callable_mp(this, &SpriteFramesEditor::_play_from_pressed)); + play_bw->connect("pressed", callable_mp(this, &SpriteFramesEditor::_play_bw_pressed)); + play_bw_from->connect("pressed", callable_mp(this, &SpriteFramesEditor::_play_bw_from_pressed)); + stop->connect("pressed", callable_mp(this, &SpriteFramesEditor::_stop_pressed)); + load = memnew(Button); load->set_flat(true); hbc->add_child(load); @@ -1369,9 +1664,10 @@ SpriteFramesEditor::SpriteFramesEditor() { frame_duration = memnew(SpinBox); frame_duration->set_prefix(String::utf8("×")); - frame_duration->set_min(0); + frame_duration->set_min(SPRITE_FRAME_MINIMUM_DURATION); // Avoid zero div. frame_duration->set_max(10); frame_duration->set_step(0.01); + frame_duration->set_allow_lesser(false); frame_duration->set_allow_greater(true); hbc->add_child(frame_duration); @@ -1616,16 +1912,16 @@ SpriteFramesEditor::SpriteFramesEditor() { } void SpriteFramesEditorPlugin::edit(Object *p_object) { - SpriteFrames *s; + Ref s; AnimatedSprite2D *animated_sprite = Object::cast_to(p_object); if (animated_sprite) { - s = *animated_sprite->get_sprite_frames(); + s = animated_sprite->get_sprite_frames(); } else { AnimatedSprite3D *animated_sprite_3d = Object::cast_to(p_object); if (animated_sprite_3d) { - s = *animated_sprite_3d->get_sprite_frames(); + s = animated_sprite_3d->get_sprite_frames(); } else { - s = Object::cast_to(p_object); + s = p_object; } } diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h index a5e0e54fb8..19ecfb00ed 100644 --- a/editor/plugins/sprite_frames_editor_plugin.h +++ b/editor/plugins/sprite_frames_editor_plugin.h @@ -33,6 +33,7 @@ #include "editor/editor_plugin.h" #include "scene/2d/animated_sprite_2d.h" +#include "scene/3d/sprite_3d.h" #include "scene/gui/button.h" #include "scene/gui/check_button.h" #include "scene/gui/dialogs.h" @@ -57,6 +58,9 @@ public: class SpriteFramesEditor : public HSplitContainer { GDCLASS(SpriteFramesEditor, HSplitContainer); + Ref frames; + Node *animated_sprite = nullptr; + enum { PARAM_USE_CURRENT, // Used in callbacks to indicate `dominant_param` should be not updated. PARAM_FRAME_COUNT, // Keep "Horizontal" & "Vertical" values. @@ -66,6 +70,17 @@ class SpriteFramesEditor : public HSplitContainer { bool read_only = false; + Ref autoplay_icon; + Ref stop_icon; + Ref pause_icon; + + HBoxContainer *playback_container = nullptr; + Button *stop = nullptr; + Button *play = nullptr; + Button *play_from = nullptr; + Button *play_bw = nullptr; + Button *play_bw_from = nullptr; + Button *load = nullptr; Button *load_sheet = nullptr; Button *delete_frame = nullptr; @@ -85,6 +100,8 @@ class SpriteFramesEditor : public HSplitContainer { Button *add_anim = nullptr; Button *delete_anim = nullptr; + HBoxContainer *autoplay_container = nullptr; + Button *autoplay = nullptr; LineEdit *anim_search_box = nullptr; Tree *animations = nullptr; @@ -95,8 +112,6 @@ class SpriteFramesEditor : public HSplitContainer { AcceptDialog *dialog = nullptr; - SpriteFrames *frames = nullptr; - StringName edited_anim; ConfirmationDialog *delete_dialog = nullptr; @@ -146,7 +161,15 @@ class SpriteFramesEditor : public HSplitContainer { void _frame_duration_changed(double p_value); void _update_library(bool p_skip_selector = false); - void _animation_select(); + void _update_stop_icon(); + void _play_pressed(); + void _play_from_pressed(); + void _play_bw_pressed(); + void _play_bw_from_pressed(); + void _autoplay_pressed(); + void _stop_pressed(); + + void _animation_selected(); void _animation_name_edited(); void _animation_add(); void _animation_remove(); @@ -183,12 +206,24 @@ class SpriteFramesEditor : public HSplitContainer { void _sheet_zoom_reset(); void _sheet_select_clear_all_frames(); + void _edit(); + void _regist_scene_undo(EditorUndoRedoManager *undo_redo); + void _fetch_sprite_node(); + void _remove_sprite_node(); + + bool sprite_node_updating = false; + void _sync_animation(); + + void _select_animation(const String &p_name, bool p_update_node = true); + void _rename_node_animation(EditorUndoRedoManager *undo_redo, bool is_undo, const String &p_filter, const String &p_new_animation, const String &p_new_autoplay); + protected: void _notification(int p_what); + void _node_removed(Node *p_node); static void _bind_methods(); public: - void edit(SpriteFrames *p_frames); + void edit(Ref p_frames); SpriteFramesEditor(); }; diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp index 5f61d10f9d..204575f021 100644 --- a/editor/project_converter_3_to_4.cpp +++ b/editor/project_converter_3_to_4.cpp @@ -215,7 +215,6 @@ static const char *gdscript_function_renames[][2] = { { "_get_configuration_warning", "_get_configuration_warnings" }, // Node { "_set_current", "set_current" }, // Camera2D { "_set_editor_description", "set_editor_description" }, // Node - { "_set_playing", "set_playing" }, // AnimatedSprite3D { "_toplevel_raise_self", "_top_level_raise_self" }, // CanvasItem { "_update_wrap_at", "_update_wrap_at_column" }, // TextEdit { "add_animation", "add_animation_library" }, // AnimationPlayer @@ -1168,6 +1167,7 @@ static const char *gdscript_properties_renames[][2] = { { "unit_db", "volume_db" }, // AudioStreamPlayer3D { "unit_offset", "progress_ratio" }, // PathFollow2D, PathFollow3D { "vseparation", "v_separation" }, // Theme + { "frames", "sprite_frames" }, // AnimatedSprite2D, AnimatedSprite3D { nullptr, nullptr }, }; diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp index a4a965aa41..ccf9696d82 100644 --- a/scene/2d/animated_sprite_2d.cpp +++ b/scene/2d/animated_sprite_2d.cpp @@ -117,7 +117,6 @@ void AnimatedSprite2D::_validate_property(PropertyInfo &p_property) const { } if (p_property.name == "animation") { - p_property.hint = PROPERTY_HINT_ENUM; List names; frames->get_animation_list(&names); names.sort_custom(); @@ -167,6 +166,12 @@ void AnimatedSprite2D::_validate_property(PropertyInfo &p_property) const { void AnimatedSprite2D::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_READY: { + if (!Engine::get_singleton()->is_editor_hint() && !frames.is_null() && frames->has_animation(autoplay)) { + play(autoplay); + } + } break; + case NOTIFICATION_INTERNAL_PROCESS: { if (frames.is_null() || !frames->has_animation(animation)) { return; @@ -176,7 +181,8 @@ void AnimatedSprite2D::_notification(int p_what) { int i = 0; while (remaining) { // Animation speed may be changed by animation_finished or frame_changed signals. - double speed = frames->get_animation_speed(animation) * Math::abs(speed_scale); + double speed = frames->get_animation_speed(animation) * speed_scale * custom_speed_scale * frame_speed_scale; + double abs_speed = Math::abs(speed); if (speed == 0) { return; // Do nothing. @@ -185,53 +191,57 @@ void AnimatedSprite2D::_notification(int p_what) { // Frame count may be changed by animation_finished or frame_changed signals. int fc = frames->get_frame_count(animation); - if (timeout <= 0) { - int last_frame = fc - 1; - if (!playing_backwards) { - // Forward. + int last_frame = fc - 1; + if (!signbit(speed)) { + // Forwards. + if (frame_progress >= 1.0) { if (frame >= last_frame) { if (frames->get_animation_loop(animation)) { frame = 0; - emit_signal(SceneStringNames::get_singleton()->animation_finished); + emit_signal("animation_looped"); } else { frame = last_frame; - if (!is_over) { - is_over = true; - emit_signal(SceneStringNames::get_singleton()->animation_finished); - } + pause(); + emit_signal(SceneStringNames::get_singleton()->animation_finished); + return; } } else { frame++; } - } else { - // Reversed. + _calc_frame_speed_scale(); + frame_progress = 0.0; + queue_redraw(); + emit_signal(SceneStringNames::get_singleton()->frame_changed); + } + double to_process = MIN((1.0 - frame_progress) / abs_speed, remaining); + frame_progress += to_process * abs_speed; + remaining -= to_process; + } else { + // Backwards. + if (frame_progress <= 0) { if (frame <= 0) { if (frames->get_animation_loop(animation)) { frame = last_frame; - emit_signal(SceneStringNames::get_singleton()->animation_finished); + emit_signal("animation_looped"); } else { frame = 0; - if (!is_over) { - is_over = true; - emit_signal(SceneStringNames::get_singleton()->animation_finished); - } + pause(); + emit_signal(SceneStringNames::get_singleton()->animation_finished); + return; } } else { frame--; } + _calc_frame_speed_scale(); + frame_progress = 1.0; + queue_redraw(); + emit_signal(SceneStringNames::get_singleton()->frame_changed); } - - timeout = _get_frame_duration(); - - queue_redraw(); - - emit_signal(SceneStringNames::get_singleton()->frame_changed); + double to_process = MIN(frame_progress / abs_speed, remaining); + frame_progress -= to_process * abs_speed; + remaining -= to_process; } - double to_process = MIN(timeout / speed, remaining); - timeout -= to_process * speed; - remaining -= to_process; - i++; if (i > fc) { return; // Prevents freezing if to_process is each time much less than remaining. @@ -275,25 +285,37 @@ void AnimatedSprite2D::_notification(int p_what) { } void AnimatedSprite2D::set_sprite_frames(const Ref &p_frames) { + if (frames == p_frames) { + return; + } + if (frames.is_valid()) { frames->disconnect(SceneStringNames::get_singleton()->changed, callable_mp(this, &AnimatedSprite2D::_res_changed)); } - + stop(); frames = p_frames; if (frames.is_valid()) { frames->connect(SceneStringNames::get_singleton()->changed, callable_mp(this, &AnimatedSprite2D::_res_changed)); - } - if (frames.is_null()) { - frame = 0; - } else { - set_frame(frame); + List al; + frames->get_animation_list(&al); + if (al.size() == 0) { + set_animation(StringName()); + set_autoplay(String()); + } else { + if (!frames->has_animation(animation)) { + set_animation(al[0]); + } + if (!frames->has_animation(autoplay)) { + set_autoplay(String()); + } + } } notify_property_list_changed(); - _reset_timeout(); queue_redraw(); update_configuration_warnings(); + emit_signal("sprite_frames_changed"); } Ref AnimatedSprite2D::get_sprite_frames() const { @@ -301,44 +323,63 @@ Ref AnimatedSprite2D::get_sprite_frames() const { } void AnimatedSprite2D::set_frame(int p_frame) { + set_frame_and_progress(p_frame, signbit(get_playing_speed()) ? 1.0 : 0.0); +} + +int AnimatedSprite2D::get_frame() const { + return frame; +} + +void AnimatedSprite2D::set_frame_progress(real_t p_progress) { + frame_progress = p_progress; +} + +real_t AnimatedSprite2D::get_frame_progress() const { + return frame_progress; +} + +void AnimatedSprite2D::set_frame_and_progress(int p_frame, real_t p_progress) { if (frames.is_null()) { return; } - if (frames->has_animation(animation)) { - int limit = frames->get_frame_count(animation); - if (p_frame >= limit) { - p_frame = limit - 1; - } - } + bool has_animation = frames->has_animation(animation); + int end_frame = has_animation ? MAX(0, frames->get_frame_count(animation) - 1) : 0; + bool is_changed = frame != p_frame; if (p_frame < 0) { - p_frame = 0; + frame = 0; + } else if (has_animation && p_frame > end_frame) { + frame = end_frame; + } else { + frame = p_frame; } - if (frame == p_frame) { - return; - } + _calc_frame_speed_scale(); + frame_progress = p_progress; - frame = p_frame; - _reset_timeout(); + if (!is_changed) { + return; // No change, don't redraw. + } queue_redraw(); emit_signal(SceneStringNames::get_singleton()->frame_changed); } -int AnimatedSprite2D::get_frame() const { - return frame; -} - void AnimatedSprite2D::set_speed_scale(float p_speed_scale) { speed_scale = p_speed_scale; - playing_backwards = signbit(speed_scale) != backwards; } float AnimatedSprite2D::get_speed_scale() const { return speed_scale; } +float AnimatedSprite2D::get_playing_speed() const { + if (!playing) { + return 0; + } + return speed_scale * custom_speed_scale; +} + void AnimatedSprite2D::set_centered(bool p_center) { centered = p_center; queue_redraw(); @@ -378,69 +419,130 @@ bool AnimatedSprite2D::is_flipped_v() const { } void AnimatedSprite2D::_res_changed() { - set_frame(frame); + set_frame_and_progress(frame, frame_progress); queue_redraw(); notify_property_list_changed(); } -void AnimatedSprite2D::set_playing(bool p_playing) { - if (playing == p_playing) { - return; +bool AnimatedSprite2D::is_playing() const { + return playing; +} + +void AnimatedSprite2D::set_autoplay(const String &p_name) { + if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) { + WARN_PRINT("Setting autoplay after the node has been added to the scene has no effect."); } - playing = p_playing; - playing_backwards = signbit(speed_scale) != backwards; - set_process_internal(playing); - notify_property_list_changed(); + + autoplay = p_name; } -bool AnimatedSprite2D::is_playing() const { - return playing; +String AnimatedSprite2D::get_autoplay() const { + return autoplay; } -void AnimatedSprite2D::play(const StringName &p_animation, bool p_backwards) { - backwards = p_backwards; - playing_backwards = signbit(speed_scale) != backwards; +void AnimatedSprite2D::play(const StringName &p_name, float p_custom_scale, bool p_from_end) { + StringName name = p_name; - if (p_animation) { - set_animation(p_animation); - if (frames.is_valid() && playing_backwards && get_frame() == 0) { - set_frame(frames->get_frame_count(p_animation) - 1); + if (name == StringName()) { + name = animation; + } + + ERR_FAIL_COND_MSG(frames == nullptr, vformat("There is no animation with name '%s'.", name)); + ERR_FAIL_COND_MSG(!frames->get_animation_names().has(name), vformat("There is no animation with name '%s'.", name)); + + if (frames->get_frame_count(name) == 0) { + return; + } + + playing = true; + custom_speed_scale = p_custom_scale; + + int end_frame = MAX(0, frames->get_frame_count(animation) - 1); + if (name != animation) { + animation = name; + if (p_from_end) { + set_frame_and_progress(end_frame, 1.0); + } else { + set_frame_and_progress(0, 0.0); } + emit_signal("animation_changed"); + } else { + bool is_backward = signbit(speed_scale * custom_speed_scale); + if (p_from_end && is_backward && frame == 0 && frame_progress <= 0.0) { + set_frame_and_progress(end_frame, 1.0); + } else if (!p_from_end && !is_backward && frame == end_frame && frame_progress >= 1.0) { + set_frame_and_progress(0, 0.0); + } + } + + notify_property_list_changed(); + set_process_internal(true); +} + +void AnimatedSprite2D::play_backwards(const StringName &p_name) { + play(p_name, -1, true); +} + +void AnimatedSprite2D::_stop_internal(bool p_reset) { + playing = false; + if (p_reset) { + custom_speed_scale = 1.0; + set_frame_and_progress(0, 0.0); } + notify_property_list_changed(); + set_process_internal(false); +} - is_over = false; - set_playing(true); +void AnimatedSprite2D::pause() { + _stop_internal(false); } void AnimatedSprite2D::stop() { - set_playing(false); - backwards = false; - _reset_timeout(); + _stop_internal(true); } double AnimatedSprite2D::_get_frame_duration() { if (frames.is_valid() && frames->has_animation(animation)) { return frames->get_frame_duration(animation, frame); } - return 0.0; + return 1.0; } -void AnimatedSprite2D::_reset_timeout() { - timeout = _get_frame_duration(); - is_over = false; +void AnimatedSprite2D::_calc_frame_speed_scale() { + frame_speed_scale = 1.0 / _get_frame_duration(); } -void AnimatedSprite2D::set_animation(const StringName &p_animation) { - ERR_FAIL_COND_MSG(frames == nullptr, vformat("There is no animation with name '%s'.", p_animation)); - ERR_FAIL_COND_MSG(!frames->get_animation_names().has(p_animation), vformat("There is no animation with name '%s'.", p_animation)); +void AnimatedSprite2D::set_animation(const StringName &p_name) { + if (animation == p_name) { + return; + } + + animation = p_name; + + emit_signal("animation_changed"); - if (animation == p_animation) { + if (frames == nullptr) { + animation = StringName(); + stop(); + ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name)); + } + + int frame_count = frames->get_frame_count(animation); + if (animation == StringName() || frame_count == 0) { + stop(); return; + } else if (!frames->get_animation_names().has(animation)) { + animation = StringName(); + stop(); + ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name)); + } + + if (signbit(get_playing_speed())) { + set_frame_and_progress(frame_count - 1, 1.0); + } else { + set_frame_and_progress(0, 0.0); } - animation = p_animation; - set_frame(0); - _reset_timeout(); notify_property_list_changed(); queue_redraw(); } @@ -468,17 +570,30 @@ void AnimatedSprite2D::get_argument_options(const StringName &p_function, int p_ Node::get_argument_options(p_function, p_idx, r_options); } +#ifndef DISABLE_DEPRECATED +bool AnimatedSprite2D::_set(const StringName &p_name, const Variant &p_value) { + if ((p_name == SNAME("frames"))) { + set_sprite_frames(p_value); + return true; + } + return false; +} +#endif void AnimatedSprite2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_sprite_frames", "sprite_frames"), &AnimatedSprite2D::set_sprite_frames); ClassDB::bind_method(D_METHOD("get_sprite_frames"), &AnimatedSprite2D::get_sprite_frames); - ClassDB::bind_method(D_METHOD("set_animation", "animation"), &AnimatedSprite2D::set_animation); + ClassDB::bind_method(D_METHOD("set_animation", "name"), &AnimatedSprite2D::set_animation); ClassDB::bind_method(D_METHOD("get_animation"), &AnimatedSprite2D::get_animation); - ClassDB::bind_method(D_METHOD("set_playing", "playing"), &AnimatedSprite2D::set_playing); + ClassDB::bind_method(D_METHOD("set_autoplay", "name"), &AnimatedSprite2D::set_autoplay); + ClassDB::bind_method(D_METHOD("get_autoplay"), &AnimatedSprite2D::get_autoplay); + ClassDB::bind_method(D_METHOD("is_playing"), &AnimatedSprite2D::is_playing); - ClassDB::bind_method(D_METHOD("play", "anim", "backwards"), &AnimatedSprite2D::play, DEFVAL(StringName()), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("play", "name", "custom_speed", "from_end"), &AnimatedSprite2D::play, DEFVAL(StringName()), DEFVAL(1.0), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("play_backwards", "name"), &AnimatedSprite2D::play_backwards, DEFVAL(StringName())); + ClassDB::bind_method(D_METHOD("pause"), &AnimatedSprite2D::pause); ClassDB::bind_method(D_METHOD("stop"), &AnimatedSprite2D::stop); ClassDB::bind_method(D_METHOD("set_centered", "centered"), &AnimatedSprite2D::set_centered); @@ -496,18 +611,28 @@ void AnimatedSprite2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_frame", "frame"), &AnimatedSprite2D::set_frame); ClassDB::bind_method(D_METHOD("get_frame"), &AnimatedSprite2D::get_frame); + ClassDB::bind_method(D_METHOD("set_frame_progress", "progress"), &AnimatedSprite2D::set_frame_progress); + ClassDB::bind_method(D_METHOD("get_frame_progress"), &AnimatedSprite2D::get_frame_progress); + + ClassDB::bind_method(D_METHOD("set_frame_and_progress", "frame", "progress"), &AnimatedSprite2D::set_frame_and_progress); + ClassDB::bind_method(D_METHOD("set_speed_scale", "speed_scale"), &AnimatedSprite2D::set_speed_scale); ClassDB::bind_method(D_METHOD("get_speed_scale"), &AnimatedSprite2D::get_speed_scale); + ClassDB::bind_method(D_METHOD("get_playing_speed"), &AnimatedSprite2D::get_playing_speed); + ADD_SIGNAL(MethodInfo("sprite_frames_changed")); + ADD_SIGNAL(MethodInfo("animation_changed")); ADD_SIGNAL(MethodInfo("frame_changed")); + ADD_SIGNAL(MethodInfo("animation_looped")); ADD_SIGNAL(MethodInfo("animation_finished")); ADD_GROUP("Animation", ""); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "frames", PROPERTY_HINT_RESOURCE_TYPE, "SpriteFrames"), "set_sprite_frames", "get_sprite_frames"); - ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation"), "set_animation", "get_animation"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "sprite_frames", PROPERTY_HINT_RESOURCE_TYPE, "SpriteFrames"), "set_sprite_frames", "get_sprite_frames"); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation", PROPERTY_HINT_ENUM, ""), "set_animation", "get_animation"); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "autoplay", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_autoplay", "get_autoplay"); ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "frame_progress", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_frame_progress", "get_frame_progress"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale"), "set_speed_scale", "get_speed_scale"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing"), "set_playing", "is_playing"); ADD_GROUP("Offset", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "centered"), "set_centered", "is_centered"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset"); diff --git a/scene/2d/animated_sprite_2d.h b/scene/2d/animated_sprite_2d.h index c1d35f3a2f..ac53bd26ee 100644 --- a/scene/2d/animated_sprite_2d.h +++ b/scene/2d/animated_sprite_2d.h @@ -38,18 +38,19 @@ class AnimatedSprite2D : public Node2D { GDCLASS(AnimatedSprite2D, Node2D); Ref frames; + String autoplay; + bool playing = false; - bool playing_backwards = false; - bool backwards = false; StringName animation = "default"; int frame = 0; float speed_scale = 1.0; + float custom_speed_scale = 1.0; bool centered = true; Point2 offset; - bool is_over = false; - float timeout = 0.0; + real_t frame_speed_scale = 1.0; + real_t frame_progress = 0.0; bool hflip = false; bool vflip = false; @@ -57,10 +58,15 @@ class AnimatedSprite2D : public Node2D { void _res_changed(); double _get_frame_duration(); - void _reset_timeout(); + void _calc_frame_speed_scale(); + void _stop_internal(bool p_reset); + Rect2 _get_rect() const; protected: +#ifndef DISABLE_DEPRECATED + bool _set(const StringName &p_name, const Variant &p_value); +#endif static void _bind_methods(); void _notification(int p_what); void _validate_property(PropertyInfo &p_property) const; @@ -82,20 +88,30 @@ public: void set_sprite_frames(const Ref &p_frames); Ref get_sprite_frames() const; - void play(const StringName &p_animation = StringName(), bool p_backwards = false); + void play(const StringName &p_name = StringName(), float p_custom_scale = 1.0, bool p_from_end = false); + void play_backwards(const StringName &p_name = StringName()); + void pause(); void stop(); - void set_playing(bool p_playing); bool is_playing() const; - void set_animation(const StringName &p_animation); + void set_animation(const StringName &p_name); StringName get_animation() const; + void set_autoplay(const String &p_name); + String get_autoplay() const; + void set_frame(int p_frame); int get_frame() const; + void set_frame_progress(real_t p_progress); + real_t get_frame_progress() const; + + void set_frame_and_progress(int p_frame, real_t p_progress); + void set_speed_scale(float p_speed_scale); float get_speed_scale() const; + float get_playing_speed() const; void set_centered(bool p_center); bool is_centered() const; diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index 71d76c0d1c..635c566ef8 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -866,7 +866,6 @@ void AnimatedSprite3D::_validate_property(PropertyInfo &p_property) const { } if (p_property.name == "animation") { - p_property.hint = PROPERTY_HINT_ENUM; List names; frames->get_animation_list(&names); names.sort_custom(); @@ -916,6 +915,12 @@ void AnimatedSprite3D::_validate_property(PropertyInfo &p_property) const { void AnimatedSprite3D::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_READY: { + if (!Engine::get_singleton()->is_editor_hint() && !frames.is_null() && frames->has_animation(autoplay)) { + play(autoplay); + } + } break; + case NOTIFICATION_INTERNAL_PROCESS: { if (frames.is_null() || !frames->has_animation(animation)) { return; @@ -925,7 +930,8 @@ void AnimatedSprite3D::_notification(int p_what) { int i = 0; while (remaining) { // Animation speed may be changed by animation_finished or frame_changed signals. - double speed = frames->get_animation_speed(animation) * Math::abs(speed_scale); + double speed = frames->get_animation_speed(animation) * speed_scale * custom_speed_scale * frame_speed_scale; + double abs_speed = Math::abs(speed); if (speed == 0) { return; // Do nothing. @@ -934,53 +940,57 @@ void AnimatedSprite3D::_notification(int p_what) { // Frame count may be changed by animation_finished or frame_changed signals. int fc = frames->get_frame_count(animation); - if (timeout <= 0) { - int last_frame = fc - 1; - if (!playing_backwards) { - // Forward. + int last_frame = fc - 1; + if (!signbit(speed)) { + // Forwards. + if (frame_progress >= 1.0) { if (frame >= last_frame) { if (frames->get_animation_loop(animation)) { frame = 0; - emit_signal(SceneStringNames::get_singleton()->animation_finished); + emit_signal("animation_looped"); } else { frame = last_frame; - if (!is_over) { - is_over = true; - emit_signal(SceneStringNames::get_singleton()->animation_finished); - } + pause(); + emit_signal(SceneStringNames::get_singleton()->animation_finished); + return; } } else { frame++; } - } else { - // Reversed. + _calc_frame_speed_scale(); + frame_progress = 0.0; + _queue_redraw(); + emit_signal(SceneStringNames::get_singleton()->frame_changed); + } + double to_process = MIN((1.0 - frame_progress) / abs_speed, remaining); + frame_progress += to_process * abs_speed; + remaining -= to_process; + } else { + // Backwards. + if (frame_progress <= 0) { if (frame <= 0) { if (frames->get_animation_loop(animation)) { frame = last_frame; - emit_signal(SceneStringNames::get_singleton()->animation_finished); + emit_signal("animation_looped"); } else { frame = 0; - if (!is_over) { - is_over = true; - emit_signal(SceneStringNames::get_singleton()->animation_finished); - } + pause(); + emit_signal(SceneStringNames::get_singleton()->animation_finished); + return; } } else { frame--; } + _calc_frame_speed_scale(); + frame_progress = 1.0; + _queue_redraw(); + emit_signal(SceneStringNames::get_singleton()->frame_changed); } - - timeout = _get_frame_duration(); - - _queue_redraw(); - - emit_signal(SceneStringNames::get_singleton()->frame_changed); + double to_process = MIN(frame_progress / abs_speed, remaining); + frame_progress -= to_process * abs_speed; + remaining -= to_process; } - double to_process = MIN(timeout / speed, remaining); - timeout -= to_process * speed; - remaining -= to_process; - i++; if (i > fc) { return; // Prevents freezing if to_process is each time much less than remaining. @@ -991,25 +1001,37 @@ void AnimatedSprite3D::_notification(int p_what) { } void AnimatedSprite3D::set_sprite_frames(const Ref &p_frames) { + if (frames == p_frames) { + return; + } + if (frames.is_valid()) { frames->disconnect(SceneStringNames::get_singleton()->changed, callable_mp(this, &AnimatedSprite3D::_res_changed)); } - + stop(); frames = p_frames; if (frames.is_valid()) { frames->connect(SceneStringNames::get_singleton()->changed, callable_mp(this, &AnimatedSprite3D::_res_changed)); - } - if (frames.is_null()) { - frame = 0; - } else { - set_frame(frame); + List al; + frames->get_animation_list(&al); + if (al.size() == 0) { + set_animation(StringName()); + set_autoplay(String()); + } else { + if (!frames->has_animation(animation)) { + set_animation(al[0]); + } + if (!frames->has_animation(autoplay)) { + set_autoplay(String()); + } + } } notify_property_list_changed(); - _reset_timeout(); _queue_redraw(); update_configuration_warnings(); + emit_signal("sprite_frames_changed"); } Ref AnimatedSprite3D::get_sprite_frames() const { @@ -1017,44 +1039,63 @@ Ref AnimatedSprite3D::get_sprite_frames() const { } void AnimatedSprite3D::set_frame(int p_frame) { + set_frame_and_progress(p_frame, signbit(get_playing_speed()) ? 1.0 : 0.0); +} + +int AnimatedSprite3D::get_frame() const { + return frame; +} + +void AnimatedSprite3D::set_frame_progress(real_t p_progress) { + frame_progress = p_progress; +} + +real_t AnimatedSprite3D::get_frame_progress() const { + return frame_progress; +} + +void AnimatedSprite3D::set_frame_and_progress(int p_frame, real_t p_progress) { if (frames.is_null()) { return; } - if (frames->has_animation(animation)) { - int limit = frames->get_frame_count(animation); - if (p_frame >= limit) { - p_frame = limit - 1; - } - } + bool has_animation = frames->has_animation(animation); + int end_frame = has_animation ? MAX(0, frames->get_frame_count(animation) - 1) : 0; + bool is_changed = frame != p_frame; if (p_frame < 0) { - p_frame = 0; + frame = 0; + } else if (has_animation && p_frame > end_frame) { + frame = end_frame; + } else { + frame = p_frame; } - if (frame == p_frame) { - return; - } + _calc_frame_speed_scale(); + frame_progress = p_progress; - frame = p_frame; - _reset_timeout(); + if (!is_changed) { + return; // No change, don't redraw. + } _queue_redraw(); emit_signal(SceneStringNames::get_singleton()->frame_changed); } -int AnimatedSprite3D::get_frame() const { - return frame; -} - void AnimatedSprite3D::set_speed_scale(float p_speed_scale) { speed_scale = p_speed_scale; - playing_backwards = signbit(speed_scale) != backwards; } float AnimatedSprite3D::get_speed_scale() const { return speed_scale; } +float AnimatedSprite3D::get_playing_speed() const { + if (!playing) { + return 0; + } + return speed_scale * custom_speed_scale; +} + Rect2 AnimatedSprite3D::get_item_rect() const { if (frames.is_null() || !frames->has_animation(animation)) { return Rect2(0, 0, 1, 1); @@ -1085,69 +1126,130 @@ Rect2 AnimatedSprite3D::get_item_rect() const { } void AnimatedSprite3D::_res_changed() { - set_frame(frame); + set_frame_and_progress(frame, frame_progress); _queue_redraw(); notify_property_list_changed(); } -void AnimatedSprite3D::set_playing(bool p_playing) { - if (playing == p_playing) { - return; +bool AnimatedSprite3D::is_playing() const { + return playing; +} + +void AnimatedSprite3D::set_autoplay(const String &p_name) { + if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) { + WARN_PRINT("Setting autoplay after the node has been added to the scene has no effect."); } - playing = p_playing; - playing_backwards = signbit(speed_scale) != backwards; - set_process_internal(playing); - notify_property_list_changed(); + + autoplay = p_name; } -bool AnimatedSprite3D::is_playing() const { - return playing; +String AnimatedSprite3D::get_autoplay() const { + return autoplay; } -void AnimatedSprite3D::play(const StringName &p_animation, bool p_backwards) { - backwards = p_backwards; - playing_backwards = signbit(speed_scale) != backwards; +void AnimatedSprite3D::play(const StringName &p_name, float p_custom_scale, bool p_from_end) { + StringName name = p_name; + + if (name == StringName()) { + name = animation; + } + + ERR_FAIL_COND_MSG(frames == nullptr, vformat("There is no animation with name '%s'.", name)); + ERR_FAIL_COND_MSG(!frames->get_animation_names().has(name), vformat("There is no animation with name '%s'.", name)); - if (p_animation) { - set_animation(p_animation); - if (frames.is_valid() && playing_backwards && get_frame() == 0) { - set_frame(frames->get_frame_count(p_animation) - 1); + if (frames->get_frame_count(name) == 0) { + return; + } + + playing = true; + custom_speed_scale = p_custom_scale; + + int end_frame = MAX(0, frames->get_frame_count(animation) - 1); + if (name != animation) { + animation = name; + if (p_from_end) { + set_frame_and_progress(end_frame, 1.0); + } else { + set_frame_and_progress(0, 0.0); + } + emit_signal("animation_changed"); + } else { + bool is_backward = signbit(speed_scale * custom_speed_scale); + if (p_from_end && is_backward && frame == 0 && frame_progress <= 0.0) { + set_frame_and_progress(end_frame, 1.0); + } else if (!p_from_end && !is_backward && frame == end_frame && frame_progress >= 1.0) { + set_frame_and_progress(0, 0.0); } } - is_over = false; - set_playing(true); + notify_property_list_changed(); + set_process_internal(true); +} + +void AnimatedSprite3D::play_backwards(const StringName &p_name) { + play(p_name, -1, true); +} + +void AnimatedSprite3D::_stop_internal(bool p_reset) { + playing = false; + if (p_reset) { + custom_speed_scale = 1.0; + set_frame_and_progress(0, 0.0); + } + notify_property_list_changed(); + set_process_internal(false); +} + +void AnimatedSprite3D::pause() { + _stop_internal(false); } void AnimatedSprite3D::stop() { - set_playing(false); - backwards = false; - _reset_timeout(); + _stop_internal(true); } double AnimatedSprite3D::_get_frame_duration() { if (frames.is_valid() && frames->has_animation(animation)) { return frames->get_frame_duration(animation, frame); } - return 0.0; + return 1.0; } -void AnimatedSprite3D::_reset_timeout() { - timeout = _get_frame_duration(); - is_over = false; +void AnimatedSprite3D::_calc_frame_speed_scale() { + frame_speed_scale = 1.0 / _get_frame_duration(); } -void AnimatedSprite3D::set_animation(const StringName &p_animation) { - ERR_FAIL_COND_MSG(frames == nullptr, vformat("There is no animation with name '%s'.", p_animation)); - ERR_FAIL_COND_MSG(!frames->get_animation_names().has(p_animation), vformat("There is no animation with name '%s'.", p_animation)); +void AnimatedSprite3D::set_animation(const StringName &p_name) { + if (animation == p_name) { + return; + } + + animation = p_name; - if (animation == p_animation) { + emit_signal("animation_changed"); + + if (frames == nullptr) { + animation = StringName(); + stop(); + ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name)); + } + + int frame_count = frames->get_frame_count(animation); + if (animation == StringName() || frame_count == 0) { + stop(); return; + } else if (!frames->get_animation_names().has(animation)) { + animation = StringName(); + stop(); + ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name)); + } + + if (signbit(get_playing_speed())) { + set_frame_and_progress(frame_count - 1, 1.0); + } else { + set_frame_and_progress(0, 0.0); } - animation = p_animation; - set_frame(0); - _reset_timeout(); notify_property_list_changed(); _queue_redraw(); } @@ -1175,35 +1277,58 @@ void AnimatedSprite3D::get_argument_options(const StringName &p_function, int p_ Node::get_argument_options(p_function, p_idx, r_options); } +#ifndef DISABLE_DEPRECATED +bool AnimatedSprite3D::_set(const StringName &p_name, const Variant &p_value) { + if ((p_name == SNAME("frames"))) { + set_sprite_frames(p_value); + return true; + } + return false; +} +#endif void AnimatedSprite3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_sprite_frames", "sprite_frames"), &AnimatedSprite3D::set_sprite_frames); ClassDB::bind_method(D_METHOD("get_sprite_frames"), &AnimatedSprite3D::get_sprite_frames); - ClassDB::bind_method(D_METHOD("set_animation", "animation"), &AnimatedSprite3D::set_animation); + ClassDB::bind_method(D_METHOD("set_animation", "name"), &AnimatedSprite3D::set_animation); ClassDB::bind_method(D_METHOD("get_animation"), &AnimatedSprite3D::get_animation); - ClassDB::bind_method(D_METHOD("set_playing", "playing"), &AnimatedSprite3D::set_playing); + ClassDB::bind_method(D_METHOD("set_autoplay", "name"), &AnimatedSprite3D::set_autoplay); + ClassDB::bind_method(D_METHOD("get_autoplay"), &AnimatedSprite3D::get_autoplay); + ClassDB::bind_method(D_METHOD("is_playing"), &AnimatedSprite3D::is_playing); - ClassDB::bind_method(D_METHOD("play", "anim", "backwards"), &AnimatedSprite3D::play, DEFVAL(StringName()), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("play", "name", "custom_speed", "from_end"), &AnimatedSprite3D::play, DEFVAL(StringName()), DEFVAL(1.0), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("play_backwards", "name"), &AnimatedSprite3D::play_backwards, DEFVAL(StringName())); + ClassDB::bind_method(D_METHOD("pause"), &AnimatedSprite3D::pause); ClassDB::bind_method(D_METHOD("stop"), &AnimatedSprite3D::stop); ClassDB::bind_method(D_METHOD("set_frame", "frame"), &AnimatedSprite3D::set_frame); ClassDB::bind_method(D_METHOD("get_frame"), &AnimatedSprite3D::get_frame); + ClassDB::bind_method(D_METHOD("set_frame_progress", "progress"), &AnimatedSprite3D::set_frame_progress); + ClassDB::bind_method(D_METHOD("get_frame_progress"), &AnimatedSprite3D::get_frame_progress); + + ClassDB::bind_method(D_METHOD("set_frame_and_progress", "frame", "progress"), &AnimatedSprite3D::set_frame_and_progress); + ClassDB::bind_method(D_METHOD("set_speed_scale", "speed_scale"), &AnimatedSprite3D::set_speed_scale); ClassDB::bind_method(D_METHOD("get_speed_scale"), &AnimatedSprite3D::get_speed_scale); + ClassDB::bind_method(D_METHOD("get_playing_speed"), &AnimatedSprite3D::get_playing_speed); ClassDB::bind_method(D_METHOD("_res_changed"), &AnimatedSprite3D::_res_changed); + ADD_SIGNAL(MethodInfo("sprite_frames_changed")); + ADD_SIGNAL(MethodInfo("animation_changed")); ADD_SIGNAL(MethodInfo("frame_changed")); + ADD_SIGNAL(MethodInfo("animation_looped")); ADD_SIGNAL(MethodInfo("animation_finished")); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "frames", PROPERTY_HINT_RESOURCE_TYPE, "SpriteFrames"), "set_sprite_frames", "get_sprite_frames"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "animation"), "set_animation", "get_animation"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "sprite_frames", PROPERTY_HINT_RESOURCE_TYPE, "SpriteFrames"), "set_sprite_frames", "get_sprite_frames"); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation", PROPERTY_HINT_ENUM, ""), "set_animation", "get_animation"); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "autoplay", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_autoplay", "get_autoplay"); ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "frame_progress", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_frame_progress", "get_frame_progress"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale"), "set_speed_scale", "get_speed_scale"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing"), "set_playing", "is_playing"); } AnimatedSprite3D::AnimatedSprite3D() { diff --git a/scene/3d/sprite_3d.h b/scene/3d/sprite_3d.h index c5509aa723..d0fd767d89 100644 --- a/scene/3d/sprite_3d.h +++ b/scene/3d/sprite_3d.h @@ -209,24 +209,29 @@ class AnimatedSprite3D : public SpriteBase3D { GDCLASS(AnimatedSprite3D, SpriteBase3D); Ref frames; + String autoplay; + bool playing = false; - bool playing_backwards = false; - bool backwards = false; StringName animation = "default"; int frame = 0; float speed_scale = 1.0; + float custom_speed_scale = 1.0; bool centered = false; - bool is_over = false; - double timeout = 0.0; + real_t frame_speed_scale = 1.0; + real_t frame_progress = 0.0; void _res_changed(); double _get_frame_duration(); - void _reset_timeout(); + void _calc_frame_speed_scale(); + void _stop_internal(bool p_reset); protected: +#ifndef DISABLE_DEPRECATED + bool _set(const StringName &p_name, const Variant &p_value); +#endif virtual void _draw() override; static void _bind_methods(); void _notification(int p_what); @@ -236,20 +241,30 @@ public: void set_sprite_frames(const Ref &p_frames); Ref get_sprite_frames() const; - void play(const StringName &p_animation = StringName(), bool p_backwards = false); + void play(const StringName &p_name = StringName(), float p_custom_scale = 1.0, bool p_from_end = false); + void play_backwards(const StringName &p_name = StringName()); + void pause(); void stop(); - void set_playing(bool p_playing); bool is_playing() const; - void set_animation(const StringName &p_animation); + void set_animation(const StringName &p_name); StringName get_animation() const; + void set_autoplay(const String &p_name); + String get_autoplay() const; + void set_frame(int p_frame); int get_frame() const; + void set_frame_progress(real_t p_progress); + real_t get_frame_progress() const; + + void set_frame_and_progress(int p_frame, real_t p_progress); + void set_speed_scale(float p_speed_scale); float get_speed_scale() const; + float get_playing_speed() const; virtual Rect2 get_item_rect() const override; diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 047997ca09..1098188883 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -1709,8 +1709,11 @@ bool AnimationPlayer::is_playing() const { void AnimationPlayer::set_current_animation(const String &p_anim) { if (p_anim == "[stop]" || p_anim.is_empty()) { stop(); - } else if (!is_playing() || playback.assigned != p_anim) { + } else if (!is_playing()) { play(p_anim); + } else if (playback.assigned != p_anim) { + float speed = get_playing_speed(); + play(p_anim, -1.0, speed, signbit(speed)); } else { // Same animation, do not replay from start } @@ -1722,7 +1725,8 @@ String AnimationPlayer::get_current_animation() const { void AnimationPlayer::set_assigned_animation(const String &p_anim) { if (is_playing()) { - play(p_anim); + float speed = get_playing_speed(); + play(p_anim, -1.0, speed, signbit(speed)); } else { ERR_FAIL_COND_MSG(!animation_set.has(p_anim), vformat("Animation not found: %s.", p_anim)); playback.current.pos = 0; @@ -2202,7 +2206,7 @@ void AnimationPlayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_process_mode", PROPERTY_HINT_ENUM, "Physics,Idle,Manual"), "set_process_callback", "get_process_callback"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_default_blend_time", PROPERTY_HINT_RANGE, "0,4096,0.01,suffix:s"), "set_default_blend_time", "get_default_blend_time"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playback_active", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_active", "is_active"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_speed", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::INT, "method_call_mode", PROPERTY_HINT_ENUM, "Deferred,Immediate"), "set_method_call_mode", "get_method_call_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "movie_quit_on_finish"), "set_movie_quit_on_finish_enabled", "is_movie_quit_on_finish_enabled"); diff --git a/scene/resources/sprite_frames.cpp b/scene/resources/sprite_frames.cpp index c101974248..818be38681 100644 --- a/scene/resources/sprite_frames.cpp +++ b/scene/resources/sprite_frames.cpp @@ -36,7 +36,7 @@ void SpriteFrames::add_frame(const StringName &p_anim, const Ref &p_t HashMap::Iterator E = animations.find(p_anim); ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist."); - p_duration = MAX(0.0, p_duration); + p_duration = MAX(SPRITE_FRAME_MINIMUM_DURATION, p_duration); Frame frame = { p_texture, p_duration }; @@ -57,7 +57,7 @@ void SpriteFrames::set_frame(const StringName &p_anim, int p_idx, const Ref::ConstIterator E = animations.find(p_anim); - ERR_FAIL_COND_V_MSG(!E, 0.0, "Animation '" + String(p_anim) + "' doesn't exist."); - ERR_FAIL_COND_V(p_idx < 0, 0.0); + ERR_FAIL_COND_V_MSG(!E, 1.0, "Animation '" + String(p_anim) + "' doesn't exist."); + ERR_FAIL_COND_V(p_idx < 0, 1.0); if (p_idx >= E->value.frames.size()) { - return 0.0; + return 1.0; } return E->value.frames[p_idx].duration; -- cgit v1.2.3