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)
The same sheet driven at two playback rates (10 fps and 24 fps)
Looping vs one-shot playback (the one-shot anim freezes on its last frame)
Replaying a one-shot at runtime with play(), which re-arms it from frame 0
Controls: SPACE - Replay the one-shot sprite ESC - Quit
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 - The same sheet driven at two playback rates (10 fps and 24 fps)
13 - Looping vs one-shot playback (the one-shot anim freezes on its last frame)
14 - Replaying a one-shot at runtime with play(), which re-arms it from frame 0
15
16Controls:
17 SPACE - Replay the one-shot sprite
18 ESC - Quit
19"""
20
21import numpy as np
22
23from simvx.core import AnimatedSprite2D, Input, InputMap, Key, Node2D, Text2D, Vec2
24from simvx.graphics import App
25
26WIDTH, HEIGHT = 800, 600
27FRAME = 64 # pixels per frame
28FRAMES = 8 # frames in the horizontal strip
29
30
31def _make_spritesheet() -> np.ndarray:
32 """Build an 8-frame horizontal strip: a dot sweeping around a ring.
33
34 Each frame places a bright marker at a different angle so the running
35 animation reads as a clear rotation, with the frame index drawn as a
36 brightening bar so playback order is obvious.
37 """
38 sheet = np.zeros((FRAME, FRAME * FRAMES, 4), dtype=np.uint8)
39 cx = cy = FRAME / 2
40 for i in range(FRAMES):
41 x0 = i * FRAME
42 # Dark frame background with a thin border so frames are distinct.
43 sheet[:, x0 : x0 + FRAME] = (24, 24, 36, 255)
44 sheet[0:2, x0 : x0 + FRAME] = (60, 60, 90, 255)
45 sheet[-2:, x0 : x0 + FRAME] = (60, 60, 90, 255)
46 # Marker dot rotating around the ring, one step per frame.
47 angle = (i / FRAMES) * 2 * np.pi
48 mx = cx + np.cos(angle) * FRAME * 0.32
49 my = cy + np.sin(angle) * FRAME * 0.32
50 yy, xx = np.ogrid[0:FRAME, 0:FRAME]
51 dot = (xx - mx) ** 2 + (yy - my) ** 2 <= 7**2
52 sheet[:, x0 : x0 + FRAME][dot] = (255, 210, 70, 255)
53 return sheet
54
55
56class AnimatedSpriteScene(Node2D):
57 """One looping sprite, one rate-varied sprite, and a one-shot sprite."""
58
59 def on_ready(self):
60 InputMap.add_action("play", [Key.SPACE])
61 InputMap.add_action("quit", [Key.ESCAPE])
62 sheet = _make_spritesheet()
63
64 # Steady loop at 10 fps.
65 self.loop_sprite = self.add_child(
66 AnimatedSprite2D(
67 texture=sheet,
68 frames_h=FRAMES,
69 frames_v=1,
70 width=128,
71 height=128,
72 position=Vec2(WIDTH * 0.25, HEIGHT * 0.5),
73 name="Loop",
74 )
75 )
76 self.loop_sprite.add_animation("spin", frames=list(range(FRAMES)), fps=10, loop=True)
77 self.loop_sprite.play("spin")
78
79 # Faster loop reusing the same sheet at a higher fps.
80 self.fast_sprite = self.add_child(
81 AnimatedSprite2D(
82 texture=sheet,
83 frames_h=FRAMES,
84 frames_v=1,
85 width=128,
86 height=128,
87 position=Vec2(WIDTH * 0.5, HEIGHT * 0.5),
88 name="Fast",
89 )
90 )
91 self.fast_sprite.add_animation("spin_fast", frames=list(range(FRAMES)), fps=24, loop=True)
92 self.fast_sprite.play("spin_fast")
93
94 # One-shot: plays once then freezes on the final frame.
95 self.once_sprite = self.add_child(
96 AnimatedSprite2D(
97 texture=sheet,
98 frames_h=FRAMES,
99 frames_v=1,
100 width=128,
101 height=128,
102 position=Vec2(WIDTH * 0.75, HEIGHT * 0.5),
103 name="Once",
104 )
105 )
106 self.once_sprite.add_animation("burst", frames=list(range(FRAMES)), fps=12, loop=False)
107 self.once_sprite.play("burst")
108
109 self.add_child(Text2D(text="AnimatedSprite2D", position=(10, 10), font_scale=1.5, name="Title"))
110 self.add_child(Text2D(text="10 fps loop", position=(WIDTH * 0.25 - 50, HEIGHT * 0.5 + 80), name="L1"))
111 self.add_child(Text2D(text="24 fps loop", position=(WIDTH * 0.5 - 50, HEIGHT * 0.5 + 80), name="L2"))
112 self.add_child(Text2D(text="one-shot", position=(WIDTH * 0.75 - 40, HEIGHT * 0.5 + 80), name="L3"))
113 self.add_child(
114 Text2D(
115 text="Space = replay one-shot | Esc = quit",
116 position=(10, HEIGHT - 30),
117 name="Hud",
118 )
119 )
120
121 def on_update(self, dt: float):
122 if Input.is_action_just_pressed("play"):
123 self.once_sprite.play("burst") # re-arm the one-shot from frame 0
124 if Input.is_action_just_pressed("quit"):
125 self.app.quit()
126
127
128if __name__ == "__main__":
129 App(width=WIDTH, height=HEIGHT, title="AnimatedSprite2D Demo").run(AnimatedSpriteScene())