How can I make the train coaches turn realistically?

Hello! First of all, I want to point out that I have a working train movement system that works with TweenService.

A problem I encountered with longer coaches is that they don’t turn realistically. And if you think the curve is too sharp - it’s not, it works fine with physics based trains. (with BodyVelocity) GIF

Is there any way I can make the coaches turn realistically? (Like Stepford County Railway and other games) I tried using 2 parts in each coach that I would tween but unfortunately that didn’t work.

Any help is greatly appreciated!

1 Like

It looks like you’re determining the angle of the coach based on the track at its center. You should instead be setting an angle that keeps the front and back wheels on the track.

1 Like

Any idea on how I can achieve this?

Can you describe how you’re doing it now? I’ll see if I can modify that.

So, I have a RootPart that is the length of a coach, and its CFrame is tweened to the next node.

function TrainSuperClass:CreateTween(carriage)
	local node = self[carriage.Name].Node
	
	if not node then
		warn(carriage.Name.." does not have next node set")
		return
	end
	
	local distance = (carriage.PrimaryPart.CFrame.p - node.CFrame.p).Magnitude
	local Time = self:GetTime(distance)
	local tweenInfo = TweenInfo.new(
		Time,
		Enum.EasingStyle.Linear,
		Enum.EasingDirection.Out,
		0,
		false,
		0
	)
	
	if type(self[carriage.Name].Tween) == "userdata" then
		self[carriage.Name].Tween:Cancel()
	end
	
	self[carriage.Name].Tween = TS:Create(carriage.PrimaryPart, tweenInfo, {CFrame = node.CFrame})
	self[carriage.Name].Tween:Play()
end

Have a RootPart at the front and rear wheels. Space the nodes correctly so that they will line up with the RootParts. Then as you tween the rear wheels to a new node, the front wheels should be at the next node or maybe the one after that, depending on how you space it.
The CFrame’s position is determined by the rear node and the look vector is the unit vector of the difference between the rear and front node’s position.

I tried having 2 RootParts that I would tween but sadly that didn’t work.

You don’t need the front RootPart. You just need the rear one, but define the CFrame as
CFrame.new(node1, node2)
where node1 is where the rear wheels are and node2 is where the front wheels are (a few nodes ahead of node1 depending on the coach length)

The CFrame’s position is determined by the rear node and the look vector is the unit vector of the difference between the rear and front node’s position.

Can you explain this part a bit more, please? Sorry if I am being annoying but I haven’t worked with more complex CFrame stuff before.

When you pass two positions into CFrame.new it creates a CFrame at the first position pointing towards the second position. (By node1 I mean node1.Position and same for node2)

Alternatively you can use CFrame.fromMatrix as described in Developer CFrame tutorial. It’s more powerful but you probably don’t need it here.

So, which part am I gonna be actually tweening? And if it’s the rear RootPart of the coach, then would I do something like this?

--RootPart2 is the rear RootPart, RootPart1 is the front RootPart; PrimaryPart of the coach is the rear RootPart
local cframe = CFrame.new(carriage.RootPart2.Position, carriage.RootPart1.Position)
local distance = (cframe.p - node.CFrame.p).Magnitude
local Time = self:GetTime(distance)
local tweenInfo = TweenInfo.new(
	Time,
	Enum.EasingStyle.Linear,
	Enum.EasingDirection.Out,
	0,
	false,
	0
)

self[carriage.Name].Tween = TS:Create(carriage.PrimaryPart, tweenInfo, {CFrame = node.CFrame})
self[carriage.Name].Tween:Play()

Just change one line:
local cframe = CFrame.new(node.Position, node2.Position)

node will be the rear wheels and node2 will be he front wheels? Or the track nodes?

The track nodes. node2 is gotten by going a few nodes ahead of node.

Alright, I will try this tomorrow, thank you!