ArcadeActions extension library for Arcade 3.x
🚀 Quick Appeal
So much of building an arcade game is a cluttered way of saying "animate this sprite until something happens", like colliding with another sprite, reaching a boundary, or an event response. Most of us manage this complexity in the game loop, using low-level movement of game objects and complex chains of if
-statements. But what if you could write a command like "keep moving this sprite, wrap it the other side of the window if it hits a boundary, and raise an event when it collides with another sprite" ... and have it be clear and concise?
import arcade
from actions import infinite, move_until, parallel
move_until(asteroid, velocity=(5, 4), condition=infinite, bounds=(-64, -64, 864, 664), boundary_behavior="wrap")
def asteroid_collision_check():
player_hit = arcade.check_for_collision(player, asteroid)
asteroid_hits = arcade.check_for_collision_with_list(asteroid, other_asteroids)
if player_hit or asteroid_hits:
return {
"player_hit": player_hit,
"asteroid_hits": asteroid_hits,
}
return None
def handle_asteroid_collision(collision_data):
if collision_data["player_hit"]:
print("Player destroyed!")
for asteroid in collision_data["asteroid_hits"]:
print("Asteroid collisions!")
This example shows how animation actions can be logically separated from collision responses, making your code simple and appealing.
If writing high-level game code appeals to you ... it's why you chose Python in the first place ... read on!
📚 Documentation Overview
Essential Reading
- API Usage Guide - START HERE - Complete guide to using the framework
- Testing Guide - Testing patterns and best practices
- PRD - Project requirements and architecture decisions
🚀 Getting Started
- Read the API Usage Guide to understand the framework
- Study the Slime Invaders example (remade for ArcadeActions) or the Stars example to understand the power of Actions
- Start with simple conditional actions and build up to complex compositions
- Use formation functions for organizing sprite positions and layouts
📖 Documentation Structure
docs/
├── README.md # This file - overview and quick start
├── api_usage_guide.md # Complete API usage patterns (START HERE)
├── testing_guide.md # Testing patterns and fixtures
└── prd.md # Requirements and architecture
🔧 Core Components
✅ Implementation
Base Action System (actions/base.py)
- Action - Core action class with global management
- CompositeAction - Base for sequential and parallel actions
- Global management - Automatic action tracking and updates
- Composition helpers -
sequence()
and parallel()
functions
Conditional Actions (actions/conditional.py)
- MoveUntil - Velocity-based movement until condition met
- FollowPathUntil - Follow Bezier curve paths with optional automatic sprite rotation
- RotateUntil - Angular velocity rotation
- ScaleUntil - Scale velocity changes
- FadeUntil - Alpha velocity changes
- DelayUntil - Wait for condition to be met
- TweenUntil - Direct property animation from start to end value
Composite Actions (actions/composite.py)
- Sequential actions - Run actions one after another (use
sequence()
)
- Parallel actions - Run actions in parallel (use
parallel()
)
Boundary Handling (actions/conditional.py)
- MoveUntil with bounds - Built-in boundary detection with bounce/wrap behaviors
Formation Management (actions/formation.py)
- Formation functions - Grid, line, circle, diamond, and V-formation positioning
Movement Patterns (actions/pattern.py)
- Movement pattern functions - Zigzag, wave, spiral, figure-8, orbit, bounce, and patrol patterns
- Condition helpers - Time-based and sprite count conditions for conditional actions
Easing Effects (actions/easing.py)
- Ease wrapper - Apply smooth acceleration/deceleration curves to any conditional action
- Multiple easing functions - Built-in ease_in, ease_out, ease_in_out support
- Custom easing - Create specialized easing curves and nested easing effects
📋 Decision Matrix
Simple sprite actions | Helper functions | move_until(sprite, ..., tag="move") |
Sprite group actions | Helper functions on SpriteList | move_until(enemies, ..., tag="formation") |
Complex sequences | Direct classes + sequence() | sequence(DelayUntil(...), MoveUntil(...)) |
Parallel behaviors | Direct classes + parallel() | parallel(MoveUntil(...), RotateUntil(...)) |
Formation positioning | Formation functions | arrange_grid(enemies, rows=3, cols=5) |
Curved path movement | follow_path_until helper | follow_path_until(sprite, points, ...) |
Boundary detection | move_until with bounds | move_until(sprite, ..., bounds=bounds, boundary_behavior="bounce") |
Smooth acceleration | ease() helper | ease(sprite, action, ...) |
Complex curved movement | ease() + follow_path_until | ease(sprite, follow_path_until(...), ...) |
Property animation | tween_until helper | tween_until(sprite, 0, 100, "center_x", ...) |
Standard sprites (no actions) | arcade.Sprite + arcade.SpriteList | Regular Arcade usage |
🎮 Example: Space Invaders Pattern
import arcade
from actions import Action, arrange_grid, duration, move_until
class SpaceInvadersGame(arcade.Window):
def __init__(self):
super().__init__(800, 600, "Space Invaders")
enemies = arrange_grid(
rows=5,
cols=10,
start_x=100,
start_y=500,
spacing_x=60,
spacing_y=40,
sprite_factory=lambda: arcade.Sprite(":resources:images/enemy.png"),
)
self.enemies = enemies
self._setup_movement_pattern()
def _setup_movement_pattern(self):
def on_boundary_hit(sprite, axis):
if axis == "x":
move_until(self.enemies, velocity=(0, -30), condition=duration(0.3))
bounds = (50, 0, 750, 600)
move_until(
self.enemies,
velocity=(50, 0),
condition=infinite,
bounds=bounds,
boundary_behavior="bounce",
on_boundary=on_boundary_hit
)
def on_update(self, delta_time):
Action.update_all(delta_time)