How are trending simulator games developing the "conveyor belt" system?

Hi,

My goal is to make a straight conveyor belt which spawns a model and moves the model along the belt as smooth as possible from point A to point B. The idea would be to do this on the server or to where it is visible by all players in the server.

As far as I am aware, there are multiple ways of moving a part from point A to point B smoothly (tweens, lerping, heartbeats, etc.), but what are most games doing?

Currently, I am doing it with a heartbeat and just modifying the CFrame on every iteration, but this does not look good and isn’t matching my game’s frame rate smoothly when testing in a public server. Here is what I currently have:

task.spawn(function()
	ConnectionModule:Connect(boxClone, RunService.Heartbeat, function(deltaTime: number)
		local primaryPart: Part = boxClone.PrimaryPart
		primaryPart.CFrame = primaryPart.CFrame + start.CFrame.LookVector * self.ConveyorSpeed * deltaTime
	end)
end)

Any insight is greatly appreciated, thanks!

1 Like

Another and reliable method is to anchor a part and constantly set it velocity to the direction you want the parts on it to be moving, something like this:


while wait() do
	script.Parent.Velocity = Vector3.new(100,0,0)
end

This makes the parts on it go up in x, and works just like a conveyor - just tried the method, it works.

Oh, really? That simple? I’ll give it a try, thanks!

Also, just to add an update to the things I’ve tried, I just tried tweening (on the server) with no luck, it still doesn’t match the frame rate in a normal server.

You might want to try PreRender in local scripts for smooth movement.

This worked! Though, would this be considered using physics? I was made aware that it could potentially become a performance issue when making use of Roblox’s physics for something like this.

If it were you, how would you set it up so that it is still global (viewable by all players)?

each client runs the same local script, if time runs with the same speed for all clients they will observe the same. Sorry, if i missed something.

As far as I know it is optomized, and doesn’t have too much perfomance issues, but you should deftinely use angular velocity(velocity is deceparated), and only make converyors that are being used have high velocity, but this can be managed from one script on the server, and is great when lots of converyors

Oh, I see what you mean. This might be a potential solution? I’ll have to try it.

I see. Thank you for your help!

Hey, so I came across PreRender only once before, and I’ve wondered when people would use it over other alternatives

Anything that is animation related should be done on the client. If you do it on the server, inconsistent ping means the client will still have to do some interpolation to smooth out what it receives from the server

The function you are running on the server would be perfectly smooth if it was running on the client (and perhaps if using PreRender)

It is basically @af_2048’s answer, although synchronizing the animation for every player is more complicated

A simple method is to synchronize the time on every client, (can be done by using UTC unix time on every client, although from experience, a script that synchronize time with the server is better, as client’s clocks not are perfectly synced. I have a module that does that). Then, you make an animation that is dependant on time

For example

local UTC = TickModule:UTC()
local Duration = 10
local alpha = UTC/Duration % 1 -- Modulo 1 makes it so [number].[decimal] becomes 0.[decimal], for example, 4.3 % 1 = 0.3
Block.CFrame = StartCFrame:Lerp(EndCFrame, alpha)

And this would be ran every frame

Since every client would have the same UTC time, and the function gives the same position for the same time, every client is synced

This approach is simple, but it’s limited. If you want an animation can is altered by something, then you’d need a more active synchronization approach, which could be the server sending the position of the block every second, and the client does some interpolation to smooth it out, or the server can do the same, but with a ā€œtimeā€ value (or more like an animation time position), and the clients use that animation time position, but with interpolation, to do the animations

Why are you using not 1 but 2 deprecated methods in that..

Really it should look like this

local ConveyorSpeed = 50
while task.wait() do
    script.Parent.AssemblyLinearVelocity = script.Parent.CFrame.LookVector * ConveyorSpeed
end

But even then, given a part is anchored, you don’t even need the while loop.

local ConveyorSpeed = 50
script.Parent.AssemblyLinearVelocity = script.Parent.CFrame.LookVector * ConveyorSpeed

Also yes, this uses physics which is not what OP wants.

Anyway @swyftey, something you could try is simulating the items point on the server but rendering it on your client.

The client (one way or another) gets the position data for the dropped item, and the client creates and positions an object based on that. Having the client simply render it makes it as smooth as you’d like, and having the server calculate it means hackers can’t really do much about it.

Of course there’d be a little desync with ping, but for now that’s probably one of the better methods.
(and right as I write this out someone else says the same thing :sob:)

1 Like

What about using workspace:GetServerTimeNow()?

1 Like

That would probably work perfectly fine. It can throw and error if the client gets disconnected, which could be annoying (why does it need to throw an error? My guess would be if it was never able to get the server time), but if the client gets disconnected, well there are other issues I guess

I always forget that it exists

I agree with that. It seems that the only way to get it truly how I see it perform in other games is to do it on the client.

Here is what I ended up doing for anybody wondering:

-- Server Loop
while self._thread do
	local bases: Folder = game.Workspace:FindFirstChild("Bases");
	
    for _, base in bases:GetChildren() do
		local npcName: string = self:_getRandomNPCName()
		local npcConfig: NPCConfig = NPCData[npcName]
		local player: Player = Players:GetPlayerByUserId(base:GetAttribute("UserId"))
		
		dropBoxRemote:FireAllClients(player, npcName, npcConfig, self.ConveyorSpeed)
	end
	
	task.wait(BoxModuleServer.DropInterval)
end

-- Client Handling
ConnectionModule:Connect(dropBoxRemote, dropBoxRemote.OnClientEvent, function(forPlayer: Player, npcName: string, npcConfig: NPCConfig, speed: number)
	local base: Model = BaseModule:GetBase(forPlayer)
	local belt: Model = base:WaitForChild("ConveyorBelt")
	local endPart: Part = belt:WaitForChild("End")
	
	local box: Model = createBox(belt, npcName, npcConfig)
	
	local distance: number = (endPart.CFrame.Position - box.PrimaryPart.CFrame.Position).Magnitude
	local duration: number = distance / speed

	local cframeValue: CFrameValue = Instance.new("CFrameValue")
	cframeValue.Name = "TargetCFrame"
	cframeValue.Parent = box
	cframeValue.Value = box:GetPivot()
	
	local tween: TweenService = TweenService:Create(
		cframeValue, 
		TweenInfo.new(duration, Enum.EasingStyle.Linear),
		{ Value = endPart.CFrame }
	)
	
	ConnectionModule:Connect(cframeValue, cframeValue:GetPropertyChangedSignal("Value"), function()
		box:PivotTo(cframeValue.Value)
		return
	end)
	
	tween:Play()
	tween.Completed:Wait()
	box:Destroy()
	return
end)

TL;DR: Using a server to client remote event to handle the entire visuals, the ā€œmoney exchangeā€ is done on the server with a remote function (to delete the model only upon success) when the model’s proximity prompt is triggered.

Also, if anybody is curious, this is the game I was recently taking a look at for reference:

If anybody has any better methods, please do let me know.

2 Likes

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