Train constantly derailing at relatively low speeds

I have a train, I have tracks. But no matter what I do, it keeps derailing. The only way I keep it stable on the track is if it’s going incredibly slow.

Like I picture a rollercoaster, I want it to be able to fly along the tracks and never have to have any concern of derailment
Train Derail.rbxl (64.6 KB)

I’m not sure what physics I could change, I’ve messed with making gliders have different friction and densities/etc. I tried placing gliders underneath the rail too (so double gliders) and they still somehow phase through track colliders.

But it’s just constant, no matter what I do, the train always finds a way to derail

1 Like

The Problem

What you just observed is often referred to as tunneling, it’s where object moves fast enough that there is enough gap between physics steps that it can just hop from one side of the object to another, essentially never touching it in the process.

Low velocity:

qxbzM

High velocity:

9u12Z

NOTE: These 2 videos are not mine and were originally posted at: terminology - In games physics engines, what is "tunneling", also known as the "bullet through paper problem"? - Game Development Stack Exchange

Your case specifically:

I noticed that your train runs at stepping frequency of 120 hz rather than the maximum of 240 hz.

xDLEHnkpnN3SRweZu1aogpFhE3mxDHHL

I was able to trigger 240 hz by switching from adaptive to fixed physics stepping method, which stopped the train from derailing on the first turn, but even that wasn’t enough for the second turn.

Pz4LbUa71UirOFXRfKpgcT7NowftmFN1

The Solution

Most video game engines provide something called continuous collision detection (CCD), which is more computationally expensive but does solve the problem for most cases. As for Roblox, there doesn’t seem to be any implementation of CCD, and if there is, it isn’t directly accessible to the developers, so you are left with few alternatives:

A) Lower the speed of the train.
B) Make the walls thicker.
C) Script the train movement yourself rather than relying on Roblox’s physics engine.
D) File a feature request requesting an option to utilize more accurate collisions.

If you are going with either A or B, I would recommend switching the physics stepping method to Enum.PhysicsSteppingMethod.Fixed (“Fixed”) due to the nature of your game (it heavily relies on the physics engine).

If you wonder how I managed to take the screenshots, it was possible with the physics controls that were added last year: Pausing Physics in Studio [Beta].

OfXoAxl8ZZ3X7840iJnbYy8bFFYUzD7P

5 Likes

I stopped relying on velocity physics with trains years ago, Roblox’s physics engine is way too buggy for it. Most big train games use a nodal approach because trains derail way too easily in the Roblox engine. Some Airport People Movers already had to do loads of hacky stuff with the bogies.

If you need physics, you can probably use a combination of AlignPosition and AlignOrientation to force it to a CFrame physically.

This is very informative! Thank you for going into so much detail, a lot of this stuff I was not aware of! :smiley: :smiley: Will try out these few things and get back when I’ve tested :+1:

My concern with using a nodal approach was attaching players to the moving train? How would I go about that effectively? Like in terms of if I was to say use tween or lerp to nodes. Cause I have 2 bogies, I’d needa do each one separately, so train can turn effectively on corners and not look stiff

How to get the train movement working using CFrames:
How would you handle a train system? - #3 by Tomi1231
How to convert a physical train to a CFrame train - #4 by Tomi1231

Moving players along with the train (without a seat):
Player won't move with model - #13 by Tomi1231

Hope this helps,

Didn’t feel like writing a lengthy answer when I’ve already tackled this topic previously, but if you have any questions, feel free to ask them

If you’re going to tween the train:

You could also use TweenServicePlus for smoother tweening on the client as long the train doesn’t need to carry anything on the server

You mention PivotTo being bad in one of your responses, why is that? I don’t want to use tween, as I want this all server sided, but want the train to still feel as smooth as it would with physics

1 Like

PivotTo() is made to move every part of a model together, but the engine is not optimized for that
The engine is optimized at moving welded assemblies, the cost of moving the rootpart only (which also moves every welded part) is far lower than moving every part on their own (which is equivalent to PivotTo(), on an unwelded model)

Note that you have to move the rootpart using .CFrame for the welded parts to move along it, .Position behaves differently for god knows what reason

You can also use TweenService on the rootpart of the welded assembly, it works perfectly. How I made it smooth is by updating the CFrame every frame

Is there anyway to use constraints while using CFrame though? I’ve tried converting to CFrame and it’s fine I guess, but my doors/etc. all lag behind, as they use constraints

I would look into AlignPosition and AlignOrientation, they have a setting called RigidityEnabled, which should make it follow the CFrame exactly, or you can tweak the settings to still allow some play

issue with that property i think is it can cause things to basically snap. which i want doors for example to smoothly open/close as needed, both while stopped and moving

If you lerp the CFrame it wont snap (with or without a physical constraint), as the CFrame’s update over every frame would be smooth

So, I decided to make a full example script on how CFrames can be used to make smooth movement

It has a lot of CFrame manipulations which are probably hard to understand. You don’t need to focus on that (if you use a bezier curve for your train, the bezier curve function will spit out a CFrame directly after you give the alpha value to the function, so it’s kinda simpler?)

What is important is the alpha value. The alpha value is calculated using time (os.clock() here), so on every frame, the alpha value gives you the completion % of the tween, and even with inconsistent fps, since it uses time, it will still be smooth. Lerp on CFrames is also really useful, you can’t lerp orientation like that

local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")

local Door = workspace.Door
local ClickDetector = Door.ClickDetector

local DoorRadius = Door.Size.X/2
local HingeOffset = CFrame.new(-DoorRadius, 0, 0) -- Offset from the center of the door to the hinge

local DoorHingeCFrameClosed = Door.CFrame * HingeOffset
local DoorHingeCFrameOpen = DoorHingeCFrameClosed * CFrame.Angles(0, math.pi/2, 0) -- math.pi/2 is 90°, but in radians

local TweenDuration = .5

local function TweenDoor(StartCFrame : CFrame, EndCFrame : CFrame)
	local StartTick = os.clock()
	
	-- Return a number from 0 to 1, 0 is 0% of the tween completed, 100% is the tween is done
	local function GetAlpha()
		return math.clamp((os.clock() - StartTick)/TweenDuration, 0, 1)
	end
	
	--
	
	-- Unbind previously binded function
	RunService:UnbindFromRenderStep("DoorTween")
	
	RunService:BindToRenderStep("DoorTween", Enum.RenderPriority.Camera.Value -1, function() 
		local alpha = GetAlpha()
		
		-- This line allows to apply easing styles to the alpha value, it basically remaps the alpha value following an "easing function"
		--alpha = TweenService:GetValue(alpha, Enum.EasingStyle.Elastic, Enum.EasingDirection.Out)
		
		local HingeCFrame = StartCFrame:Lerp(EndCFrame, alpha)
		Door.CFrame = HingeCFrame * HingeOffset:Inverse() -- Need to go from hinge to door, rather than door to hinge, so need to invert the offset
		
		if alpha == 1 then RunService:UnbindFromRenderStep("DoorTween") end
	end)
end

local IsOpen = false
ClickDetector.MouseClick:Connect(function() 
	IsOpen = not IsOpen
	
	-- Need to get the current hinge CFrame, if the door was still being tweened when clicked
	local CurrentHingeCFrame = Door.CFrame * HingeOffset
	
	if IsOpen then
		TweenDoor(CurrentHingeCFrame, DoorHingeCFrameOpen)
	else 
		TweenDoor(CurrentHingeCFrame, DoorHingeCFrameClosed)
	end
end)

The door is just a simple part, with a ClickDetector inside

Perhaps try the trick irl rollercoasters use: wheels on 3 sides to fully lock the cart to the rails.

Image src:
External Image