Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/sanchedev/fraxel/llms.txt

Use this file to discover all available pages before exploring further.

Fraxel includes a lightweight physics simulation system that handles gravity, velocity integration, force accumulation, impulses, and collision response. The system is built around the RigidBody node — a sibling to a <collider> that registers itself with the singleton PhysicsSystem at startup. Each frame the physics system applies gravity, integrates velocity into position, and resolves collisions between registered bodies using separation and impulse-based response.

RigidBody Node

Add <rigid-body> as a sibling of a <collider> node inside a <transform> to enable physics on that object. The RigidBody node reads the sibling Collider at startup and registers both with the PhysicsSystem.
import { shapes } from 'fraxel'

function FallingRock() {
  return (
    <transform position={[100, 0]}>
      <sprite textureId={ROCK} />
      <collider shape={shapes.circle(16)} group={['rock']} collidesWith={['ground']} />
      <rigid-body mass={2} bounce={0.6} />
    </transform>
  )
}
<rigid-body> must be a sibling of a <collider> inside the same <transform>. Without a sibling collider the physics body registers but has no shape to collide with.

Props

PropTypeDefaultDescription
massnumber1Body mass. Higher values are heavier. 0 = infinite mass (static)
frictionnumber0.1Friction coefficient (0–1). Applied to sliding on collision
bouncenumber0Restitution coefficient. 0 = no bounce, 1 = perfect bounce
isStaticbooleanfalseIf true, the body never moves regardless of forces
useGravitybooleantrueIf false, gravity is not applied to this body

Gravity

The default gravity is 980 px/s² downward — equivalent to Earth gravity in pixel units. Change it at any time through the PhysicsSystem.gravity static setter:
import { PhysicsSystem, Vector2 } from 'fraxel'

// Standard downward gravity (default)
PhysicsSystem.gravity = new Vector2(0, 980)

// Float upward
PhysicsSystem.gravity = new Vector2(0, -200)

// Moon gravity (~1/6 Earth)
PhysicsSystem.gravity = new Vector2(0, 160)

// Zero gravity — space / top-down game
PhysicsSystem.gravity = new Vector2(0, 0)
PhysicsSystem is a singleton — gravity changes apply immediately to every non-static body in the scene.

Forces & Impulses

Access the underlying PhysicsBody through rigidBodyNode.physicsBody. The physics body exposes three motion methods:

applyForce(force: Vector2)

Accumulates a force that is applied during the next physics integration step. Forces are expressed in pixels/second² and reset after each frame. Use for persistent effects such as thrust, wind, or buoyancy.

applyImpulse(impulse: Vector2)

Adds an instantaneous velocity change in pixels/second. The change is permanent until another force modifies the velocity. Use for jumps, explosions, or knockback.

setVelocity(v: Vector2)

Replaces the body’s velocity directly with the supplied value.
import { useNode, useEvent } from 'fraxel/hooks'
import { PrimaryNode, Vector2, shapes } from 'fraxel'

function Player() {
  const body = useNode(PrimaryNode.RigidBody)

  useEvent(body, 'updated', (delta) => {
    // Continuous rightward thrust
    body.node.physicsBody.applyForce(new Vector2(100 * delta, 0))
  })

  const jump = () => {
    // Instant upward impulse
    body.node.physicsBody.applyImpulse(new Vector2(0, -400))
  }

  return (
    <transform>
      <sprite textureId={PLAYER} onClick={jump} />
      <collider shape={shapes.rectangle(16, 16)} group={['player']} collidesWith={['ground']} />
      <rigid-body ref={body} />
    </transform>
  )
}

setVelocity

Use setVelocity when you want direct, authoritative control over movement — for example, capping a falling speed or matching a conveyor belt:
import { Vector2 } from 'fraxel'

// Cap downward speed to 500 px/s
useEvent(body, 'updated', () => {
  const vel = body.node.physicsBody.velocity
  if (vel.y > 500) {
    body.node.physicsBody.setVelocity(new Vector2(vel.x, 500))
  }
})

Static Bodies

Set isStatic (or mass={0}) on any rigid body that should never move — platforms, walls, and ground tiles are typical candidates. Static bodies still participate in collision resolution and push dynamic bodies away, but their own position is never changed by the physics solver.
function Platform() {
  return (
    <transform position={[0, 100]}>
      <sprite textureId={PLATFORM} />
      <collider
        shape={shapes.rectangle(128, 16)}
        group={['ground']}
        collidesWith={['player', 'rock']}
      />
      <rigid-body isStatic />
    </transform>
  )
}
Both of the following are equivalent ways to create a static body:
<rigid-body isStatic />
<rigid-body mass={0} />

Collision Response

When two registered physics bodies overlap, PhysicsSystem runs a three-step resolver each frame:
  1. Separation — both bodies are pushed apart along the collision normal, proportional to their inverse mass (lighter bodies move more).
  2. Impulse — a velocity impulse is applied to each body based on relative velocity and the bounce coefficient. A bounce of 0 absorbs all relative velocity; 1 reflects it perfectly.
  3. Friction — lateral velocity is reduced by the friction coefficient of both bodies to simulate sliding resistance.
// Bouncy ball — high restitution, low friction
<rigid-body mass={1} bounce={0.9} friction={0.01} />

// Heavy, grippy box — medium restitution, high friction
<rigid-body mass={5} bounce={0.2} friction={0.8} />

// Immovable platform — static, no bounce
<rigid-body mass={0} isStatic />

Complete Example

The following scene demonstrates gravity, a player with jump impulse, and a static ground:
import { useNode, useMount, useEvent } from 'fraxel/hooks'
import { PrimaryNode, PhysicsSystem, Vector2, shapes } from 'fraxel'

function GameScene() {
  useMount(() => {
    PhysicsSystem.gravity = new Vector2(0, 980)
  })

  return (
    <transform>
      <sprite textureId={BG} />

      {/* Player */}
      <transform position={[50, 50]}>
        <sprite textureId={PLAYER} />
        <collider shape={shapes.rectangle(16, 16)} group={['player']} collidesWith={['ground']} />
        <rigid-body />
      </transform>

      {/* Ground */}
      <transform position={[0, 150]}>
        <sprite textureId={GROUND} />
        <collider shape={shapes.rectangle(192, 16)} group={['ground']} collidesWith={['player']} />
        <rigid-body isStatic />
      </transform>
    </transform>
  )
}
For the full collision API — shapes, groups, and collision events — see API: Collision and Nodes (2D).

Build docs developers (and LLMs) love