====== 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: * [[manual:subwaysim:vehicle_development:complex_vehicle_lua|Complex Vehicle.lua]] ---- ===== 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. ---@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", }, {{ :manual:subwaysim:vehicle_lua:image.webp |}} ---- ==== 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. | ---- ===== Creating a Variant Vehicle using deepCopy ===== In this section, we create a **variant** of an existing vehicle by copying an already defined `RailVehicle_DataTable` and adjusting only the values that differ. This approach is very common and highly recommended, especially for: * A- and B-cars * Direction-dependent variants * Slightly different vehicle bodies sharing the same base configuration Instead of defining the entire vehicle again, we reuse the existing data. ---- ==== Copying the Base Vehicle ==== ---@type RailVehicle_DataTable local GI1E_b = TableUtil.deepCopy(GI1E_a); This line creates a **deep copy** of the previously defined vehicle `GI1E_a`. Important points: * All values from `GI1E_a` are copied into `GI1E_b` * Nested tables (lights, brakes, couplings, etc.) are copied as well * Changes made to `GI1E_b` do **not** affect `GI1E_a` This gives us a fully independent vehicle configuration that starts as a 1:1 copy. ---- ==== Reusing Component Definitions ==== GI1E_b.components = GI1E_a.components; -- TODO Here, the component list is explicitly reused. This means: * Both vehicles use the same Lua components (LightManager, AudioManager, PIS, etc.) * This is safe as long as the component behavior is identical * The comment `-- TODO` indicates that this could be changed later if needed This avoids unnecessary duplication of identical component lists. ---- ==== Changing the Content Name ==== GI1E_b.contentName = "Berlin_GI1E_b"; Each vehicle must have a **unique `contentName`**. Even though `GI1E_b` is based on `GI1E_a`, it must be registered as a separate vehicle in the Content Manager. This name is later used: * in train compositions * when spawning vehicles * internally by the game ---- ==== Assigning a Different Blueprint ==== GI1E_b.blueprintFilename = "/SubwaySim_Berlin/Vehicles/GI1E/BP_GI1E_B.BP_GI1E_B"; The B-car uses a **different Vehicle Blueprint**. Typical reasons for this: * Different interior layout * No driver cab * Different light setup * Different coupling setup Even though most logic is shared, the physical Blueprint can differ. ---- ==== Removing Head- and Taillights ==== GI1E_b.lights.headLights = nil; GI1E_b.lights.tailLights = nil; In this case, the B-car does **not** have its own headlights or taillights. By setting these entries to `nil`: * The light definitions inherited from `GI1E_a` are removed * The vehicle will not attempt to control head- or taillights * This prevents duplicated or incorrect lighting inside a train consist This is typical for intermediate cars in a multiple-unit train. ---- ==== Adjusting Coupling Bone Names ==== GI1E_b.couplingFront.couplingBoneName = "Coupling_Rotation3"; GI1E_b.couplingRear.couplingBoneName = "Coupling_Rotation4"; The B-car uses **different coupling bones** than the A-car. Reasons for this include: * Different skeleton layout * Additional coupling bones for internal train connections * Separate animation channels for different couplers Only the bone names are changed — all other coupling parameters remain the same. ---- ==== Summary: Differences Between GI1E_a and GI1E_b ==== ^ Aspect ^ GI1E_a (A-car) ^ GI1E_b (B-car) ^ | Base data | Fully defined | Copied from GI1E_a | | Blueprint | BP_GI1E_A | BP_GI1E_B | | Lights | Head- and tail lights present | No head- or tail lights | | Coupling bones | Rotation1 / Rotation2 | Rotation3 / Rotation4 | | Components | Own list | Reused from GI1E_a | | contentName | Berlin_GI1E_a | Berlin_GI1E_b | Using `TableUtil.deepCopy` keeps your Lua files clean, maintainable, and easy to extend when creating multiple vehicle variants. ---- ===== Train Compositions and Content Registration ===== At the end of the file comes one of the most important parts: **Train compositions** and the registration of all DataTables in the **Content Manager**. Without these steps, the game would know that the vehicle exists as a data table — but it would not know: * how to spawn it as a full train consist, * and it would not load the content at all. ---- ==== What is a TrainComposition_DataTable? ==== A `TrainComposition_DataTable` defines a **complete train consist** (multiple vehicles in a fixed order). AI trains in SubwaySim 2 are usually spawned using **train compositions**, not single vehicles. That is why this section is essential for AI vehicles. ---- ==== Example: GI1E_1x (one train unit) ==== ---@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 }, }, }; ^ Field ^ Meaning ^ | `contentType = "trainComposition"` | Defines that this table is a train composition. | | `contentName` | Internal unique identifier for this consist. Used by the Content Manager and spawners. | | `title` | Display name (used in menus or debug lists). | | `author` | Author name. | | `hidden = true` | Hides this composition from the vehicle selection menu. Very common for AI-only trains. | | `vehicles` | The ordered list of vehicles that form the train consist. | ---- ==== The vehicles list ==== The most important part is the `vehicles = { ... }` block. Each entry uses: ^ Field ^ Meaning ^ | `contentName` | Refers to a vehicle previously defined and registered (e.g. `Berlin_GI1E_a`). | | `forward` | Defines the orientation of that vehicle inside the consist. `true` means forward-facing, `false` means reversed. | This is where you can clearly see why `contentName` is so important: It acts as the unique key that links your **vehicle DataTables** into a full train consist. ---- ==== Example: GI1E_2x (two units coupled) ==== The 2x consist simply repeats the same unit again, resulting in a longer AI train. ---@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 }, }, }; ---- ==== Registering Content with the Content Manager ==== At the very end, we must register all content tables so SubwaySim 2 actually loads them. g_contentManager:addContent(GI1E_a); g_contentManager:addContent(GI1E_b); g_contentManager:addContent(GI1E_1x); g_contentManager:addContent(GI1E_2x); This does two things: * It tells the game: **"These DataTables exist and should be loaded."** * It makes the `contentName` entries usable by the spawning and AI systems. Important: * We pass the **local Lua variables** here (e.g. `GI1E_a`), not the string names. * If you forget to register a table, it will not exist in the game content system. ---- ==== Summary ==== * `RailVehicle_DataTable` defines **single vehicles** * `TrainComposition_DataTable` defines **AI train consists** * `g_contentManager:addContent(...)` is required so SubwaySim 2 loads the vehicles and compositions at all With this section completed, the vehicle and its AI train compositions are fully registered and can be used by SubwaySim 2. ---- ===== What's Next? ===== This page covered the setup of a **simple vehicle.lua**, mainly intended for AI-operated trains. If you want to create more advanced vehicles — such as player-driven trains, vehicles with interactive cabs, advanced systems, or extended logic — you will find further detailed information in the following chapters: * **[[manual:subwaysim:vehicle_development:complex_vehicle_blueprint|Complex Vehicle Blueprint]]** Learn how to build advanced vehicle Blueprints with interactive components, cab logic, and extended systems. * **[[manual:subwaysim:vehicle_development:complex_vehicle_lua|Complex Vehicle.lua]]** Explore advanced Lua setups including player controls, extended systems, and complex vehicle behavior. These chapters expand on the foundations explained here and guide you step by step towards fully-featured, player-ready vehicles. {{page>manual:footer}}