I have a script which makes it so the player get’s cframed along with a train. The train uses cframe to move so I also use cframe to make it so the player moves with the train. But I have a really annoying issue, where the player can’t move to a train car in front of them:
I’ve tried making a platform between each train-car, but to no avail. I’ve tried making the train slower than the player’s walkspeed which works, but I’d rather not have to make the train outrageously slow in order to fix this issue.
Here is the code i’m using:
-- Localscript inside of StarterCharacterScripts
local Players = game:GetService("Players")
local player = game.Players.LocalPlayer
local RunService = game:GetService('RunService')
local LastHit
local LastTrainCFrame
local Function
local Function2
Function = RunService.Heartbeat:Connect(function()
local RootPart = player.Character.HumanoidRootPart
local Ignore = player.Character
local ray = Ray.new(RootPart.CFrame.p, Vector3.new(0,-500,0))
local Hit, Position, Normal, Material = workspace:FindPartOnRay(ray,Ignore)
if Hit and Hit:IsDescendantOf(workspace.Trains) then
local Train = Hit
if not LastTrainCFrame then
LastTrainCFrame = Train.CFrame
end
if Train ~= LastHit then
LastTrainCFrame = Train.CFrame
end
local TrainCF = Train.CFrame
local Rel = TrainCF * LastTrainCFrame:inverse()
LastTrainCFrame = Train.CFrame
RootPart.CFrame = Rel * RootPart.CFrame
LastHit = Train
else
LastTrainCFrame = nil
end
if not Function2 then
Function2 = player.Character.Humanoid.Died:Connect(function()
Function:Disconnect()
Function2:Disconnect()
end)
end
end)
When I print what object the raycast hits, when I get to the edge of a train car, and try to walk to the one in front of it, the localscript prints the train-car that i’m trying to walk to, then the next time the physics-frame fires it prints the train-car that I was already standing on.
So can you fire 2 rays? One straight below the player to see what car they are currently on and another slightly ahead to give something for the script to realize it needs to change to the next car?
Looks like it is only seeing the current car and cframing it with that car and you’ll never be able to go to the next without some form of switch over.
If I knew I’d pass it on. It seems very strange that the ray and script won’t recognize the next rail car though…
I know waits are not the best, but what if you put a wait() in there? It would add a delay, but would it allow your ray to recognize the next car?
As far as I can see, the issue is that there are 2 faces in the same area which confuse the raycasting. Cast one ray with :FindPartOnRayWithWhitelist(theray,{LastHit}) and keep that as your hit; otherwise, check for a new part.
Another issue I see is that there is a gap between the segments, so physics might be partially to blame here. If nothing is found, it’ll disable what was holding you, then re-enable in on the next frame, giving these jerky motions. To counteract this, you might have to add non-collideable platforms which phase through each other at the point of connection. Basically a slightly longer part which is around the base of your segments.
Also, you can make it much more stable by replacing your raycasting with :FindPartOnRayWithWhitelist(theray,{workspace.Trains}).
You can also cache the whitelists to avoid creating new ones each frame.
Mine only checks parts that are parented to workspace.Trains, while yours will also fire if there is a character or some random part under it. This means that with your case, if the player jumps over someone else, they’ll suddenly get flung back because it sees them as not part of the train. Mine will just check if there is a cart below.
When doing that, is it ok for me to add workspace.Trains to the Whitelist table? And should I always use that when raycasting, or are there only certain situations where I would need to use that line of code?
OK, I found the issue and rewrote the basic code you posted with a fix and some of the stuff I previously said.
-- Localscript inside of StarterCharacterScripts
local Players = game:GetService("Players")
local player = game.Players.LocalPlayer
local RunService = game:GetService('RunService')
local LastHit
local LastTrainCFrame
local Function
local Function2
local Whitelist = {workspace.Trains}
Function = RunService.Heartbeat:Connect(function()
local RootPart = player.Character.HumanoidRootPart
local Ignore = player.Character
local ray = Ray.new(RootPart.CFrame.p, Vector3.new(0,-500,0))
local Hit, Position, Normal, Material
if LastHit then
Hit, Position, Normal, Material = workspace:FindPartOnRayWithWhitelist(ray,{LastHit})
if not Hit then
Hit, Position, Normal, Material = workspace:FindPartOnRayWithWhitelist(ray,Whitelist)
end
else
Hit, Position, Normal, Material = workspace:FindPartOnRayWithWhitelist(ray,Whitelist)
end
if Hit then
local Train = Hit
if not LastTrainCFrame then
LastTrainCFrame = Train.CFrame
end
local TrainCF
if Train ~= LastHit and LastHit then
TrainCF = LastHit.CFrame
else
TrainCF = Train.CFrame
end
local Rel = TrainCF * LastTrainCFrame:inverse()
LastTrainCFrame = Train.CFrame
RootPart.CFrame = Rel * RootPart.CFrame
LastHit = Train
if Train ~= LastHit then
LastTrainCFrame = Train.CFrame
end
else
LastTrainCFrame = nil
end
if not Function2 then
Function2 = player.Character.Humanoid.Died:Connect(function()
Function:Disconnect()
Function2:Disconnect()
end)
end
end)
The issue was that when you switched parts, you’d set the LastTrainCFrame to the new part’s CFrame, making the Rel value equal to an identity matrix (no offset and no orientation), thus it would not move the player forward, allowing physics to push them back.
Also, decrease the -500 to something like -10 as it can cause lag on complex maps.
Finally. Someone who made a good, smooth and better player standing on train script. I used to keep messing with mines until I broke my script. Now I found a good one. Thanks to @nooneisback