nodes/folder_sprite.pyΒΆ
Part of PirateMaker.
1"""Multi-PNG animated sprite: cycles Sprite2D.texture across a folder of frames.
2
3Same pattern used in clear_code_zelda. PirateMaker assets are stored as folders
4of individual PNGs, and SimVX's AnimatedSprite2D.from_frames composes them into
5a strip, but we want per-instance frame control (water, coin, palm, enemies)
6without re-uploading 232 land textures or 8-frame water on every cell.
7
8This implementation lazily swaps Sprite2D.texture each frame; the engine's
9_invalidate_texture_slot hook + scene adapter handle the GPU upload.
10"""
11
12from __future__ import annotations
13
14from simvx.core import Property, Sprite2D
15
16
17class FolderSprite(Sprite2D):
18 """Sprite2D that cycles a list of file paths."""
19
20 fps = Property(8.0, range=(0.1, 60.0), hint="Animation frames per second")
21
22 def __init__(
23 self, frames: list[str] | None = None, fps: float = 8.0, loop: bool = True, anim_offset: float = 0.0, **kwargs
24 ):
25 first = frames[0] if frames else None
26 super().__init__(texture=first, filter="nearest", **kwargs)
27 self._frames = list(frames) if frames else []
28 self.fps = fps
29 self._loop = loop
30 self._frame_index = anim_offset
31 self._finished = False
32
33 @property
34 def finished(self) -> bool:
35 return self._finished
36
37 @property
38 def frame_count(self) -> int:
39 return len(self._frames)
40
41 def play(self, frames: list[str], fps: float | None = None, loop: bool = True) -> None:
42 """Switch to ``frames`` and restart from frame 0.
43
44 Re-requesting the frames already playing is a no-op, so callers can drive
45 this straight from a per-frame state check without stuttering.
46 """
47 if frames == self._frames:
48 return
49 self._frames = list(frames)
50 if fps is not None:
51 self.fps = fps
52 self._loop = loop
53 self._frame_index = 0.0
54 self._finished = False
55 if self._frames:
56 self.texture = self._frames[0]
57
58 def on_update(self, dt: float) -> None:
59 if not self._frames:
60 return
61 self._frame_index += dt * self.fps
62 if self._frame_index >= len(self._frames):
63 if self._loop:
64 self._frame_index %= len(self._frames)
65 else:
66 self._frame_index = len(self._frames) - 1
67 self._finished = True
68 idx = int(self._frame_index)
69 new_tex = self._frames[idx]
70 if self.texture != new_tex:
71 self.texture = new_tex