Skip to content

Physics

Physics makes things fall, collide and push each other. It runs on Jolt at 60 steps a second, and only while the game plays or a scene page simulates. In edit mode nothing moves.

Give an entity Add Component > Rigid Body to make it solid. Its Motion decides who moves it:

  • Static: never moves. Floors, walls, level geometry.
  • Kinematic: moved by you, from a script or an animation, and pushes dynamic bodies out of its way. Use it for moving platforms and doors. Set its position as you would any entity’s.
  • Dynamic: moved by physics, falling and bouncing. Use it for crates, balls and debris. While the game plays, physics owns its position, so setting the position from a script has no effect.

Its Shape is one of:

Shape Size from Notes
Box Half Extents Half the box’s size along each axis: (0.5, 0.5, 0.5) is a 1 m cube.
Sphere Radius
Capsule Radius, Half Height Half Height is half the length of the straight middle.
Cooked Collision Shape A shape made from a mesh (below).
Plane Plane Half Extent The entity’s flat XZ plane, solid below. Static only.
Heightfield Heightfield Terrain ground (below). Static only.

The inspector shows only the size fields the chosen shape uses. A plane, a heightfield or a triangle-mesh cooked shape on a body that is not static is simulated as static, and the log says so.

Box, sphere and capsule sizes ignore the entity’s scale, so size them with their fields. A cooked shape follows the scale.

The other settings:

  • Friction (0.5) and Restitution (0, no bounce), or a Material (below) that sets both.
  • Linear Damping and Angular Damping (0.05 each): how quickly motion and spin die away.
  • Mass: in kilograms. At 0, the default, it is worked out from the shape’s size and density.
  • Continuous Collision: for small, fast dynamic bodies that would otherwise pass through thin walls. It costs a little more, so turn it on only where needed.
  • Is Trigger, Layer and Collision Group are described below.

A body is built when the game starts, and again when its entity is turned off and on. Changing its shape or motion while the game runs has no effect until then.

A body’s shape can be made of several shapes. Add Collider components to child entities of the body. Each collider adds its shape, at the child’s position and rotation, to the nearest rigid body above it. A table, for example, is a rigid body with a box for the top and a child collider for each leg.

For a shape that follows a model, make a Collision Shape asset (right-click in the Assets panel, then New > Physics > Collision Shape), set its Source Mesh, and set its Cook Mode:

  • ConvexHull: a simplified wrapping with no dents or holes. It can move, so use it for dynamic bodies. Hull Tolerance sets how closely it follows the mesh.
  • TriangleMesh: the mesh exactly, for static level geometry only.

Then set the body’s Shape to Cooked and its Collision Shape to the asset. Importing a model with Generate collision on does this for you: it cooks a collision shape for each mesh and adds colliders and a static rigid body to the model’s generated prefab. With Convex collision also on, the shapes are convex hulls instead of triangle meshes.

To see the shapes in the scene viewport, turn on Colliders in the viewport’s Overlays menu.

The scene viewport with the Colliders overlay on: an island’s cooked triangle-mesh shape drawn as a wireframe over it, and the character’s capsule.

A Physical Material asset (New > Physics > Physical Material) holds a Friction, a Restitution and a Density (1000 kg per cubic metre, like water, by default). Set it as a body’s Material to share the values between bodies; it overrides the body’s own friction and restitution. A ray that hits a body can tell which material it hit, for example to pick a footstep sound.

A body with Is Trigger on does not block anything. Instead it notices what enters and leaves it. Use one for pickups, checkpoints and areas that start an event. The scripts on the trigger receive onTriggerEnter and onTriggerExit, and so do the scripts on whatever entered.

Two settings filter collisions.

Layer says what kind of body it is: Static, Dynamic, Kinematic or Trigger. Static bodies never collide with each other, triggers never collide with each other, and triggers never pair with static bodies; every other pair collides. Keep the layer the same as the motion, and use Trigger for a trigger.

Collision Group (0 to 31) is yours to define. Select the scene’s own row at the top of the hierarchy, and the inspector shows the scene’s settings. Its Physics section has a Collision Groups grid: name the groups, and untick the pairs that should pass through each other, such as the player and its own bullets. By default every group collides with every other.

The same section has the scene’s Gravity (0, -9.81, 0), Collision Steps (1; more collision steps in each physics step make fast stacks steadier, at a cost), and Debug Draw, which outlines every body as the game plays: green for a moving dynamic body, grey for one that is asleep, blue for static and kinematic bodies, and yellow for triggers.

When two bodies touch, the behaviors on both entities are called, and each is told about the other:

function Crate:onContactBegin(other, point, normal, speed)
if speed > 2.0 then
Log.info("hit " .. other:name() .. " hard")
end
end
function Crate:onContactEnd(other, point, normal, speed)
end
function Checkpoint:onTriggerEnter(other)
other:send("Checkpoint")
end
function Checkpoint:onTriggerExit(other)
end

speed is how fast the two came together, which tells a tap from a crash. point is where they touched. In onContactEnd the three values are zero.

These calls arrive after the physics step, never in the middle of it, so a handler may change anything: destroy the entity, spawn another, or move a body.

ScenePhysics asks the world questions and pushes bodies:

local physics = ScenePhysics.of(self.entity.scene)
-- Straight down, up to 2 m: is there ground?
local hit = physics:rayCast(self.entity:worldPosition(), Float3.new(0, -1, 0), 2.0)
if hit.hit then
Log.info("ground " .. hit.distance .. " m below, on " .. hit:entity():name())
end
-- Everything within 3 m
for _, near in ipairs(physics:overlapSphere(self.entity:worldPosition(), 3.0)) do
physics:applyImpulse(near, Float3.new(0, 4, 0))
end
  • rayCast(from, direction, distance) finds the first body along a line. Give it a direction of length 1. The result has hit, distance, position, normal, entity() and material(); hit is false when nothing was found.
  • sphereCast(from, direction, distance, radius) does the same with a ball, for a wider probe.
  • overlapSphere(center, radius) lists every entity within the sphere. nearestOverlap(center, radius) returns the nearest one, in the same form as a ray’s result.
  • Each query takes an optional last argument, a group mask, to look only at some collision groups: with bit g set, group g counts.
  • applyImpulse(entity, impulse) kicks a dynamic body: a jump, an explosion, a hit.
  • setVelocity(entity, velocity) sets its speed and direction at once, in metres a second: a launch, a dash, a sudden stop. velocity(entity) reads it.
  • setForce(entity, force) holds a steady push on a dynamic body, in newtons, until you set it again: thrust, wind, a current. Set it to zero to let go. Because the force is held, it pushes the same at any frame rate, so there is no need to set it every frame.
  • An impulse or a velocity given to a body spawned this frame is applied once the body exists.
  • setGravity(gravity) changes the scene’s gravity while it plays, and gravity() reads it.

Queries see triggers too.

A script reads a body’s settings through RigidBodyComponent.of(entity) (RigidBodyComponent::of in AngelScript), for example its mass.

A Joint component ties its entity’s rigid body to another body, or to the world:

Kind Allows
Fixed Nothing: the two move as one.
Point Swinging in any direction about the anchor: a chain, a hanging lamp.
Hinge Turning about one axis: a door, a wheel.
Slider Sliding along one axis: a drawer, a piston.
Distance Moving freely within a range of distances: a rope.

The body it ties to is Target Entity. Left empty, it is the nearest rigid body above the entity, or the world if there is none. Local Anchor is the pivot and Local Axis the hinge or slider axis, both in the entity’s own space. The inspector shows only the fields the chosen kind uses.

Limit Min and Limit Max bound a hinge, in radians, or a slider, in metres. A minimum above the maximum means no limit, and that is the default. A hinge or slider can also have a motor: turn on Motor Enabled, and set its Motor Target Velocity (radians or metres a second) and Motor Limit, the most torque or force it may use. A Distance joint uses Min Distance and Max Distance instead; left negative, they keep the distance the bodies start at.

A player or a walking enemy needs to walk up slopes and steps, stand on moving floors, and push crates, without tipping over. The Character component does this: it is a capsule moved by your script, not by forces. Give the entity a Character instead of a rigid body.

  • Radius (0.35) and Half Height (0.55) size the capsule; its height is twice their sum, 1.8 m. The entity’s position is the capsule’s centre.
  • Max Slope Degrees (50) is the steepest ground it can stand on.
  • Step Up (0.4) and Step Down (0.5) are the steps it walks up and down without leaving the ground.
  • Mass (70) and Max Strength (500) decide how hard it pushes bodies.

Its script sets the speed it should move at every frame:

function Player:onUpdate(dt)
local character = CharacterComponent.of(self.entity)
local move = Input.value2D("Move")
character:move(move.x * 5.0, -move.y * 5.0)
if character:grounded() and Input.wasPressed("Jump") then
character:jump(6.0)
end
end
  • move(x, z) sets the speed across the ground, in metres a second; gravity does the rest.
  • jump(speed) jumps the next time it is on the ground; launch(speed) sets its upward speed even in the air, for a double jump or a spring. A negative speed slams it down.
  • drive(velocity) hands the whole motion, gravity included, to the script until the next move, for flying, swimming or sliding.
  • setPosition(position) moves it at once, to respawn it. Use this instead of the entity’s own setPosition, which physics would overwrite.
  • grounded() says whether it stands on the ground. velocity is how fast it moved over the last step, and groundNormal the direction the ground under it faces (straight up in the air).

A character sets off triggers, but it is not a body: contacts with it are not reported, and rays and overlaps do not see it.

A terrain is not solid by itself. To walk on it, give the terrain’s entity a Rigid Body with Motion Static, Shape Heightfield and its Heightfield set to the terrain’s heightfield. Holes cut in the terrain stay open. Vegetation that has a Collision Radius is solid by itself.