Make a part follow another part thats being tweened

I’m here to see if anyone has a better way to make a train car follow another train car that is being tweened.
Right now, I am making each car tween individually, but I think there is a better way than to do this

However, I don’t mean that they are both parts in the same position, I mean one has to be following the other from behind, for example, 2 cars on a train. The 1st car is moving, and the 2nd car is moving along with it (following it) from behind.

Welds dont work btw,

You could tween the main part and change the other part’s CFrame inside of a RunService loop in-order to make them follow the main part at a fixed offset, using CFrame’s ToWorldSpace method:

-- Server Script in ServerScriptService
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")

local partA = Instance.new("Part")
partA.Position = Vector3.new(0, 4, 0)
partA.Anchored = true
partA.Parent = Workspace

local partB = Instance.new("Part")
partB.Anchored = true
partB.Parent = Workspace

local tweenInfo = TweenInfo.new(4, Enum.EasingStyle.Linear, Enum.EasingDirection.In)

local tween = TweenService:Create(partA, tweenInfo, {Position = partA.Position + Vector3.new(8, 0, -16)})

local partLength = partA.Size.Z

local offsetCFrame = CFrame.new(0, 0, partLength)
print(offsetCFrame)

local connection = RunService.Heartbeat:Connect(function()
	partB.CFrame = partA.CFrame:ToWorldSpace(offsetCFrame)
end)

tween:Play()
tween.Completed:Wait()
connection:Disconnect()

@Chimmy_Coder

The RunService loop should be disconnected when the tween finishes playing, I’ve edited the code to reflect this :slight_smile::+1:

1 Like

I assume partLength is the offset?

1 Like

Yes, the partLength variable determines the offset between each part

If each train car has the same length, then it should be set to the train car part’s Z size (this is assuming that -Z axis is the forward axis of your train car) + (the desired air gap you want between each part (in studs) * 2)

1 Like

Also, I understand the partLength and offsetCFrame, as its using the part length to determine how much too offset B (correct me if i am wrong)

However, I dont really understand what this part of the script does
(mainly cause i have nevered used RunService, Heartbeat and ToWorldSpace)

Can you help me summarize this. I would appreciate the help so much

local connection = RunService.Heartbeat:Connect(function()
	partB.CFrame = partA.CFrame:ToWorldSpace(offsetCFrame)
end)

Also why do i have too

(the desired air gap you want between each part (in studs) * 2)

Why do i have to multiply the airgap by 2 btw


Based of the image, does that mean that it is my train Z axis size + AirGap(2 Studs)*2 so 4?

Oh ya, I noticed something, for your code right, when the 2 train cars go onto a turn, I assume they turn as one whole block, However I need the train cars to turn along with the track.

Do you have an idea on how to go about doing this. I was thinking to list down every single CFrame that A was in, and then B would follow the CFrame listed, but with a delay so it would go something like that


Sorry for the terrible diagrams (and English)

1 Like

RunService’s Heartbeat loop is a loop that runs once a frame, although after physics is simulated. It’s essentially very similar to a regular while true do loop, but it runs the function connected to it in a separate thread, so it doesn’t block the code written below it from running until the loop is broken

CFrame:ToWorldSpace is a method that takes a CFrame value and converts it from object space into world space. Let’s say a chair is placed 10 studs to the right of a table, and the table’s position is 4, 2, 5. This means that the chair’s world position is at 14, 2, 5, and the chair’s position relative to the table’s position is 10, 0, 0. This essentially means that object space is the position or CFrame of an object relative to the position or CFrame of another object, which is ideal to use for a situation like yours

That’s due to the fact that, by default, the origin of a part’s CFrame and position is its center. This means that if you don’t multiply the value by 2 and you’d like the air gap to be 1 stud, the visible gap between each part would actually be 0.5 studs instead

That would be quite hard to do using CFrame alone, I’d recommend using physics constraints instead. You could also use Vector3Curves, but this would require a lot of tinkering in-order to match the curve to the tracks as closely as possible

1 Like

Hey there! You could try using RunService and lerp() on Train2’s CFrame.

local connection = RunService.RenderStepped:Connect(function()
	local targetPos = Train1.CFrame * CFrame.new(offsetPos);
	Train2.CFrame = Train1.CFrame:lerp(targetPos, .1)
end)
1 Like

What does the function Lerp return, and what do the arguements stand for (Sorry for asking, but I could not understand the definition when i search on google)

lerp() stands for “Linear Interpolation”. On Roblox, is used to gradually move one position, rotation, or other property toward a target value by a specific percentage each frame.

CFrame:lerp(target, percentage) works by creating a CFrame that is some percentage of the way between the current CFrame and the target.

  • percentage = 0 gives the starting CFrame.
  • percentage = 1 gives the target CFrame.
  • Values between 0 and 1 blend the two CFrames proportionally, so percentage = 0.5 moves halfway between them.

In the example I provided to you, 0.1 represents 10%, so the Train2 will move 10% closer to Train1.

1 Like

One correction though: Since in the code provided the percentage’s value is constant, the reason why Train1 is moving closer to Train2’s position is due to the fact that you’re changing the target position

The intended way to use lerp functions is to vary the percentage rather than the starting value and the target value


Plus when you multiply a CFrame by another CFrame, it’s the same as using CFrame1:ToWorldSpace(CFrame2), so the only difference between your code and mine is how we’re handling the air gap between each part

1 Like

Yep, you’re right! I made some changes, you’re free to suggest extra modifications.

--// Services
local RunService = game:GetService("RunService")
local tweenService = game:GetService("TweenService")

--// Variables
local trainCar1 = game.Workspace.Train1
local trainCar2 = game.Workspace.Train2
local offset = CFrame.new(20, 0, 0)

local connection
local myTween

local function updateFollower()
	
	connection = RunService.Heartbeat:Connect(function()

		local targetPosition = trainCar1.CFrame:ToWorldSpace(offset)

		trainCar2.CFrame = trainCar2.CFrame:lerp(targetPosition, 0.9)
		
	end)
	
end

local function onTweenCompleted()
	connection:Disconnect()
end

delay(5, function()

	myTween = tweenService:Create(trainCar1, TweenInfo.new(20), {Position = trainCar1.Position + Vector3.new(200, 0, 0)})
	
	coroutine.wrap(updateFollower)()
	
	myTween:Play()

	myTween.Completed:Connect(onTweenCompleted)
end)

You’re free to remove delay() obviously, I simply added it to see the animation with a delay upon client added.

1 Like

Does anyone have a solution to this, Or how i would be able to code this?
Right now I am thinking updating B position to A but with a delay to make it seem like its following it but it does not work due to some flaws

Does anyone know of a way to code this? I just need ideas

2 Likes

I think the idea made together with @JohhnyLegoKing should work. Refer to above idea/solution.

2 Likes

Unfortunately, it doesnt, the train cars still run parallel to each other instead of following along the tracks

I guess you’re using models for Trains. You’d have to work with PrimaryParts. And be sure to modify the Offset vector (x, y, z) depending on your model.

1 Like

Im not using models, Im using parts

Then that’s it. You said they’re moving parallel, I guess it’s just an issue on offset vectors.

2 Likes

You could also try animating the train if you’d prefer not to use physics constrains. You can make the train stop by setting the AnimationTrack’s speed to 0, and you can use animation events to make the train stop when it reaches a station


@Chimmy_Coder

Here’s an example of how the code will need to be if you choose to use animations:

-- Server Script inside of the train's model
local ANIMATION_SPEED = 1 -- This affects the speed of your train
local STATION_WAIT_DURATION = 4 -- How long the train will wait at a station

local train = script.Parent

local animator = train:FindFirstDescendant("Animator")

if animator and animator:IsA("Animator") then
	local animation = Instance.new("Animation")
	animation.AnimationId = "rbxassetid://" -- Remember to set this to the animation ID

	local animationTrack = animator:LoadAnimation(animation)

	-- For this to work, the animation events will need to be named StationReached
	animationTrack:GetMarkerReachedSignal("StationReached"):Connect(function()
		animationTrack:AdjustSpeed(0)
		task.wait(STATION_WAIT_DURATION)
		animationTrack:AdjustSpeed(ANIMATION_SPEED)
	end)

	animationTrack:Play(nil, nil, ANIMATION_SPEED)
else
	warn("Animator not found as a descendant of the train model")
end
1 Like

Allright. Thank you for all yalls help tho

2 Likes