AnimatedSprite{2D,3D} improvements

* Add support for individual frame duration to `SpriteFrames`.
* Various minor improvements.
This commit is contained in:
Danil Alexeev 2022-11-26 17:00:38 +03:00
parent dc3175e4cd
commit 0d25d8e7fc
No known key found for this signature in database
GPG key ID: 124453E157DA8DC7
13 changed files with 379 additions and 230 deletions

View file

@ -6,7 +6,7 @@
<description>
[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, call [method stop] or set [member playing] to [code]false[/code]. Alternatively, setting [member speed_scale] to [code]0[/code] also preserves the current frame's elapsed time.
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.
[b]Note:[/b] You can associate a set of normal or specular maps by creating additional [SpriteFrames] resources with a [code]_normal[/code] or [code]_specular[/code] suffix. For example, having 3 [SpriteFrames] resources [code]run[/code], [code]run_normal[/code], and [code]run_specular[/code] will make it so the [code]run[/code] animation uses normal and specular maps.
</description>
<tutorials>
@ -20,13 +20,14 @@
<param index="1" name="backwards" type="bool" default="false" />
<description>
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.
</description>
</method>
<method name="stop">
<return type="void" />
<description>
Stops the current [member animation] at the current [member frame].
[b]Note:[/b] This method resets the current frame's elapsed time. If this behavior is undesired, consider setting [member speed_scale] to [code]0[/code], instead.
[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.
</description>
</method>
</methods>
@ -53,7 +54,9 @@
The texture's drawing offset.
</member>
<member name="playing" type="bool" setter="set_playing" getter="is_playing" default="false">
If [code]true[/code], the [member animation] is currently playing. Setting this property to [code]false[/code] is the equivalent of calling [method stop].
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].
</member>
<member name="speed_scale" type="float" setter="set_speed_scale" getter="get_speed_scale" default="1.0">
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.
@ -62,7 +65,7 @@
<signals>
<signal name="animation_finished">
<description>
Emitted when the animation is finished (when it plays the last frame). If the animation is looping, this signal is emitted every time the last frame is drawn.
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.
</description>
</signal>
<signal name="frame_changed">

View file

@ -6,7 +6,7 @@
<description>
[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, call [method stop] or set [member playing] to [code]false[/code]. Alternatively, setting [member speed_scale] to [code]0[/code] also preserves the current frame's elapsed time.
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.
</description>
<tutorials>
<link title="2D Sprite animation (also applies to 3D)">$DOCS_URL/tutorials/2d/2d_sprite_animation.html</link>
@ -18,13 +18,14 @@
<param index="1" name="backwards" type="bool" default="false" />
<description>
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.
</description>
</method>
<method name="stop">
<return type="void" />
<description>
Stops the current [member animation] at the current [member frame].
[b]Note:[/b] This method resets the current frame's elapsed time. If this behavior is undesired, consider setting [member speed_scale] to [code]0[/code], instead.
[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.
</description>
</method>
</methods>
@ -39,7 +40,9 @@
The [SpriteFrames] resource containing the animation(s).
</member>
<member name="playing" type="bool" setter="set_playing" getter="is_playing" default="false">
If [code]true[/code], the [member animation] is currently playing. Setting this property to [code]false[/code] is the equivalent of calling [method stop].
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].
</member>
<member name="speed_scale" type="float" setter="set_speed_scale" getter="get_speed_scale" default="1.0">
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.
@ -48,7 +51,7 @@
<signals>
<signal name="animation_finished">
<description>
Emitted when the animation is finished (when it plays the last frame). If the animation is looping, this signal is emitted every time the last frame is drawn.
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.
</description>
</signal>
<signal name="frame_changed">

View file

@ -20,8 +20,9 @@
<method name="add_frame">
<return type="void" />
<param index="0" name="anim" type="StringName" />
<param index="1" name="frame" type="Texture2D" />
<param index="2" name="at_position" type="int" default="-1" />
<param index="1" name="texture" type="Texture2D" />
<param index="2" name="duration" type="float" default="1.0" />
<param index="3" name="at_position" type="int" default="-1" />
<description>
Adds a frame to the given animation.
</description>
@ -56,22 +57,34 @@
<return type="float" />
<param index="0" name="anim" type="StringName" />
<description>
The animation's speed in frames per second.
</description>
</method>
<method name="get_frame" qualifiers="const">
<return type="Texture2D" />
<param index="0" name="anim" type="StringName" />
<param index="1" name="idx" type="int" />
<description>
Returns the animation's selected frame.
Returns the speed in frames per second for the [param anim] animation.
</description>
</method>
<method name="get_frame_count" qualifiers="const">
<return type="int" />
<param index="0" name="anim" type="StringName" />
<description>
Returns the number of frames in the animation.
Returns the number of frames for the [param anim] animation.
</description>
</method>
<method name="get_frame_duration" qualifiers="const">
<return type="float" />
<param index="0" name="anim" type="StringName" />
<param index="1" name="idx" type="int" />
<description>
Returns a relative duration of the frame [param idx] in the [param anim] animation (defaults to [code]1.0[/code]). For example, a frame with a duration of [code]2.0[/code] is displayed twice as long as a frame with a duration of [code]1.0[/code]. You can calculate the absolute duration (in seconds) of a frame using the following formula:
[codeblock]
absolute_duration = relative_duration / (animation_fps * abs(speed_scale))
[/codeblock]
In this example, [code]speed_scale[/code] refers to either [member AnimatedSprite2D.speed_scale] or [member AnimatedSprite3D.speed_scale].
</description>
</method>
<method name="get_frame_texture" qualifiers="const">
<return type="Texture2D" />
<param index="0" name="anim" type="StringName" />
<param index="1" name="idx" type="int" />
<description>
Returns the texture of the frame [param idx] in the [param anim] animation.
</description>
</method>
<method name="has_animation" qualifiers="const">
@ -115,18 +128,19 @@
<method name="set_animation_speed">
<return type="void" />
<param index="0" name="anim" type="StringName" />
<param index="1" name="speed" type="float" />
<param index="1" name="fps" type="float" />
<description>
The animation's speed in frames per second.
Sets the speed for the [param anim] animation in frames per second.
</description>
</method>
<method name="set_frame">
<return type="void" />
<param index="0" name="anim" type="StringName" />
<param index="1" name="idx" type="int" />
<param index="2" name="txt" type="Texture2D" />
<param index="2" name="texture" type="Texture2D" />
<param index="3" name="duration" type="float" default="1.0" />
<description>
Sets the texture of the given frame.
Sets the texture and the duration of the frame [param idx] in the [param anim] animation.
</description>
</method>
</methods>

View file

@ -426,7 +426,7 @@ Rect2 AnimationTrackEditSpriteFrame::get_key_rect(int p_index, float p_pixels_se
animation_name = get_animation()->track_get_key_value(animation_track, animaiton_index);
}
Ref<Texture2D> texture = sf->get_frame(animation_name, frame);
Ref<Texture2D> texture = sf->get_frame_texture(animation_name, frame);
if (!texture.is_valid()) {
return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec);
}
@ -518,7 +518,7 @@ void AnimationTrackEditSpriteFrame::draw_key(int p_index, float p_pixels_sec, in
animation_name = get_animation()->track_get_key_value(animation_track, animaiton_index);
}
texture = sf->get_frame(animation_name, frame);
texture = sf->get_frame_texture(animation_name, frame);
if (!texture.is_valid()) {
AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right);
return;

View file

@ -265,7 +265,7 @@ 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);
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);
}
@ -375,9 +375,9 @@ void SpriteFramesEditor::_sheet_spin_changed(double p_value, int p_dominant_para
void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) {
Ref<Texture2D> texture = ResourceLoader::load(p_file);
if (!texture.is_valid()) {
if (texture.is_null()) {
EditorNode::get_singleton()->show_warning(TTR("Unable to load images"));
ERR_FAIL_COND(!texture.is_valid());
ERR_FAIL_COND(texture.is_null());
}
frames_selected.clear();
last_frame_selected = -1;
@ -477,7 +477,7 @@ void SpriteFramesEditor::_file_load_request(const Vector<String> &p_path, int p_
int count = 0;
for (const Ref<Texture2D> &E : resources) {
undo_redo->add_do_method(frames, "add_frame", edited_anim, E, p_at_pos == -1 ? -1 : p_at_pos + count);
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);
count++;
}
@ -521,8 +521,18 @@ void SpriteFramesEditor::_load_pressed() {
void SpriteFramesEditor::_paste_pressed() {
ERR_FAIL_COND(!frames->has_animation(edited_anim));
Ref<Texture2D> r = EditorSettings::get_singleton()->get_resource_clipboard();
if (!r.is_valid()) {
Ref<Texture2D> texture;
float duration = 1.0;
Ref<EditorSpriteFramesFrame> frame = EditorSettings::get_singleton()->get_resource_clipboard();
if (frame.is_valid()) {
texture = frame->texture;
duration = frame->duration;
} else {
texture = EditorSettings::get_singleton()->get_resource_clipboard();
}
if (texture.is_null()) {
dialog->set_text(TTR("Resource clipboard is empty or not a texture!"));
dialog->set_title(TTR("Error!"));
//dialog->get_cancel()->set_text("Close");
@ -533,7 +543,7 @@ void SpriteFramesEditor::_paste_pressed() {
Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo();
undo_redo->create_action(TTR("Paste Frame"));
undo_redo->add_do_method(frames, "add_frame", edited_anim, r);
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->add_do_method(this, "_update_library");
undo_redo->add_undo_method(this, "_update_library");
@ -543,15 +553,20 @@ void SpriteFramesEditor::_paste_pressed() {
void SpriteFramesEditor::_copy_pressed() {
ERR_FAIL_COND(!frames->has_animation(edited_anim));
if (tree->get_current() < 0) {
return;
}
Ref<Texture2D> r = frames->get_frame(edited_anim, tree->get_current());
if (!r.is_valid()) {
if (frame_list->get_current() < 0) {
return;
}
EditorSettings::get_singleton()->set_resource_clipboard(r);
Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, frame_list->get_current());
if (texture.is_null()) {
return;
}
Ref<EditorSpriteFramesFrame> frame = memnew(EditorSpriteFramesFrame);
frame->texture = texture;
frame->duration = frames->get_frame_duration(edited_anim, frame_list->get_current());
EditorSettings::get_singleton()->set_resource_clipboard(frame);
}
void SpriteFramesEditor::_empty_pressed() {
@ -559,19 +574,19 @@ void SpriteFramesEditor::_empty_pressed() {
int from = -1;
if (tree->get_current() >= 0) {
from = tree->get_current();
if (frame_list->get_current() >= 0) {
from = frame_list->get_current();
sel = from;
} else {
from = frames->get_frame_count(edited_anim);
}
Ref<Texture2D> r;
Ref<Texture2D> texture;
Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo();
undo_redo->create_action(TTR("Add Empty"));
undo_redo->add_do_method(frames, "add_frame", edited_anim, r, from);
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->add_do_method(this, "_update_library");
undo_redo->add_undo_method(this, "_update_library");
@ -583,19 +598,19 @@ void SpriteFramesEditor::_empty2_pressed() {
int from = -1;
if (tree->get_current() >= 0) {
from = tree->get_current();
if (frame_list->get_current() >= 0) {
from = frame_list->get_current();
sel = from;
} else {
from = frames->get_frame_count(edited_anim);
}
Ref<Texture2D> r;
Ref<Texture2D> texture;
Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo();
undo_redo->create_action(TTR("Add Empty"));
undo_redo->add_do_method(frames, "add_frame", edited_anim, r, from + 1);
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->add_do_method(this, "_update_library");
undo_redo->add_undo_method(this, "_update_library");
@ -605,11 +620,11 @@ void SpriteFramesEditor::_empty2_pressed() {
void SpriteFramesEditor::_up_pressed() {
ERR_FAIL_COND(!frames->has_animation(edited_anim));
if (tree->get_current() < 0) {
if (frame_list->get_current() < 0) {
return;
}
int to_move = tree->get_current();
int to_move = frame_list->get_current();
if (to_move < 1) {
return;
}
@ -618,11 +633,11 @@ void SpriteFramesEditor::_up_pressed() {
sel -= 1;
Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo();
undo_redo->create_action(TTR("Delete Resource"));
undo_redo->add_do_method(frames, "set_frame", edited_anim, to_move, frames->get_frame(edited_anim, to_move - 1));
undo_redo->add_do_method(frames, "set_frame", edited_anim, to_move - 1, frames->get_frame(edited_anim, to_move));
undo_redo->add_undo_method(frames, "set_frame", edited_anim, to_move, frames->get_frame(edited_anim, to_move));
undo_redo->add_undo_method(frames, "set_frame", edited_anim, to_move - 1, frames->get_frame(edited_anim, to_move - 1));
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->add_do_method(this, "_update_library");
undo_redo->add_undo_method(this, "_update_library");
undo_redo->commit_action();
@ -631,11 +646,11 @@ void SpriteFramesEditor::_up_pressed() {
void SpriteFramesEditor::_down_pressed() {
ERR_FAIL_COND(!frames->has_animation(edited_anim));
if (tree->get_current() < 0) {
if (frame_list->get_current() < 0) {
return;
}
int to_move = tree->get_current();
int to_move = frame_list->get_current();
if (to_move < 0 || to_move >= frames->get_frame_count(edited_anim) - 1) {
return;
}
@ -644,11 +659,11 @@ void SpriteFramesEditor::_down_pressed() {
sel += 1;
Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo();
undo_redo->create_action(TTR("Delete Resource"));
undo_redo->add_do_method(frames, "set_frame", edited_anim, to_move, frames->get_frame(edited_anim, to_move + 1));
undo_redo->add_do_method(frames, "set_frame", edited_anim, to_move + 1, frames->get_frame(edited_anim, to_move));
undo_redo->add_undo_method(frames, "set_frame", edited_anim, to_move, frames->get_frame(edited_anim, to_move));
undo_redo->add_undo_method(frames, "set_frame", edited_anim, to_move + 1, frames->get_frame(edited_anim, to_move + 1));
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->add_do_method(this, "_update_library");
undo_redo->add_undo_method(this, "_update_library");
undo_redo->commit_action();
@ -657,11 +672,11 @@ void SpriteFramesEditor::_down_pressed() {
void SpriteFramesEditor::_delete_pressed() {
ERR_FAIL_COND(!frames->has_animation(edited_anim));
if (tree->get_current() < 0) {
if (frame_list->get_current() < 0) {
return;
}
int to_delete = tree->get_current();
int to_delete = frame_list->get_current();
if (to_delete < 0 || to_delete >= frames->get_frame_count(edited_anim)) {
return;
}
@ -669,7 +684,7 @@ void SpriteFramesEditor::_delete_pressed() {
Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo();
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(edited_anim, to_delete), 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->add_do_method(this, "_update_library");
undo_redo->add_undo_method(this, "_update_library");
undo_redo->commit_action();
@ -683,7 +698,7 @@ void SpriteFramesEditor::_animation_select() {
if (frames->has_animation(edited_anim)) {
double value = anim_speed->get_line_edit()->get_text().to_float();
if (!Math::is_equal_approx(value, (double)frames->get_animation_speed(edited_anim))) {
_animation_fps_changed(value);
_animation_speed_changed(value);
}
}
@ -824,8 +839,9 @@ void SpriteFramesEditor::_animation_remove_confirmed() {
undo_redo->add_undo_method(frames, "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<Texture2D> frame = frames->get_frame(edited_anim, i);
undo_redo->add_undo_method(frames, "add_frame", edited_anim, frame);
Ref<Texture2D> 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_do_method(this, "_update_library");
undo_redo->add_undo_method(this, "_update_library");
@ -853,7 +869,7 @@ void SpriteFramesEditor::_animation_loop_changed() {
undo_redo->commit_action();
}
void SpriteFramesEditor::_animation_fps_changed(double p_value) {
void SpriteFramesEditor::_animation_speed_changed(double p_value) {
if (updating) {
return;
}
@ -864,11 +880,10 @@ void SpriteFramesEditor::_animation_fps_changed(double p_value) {
undo_redo->add_undo_method(frames, "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();
}
void SpriteFramesEditor::_tree_input(const Ref<InputEvent> &p_event) {
void SpriteFramesEditor::_frame_list_gui_input(const Ref<InputEvent> &p_event) {
const Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
@ -884,6 +899,42 @@ void SpriteFramesEditor::_tree_input(const Ref<InputEvent> &p_event) {
}
}
void SpriteFramesEditor::_frame_list_item_selected(int p_index) {
if (updating) {
return;
}
sel = p_index;
updating = true;
frame_duration->set_value(frames->get_frame_duration(edited_anim, p_index));
updating = false;
}
void SpriteFramesEditor::_frame_duration_changed(double p_value) {
if (updating) {
return;
}
int index = frame_list->get_current();
if (index < 0) {
return;
}
sel = index;
Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, index);
float old_duration = frames->get_frame_duration(edited_anim, index);
Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo();
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->add_do_method(this, "_update_library");
undo_redo->add_undo_method(this, "_update_library");
undo_redo->commit_action();
}
void SpriteFramesEditor::_zoom_in() {
// Do not zoom in or out with no visible frames
if (frames->get_frame_count(edited_anim) <= 0) {
@ -892,8 +943,8 @@ void SpriteFramesEditor::_zoom_in() {
if (thumbnail_zoom < max_thumbnail_zoom) {
thumbnail_zoom *= scale_ratio;
int thumbnail_size = (int)(thumbnail_default_size * thumbnail_zoom);
tree->set_fixed_column_width(thumbnail_size * 3 / 2);
tree->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));
frame_list->set_fixed_column_width(thumbnail_size * 3 / 2);
frame_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));
}
}
@ -905,20 +956,22 @@ void SpriteFramesEditor::_zoom_out() {
if (thumbnail_zoom > min_thumbnail_zoom) {
thumbnail_zoom /= scale_ratio;
int thumbnail_size = (int)(thumbnail_default_size * thumbnail_zoom);
tree->set_fixed_column_width(thumbnail_size * 3 / 2);
tree->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));
frame_list->set_fixed_column_width(thumbnail_size * 3 / 2);
frame_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));
}
}
void SpriteFramesEditor::_zoom_reset() {
thumbnail_zoom = MAX(1.0f, EDSCALE);
tree->set_fixed_column_width(thumbnail_default_size * 3 / 2);
tree->set_fixed_icon_size(Size2(thumbnail_default_size, thumbnail_default_size));
frame_list->set_fixed_column_width(thumbnail_default_size * 3 / 2);
frame_list->set_fixed_icon_size(Size2(thumbnail_default_size, thumbnail_default_size));
}
void SpriteFramesEditor::_update_library(bool p_skip_selector) {
updating = true;
frame_duration->set_value(1.0); // Default.
if (!p_skip_selector) {
animations->clear();
@ -951,7 +1004,7 @@ void SpriteFramesEditor::_update_library(bool p_skip_selector) {
}
}
tree->clear();
frame_list->clear();
if (!frames->has_animation(edited_anim)) {
updating = false;
@ -966,33 +1019,39 @@ void SpriteFramesEditor::_update_library(bool p_skip_selector) {
for (int i = 0; i < frames->get_frame_count(edited_anim); i++) {
String name;
Ref<Texture2D> frame = frames->get_frame(edited_anim, i);
if (frame.is_null()) {
name = itos(i) + ": " + TTR("(empty)");
} else {
name = itos(i) + ": " + frame->get_name();
Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, i);
float duration = frames->get_frame_duration(edited_anim, i);
String duration_string;
if (duration != 1.0f) {
duration_string = String::utf8(" [ ×") + String::num_real(frames->get_frame_duration(edited_anim, i)) + " ]";
}
tree->add_item(name, frame);
if (frame.is_valid()) {
String tooltip = frame->get_path();
if (texture.is_null()) {
name = itos(i) + ": " + TTR("(empty)") + duration_string;
} else {
name = itos(i) + ": " + texture->get_name() + duration_string;
}
frame_list->add_item(name, texture);
if (texture.is_valid()) {
String tooltip = texture->get_path();
// Frame is often saved as an AtlasTexture subresource within a scene/resource file,
// thus its path might be not what the user is looking for. So we're also showing
// subsequent source texture paths.
String prefix = String::utf8("┖╴");
Ref<AtlasTexture> at = frame;
Ref<AtlasTexture> at = texture;
while (at.is_valid() && at->get_atlas().is_valid()) {
tooltip += "\n" + prefix + at->get_atlas()->get_path();
prefix = " " + prefix;
at = at->get_atlas();
}
tree->set_item_tooltip(-1, tooltip);
frame_list->set_item_tooltip(-1, tooltip);
}
if (sel == i) {
tree->select(tree->get_item_count() - 1);
frame_list->select(frame_list->get_item_count() - 1);
frame_duration->set_value(frames->get_frame_duration(edited_anim, i));
}
}
@ -1059,13 +1118,13 @@ Variant SpriteFramesEditor::get_drag_data_fw(const Point2 &p_point, Control *p_f
return false;
}
int idx = tree->get_item_at_position(p_point, true);
int idx = frame_list->get_item_at_position(p_point, true);
if (idx < 0 || idx >= frames->get_frame_count(edited_anim)) {
return Variant();
}
Ref<Resource> frame = frames->get_frame(edited_anim, idx);
Ref<Resource> frame = frames->get_frame_texture(edited_anim, idx);
if (frame.is_null()) {
return Variant();
@ -1088,7 +1147,7 @@ bool SpriteFramesEditor::can_drop_data_fw(const Point2 &p_point, const Variant &
}
// reordering frames
if (d.has("from") && (Object *)(d["from"]) == tree) {
if (d.has("from") && (Object *)(d["from"]) == frame_list) {
return true;
}
@ -1134,7 +1193,7 @@ void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
return;
}
int at_pos = tree->get_item_at_position(p_point, true);
int at_pos = frame_list->get_item_at_position(p_point, true);
if (String(d["type"]) == "resource" && d.has("resource")) {
Ref<Resource> r = d["resource"];
@ -1143,28 +1202,30 @@ void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
if (texture.is_valid()) {
bool reorder = false;
if (d.has("from") && (Object *)(d["from"]) == tree) {
if (d.has("from") && (Object *)(d["from"]) == frame_list) {
reorder = true;
}
Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo();
if (reorder) { //drop is from reordering frames
int from_frame = -1;
float duration = 1.0;
if (d.has("frame")) {
from_frame = d["frame"];
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, at_pos == -1 ? -1 : at_pos);
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, from_frame);
undo_redo->add_undo_method(frames, "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, at_pos == -1 ? -1 : at_pos);
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->add_do_method(this, "_update_library");
undo_redo->add_undo_method(this, "_update_library");
@ -1240,11 +1301,11 @@ SpriteFramesEditor::SpriteFramesEditor() {
anim_speed = memnew(SpinBox);
anim_speed->set_suffix(TTR("FPS"));
anim_speed->set_min(0);
anim_speed->set_max(100);
anim_speed->set_max(120);
anim_speed->set_step(0.01);
anim_speed->set_h_size_flags(SIZE_EXPAND_FILL);
hbc_anim_speed->add_child(anim_speed);
anim_speed->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_animation_fps_changed));
anim_speed->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_animation_speed_changed));
anim_loop = memnew(CheckButton);
anim_loop->set_text(TTR("Loop"));
@ -1303,6 +1364,20 @@ SpriteFramesEditor::SpriteFramesEditor() {
delete_frame->set_flat(true);
hbc->add_child(delete_frame);
hbc->add_child(memnew(VSeparator));
Label *label = memnew(Label);
label->set_text(TTR("Frame Duration:"));
hbc->add_child(label);
frame_duration = memnew(SpinBox);
frame_duration->set_prefix(String::utf8("×"));
frame_duration->set_min(0);
frame_duration->set_max(10);
frame_duration->set_step(0.01);
frame_duration->set_allow_greater(true);
hbc->add_child(frame_duration);
hbc->add_spacer();
zoom_out = memnew(Button);
@ -1326,17 +1401,18 @@ SpriteFramesEditor::SpriteFramesEditor() {
file = memnew(EditorFileDialog);
add_child(file);
tree = memnew(ItemList);
tree->set_v_size_flags(SIZE_EXPAND_FILL);
tree->set_icon_mode(ItemList::ICON_MODE_TOP);
frame_list = memnew(ItemList);
frame_list->set_v_size_flags(SIZE_EXPAND_FILL);
frame_list->set_icon_mode(ItemList::ICON_MODE_TOP);
tree->set_max_columns(0);
tree->set_icon_mode(ItemList::ICON_MODE_TOP);
tree->set_max_text_lines(2);
tree->set_drag_forwarding(this);
tree->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_tree_input));
frame_list->set_max_columns(0);
frame_list->set_icon_mode(ItemList::ICON_MODE_TOP);
frame_list->set_max_text_lines(2);
frame_list->set_drag_forwarding(this);
frame_list->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_frame_list_gui_input));
frame_list->connect("item_selected", callable_mp(this, &SpriteFramesEditor::_frame_list_item_selected));
sub_vb->add_child(tree);
sub_vb->add_child(frame_list);
dialog = memnew(AcceptDialog);
add_child(dialog);
@ -1351,33 +1427,34 @@ SpriteFramesEditor::SpriteFramesEditor() {
move_up->connect("pressed", callable_mp(this, &SpriteFramesEditor::_up_pressed));
move_down->connect("pressed", callable_mp(this, &SpriteFramesEditor::_down_pressed));
load->set_shortcut_context(tree);
load->set_shortcut_context(frame_list);
load->set_shortcut(ED_SHORTCUT("sprite_frames/load_from_file", TTR("Add frame from file"), KeyModifierMask::CMD_OR_CTRL | Key::O));
load_sheet->set_shortcut_context(tree);
load_sheet->set_shortcut_context(frame_list);
load_sheet->set_shortcut(ED_SHORTCUT("sprite_frames/load_from_sheet", TTR("Add frames from sprite sheet"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::O));
delete_frame->set_shortcut_context(tree);
delete_frame->set_shortcut_context(frame_list);
delete_frame->set_shortcut(ED_SHORTCUT("sprite_frames/delete", TTR("Delete Frame"), Key::KEY_DELETE));
copy->set_shortcut_context(tree);
copy->set_shortcut_context(frame_list);
copy->set_shortcut(ED_SHORTCUT("sprite_frames/copy", TTR("Copy Frame"), KeyModifierMask::CMD_OR_CTRL | Key::C));
paste->set_shortcut_context(tree);
paste->set_shortcut_context(frame_list);
paste->set_shortcut(ED_SHORTCUT("sprite_frames/paste", TTR("Paste Frame"), KeyModifierMask::CMD_OR_CTRL | Key::V));
empty_before->set_shortcut_context(tree);
empty_before->set_shortcut_context(frame_list);
empty_before->set_shortcut(ED_SHORTCUT("sprite_frames/empty_before", TTR("Insert Empty (Before Selected)"), KeyModifierMask::ALT | Key::LEFT));
empty_after->set_shortcut_context(tree);
empty_after->set_shortcut_context(frame_list);
empty_after->set_shortcut(ED_SHORTCUT("sprite_frames/empty_after", TTR("Insert Empty (After Selected)"), KeyModifierMask::ALT | Key::RIGHT));
move_up->set_shortcut_context(tree);
move_up->set_shortcut_context(frame_list);
move_up->set_shortcut(ED_SHORTCUT("sprite_frames/move_left", TTR("Move Frame Left"), KeyModifierMask::CMD_OR_CTRL | Key::LEFT));
move_down->set_shortcut_context(tree);
move_down->set_shortcut_context(frame_list);
move_down->set_shortcut(ED_SHORTCUT("sprite_frames/move_right", TTR("Move Frame Right"), KeyModifierMask::CMD_OR_CTRL | Key::RIGHT));
zoom_out->set_shortcut_context(tree);
zoom_out->set_shortcut_context(frame_list);
zoom_out->set_shortcut(ED_SHORTCUT_ARRAY("sprite_frames/zoom_out", TTR("Zoom Out"),
{ int32_t(KeyModifierMask::CMD_OR_CTRL | Key::MINUS), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_SUBTRACT) }));
zoom_in->set_shortcut_context(tree);
zoom_in->set_shortcut_context(frame_list);
zoom_in->set_shortcut(ED_SHORTCUT_ARRAY("sprite_frames/zoom_in", TTR("Zoom In"),
{ int32_t(KeyModifierMask::CMD_OR_CTRL | Key::EQUAL), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_ADD) }));
file->connect("files_selected", callable_mp(this, &SpriteFramesEditor::_file_load_request).bind(-1));
frame_duration->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_frame_duration_changed));
loading_scene = false;
sel = -1;

View file

@ -46,6 +46,14 @@
class EditorFileDialog;
class EditorSpriteFramesFrame : public Resource {
GDCLASS(EditorSpriteFramesFrame, Resource);
public:
Ref<Texture2D> texture;
float duration;
};
class SpriteFramesEditor : public HSplitContainer {
GDCLASS(SpriteFramesEditor, HSplitContainer);
@ -70,7 +78,8 @@ class SpriteFramesEditor : public HSplitContainer {
Button *zoom_out = nullptr;
Button *zoom_reset = nullptr;
Button *zoom_in = nullptr;
ItemList *tree = nullptr;
SpinBox *frame_duration = nullptr;
ItemList *frame_list = nullptr;
bool loading_scene;
int sel;
@ -134,6 +143,7 @@ class SpriteFramesEditor : public HSplitContainer {
void _delete_pressed();
void _up_pressed();
void _down_pressed();
void _frame_duration_changed(double p_value);
void _update_library(bool p_skip_selector = false);
void _animation_select();
@ -143,9 +153,11 @@ class SpriteFramesEditor : public HSplitContainer {
void _animation_remove_confirmed();
void _animation_search_text_changed(const String &p_text);
void _animation_loop_changed();
void _animation_fps_changed(double p_value);
void _animation_speed_changed(double p_value);
void _frame_list_gui_input(const Ref<InputEvent> &p_event);
void _frame_list_item_selected(int p_index);
void _tree_input(const Ref<InputEvent> &p_event);
void _zoom_in();
void _zoom_out();
void _zoom_reset();

View file

@ -72,7 +72,7 @@ bool AnimatedSprite2D::_edit_use_rect() const {
Ref<Texture2D> t;
if (animation) {
t = frames->get_frame(animation, frame);
t = frames->get_frame_texture(animation, frame);
}
return t.is_valid();
}
@ -92,7 +92,7 @@ Rect2 AnimatedSprite2D::_get_rect() const {
Ref<Texture2D> t;
if (animation) {
t = frames->get_frame(animation, frame);
t = frames->get_frame_texture(animation, frame);
}
if (t.is_null()) {
return Rect2();
@ -172,17 +172,21 @@ void AnimatedSprite2D::_notification(int p_what) {
return;
}
double speed = frames->get_animation_speed(animation) * Math::abs(speed_scale);
if (speed == 0) {
return; // Do nothing.
}
int last_frame = frames->get_frame_count(animation) - 1;
double remaining = get_process_delta_time();
int i = 0;
while (remaining) {
if (timeout <= 0) {
timeout = _get_frame_duration();
// Animation speed may be changed by animation_finished or frame_changed signals.
double speed = frames->get_animation_speed(animation) * Math::abs(speed_scale);
if (speed == 0) {
return; // Do nothing.
}
// 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.
if (frame >= last_frame) {
@ -217,14 +221,21 @@ void AnimatedSprite2D::_notification(int p_what) {
}
}
timeout = _get_frame_duration();
queue_redraw();
emit_signal(SceneStringNames::get_singleton()->frame_changed);
}
double to_process = MIN(timeout, remaining);
double to_process = MIN(timeout / speed, remaining);
timeout -= to_process * speed;
remaining -= to_process;
timeout -= to_process;
i++;
if (i > fc) {
return; // Prevents freezing if to_process is each time much less than remaining.
}
}
} break;
@ -233,7 +244,7 @@ void AnimatedSprite2D::_notification(int p_what) {
return;
}
Ref<Texture2D> texture = frames->get_frame(animation, frame);
Ref<Texture2D> texture = frames->get_frame_texture(animation, frame);
if (texture.is_null()) {
return;
}
@ -312,7 +323,6 @@ void AnimatedSprite2D::set_frame(int p_frame) {
frame = p_frame;
_reset_timeout();
queue_redraw();
emit_signal(SceneStringNames::get_singleton()->frame_changed);
}
@ -320,22 +330,12 @@ int AnimatedSprite2D::get_frame() const {
return frame;
}
void AnimatedSprite2D::set_speed_scale(double p_speed_scale) {
if (speed_scale == p_speed_scale) {
return;
}
double elapsed = _get_frame_duration() - timeout;
void AnimatedSprite2D::set_speed_scale(float p_speed_scale) {
speed_scale = p_speed_scale;
playing_backwards = signbit(speed_scale) != backwards;
// We adapt the timeout so that the animation speed adapts as soon as the speed scale is changed.
_reset_timeout();
timeout -= elapsed;
}
double AnimatedSprite2D::get_speed_scale() const {
float AnimatedSprite2D::get_speed_scale() const {
return speed_scale;
}
@ -379,8 +379,8 @@ bool AnimatedSprite2D::is_flipped_v() const {
void AnimatedSprite2D::_res_changed() {
set_frame(frame);
queue_redraw();
notify_property_list_changed();
}
void AnimatedSprite2D::set_playing(bool p_playing) {
@ -388,7 +388,7 @@ void AnimatedSprite2D::set_playing(bool p_playing) {
return;
}
playing = p_playing;
_reset_timeout();
playing_backwards = signbit(speed_scale) != backwards;
set_process_internal(playing);
notify_property_list_changed();
}
@ -414,23 +414,18 @@ void AnimatedSprite2D::play(const StringName &p_animation, bool p_backwards) {
void AnimatedSprite2D::stop() {
set_playing(false);
backwards = false;
_reset_timeout();
}
double AnimatedSprite2D::_get_frame_duration() {
if (frames.is_valid() && frames->has_animation(animation)) {
double speed = frames->get_animation_speed(animation) * Math::abs(speed_scale);
if (speed > 0) {
return 1.0 / speed;
}
return frames->get_frame_duration(animation, frame);
}
return 0.0;
}
void AnimatedSprite2D::_reset_timeout() {
if (!playing) {
return;
}
timeout = _get_frame_duration();
is_over = false;
}
@ -444,8 +439,8 @@ void AnimatedSprite2D::set_animation(const StringName &p_animation) {
}
animation = p_animation;
_reset_timeout();
set_frame(0);
_reset_timeout();
notify_property_list_changed();
queue_redraw();
}
@ -455,12 +450,10 @@ StringName AnimatedSprite2D::get_animation() const {
}
PackedStringArray AnimatedSprite2D::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
PackedStringArray warnings = Node2D::get_configuration_warnings();
if (frames.is_null()) {
warnings.push_back(RTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite2D to display frames."));
}
return warnings;
}

View file

@ -43,7 +43,7 @@ class AnimatedSprite2D : public Node2D {
bool backwards = false;
StringName animation = "default";
int frame = 0;
float speed_scale = 1.0f;
float speed_scale = 1.0;
bool centered = true;
Point2 offset;
@ -94,8 +94,8 @@ public:
void set_frame(int p_frame);
int get_frame() const;
void set_speed_scale(double p_speed_scale);
double get_speed_scale() const;
void set_speed_scale(float p_speed_scale);
float get_speed_scale() const;
void set_centered(bool p_center);
bool is_centered() const;

View file

@ -837,7 +837,7 @@ void AnimatedSprite3D::_draw() {
return;
}
Ref<Texture2D> texture = frames->get_frame(animation, frame);
Ref<Texture2D> texture = frames->get_frame_texture(animation, frame);
if (texture.is_null()) {
set_base(RID());
return;
@ -921,17 +921,21 @@ void AnimatedSprite3D::_notification(int p_what) {
return;
}
double speed = frames->get_animation_speed(animation) * Math::abs(speed_scale);
if (speed == 0) {
return; // Do nothing.
}
int last_frame = frames->get_frame_count(animation) - 1;
double remaining = get_process_delta_time();
int i = 0;
while (remaining) {
if (timeout <= 0) {
timeout = _get_frame_duration();
// Animation speed may be changed by animation_finished or frame_changed signals.
double speed = frames->get_animation_speed(animation) * Math::abs(speed_scale);
if (speed == 0) {
return; // Do nothing.
}
// 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.
if (frame >= last_frame) {
@ -966,14 +970,21 @@ void AnimatedSprite3D::_notification(int p_what) {
}
}
timeout = _get_frame_duration();
_queue_redraw();
emit_signal(SceneStringNames::get_singleton()->frame_changed);
}
double to_process = MIN(timeout, remaining);
double to_process = MIN(timeout / speed, remaining);
timeout -= to_process * speed;
remaining -= to_process;
timeout -= to_process;
i++;
if (i > fc) {
return; // Prevents freezing if to_process is each time much less than remaining.
}
}
} break;
}
@ -1028,7 +1039,6 @@ void AnimatedSprite3D::set_frame(int p_frame) {
frame = p_frame;
_reset_timeout();
_queue_redraw();
emit_signal(SceneStringNames::get_singleton()->frame_changed);
}
@ -1036,22 +1046,12 @@ int AnimatedSprite3D::get_frame() const {
return frame;
}
void AnimatedSprite3D::set_speed_scale(double p_speed_scale) {
if (speed_scale == p_speed_scale) {
return;
}
double elapsed = _get_frame_duration() - timeout;
void AnimatedSprite3D::set_speed_scale(float p_speed_scale) {
speed_scale = p_speed_scale;
playing_backwards = signbit(speed_scale) != backwards;
// We adapt the timeout so that the animation speed adapts as soon as the speed scale is changed.
_reset_timeout();
timeout -= elapsed;
}
double AnimatedSprite3D::get_speed_scale() const {
float AnimatedSprite3D::get_speed_scale() const {
return speed_scale;
}
@ -1065,7 +1065,7 @@ Rect2 AnimatedSprite3D::get_item_rect() const {
Ref<Texture2D> t;
if (animation) {
t = frames->get_frame(animation, frame);
t = frames->get_frame_texture(animation, frame);
}
if (t.is_null()) {
return Rect2(0, 0, 1, 1);
@ -1086,8 +1086,8 @@ Rect2 AnimatedSprite3D::get_item_rect() const {
void AnimatedSprite3D::_res_changed() {
set_frame(frame);
_queue_redraw();
notify_property_list_changed();
}
void AnimatedSprite3D::set_playing(bool p_playing) {
@ -1095,7 +1095,7 @@ void AnimatedSprite3D::set_playing(bool p_playing) {
return;
}
playing = p_playing;
_reset_timeout();
playing_backwards = signbit(speed_scale) != backwards;
set_process_internal(playing);
notify_property_list_changed();
}
@ -1121,23 +1121,18 @@ void AnimatedSprite3D::play(const StringName &p_animation, bool p_backwards) {
void AnimatedSprite3D::stop() {
set_playing(false);
backwards = false;
_reset_timeout();
}
double AnimatedSprite3D::_get_frame_duration() {
if (frames.is_valid() && frames->has_animation(animation)) {
double speed = frames->get_animation_speed(animation) * Math::abs(speed_scale);
if (speed > 0) {
return 1.0 / speed;
}
return frames->get_frame_duration(animation, frame);
}
return 0.0;
}
void AnimatedSprite3D::_reset_timeout() {
if (!playing) {
return;
}
timeout = _get_frame_duration();
is_over = false;
}
@ -1145,13 +1140,14 @@ void AnimatedSprite3D::_reset_timeout() {
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));
if (animation == p_animation) {
return;
}
animation = p_animation;
_reset_timeout();
set_frame(0);
_reset_timeout();
notify_property_list_changed();
_queue_redraw();
}
@ -1165,7 +1161,6 @@ PackedStringArray AnimatedSprite3D::get_configuration_warnings() const {
if (frames.is_null()) {
warnings.push_back(RTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite3D to display frames."));
}
return warnings;
}

View file

@ -214,7 +214,7 @@ class AnimatedSprite3D : public SpriteBase3D {
bool backwards = false;
StringName animation = "default";
int frame = 0;
float speed_scale = 1.0f;
float speed_scale = 1.0;
bool centered = false;
@ -248,8 +248,8 @@ public:
void set_frame(int p_frame);
int get_frame() const;
void set_speed_scale(double p_speed_scale);
double get_speed_scale() const;
void set_speed_scale(float p_speed_scale);
float get_speed_scale() const;
virtual Rect2 get_item_rect() const override;

View file

@ -32,19 +32,40 @@
#include "scene/scene_string_names.h"
void SpriteFrames::add_frame(const StringName &p_anim, const Ref<Texture2D> &p_frame, int p_at_pos) {
void SpriteFrames::add_frame(const StringName &p_anim, const Ref<Texture2D> &p_texture, float p_duration, int p_at_pos) {
HashMap<StringName, Anim>::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);
Frame frame = { p_texture, p_duration };
if (p_at_pos >= 0 && p_at_pos < E->value.frames.size()) {
E->value.frames.insert(p_at_pos, p_frame);
E->value.frames.insert(p_at_pos, frame);
} else {
E->value.frames.push_back(p_frame);
E->value.frames.push_back(frame);
}
emit_changed();
}
void SpriteFrames::set_frame(const StringName &p_anim, int p_idx, const Ref<Texture2D> &p_texture, float p_duration) {
HashMap<StringName, Anim>::Iterator E = animations.find(p_anim);
ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist.");
ERR_FAIL_COND(p_idx < 0);
if (p_idx >= E->value.frames.size()) {
return;
}
p_duration = MAX(0.0, p_duration);
Frame frame = { p_texture, p_duration };
E->value.frames.write[p_idx] = frame;
emit_changed();
}
int SpriteFrames::get_frame_count(const StringName &p_anim) const {
HashMap<StringName, Anim>::ConstIterator E = animations.find(p_anim);
ERR_FAIL_COND_V_MSG(!E, 0, "Animation '" + String(p_anim) + "' doesn't exist.");
@ -57,6 +78,7 @@ void SpriteFrames::remove_frame(const StringName &p_anim, int p_idx) {
ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist.");
E->value.frames.remove_at(p_idx);
emit_changed();
}
@ -65,6 +87,7 @@ void SpriteFrames::clear(const StringName &p_anim) {
ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist.");
E->value.frames.clear();
emit_changed();
}
@ -151,7 +174,10 @@ Array SpriteFrames::_get_animations() const {
d["loop"] = anim.loop;
Array frames;
for (int i = 0; i < anim.frames.size(); i++) {
frames.push_back(anim.frames[i]);
Dictionary f;
f["texture"] = anim.frames[i].texture;
f["duration"] = anim.frames[i].duration;
frames.push_back(f);
}
d["frames"] = frames;
anims.push_back(d);
@ -175,8 +201,21 @@ void SpriteFrames::_set_animations(const Array &p_animations) {
anim.loop = d["loop"];
Array frames = d["frames"];
for (int j = 0; j < frames.size(); j++) {
// For compatibility.
Ref<Resource> res = frames[j];
anim.frames.push_back(res);
if (res.is_valid()) {
Frame frame = { res, 1.0 };
anim.frames.push_back(frame);
continue;
}
Dictionary f = frames[j];
ERR_CONTINUE(!f.has("texture"));
ERR_CONTINUE(!f.has("duration"));
Frame frame = { f["texture"], f["duration"] };
anim.frames.push_back(frame);
}
animations[d["name"]] = anim;
@ -191,17 +230,20 @@ void SpriteFrames::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_animation_names"), &SpriteFrames::get_animation_names);
ClassDB::bind_method(D_METHOD("set_animation_speed", "anim", "speed"), &SpriteFrames::set_animation_speed);
ClassDB::bind_method(D_METHOD("set_animation_speed", "anim", "fps"), &SpriteFrames::set_animation_speed);
ClassDB::bind_method(D_METHOD("get_animation_speed", "anim"), &SpriteFrames::get_animation_speed);
ClassDB::bind_method(D_METHOD("set_animation_loop", "anim", "loop"), &SpriteFrames::set_animation_loop);
ClassDB::bind_method(D_METHOD("get_animation_loop", "anim"), &SpriteFrames::get_animation_loop);
ClassDB::bind_method(D_METHOD("add_frame", "anim", "frame", "at_position"), &SpriteFrames::add_frame, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("get_frame_count", "anim"), &SpriteFrames::get_frame_count);
ClassDB::bind_method(D_METHOD("get_frame", "anim", "idx"), &SpriteFrames::get_frame);
ClassDB::bind_method(D_METHOD("set_frame", "anim", "idx", "txt"), &SpriteFrames::set_frame);
ClassDB::bind_method(D_METHOD("add_frame", "anim", "texture", "duration", "at_position"), &SpriteFrames::add_frame, DEFVAL(1.0), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("set_frame", "anim", "idx", "texture", "duration"), &SpriteFrames::set_frame, DEFVAL(1.0));
ClassDB::bind_method(D_METHOD("remove_frame", "anim", "idx"), &SpriteFrames::remove_frame);
ClassDB::bind_method(D_METHOD("get_frame_count", "anim"), &SpriteFrames::get_frame_count);
ClassDB::bind_method(D_METHOD("get_frame_texture", "anim", "idx"), &SpriteFrames::get_frame_texture);
ClassDB::bind_method(D_METHOD("get_frame_duration", "anim", "idx"), &SpriteFrames::get_frame_duration);
ClassDB::bind_method(D_METHOD("clear", "anim"), &SpriteFrames::clear);
ClassDB::bind_method(D_METHOD("clear_all"), &SpriteFrames::clear_all);

View file

@ -36,10 +36,15 @@
class SpriteFrames : public Resource {
GDCLASS(SpriteFrames, Resource);
struct Frame {
Ref<Texture2D> texture;
float duration = 1.0;
};
struct Anim {
double speed = 5.0;
bool loop = true;
Vector<Ref<Texture2D>> frames;
Vector<Frame> frames;
};
HashMap<StringName, Anim> animations;
@ -65,9 +70,13 @@ public:
void set_animation_loop(const StringName &p_anim, bool p_loop);
bool get_animation_loop(const StringName &p_anim) const;
void add_frame(const StringName &p_anim, const Ref<Texture2D> &p_frame, int p_at_pos = -1);
void add_frame(const StringName &p_anim, const Ref<Texture2D> &p_texture, float p_duration = 1.0, int p_at_pos = -1);
void set_frame(const StringName &p_anim, int p_idx, const Ref<Texture2D> &p_texture, float p_duration = 1.0);
void remove_frame(const StringName &p_anim, int p_idx);
int get_frame_count(const StringName &p_anim) const;
_FORCE_INLINE_ Ref<Texture2D> get_frame(const StringName &p_anim, int p_idx) const {
_FORCE_INLINE_ Ref<Texture2D> get_frame_texture(const StringName &p_anim, int p_idx) const {
HashMap<StringName, Anim>::ConstIterator E = animations.find(p_anim);
ERR_FAIL_COND_V_MSG(!E, Ref<Texture2D>(), "Animation '" + String(p_anim) + "' doesn't exist.");
ERR_FAIL_COND_V(p_idx < 0, Ref<Texture2D>());
@ -75,19 +84,20 @@ public:
return Ref<Texture2D>();
}
return E->value.frames[p_idx];
return E->value.frames[p_idx].texture;
}
void set_frame(const StringName &p_anim, int p_idx, const Ref<Texture2D> &p_frame) {
HashMap<StringName, Anim>::Iterator E = animations.find(p_anim);
ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist.");
ERR_FAIL_COND(p_idx < 0);
_FORCE_INLINE_ float get_frame_duration(const StringName &p_anim, int p_idx) const {
HashMap<StringName, Anim>::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);
if (p_idx >= E->value.frames.size()) {
return;
return 0.0;
}
E->value.frames.write[p_idx] = p_frame;
return E->value.frames[p_idx].duration;
}
void remove_frame(const StringName &p_anim, int p_idx);
void clear(const StringName &p_anim);
void clear_all();

View file

@ -210,9 +210,9 @@ TEST_CASE("[SpriteFrames] Frame addition, removal, and retrieval") {
frames.get_frame_count(test_animation_name) == 0,
"Animation has a default frame count of 0");
frames.add_frame(test_animation_name, dummy_frame1, 0);
frames.add_frame(test_animation_name, dummy_frame1, 1);
frames.add_frame(test_animation_name, dummy_frame1, 2);
frames.add_frame(test_animation_name, dummy_frame1, 1.0, 0);
frames.add_frame(test_animation_name, dummy_frame1, 1.0, 1);
frames.add_frame(test_animation_name, dummy_frame1, 1.0, 2);
CHECK_MESSAGE(
frames.get_frame_count(test_animation_name) == 3,
@ -227,7 +227,7 @@ TEST_CASE("[SpriteFrames] Frame addition, removal, and retrieval") {
// These error handling cases should not crash.
ERR_PRINT_OFF;
frames.add_frame("does not exist", dummy_frame1, 0);
frames.add_frame("does not exist", dummy_frame1, 1.0, 0);
frames.remove_frame(test_animation_name, -99);
frames.remove_frame("does not exist", 0);
ERR_PRINT_ON;