terminalCode Example

Randomized Clothing for NPC Citizens using Dresser.Randomize() on Prefabs

calendar_today May 18, 2026 schedule ~3 min read person patrickjr verified 50

Overview

This demonstrates how to create NPC citizen prefabs with randomized clothing using the built-in Dresser component. Each time the prefab spawns or is enabled, it generates a unique random outfit — no manual clothing configuration required.

This pattern is ideal for bar patrons, crowd NPCs, seated characters, or any ambient citizen that needs visual variety without per-instance setup.

Architecture

CODE
Prefab (sitting_terry.prefab)
├── SeatedTerry (Component) — orchestrates initialization
├── SkinnedModelRenderer — renders the citizen model
├── Dresser — handles clothing selection and application
└── CitizenAnimationHelper (optional) — poses the character
Flow:
  1. OnStart / OnEnabledInitializeTerry()
  2. Ensures SkinnedModelRenderer and Dresser exist (auto-creates if missing)
  3. Loads the citizen model and assigns it to the renderer
  4. Calls Dresser.Randomize() to pick a random outfit from the engine's clothing catalog
  5. Sets neutral body parameters (age, height, skin tint)
  6. Calls Dresser.Apply() to dress the model

Implementation

CSHARP
using Sandbox;

[Title( "Seated Terry" )]
[Category( "Bar Flips" )]
public sealed class SeatedTerry : Component
{
    [Property]
    public string ModelPath { get; set; } = "models/citizen/citizen.vmdl";

    [Property]
    public SkinnedModelRenderer Renderer { get; set; }

    [Property]
    public Dresser Dresser { get; set; }

    private Model _loadedModel;

    private void InitializeTerry()
    {
        // Auto-wire references if not set in inspector
        if ( Renderer == null )
            Renderer = Components.GetOrCreate<SkinnedModelRenderer>();

        if ( Dresser == null )
            Dresser = Components.GetOrCreate<Dresser>();

        // Load and assign the citizen model
        if ( _loadedModel == null && !string.IsNullOrEmpty( ModelPath ) )
            _loadedModel = Model.Load( ModelPath );

        if ( _loadedModel != null )
            Renderer.Model = _loadedModel;

        // Configure dresser to target our renderer
        if ( Dresser != null )
        {
            Dresser.BodyTarget = Renderer;
            Dresser.ApplyHeightScale = false;
        }

        // Generate a random outfit from the engine's clothing catalog
        Dresser?.Randomize();

        // Apply neutral body parameters and dress the model
        if ( Dresser != null )
        {
            Dresser.ManualAge = 0.5f;
            Dresser.ManualHeight = 0.5f;
            Dresser.ManualTint = 0.5f;
            Dresser.Apply();
        }
    }

    protected override void OnStart()
    {
        InitializeTerry();
    }

    protected override void OnEnabled()
    {
        InitializeTerry();
    }
}

Prefab Configuration

The prefab needs these components:

ComponentPurpose
SeatedTerryOrchestrates model loading and clothing randomization
SkinnedModelRendererRenders the citizen with models/citizen/citizen.vmdl
DresserSource = Manual, empty Clothing list (filled at runtime by Randomize)
CitizenAnimationHelper (optional)For sitting pose, head tracking, etc.
Key Dresser settings in the prefab:
  • Source: Manual (clothing is set programmatically, not from Steam inventory)
  • Clothing: Empty array (populated at runtime by Randomize())
  • RemoveUnownedItems: true
  • ApplyHeightScale: true in prefab, overridden to false at runtime

How Dresser.Randomize() Works

Dresser.Randomize() is a built-in s&box method that:
  1. Clears the current clothing list
  2. Picks random items from the engine's clothing catalog for each slot (hat, shirt, trousers, shoes, etc.)
  3. Ensures slot compatibility (no conflicting items)
  4. Populates Dresser.Clothing with the selected entries
Each call produces a different outfit. The randomization is non-deterministic — every spawn creates a unique look.

Extending the System

Adding Body Variation

Randomize the body parameters for more visual diversity:

CSHARP
Dresser.ManualAge = Random.Shared.Float( 0.1f, 0.9f );
Dresser.ManualHeight = Random.Shared.Float( 0.3f, 0.8f );
Dresser.ManualTint = Random.Shared.Float( 0.0f, 1.0f );

Seed-Based Deterministic Randomization

For consistent appearance across sessions or network sync:

CSHARP
// Use a stable seed (e.g., based on position or entity ID)
var rng = new Random( GameObject.Id.GetHashCode() );
Dresser.ManualAge = rng.NextSingle();
Dresser.ManualHeight = rng.NextSingle() * 0.5f + 0.3f;
Dresser.ManualTint = rng.NextSingle();
Dresser.Randomize(); // Note: Randomize() uses its own RNG internally

Manual Clothing Pools (Curated Outfits)

If you want controlled randomization from a curated set instead of the full catalog:

CSHARP
[Property]
public List<ClothingContainer.ClothingEntry> OutfitPool { get; set; } = new();

private void ApplyCuratedOutfit()
{
    if ( OutfitPool.Count == 0 ) return;
    
    Dresser.Clothing.Clear();
    // Pick a random subset or a full predefined outfit
    var outfit = OutfitPool[Random.Shared.Int( 0, OutfitPool.Count - 1 )];
    Dresser.Clothing.Add( outfit );
    Dresser.Apply();
}

Multiplayer Considerations

For networked games, clothing applied via Dresser.Randomize() on a networked prefab works as follows:

Optimization Notes

  • Dresser.Apply() is async — it loads clothing models. Avoid calling it every frame.
  • The _loadedModel cache prevents redundant Model.Load() calls on re-enable.
  • GetOrCreate pattern ensures the component works even if inspector references are cleared.
  • For many NPCs, stagger initialization across frames to avoid frame spikes from simultaneous clothing loads.
Was this helpful?