"Rigidbody component API"
▸ SYMPTOM
- You need a pushable crate / physics prop and don't know which members the
Rigidbodycomponent actually exposes. ApplyForcedoes nothing: wrong force scale or missing collider setup.- A prop flies off or barely budges when the player walks into it.
▸ CAUSE
The Rigidbody component's public surface isn't fully enumerated in the wiki. Some members (like MassOverride) are set-only, others (like PhysicsBody) are nullable, and force units are engine-inches-based, all easy to get wrong without a verified reference.
▸ FIX
Verified Rigidbody members:
// Core properties
rb.Gravity // bool - enable/disable gravity
rb.MassOverride // float (kg) - SET-ONLY override
rb.Mass // float (kg) - READ
rb.Velocity // Vector3 - read/write
rb.MotionEnabled // bool
// Physics body - typed PhysicsBody? but populated on the creation tick (26.08.05)
rb.PhysicsBody // → .MassCenter, .AutoSleep (SET-ONLY)
rb.Sleeping // setter per-body, GETTER reads scene-wide (see below)
rb.SleepThreshold // floatForce API:
rb.ApplyForce(force); // whole-step force
rb.ApplyForceAt(position, force); // force at world point
rb.ApplyImpulseAt(position, impulse);Dynamic prop recipe:
A pushable physics prop = ModelRenderer + non-static BoxCollider + Rigidbody:
var go = new GameObject();
var renderer = go.Components.Create<ModelRenderer>();
renderer.Model = Model.Load("models/dev/box.vmdl");
var col = go.Components.Create<BoxCollider>();
col.Static = false; // MUST be non-static
var rb = go.Components.Create<Rigidbody>();
rb.Gravity = true;
rb.MassOverride = 10f; // kgPush-on-contact spring (drag toward hold point):
var offset = holdPoint - body.MassCenter;
var force = offset * springK * body.Mass;
// Cap magnitude so heavy props resist a single puller
force = force.ClampLength(maxForce);
body.ApplyForce(force);Force units are engine-based (kg·units/s²). To convert from SI Newtons, multiply by the meters-to-units constant (39.37). Light props (MassOverride small) fly; heavy ones barely move. That's F = ma working correctly.
AutoSleep is on PhysicsBody (set-only), and the creation-tick null trap is gone
AutoSleep is not a member on Rigidbody itself. The real path is Rigidbody.PhysicsBody.AutoSleep. Two things to know as of 26.08.05:
PhysicsBodyis now populated on the creation tick. On builds ≤ 26.07.22 it readnullon the same tickComponents.Create<Rigidbody>()returned, so you had to defer theAutoSleepwrite by a tick. That trap is fixed on 26.08.05:PhysicsBodyreads non-null immediately (confirmed in edit mode and in a live editor play session, across two fresh scenes; true even for a bareRigidbodywith no collider). You can set it right away.AutoSleepis set-only. ReadingPhysicsBody.AutoSleepno longer compiles (CS0154). You can only assign it.
var rb = go.Components.Create<Rigidbody>();
// 26.08.05: rb.PhysicsBody is already non-null here.
rb.PhysicsBody.AutoSleep = false; // OK - set-only, don't try to read it backEvidence is an in-editor play session (
EditorScene.Play), not a shipped/networked game session. Behavior in a fully networked build may differ.
Perf corollary: s&box rigidbodies auto-sleep by default. A worst-case count of free/dynamic bodies must be split into sleeping vs. awake. A scene full of settled dynamic props is far cheaper than a naive "N rigidbodies" count assumes, and a perf budget written against the naive count overstates the cost.
Rigidbody.Sleeping: the setter is per-body, the getter reads scene-wide
Rigidbody.Sleeping's setter lands per-body: waking one crate wakes that one crate. But its getter reads scene-wide, not per-instance. Waking a single crate out of 12 scattered, non-touching crates made a census that read Rigidbody.Sleeping on every crate report all 12 as awake for 1–2 seconds, then all 12 asleep together, reproduced twice from clean boots.
A sleep census that needs genuine per-instance truth cannot call the component getter naively. Read a different per-body signal (velocity, or PhysicsBody state directly) if you must distinguish individual sleep state rather than a scene-wide aggregate.
▸ WHY IT WORKS
The Rigidbody wraps a PhysicsBody managed by the engine's physics simulation. Setting Static = false on the collider is what makes the body dynamic (the default is static/kinematic). MassOverride as a set-only property means you can't read back what you set. Use .Mass for reads. Force applied per-step accumulates naturally with the fixed-tick simulation.
- Engine audit (26.08.05): the null-on-creation-tick trap is fixed. Rigidbody.PhysicsBody now reads non-null on the same tick Components.Create<Rigidbody>() returns (confirmed edit mode + editor play session), so AutoSleep can be set immediately. PhysicsBody.AutoSleep is now set-only (reading it fails to compile, CS0154). Verified surface also carries .SleepThreshold.
- Added (26.07.22): Rigidbody.Sleeping's setter is per-body but its getter reads scene-wide. A per-instance sleep census reads every body as awake/asleep together for 1-2s. Use velocity or PhysicsBody state directly for genuine per-body sleep state.
- Added (26.07.22): AutoSleep is not a member on Rigidbody itself. It lives on Rigidbody.PhysicsBody, which reads null on the same tick Components.Create<Rigidbody>() returns. Set it one tick later and read it back. Perf corollary: bodies auto-sleep by default, so split a worst-case body count into sleeping vs awake.