返回博客
Guide3 分钟阅读

FiveM 火车系统脚本:自定义轨道路线和车站

在 FiveM 中让火车栩栩如生。自定义路线、可控引擎、乘客座位以及最佳火车脚本,打造沉浸式交通角色扮演体验。

Agency Scripts

Agency Scripts 创始人兼首席开发者

作为服务器基础设施的公共交通

火车和公共交通系统将FiveM服务器从汽车为中心的城市转变为拥有功能性公共交通的活力大都市。火车按时刻表沿GTA现有铁路运行,公交车沿城市街道线路行驶,地铁站提供区间快速通行,共同营造真实生动的世界。公共交通惠及所有玩家,无论财富或职业。新玩家无法负担汽车可乘公交到达工作地点。资深玩家高峰期乘地铁避开拥堵。犯罪玩家利用公共交通无车可追踪地移动。运输系统还创造独特工作机会,如玩家运营的公交司机、列车员及维护时刻表、收取车费和处理网络事件的交通管理人员。从技术角度看,按固定路线运行的火车和公交车作为移动实体填充世界,使城市显得繁忙,无需额外玩家数量。

火车路线与时间表配置

GTA V 内置了环绕地图的铁路网络,您的火车系统应利用这些现有轨道以实现最大兼容性。将火车路线定义为沿轨道的车站序列,抵达和出发时间根据火车在车站间的平均速度计算。每个车站需要一个乘客上下车的平台区域、售票机交互点和显示即将到达的公告牌。配置多个火车服务,在网络不同区段同时运行,时刻表按可配置周期重复,匹配您服务器的昼夜进程:

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 },
        },
    },
}

frequency value 决定新列车从路线第一站出发的频率。多列列车可同时在同一路线上运行,间隔由频率决定。 stopDuration 每个车站停留时间足够玩家上下车,列车到站时播放语音提示,离站前几秒播放出发警告铃声。

火车实体生成与移动

在 FiveM 中生成和控制火车需要特定的 native 函数,这些函数与常规车辆处理不同。使用 CreateMissionTrain 在轨道网络上生成火车,自动将其放置在最近的轨道上并处理轨道跟随物理。使用控制火车速度 SetTrainSpeedSetTrainCruiseSpeed,并通过在接近站台坐标时逐渐减速使火车在车站停下。火车必须精确停在站台位置,以便车门与登车区域对齐,这需要根据到下一站的距离进行仔细的减速计算:

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)

乘客登机与售票

当火车停靠车站时,向附近玩家显示交互提示,允许登车。检查玩家是否持有效车票或通行证方可上车。车票可在各站的售票机道具处购买,使用简单 NUI 界面显示可用路线和价格。登车后,将玩家传送至车厢内部,并设置其相对火车实体的位置,使其随火车移动。GTA 内置火车内饰适合基础实现,但使用附加物体或实例的自定义内饰能提供更佳体验,包括座位、站立区和显示外景的窗户。追踪每位乘客的上车站点,以便系统计算基于距离的票价差异。通过 3D 文字元素或附加 NUI 显示车厢内下一站名称和预计到达时间,随火车行进实时更新。

公交系统和NPC司机

公交车通过覆盖铁路未到达的区域来补充火车网络。生成遵循预定义路线的NPC驾驶公交车,沿城市街道行驶,在标记的公交站点停靠,接送玩家。NPC司机使用GTA的任务系统在站点之间导航,使用 TaskVehicleDriveToCoordLongrange 带有适当的驾驶标志以遵守交通法规并在指定地点停车。在每个站点,公交车等待配置的时间,玩家上下车后NPC司机继续前往下一站。公交站道具显示路线信息,包括路线号、目的地和下一班车预计到达时间。对于玩家操作版本,允许拥有交通管理工作岗位的玩家手动驾驶公交车,按乘客数获得报酬,并因保持时间表获得额外奖金。

交通地图与车站UI

创建可通过手机、车站自助机或公交站牌访问的交通地图。地图显示所有活跃线路,按线路颜色编码,车站位置及换乘指示,多线路交汇处标明换乘。实时列车位置每几秒更新一次。车站自助机界面显示当前时间、各线路下一班车到达时间、服务中断或延误信息及购票界面。将交通地图实现为 NUI 覆盖层,采用示意图方式渲染轨道网络,类似现实地铁图,优先考虑清晰度而非地理准确性。添加实时指示器,以移动点显示每列车沿线路的当前位置,给予玩家即时反馈,了解下一班车等待时间。

车费系统与交通通行证

实现分层票价系统,包括单程票、日票和月票。单程票按基础票价收费,乘车一次后失效。日票允许在二十四小时内无限次乘车,固定价格,在三到四次乘车后更具经济性。月票为常规通勤者提供最佳的单次乘车价值,作为物品存储在玩家库存中,带有过期时间戳。通过对旋转门道具的目标交互在车站入口闸机处验证票价,乘车时通过司机交互验证公交车票价。对于基于距离的计费,乘客下车时根据经过的车站数量计算票价。无有效车票的玩家在交通管理人员随机检查时被抓获,将被罚款,为交通管理工作增加执法玩法。

性能与同步

火车和公交车是大型实体,必须在所有客户端同步,因此它们是对性能敏感的功能,需要仔细优化。使用服务器端状态管理控制火车位置并向客户端广播更新,让每个客户端根据接收的坐标本地渲染火车实体。对于远离任何车站或铁路线的玩家,销毁火车实体以减少渲染开销,仅当玩家进入铁路线附近时才生成它们。公交车NPC司机仅在玩家附近时才主动执行任务,远处公交车使用简化的位置插值而非完整AI路径规划。限制服务器上同时存在的火车和公交车实体总数,以防高峰时段性能下降。不同路线错开发车时间,避免多辆车辆同时生成。资源重启时清理所有交通实体,并实现恢复逻辑,在资源启动时根据时间表重新计算火车位置,使火车立即出现在正确位置,而非每次都从第一站开始。

准备好开始了吗?

在我们的商店获取脚本,或加入 Discord 获取支持、更新以及新功能预告。