Skip to content
Docs
Games

World & physics

Create discs, walls, goals and lines in the physical world of a running match.

During a match, your game has a physical world: the ball, the players' discs, the walls, the goals. You create these objects and set their properties; disko moves them, makes them bounce off each other, and tells you when something interesting happens.

You never write the physics yourself. There's no "move the ball by its velocity" or "check if two circles overlap" in a disko game. That work is done by disko, the same way on every player's screen.

When the world exists

The world belongs to a match. It's created when a match starts and thrown away when it stops, so you build your field in the start handler. The world global is only usable between start and stop; using an object after its match ended throws a StaleHandleError.

What happens in one tick

A running match advances in ticks, 60 per second. In each tick, disko applies the players' inputs, runs your code, moves everything, and then tells you what happened:

Each tick: players' inputs are applied; your tick handler runs; disko moves discs and resolves collisions; then your crossing, sensor and post-tick handlers run with what happened. Then the next tick begins.
  • tick runs before the physics step: a good place to change velocities or rules for this tick.

  • post-tick runs after it, along with crossing and sensor events: a good place to react to where things ended up, such as a goal.

Both handlers receive the tick number and the events of that turn, in the order they were delivered.

The objects

Create withObjectWhat it's for
world.createDiscDiscballs and player discs
world.createPlanePlaneinfinite straight walls
world.createVertexVertexfixed points, and the ends of segments
world.createSegmentSegmentstraight or curved walls between two vertices
world.createJointJointkeeping two discs at a distance
world.crossingCrossinglines that report discs crossing them
world.visualVisuallabels and rings that don't collide

This builds a small field: four walls, a ball, a goal post and a goal line.

src/field.ts

Every object has setters for its properties, such as disc.setVelocity, disc.setMass or segment.setCurve. state() returns where an object is and how it's moving at the current tick; a disc's DiscState has its position, velocity, radius and collision properties. world.destroy(object) removes an object.

How a disc looks

disc.setAppearance({ fill, stroke, strokeWidth }) sets a disc's colors: #rrggbb or #rrggbbaa colors and an outline from 0 to 64 CSS pixels wide (0 hides it). All three are required. A new disc is #f5f7fb with a black 2-pixel outline. For an image instead of a color, see Assets.

Who collides with what

Not everything should bump into everything. In football, players shouldn't walk into the goal net, but the ball should bounce off it.

That's what collision groups are for. Each object belongs to groups (addGroup, removeGroup) and lists the groups it collides with (setAcceptedGroups). Two objects only collide if each accepts the other.

Some objects shouldn't collide at all, only notice contact:

  • A sensor reports sensor-enter and sensor-leave instead of bouncing, for example to detect a player standing in an area.

  • A crossing is a line that reports discs passing through it. It never blocks anything, which makes it the natural goal line. It has groups too, so it can ignore players and only watch the ball.

Every collidable object also has a restitution: how bouncy its collisions are, from 0 (dead stop) to 1 (no energy lost).

Reacting to crossings

crossing fires when a disc crosses a crossing line. It tells you which disc (object), which way (forward or backward), where, and the fraction of the tick's movement at which the crossing happened. This resets the ball when it crosses forward:

src/field.ts

Ending a match

stop reports the final tick and why the match ended: finished after your game calls game.finish(), or stopped when the room stopped it.

Visuals

Visuals are drawings with no physical presence, such as a name above a player or a ring under the disc you control. world.visual({ anchor, appearance, audience }) attaches a text label or a ring to a disc or a position, and audience chooses which players see it. visual.update(patch) changes it and visual.destroy() removes it.

See the world reference for every option.