This is an old revision of the document!
Table of Contents
Creating a Simple Vehicle.lua
This page explains how to create a simple `vehicle.lua` file, primarily intended for AI-controlled trains in SubwaySim 2.
A simple `vehicle.lua` usually defines:
- Vehicle metadata (name, author, IDs)
- Basic physical parameters (mass, length, vmax)
- Blueprint references
- Coupling definitions
- Braking systems
- Basic lights and audio
- Train compositions for AI
For a detailed explanation of a fully featured and player-drivable vehicle, see:
Example Vehicle.lua (AI Vehicle)
The following example is based on the GI1E (Gisela) AI vehicle used in the SubwaySim base game.
Because the original file is not publicly downloadable, the full structure is shown below and explained step by step in separate sections.
- vehicle.lua
---@type RailVehicle_DataTable local GI1E_a = { contentType = "railVehicle", contentName = "Berlin_GI1E_a", title = "GI1E", author = "$GameDeveloper", emptyMass = 37.0, vmax = 70, length = 12.64, frontToFirstBogie = 2.53, rearToLastBogie = 2.53, maxEnginePower = 480, maxEngineForce = 50, blueprintFilename = "/SubwaySim_Berlin/Vehicles/GI1E/BP_GI1E_A.BP_GI1E_A", couplingFront = { rotationOrigin = "Coupling_F", couplingOffset = 0.985, automaticCoupling = true, skeletalMesh = "Exterior", couplingBoneName = "Coupling_Rotation1", }, couplingRear = { rotationOrigin = "Coupling_B", couplingOffset = 0.985, automaticCoupling = true, skeletalMesh = "Exterior", couplingBoneName = "Coupling_Rotation2", }, -- players shouldn't try to attach this vehicle cameras = { exteriorCameras = {}, }, brakingSystems = { pneumatic5bar = { maxBrakeForce = 60, }, electric = { maxBrakeForce = 50, maxBrakePower = 480, }, }, components = { LightManager, VehicleNumber, AudioManager, Berlin_PIS, }, audio = { audioBogieAI = { { audioComponent = "AiAudio", }, }, }, lights = { headLights = { { direction = 1, headlightElements = { { mesh = "Exterior", materialSlot = "GI1E_LightsExterior_mat", materialParams = { ["Emissive01"] = 100, }, lightComponents = { {lightComponent ="Headlight1", intensity = 50, temperature = 4500, attenuationRadius = 500, innerConeAngle = 0, outerConeAngle = 70}, {lightComponent ="Headlight2", intensity = 50, temperature = 4500, attenuationRadius = 500, innerConeAngle = 0, outerConeAngle = 70}, }, }, }, }, }, tailLights = { { mesh = "Exterior", materialSlot = "GI1E_LightsExterior_mat", direction = -1, materialParams = { ["Emissive02"] = 100, }, }, }, }, ---@type Berlin_PIS_DataTable PIS = { destinationDisplays = { { componentName = "ZZA", direction = 1, }, }, announcementNextStop = {}, announcementTerminus = { "/SubwaySim_Berlin/Vehicles/Shared/Audio/Announcements/General_Termination01.General_Termination01", }, announcementExitLeft = {}, announcementExitRight = { "/SubwaySim_Berlin/Vehicles/Shared/Audio/Announcements/General_ExitRight.General_ExitRight", }, announcementBell = { "/SubwaySim_Berlin/Vehicles/Shared/Audio/Gong/Berlin_Standardgong_MasterV1_kurz.Berlin_Standardgong_MasterV1_kurz", }, announcementTerminusBell = { "/SubwaySim_Berlin/Vehicles/Shared/Audio/Gong/Berlin_Infogong_MasterV2_kurz.Berlin_Infogong_MasterV2_kurz", }, announcementDelay = 1.2, defaultDestinationAnnouncement = "/SubwaySim_Berlin/Vehicles/Shared/Audio/Announcements/Destination_General.Destination_General", destinationAnnouncementByLine = { ["U1"] = "/SubwaySim_Berlin/Vehicles/Shared/Audio/Announcements/Destination_U1.Destination_U1", ["U2"] = "/SubwaySim_Berlin/Vehicles/Shared/Audio/Announcements/Destination_U2.Destination_U2", ["U3"] = "/SubwaySim_Berlin/Vehicles/Shared/Audio/Announcements/Destination_U3.Destination_U3", ["U4"] = "/SubwaySim_Berlin/Vehicles/Shared/Audio/Announcements/Destination_U4.Destination_U4", }, audioComponent = "PIS_Audio", autoTerminateAnnouncement = false, }, vehicleNumber = { labels = { { mesh = "Exterior", materialSlot = "DecalDigits_mat", }, }, poolName = "Berlin_GI1E", poolValueMin = 1070, poolValueMax = 1094, }, }; ---@type RailVehicle_DataTable local GI1E_b = TableUtil.deepCopy(GI1E_a); GI1E_b.components = GI1E_a.components; -- TODO GI1E_b.contentName = "Berlin_GI1E_b"; GI1E_b.blueprintFilename = "/SubwaySim_Berlin/Vehicles/GI1E/BP_GI1E_B.BP_GI1E_B"; GI1E_b.lights.headLights = nil; GI1E_b.lights.tailLights = nil; GI1E_b.couplingFront.couplingBoneName = "Coupling_Rotation3"; GI1E_b.couplingRear.couplingBoneName = "Coupling_Rotation4"; ---@type TrainComposition_DataTable local GI1E_1x = { contentType = "trainComposition", contentName = "Berlin_GI1e_1x", title = "GI1e x1", author = "Simuverse GmbH", -- not required for AI trains description = "", previewFilename = "", hidden = true, vehicles = { { contentName = "Berlin_GI1E_a", forward = true, }, { contentName = "Berlin_GI1E_b", forward = true, }, { contentName = "Berlin_GI1E_b", forward = false, }, { contentName = "Berlin_GI1E_a", forward = false, }, }, }; ---@type TrainComposition_DataTable local GI1E_2x = { contentType = "trainComposition", contentName = "Berlin_GI1e_2x", title = "GI1e x2", author = "Simuverse GmbH", -- not required for AI trains description = "", previewFilename = "", hidden = true, vehicles = { { contentName = "Berlin_GI1E_a", forward = true, }, { contentName = "Berlin_GI1E_b", forward = true, }, { contentName = "Berlin_GI1E_b", forward = false, }, { contentName = "Berlin_GI1E_a", forward = false, }, { contentName = "Berlin_GI1E_a", forward = true, }, { contentName = "Berlin_GI1E_b", forward = true, }, { contentName = "Berlin_GI1E_b", forward = false, }, { contentName = "Berlin_GI1E_a", forward = false, }, }, }; g_contentManager:addContent(GI1E_a); g_contentManager:addContent(GI1E_b); g_contentManager:addContent(GI1E_1x); g_contentManager:addContent(GI1E_2x);
RailVehicle_DataTable Explained
Now we will go through the `RailVehicle_DataTable` step by step.
The `RailVehicle_DataTable` contains the core configuration of a vehicle. It defines all information required so the game can correctly:
- load the vehicle Blueprint,
- place the vehicle on the track,
- apply physics and braking,
- configure couplings,
- and register audio, lights, and additional Lua components.
In this example, the DataTable is stored in a local Lua table called `GI1E_a`.
Creating the Vehicle Data Table
---@type RailVehicle_DataTable local GI1E_a = {
| Entry | Explanation |
|---|---|
| `local GI1E_a = {` | Creates a local Lua table called `GI1E_a`. This table holds all vehicle data and will later be registered via the Content Manager. |
Basic Metadata
These fields define what the vehicle is and how it appears inside the content system.
| Entry | Explanation |
|---|---|
| `contentType = “railVehicle”` | Defines the content type. For vehicles, this must be `railVehicle`. |
| `contentName = “Berlin_GI1E_a”` | Internal unique identifier used by the Content Manager. Must be unique across all loaded content. |
| `title = “GI1E”` | Display name of the vehicle. |
| `author = “$GameDeveloper”` | Author name shown in content information. You can replace this with your name/project. |
| `description = ““` | Optional description text. Not required for AI-only trains. |
| `previewFilename = ”“` | Optional preview image path. Not required for AI-only trains. |
Physical Parameters
These values define physical base properties and basic performance.
| Entry | Explanation |
|---|---|
| `emptyMass = 37.0` | Empty mass of the vehicle in tons. |
| `vmax = 70` | Vehicle maximum speed (km/h) that the vehicle can reach by its own traction. |
Dimensions and Bogie Distances
These values are critical for correct placement on the track and correct bogie positioning.
| Entry | Explanation |
|---|---|
| `length = 12.64` | Total vehicle length from coupling tip to coupling tip (meters). |
| `frontToFirstBogie = 2.53` | Distance from the front coupling tip to the center of the front bogie (meters). |
| `rearToLastBogie = 2.53` | Distance from the rear coupling tip to the center of the rear bogie (meters). |
Traction / Engine Settings
These values define the maximum propulsion capability of the vehicle.
| Entry | Explanation |
|---|---|
| `maxEnginePower = 480` | Maximum engine power (kW). If you have multiple motors per car, sum them up for the full carbody. |
| `maxEngineForce = 50` | Maximum tractive effort / force (kN). |
Blueprint Reference
This defines which Unreal Blueprint is loaded for this vehicle.
| Entry | Explanation |
|---|---|
| `blueprintFilename = ”/SubwaySim_Berlin/Vehicles/GI1E/BP_GI1E_A.BP_GI1E_A”` | Unreal asset path to the Vehicle Blueprint. Important: Unreal requires the Blueprint name twice at the end, without `.uasset`. Example: `/MyMod/Vehicles/TestTrain/BP_TestTrain.BP_TestTrain` |
Coupling Configuration
This section is crucial for:
- correct vehicle placement on the rail,
- correct coupling rotation,
- automatic coupling behavior in consists.
The front coupling:
couplingFront = { rotationOrigin = "Coupling_F", couplingOffset = 0.985, automaticCoupling = true, skeletalMesh = "Exterior", couplingBoneName = "Coupling_Rotation1", },
| Entry | Explanation |
|---|---|
| `rotationOrigin` | Name of the Scene Component in your Vehicle Blueprint that defines the front coupling reference point (e.g. `Coupling_F`). |
| `couplingOffset` | Offset distance between the coupling reference point and the coupling rotation pivot (meters). |
| `automaticCoupling` | If `true`, coupling happens automatically when approaching another vehicle. |
| `skeletalMesh` | Name of the Skeletal Mesh Component inside the Blueprint that contains the coupling mesh (e.g. `Exterior`). |
| `couplingBoneName` | Name of the coupling rotation bone inside the skeleton (driven by animation). |
The rear coupling works the same way:
couplingRear = { rotationOrigin = "Coupling_B", couplingOffset = 0.985, automaticCoupling = true, skeletalMesh = "Exterior", couplingBoneName = "Coupling_Rotation2", },
Cameras
Defines exterior cameras if the vehicle supports them. For AI vehicles, this is typically empty.
cameras = { exteriorCameras = {}, },
Braking Systems
Defines available brake systems and their maximum forces.
brakingSystems = { pneumatic5bar = { maxBrakeForce = 60, }, electric = { maxBrakeForce = 50, maxBrakePower = 480, }, },
| Entry | Explanation |
|---|---|
| `pneumatic5bar.maxBrakeForce` | Maximum brake force of the pneumatic brake (kN). |
| `electric.maxBrakeForce` | Maximum brake force of the electric brake (kN). |
| `electric.maxBrakePower` | Maximum braking power of the electric brake (kW). |
Components
This list defines which Lua components are attached to the vehicle.
components = { LightManager, VehicleNumber, AudioManager, Berlin_PIS, },
| Component | Purpose |
|---|---|
| `LightManager` | Controls vehicle lighting logic. |
| `VehicleNumber` | Handles vehicle number generation and decals. |
| `AudioManager` | Manages audio playback and audio components. |
| `Berlin_PIS` | Controls destination displays and announcements. |
Audio (AI Bogie Audio)
Defines which Blueprint Audio Component is used for AI bogie sound playback.
audio = { audioBogieAI = { { audioComponent = "AiAudio", }, }, },
| Entry | Explanation |
|---|---|
| `audioComponent = “AiAudio”` | Name of the Audio Component inside the Vehicle Blueprint that plays the AI MetaSound / audio setup. |
Lights
This section defines headlights and taillights, including:
- which direction they apply to,
- which mesh + material slot is affected,
- which emissive parameters are set,
- and which Light Components are controlled.
| Concept | Explanation |
|---|---|
| `direction = 1` | Applies when the vehicle faces the forward direction. |
| `direction = -1` | Applies when the vehicle faces the reverse direction. |
| `mesh` | Skeletal Mesh Component name used for material manipulation (e.g. `Exterior`). |
| `materialSlot` | The material slot name that contains emissive parameters. |
| `materialParams` | Material parameter values applied when the light is active (usually emissive strength). |
| `lightComponents` | Actual Unreal Light Components to switch on/off and configure (intensity, cone, radius, etc.). |
Headlights example:
headLights = { { direction = 1, headlightElements = { { mesh = "Exterior", materialSlot = "GI1E_LightsExterior_mat", materialParams = { ["Emissive01"] = 100, }, lightComponents = { {lightComponent ="Headlight1", intensity = 50, temperature = 4500, attenuationRadius = 500, innerConeAngle = 0, outerConeAngle = 70}, {lightComponent ="Headlight2", intensity = 50, temperature = 4500, attenuationRadius = 500, innerConeAngle = 0, outerConeAngle = 70}, }, }, }, }, },
Taillights example:
tailLights = { { mesh = "Exterior", materialSlot = "GI1E_LightsExterior_mat", direction = -1, materialParams = { ["Emissive02"] = 100, }, }, },
PIS (Destination Displays & Announcements)
The PIS section defines:
- which destination display components exist,
- which announcement audio files are used,
- the audio component for playback,
- and additional announcement logic settings.
Key elements:
- `destinationDisplays` defines display components (e.g. `ZZA`)
- audio lists define announcement file assets
- `audioComponent` defines the Blueprint Audio Component used for announcements
(Your provided PIS block remains valid and can be explained further in the next section.)
Vehicle Numbers
Defines how vehicle numbers are generated and displayed.
vehicleNumber = { labels = { { mesh = "Exterior", materialSlot = "DecalDigits_mat", }, }, poolName = "Berlin_GI1E", poolValueMin = 1070, poolValueMax = 1094, },
| Entry | Explanation |
|---|---|
| `labels.mesh` | Mesh that receives the number decal material. |
| `labels.materialSlot` | Material slot where number decals are applied. |
| `poolName` | Name of the number pool. |
| `poolValueMin / poolValueMax` | Range of numbers the vehicle can randomly receive. |
(Continuation follows: copying variants with `deepCopy`, defining TrainCompositions, and registering content via `g_contentManager:addContent()`.)

