Animated Sprite¶
Frame animation from a procedural spritesheet via AnimatedSprite2D.
▶ Run in browserTags: 2d sprite animation spritesheet
What it demonstrates¶
AnimatedSprite2D driving frame playback from a single sheet texture
A spritesheet built procedurally as an RGBA uint8 ndarray (no external asset)
Named animations registered with add_animation(name, frames, fps, loop)
Switching animations and playback rate at runtime with play()
Looping vs one-shot playback (the one-shot anim freezes on its last frame)
Source¶
1"""Animated Sprite: Frame animation from a procedural spritesheet via AnimatedSprite2D.
2
3# /// simvx
4# tags = ["sprite", "animation", "spritesheet"]
5# web = { root = "AnimatedSpriteScene", width = 800, height = 600, responsive = true }
6# ///
7
8## What it demonstrates
9 - AnimatedSprite2D driving frame playback from a single sheet texture
10 - A spritesheet built procedurally as an RGBA uint8 ndarray (no external asset)
11 - Named animations registered with add_animation(name, frames, fps, loop)
12 - Switching animations and playback rate at runtime with play()
13 - Looping vs one-shot playback (the one-shot anim freezes on its last frame)
14"""
15
16import numpy as np
17
18from simvx.core import AnimatedSprite2D, Input, InputMap, Key, Node2D, Text2D, Vec2
19from simvx.graphics import App
20
21WIDTH, HEIGHT = 800, 600
22FRAME = 64 # pixels per frame
23FRAMES = 8 # frames in the horizontal strip
24
25
26def _make_spritesheet() -> np.ndarray:
27 """Build an 8-frame horizontal strip: a dot sweeping around a ring.
28
29 Each frame places a bright marker at a different angle so the running
30 animation reads as a clear rotation, with the frame index drawn as a
31 brightening bar so playback order is obvious.
32 """
33 sheet = np.zeros((FRAME, FRAME * FRAMES, 4), dtype=np.uint8)
34 cx = cy = FRAME / 2
35 for i in range(FRAMES):
36 x0 = i * FRAME
37 # Dark frame background with a thin border so frames are distinct.
38 sheet[:, x0:x0 + FRAME] = (24, 24, 36, 255)
39 sheet[0:2, x0:x0 + FRAME] = (60, 60, 90, 255)
40 sheet[-2:, x0:x0 + FRAME] = (60, 60, 90, 255)
41 # Marker dot rotating around the ring, one step per frame.
42 angle = (i / FRAMES) * 2 * np.pi
43 mx = cx + np.cos(angle) * FRAME * 0.32
44 my = cy + np.sin(angle) * FRAME * 0.32
45 yy, xx = np.ogrid[0:FRAME, 0:FRAME]
46 dot = (xx - mx) ** 2 + (yy - my) ** 2 <= 7 ** 2
47 sheet[:, x0:x0 + FRAME][dot] = (255, 210, 70, 255)
48 return sheet
49
50
51class AnimatedSpriteScene(Node2D):
52 """One looping sprite, one rate-varied sprite, and a one-shot sprite."""
53
54 def on_ready(self):
55 InputMap.add_action("play", [Key.SPACE])
56 InputMap.add_action("quit", [Key.ESCAPE])
57 sheet = _make_spritesheet()
58
59 # Steady loop at 10 fps.
60 self.loop_sprite = self.add_child(AnimatedSprite2D(
61 texture=sheet, frames_horizontal=FRAMES, frames_vertical=1,
62 width=128, height=128, position=Vec2(WIDTH * 0.25, HEIGHT * 0.5),
63 name="Loop",
64 ))
65 self.loop_sprite.add_animation("spin", frames=list(range(FRAMES)), fps=10, loop=True)
66 self.loop_sprite.play("spin")
67
68 # Faster loop reusing the same sheet at a higher fps.
69 self.fast_sprite = self.add_child(AnimatedSprite2D(
70 texture=sheet, frames_horizontal=FRAMES, frames_vertical=1,
71 width=128, height=128, position=Vec2(WIDTH * 0.5, HEIGHT * 0.5),
72 name="Fast",
73 ))
74 self.fast_sprite.add_animation("spin_fast", frames=list(range(FRAMES)), fps=24, loop=True)
75 self.fast_sprite.play("spin_fast")
76
77 # One-shot: plays once then freezes on the final frame.
78 self.once_sprite = self.add_child(AnimatedSprite2D(
79 texture=sheet, frames_horizontal=FRAMES, frames_vertical=1,
80 width=128, height=128, position=Vec2(WIDTH * 0.75, HEIGHT * 0.5),
81 name="Once",
82 ))
83 self.once_sprite.add_animation("burst", frames=list(range(FRAMES)), fps=12, loop=False)
84 self.once_sprite.play("burst")
85
86 self.add_child(Text2D(text="AnimatedSprite2D", position=(10, 10), font_scale=1.5, name="Title"))
87 self.add_child(Text2D(text="10 fps loop", position=(WIDTH * 0.25 - 50, HEIGHT * 0.5 + 80), name="L1"))
88 self.add_child(Text2D(text="24 fps loop", position=(WIDTH * 0.5 - 50, HEIGHT * 0.5 + 80), name="L2"))
89 self.add_child(Text2D(text="one-shot", position=(WIDTH * 0.75 - 40, HEIGHT * 0.5 + 80), name="L3"))
90 self.add_child(Text2D(
91 text="Space = replay one-shot | Esc = quit", position=(10, HEIGHT - 30), name="Hud",
92 ))
93
94 def on_update(self, dt: float):
95 if Input.is_action_just_pressed("play"):
96 self.once_sprite.play("burst") # re-arm the one-shot from frame 0
97 if Input.is_action_just_pressed("quit"):
98 self.app.quit()
99
100
101if __name__ == "__main__":
102 App(width=WIDTH, height=HEIGHT, title="AnimatedSprite2D Demo").run(AnimatedSpriteScene())