Road and moving scenery syncing difficulties

Hello everyone! :wave::slightly_smiling_face:
I’m trying to create a moving road and moving scenery (to create the effect that the car is moving), but while scripting the syncing for the road and the scenery, I’ve run into some problems.

The first problem is that there is a slight difference in the time the road tween takes and the time the scenery tween takes. The difference is most noticeable when I change the speed of the vehicle, which is supposed to make the road and scenery pass by slower. I used a certain formula to calculate the tween time by distance and speed, so perhaps there might be an error in that.

The second problem (not really a problem, at least for the scripting) is that I suck at math, while this is just a case of math.

Here are the scripts for reference:
Road Tween Script (doesn’t have any issues, but is still important to show)

local TweenService = game:GetService("TweenService")
local ServerStorage = game:GetService("ServerStorage")
local EventResources = ServerStorage:WaitForChild("EventResources")
local ServerInfo = workspace:WaitForChild("ServerInfo")

local road = script.Parent.RoadModel
local hitboxes = road.Hitboxes

local roadRoot = road.PrimaryPart

local roadTouchedConnection
local grassTouchedConnection1
local grassTouchedConnection2

local moveTween

local function resetRoad()
	local newTweenTime = ((roadRoot.Size.Z / 10) / (ServerInfo.VehicleSpeed.Value / 5))
	local moveInfo = TweenInfo.new(newTweenTime, Enum.EasingStyle.Linear)
	
	moveTween = TweenService:Create(roadRoot, moveInfo, {CFrame = script.Parent.End.CFrame})

	moveTween:Play()

	moveTween.Completed:Wait()

	road:SetPrimaryPartCFrame(script.Parent.Start.CFrame)

end

while task.wait() do
	resetRoad()
end

Scenery Tween Script

local TweenService = game:GetService("TweenService")
local ServerStorage = game:GetService("ServerStorage")
local RoadObjects = ServerStorage:WaitForChild("RoadObjects"):GetChildren()
local ServerInfo = workspace:WaitForChild("ServerInfo")

local road = script.Parent.RoadModel

local roadZSize = 2400
local objectsSpawned = 0
local lastSpawnedObject = nil

local moveTweens = {}


function getTimeByDistance(distance, speed)
	return (distance / (5 / 4)) / (speed / 5)
end

local function newObject()
	local newTweenTime = ((roadZSize / (5 / 4)) / (ServerInfo.VehicleSpeed.Value / 5))
	local moveInfo = TweenInfo.new(newTweenTime, Enum.EasingStyle.Linear)
	local newObject = RoadObjects[math.random(1, #RoadObjects)]:Clone()
	
	objectsSpawned += 1
	lastSpawnedObject = newObject
	
	newObject.Name = newObject.Name .. objectsSpawned
	
	newObject.Parent = road
	
	moveTweens[newObject.Name] = TweenService:Create(newObject.PrimaryPart, moveInfo, {CFrame = script.Parent.End.CFrame})

	moveTweens[newObject.Name]:Play()

	repeat
		task.wait(1)
	until newObject.PrimaryPart.Position == script.Parent.End.Position
	
	newObject:Destroy()
end

ServerInfo.VehicleSpeed.Changed:Connect(function(value)
	for i, object in road:GetChildren() do
		local lastCFrame = object.PrimaryPart.CFrame
		moveTweens[object.Name]:Cancel()
		object:PivotTo(lastCFrame)
		
		local realObjectPositionZ
		if object.PrimaryPart.Position.Z > 0 then
			realObjectPositionZ = object.PrimaryPart.Position.Z
		elseif object.PrimaryPart.Position.Z < 0 then
			realObjectPositionZ = object.PrimaryPart.Position.Z * -1
		end
		
		local differenceRealAndZero
		local differenceEndAndZero
		
		local newMoveInfo = TweenInfo.new(getTimeByDistance(roadZSize - ((realObjectPositionZ + script.Parent.End.Position.Z) / script.Parent.End.Position.Z), ServerInfo.VehicleSpeed.Value), Enum.EasingStyle.Linear)
		moveTweens[object.Name] = TweenService:Create(object.PrimaryPart, newMoveInfo, {CFrame = script.Parent.End.CFrame })
		moveTweens[object.Name]:Play()
	end
end)

task.wait(1)

task.spawn(function()
	while true do
		if ServerInfo.VehicleSpeed.Value > 0 then
			task.spawn(newObject)
		end
		
		local lastSpawnedObjectRealPositionZ
		
		if lastSpawnedObject then
			if lastSpawnedObject.PrimaryPart.Position.Z > 0 then
				lastSpawnedObjectRealPositionZ = lastSpawnedObject.PrimaryPart.Position.Z
			elseif lastSpawnedObject.PrimaryPart.Position.Z < 0 then
				lastSpawnedObjectRealPositionZ = lastSpawnedObject.PrimaryPart.Position.Z * -1
			end
			
			repeat
				task.wait()
			until (roadZSize - ((lastSpawnedObjectRealPositionZ + (script.Parent.Start.Position.Z * -1)) / (script.Parent.Start.Position.Z * -1))) > lastSpawnedObject.PrimaryPart.Size.Z
		end
		task.wait(10)
	end
end)

Video showcasing the problem
During the video, you might notice the scenery passing by faster due to me adjusting the vehicle speed.
It’s hardly noticeable, but you can see it the most when the scenery has crossed the center of the map (where the camper van is)

I don’t know how to do that math but I tried grouping everything under a single model and interpolating the model

This is what I got, all the parts are anchored, I don’t know if this is perforance intensive. I imagine it wouldn’t be.

model_tween_example.rbxl (113.1 KB)

Thank you for your reply, but sadly I want the final script to take a random scenery object to tween along the road, or else the scenery would be repetitive.

Parent random objects to the model as it’s moving?

Idk maybe someone else has a better answer.

No, more like there is a new model which contains all the scenery, and tween every spawned scenery object apart from each other, with the same speed as the road.

Yeah but I’m saying just put all the scenery and all the road and everything into one model and interpolate. You can make a function to make randomized “road segments” with different scenery on the side. Then just interpolate the whole thing at once. It’ll be easier imo.

1 Like

I tried your suggestion, but now I have another issue where when I change the vehicle speed, the speed of the already spawned in road segments don’t have the same speed as the the road segments that spawn in after the vehicle speed was changed.

I somehow think that it has either to do something with the getTimeByDistance formula, or the repeat until line at the last while do loop.

This is the new script:

local TweenService = game:GetService("TweenService")
local ServerStorage = game:GetService("ServerStorage")
local EventResources = ServerStorage:WaitForChild("EventResources")
local ServerInfo = workspace:WaitForChild("ServerInfo")

local RoadObjectSegments = ServerStorage:WaitForChild("RoadSegments"):GetChildren()
local RoadSegments = script.Parent.RoadSegments

local roadZSize = 2400
local entireRoadSize = 4096 * 2
local objectsSpawned = 0
local lastSpawnedObject = nil
local deathEnabled = true

local moveTweens = {}
local touchedConnections = {}


function getTimeByDistance(distance)
	return (distance / (5 / 20)) / (ServerInfo.VehicleSpeed.Value / 5)
end

local function newRoadSegment()
	local randomRoadSegment = RoadObjectSegments[math.random(1, #RoadObjectSegments)]:Clone()
	local newTweenTime = ((entireRoadSize / (5 / 20)) / ServerInfo.VehicleSpeed.Value / 5)
	local moveInfo = TweenInfo.new(newTweenTime, Enum.EasingStyle.Linear)
	
	objectsSpawned += 1
	
	randomRoadSegment.Name = randomRoadSegment.Name .. objectsSpawned
	randomRoadSegment:PivotTo(script.Parent.Start.CFrame)
	randomRoadSegment.Parent = RoadSegments
	moveTweens[randomRoadSegment.Name] = TweenService:Create(randomRoadSegment.PrimaryPart, moveInfo, {CFrame = script.Parent.End.CFrame})
	moveTweens[randomRoadSegment.Name]:Play()
	
	if deathEnabled == true then
		for i, object in randomRoadSegment:GetDescendants() do
			if object:IsA("BasePart") and object:GetAttribute("DeadOnTouch") then
				table.insert(touchedConnections, object.Touched:Connect(deadHit))
			end
		end
	end

	repeat
		task.wait(1)
	until randomRoadSegment.PrimaryPart.Position == script.Parent.End.Position
	
	randomRoadSegment:Destroy()
end

function deadHit(otherPart: Instance)
	if otherPart.Parent:FindFirstChild("Humanoid") and otherPart.Parent.Humanoid.Health ~= 0 and not otherPart.Parent:GetAttribute("HitDelay") then
		otherPart.Parent:SetAttribute("HitDelay", true)

		otherPart.Parent.Humanoid:TakeDamage(100)

		local force = Instance.new("BodyForce")
		force.Force = Vector3.new(0, ServerInfo.VehicleSpeed.Value * 2.5, ServerInfo.VehicleSpeed.Value * 10)
		force.Parent = otherPart.Parent:FindFirstChild("LowerTorso")

	end
end

local function setDead(value: boolean)
	if value == true then
		deathEnabled = true
		for i, roadSegment in RoadSegments:GetChildren() do
			for i, object in roadSegment:GetDescendants() do
				if object:IsA("BasePart") and object:GetAttribute("DeadOnTouch") then
					table.insert(touchedConnections, object.Touched:Connect(deadHit))
				end
			end
		end
	elseif value == false then
		deathEnabled = false
		for i, connection in touchedConnections do
			connection:Disconnect()
			connection = nil
		end
	end
end

setDead(true)

ServerInfo.VehicleSpeed.Changed:Connect(function(value)
	for i, object in RoadSegments:GetChildren() do
		local lastCFrame = object.PrimaryPart.CFrame
		moveTweens[object.Name]:Cancel()
		object:PivotTo(lastCFrame)

		local realObjectPositionZ
		if object.PrimaryPart.Position.Z > 0 then
			realObjectPositionZ = object.PrimaryPart.Position.Z
		elseif object.PrimaryPart.Position.Z < 0 then
			realObjectPositionZ = object.PrimaryPart.Position.Z * -1
		end

		local differenceRealAndZero
		local differenceEndAndZero

		local newMoveInfo = TweenInfo.new(getTimeByDistance(roadZSize - ((realObjectPositionZ + script.Parent.End.Position.Z) / script.Parent.End.Position.Z)), Enum.EasingStyle.Linear)
		moveTweens[object.Name] = TweenService:Create(object.PrimaryPart, newMoveInfo, {CFrame = script.Parent.End.CFrame })
		moveTweens[object.Name]:Play()
	end
	if value == 0 then
		workspace:WaitForChild("Sounds"):WaitForChild("HighwayAmbience").VolumeValue.Value = 0
		local clone = EventResources:WaitForChild("NotMovingRoad"):Clone()
		clone.Parent = script.Parent
		setDead(false)
	else
		workspace:WaitForChild("Sounds"):WaitForChild("HighwayAmbience").VolumeValue.Value = ServerInfo.VehicleSpeed.Value / 500
		if script.Parent:FindFirstChild("NotMovingRoad") then
			script.Parent.NotMovingRoad:Destroy()
		end
		setDead(true)
	end
end)

while task.wait() do
	if ServerInfo.VehicleSpeed.Value > 0 then
		task.spawn(newRoadSegment)
	end

	local lastSpawnedObjectRealPositionZ

	if lastSpawnedObject then
		if lastSpawnedObject.PrimaryPart.Position.Z > 0 then
			lastSpawnedObjectRealPositionZ = lastSpawnedObject.PrimaryPart.Position.Z
		elseif lastSpawnedObject.PrimaryPart.Position.Z < 0 then
			lastSpawnedObjectRealPositionZ = lastSpawnedObject.PrimaryPart.Position.Z * -1
		end

		repeat
			task.wait()
		until (roadZSize - ((lastSpawnedObjectRealPositionZ + (script.Parent.Start.Position.Z * -1)) / (script.Parent.Start.Position.Z * -1))) > roadZSize
	end
	task.wait(1)
end

Luckily I found a way (by moving the entire road to the positive Z axis) to get the newly spawned road segments sync up with the already spawned road segments, but I still got an issue with how quickly the segments are spawning in. It’s supposed to spawn in a new segment every time the previous one has passed its Z axis size, but instead it spawns segments shortly after each other which makes them overlap. I think it has something to do with this part.

while task.wait() do
	if ServerInfo.VehicleSpeed.Value > 0 then
		task.spawn(newRoadSegment)
	end

	if lastSpawnedObject then
		repeat
			task.wait()
		until (lastSpawnedObject.PrimaryPart.Position.Z) < roadZSize
	end
	task.wait(1)
end

Especially this line: (lastSpawnedObject.PrimaryPart.Position.Z) < roadZSize

Bump

Ignore dis spoiler please

Welp I guess math just isn’t my strongest subject.
Got the system finally working by changing the line where I expected the error was, to this:

lastSpawnedObject.PrimaryPart.Position.Z - 20 <= entireRoadSize - roadZSize

The - 20 is for the possible gaps between the segments.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.