Need To Know How To Move Tweens From Server Side To Client Side

I have a round based game in which we have to survive the obstacles, I have a module for tweens and the module is placed in “ServerStorage”. I’m currently running the tweens by a server script and the the code is

local ObstacleModule = require(ServerStorage:WaitForChild("ObstacleModule"))

local EventNames = {}

for EventName, Method in pairs(ObstacleModule) do
	if type(Method) == "function" then --make sure that this value is a function
		table.insert(EventNames, EventName)
	end
end

while true do
	local RandomEventName = EventNames[math.random(1, #EventNames)]
	local Event = ObstacleModule[RandomEventName]

	Event()
	task.wait(8)
end

The game is very laggy because the tweens are ran by the server. I want to move it to local script but I dont know how to do it, so please can anyone help me with it? Here is a video showcasing it. I want to make sure one thing is that I want same obstacle to run for all players.

I’m not able to upload the video but here is the link to the game.

1 Like

Create a RemoteEvent and name it TweenObject or something and put it in ReplicatedStorage.

In the server script, fire the remote event to all clients and pass the object and necessary info:

--Server

local tweenObjectRemote = game.ReplicatedStorage.TweenObject

tweenObjectRemote:FireAllClients(object, tweenInfo, goal)

And put a LocalScript inside of StarterPlayer → StarterPlayerScripts and put this in it:

--Client
local tweenService = game:GetService("TweenService")
local tweenObjectRemote = game.ReplicatedStorage.TweenObject

tweenObjectRemote.OnClientEvent:Connect(function(object, tweenInfo, goal)
	local tween = tweenService:Create(object, tweenInfo, goal)
	tween:Play()
end)

This will tell every player’s computer at the same time to play the tween, so it’ll be smooth for each and every one of them. I guess the problem that comes with this is that the object never actually moves on the server so you’d have to trust the clients not to lie about whether or not they hit the object.

1 Like

I don’t think this is going to work because there are multiple different obstacles like spinning wheel, meteor shower, hole in the wall etc and each one has multiple different tweens.

1 Like

Using Tween service on the server should not create more lag. You’re probably doing something wrong, if anything in this situation You should use TweenService on the server in this situation, because it will replicate the positional and orientation changes to all clients without using events.

And show me the code you’re using to Tween these objects, that may be causing the lag, on top of your use of events.

THis is the code I wwant to chang

He’s probably talking about how the replication of the tween looks choppy because it’s being done by the server.

The code I provided is an example, but it outlines pretty much exactly what you should do to get the result you’re gunning for. You want the clients to perform the tweens themselves, no? It’s why I added the “object”, “tweenInfo”, and “goal” variables in the parentheses on the LocalScript, it allows you to send the client information from the server.

Here, I’ll even change your script so that it works with the LocalScript I provided.

local ObstacleModule = {}

local TweenService = game:GetService("TweenService")
local ObstacleFolder = game.Workspace.ObstaclesFolder

-- Obstacle Parts

-- Moving Kill Brick parts
local MovePart = ObstacleFolder.MoveKillBrick["Kill-Brick"]
local MovePart_EndPos = ObstacleFolder.MoveKillBrick.EndPos

-- Spinning Kill Brick parts
local SpinningKillBrick = ObstacleFolder.SpinningKillBrick["Kill-Brick"]
local SpinningPartSupport = ObstacleFolder.SpinningKillBrick.Support

-- Hole In The Wall parts
local HITW_MovePart = ObstacleFolder.HoleInTheWall.MovingPart
local HITW_EndPos = ObstacleFolder.HoleInTheWall.EndPos

-- Standing Platform parts
local StandingParts = game.Workspace.Pillars.StandingPart:GetChildren()

-- Meteorites
local Meteorite = ObstacleFolder.Meteorite.Meteorite
local Meteorite_Pos1 = ObstacleFolder.Meteorite.Pos_1.Position
local Meteorite_Pos2 = ObstacleFolder.Meteorite.Pos_2.Position
local Meteorite_CloneFolder = ObstacleFolder.Meteorite.CloneMeteorite

-- Tween Info

local MovingPartTweenInfo = TweenInfo.new(4, Enum.EasingStyle.Cubic, Enum.EasingDirection.InOut, 0, true)
local SpinningPartTweenInfo = TweenInfo.new(4, Enum.EasingStyle.Cubic, Enum.EasingDirection.InOut, 0, false)
local HITW_MovingPartTweenInfo = TweenInfo.new(8, Enum.EasingStyle.Cubic, Enum.EasingDirection.InOut, 0, true)
local StandingPartSizeTweenInfo = TweenInfo.new(2, Enum.EasingStyle.Cubic, Enum.EasingDirection.InOut, 0, true)
local StandingPartRotateTweenInfo = TweenInfo.new(2, Enum.EasingStyle.Cubic, Enum.EasingDirection.InOut, 0, true)
local StandingPartMoveTweenInfo = TweenInfo.new(4, Enum.EasingStyle.Cubic, Enum.EasingDirection.InOut, 0, true)
local StandingPartSpinTweenInfo = TweenInfo.new(1, Enum.EasingStyle.Cubic, Enum.EasingDirection.InOut, 0, true)

local PartSizeTweenInfo = TweenInfo.new(1, Enum.EasingStyle.Cubic,Enum.EasingDirection.InOut, 0, false)
local StandingPartColorTweenInfo = TweenInfo.new(1, Enum.EasingStyle.Cubic, Enum.EasingDirection.InOut, 0, true)


-- Properties
local MovingPartProp = {Position = MovePart_EndPos.Position}
local SpinningPartProp = {Orientation = Vector3.new(0, SpinningKillBrick.Orientation.Y + 180, 0)}
local HITW_MovingPartProp = {Position = HITW_EndPos.Position}

local MoveAndSpin_BigSizeProp = {Size = Vector3.new(1, 1, 100)}
local MoveAndSpin_SmallSizeProp = {Size = Vector3.new(1, 1, 1)}

local HITW_BigSizeProp = {Size = Vector3.new(4, 16, 100)}
local HITW_SmallSizeProp = {Size = Vector3.new(1, 1, 1)}

local StandingPartSmallSizeProp = {Size = Vector3.new(3, 1, 3)}
local StandingPartRotateProp = {Orientation = Vector3.new(0, 0, 60)}
local StandingPartSpinProp = { Orientation = Vector3.new(0, 360, 0) }
local StandingPartColorProp = { Color = Color3.new(0.101961, 0.101961, 0.101961)}

-- Tweens


-- Tweens Playing

local tweenObjectRemote = game.ReplicatedStorage.TweenObject

-- Reusable Functions
local function BigSize(partname, BigSizeProp)
	partname.Transparency = 0
	partname.CanTouch = true
	tweenObjectRemote:FireAllClients(partname, PartSizeTweenInfo, BigSizeProp)
end

local function SmallSize(partname, SmallSizeProp)
	tweenObjectRemote:FireAllClients(partname, PartSizeTweenInfo, SmallSizeProp)
	task.wait(1)
	partname.Transparency = 1
	partname.CanTouch = false
end

-- Tween Functions

function ObstacleModule:MovingPartTween()
	BigSize(MovePart, MoveAndSpin_BigSizeProp)
	task.wait(1)
	tweenObjectRemote:FireAllClients(MovePart, MovingPartTweenInfo, MovingPartProp)
	task.wait(8)
	SmallSize(MovePart, MoveAndSpin_SmallSizeProp)
end

function ObstacleModule:SpinningPartTween()
	BigSize(SpinningKillBrick, MoveAndSpin_BigSizeProp)
	SpinningPartSupport.Transparency = 0
	task.wait(1)
	tweenObjectRemote:FireAllClients(SpinningKillBrick, SpinningPartTweenInfo, SpinningPartProp)
	task.wait(4)
	SmallSize(SpinningKillBrick, MoveAndSpin_SmallSizeProp)
	SpinningPartSupport.Transparency = 1
end

function ObstacleModule:HoleInTheWallTween()
	BigSize(HITW_MovePart, HITW_BigSizeProp)
	task.wait(1)
	tweenObjectRemote:FireAllClients(HITW_MovePart, HITW_MovingPartTweenInfo, HITW_MovingPartProp)
	task.wait(16)
	SmallSize(HITW_MovePart, HITW_SmallSizeProp)
end

function ObstacleModule:StandingPartSize()
	for index, value in pairs(StandingParts) do
		task.spawn(function()
			-- Tweens
			tweenObjectRemote:FireAllClients(StandingParts[index], StandingPartColorTweenInfo, StandingPartColorProp)
			tweenObjectRemote:FireAllClients(StandingParts[index], StandingPartSizeTweenInfo, StandingPartSmallSizeProp, 1.5) -- add delay parameter to function on client
		end)
	end
end

function ObstacleModule:StandingPartRotate()
	for index, value in pairs(StandingParts) do
		task.spawn(function()
			-- Tweens
			tweenObjectRemote:FireAllClients(StandingParts[index], StandingPartColorTweenInfo, StandingPartColorProp)
			tweenObjectRemote:FireAllClients(StandingParts[index], StandingPartRotateTweenInfo, StandingPartRotateProp, 1.5)
		end)
	end
end

function ObstacleModule:StandingPartMove()
	for index, value in pairs(StandingParts) do
		task.spawn(function()
			-- Tweens
			tweenObjectRemote:FireAllClients(StandingParts[index], StandingPartColorTweenInfo, StandingPartColorProp)
			tweenObjectRemote:FireAllClients(StandingParts[index], StandingPartMoveTweenInfo, { Position = StandingParts[index].Position + Vector3.new(10, 0, 0) }, 1.5)
		end)
	end
end

function ObstacleModule:StandingPartSpinAndMove()
	for index, value in pairs(StandingParts) do
		task.spawn(function()
			-- Tweens
			tweenObjectRemote:FireAllClients(StandingParts[index], StandingPartColorTweenInfo, StandingPartColorProp)
			tweenObjectRemote:FireAllClients(StandingParts[index], StandingPartSpinTweenInfo, StandingPartSpinProp, 1)
			tweenObjectRemote:FireAllClients(StandingParts[index], StandingPartSizeTweenInfo, StandingPartSmallSizeProp)
		end)
	end
end

function ObstacleModule:Meteorite()
	for timer = 0, 15, 1 do
		local RandomX = math.random(Meteorite_Pos1.X, Meteorite_Pos2.X)
		local RandomZ = math.random(Meteorite_Pos1.Z, Meteorite_Pos2.Z)
		
		local MeteoriteClone = Meteorite:Clone()
		local Otherparts = MeteoriteClone:GetChildren()
		MeteoriteClone.Position = Vector3.new(RandomX, Meteorite_Pos1.Y, RandomZ)
		
		for index, parts in Otherparts do
			if parts:IsA("Part") then
				parts.Position = MeteoriteClone.Position
				parts.Anchored = false
				parts.CanCollide = false
				parts.Transparency = 0

				MeteoriteClone.Position = MeteoriteClone.Position
				MeteoriteClone.Anchored = false
				MeteoriteClone.CanCollide = false
				MeteoriteClone.Transparency = 0
				
				parts.Parent = MeteoriteClone
				MeteoriteClone.Parent = Meteorite_CloneFolder
			end
		end

		task.wait(0.5)
	end
end


return ObstacleModule

Also, change the LocalScript to this:

local tweenService = game:GetService("TweenService")
local tweenObjectRemote = game.ReplicatedStorage.TweenObject

tweenObjectRemote.OnClientEvent:Connect(function(object, tweenInfo, goal, waitTime)
	if (waitTime) then
		task.wait(waitTime)
	end
	local tween = tweenService:Create(object, tweenInfo, goal)
	tween:Play()
end)

It’ll allow you to wait before playing the tween by providing a fourth argument from the server.
This all should work just fine.

1 Like

So I just add this script in local scripts and add a remote event and it should work?

Server script to local script. The running thingy. Idk the name, but you have 3 options: legacy, classic and client. It’s a property

I’m not a good scripter or dev so please can you further explain

Now that im in studio i can explain:

change it to:
image

Try if it works

Yeah just change the server script to the top bit of code and create a LocalScript with the bottom bit of code, and yeah create a RemoteEvent in ReplicatedStorage called TweenObject and it should work fine.

You are using ServerStorage here, which restricts you to server scripts. If you wish to handle this from the client, you could use remotes to communicate between the server and the client.

Alternatively, you can use ReplicatedStorage and move the entire task to the client in one client script.

Option 1 … Full client. One script

Place this in a LocalScript in StarterPlayerScripts or StarterCharacterScripts and move your module to ReplicatedStorage.

local ObstacleModule = require(game:GetService("ReplicatedStorage"):WaitForChild("ObstacleModule"))
local EventNames = {}

for EventName, Method in pairs(ObstacleModule) do
	if type(Method) == "function" then
		table.insert(EventNames, EventName)
	end
end

while true do
	local RandomEventName = EventNames[math.random(1, #EventNames)]
	local Event = ObstacleModule[RandomEventName]
	Event()
	task.wait(8)
end

This way, all the logic runs on the client side, reducing server-side lag. This will run better without lag but is a bit open to getting hacked. Making this a trade-off between performance and security.

You will have to weigh the options here and decide if that matters.

Option 2 … 1/2 and 1/2. Two scripts.

Place this in a Script in ServerScriptService.

local ServerStorage = game:GetService("ServerStorage")
local ObstacleModule = require(ServerStorage:WaitForChild("ObstacleModule"))
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RemoteEvent = Instance.new("RemoteEvent")
RemoteEvent.Name = "ObstacleEvent"
RemoteEvent.Parent = ReplicatedStorage

local EventNames = {}

for EventName, Method in pairs(ObstacleModule) do
	if type(Method) == "function" then
		table.insert(EventNames, EventName)
	end
end

while true do
	local RandomEventName = EventNames[math.random(1, #EventNames)]
	RemoteEvent:FireAllClients(RandomEventName)
	task.wait(8)
end

Place this in a LocalScript in StarterPlayerScripts or StarterCharacterScripts.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ObstacleModule = require(game:GetService("ServerStorage"):WaitForChild("ObstacleModule"))
local RemoteEvent = ReplicatedStorage:WaitForChild("ObstacleEvent")

RemoteEvent.OnClientEvent:Connect(function(EventName)
	local Event = ObstacleModule[EventName]
	if Event then
		Event()
	end
end)

Good luck!

1 Like

Thanks, It worked. The reason I didnt put the module in replicated storage cuz i was scared of hackers

1 Like

I do that too sometimes. Case by case thing.