Physics and collisions
Physics lets objects collide, fall, slide, trigger areas, and respond to forces. Opal's 2D physics runtime is Rapier when available, with a shared inspector and Flow events on top.
Core components
Physics usually starts with two components:
| Component | Purpose | When to add it |
|---|---|---|
| Collider 2D | Defines the collision shape | Always — without it nothing collides |
| Rigid Body 2D | Defines how it moves in the physics world | When it should fall, be pushed, or be kinematic |
An object can have a collider without a rigid body when it should act as a static obstacle or sensor. Moving physical objects usually need both.
Body types
| Body type | Moves how | Use for |
|---|---|---|
| Dynamic | Gravity, collisions, impulses, forces | Crates, balls, loose enemies, ragdolls |
| Fixed / Static | Never moves — infinite mass | Walls, floors, platforms |
| Kinematic | Moves only when you set position/velocity | Platforms on a track, player controllers that still collide |
Fixed walls are cheapest. Kinematic platforms are the middle ground — script-driven but still participate in contacts.
Collider shapes
| Shape | Good for | Tip |
|---|---|---|
| Box | Platforms, walls, rectangular props | Enlarge 1–2 px beyond the sprite for forgiving jumps |
| Circle | Balls, round pickups, radial trigger zones | Cheapest continuous collision |
| Capsule | Characters and enemies | Rounded bottom avoids snagging on tile seams |
| Polygon / custom | Irregular objects | Keep vertex count low — simple beats pixel-perfect |
A visually detailed sprite usually plays better with a simple collider than one that traces every pixel.
Click Edit Shape or press C in Arrange to tune the shape directly. Square handles resize shapes or move polygon vertices; polygon edge diamonds insert new vertices, and Alt-click removes them. The center crosshair moves the collider without moving the artwork. Hold Shift for centered box/capsule resizing. Escape cancels the current drag, and each completed gesture has one undo step. The Tools menu contains fitting, centering, and matching actions. See the physics authoring tools for the full controls.
Use Solid, Ice, and Rubber contact presets to try surface responses. Rigid Body 2D provides Dynamic, Upright, Floating, Platform, and Fixed motion presets. The selected body's viewport guide shows its launch direction, speed, spin, and constraints before you press Play.
Pouring and floating liquid
Choose Help → Fluid Playground… to import the experimental liquid example as a new scene. Press Play to try the moving gate, add water, and reset the tank. The crate floats while the denser weight sinks; the scene also works in an exported game.
To make your own, build a container with solid colliders and place a Fluid 2D object inside. Its rectangle sets the initial water fill, so the walls must be separate objects. Add a Fluid Emitter 2D to a nozzle and select that Fluid 2D object as its Fluid Domain. A Fluid Drain 2D removes water inside its rectangle. Several emitters can feed the same domain.
The hierarchy create menu includes Water Fill, Empty Fluid Domain, Emitter, Drain and Probe presets. A Fluid Probe detects entering and leaving water and exposes local flow for gameplay.
Start with the example's small particle count. Smaller Particle Spacing and higher Maximum Iterations cost more time. Show Particles and Show Velocity help inspect the simulation. This experimental version supports solid rigid colliders; soft-body fluid coupling and networked liquid are not available. See Experimental liquid for controls, actions, and limits.
Sensors and triggers
A sensor detects overlap without physically blocking movement. Flip Sensor on the Collider 2D.
Use sensors for:
- Collectible pickup radius
- Door or portal trigger
- Enemy sight range
- Damage zones
- UI-like world interaction areas
Sensor events are handled in Object Flow or Scene Flow — pick the scope that owns the logic (one coin vs the whole level).
Collision events in Flow
Physics events drive graphs. Attach the needed Collider 2D and Rigid Body 2D before expecting events to fire.
| Pattern | Flow event | Example |
|---|---|---|
| Collision starts | On Collision Start | Player touches hazard → health.damage |
| Collision ends | On Collision End | Player leaves ladder / water → stop climb / splash sound |
| Sensor overlap | Collision Start / Collision End with Sensor enabled | Pickup coin, open prompt, trigger dialog |
| Hit response | Condition + action | Play sound, spawn VFX, destroy projectile |
For sticky arrows / bolts, add Projectile on top of a dynamic Rigid Body 2D (enable Continuous Collision) and Collider 2D — it aligns to velocity and can weld into what it hits. See Built-in reference — Projectile.
Physics tuning
| Field | What it changes | Start here |
|---|---|---|
| Gravity Scale | How strongly global gravity pulls this body | 1.0 default, 0 for floaters |
| Friction | How much surfaces slow sliding | 0.7 default, 0 ice, 1.0 sandpaper |
| Restitution | Bounce — 0 dead stop, 1 perfect bounce | 0.0 for characters, 0.6 for balls |
| Density / Additional Mass | How strongly forces/impulses move it | Density is mass-per-area on the Collider; Additional Mass on the Rigid Body adds extra on top — small tweaks there are cheapest |
Change one field at a time and re-test in Play mode.
Four tuning recipes
| Add to crate | Setting |
|---|---|
| Rigid Body 2D | Body Type Dynamic, Gravity Scale 1.0 |
| Collider 2D | Shape Box, Sensor off, Friction 0.7 (default) |
| (optional) | Additional Mass 2 so the player can still shove it |
Push it with a Top-Down controller or a Projectile impulse. If it slides forever, raise friction to 0.8. If the player can't push it, lower mass or raise controller push force.
| Add to coin | Setting |
|---|---|
| Collider 2D | Shape Circle, Sensor on, Radius = sprite + 6 px |
| No rigid body | Static trigger — no gravity needed |
Object Flow on the coin:
On Sensor Enter (other is player) → Add Counter score +1 → Play Audio "coin.wav" → Spawn VFX sparkle → DestroyIf it never fires, the player needs its own Collider 2D (even when moved by a controller, the body still needs a shape).
| Add to platform | Setting |
|---|---|
| Rigid Body 2D | Body Type Kinematic |
| Collider 2D | Shape Box, Sensor off |
Drive it from Flow or a Behavior Component: use a Kinematic body and move it with a Behavior Component's onTick: { drift: { x: "80", y: "0" } } (or a Flow loop that sets velocity/position each frame); reverse direction at endpoints. Characters with Dynamic bodies will ride it. Set the platform Collision Groups so enemies fall through if you want. A true one-way (jump-through) platform is a separate setup — this recipe is a moving solid platform.
| Add to arrow | Setting |
|---|---|
| Rigid Body 2D | Body Type Dynamic, Continuous Collision on |
| Collider 2D | Shape Box thin, Sensor off |
| Projectile | Arm Delay 0.08, Ignore Owner on |
Flow on the arrow: On Spawn → Apply Impulse forward. On hit the Projectile welds into the target (creating a Joint) and stops simulating. If it never welds: CCD was off (fast arrow tunnels), Arm Delay not elapsed, or Ignore Owner filtered the target.
Connect objects with joints
Build mechanisms from objects with Rigid Body 2D, Collider 2D, and Joint 2D components. Group the objects under a parent and save them as a Prefab to reuse the mechanism with fresh joint references.
Select two objects in the hierarchy or on stage, then open Connect objects… in the inspector. Choose Hinge, Limited hinge, Fixed, or Distance. The last selected object owns the new joint. Missing dynamic bodies are added in the same undo step. Add colliders to objects that should hit the world. If the active object already has a joint, make the unconnected object active first.
Hinges place the anchor at the active object's center. Limited hinges and fixed connections meet between the two centers; distance connections retain separate anchors. Limited hinges start with up to 60° of movement either side of the current relative angle, clipped at −180°/180°. At an exact half turn, limits start unrestricted to keep the initial pose stable. Tune these in Joint 2D.
The optional Joint tools disclosure contains presets and Align target anchor. Presets preserve targets, anchors, and break settings. Align matches the anchors without moving either object. Drag aligned hinge or fixed anchors together on stage; Shift-drag moves one independently and Escape cancels. Distance endpoints move independently. Break Separation measures pixels, not impact force. Use Break Joint for gameplay-driven detachment.
For a powered hinge, set Motor Speed and Motor Force on Joint 2D, or use Set Motor. Zero speed with positive force brakes; zero force releases it. For input-driven control, use Add Component → Joint Motor Controller and configure its inputs. Speed is relative to the target body; reverse its sign to change direction. To control it from game logic, choose Controlled By → Flow / Script, then use Set Drive or GetComponent<JointMotorController>().SetDrive(value). Values range from -1 through 0 to 1, including partial analog input.
Character movement — controllers over manual forces
For platformers or top-down games, prefer a controller component — it packages gravity, ground detection, acceleration, and collision so you don't hand-build them in graphs.
| Controller | Use when |
|---|---|
| Side-Scroller Controller | 2.5D run along X with jump |
| Platformer Controller | Grounded jumps, coyote time, ledges |
| Top-Down Controller | 8-way walk, no gravity |
Use graphs to set intent (direction, jump, attack, interact). Let the controller handle movement math. For touch, see Input and controls — virtualInput patterns.
Debugging collisions
If collisions are not working, run this in order:
- Both objects have appropriate colliders (at least one not a sensor if you want a block).
- Moving physical objects have a Rigid Body 2D.
- Sensor vs blocker matches the event you listen for.
- Collider size and offset match the sprite — select the object and toggle C (collider shape editor).
- Collision groups aren't filtering each other out.
- Flow overlay in Play mode — does
On Collision Startflash? If not, the physics pair never contacted.
For sticky-projectile bugs specifically: CCD on, Arm Delay elapsed, Ignore Owner correct — see Troubleshooting.
Related docs
- RigidBody2D, Collider2D — OpalScript classes
- Physics reference
- Built-in reference — Physics (Rigid Body 2D, Collider 2D, Joint 2D, Ragdoll, Projectile)
- Components and properties
- Input and controls
- Visual scripting
- Testing and debugging