Physics

The engine includes a 2D physics system with collision detection. It runs in the physics phase of the game loop. Physics is built from three pieces:

  • Colliders define an entity’s collision shape and the layer it belongs to.
  • RigidBody makes an entity move under velocity, acceleration, and gravity.
  • CollisionRepository lets systems query which colliders are currently colliding.

RigidBody

The RigidBody component enables physics movement for an entity. There are three body types:

TypeDescription
RigidBodyType.DynamicAffected by gravity and velocity, and moved by collisions with other bodies. For objects that need full physical behavior.
RigidBodyType.KinematicMoved by applied velocity, but unaffected by gravity or collisions from other bodies. For moving platforms or scripted movement.
RigidBodyType.StaticImmobile. Unaffected by velocity or gravity. For walls and level geometry.
OptionTypeDescription
typeRigidBodyTypeThe body type. Defaults to Dynamic.
velocityVector2Velocity in pixels per second. For Dynamic and Kinematic bodies.
accelerationVector2Acceleration in pixels per second squared. For Dynamic and Kinematic bodies.
gravitynumberGravity in pixels per second squared. For Dynamic bodies only.

A RigidBody works together with a collider on the same entity. To make an entity move and collide, add both a collider and a RigidBody:

import { Transform, RigidBody, RigidBodyType, BoxCollider } from "angry-pixel";

this.entityManager.createEntity([
    new Transform(),
    new BoxCollider({ width: 16, height: 16, layer: "Player" }),
    new RigidBody({ type: RigidBodyType.Dynamic, gravity: 1000 }),
]);

Colliders

A collider defines a collision shape. The engine provides five collider components:

ComponentShape
BoxColliderRectangle.
BallColliderCircle.
PolygonColliderConvex polygon.
EdgeColliderConnected line segments.
TilemapColliderColliders generated from a tilemap’s edges.

All colliders share these options:

OptionTypeDescription
layerstringThe collision layer the collider belongs to.
physicsbooleantrue if the collider interacts with rigid bodies. Defaults to true.
offsetVector2Offset from the entity’s position.
ignoreCollisionsWithLayersstring[]Layers this collider ignores.

Each shape adds its own options — for example, BoxCollider has width, height, and rotation; BallCollider has radius. See Built-in Components for the full options of each collider.

A collider can be added without a RigidBody when an entity only needs collision detection, such as a static wall:

import { Transform, BoxCollider } from "angry-pixel";

this.entityManager.createEntity([
    new Transform(),
    new BoxCollider({ width: 64, height: 16, layer: "Wall" }),
]);

When physics is true, the collider participates in physics resolution with rigid bodies. When false, it is only used for collision detection — useful for triggers and sensors that report overlaps without pushing other bodies.

Collision layers

Each collider belongs to a layer. Which layers are allowed to collide is configured with the collision matrix in the game configuration. See The Game Class.

collisions: {
    collisionMatrix: [
        ["Player", "Enemy"],
        ["Player", "Wall"],
    ],
}

With this matrix, players collide with enemies and walls, but enemies and walls do not collide with each other.

A collider can also opt out of specific layers with ignoreCollisionsWithLayers.

Querying collisions

The CollisionRepository reports the collisions detected in the current frame. In systems extending GameSystem it is available as this.collisionRepository. See Custom Components and Systems.

MethodDescription
findCollisionsForCollider(collider)Collisions where the given collider is the local collider.
findCollisionsForColliderAndLayer(collider, layer)Collisions with a remote collider on the given layer.
findAll()All current collisions.
import { GameSystem, BoxCollider } from "angry-pixel";
import { Player } from "../component/Player";

export class PlayerCollisionSystem extends GameSystem {
    onUpdate() {
        this.entityManager.search(Player, (player, entity) => {
            const collider = this.entityManager.getComponent(entity, BoxCollider);
            const collisions = this.collisionRepository.findCollisionsForColliderAndLayer(collider, "Enemy");

            for (const collision of collisions) {
                // react to hitting an enemy
            }
        });
    }
}

Each Collision contains:

FieldTypeDescription
localEntityEntityThe entity the queried collider belongs to.
localColliderColliderThe queried collider.
remoteEntityEntityThe other entity in the collision.
remoteColliderColliderThe other collider.
resolutionCollisionResolutionCollision data: penetration (overlap in pixels) and direction (Vector2).