the symptom, in your words

"FirstOrDefault() on SkinnedModelRenderer can grab clothing, not the body"

✓ verified on 26.07.22
lane Writing gameplayposted

▸ SYMPTOM

  • You grab a citizen's renderer with GetAllComponents<SkinnedModelRenderer>().FirstOrDefault() and a sequence/parameter probe reports every locomotion and hold clip absent — reads like "this model has no sequences."
  • The renderer reference is non-null and valid, so nothing looks wrong — but animation code driving it does nothing.

▸ CAUSE

Citizen clothing attaches as its own bone-merged SkinnedModelRenderer (a hat, a jacket) — a sibling or child of the body renderer. So a scene with a dressed citizen carries multiple valid SkinnedModelRenderer instances, and FirstOrDefault() returns whichever one happens to enumerate first — not necessarily the body.

When that first renderer is a hat, a sequence/param probe run against it reports every locomotion/hold clip missing, because a hat mesh genuinely has no locomotion sequences. The real story ("this is a hat, not the body") reads as "this model has no sequences."

This is a distinct failure mode from a child-renderer lookup returning null: here the query returns a wrong, non-null renderer because of enumeration order, not an empty result.

▸ FIX

Never grab the first SkinnedModelRenderer blind. Select the body renderer explicitly:

snippet
// WRONG — may return a bone-merged clothing renderer (a hat):
var body = go.GetAllComponents<SkinnedModelRenderer>().FirstOrDefault();

// RIGHT — pick the renderer whose Model is the citizen body:
var body = go.GetAllComponents<SkinnedModelRenderer>()
    .FirstOrDefault( r => r.Model?.Name?.Contains( "citizen.vmdl" ) == true );

// Or, best: hold the body renderer reference the root assigns, and use that
// directly instead of re-querying the component graph.

If you own the character setup, keep an explicit reference to the body SkinnedModelRenderer at spawn time and pass it around — don't re-discover it by enumeration, which is order-dependent and breaks the moment clothing is added.

▸ WHY IT WORKS

GetAllComponents<T>() walks the object's component graph in enumeration order, which is not guaranteed to put the body first — clothing renderers are first-class SkinnedModelRenderer components too. Filtering on the body model (or holding an explicit reference) removes the dependency on enumeration order, so the code always drives the renderer that actually carries the locomotion sequences.

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