Snap Physics Plugin
he Snap Physics Plugin provides a lightweight, deterministic physics system inspired by Maddy Thorson’s TowerFall physics engine. It excels at platformer-style games requiring precise collision detection and resolution.
Core Concepts
The system is built around three main entity types:
Actors (Actor
)
Dynamic entities that can move and collide with other objects. Actors:
- Handle their own movement and collision detection
- Can ride on moving platforms
- Support pass-through collision filtering
- Maintain sub-pixel movement precision
class Player extends Actor { update(deltaTime: number) { // Move with precise collision detection this.moveX(velocity.x * deltaTime); this.moveY(velocity.y * deltaTime); }}
Solids (Solid
)
Static or kinematic objects that block movement. Solids:
- Can move and push actors
- Support one-way platforms
- Handle riding actors automatically
- Can be static or kinematic
class Platform extends Solid { move(x: number, y: number) { // Automatically pushes riding actors this.move(x, y); }}
Sensors (Sensor
)
Trigger areas that detect but don’t block movement. Sensors:
- Detect overlapping entities
- Don’t affect movement
- Support filtering by entity type
- Useful for triggers and detection zones
class TriggerZone extends Sensor { passThroughTypes = ['Actor', 'Player']; // Entities that won't trigger update() { // Check all overlapping entities this.activeCollisions = this.resolveAllCollisions(); }}
Collision Resolution
Basic Resolution
By default, collisions stop movement and prevent overlap. However, you can customize this behavior by implementing a collision resolver:
import { Scene } from 'dill-pixel';import SnapPhysicsPlugin from '@dill-pixel/plugin-snap-physics';
export class MyPhysicsScene extends Scene { get physics() { return this.app.getPlugin('snap-physics') as SnapPhysicsPlugin; }
async initialize() { // ... other initialization code this.physics.system.initialize({ // ... other options collisionResolver: this._resolveCollision, }); }
private _resolveCollision(collision: Collision) { // Implement your custom collision resolution logic here switch (collision.type) { case 'Player|Enemy': handlePlayerEnemyCollision(collision); return false; // Don't block movement case 'Player|Coin': collectCoin(collision); return false; // Pass through default: return true; // Block movement (default behavior) } }}
Advanced Resolution
The collision resolver receives detailed information about each collision:
type Collision = { entity1: Entity; // First colliding entity entity2: Entity; // Second colliding entity type: string; // Combined type (e.g. "Player|Platform") direction: string; // Collision direction overlap: { // Overlap amounts x: number; y: number; }; // Additional collision data};
Riding & Pass-Through
The system supports advanced features like:
- Riding - Actors can ride moving platforms
- Pass-Through - One-way platforms and selective collision filtering
- Pushing - Solids can push actors out of the way when moving
// Example of configuring pass-through typesclass Player extends Actor { passThroughTypes = ['OneWayPlatform'];}
Common Resolution Patterns
- One-Way Platforms:
// Example of a one-way platform`private _resolveCollision(collision: Collision): boolean { if (collision.type === 'Player|Platform') { const platform = collision.entity2 as Platform; if (platform.oneWay) { // Only collide when coming from above return collision.direction === 'bottom'; } } return true;}
- Trigger Areas:
class TriggerZone extends Sensor { private _onPlayerEnter() { // Handle player entering trigger zone }
update() { // Check for collisions without blocking const collisions = this.resolveAllCollisions(); if (collisions) { collisions.forEach((collision) => { if (collision.entity2.type === 'Player') { this._onPlayerEnter(); } }); } }}
- Moving Platforms:
class MovingPlatform extends Solid { move(dx: number, dy: number) { // First move the platform this.position.set(this.x + dx, this.y + dy); // Then push any riding actors this.getAllRiding().forEach((actor) => { actor.move(dx, dy); }); }}
Spatial Partitioning
For better performance with many objects, the system uses a spatial hash grid:
// Configure in your scenethis.physics.system.initialize({ useSpatialHashGrid: true, gridCellSize: 200, // Adjust based on average object size});
Fixed Update System
Unlike other plugins that use Pixi’s ticker, the Snap Physics Plugin implements a fixed timestep update loop for deterministic physics simulation. This approach ensures:
- Consistent physics behavior regardless of frame rate
- Predictable collision detection
- Frame-rate independent gameplay logic
How it Works
The system uses setInterval
to run physics updates at a fixed frequency (default 60 FPS):
static set enabled(value: boolean) { if (value === System._enabled) return; System._enabled = value; if (System._enabled) { // Start fixed update loop System._fixedUpdateInterval = setInterval(() => { System.fixedUpdate(System._fixedTimeStep / 1000); }, System._fixedTimeStep); } else { // Stop fixed update loop if (System._fixedUpdateInterval) { clearInterval(System._fixedUpdateInterval); System._fixedUpdateInterval = null; } }}
The fixed update cycle processes entities in a specific order:
- Pre-update phase for all entities
- Custom update hooks
- Solids update
- Sensors update
- Actors update
- Post-update phase for all entities
- Camera update (if enabled)
- Debug drawing (if enabled)
Configuration
You can configure the update frequency when initializing the plugin:
this.physics.initialize({ fps: 60, // Sets the fixed update rate // other options...});
Entity Updates
Each entity type (Actor
, Solid
, Sensor
) can implement the following update methods:
class MyEntity extends Actor { // Called before physics update preFixedUpdate() {}
// Main physics update fixedUpdate(deltaTime: number) { // Implement physics logic }
// Called after physics update postFixedUpdate() {}}
For implementation examples, see:
startLine: 67endLine: 115
Cleanup
When destroying the plugin, the fixed update loop is automatically cleaned up.
Examples
For implementation examples, see the following:
- Basic Collisions: Demo | Source
- Platformer Physics: Demo | Source
- Endless Runner: Demo | Source
- Projectile Physics: Demo | Source