Public Transport as Server Infrastructure
A train and public transport system transforms your FiveM server from a car-centric city into a living metropolitan area with functional mass transit. Trains running on schedules along GTA's existing rail network, buses following routes through city streets, and metro stations providing fast travel between districts all contribute to a world that feels genuinely alive. Public transport benefits every player on the server regardless of wealth or job. New players who cannot afford a car can ride the bus to reach job locations. Experienced players use the metro during peak traffic to avoid congested roads. Criminal players use transit to move around the city without a traceable personal vehicle. The transport system also creates unique job opportunities for player-operated bus drivers, train conductors, and transit authority staff who maintain schedules, collect fares, and manage incidents on the network. From a technical perspective, trains and buses that run on consistent routes populate the world with moving entities that make the city feel busy without requiring additional player count.
Train Route and Schedule Configuration
GTA V has a built-in railway network that circles the map, and your train system should leverage these existing tracks for maximum compatibility. Define train routes as sequences of station stops along the track with arrival and departure times calculated from the train's average speed between stations. Each station needs a platform zone where players board and exit, a ticket machine interaction point, and display boards showing upcoming arrivals. Configure multiple train services that run simultaneously on different segments of the network, with timetables that repeat on a configurable cycle matching your server's day-night progression:
Config.TrainSystem = {
ticketPrice = 50,
speedMultiplier = 1.0,
despawnDistance = 500.0,
announceArrival = true,
announceDeparture = true,
}
Config.TrainRoutes = {
['metro_line_1'] = {
label = 'Metro Line 1 - City Loop',
model = 'metrotrain',
carriages = 3,
frequency = 300, -- seconds between departures
stations = {
{
name = 'Los Santos Central',
coords = vector3(268.09, -1204.28, 38.90),
platform = vector3(264.55, -1200.12, 38.90),
stopDuration = 20,
announceText = 'Now arriving at Los Santos Central Station',
},
{
name = 'Strawberry',
coords = vector3(36.81, -1393.78, 29.36),
platform = vector3(40.22, -1389.45, 29.36),
stopDuration = 15,
announceText = 'Now arriving at Strawberry Station',
},
{
name = 'Del Perro',
coords = vector3(-1359.88, -474.17, 15.02),
platform = vector3(-1355.44, -470.89, 15.02),
stopDuration = 15,
announceText = 'Now arriving at Del Perro Station',
},
{
name = 'Rockford Hills',
coords = vector3(-512.47, -680.33, 33.42),
platform = vector3(-508.12, -676.88, 33.42),
stopDuration = 15,
announceText = 'Now arriving at Rockford Hills Station',
},
{
name = 'Burton',
coords = vector3(-283.66, -324.78, 10.07),
platform = vector3(-279.33, -321.44, 10.07),
stopDuration = 15,
announceText = 'Now arriving at Burton Station',
},
},
},
}
Config.BusRoutes = {
['route_1'] = {
label = 'Route 1 - Downtown Express',
model = 'bus',
frequency = 180,
stops = {
{ name = 'Legion Square', coords = vector3(208.24, -935.88, 30.69),
stopDuration = 12 },
{ name = 'Pillbox Hospital', coords = vector3(311.44, -592.33, 43.29),
stopDuration = 12 },
{ name = 'Alta Street', coords = vector3(-226.87, -381.44, 30.05),
stopDuration = 12 },
{ name = 'Vinewood Blvd', coords = vector3(302.33, 193.67, 104.38),
stopDuration = 12 },
},
},
}
The frequency value determines how often a new train departs from the first station on the route. Multiple trains can operate on the same route simultaneously, spaced by the frequency interval. The stopDuration at each station gives players enough time to board or exit, with an audio announcement playing when the train arrives and a departure warning chime a few seconds before it leaves.
Train Entity Spawning and Movement
Spawning and controlling trains in FiveM requires specific native functions that differ from regular vehicle handling. Use CreateMissionTrain to spawn trains on the rail network, which automatically places them on the nearest track and handles rail following physics. Control train speed with SetTrainSpeed and SetTrainCruiseSpeed, and bring trains to a stop at stations by reducing speed gradually as they approach the platform coordinates. The train must stop precisely at the platform position so doors align with boarding zones, which requires careful speed reduction calculations based on distance to the next station:
local activeTrains = {}
function SpawnTrain(routeId)
local route = Config.TrainRoutes[routeId]
if not route then return end
local firstStation = route.stations[1]
local variation = 24 -- metro train variation
-- Create the mission train
local train = CreateMissionTrain(variation, firstStation.coords.x,
firstStation.coords.y, firstStation.coords.z, true)
if not DoesEntityExist(train) then
print('[Transit] Failed to spawn train for route: ' .. routeId)
return
end
SetTrainSpeed(train, 0.0)
SetTrainCruiseSpeed(train, 0.0)
SetEntityAsMissionEntity(train, true, true)
-- Store train state
activeTrains[routeId] = {
entity = train,
route = route,
currentStation = 1,
state = 'stopped', -- stopped, departing, moving, arriving
stateTimer = GetGameTimer(),
passengers = {},
}
return train
end
-- Train movement controller
CreateThread(function()
while true do
for routeId, trainData in pairs(activeTrains) do
local train = trainData.entity
if not DoesEntityExist(train) then
activeTrains[routeId] = nil
goto continue
end
local station = trainData.route.stations[trainData.currentStation]
local elapsed = GetGameTimer() - trainData.stateTimer
if trainData.state == 'stopped' then
if elapsed >= (station.stopDuration * 1000) then
-- Depart from station
trainData.state = 'departing'
trainData.stateTimer = GetGameTimer()
AnnounceToPassengers(trainData, 'Doors closing. Next stop: '
.. GetNextStationName(trainData))
end
elseif trainData.state == 'departing' then
local speed = math.min(15.0, elapsed * 0.005)
SetTrainCruiseSpeed(train, speed)
SetTrainSpeed(train, speed)
if speed >= 15.0 then
trainData.state = 'moving'
end
elseif trainData.state == 'moving' then
local nextIdx = trainData.currentStation + 1
if nextIdx > #trainData.route.stations then
nextIdx = 1
end
local nextStation = trainData.route.stations[nextIdx]
local trainCoords = GetEntityCoords(train)
local dist = #(trainCoords - nextStation.coords)
if dist < 200.0 then
trainData.state = 'arriving'
trainData.stateTimer = GetGameTimer()
AnnounceToPassengers(trainData, nextStation.announceText)
end
elseif trainData.state == 'arriving' then
local nextIdx = trainData.currentStation + 1
if nextIdx > #trainData.route.stations then
nextIdx = 1
end
local nextStation = trainData.route.stations[nextIdx]
local trainCoords = GetEntityCoords(train)
local dist = #(trainCoords - nextStation.coords)
-- Gradually slow down
local speed = math.max(0.0, dist * 0.08)
SetTrainCruiseSpeed(train, speed)
SetTrainSpeed(train, speed)
if dist < 3.0 then
SetTrainSpeed(train, 0.0)
SetTrainCruiseSpeed(train, 0.0)
trainData.currentStation = nextIdx
trainData.state = 'stopped'
trainData.stateTimer = GetGameTimer()
end
end
::continue::
end
Wait(100)
end
end)
Passenger Boarding and Ticketing
When a train stops at a station, display interaction prompts to nearby players allowing them to board. Check that the player has a valid ticket or transit pass before allowing them on. Tickets can be purchased from ticket machine props at each station using a simple NUI interface that shows available routes and prices. Once boarded, teleport the player into the train carriage interior and set their position relative to the train entity so they move with it. GTA's built-in train interiors work for basic implementations, but custom interiors using attached objects or instances provide a better experience with seating, standing areas, and windows that show the passing scenery. Track which station each passenger boarded at so the system can calculate fare differences for distance-based pricing. Display the next station name and estimated arrival time inside the carriage through a 3D text element or attached NUI display that updates as the train progresses along its route.
Bus System and NPC Drivers
Buses complement the train network by covering areas the rail does not reach. Spawn NPC-driven buses that follow predefined routes through city streets, stopping at marked bus stops to pick up and drop off players. The NPC driver uses GTA's task system to navigate between stops using TaskVehicleDriveToCoordLongrange with appropriate driving flags for obeying traffic laws and stopping at designated locations. At each stop, the bus waits for a configured duration while players board or exit, then the NPC driver continues to the next stop. Display route information at bus stop props showing the route number, destination, and estimated wait time until the next bus arrives. For a player-operated variant, allow players with the transit authority job to drive buses manually, earning pay per passenger transported and bonus pay for maintaining schedule adherence.
Transit Map and Station UI
Create a transit map that players can access from their phone, station kiosks, or bus stop displays. The map should show all active routes color-coded by line, station locations with interchange indicators where multiple lines connect, and real-time train positions updated every few seconds. The station UI at kiosks should display the current time, next arrivals for each line serving that station, any service disruptions or delays, and a ticket purchase interface. Implement the transit map as an NUI overlay that renders the rail network schematically rather than geographically, similar to real-world metro maps that prioritize clarity over geographic accuracy. Add real-time indicators showing each train's current position as a moving dot along its route line, giving players immediate feedback about how long they need to wait for the next service.
Fare System and Transit Passes
Implement a tiered fare system with single-ride tickets, day passes, and monthly subscriptions. Single tickets cost the base fare and expire after one journey. Day passes allow unlimited rides for twenty-four in-game hours at a fixed price that becomes economical after three or four trips. Monthly passes provide the best per-ride value for regular commuters and are stored as an item in the player's inventory with an expiration timestamp. Validate fares at station entry gates using target interactions on turnstile props, and on buses through the driver interaction when boarding. For distance-based pricing, calculate the fare when the passenger exits based on the number of stations traveled. Players without valid tickets who are caught during random inspections by transit authority staff receive fines, adding enforcement gameplay for the transit authority job.
Performance and Synchronization
Trains and buses are large entities that must be synchronized across all clients, making them performance-sensitive features that require careful optimization. Use server-side state management to control train positions and broadcast updates to clients, letting each client render the train entity locally based on received coordinates. Despawn train entities for players who are far from any station or rail line to reduce rendering overhead, and only spawn them when a player enters the vicinity of the rail network. Bus NPC drivers should only be actively tasked when players are nearby, with distant buses using simplified position interpolation rather than full AI pathfinding. Limit the total number of concurrent train and bus entities across the server to prevent performance degradation during peak hours. Stagger departure times across different routes so multiple vehicles are not spawning simultaneously. Clean up all transit entities on resource restart and implement recovery logic that recalculates train positions based on the timetable when the resource starts, so trains appear at correct locations immediately rather than starting from the first station every time.