Skip to content

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 layers
const WATER = physics.registerCollisionLayer('WATER', 0, 'Water surfaces');
const LAVA = physics.registerCollisionLayer('LAVA', 1, 'Lava that damages players');
// Create an entity with custom collision layer
const 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 enemies
const 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 enemies
const 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 layers
const environmentMask = physics.createCollisionMask(CollisionLayer.PLATFORM, CollisionLayer.WALL, WATER, LAVA);
// Check if a layer is included in a mask
if (CollisionLayers.isLayerInMask(CollisionLayer.PLAYER, entity.collisionMask)) {
console.log('Entity can collide with players');
}
// Get all registered layers
const 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 system
await 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:

  1. Divides the world into grid cells
  2. Tracks which solids are in each cell
  3. Only checks collisions with solids in relevant cells
  4. 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 collisions
physics.setActorCollisionsEnabled(true);
// Set up a custom resolver for actor collisions
physics.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:

  1. Use Selective Collision Masks:
// Only check collisions between relevant actors
const 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
});
  1. 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);
}
});
  1. 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

  1. Riding Detection:
class Actor {
isRidingSolid(): boolean {
// Cached check for performance
if (this._isRidingSolidCache !== null) {
return this._isRidingSolidCache;
}
return this.checkRiding();
}
}
  1. 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 platform
const platform = physics.createSolid({
type: 'Platform',
position: [100, 300],
size: [200, 32],
view: platformSprite,
collisionLayer: CollisionLayer.PLATFORM,
collisionMask: CollisionLayer.PLAYER | CollisionLayer.ENEMY,
});
// Horizontal movement
gsap.to(platform, {
x: 500, // Target x position
duration: 2,
ease: 'power1.inOut',
yoyo: true, // Reverse the animation
repeat: -1, // Repeat infinitely
});
// Vertical movement
gsap.to(platform, {
y: platform.y - 200, // Move up 200 pixels
duration: 3,
ease: 'power1.inOut',
yoyo: true,
repeat: -1,
});
  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 layers
const 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 layers
const 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 layers
const coin = physics.createSensor({
type: 'Coin',
position: [400, 300],
size: [32, 32],
view: coinSprite,
collisionLayer: CollisionLayer.ITEM,
collisionMask: CollisionLayer.PLAYER,
});

Lifecycle Management

// Remove entities
physics.removeActor(actor);
physics.removeSolid(solid);
physics.removeSensor(sensor);
// Clean up
physics.destroy(); // Removes all entities and stops updates

Examples

For implementation examples, see:

Performance Tips

  1. 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²)
});
  1. Culling:
// Enable automatic culling of off-screen entities
physics.initialize(app, {
culling: true,
boundary: new Rectangle(0, 0, width, height),
});
  1. Entity Pooling:
// Use object pooling for frequently created/destroyed entities
const 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 rendering
physics.system.debug = true;
// Debug features:
// - Grid cell visualization
// - Entity bounds
// - Collision points
// - Velocity vectors
// - Custom debug colors per entity
// - Collision layer visualization