How to make a roller coaster using CFrame, Tween?

Project with an example of scripts work:
roller coaster test.rbxl (40.6 KB)

script v1
-- With time, the model will stop touching the part and will go to infinity

local qtween = game:GetService("TweenService")
local info =  TweenInfo.new(
	0.12, -- Time to make a smooth turn
	Enum.EasingStyle.Linear,
	Enum.EasingDirection.InOut
)

local mod = workspace.Model

-- box colision
local ovr = OverlapParams.new()
ovr.MaxParts = 1
ovr.CollisionGroup = "no"
local col = mod.Colision
local CFrame_col
local size_col = col.Size
local box,box1 -- list
local xr,yr,zr

local CF= Instance.new("CFrameValue")
CF.Value = mod.WorldPivot.Rotation
task.wait(4)

function start() -- The object moves without stopping
	while task.wait() do
		mod:PivotTo(mod.WorldPivot*CFrame.new(0.3,0,0))
	end
end

task.spawn(start)

while true do
	task.wait()
	CFrame_col = col.CFrame
	box = workspace:GetPartBoundsInBox(CFrame_col,size_col,ovr)
	-- A list is created with 1 object, which is the next part. To read the rotation and apply it to the model
	if #box == 1 then
		box[1].CollisionGroupId = 1
		-- New collision group for the part, so it no longer appears in the list
		local q = box[1]
		task.delay(1,function () q.CollisionGroupId = 0 end)
		-- Returns the original collision group, so you can move the model in circles
		xr = math.rad(box[1].Rotation.X)
		yr = math.rad(box[1].Rotation.Y)
		zr = math.rad(box[1].Rotation.Z)
		-- Since "CFrame.Angles" only has "(number,number,number)" for convenience, I create three variables
		-- in "CFrame.Angles" you cannot put "box[1].CFrame.Rotation", I tried
		-- a not working code example: CFrame.Angles(box[1].CFrame.Rotation)
		CF.Changed:Connect(function(F) -- Triggers as soon as "CF" value changes, "F" = "CF.Value"
			mod:PivotTo(CFrame.new(mod.WorldPivot.Position)*F) -- Turning without changing position
		end)
		local tween = qtween:Create(CF,info,{Value = CFrame.Angles(xr,yr,zr)})
		tween:Play()
	end
end
script v2
local mod = workspace.Model_V2

local qtween = game:GetService("TweenService")
local info =  TweenInfo.new(
	1, -- Time for moving and turning at the same time
	Enum.EasingStyle.Linear,
	Enum.EasingDirection.In
)

-- box colision
local ovr = OverlapParams.new()
ovr.MaxParts = 1
ovr.CollisionGroup = "no"
local col = mod.Center
local CFrame_col
local size_col = col.Size
local box -- list

local CF= Instance.new("CFrameValue")
CF.Value = mod.Center.CFrame

task.wait(4)

while true do
	task.wait()
	CFrame_col = col.CFrame
	box = workspace:GetPartBoundsInBox(CFrame_col,size_col,ovr)
	if #box == 1 then
		box[1].CollisionGroupId = 1
		local q = box[1]
		task.delay(1.1,function () q.CollisionGroupId = 0 end)
		CF.Changed:Connect(function(F) 
			mod:PivotTo(F)
		end)
		tween = qtween:Create(CF, info, {Value = box[1].CFrame*CFrame.new(3,0,0)})
		tween:Play()
	end
end

I am not going to insert a bunch of example videos here so that the topic does not take 100 km, just open the project and you will see everything I describe below

I’m new to scripting, in fact I’ve been learning roblox since 03/18/2022, I haven’t spent all my time learning scripting, I’ve been partially creating objects, and before I decided to do this roller coaster, I was doing great.

Here’s what I have:

In v1 the model moves only with CFrame, I couldn’t figure out how to make a smooth rotation, I just connected TweenService.

Problems (something I’ve been trying to fix, but haven’t figured out how)

  1. the object does not go to a point as in v2, but simply moves forward all the time, and when it touches a new part, repeats a turn, seems like an ideal system, but over time, the object due to inaccuracy of rotation (During the turn the object is moving, and before the turn is completed, the model has time to move slightly away from the original desired position), will get out of line and just fly away to infinity.

  2. as in v2, there is no speed control, but I think this problem is clearly easier to solve than previous one

  3. I don’t really understand how it works, but the character won’t move with the object, because there’s no physics, and everything moves through the CFrame.

In v2, the model moves with the TweenService, CFrame only for the calculated points where to move.

Problems

  1. When rotating the model shifts a bit, the solution would probably be to create more intermediate points, or to reduce the size of the part

  2. Unlike v1, I can’t even imagine how to control the speed here, look like you can’t change it here. + all parts must be the same size (and at the same distance) otherwise the speed will constantly change (longer = faster, shorter = slower)

  3. is the same as 3 in v1.

I think v1 is better than v2, even though it is more complicated, because if I solve the first problem, speed control will not be an issue (do a smooth increase/decrease)

And so, I have a request, who really knows, tell me how to solve the first problem in v1, or maybe better to use v2? Or somehow combine these two versions to make v3, where everything will work very well. I dug a lot of information, thought for a long time, what and how you can do, but realized that it is not in my power, so I turn to connoisseurs.

I am not interested in moving slides by setting the speed (using the physical engine roblox) because there is a chance that the cars will just fly off the slide. After that they need to be restarted, and other problems, those who are good at it know what I mean. That’s why I decided to try to do a roller coaster through CFrame, I didn’t think it would be that difficult.

I saw how to creation Frame+physics, but I do not even know how to do it (I tried to do it, but it did not work)

1 Like

I was able to do the acceleration:

What I added
-- Acceleration
task.delay(5,function()
	for i =1,100 do
		info = TweenInfo.new(
			x-1*i/200,
			Enum.EasingStyle.Linear,
			Enum.EasingDirection.In
		)
		task.wait(0.05)
	end
end)
Full script
local mod = workspace.Model_V2
local x = 1

local qtween = game:GetService("TweenService")
local info = TweenInfo.new(
	x, -- Time to move
	Enum.EasingStyle.Linear,
	Enum.EasingDirection.In
)

-- box colision
local ovr = OverlapParams.new()
ovr.MaxParts = 1
ovr.CollisionGroup = "no"
local col = mod.Center
local CFrame_col
local size_col = col.Size
local box -- list

local CF= Instance.new("CFrameValue")
CF.Value = mod.Center.CFrame

task.wait(4)

-- Acceleration
task.delay(5,function()
	for i =1,100 do
		info = TweenInfo.new(
			x-1*i/200,
			Enum.EasingStyle.Linear,
			Enum.EasingDirection.In
		)
		task.wait(0.05)
	end
end)

while true do
	task.wait()
	CFrame_col = col.CFrame
	box = workspace:GetPartBoundsInBox(CFrame_col,size_col,ovr)
	if #box == 1 then
		box[1].CollisionGroupId = 1
		local q = box[1]
		print(info)
		task.delay(1.1,function () q.CollisionGroupId = 0 end)
		CF.Changed:Connect(function(F) 
			mod:PivotTo(F)
		end)
		tween = qtween:Create(CF, info, {Value = box[1].CFrame*CFrame.new(3,0,0)})
		tween:Play()
	end
end

In fact, the smoothness of the depends on the number of parts, when a new part is detected, a new Tween is created, which takes the time value that is specified, and the time in turn is gradually reduced over a period of 5 seconds. So when a new part is detected, the “info” is read again, and thus the speed is increased.

After that, I decided to make a turn separately (I disabled acceleration so as not to interfere), but because of the way mod:PivotTo, to do normally not work, because there is no way to change the position and turn separately.

Script
local mod = workspace.Model_V2
local x = 2

local qtween = game:GetService("TweenService")
local info =  TweenInfo.new(
	x, -- Time to move
	Enum.EasingStyle.Linear,
	Enum.EasingDirection.In
)
local info2 =  TweenInfo.new(
	x/8, -- Time for turning
	Enum.EasingStyle.Linear,
	Enum.EasingDirection.In
)

-- box colision
local ovr = OverlapParams.new()
ovr.MaxParts = 1
ovr.CollisionGroup = "no"
local col = mod.Center
local CFrame_col
local size_col = col.Size
local box -- list

local CF= Instance.new("CFrameValue")
CF.Value = mod.Center.CFrame
local CF2= Instance.new("CFrameValue")
CF2.Value = mod.Center.CFrame

task.wait(4)

--Acceleration
--task.delay(5,function()
--	for i =1,100 do
--		info =  TweenInfo.new(
--			x-1*i/200,
--			Enum.EasingStyle.Linear,
--			Enum.EasingDirection.In
--		)
--	task.wait(0.05)
--	end
--end)

while true do
	task.wait()
	CFrame_col = col.CFrame
	box = workspace:GetPartBoundsInBox(CFrame_col,size_col,ovr)
	if #box == 1 then
		box[1].CollisionGroupId = 1
		local q = box[1]
		--print(info)
		task.delay(1.1,function () q.CollisionGroupId = 0 end)
		CF.Changed:Connect(function(F) 
			mod:PivotTo(CFrame.new(F.Position) * mod.WorldPivot.Rotation)
		end)
		CF2.Changed:Connect(function(F) 
			mod:PivotTo((CFrame.new(mod.WorldPivot.Position)*F.Rotation))
		end)
		tween = qtween:Create(CF, info, {Value = box[1].CFrame*CFrame.new(3,0,0)})
		tween2 = qtween:Create(CF2, info2, {Value = box[1].CFrame.Rotation})
		tween2:Play()
		tween:Play()
	end
end

So I decided to move not model, but part, because there is an opportunity to change the position and rotation separately, but I ran into a bug in the rotation: If changes “Rotation”, there are a lot of bugs, and if change “Orientation”, most disappear, but not completely.

Script
local mod = workspace.Center
local x = 1
local d = CFrame.new()
local qtween = game:GetService("TweenService")
local info =  TweenInfo.new(
	x, -- Time to move
	Enum.EasingStyle.Linear,
	Enum.EasingDirection.In
)
local info2 =  TweenInfo.new(
	x/4, -- Time for turning
	Enum.EasingStyle.Linear,
	Enum.EasingDirection.In
)

-- box colision
local ovr = OverlapParams.new()
ovr.MaxParts = 1
ovr.CollisionGroup = "no"
local col = mod
local CFrame_col
local size_col = col.Size
local box -- list

task.wait(4)

-- Acceleration
task.delay(5,function()
	for i =1,100 do
		info =  TweenInfo.new(
			x-1*i/200,
			Enum.EasingStyle.Linear,
			Enum.EasingDirection.In
		)
	task.wait(0.05)
	end
end)

while true do
	task.wait()
	CFrame_col = col.CFrame
	box = workspace:GetPartBoundsInBox(CFrame_col,size_col,ovr)
	if #box == 1 then
		box[1].CollisionGroupId = 1
		local q = box[1]
		task.delay(1.1,function () q.CollisionGroupId = 0 end)
		d = box[1].CFrame*CFrame.new(3,0,0)
		tween = qtween:Create(mod, info, {Position = d.Position})
		tween2 = qtween:Create(mod, info2, {Orientation = box[1].Orientation})
		print(box[1].Orientation)
		tween2:Play()
		tween:Play()
	end
end

In total: in the model is not possible to make a separate turn and movement, and in part some bugs with the turn.

Hey, this helped me a lot so here u go!

~ Roblox CFRAME Roller Coaster TUTORIAL ~ (Roblox Studio) - YouTube

After spending a day studying the script, here’s what I can say:

The movement is the same as mine - there is a center piece that moves from center to center

In fact, the system from center to center is probably used by all who have created a roller coaster, as to create another, more accurate, will not be easy (probably) and no reason, since this has proven itself very well and more than once

Only in my script there is a fixed time in which the part has to go from center to center.
That is, the length of the parts of the path and the distance from each other affect the speed. (as Tween is used to move)

The script in the video has a more complex but better implementation. (teleportation by points)

Parts of the track are only needed as approximate points to move, the actual ones are created by the script, and are located at the same distance from each other.

in video creates red parts that show where the script creates points where the train moves, and they are all at the same distance from each other. (Only a fraction of the points are shown, there are more, and you can tell by the smoothness of the movement)

I was trying to figure out how it was implemented, but it’s too complicated for me.

Result:
The script is very well done.

There is full speed control, and not depending on the size/position of parts of the track speed will not change

But the script uses outdated methods (such as “wait” and others), that is, it would be nice to update the script, because apparently it was written a long time ago.

Train moves locally, that is, on the server train as it stood somewhere on the ground, and stands.

The server receives the minimum amount of data needed to synchronize between different players. (so that the train is in the same place for all)

And this is a huge plus, because it is a very good idea in terms of optimization, the load on the server is almost zero, but the train is moving.

But in my game, the train must also move and on the server
In this respect, my game is like an exception.

Since my version of the script suits me completely, and it works well:


I have no particular desire to spend hours of time to understand this complex script, and how the movement was implemented(Evenly spaced dots)

If I have the desire and time, I may be able to figure it out and post here a script where I will explain what works and how it works, and what is responsible for.
No extras, purely a move-only script (and no obsolete methods).

Or maybe someone will do it for me? :blush: Or maybe someone has already done it, but I don’t know…

1 Like