the symptom, in your words

"The stock PlayerController has no public speed property — you can't scale move speed from outside"

✓ verified on 26.07.15a
lane Tooling & environmentposted

▸ SYMPTOM

You want another component to scale the player's movement speed — a slow effect, a root, a haste buff — and you go looking for controller.Speed or controller.WalkSpeed. They aren't reachable. There is no clean, order-independent way to turn player move speed down from an external component. Verified on engine 26.07.15a.

▸ CAUSE

The stock Sandbox.PlayerController exposes no public speed property. WalkSpeed, RunSpeed, DuckedSpeed, and Speed are private serialized [Property] fields.

You can confirm this from the installed doc XML (bin/managed/Sandbox.Engine.xml): the public members it lists touching movement are WishVelocity, Velocity, AccelerationTime, RunByDefault, AltMoveButton, IsDucking — the four speed fields never appear (only DebugFootsteps does). Meanwhile the Player Controller prefab template does serialize WalkSpeed / RunSpeed / DuckedSpeed / Speed, which proves they exist as private [Property] fields rather than public members.

WishVelocity is public, but the controller recomputes it from input every fixed-update, so setting it from another component is order-dependent and gets overwritten — a hack, not a seam. And reflection is off the table (whitelist), so you can't reach the privates that way either.

▸ FIX

Give yourself a real seam instead of fighting the stock controller:

  • Swap in a custom controller that exposes a public speed knob, or
  • Expose one via a subclass / authored prefab that surfaces the value you need, or
  • Leave player move-speed status as a documented stub if it's not worth the controller swap yet.

Note that NPC brains are unaffected — an NPC that owns its own WalkSpeed / RunSpeed fields reads its own values, so status effects on NPC speed work fine. The gap is specifically the stock player controller.

Whatever you do, don't hunt for controller.Speed / controller.WalkSpeed — they aren't reachable from outside.

▸ WHY IT WORKS

The stock controller was authored with those fields private and serialized, so its speed is a designer-tuned constant, not a runtime API. The only way to get an order-independent runtime knob is to own the field yourself — in a custom controller or a subclass — where you control both the storage and the fixed-update that reads it. That removes the recompute-overwrite race that makes poking WishVelocity unreliable.

Verified on engine 26.07.15a: seen in a real project.
s&box moves fast; an undated fix is a liability. Spot a stale detail?
changelog
  • Published.

Want to know when new guides or fixes drop? Join the community to help build this out. Report gotchas, flag outdated fixes, or just lurk.

Join the Discord