Crunch Physics Plugin
The Crunch Physics Plugin is a robust, grid-based physics system designed for high-performance games with many entities. It excels at platformers, endless runners, and games requiring precise collision detection with efficient spatial partitioning.
Core Features
- Grid-based spatial partitioning for efficient collision detection
- Support for dynamic actors, static solids, and trigger sensors
- Advanced entity grouping and management
- Built-in culling system for off-screen entities
- Debug visualization tools
- Customizable collision layers and masks for fine-grained collision control
- Pixel-based physics system optimized for 2D games
Core Concepts
The system is built around three main entity types and a group system:
Actors (Actor
)
Dynamic entities that can move and collide with solids. Key features:
- Velocity-based movement with gravity (in pixels/second²)
- Precise collision detection and response
- Support for riding moving platforms
- Sub-pixel movement precision
- Configurable collision layers and masks
class Player extends Actor { initialize() { // Setup player-specific properties this.velocity = { x: 0, y: 0 }; // Velocity in pixels/second // Set collision layer and mask this.collisionLayer = CollisionLayer.PLAYER; this.collisionMask = CollisionLayer.PLATFORM | CollisionLayer.WALL | CollisionLayer.ENEMY; }
update(dt: number) { // Handle movement and physics if (this.isRidingSolid()) { this.velocity.y = 0; } super.update(dt); }
onCollide(result: CollisionResult) { // Handle collisions if (CollisionLayers.isLayerInMask(CollisionLayer.ENEMY, result.solid.collisionLayer)) { this.die(); } }}
Solids (Solid
)
Static or kinematic objects that block movement. Features:
- Can be static or moving
- Automatically push overlapping actors
- Carry riding actors
- Support for one-way platforms
- Efficient grid-based collision checks
- Configurable collision layers for selective collisions
class Platform extends Solid { initialize() { this.collideable = true; // Enable collisions this.moving = true; // Enable movement updates // Set up collision layer and mask this.collisionLayer = CollisionLayer.PLATFORM; this.collisionMask = CollisionLayer.PLAYER | CollisionLayer.ENEMY; }
update(dt: number) { // Handle platform-specific logic if (this.data.isOneWay) { // Custom one-way platform logic this.handleOneWayCollisions(); } super.update(dt); }}
Sensors (Sensor
)
Trigger zones that detect but don’t block movement. Features:
- Overlap detection with specific actor types
- Enter/exit event callbacks
- Optional gravity and movement
- Can be static or dynamic
- Selective collision detection using layers
class Coin extends Sensor { initialize() { // Set up collision layer and mask to only detect players this.collisionLayer = CollisionLayer.ITEM; this.collisionMask = CollisionLayer.PLAYER; this.isStatic = true; }
onActorEnter(actor: Actor) { if (CollisionLayers.isLayerInMask(CollisionLayer.PLAYER, actor.collisionLayer)) { (actor as Player).addScore(10); this.system.removeSensor(this); } }}
Groups (Group
)
Container for managing collections of related entities:
- Manage multiple entities as a single unit
- Share common properties and behaviors
- Efficient batch updates and transformations
class PlatformGroup extends Group { initialize() { // Create a group of platforms const platform1 = this.system.createSolid({ /*...*/ collisionLayer: CollisionLayer.PLATFORM, collisionMask: CollisionLayer.PLAYER | CollisionLayer.ENEMY, }); const platform2 = this.system.createSolid({ /*...*/ collisionLayer: CollisionLayer.PLATFORM, collisionMask: CollisionLayer.PLAYER | CollisionLayer.ENEMY, });
this.add(platform1); this.add(platform2); }
update(dt: number) { // Update all platforms in the group super.update(dt); }}
Collision Layer System
The Crunch Physics Plugin uses a powerful bitwise collision layer system that allows for fine-grained control over which entities can collide with each other:
Built-in Layers
// Built-in collision layers (bits 0-15)enum CollisionLayer { NONE = 0, DEFAULT = 1 << 0, PLAYER = 1 << 1, ENEMY = 1 << 2, PROJECTILE = 1 << 3, PLATFORM = 1 << 4, TRIGGER = 1 << 5, ITEM = 1 << 6, WALL = 1 << 7, FX = 1 << 8, // Bits 16-31 are reserved for user-defined layers ALL = 0xffffffff,}
Custom Layers
You can create custom collision layers using bits 16-31:
// Register custom collision layersconst WATER = physics.registerCollisionLayer('WATER', 0, 'Water surfaces');const LAVA = physics.registerCollisionLayer('LAVA', 1, 'Lava that damages players');
// Create an entity with custom collision layerconst waterArea = physics.createActor({ type: 'Water', position: [100, 400], size: [800, 100], collisionLayer: WATER, collisionMask: CollisionLayer.PLAYER | CollisionLayer.ENEMY,});
Collision Masks
Collision masks determine which layers an entity can interact with:
// Create a player that collides with platforms, walls, and enemiesconst player = physics.createActor({ type: 'Player', position: [100, 100], size: [32, 64], collisionLayer: CollisionLayer.PLAYER, collisionMask: CollisionLayer.PLATFORM | CollisionLayer.WALL | CollisionLayer.ENEMY,});
// Create a projectile that only collides with enemiesconst projectile = physics.createActor({ type: 'Projectile', position: [0, 0], size: [16, 16], collisionLayer: CollisionLayer.PROJECTILE, collisionMask: CollisionLayer.ENEMY,});
Layer Management
// Create a custom collision mask from multiple layersconst environmentMask = physics.createCollisionMask(CollisionLayer.PLATFORM, CollisionLayer.WALL, WATER, LAVA);
// Check if a layer is included in a maskif (CollisionLayers.isLayerInMask(CollisionLayer.PLAYER, entity.collisionMask)) { console.log('Entity can collide with players');}
// Get all registered layersconst layers = physics.getCollisionLayers();console.log(`Registered layers: ${layers.map((l) => l.name).join(', ')}`);
Spatial Grid System
The Crunch Physics Plugin uses an efficient grid-based spatial partitioning system:
// Configure the physics systemawait physics.initialize(app, { gridSize: 32, // Size of each grid cell in pixels gravity: 900, // Gravity in pixels/second² maxVelocity: 1000, // Maximum velocity in pixels/second debug: true, // Enable debug visualization culling: true, // Enable automatic culling boundary: new Rectangle(0, 0, 800, 600), // Boundary in pixels});
Grid-Based Collision Detection
The system automatically:
- Divides the world into grid cells
- Tracks which solids are in each cell
- Only checks collisions with solids in relevant cells
- Updates grid assignments when entities move
// Internal grid management (handled automatically)private addSolidToGrid(solid: Solid): void { const cells = this.getCells(solid.bounds); for (const cell of cells) { if (!this.grid.has(cell)) { this.grid.set(cell, new Set()); } this.grid.get(cell)!.add(solid); }}
Collision Resolution
Basic Resolution
The system provides flexible collision handling:
class MyScene extends Scene { async initialize() { await this.physics.initialize(app, { collisionResolver: (collisions: Collision[]) => { for (const collision of collisions) { // Check collision types using layers const isPlayerEnemyCollision = CollisionLayers.isLayerInMask(CollisionLayer.PLAYER, collision.entity1.collisionLayer) && CollisionLayers.isLayerInMask(CollisionLayer.ENEMY, collision.entity2.collisionLayer);
if (isPlayerEnemyCollision) { this.handlePlayerEnemyCollision(collision); } } }, }); }}
Actor-to-Actor Collisions
By default, actors only collide with solids. However, you can enable actor-to-actor collisions for specific scenarios:
// Enable actor-to-actor collisionsphysics.setActorCollisionsEnabled(true);
// Set up a custom resolver for actor collisionsphysics.setActorCollisionResolver((collisions: ActorCollision[]) => { for (const collision of collisions) { // Example: Bullets colliding with enemies const isBulletEnemyCollision = CollisionLayers.isLayerInMask(CollisionLayer.PROJECTILE, collision.actor1.collisionLayer) && CollisionLayers.isLayerInMask(CollisionLayer.ENEMY, collision.actor2.collisionLayer);
if (isBulletEnemyCollision) { handleBulletEnemyCollision(collision); } }});
Use Cases
Actor-to-actor collisions are useful for:
- Projectile-based combat systems
- Enemy-enemy collision avoidance
- Player vs player interactions in multiplayer games
- Physics-based puzzles with multiple moving objects
- Particle system interactions
Best Practices
To minimize performance impact when using actor-to-actor collisions:
- Use Selective Collision Masks:
// Only check collisions between relevant actorsconst bullet = physics.createActor({ type: 'Bullet', collisionLayer: CollisionLayer.PROJECTILE, collisionMask: CollisionLayer.ENEMY, // Only collide with enemies});
const enemy = physics.createActor({ type: 'Enemy', collisionLayer: CollisionLayer.ENEMY, collisionMask: CollisionLayer.PROJECTILE | CollisionLayer.PLAYER, // Only collide with bullets and player});
- Implement Early Exit Conditions:
physics.setActorCollisionResolver((collisions: ActorCollision[]) => { for (const collision of collisions) { // Skip processing if either actor is already destroyed if (!collision.actor1.active || !collision.actor2.active) continue;
// Process only necessary collisions handleActorCollision(collision); }});
- Consider Alternatives:
- Use sensors for overlap detection when collision response isn’t needed
- Implement spatial awareness systems for enemy AI instead of physical collisions
- Use simplified collision shapes for performance-critical interactions
- Consider disabling actor collisions in areas with many entities
Advanced Features
- Riding Detection:
class Actor { isRidingSolid(): boolean { // Cached check for performance if (this._isRidingSolidCache !== null) { return this._isRidingSolidCache; } return this.checkRiding(); }}
- Moving Platforms:
class MovingPlatform extends Solid { update(dt: number) { // Platform movement automatically: // - Updates grid position // - Pushes overlapping actors // - Carries riding actors this.move(dx, dy); }}
Animating Platforms
You can create smooth platform movements using GSAP:
// Create a moving platformconst platform = physics.createSolid({ type: 'Platform', position: [100, 300], size: [200, 32], view: platformSprite, collisionLayer: CollisionLayer.PLATFORM, collisionMask: CollisionLayer.PLAYER | CollisionLayer.ENEMY,});
// Horizontal movementgsap.to(platform, { x: 500, // Target x position duration: 2, ease: 'power1.inOut', yoyo: true, // Reverse the animation repeat: -1, // Repeat infinitely});
// Vertical movementgsap.to(platform, { y: platform.y - 200, // Move up 200 pixels duration: 3, ease: 'power1.inOut', yoyo: true, repeat: -1,});
- Sensor Overlaps:
class TriggerZone extends Sensor { checkActorOverlaps(): Set<SensorOverlap> { // Efficiently check overlaps using collision layers // Triggers enter/exit callbacks return this._checkOverlaps(); }}
Entity Management
Creation and Initialization
// Create an actor with collision layersconst player = physics.createActor({ type: 'Player', position: [100, 100], size: [32, 64], view: playerSprite, collisionLayer: CollisionLayer.PLAYER, collisionMask: CollisionLayer.PLATFORM | CollisionLayer.WALL | CollisionLayer.ENEMY,});
// Create a solid with collision layersconst platform = physics.createSolid({ type: 'Platform', position: [0, 500], size: [800, 32], view: platformSprite, collisionLayer: CollisionLayer.PLATFORM, collisionMask: CollisionLayer.PLAYER | CollisionLayer.ENEMY,});
// Create a sensor with collision layersconst coin = physics.createSensor({ type: 'Coin', position: [400, 300], size: [32, 32], view: coinSprite, collisionLayer: CollisionLayer.ITEM, collisionMask: CollisionLayer.PLAYER,});
Lifecycle Management
// Remove entitiesphysics.removeActor(actor);physics.removeSolid(solid);physics.removeSensor(sensor);
// Clean upphysics.destroy(); // Removes all entities and stops updates
Examples
For implementation examples, see:
Performance Tips
- Grid Size Optimization:
// Choose grid size based on average entity size (in pixels)physics.initialize(app, { gridSize: 32, // Adjust based on your game's needs gravity: 900, // Typical platformer gravity (pixels/second²)});
- Culling:
// Enable automatic culling of off-screen entitiesphysics.initialize(app, { culling: true, boundary: new Rectangle(0, 0, width, height),});
- Entity Pooling:
// Use object pooling for frequently created/destroyed entitiesconst pool = new Pool<Actor>(Actor);const actor = pool.get(config);physics.addActor(actor);// Later...pool.return(actor);
Debug Visualization
The plugin includes built-in debug visualization:
// Enable debug renderingphysics.system.debug = true;
// Debug features:// - Grid cell visualization// - Entity bounds// - Collision points// - Velocity vectors// - Custom debug colors per entity// - Collision layer visualization