Shade (merchant-shade.itch.io) entity animation tests
This commit is contained in:
parent
2681cbd957
commit
6fdf7279ce
10 changed files with 1813 additions and 3 deletions
127
shade_sprite/animation.py
Normal file
127
shade_sprite/animation.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
"""AnimatedSprite - animation state machine for sprite sheet playback.
|
||||
|
||||
Wraps an mcrfpy.Sprite with frame timing and directional animation.
|
||||
Call tick() each frame (or from a timer) to advance the animation.
|
||||
"""
|
||||
from .formats import Direction, SheetFormat, AnimDef
|
||||
|
||||
|
||||
class AnimatedSprite:
|
||||
"""Animates an mcrfpy.Sprite using a SheetFormat definition.
|
||||
|
||||
The sprite's sprite_index is updated automatically based on the
|
||||
current animation, direction, and elapsed time.
|
||||
|
||||
Args:
|
||||
sprite: An mcrfpy.Sprite object to animate
|
||||
fmt: SheetFormat describing the sheet layout
|
||||
direction: Initial facing direction (default: Direction.S)
|
||||
"""
|
||||
|
||||
def __init__(self, sprite, fmt, direction=Direction.S):
|
||||
self.sprite = sprite
|
||||
self.fmt = fmt
|
||||
self._direction = direction
|
||||
self._anim_name = None
|
||||
self._anim = None
|
||||
self._frame_idx = 0
|
||||
self._elapsed = 0.0
|
||||
self._finished = False
|
||||
|
||||
# Start with idle if available
|
||||
if "idle" in fmt.animations:
|
||||
self.play("idle")
|
||||
|
||||
@property
|
||||
def direction(self):
|
||||
return self._direction
|
||||
|
||||
@direction.setter
|
||||
def direction(self, d):
|
||||
if not isinstance(d, Direction):
|
||||
d = Direction(d)
|
||||
if d != self._direction:
|
||||
self._direction = d
|
||||
self._update_tile()
|
||||
|
||||
@property
|
||||
def animation_name(self):
|
||||
return self._anim_name
|
||||
|
||||
@property
|
||||
def frame_index(self):
|
||||
return self._frame_idx
|
||||
|
||||
@property
|
||||
def finished(self):
|
||||
return self._finished
|
||||
|
||||
def set_direction(self, d):
|
||||
"""Set facing direction. Updates tile immediately."""
|
||||
self.direction = d
|
||||
|
||||
def play(self, anim_name):
|
||||
"""Start playing a named animation.
|
||||
|
||||
Args:
|
||||
anim_name: Animation name (must exist in the format's animations dict)
|
||||
|
||||
Raises:
|
||||
KeyError: If animation name not found in format
|
||||
"""
|
||||
if anim_name not in self.fmt.animations:
|
||||
raise KeyError(
|
||||
f"Animation '{anim_name}' not found in format '{self.fmt.name}'. "
|
||||
f"Available: {list(self.fmt.animations.keys())}"
|
||||
)
|
||||
self._anim_name = anim_name
|
||||
self._anim = self.fmt.animations[anim_name]
|
||||
self._frame_idx = 0
|
||||
self._elapsed = 0.0
|
||||
self._finished = False
|
||||
self._update_tile()
|
||||
|
||||
def tick(self, dt_ms):
|
||||
"""Advance animation clock by dt_ms milliseconds.
|
||||
|
||||
Call this from a timer callback or game loop. Updates the
|
||||
sprite's sprite_index when frames change.
|
||||
|
||||
Args:
|
||||
dt_ms: Time elapsed in milliseconds since last tick
|
||||
"""
|
||||
if self._anim is None or self._finished:
|
||||
return
|
||||
|
||||
self._elapsed += dt_ms
|
||||
frames = self._anim.frames
|
||||
|
||||
# Advance frames while we have accumulated enough time
|
||||
while self._elapsed >= frames[self._frame_idx].duration:
|
||||
self._elapsed -= frames[self._frame_idx].duration
|
||||
self._frame_idx += 1
|
||||
|
||||
if self._frame_idx >= len(frames):
|
||||
if self._anim.loop:
|
||||
self._frame_idx = 0
|
||||
else:
|
||||
# One-shot finished
|
||||
if self._anim.chain_to and self._anim.chain_to in self.fmt.animations:
|
||||
self.play(self._anim.chain_to)
|
||||
return
|
||||
else:
|
||||
# Stay on last frame
|
||||
self._frame_idx = len(frames) - 1
|
||||
self._finished = True
|
||||
self._elapsed = 0.0
|
||||
break
|
||||
|
||||
self._update_tile()
|
||||
|
||||
def _update_tile(self):
|
||||
"""Set sprite.sprite_index based on current animation frame and direction."""
|
||||
if self._anim is None:
|
||||
return
|
||||
frame = self._anim.frames[self._frame_idx]
|
||||
idx = self.fmt.sprite_index(frame.col, self._direction)
|
||||
self.sprite.sprite_index = idx
|
||||
Loading…
Add table
Add a link
Reference in a new issue