Skip to content

Physics Components

The physics module provides three components for adding physics to entities: Collider, RigidBody, and StaticBody. All require a Transform component.

physics.Collider

Defines the collision shape for an entity. By itself (without RigidBody or StaticBody), creates a kinematic body that is moved by Transform, not by physics.

teal
local Transform = tecs.builtins.Transform

-- Circle collider
world:spawn(
    Transform(100, 100),
    physics.Collider.new({ shape = "circle", radius = 16 })
)

-- Rectangle collider
world:spawn(
    Transform(200, 100),
    physics.Collider.new({ shape = "rectangle", width = 32, height = 48 })
)

Configuration

PropertyTypeDefaultDescription
shapestring"circle"Shape type: "circle" or "rectangle"
radiusnumber16Radius for circle shapes
widthnumber32Width for rectangle shapes
heightnumber32Height for rectangle shapes
sensorbooleanfalseIf true, detects collisions but doesn't respond physically
restitutionnumber0Bounciness (0 = no bounce, 1 = full bounce)
frictionnumber0.3Surface friction
categoriesinteger0xFFFFCollision category bitmask (16-bit)
maskinteger0xFFFFCollision mask bitmask (16-bit)
groupIndexinteger0Collision group (-32768 to 32767, see below)

See Shape:setFilterData for details on collision filtering.

Restitution and friction precedence

  • For static bodies, the Collider's restitution and friction values are used.
  • For dynamic bodies (entities with RigidBody), the RigidBody's restitution and friction values take precedence and the Collider's values are ignored.

Runtime Access

After spawning, the Collider component exposes the Box2D body and shapes:

FieldTypeDescription
bodyBodyThe Box2D body
shapeList{Shape}List of shapes (for multi-shape colliders)
teal
local c = world:get(entityId, physics.Collider)
c.body:applyForce(100, 0)
c.body:applyLinearImpulse(0, -200)
c.body:setLinearVelocity(100, 0)

See Body:applyForce and Body:applyLinearImpulse for details.

physics.RigidBody

Makes an entity a dynamic physics body that responds to forces and collisions.

teal
world:spawn(
    Transform(100, 100),
    physics.Collider.new({ shape = "circle", radius = 16 }),
    physics.RigidBody.new({ mass = 1, restitution = 0.8 })
)

Configuration

PropertyTypeDefaultDescription
massnumbernilExplicit body mass in kg (see below)
densitynumber1Shape density in kg/px², used by Box2D to compute mass from area
restitutionnumber0Bounciness, 0-1 (overrides Collider's value)
frictionnumber0.3Surface friction, 0+ (overrides Collider's value)
dragnumber0Linear damping, 0+ (higher = more resistance)
angularDragnumber0Angular damping, 0+ (higher = more rotational resistance)
vxnumber0Initial X velocity in px/s
vynumber0Initial Y velocity in px/s
bulletbooleanfalseEnable continuous collision detection for fast objects
fixedRotationbooleanfalsePrevent rotation (default from plugin config)

The fixedRotation default comes from the plugin's defaultFixedRotation config option. If neither is set, it defaults to false (matching Box2D's default).

Mass vs Density

By default, Box2D computes mass automatically from shape area and density. This is usually what you want: larger shapes are heavier.

Set mass to override this with an explicit value. When mass is set, the body's total mass is forced to that value after shape creation, preserving the center of mass and inertia computed from density.

See Body for more about Box2D bodies.

physics.StaticBody

Marks an entity as a static physics body. Static bodies don't move but can be collided with. Takes no arguments.

teal
-- Ground platform
world:spawn(
    Transform(400, 550),
    physics.Collider.new({ shape = "rectangle", width = 800, height = 20 }),
    physics.StaticBody()
)

Multi-Shape Colliders

Create colliders with multiple shapes for complex hitboxes:

teal
world:spawn(
    Transform(100, 100),
    physics.Collider.new({
        shapes = {
            { type = "circle", radius = 16, offsetX = 0, offsetY = -20 },  -- Head
            { type = "rectangle", width = 24, height = 40 },               -- Body
        }
    }),
    physics.RigidBody.new({ mass = 1 })
)

All shapes share a single Box2D body. Individual shapes are accessible at runtime via Collider.shapeList.

Shape Config Fields

PropertyTypeDefaultDescription
typestring"circle" or "rectangle" (required)
radiusnumber16Radius for circle shapes
widthnumber32Width for rectangle shapes
heightnumber32Height for rectangle shapes
offsetXnumber0X offset from body center
offsetYnumber0Y offset from body center
sensorbooleanfalsePer-shape sensor flag
namestringnilOptional name, accessible via shape user data

Applying Forces

Access the Box2D body through the Collider component. Force application should typically run in FixedUpdate:

teal
world:addSystem({
    phase = tecs.phases.FixedUpdate,
    run = function()
        local c = world:get(playerId, physics.Collider)
        if c and c.body then
            c.body:applyForce(100, 0)
            c.body:applyLinearImpulse(0, -200)
            c.body:setLinearVelocity(100, 0)
        end
    end
})