Update Animation System wiki with .animate() method, Easing enum, verified properties, callback signature
parent
222ee83009
commit
dd2a24f623
1 changed files with 248 additions and 159 deletions
|
|
@ -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
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue