Skip to content

PixelSprite2D

Extends AnimatedSprite2D. Read from the plugin's source on every build.

A node that draws a document. Set document and that is the whole of it: no export, no texture to build, nothing to keep in step by hand.

The front door. PixelSpriteTexture and PixelSpriteFrames are underneath and stay useful on their own — a TextureRect, a TileSet, anything that wants artwork without being a sprite — but finding them means knowing to look inside a Texture slot for a resource type you have not heard of. This is in the Create Node dialog under its own name.

An AnimatedSprite2D rather than a Sprite2D even for a still, because a document with one frame can grow a second. A node built on a plain texture stops being right at that moment and says nothing about it: the artwork gains an animation and the sprite goes on showing frame one, which is a bug you find by wondering why nothing moves. One frame here is an animation of one frame, and the second frame plays.

Properties

document

@export var document: PixelDocument

The document this node draws: its frames are built from it, and rebuilt as it changes.

preview_in_editor

@export var preview_in_editor: bool = true

Whether the animation runs in the editor as well as in the game.

autoplay only means anything once the scene is running: the editor does not tick a node in the scene you are editing, so an AnimatedSprite2D sits on frame zero until you press play. This script is a @tool, which means it can run its own clock and show you the animation while you are building the scene — which is the whole reason for drawing against a document rather than an export.

Off makes the node static in the editor and changes nothing about the game.

saved_animation

@export_storage var saved_animation: StringName

Which animation the scene keeps for this node, and why animation itself is not stored.

A scene writes a node’s engine properties before its script and reads them back in that order, so a stored animation reaches a bare AnimatedSprite2D with an empty frames slot. The engine refuses the name with “There is no animation with name”, clears it, and only then attaches this script and the document. Every scene the editor saves does that, and so does every scene an export re-packs: one error per PixelSprite2D on load, and a name that was not the first tag came back as the first tag. Nothing on this script can run before that first assignment, so the name travels as a property of the script instead, written after document and read back after it, once the frames hold the tags.

Read, it is animation as it stands. Set, it plays the animation named when the frames have it, and is otherwise dropped, which is what the engine does with a name its frames do not hold. Not offered in the inspector: animation is where a person picks it.

Methods

set_document

func set_document(value: PixelDocument) -> void

Sets document and builds the frames from it, once, however the property was assigned.

path_changed

static func path_changed(document: PixelDocument) -> int

Puts document, just read off disk again, on every live node drawing the file it came from, and answers how many there were. Each keeps the animation and the frame it was on; see swap_document.

After PixelSpriteFrames.path_changed, never before: that one queues the rebuild of the frames this node holds, and the rebuild here does it now and clears the queue, so the animation is composited once.

swap_document

func swap_document(value: PixelDocument) -> void

Takes value in place of the document held and keeps the animation and the frame the node was on, where the new document still has them; an animation it no longer describes falls back the way a load does, to the first tag.

The same instance is the ordinary case. The listener reloads with CACHE_MODE_REPLACE, which refills the instance this node holds, so the frames are rebuilt in place. Rebuilding clears every animation before it adds the frames back, and an AnimatedSprite2D whose frames were cleared under it is on frame zero, which is why the frame is read before and put back after.

rebuild

func rebuild() -> void

Builds the frames from the document again, now.

PixelSpriteTexture and PixelSpriteFrames have the same call, and it is here for the same reason: the frames follow the document’s own signals, but a caller that has changed the document behind them can ask. That is what PixelDocument.recolor and anything else reaching into the cels from game code wants after it has finished.

Following the signals is coalesced to the next idle frame, so this is also how a game gets the new pixels on screen inside the frame it changed them in. It composites every frame of every animation the document describes.

set_preview_in_editor

func set_preview_in_editor(value: bool) -> void

Sets preview_in_editor and starts or stops the preview clock to match.

advance_preview

func advance_preview(delta: float) -> void

One step of that clock. Separate from _process because the editor check belongs at the edge: Engine.is_editor_hint() is false in a script run, so a _process that tested it was a _process no test could reach.

Constants

HIDDEN_PROPERTIES

const HIDDEN_PROPERTIES: Array[StringName] = [&"frame", &"frame_progress"]

Where the animation currently is, which is a reading rather than a setting.

Both are the preview clock’s output: a node ticking in the editor moves them several times a second, so a spin box over either is a control fighting the thing it controls. The frame is picked by playing an animation — the SpriteFrames panel below is where an AnimatedSprite2D is driven by hand, and sprite_frames stays visible so that panel stays reachable.

Still stored exactly as AnimatedSprite2D stores them, less what _hide_from_storage takes off a frame the clock moved.

frame is the one this takes away; the engine already withholds frame_progress, which is listed so it stays withheld if that ever moves.

JamChip 0.1.0, by GlitterSnack. MIT licence. Built from the source on every merge, artwork and screenshots included, with JamChip.