AnimatedSprite{2D,3D} improvements
* Add support for individual frame duration to `SpriteFrames`. * Various minor improvements.
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
+32
-37
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user