Player isn't able to move to a certain train car

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)

So, what can I do to fix this?

5 Likes

I’m not the best person for building and physics, but maybe a quick force or constraint applied to the player can help.

Also, can you tell me how people make those smooth, curved rails? Is it a rail plugin?

1 Like

I just go to the toolbox, look up train tracks, and use some train tracks from there.

Can someone help me fix my problem?

1 Like

Have you tried printing to Output what the ray is hitting to see if you have a nil or some such thing that’s interfering with your script/cframing?

1 Like

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.

1 Like

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.

2 Likes

Any ideas on how I could do that?

1 Like

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?

1 Like

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.

1 Like

What would be the difference between this

:FindPartOnRayWithWhitelist(theray,{workspace.Trains})

And this

:FindPartOnRay(ray,Ignore)
1 Like

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.

3 Likes

I actually did a test on that with a train car with two parts that looks something like this:

Notice how both parts are right next to each other, no gaps.

Here is a video of me trying to go from the red part, to the green part:

Same thing happens, when I try to put both parts slightly inside each other.

Not sure what I can do to fix that.

Edit: wait, let me try this

1 Like

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?

1 Like

That’s only to check if the previous part is still under the character. If it is there, don’t check for the other part.

1 Like

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.

12 Likes

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

3 Likes