simvx.core.animation.sprite

Sprite nodes with frame-based animation support.

Module Contents

Classes

Sprite2D

2D sprite node – renders a texture via Draw2D.draw_texture_region().

SpriteAnimation

Named sprite animation with frame range.

AnimatedSprite2D

Sprite with frame-based animation from sprite sheets.

API

class simvx.core.animation.sprite.Sprite2D(texture: Any = None, position=None, rotation: float = 0.0, scale=None, colour: tuple = (1.0, 1.0, 1.0, 1.0), width: int | None = None, height: int | None = None, filter: str = 'linear', flip_h: bool = False, flip_v: bool = False, **kwargs)[source]

Bases: simvx.core.graphics.texture_slot.TextureSlot, simvx.core.nodes_2d.node2d.Node2D

2D sprite node – renders a texture via Draw2D.draw_texture_region().

The texture property takes a file path, encoded bytes, an RGBA ndarray, a live offscreen target (a SubViewport or a RenderView), or a

Class:

~simvx.core.graphics.Texture resource. The graphics backend resolves it through TextureManager and hands the resulting handle back via

Meth:

~simvx.core.graphics.TextureSlot.publish_texture_slot. The draw() callback emits a textured quad through the renderer (Draw2D).

Reassigning texture re-resolves: the cached handle is dropped and the next frame shows the new image. Wrap the source in a Texture when the PIXELS change rather than the source, and call update().

A live offscreen target is sampled in display range, not scene range. A SubViewport renders to a float target so that 3D content inside it keeps its high-dynamic-range values, but a sprite reads it through the ordinary 8-bit sprite path, so anything the offscreen scene renders brighter than 1.0 is clipped at 1.0 when a sprite samples it, and the result is quantised to 8 bits per channel. The same feed used as a 3D material stays in scene range and is tone-mapped with the rest of the frame, so the two can differ for an HDR-bright source. Author a SubViewport that a sprite samples to land inside 0..1 – which is what UI, minimaps, portals and camera feeds already do.

Attributes: texture: Path to the image file (PNG/JPG). colour: RGBA tint (0.0-1.0 floats). width: Display width in pixels, or None for the texture’s native width. height: Display height in pixels, or None for the texture’s native height.

None is how a sprite asks for the native size; any other value is a literal pixel count, so 0 is a zero-area quad and not a request for anything. (A 0 read back out of a save written when that was how a sprite asked for its native size loads as None; see :meth:from_dict.) Read :attr:draw_size when what you want is the size on screen, since that answers in pixels whichever way the sprite was set up.

Initialization

frames_h

1

frames_v

1

texture

‘Property(…)’

colour

‘Colour(…)’

width

‘Property(…)’

height

‘Property(…)’

filter

‘Property(…)’

flip_h

‘Property(…)’

flip_v

‘Property(…)’

property draw_size: simvx.core.math.types.Vec2[source]

Pixel size of the quad this sprite draws, before scale.

width / height when both are set, otherwise the texture’s native dimensions (one cell of the grid for a sprite sheet). Read this instead of width / height when you need the size on screen: those two stay None for a native-size sprite, whereas this always answers in pixels. Hit-testing, editor gizmos and layout code want this one.

on_draw(renderer) None[source]

Emit a textured quad via the renderer (Draw2D).

flip_h / flip_v flip the source UV rectangle rather than the node’s scale, so the sprite pivot stays at world_position (the Godot semantics ports rely on).

property texture_slot: int
publish_texture_slot(slot: int) None
publish_overlay_slot(slot: int) None
strict_errors: ClassVar[bool]

True

dev_checks: ClassVar[bool]

None

script_error_raised

‘Signal(…)’

dynamic: bool

False

visible

‘Property(…)’

update_mode

‘Property(…)’

__properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]

None

classmethod __init_subclass__(**kwargs)
property name: str
property visible_in_tree: bool
reset_error() None
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) None
reparent(new_parent: simvx.core.node.Node)
node_at(path, default=_NO_DEFAULT)
find(target, *, direct: bool = False)
find_all(target, *, direct: bool = False)
expect(target, *, direct: bool = False)
ancestor(target)
walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]
property path: str
property is_scene_root: bool
add_to_group(group: str)
remove_from_group(group: str)
is_in_group(group: str) bool
on_ready() None
on_enter_tree() None
on_exit_tree() None
on_update(dt: float) None
on_fixed_update(dt: float) None
on_picked(event: simvx.core.events.InputEvent) None
on_unhandled_input(event: simvx.core.events.TreeInputEvent) None
start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle
stop_coroutine(gen_or_handle)
queue_redraw() None
property render_dirty: bool
clear_children()
destroy()
property destroying: bool
call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None
property app
property tree: simvx.core.scene_tree.SceneTree
property physics
property physics_2d
__getitem__(key: str)
classmethod get_properties() dict[str, simvx.core.descriptors.Property]
__repr__()
position

‘_SpatialVecProperty(…)’

rotation

‘Property(…)’

scale

‘_SpatialVecProperty(…)’

z_index

‘Property(…)’

z_as_relative

‘Property(…)’

render_layer

‘Property(…)’

set_render_layer(index: int, enabled: bool = True) None
is_on_render_layer(index: int) bool
property absolute_z_index: int
property rotation_degrees: float
property world_position: simvx.core.math.types.Vec2
property world_rotation: float
property world_scale: simvx.core.math.types.Vec2
property world_transform: tuple[simvx.core.math.types.Vec2, simvx.core.math.types.Vec2, float]
property forward: simvx.core.math.types.Vec2
property right: simvx.core.math.types.Vec2
translate(offset: tuple[float, float] | numpy.ndarray)
rotate(radians: float)
rotate_deg(degrees: float)
look_at(target: tuple[float, float] | numpy.ndarray)
transform_points(points: list[simvx.core.math.types.Vec2]) list[simvx.core.math.types.Vec2]
draw_polygon(renderer, points: list[simvx.core.math.types.Vec2], closed=True, colour=None)
wrap_screen(margin: float = 20)
hdr

‘Property(…)’

property transform_render_dirty: bool
class simvx.core.animation.sprite.SpriteAnimation[source]

Named sprite animation with frame range.

name: str

None

frames: list[int]

None

fps: float

10.0

loop: bool

True

class simvx.core.animation.sprite.AnimatedSprite2D(**kwargs)[source]

Bases: simvx.core.animation.sprite.Sprite2D

Sprite with frame-based animation from sprite sheets.

Inherits from Sprite2D (Node2D), so it participates in the scene tree and gets on_update(dt) and on_draw(renderer) called automatically.

The sheet grid, the registered animations and whether playback runs are authoring state, so they are Properties and a saved scene carries them. The playback position (frame, frame_time) is not: a reloaded scene is the sprite that was set up, not the instant a save happened to catch.

Example: sprite = AnimatedSprite2D( texture=”player.png”, frames_h=4, frames_v=4 ) sprite.add_animation(“walk”, frames=[0, 1, 2, 3], fps=10, loop=True) sprite.add_animation(“jump”, frames=[4, 5, 6], fps=15, loop=False) sprite.play(“walk”)

Initialization

frames_h

‘Property(…)’

frames_v

‘Property(…)’

frame_width

‘Property(…)’

frame_height

‘Property(…)’

animations

‘Property(…)’

current_animation

‘Property(…)’

playing

‘Property(…)’

__transient__

‘frozenset(…)’

add_animation(name: str, frames: list[int], fps: float = 10.0, loop: bool = True)[source]

Register a named animation.

play(animation_name: str = 'default')[source]

Play named animation.

stop()[source]

Stop animation and reset to the start of the current animation.

playing becomes False and the frame counter resets so a subsequent play() or resume() begins from frame 0.

pause()[source]

Pause animation, preserving the current frame and frame time.

playing becomes False but no state is reset; resume() continues from where playback left off.

resume()[source]

Resume animation from the current frame.

on_update(dt: float)[source]

Advance sprite animation each frame.

on_draw(renderer) None[source]

Draw the current animation frame as a textured quad with proper UVs.

flip_h / flip_v (inherited from Sprite2D) swap the UV endpoints: pivot remains at world_position regardless.

The texture resolves through the same path as Sprite2D, so a live offscreen source (a SubViewport, a RenderView) draws the live feed rather than nothing. The frame grid is a plain UV sub-rectangle of whatever is bound, so the default 1x1 grid samples the whole viewport and a larger grid slices it exactly as it slices a sprite sheet. Left at native size, the quad is the viewport’s own resolution divided by that grid, and it tracks the viewport if it is resized.

property current_frame_index: int[source]

Absolute frame index in the sprite sheet.

property frame_uv: tuple[simvx.core.math.types.Vec2, simvx.core.math.types.Vec2][source]

UV coordinates for the current frame (top-left, bottom-right).

classmethod from_frames(frames: list[Any] | str | pathlib.Path, fps: float = 10.0, *, name: str = 'default', loop: bool = True, play: bool = True, **kwargs) simvx.core.animation.sprite.AnimatedSprite2D[source]

Build a flipbook AnimatedSprite2D from a list of frame textures or a folder.

frames accepts any of: * list: each element is a per-frame texture source (file path, PNG bytes, or H×W×4 uint8 ndarray). Frames are stitched into a single horizontal strip atlas. * str / Path to a directory: every *.png (recursive: no, top-level only) is sorted alphabetically and treated as one frame. An empty directory or no PNG files raises FileNotFoundError.

The resulting sprite uses a single sheet texture (so it follows the same fast GPU path as a hand-authored atlas: no per-frame upload at runtime) with frames_h = N, frames_v = 1. The animation named name is registered with all N frames; play=True starts playback immediately.

All frames must have the same pixel dimensions; mismatched sizes raise ValueError.

texture

‘Property(…)’

colour

‘Colour(…)’

width

‘Property(…)’

height

‘Property(…)’

filter

‘Property(…)’

flip_h

‘Property(…)’

flip_v

‘Property(…)’

property draw_size: simvx.core.math.types.Vec2
property texture_slot: int
publish_texture_slot(slot: int) None
publish_overlay_slot(slot: int) None
strict_errors: ClassVar[bool]

True

dev_checks: ClassVar[bool]

None

script_error_raised

‘Signal(…)’

dynamic: bool

False

visible

‘Property(…)’

update_mode

‘Property(…)’

__properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]

None

classmethod __init_subclass__(**kwargs)
property name: str
property visible_in_tree: bool
reset_error() None
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) None
reparent(new_parent: simvx.core.node.Node)
node_at(path, default=_NO_DEFAULT)
find(target, *, direct: bool = False)
find_all(target, *, direct: bool = False)
expect(target, *, direct: bool = False)
ancestor(target)
walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]
property path: str
property is_scene_root: bool
add_to_group(group: str)
remove_from_group(group: str)
is_in_group(group: str) bool
on_ready() None
on_enter_tree() None
on_exit_tree() None
on_fixed_update(dt: float) None
on_picked(event: simvx.core.events.InputEvent) None
on_unhandled_input(event: simvx.core.events.TreeInputEvent) None
start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle
stop_coroutine(gen_or_handle)
queue_redraw() None
property render_dirty: bool
clear_children()
destroy()
property destroying: bool
call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None
property app
property tree: simvx.core.scene_tree.SceneTree
property physics
property physics_2d
__getitem__(key: str)
classmethod get_properties() dict[str, simvx.core.descriptors.Property]
__repr__()
position

‘_SpatialVecProperty(…)’

rotation

‘Property(…)’

scale

‘_SpatialVecProperty(…)’

z_index

‘Property(…)’

z_as_relative

‘Property(…)’

render_layer

‘Property(…)’

set_render_layer(index: int, enabled: bool = True) None
is_on_render_layer(index: int) bool
property absolute_z_index: int
property rotation_degrees: float
property world_position: simvx.core.math.types.Vec2
property world_rotation: float
property world_scale: simvx.core.math.types.Vec2
property world_transform: tuple[simvx.core.math.types.Vec2, simvx.core.math.types.Vec2, float]
property forward: simvx.core.math.types.Vec2
property right: simvx.core.math.types.Vec2
translate(offset: tuple[float, float] | numpy.ndarray)
rotate(radians: float)
rotate_deg(degrees: float)
look_at(target: tuple[float, float] | numpy.ndarray)
transform_points(points: list[simvx.core.math.types.Vec2]) list[simvx.core.math.types.Vec2]
draw_polygon(renderer, points: list[simvx.core.math.types.Vec2], closed=True, colour=None)
wrap_screen(margin: float = 20)
hdr

‘Property(…)’

property transform_render_dirty: bool