If you put this script inside the StarterCharacterScripts in a localscript it should fix your issue.
Just remember to have an invisible part at the bottom of each train cart named “TrainFloor”
local Players = game:GetService("Players")
local player = game.Players.LocalPlayer
local RunService = game:GetService('RunService')
local LastTrainCFrame
local Function
local Function2
local TRAIN_BASE_NAME = "TrainFloor"
Function = RunService.Heartbeat:Connect(function()
-- CHECK PLATFORM BELOW:
local RootPart = player.Character.LowerTorso
local Ignore = player.Character
local ray = Ray.new(RootPart.CFrame.p,Vector3.new(0,-50,0))
local Hit, Position, Normal, Material = workspace:FindPartOnRay(ray,Ignore)
if Hit and Hit.Name == TRAIN_BASE_NAME then
-- MOVE PLAYER TO NEW POSITON FROM OLD POSITION:
local Train = Hit
if LastTrainCFrame == nil then -- If no LastTrainCFrame exists, make one!
LastTrainCFrame = Train.CFrame -- This is updated later.
end
local TrainCF = Train.CFrame
local Rel = TrainCF * LastTrainCFrame:inverse()
LastTrainCFrame = Train.CFrame -- Updated here.
RootPart.CFrame = Rel * RootPart.CFrame -- Set the player's CFrame
else
LastTrainCFrame = nil -- Clear the value when the player gets off.
end
Function2 = player.Character.Humanoid.Died:Connect(function()
Function:Disconnect() -- Stop memory leaks
Function2:Disconnect() -- Stop memory leaks
end)
end)
for example if this was your train:
the red transparent part is what you’d name “TrainFloor”:
the script uses raycasting to detect if you’re on top of train floor and if you are it cframes relatively to the red part (train floor).

