Update Animation System wiki with .animate() method, Easing enum, verified properties, callback signature

John McCardle 2026-02-07 22:15:31 +00:00
commit dd2a24f623

@ -1,159 +1,248 @@
# Animation System # Animation System
The Animation System provides property-based animations with 24+ easing functions for smooth transitions on all UI elements. The Animation System provides property-based animations with 30+ easing functions for smooth transitions on all UI elements.
## Quick Reference ## Quick Reference
**Related Issues:** **Related Issues:**
- [#120](../issues/120) - Animation Property Locking (Tier 1 - Active) - [#120](../issues/120) - Animation Property Locking (Tier 1 - Active)
- [#119](../issues/119) - Animation Completion Callbacks (Closed - Implemented) - [#119](../issues/119) - Animation Completion Callbacks (Closed - Implemented)
- [#100](../issues/100) - Add Rotation Support - [#229](../issues/229) - Animation callbacks pass (target, property, value)
**Key Files:** **Key Files:**
- `src/AnimationManager.h` / `src/AnimationManager.cpp` - Animation execution engine - `src/AnimationManager.h` / `src/AnimationManager.cpp` - Animation execution engine
- `src/Animation.h` - Base Animation class - `src/Animation.h` - Base Animation class
- `src/UIDrawable.h` - Animatable properties defined here - `src/UIDrawable.h` - Animatable properties defined here
**API Reference:** ---
- See [mcrfpy.animate()](../docs/api_reference_dynamic.html#animate) in generated API docs
## The `.animate()` Method
## Architecture Overview
The primary way to create animations is the `.animate()` method available on all UI elements:
### Property-Based Animation
```python
Animations modify any numeric property on UI objects over time: import mcrfpy
```python frame = mcrfpy.Frame(pos=(100, 100), size=(200, 150))
import mcrfpy
# animate(property, target_value, duration_seconds, easing)
sprite = mcrfpy.Sprite("player.png", 100, 100) frame.animate("x", 500.0, 2.0, mcrfpy.Easing.EASE_IN_OUT)
frame.animate("opacity", 0.5, 1.0, mcrfpy.Easing.EASE_OUT_QUAD)
# Animate position with easing ```
mcrfpy.animate(sprite, "x", 300, 1000, "ease_in_out_cubic")
**Parameters:**
# Animate multiple properties - `property` (str): Name of the property to animate
mcrfpy.animate(sprite, "opacity", 0.5, 500, "ease_out_quad") - `target_value`: End value (float, int, or Color tuple)
mcrfpy.animate(sprite, "scale_x", 2.0, 800, "bounce_out") - `duration` (float): Duration in seconds
``` - `easing` (Easing): Easing function from `mcrfpy.Easing` enum
- `callback` (optional): Function called on completion
**Supported Properties:**
- Position: `x`, `y`, `pos` ---
- Size: `w`, `h`, `size`, `scale_x`, `scale_y`
- Colors: `r`, `g`, `b`, `a`, `fill_color`, `outline_color` ## Animatable Properties by Type
- Grid-specific: `zoom`, `left_edge`, `top_edge`
- Caption-specific: `font_size` ### Frame
| Property | Type | Notes |
**Implementation:** See `src/AnimationManager.cpp::createAnimation()` |----------|------|-------|
| `x`, `y` | float | Position |
### Easing Functions | `w`, `h` | float | Size |
| `outline` | float | Outline thickness |
24+ easing functions available: | `opacity` | float | 0.0 to 1.0 |
| `fill_color` | Color | Animate as `(r, g, b, a)` tuple |
**Families:** | `outline_color` | Color | Animate as `(r, g, b, a)` tuple |
- Linear
- Quad, Cubic, Quart, Quint ### Caption
- Sine, Expo, Circ | Property | Type | Notes |
- Elastic, Back, Bounce |----------|------|-------|
| `x`, `y` | float | Position |
**Variants:** `ease_in_*`, `ease_out_*`, `ease_in_out_*` | `opacity` | float | 0.0 to 1.0 |
| `outline` | float | Text outline thickness |
**Implementation:** All easing functions in `src/Animation.cpp` | `fill_color` | Color | Text fill color |
| `outline_color` | Color | Text outline color |
### Execution Model
### Sprite
**Pure C++ Execution:** | Property | Type | Notes |
- Animations run in C++ update loop - no Python callbacks per frame |----------|------|-------|
- High performance: thousands of concurrent animations possible | `x`, `y` | float | Position |
- Frame-independent: adjusts for variable frame times | `scale` | float | Uniform scale factor |
| `sprite_index` | int | Sprite sheet index (truncated to int) |
**Lifecycle:** | `opacity` | float | 0.0 to 1.0 |
1. Created via `mcrfpy.animate()`
2. AnimationManager updates each frame ### Grid
3. Auto-destroyed on completion or target destruction | Property | Type | Notes |
4. Weak pointer tracking prevents use-after-free |----------|------|-------|
| `x`, `y` | float | Position on screen |
**See:** `src/AnimationManager.cpp::update()` for frame update logic | `w`, `h` | float | Viewport size |
| `center_x`, `center_y` | float | Camera pan position |
## Current Issues & Limitations | `zoom` | float | Camera zoom level |
**Known Issues:** ### Entity (must be attached to a Grid)
- API inconsistency: position vs frame animation differs (see [FINAL_RECOMMENDATIONS.md](../FINAL_RECOMMENDATIONS.md)) | Property | Type | Notes |
- Property locking: Multiple animations can conflict on same property ([#120](../issues/120)) |----------|------|-------|
- Rotation not yet supported ([#100](../issues/100)) | `x`, `y` | float | Alias for draw position |
| `draw_x`, `draw_y` | float | Visual position in tile coordinates |
**Recommendations from Strategic Review:** | `sprite_index` | int | Sprite sheet index |
Per FINAL_RECOMMENDATIONS.md, animation bugs contributed to tutorial burnout. Current system is feature-complete but needs: | `sprite_scale` | float | Entity sprite scale |
1. API consistency audit
2. Bug fixes for known issues ---
3. Comprehensive testing
## Easing Functions (`mcrfpy.Easing`)
## Common Tasks
30+ easing functions are available:
### Basic Animation
**Basic:**
```python - `Easing.LINEAR`
# Linear movement - `Easing.EASE_IN`, `Easing.EASE_OUT`, `Easing.EASE_IN_OUT`
mcrfpy.animate(entity, "x", 500, 2000, "linear")
**Families** (each has `_IN`, `_OUT`, `_IN_OUT` variants):
# Smooth bounce - `QUAD` - Quadratic
mcrfpy.animate(frame, "y", 300, 1000, "ease_out_bounce") - `CUBIC` - Cubic
``` - `QUART` - Quartic
- `SINE` - Sinusoidal
### Color Fades - `EXPO` - Exponential
- `CIRC` - Circular
```python - `ELASTIC` - Elastic spring
# Fade to red - `BACK` - Overshoot
mcrfpy.animate(caption, "r", 255, 500, "ease_in_quad") - `BOUNCE` - Bouncing
# Fade out **Example:** `Easing.EASE_IN_OUT_CUBIC`, `Easing.EASE_OUT_BOUNCE`, `Easing.EASE_IN_ELASTIC`
mcrfpy.animate(sprite, "a", 0, 1000, "ease_out_cubic")
``` ---
### Delta Animations ## Completion Callbacks
```python Animation callbacks receive `(target, property, final_value)`:
# Move relative to current position
current_x = entity.x ```python
mcrfpy.animate(entity, "x", current_x + 100, 500, "ease_in_out_quad") def on_complete(target, prop, value):
``` print(f"{type(target).__name__}.{prop} reached {value}")
### Completion Callbacks frame.animate("x", 500.0, 2.0, mcrfpy.Easing.EASE_IN_OUT, callback=on_complete)
```
```python
def on_complete(runtime_ms): **Callback arguments:**
print(f"Animation completed after {runtime_ms}ms") - `target`: The animated object (Frame, Sprite, Entity, etc.)
# Trigger next action - `prop` (str): Property name that was animated (e.g., `"x"`, `"opacity"`)
- `value`: Final value (float, int, or `(r, g, b, a)` tuple for colors)
mcrfpy.animate(sprite, "x", 400, 1000, "linear", callback=on_complete)
``` ### Chaining Animations
**Implementation:** [#119](../issues/119) - Callbacks fire when animation completes Use callbacks to create animation sequences:
## Related Systems ```python
def step2(target, prop, value):
- [[UI-Component-Hierarchy]] - All UIDrawable objects are animatable target.animate("y", 300.0, 1.0, mcrfpy.Easing.EASE_OUT_BOUNCE)
- [[Grid-System]] - Grid viewport animations (zoom, pan)
- [[Performance-and-Profiling]] - Animation time tracked separately def step1(target, prop, value):
target.animate("x", 500.0, 1.0, mcrfpy.Easing.EASE_IN_OUT, callback=step2)
## Design Decisions
frame.animate("opacity", 1.0, 0.5, mcrfpy.Easing.EASE_IN, callback=step1)
**Why Property-Based?** ```
- Flexible: Any numeric property animatable
- Type-safe: Properties validated at C++ level **Note:** It is safe to start new animations from within callbacks, including animations on the same target and property.
- Performant: No Python overhead during animation
---
**Why Weak Pointers?**
- Prevents crashes when animated objects destroyed ## Execution Model
- Automatic cleanup on target death
- See: `src/AnimationManager.cpp` RAII overhaul (commit e9e9cd2) **Pure C++ Execution:**
- Animations run in the C++ update loop - no Python callbacks per frame
**Tradeoffs:** - High performance: thousands of concurrent animations
- More complex than tween libraries - Frame-independent: adjusts for variable frame times
- Requires property exposure in Python bindings
- API surface larger than simple position tweening **Lifecycle:**
1. Created via `.animate()` call
--- 2. AnimationManager updates each frame
3. Auto-destroyed on completion or target destruction
**Historical Notes:** 4. Weak pointer tracking prevents use-after-free
- ANIMATION_FIX_IMPLEMENTATION.md documents segfault fix (race conditions resolved)
- Major refactor July 2025: Weak pointer tracking eliminated use-after-free bugs **Conflicting Animations:**
Multiple animations on the same property will conflict. The most recently created animation takes precedence. Property locking ([#120](../issues/120)) is planned to prevent this.
---
## Color Animations
Animate colors by targeting `fill_color` or `outline_color`:
```python
# Fade to red fill
frame.animate("fill_color", (255, 0, 0, 255), 1.0, mcrfpy.Easing.EASE_IN)
# Animate outline to transparent
frame.animate("outline_color", (255, 255, 255, 0), 0.5, mcrfpy.Easing.LINEAR)
```
Color animations interpolate each channel (R, G, B, A) independently.
---
## Common Patterns
### Smooth Entity Movement
```python
def move_entity(entity, target_x, target_y, speed=0.3):
entity.animate("x", float(target_x), speed, mcrfpy.Easing.EASE_OUT_QUAD)
entity.animate("y", float(target_y), speed, mcrfpy.Easing.EASE_OUT_QUAD)
```
### Fade In/Out
```python
# Fade in
frame.opacity = 0.0
frame.animate("opacity", 1.0, 0.5, mcrfpy.Easing.EASE_IN)
# Fade out and remove
def remove_on_fade(target, prop, value):
# Remove from parent scene
pass # implement removal logic
frame.animate("opacity", 0.0, 0.5, mcrfpy.Easing.EASE_OUT, callback=remove_on_fade)
```
### Pulse/Breathing Effect
```python
def pulse(target, prop, value):
if value > 0.8:
target.animate("opacity", 0.3, 1.0, mcrfpy.Easing.EASE_IN_OUT_SINE, callback=pulse)
else:
target.animate("opacity", 1.0, 1.0, mcrfpy.Easing.EASE_IN_OUT_SINE, callback=pulse)
frame.animate("opacity", 0.3, 1.0, mcrfpy.Easing.EASE_IN_OUT_SINE, callback=pulse)
```
### Camera Pan
```python
grid.animate("center_x", target_x * 16.0, 0.5, mcrfpy.Easing.EASE_IN_OUT)
grid.animate("center_y", target_y * 16.0, 0.5, mcrfpy.Easing.EASE_IN_OUT)
```
---
## Animation Object (Advanced)
The `mcrfpy.Animation` class can be used directly for more control, though `.animate()` is preferred:
```python
anim = mcrfpy.Animation("x", 500.0, 2.0, mcrfpy.Easing.LINEAR, callback=my_callback)
anim.start(frame)
```
This is functionally equivalent to `frame.animate("x", 500.0, 2.0, mcrfpy.Easing.LINEAR, callback=my_callback)`.
---
## Related Systems
- [[UI-Component-Hierarchy]] - All UIDrawable objects are animatable
- [[Grid-System]] - Grid viewport animations (zoom, pan)
- [[Input-and-Events]] - Trigger animations from input callbacks
- [[Performance-and-Profiling]] - Animation time tracked separately
---
*Last updated: 2026-02-07*