Toggleable Components
A common customization feature for equipment is to have parts of the model that can be toggled on and off, or variations selected between: different pilots, smoke stacks, an optional tool box. We recommend the following setup to build such a feature set for your cars and locomotives:
- Base car body model
- Multiple
DetailModelcomponents. Each references a prefab model: PilotA, PilotB, SmokeStackA, SmokeStackB, ToolBox - Relay scripts to toggle
DetailModelvisibility - Relay scripts to provide customization UI (optional)
Railroader uses this approach on multiple locomotives, namely the G-16 and L-27, among others. You can inspect their Definitions.json and Main.rl files to learn how they work, but we’ll walk through the basics below.
How it works
Section titled “How it works”Every component has an enabled flag.
You could write a script to toggle enabled, but Railroader ships with a ready-made script designed to be used with the Script component, rr/EnableComponentsPropertyEquals.
rr/EnableComponentsPropertyEquals watches one property key and enables the named components when it equals a given value, disabling them otherwise. It applies on load, so a car restored from a save shows the right variant immediately.
Because the state lives in a car property rather than in the definition, it persists across save/load and syncs in multiplayer automatically. See Properties.
By having the UI write to this property we can control the component’s visibility (described below).
Wiring it up
Section titled “Wiring it up”Add a Script component to the car definition alongside the components it should control, and set:
- Kind —
Script - Name — Ctrl Switch Pilot A
- ScriptAsset —
rr/EnableComponentsPropertyEquals - Parameters — a free-form key/value tree, which the editor builds up as nested entries:
components— 1 element array containing:switching-pilot-akey—customize.pilot.combovalue—SwitchPilotA
In plain terms: whenever this car’s customize.pilot.combo property is set to SwitchPilotA, the switching-pilot-a component is enabled. Any other value disables it. The property name and the value are both yours to choose; see below for the full parameter reference.
Author the variant components themselves with Enabled unchecked, so a car with no property set doesn’t show all of them stacked on top of each other.
Mutually exclusive variants
Section titled “Mutually exclusive variants”For a set of variants where exactly one applies, add one Script component per variant, all using rr/EnableComponentsPropertyEquals, all watching the same key, each matching a different value:
| Component | components | key | value |
|---|---|---|---|
| Ctrl Switch Pilot A | ["switching-pilot-a"] | customize.pilot.combo | SwitchPilotA |
| Ctrl Wood Road Pilot | ["wood-road-pilot"] | customize.pilot.combo | WoodRoadPilot |
Each script enables its own components when the property matches and disables them otherwise, so setting customize.pilot.combo to WoodRoadPilot swaps the pair in one write.
Seed the starting variant in the car definition’s defaultProperties rather than with defaultEnabled, with one entry keyed by property:
| Property key | Value |
|---|---|
customize.pilot.combo | SwitchPilotA |
This keeps the default in one obvious place. defaultEnabled set on more than one variant is load-order dependent (whichever script loads first wins), so avoid it once you have more than two options.
Letting players choose
Section titled “Letting players choose”The property is just car state; nothing changes it until you add a control. Register a UI extension from onWorldLoad in your pack’s Main.rl to add one to the car inspector’s Customize section:
from railroader import ui
func onWorldLoad(ctx) { ui.extend("carInspector.equipment.customize") .id("mymod.pilot") .matching({"identifier": "mymod/my-locomotive"}) .with(buildUI)}
func buildUI(builder, car) { var values = ["SwitchPilotA", "WoodRoadPilot"] builder.rebuildOnPropertyChange(car.properties, "customize.pilot.combo")
var current = values.indexOf(car.properties["customize.pilot.combo"] ?? values[0]) if current < 0 { current = 0 }
builder.fieldDropdown("Pilot", ["Switching Pilot A", "Wood Road Pilot"], current, (selected) => { car.properties["customize.pilot.combo"] = values[selected] })}For a simple on/off variant, fieldToggle writing true/false works the same way.
The UI isn’t required, though: it’s an ordinary property, so a script can set it from an ops event, a purchase, or a scenario step. The components follow whatever writes the key.
rr/EnableComponentsPropertyEquals API
Section titled “rr/EnableComponentsPropertyEquals API”The script takes four parameters:
| Parameter | Type | Description |
|---|---|---|
components | array | Component ids to control: the keys in the definition’s components object, not the display name. An id that doesn’t resolve is skipped (and logged). |
key | string | Property key to observe. Any string works; the key is opaque to the game. Railroader’s own cars prefix player-facing customization state with customize.; prefix keys of your own with your mod’s name so they can’t collide. |
value | any | The value that means these components should be enabled. Anything else disables them. |
defaultEnabled | bool | If true and the property has never been set, initialize it to value, making this the variant a fresh car starts with. |
One component can name several ids in components, so a single variant can switch on a group of parts at once (a pilot plus its footboards, say). The script re-reads the property whenever it changes and on model load, so nothing needs to be re-applied by hand after a save is restored.
The script itself is only about thirty lines, and worth a read if you’re planning to write your own. It ships with the game at: Railroader_Data/StreamingAssets/AssetPacks/shared/Scripts/EnableComponentsPropertyEquals.rl
When to write your own script
Section titled “When to write your own script”rr/EnableComponentsPropertyEquals only ever sets enabled. For anything more (swapping a material, driving an animation, reacting to two properties at once), write a Script component and observe the property yourself; see Properties for the pattern.
See also
Section titled “See also”- Shared Scripts — the other ready-made scripts that ship with the game
- Script — the component kind that attaches
rr/EnableComponentsPropertyEquals - Properties — persistence, multiplayer, and observing changes
- Extending the UI — the Customize-section extension point
© 2026 Giraffe Lab LLC. All rights reserved.