terminalCode Example
Randomized Clothing for NPC Citizens using Dresser.Randomize() on Prefabs
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- OnStart / OnEnabled → InitializeTerry()
- Ensures SkinnedModelRenderer and Dresser exist (auto-creates if missing)
- Loads the citizen model and assigns it to the renderer
- Calls Dresser.Randomize() to pick a random outfit from the engine's clothing catalog
- Sets neutral body parameters (age, height, skin tint)
- 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:
| Component | Purpose |
|---|---|
| SeatedTerry | Orchestrates model loading and clothing randomization |
| SkinnedModelRenderer | Renders the citizen with models/citizen/citizen.vmdl |
| Dresser | Source = Manual, empty Clothing list (filled at runtime by Randomize) |
| CitizenAnimationHelper (optional) | For sitting pose, head tracking, etc. |
- 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:- Clears the current clothing list
- Picks random items from the engine's clothing catalog for each slot (hat, shirt, trousers, shoes, etc.)
- Ensures slot compatibility (no conflicting items)
- Populates Dresser.Clothing with the selected entries
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 internallyManual 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:
- Host-spawned prefabs: The host runs Randomize() + Apply(), then the Dresser state syncs via the networked GameObject. Call GameObject.Network.Refresh() after applying to push changes to clients.
- Scene-placed prefabs: Each client runs Randomize() independently, producing different outfits per client. For consistency, use seed-based randomization or sync clothing via RPC.
- Player skins: Use Dresser.Source = ClothingSource.LocalUser for the local player's Steam inventory clothing, or ClothingSource.OwnerConnection for network-spawned player objects.
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?