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