The goal is to create a robust, conditional Actions system for the Arcade 3.x Python library, inspired by Cocos2D's action system but reimagined to fit Arcade's API.
This system enables complex sprite behaviors (movement, rotation, scaling, fading, scheduling) in games like Space Invaders, Galaga, and Asteroids — all using high-level declarative condition-based actions that work directly with Arcade's native sprites.
| Module / Feature | Why It's Included |
|---|---|
base.py |
Core Action class with global action management and composition helpers |
movement.py / paths.py / transforms.py / effects.py / callbacks.py / parametric.py |
Condition-based actions (MoveUntil, RotateUntil, etc.) |
composite.py |
Composite actions for combining multiple actions (sequential, parallel) |
movement.py |
Includes boundary handling in MoveUntil for arcade-style patterns |
pattern.py |
Formation functions for positioning and layout patterns |
pools.py |
Experimental SpritePool for zero-allocation gameplay |
display.py |
Cross-platform window centering utility (SDL2 + screeninfo) |
easing.py |
Easing wrapper for smooth acceleration/deceleration effects on any action |
helpers.py |
Convenience wrappers for actions, e.g., move_until() |
| Global Action Management | Automatic action tracking, updates, and lifecycle management |
| Test Suite | Pytest-based unit and integration tests to validate core and edge behavior |
| Operator Overloading | + for sequence and ` |
The Actions library uses direct property updates with Arcade's native sprite system:
All actions work by directly modifying sprite properties:
- Position - Updated via
center_x,center_y - Angle - Updated via
angleproperty - Scale - Updated via
scaleproperty (supports both float and tuple) - Alpha - Updated via
alphaproperty
Actions calculate velocity-based changes and apply them directly:
- Actions calculate position/angle/scale/alpha changes based on velocity and delta_time
- Actions apply changes directly to sprite properties
- Actions check conditions each frame to determine completion
- Global
Action.update_all()handles all active actions automatically
Unlike duration-based actions, ArcadeActions uses condition-based actions:
- MoveUntil - Move until condition is met
- RotateUntil - Rotate until condition is met
- FadeTo - Fade until a target alpha is reached
- DelayFrames - Wait for a number of frames (or early-exit condition)
This enables more flexible, game-state-driven behaviors.
from arcadeactions import DelayFrames, FadeTo, MoveUntil, RotateUntil, infinite
# Clean declarative syntax with operators
# Unbound actions can be created by passing `None` as the target
move = MoveUntil(velocity=(100, 0), condition=infinite)
rotate = RotateUntil(angular_velocity=1.5, condition=infinite)
delay = DelayFrames(frames=60, condition=infinite)
fade = FadeTo(target_alpha=0, speed=3.0, condition=infinite)
seq = move + rotate
par = move | rotate
complex_action = delay + (move | fade) + final_action
# Apply the composed action to a target
complex_action.apply(sprite)# Single update handles all actions
def on_update(self, delta_time):
Action.update_all(delta_time)- High-level declarative action API over Arcade 3.x
- Core conditional actions: MoveUntil, FollowPathUntil, RotateUntil, ScaleUntil, FadeTo
- Path following with automatic sprite rotation for smooth curved movement
- Easing wrapper for smooth acceleration/deceleration effects on any conditional action
- Composite actions (sequential, parallel) with composition helpers
- Boundary actions for arcade-style movement patterns
- Formation functions for positioning and layout patterns
- Global action management system
- Unit and integration test coverage for actions and patterns
- Example patterns for common game behaviors
- Optional PyMunk physics integration (MoveUntil, RotateUntil velocity routing; FollowPathUntil steering)
- Advanced pathfinding or AI (A*)
- Visual editor or GUI tools for creating action sequences
- Multiplayer or networking features
- Custom sprite classes (works with standard arcade.Sprite)
- Manual action tracking systems (uses global management)
| Layer | Technology |
|---|---|
| Core Language | Python 3.13+ |
| Game Engine | Arcade 3.x |
| Actions Framework | Custom-built ArcadeActions library, condition-based paradigm |
| Testing | Pytest |
| Dependencies | Minimal; self-contained aside from Arcade |
| Version Control | Git (recommended) |
| Build System | Makefile for common development tasks |
| Package Management | uv for dependency management |
This system:
✅ Makes Arcade more high-level and expressive for animation and behavior
✅ Supports condition-based behaviors critical for responsive game logic
✅ Enables rapid prototyping of sophisticated gameplay without low-level frame management
✅ Offers function-based composition for clean, declarative behavior sequences
✅ Works seamlessly with Arcade's native sprite system
✅ Provides global action management eliminating manual tracking overhead
We are delivering a modern condition-based Actions system for Arcade that empowers indie devs to build complex 2D games faster with cleaner, more maintainable code through declarative action composition.
-
Core Action Testing
- All conditional action types must have comprehensive test coverage
- Edge cases must be explicitly tested
- Boundary conditions must be tested for movement actions
- Composite actions must be tested for all combinations
- Global action management must be tested
-
Property Update Testing
- Test direct property updates for position, angle, scale, alpha
- Verify condition evaluation and action completion
- Test pause/resume functionality
- Test global action lifecycle management
-
Test Categories and Patterns
- Individual action tests using direct
action.apply()calls - Group action tests applying actions to
arcade.SpriteList - Composite action tests using composition helpers
- Formation function tests for positioning patterns
- Boundary action tests for arcade-style patterns
- Individual action tests using direct
-
Documentation Requirements
- Each test file must have a clear docstring explaining its purpose
- Each test class must document the specific action being tested
- Each test method must explain what aspect is being tested
- Complex test setups must be documented with comments
- Test fixtures must be documented with their purpose
-
Quality Requirements
- Tests must be deterministic and repeatable
- Tests must be independent of each other
- Tests must clean up after themselves using global action management
- Tests must be fast and efficient
- Tests must be maintainable and readable
This PRD provides the architectural foundation. For implementation details, consult:
- api_usage_guide.md - Primary implementation reference
- Complete API usage patterns and implementation details
- Comprehensive examples of conditional actions and composition
- Formation function usage patterns and best practices
- testing_guide.md - Testing patterns and best practices
PRD.md (this file) → Architecture & Requirements
├── api_usage_guide.md → Implementation Patterns (PRIMARY)
├── testing_guide.md → Testing Patterns & Best Practices
├── testing_guide.md → Testing Patterns
└── README.md → Quick Start Guide
ZERO TOLERANCE for runtime type/attribute checking - This includes:
hasattr()for type discriminationgetattr()with defaults for missing attributesisinstance()for runtime type checking- EAFP with exception silencing (
except AttributeError: pass)
The Real Problem: Unclear interfaces, not the checking pattern.
The Solution: Design interfaces so checking isn't needed through:
- Consistent base interfaces with well-defined contracts
- Clear protocols guaranteeing expected methods/attributes exist
- Composition patterns eliminating optional attributes
- Unified interfaces for similar objects (Action base class)
- Global Action Management: All actions must use the global
Action.update_all()system - Condition-Based Design: Actions must be condition-based, not duration-based
- Native Sprite Compatibility: Must work with standard
arcade.Spriteandarcade.SpriteList - Function Composition: Support
sequence()andparallel()for clean action combination - Tag-Based Organization: Support tagged action management for complex behaviors
- Clean API Design: Minimize wrapper methods and prefer direct action application
- No Custom Sprite Classes: Works directly with
arcade.Sprite- no ActionSprite needed - Global Management: Central
Actionclass manages all active actions automatically - Condition-Based: Actions run until conditions are met, enabling state-driven behavior
- Composition Helpers: Helper functions create composite actions cleanly
- Formation Pattern: Position sprites in organized layouts without replacing core Arcade classes
from arcadeactions import infinite, move_until
# Works with any arcade.Sprite or arcade.SpriteList
sprite = arcade.Sprite("image.png")
enemies = arcade.SpriteList()
move_until(sprite, velocity=(100, 0), condition=lambda: sprite.center_x > 700)
move_until(enemies, velocity=(100, 0), condition=infinite)from arcadeactions import infinite, move_until, rotate_until, fade_until
# Clean declarative syntax with operators
# Unbound actions can be created by passing `None` as the target
move = move_until(None, velocity=(100, 0), condition=infinite)
rotate = rotate_until(None, velocity=1.5, condition=infinite)
seq = move + rotate
par = move | rotate
complex_action = delay + (move | fade) + final_action
# Apply the composed action to a target
complex_action.apply(sprite)from arcadeactions import Action
# Single update handles all actions
def on_update(self, delta_time):
Action.update_all(delta_time)from arcadeactions import arrange_grid
arrange_grid(enemies, rows=3, cols=5, start_x=100, start_y=400)To eliminate per-wave allocations, pre-allocate sprites and reuse:
from arcadeactions.pools import SpritePool
from arcadeactions import arrange_grid
import arcade
def make_enemy():
return arcade.Sprite(":resources:images/enemies/bee.png", scale=0.5)
pool = SpritePool(make_enemy, max_size=300)
wave = pool.acquire(20)
arrange_grid(sprites=wave, rows=4, cols=5, start_x=100, start_y=400)
pool.release(wave)Arrange function contract updates:
- Provide exactly one of
spritesor creation inputs (count/sprite_factory) - For grids,
len(sprites) == rows * colswhenspritesis supplied
This architecture provides a clean, powerful, and maintainable action system that enhances Arcade without replacing its core functionality.