dot-physics does not simulate anything — Godot does that. It decides what
Godot is told, and puts those decisions somewhere a person can read them: a mask
is mask_of([&"world", &"prop"]) rather than 1 << 3 | 1 << 7.
Requires dot-core.
Usage
var physics := DotPhysicsWorld.new()
physics.profile = DotPhysicsProfile.arcade_shooter() # 128 Hz, gravity 20
physics.layout = DotPhysicsLayout.shooter_3d() # sixteen named layers
physics.surfaces = DotPhysicsSurfaceSet.standard() # eleven materials
add_child(physics)
var res := physics.setup()
if not res.ok:
push_error(res.error.message)
physics.classify(player_body, &"player") # layer and mask, by name
var hit := physics.query().shot_3d(world, muzzle, muzzle + dir * 100.0)
if not hit.is_empty():
var surface: DotPhysicsSurface = hit["surface"] # what it is made of
audio.play(surface.impact_sound)
`DotPhysicsWorld` restores every setting it overwrote
A dedicated server that switches from a sandbox at 64 Hz to a timer server at 128 must not leave the second game running on the first one’s gravity, and a settings write with no matching restore is how that happens.
Layouts
shooter_3d |
Sixteen layers: player and NPC clip volumes, debris that touches only the world, a query-only hitbox layer for lag-compensated hit registration. |
sandbox_3d |
The shooter’s, plus held_prop and frozen_prop. A held prop does not push the player holding it. |
platformer_2d |
Side-on. One-way platforms are their own layer, so a projectile is not stopped by a surface the player walks through. |
top_down_2d |
No gravity, many bodies, players who pass through each other. |
custom |
Build your own. |
A layout declares each collision relation once — “debris collides with world” —
and build() closes it symmetrically, because a table whose halves are written
by hand drifts. A layer name that does not exist is refused at build time: a
typo contributes no bit, so the pair silently never collides.
Profiles
Named for how they feel, since that is the only thing about them anybody can
reason about. Each is a DotConfig, so --physics-tick-rate 128 retunes a
dedicated server with no rebuild.
arcade_shooter |
128 Hz, gravity 20 m/s², interpolation off, characters not pushed by bodies. |
grounded |
64 Hz, gravity 12, sixteen solver iterations, interpolation on. |
sandbox |
Twenty-four iterations so a stack of crates holds; an aggressive sleep threshold so two hundred settled props cost nothing. |
floaty_platformer |
Long jumps, generous slopes. |
top_down |
No gravity at all. |
Surfaces
DotPhysicsSurfaceSet.standard() is eleven materials carrying friction,
restitution, density, sheet thickness, movement multipliers and
dot-audio / dot-fx
ids.
The lookup is the hard half. A ray hits a collider and nothing in Godot’s result
says it hit concrete, so for_collider() tries node metadata, then groups, then
the node’s name — the last an honest guess, because a level built by a mapper is
the case where nothing else is available.
This is not dot-player-controller's surface table
That one holds multipliers on player movement; this one is what the solver is told about a rigid body. A game wants both, they are read by different code at different times, and merging them would break one.
