A playback node extending Node2D.
Specify a resource and an animation, then start playback.
@onready var ssnode: SpriteStudioPlayer2D = $target
func _ready() -> void:
# Load the .ssab and assign it as the resource
var ssab: SSABResource = ResourceLoader.load("res://ssab_generated/Sample.ssab")
ssnode.set_ssab_resource(ssab)
# Specify the animation name
ssnode.set_animation("anime_1")
# Play
ssnode.set_loop_count(-1) # -1 = infinite loop
ssnode.set_speed_scale(1.0)
ssnode.play()set_ssab_resource(res: SSABResource)/get_ssab_resource() -> SSABResourceset_animation(name: String)/get_current_animation() -> Stringset_autoplay(autoplay: bool)/is_autoplay() -> bool: Whether to start playing automatically when the scene starts. Defaulttrue— a node placed in a scene has no code to callplay()for it, so it plays. Set it off for a node you drive yourself, or one that should hold its first frame as a pose.set_offset(offset: Vector2)/get_offset() -> Vector2: Shifts the drawing position without moving the Node2D's origin.set_flip_h(flip: bool)/is_flipped_h() -> bool: Flips the animation horizontally.set_flip_v(flip: bool)/is_flipped_v() -> bool: Flips the animation vertically.set_animation_process_mode(mode: AnimationProcessMode)/get_animation_process_mode() -> AnimationProcessMode: Sets whether to sync with_physics_process(ANIMATION_PROCESS_PHYSICS/0) or_process(ANIMATION_PROCESS_IDLE/1), or to stop ticking on its own (ANIMATION_PROCESS_MANUAL/2).advance(delta: float): Steps playback bydeltaseconds and emitsframe_updated, exactly as an automatic tick would. Meant forANIMATION_PROCESS_MANUAL— under the other modes it advances the animation on top of the node's own tick.- In-editor preview: Select the node and use the SpriteStudio bottom panel — play from start / play from current / stop, a frame scrubber, and loop and speed controls — to preview without running the game. Shortcuts mirror the AnimationPlayer editor (D play from current, Shift+D play from start, S stop). (The former
editor_playinginspector toggle has been replaced by this panel.) play(start_frame: float = -1.0): Starts playback.-1.0(the default) rewinds to the start of the section — its end when the direction is backward — rather than continuing from where the playhead is. Passget_frame_no()to play on from the current position.pause(): Holds playback where it stands, keeping the current frame. Idempotent — pausing twice leaves it paused.resume(): Lifts the hold and carries on from the same frame. Idempotent, and a no-op on an animation that is stopped rather than held —play()is what starts a stopped animation, and it rewinds.stop(): Stops playback. The playhead stays where it was, so the node keeps drawing the frame it stopped on.is_playing() -> bool:truewhile playing, including while paused — a pause is a hold, not a stop. /is_pausing() -> bool:trueonly while held. Both arefalseon an animation that has never played, and afterstop().is_finished() -> bool: Whether every configured loop has played. A state, not a pulse — it latches on completion and holds until the nextplay()or animation change, so a caller that was not connected whenanimation_finishedfired can still ask whether the run is over. Nevertrueunder an infiniteloop_count, and not raised bystop()— a commanded stop is not a completion, which is what separates it fromnot is_playing().just_looped() -> bool: Whether the last update crossed a loop boundary. A pulse, not a state — the runtime clears it at the top of every update, so it only readstrueinside the tick that crossed (which is why it is notis_looped: reading it a tick late readsfalse). Theanimation_loopedsignal delivers the same edge if you would rather not poll.get_animation_names() -> PackedStringArray: Names of the animations in the assigned [SSABResource] — the same list theanimationproperty is chosen from.is_playing_forward() -> bool: Which way the playhead is actually travelling. Notget_playback_direction(), which reports the configured heading: on a ping-pong return leg this readsfalsewhile that still readsPLAYBACK_DIRECTION_FORWARD(a speed of zero or below is a stop, not a reversal, and does not flip it either). Gate audio on it — SpriteStudio has no reverse audio, so a sound key crossed on a backward leg is not meant to sound, and this is the test the node itself applies whenplay_audiois on.truebefore anything has played: forward is the resting state.set_frame_no(frame: float)/get_frame_no() -> float/get_total_frames() -> intget_start_frame() -> int/get_end_frame() -> int: The first and last frame that actually plays — the current playback section. They return the same values asget_animation_section_start()/get_animation_section_end(), which is the whole animation untilset_animation_section()narrows it.set_speed_scale(speed_scale: float)/get_speed_scale() -> floatset_frame_rate(fps: int)/get_frame_rate() -> intset_animation_section(start: int, end: int): Limits the playback to a specific frame range.set_animation_section_start(start: int)/get_animation_section_start() -> int/set_animation_section_end(end: int)/get_animation_section_end() -> int: Moves one endpoint of the section while keeping the other. These back theanimation_section_start/animation_section_endinspector properties.set_playback_direction(direction: PlaybackDirection, style: PlaybackStyle): Sets the playback direction and style. See the table below for values.get_playback_direction() -> PlaybackDirection/get_playback_style() -> PlaybackStyle: Reads back the two halves of the setter individually.set_loop_count(count: int)/get_loop_count() -> int:nplaysncycles then stops (1plays once).-1means infinite loop (0is an alias for infinite).set_frame_skip_enabled(enabled: bool)/is_frame_skip_enabled() -> bool(default:true)set_sub_frame_enabled(enabled: bool)/is_sub_frame_enabled() -> bool(default:false)set_cellmap_texture(cellmap_name: String, texture: Texture2D)/get_cellmap_texture(cellmap_name: String) -> Texture2Dget_cellmap_names() -> PackedStringArray/get_cell_names(cellmap_name: String) -> PackedStringArray: Names read from the assignedSSABResource(empty when none is assigned) — the discovery half ofset_part_cell_override(). Also available onSSABResourceitself for an.ssabthat is not on a player.set_play_audio(enabled: bool)/is_play_audio() -> bool(default:true),set_audio_volume(volume: float)/get_audio_volume() -> float,set_audio_backend(backend: SpriteStudioAudioBackend)/get_audio_backend() -> SpriteStudioAudioBackend: Built-in audio playback. See Audio below.
| Argument | Constant | Value | Meaning |
|---|---|---|---|
direction |
PLAYBACK_DIRECTION_FORWARD |
0 |
Forward |
direction |
PLAYBACK_DIRECTION_BACKWARD |
1 |
Backward |
style |
PLAYBACK_STYLE_NORMAL |
0 |
Normal / One-way |
style |
PLAYBACK_STYLE_PING_PONG |
1 |
PingPong (Round-trip) |
get_part_names() -> PackedStringArray: Every part name in the asset (.ssab). Parts do not depend on the animation, so the list is the same for every animation in that asset.find_part_index(part_name: String) -> int: Resolves a part name to its part index, or-1if it does not exist.get_part_transform(part_name: String) -> Transform2D: The part'sTransform2Don the current frame, in the player node's local space (flip_h/flip_v/offsetincluded). Returns the identity when the part is unknown.is_part_hidden(part_name: String) -> bool: Whether the part is hidden on the current frame.is_part_skinned_mesh(part_name: String) -> bool: Whether the part is a skinned mesh (falsewhen the part is unknown). Skinned-mesh parts are not suitable follow targets, soSpriteStudioPartAttachment2Dexcludes them.
See Scripting and Event-Driven Control → Part Tracking for SpriteStudioPartAttachment2D, the node that makes another node follow a specified part.
Override a single part's color / cell / visibility so that it wins over the keyframes. Every method returns true on success, or false when the part is unknown or the runtime rejects the call. See Scripting and Event-Driven Control → Part Overrides for the details and caveats.
set_part_color_override(part_name: String, color: Color, blend_op: ColorBlendOperation = COLOR_BLEND_MIX, priority: OverridePriority = OVERRIDE_PRIORITY_HOLD_UNTIL_NEXT_ANIMATION) -> boolset_part_color_override_corners(part_name: String, corners: PackedColorArray, blend_op: ColorBlendOperation = COLOR_BLEND_MIX, priority: OverridePriority = OVERRIDE_PRIORITY_HOLD_UNTIL_NEXT_ANIMATION) -> bool: Four-corner (per-vertex) colour, for a gradient across the part.cornersholds exactly four colours in the order left-top, right-top, left-bottom, right-bottom. Shares one override slot withset_part_color_override— the last call wins, andclear_part_color_overrideclears either kind.set_part_cell_override(part_name: String, cellmap_name: String, cell_name: String, priority: OverridePriority = OVERRIDE_PRIORITY_HOLD_UNTIL_NEXT_ANIMATION) -> boolset_part_visibility_override(part_name: String, force_hidden: bool, cascade: bool = false) -> boolclear_part_color_override(part_name: String) -> bool/clear_part_cell_override(part_name: String) -> bool/clear_part_visibility_override(part_name: String) -> boolclear_all_part_overrides() -> bool- Each method has an index-based variant
*_by_index(part_index: int, ...)that skips the name lookup (get the index fromfind_part_index()).
| Constant | Value | Blend operation |
|---|---|---|
COLOR_BLEND_MIX |
0 |
Mix (default) |
COLOR_BLEND_MUL |
1 |
Mul (multiply) |
COLOR_BLEND_ADD |
2 |
Add |
COLOR_BLEND_SUB |
3 |
Sub (subtract) |
| Constant | Value | Meaning |
|---|---|---|
OVERRIDE_PRIORITY_OVERWRITE_ON_NEXT_KEYFRAME |
0 |
Applies until the animation updates that attribute |
OVERRIDE_PRIORITY_HOLD_UNTIL_NEXT_ANIMATION |
1 |
Applies for the current animation; cleared when a new animation is set up (default) |
OVERRIDE_PRIORITY_PERMANENT |
2 |
Applies for as long as the same .ssab is playing, surviving animation changes |
Note
set_part_visibility_override has no priority. It always wins over the keyframes and is always cleared when a new animation is set up.
| Signal | Arguments | Emitted When |
|---|---|---|
animation_started |
anim_name: String |
Playback starts |
animation_changed |
anim_name: String |
The animation name is changed |
animation_finished |
anim_name: String |
Every configured loop has been played. Never emitted under an infinite loop |
animation_looped |
anim_name: String |
The animation looped back to the start. Not emitted on the final cycle, which emits animation_finished instead |
frame_updated |
frame_no: float |
The frame's part poses have just been finalized (right after the player's update, before the render phase). Which process it fires in follows animation_process_mode |
user_data |
payload: Dictionary |
A "User Data" keyframe on the timeline is hit |
signal_emitted |
command: String, value: Dictionary, info: Dictionary |
A "Signal" keyframe on the timeline is hit |
audio |
payload: Dictionary |
An "Audio" keyframe on the timeline is hit |
The User Data values configured in SpriteStudio are delivered as a Dictionary. The three origin keys are always present; of the four value keys, only the ones that were set are present — an unset field is omitted entirely rather than defaulted, because 0 is a value an author can mean.
| Key | Type | Meaning |
|---|---|---|
part_index |
int |
Index of the part the key sits on |
part_name |
String |
Name of that part |
frame_no |
int |
The frame the key sits on. Not necessarily the frame it was noticed on — one tick can step across several |
integer |
int |
Integer value |
point |
Vector2 |
Point value |
rect |
Rect2 |
Rectangle value (x, y, width, height) |
string |
String |
String value |
The parameters configured on the timeline "Signal" keyframe are delivered as value, a Dictionary keyed by parameter ID, with each value as bool / int / float / String, etc. The command argument receives the signal name (command_id).
The event's origin arrives as a separate info dictionary rather than as more keys in value, precisely because value's keys are author-defined and a fixed key could shadow one of them.
info key |
Type | Meaning |
|---|---|---|
part_index |
int |
Index of the part the key sits on |
part_name |
String |
Name of that part |
frame_no |
int |
The frame the key sits on |
The information configured on the timeline audio keyframe is delivered as a Dictionary. This signal is an observation channel: it fires in every playback direction and in the editor, independently of whether the built-in playback (play_audio) is on. Connect to it to react to a sound, or to replace playback entirely — see Audio.
| Key | Type | Meaning |
|---|---|---|
part_index |
int |
Index of the part that fired |
part_name |
String |
Name of that part |
frame_no |
int |
The frame the key sits on |
sound_list_name_hash |
int |
Hash of the sound list name |
sound_name_hash |
int |
Hash of the sound name |
sound_name |
String |
Sound name (present only when set) |
loop_num |
int |
Play count (1 plays once; SpriteStudio has no infinite audio loop) |
Note
For the exact types and the latest set of accepted values, also refer to the implementation files ss_player/ss_player_node_2d.h and ss_player/ss_internal_player.cpp.
Audio parts play through Godot out of the box — the node owns a pooled set of AudioStreamPlayer voices and starts one whenever the playhead crosses an audio key while playing forward. Audio Playback covers the semantics (fire-and-forget, no seek re-sync, overlap on re-fire); this is the API surface.
| Member | Type | Default | Description |
|---|---|---|---|
play_audio |
bool |
true |
Whether the built-in player makes sound. set_play_audio(false) also stops any in-flight built-in voices |
audio_volume |
float |
1.0 |
Linear volume in [0, 1] for the built-in voices. Ignored while audio_backend is assigned |
audio_backend |
SpriteStudioAudioBackend |
(none) | Replaces the built-in player entirely |
A Resource subclass with a single overridable method. Assigning one to audio_backend suppresses the built-in playback, audio_volume included, so the backend owns voice lifecycle and play counts.
play_audio(payload: Dictionary, ssab: SSABResource, player: Node) -> void: Called once per audio event, with the samepayloadtheaudiosignal carries. The default implementation does nothing.
extends SpriteStudioAudioBackend
func play_audio(payload: Dictionary, ssab: SSABResource, player: Node) -> void:
var info := ssab.get_sound_info(payload["sound_list_name_hash"], payload["sound_name_hash"])
if not info.is_empty():
MyMiddleware.play(info["path"], payload["loop_num"])Resolving a sound from the payload is done on SSABResource, via get_sound_stream() or get_sound_info().
The frame_no property is animatable, so an AnimationPlayer can scrub a SpriteStudio animation in lockstep with its own timeline (and any other tracks on it — audio, calls, other nodes).
- Assign the
Ssabresource and pick anAnimationon theSpriteStudioPlayer2Das usual. - In the
AnimationPlayer, add a Property Track targeting the node'sframe_noproperty. - Keyframe
frame_noover time (e.g.0→ the last frame across the desired duration).frame_nois a float, so values interpolate. - Play the
AnimationPlayer.
Important
While the AnimationPlayer drives frame_no, do not let the node play itself — leave Autoplay off and don't call play(). Otherwise the node's own playback and the keyframed frame_no fight each other every frame.
No setup beyond this is required: keyframe values live in the AnimationPlayer's animation (the node's frame_no is not stored in the scene), and the same track drives playback at runtime.