Documentation Index
Fetch the complete documentation index at: https://mintlify.com/sanchedev/tiny-engine/llms.txt
Use this file to discover all available pages before exploring further.
tiny-engine’s collision system pairs a spatial hash broadphase with a precise narrowphase detector to keep collision checks efficient even in dense scenes. Colliders register themselves automatically when they start, and the system is updated each frame by Game.loop() — you never call the collision pipeline directly.
Shapes
The shapes factory creates typed shape objects that describe a collider’s geometry.
Rectangle
import { shapes } from 'tiny-engine'
<collider shape={shapes.rectangle(32, 32)} group={['player']} collidesWith={['enemy']} />
shapes.rectangle(width, height) returns a RectangleShape:
interface RectangleShape {
type: 'rectangle'
size: Vector2 // { x: width, y: height }
}
Circle
<collider shape={shapes.circle(16)} group={['projectile']} collidesWith={['zombie']} />
shapes.circle(radius) returns a CircleShape:
interface CircleShape {
type: 'circle'
radius: number
}
The Shape union type is discriminated by shape.type:
type Shape = RectangleShape | CircleShape
function getArea(shape: Shape): number {
if (shape.type === 'rectangle') {
return shape.size.x * shape.size.y
}
return Math.PI * shape.radius * shape.radius
}
Collider JSX
<collider
shape={shapes.rectangle(24, 24)}
group={['player']}
collidesWith={['enemy', 'wall']}
/>
| Prop | Type | Description |
|---|
shape | Shape | The collision geometry. Required. |
group | string[] | Group labels this collider belongs to. |
collidesWith | string[] | Group labels this collider should test against. |
Collision events
Events fire on both participating colliders each frame. Subscribe with useEvent:
| Event | Payload | Description |
|---|
colliderEntered | Collider | Fires the first frame two colliders overlap. |
collided | Collider | Fires every frame while the overlap continues. |
colliderExited | Collider | Fires the first frame the overlap ends. |
import { useEvent, useRefNode } from 'tiny-engine/hooks'
import { PrimaryNode } from 'tiny-engine'
function Player() {
const collider = useRefNode(PrimaryNode.Collider)
useEvent(collider, 'colliderEntered', (other) => {
console.log('Started overlapping with', other)
})
useEvent(collider, 'collided', (other) => {
// Runs every frame while touching
})
useEvent(collider, 'colliderExited', (other) => {
console.log('Stopped overlapping with', other)
})
return (
<collider
ref={collider}
shape={shapes.rectangle(24, 24)}
group={['player']}
collidesWith={['enemy']}
/>
)
}
Full example: Projectile with damage
import { useEvent, useRefNode } from 'tiny-engine/hooks'
import { PrimaryNode, shapes } from 'tiny-engine'
function Projectile() {
const collider = useRefNode(PrimaryNode.Collider)
useEvent(collider, 'colliderEntered', (enemyCollider) => {
// Access the enemy's script and apply damage
enemyCollider.parent.script.applyDamage(20)
// Destroy the projectile on first hit
collider.node.destroy()
})
return (
<collider
ref={collider}
shape={shapes.circle(4)}
group={['projectile']}
collidesWith={['enemy']}
/>
)
}
The colliderEntered callback receives the other Collider node. From there, you can walk collider.parent to reach the owning node and call methods on its attached script.
Raycasting
A <ray-cast> node projects a ray in a given direction and tracks which collider it first hits.
import { useEvent, useRefNode } from 'tiny-engine/hooks'
import { PrimaryNode, Vector2 } from 'tiny-engine'
function EnemySensor() {
const ray = useRefNode(PrimaryNode.RayCast)
useEvent(ray, 'colliderEntered', (collider) => {
console.log('Ray hit:', collider)
})
useEvent(ray, 'colliderExited', (collider) => {
console.log('Ray left:', collider)
})
return (
<ray-cast
ref={ray}
direction={new Vector2(100, 0)}
collidesWith={['enemy']}
/>
)
}
| Prop | Type | Description |
|---|
direction | Vector2 | The direction vector of the ray. Its magnitude determines the cast length. |
collidesWith | string[] | Group labels the ray should detect. |
Ray-cast events and methods
| Member | Description |
|---|
colliderEntered | Fires when the ray begins hitting a new collider. |
colliderExited | Fires when the ray stops hitting the current collider. |
getCollider() | Returns the currently detected Collider, or null if the ray hits nothing. |
The ray always tracks only the nearest collider in its collidesWith groups. If a closer collider enters the ray path, the old one receives a colliderExited event and the new one receives colliderEntered.
Group filtering
group declares which logical layers a collider belongs to. collidesWith declares which layers it tests against. A collision is considered only when collider A’s collidesWith contains at least one group present in collider B’s group.
// This projectile tests against 'enemy' only
<collider shape={shapes.circle(4)} group={['projectile']} collidesWith={['enemy']} />
// This enemy tests against 'player' and 'projectile'
<collider shape={shapes.rectangle(16, 16)} group={['enemy']} collidesWith={['player', 'projectile']} />
You can give a collider multiple group labels to let different collider types interact with it:
<collider shape={shapes.rectangle(16, 16)} group={['wall', 'obstacle']} collidesWith={[]} />
How the spatial hash works
The spatial hash broadphase divides the world into a fixed-size grid (cell size 64). Each frame, every registered collider is inserted into the cells it overlaps — based on its AABB — and only pairs sharing a cell proceed to narrowphase shape testing.
Raycasts bypass the spatial hash entirely. They query the #colliderGroups map directly, which indexes colliders by group string, and then run a per-shape distance test to find the nearest hit.
group and collidesWith are set at construction time and are immutable afterwards. If you need dynamic layer membership, spawn a new collider node with the updated groups.