the symptom, in your words

"Trace-based kinematic controllers don't fire trigger volumes"

✓ verified on 26.07.08e
lane Writing gameplayposted

▸ SYMPTOM

Pickup items or zone triggers use OnTriggerEnter / ITriggerListener to detect when the player walks in. The trigger volume is correctly sized and positioned, but the callback never fires. The player walks straight through with no response.

▸ CAUSE

A trace-based kinematic character controller moves by casting traces (sweeps) each tick and stepping the transform directly — it has no Collider component on its GameObject. The physics engine only generates trigger overlap events between two shapes registered in the broadphase. A controller that exists purely as traces is invisible to the trigger system: there is no shape to overlap against, so ITriggerListener.OnTriggerEnter never fires.

This is a fundamental mismatch. Triggers require two physics shapes. A trace-based controller has zero.

▸ FIX

Replace trigger-based detection with distance polling on each fixed tick:

snippet
protected override void OnFixedUpdate()
{
    var player = PlayerCharacter.Local;
    if ( player == null ) return;

    float distance = player.WorldPosition.Distance( WorldPosition );
    if ( distance < PickupRadius )
    {
        Consume( player );
    }
}

This runs on the pickup/zone component itself. Check the distance to the local character singleton each tick and consume on proximity. No collider needed on either side.

For zones with irregular shapes, use the zone's bounding box or run a single trace from the player's position to confirm line-of-sight, but for most pickups and circular trigger areas, a distance check is simpler and cheaper than maintaining a phantom collider.

▸ WHY IT WORKS

Distance polling sidesteps the trigger system entirely. It doesn't depend on either object having a registered physics shape — just two world positions and a radius. Since the character controller already updates its WorldPosition every fixed tick via traces, the distance is always current. The cost is one Distance() call per pickup per tick, which is negligible compared to the traces the controller is already running.

Verified on engine 26.07.08e: 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