StreamTween - Handle Tweens on Client

Hello everyone!

I’ve been working on this module for a couple hours to see how much I could get done, and this is the end result.
This module allows you to tween objects on the client, from the server!
Tweening objects on the server is usually a practice that most people do without thinking much about, which is actually pretty bad! Tweening doesn’t have much a lot of strain on the server, but once you begin tweening a lot of objects at once, things get messy.

So, what would the solution be? Let the client handle it!
As some people say, the client can “take a beating” when it comes to performance, unlike the server. This allows you to run some stuff that could usually slow down the server by a bit, but would barely affect the client! Obviously, the client would have some delay when it comes to sending stuff from server to client. I go about this by calculating how long it took, and just adding that to the start value, syncing it as much as possible with the server.

Okay Pyseph, but what about all the other modules that have been released, doing the exact same thing?
Well, the main reason I’ve worked on this is due to the fact that they haven’t been updated for quite a while, and are usually overcomplicated in the source. This module doesn’t have as many functionalities as them (yet :tm:) such as Pause(), Stop() etc, but I plan to add them in the future if you guys really want it. This does support properties like TweenInfo.RepeatCount, TweenInfo.Reverses, TweenInfo.DelayTime etc!

I’ve made this module as easy to use as possible for beginners - if you don’t understand anything or have questions, feel free to ask!

Documentation

*/ StreamTween, by PysephDEV

# Documentation

Module:CreateTween(Object, TweenInformation, TweenData)
	# Description
		Creates a new StreamTween object.
	# Return
		StreamTween
		
StreamTween:PlayAllClients()
	# Description
		Plays the tween on all clients

StreamTween:PlayClient(Player)
	# Description
		Plays tween only for specific player

Example script

local StreamTween = require(game:GetService('ServerStorage').StreamTween)
local ExampleTween = StreamTween:CreateTween(workspace.Part, TweenInfo.new(3), {
	Size = Vector3.new(10, 10, 10)
})

ExampleTween:PlayAllClients()

Setup

To set this up, you need to add one localscript and a RemoteEvent.
The ModuleScript you can put anywhere, but make sure to set the Remote ObjectValue inside it to the remote object it’s going to use. The remote has to be accessible by both the client and the server, so make sure it’s not in ServerStorage for instance, where the client can’t access it.
Both the scripts automatically let you know if you haven’t set them :tongue:

Once setup, you’re free to go! use the module in any script you want.

Source

ModuleScript
--[[
*/ StreamTween, by PysephDEV

# Documentation

Module:CreateTween(Object, TweenInformation, TweenData)
	# Description
		Creates a new StreamTween object.
	# Return
		StreamTween
		
StreamTween:PlayAllClients()
	# Description
		Plays the tween on all clients

StreamTween:PlayClient(Player)
	# Description
		Plays tween only for specific player

]]--

local Network = {}
local StreamTween = {}

local ReplicatedStorage = game:GetService('ReplicatedStorage')
local Players = game:GetService('Players')

local StreamRemote = script.Remote.Value
assert(StreamRemote and StreamRemote.ClassName == 'RemoteEvent', 'No remote has been set for StreamTween! have you made sure to set the Remote ObjectValue to a RemoteEvent?')

StreamTween.__index = StreamTween
function StreamTween:PlayAllClients()
	StreamRemote:FireAllClients(os.time(), self.Instance, self.TweenInfo, self.Properties)
end
function StreamTween:PlayClient(Client)
	StreamRemote:FireClient(Client, os.time(), self.Instance, self.TweenInfo, self.Properties)
end
function Network:CreateTween(Object, TweenInformation, TweenData)
	local Tween = setmetatable({
		Instance = Object,
		TweenInfo = tostring(TweenInformation),
		Properties = TweenData
	}, StreamTween)


	return Tween
end

return Network
LocalScript
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local TweenService = game:GetService('TweenService')
local RunService = game:GetService('RunService')

local Stepped = RunService.Stepped

function LerpObj(a, b, t)
	local Class = type(a)
	if a == 'bool' then
		return true
	elseif a == 'number' then
		return a * (1-t) + b * t
	elseif a.Lerp then
		return a:Lerp(b, t)
	end
end

local Order = {
	Time = 1,
	EasingStyle = 2,
	EasingDirection = 3,
	RepeatCount = 4,
	Reverses = 5,
	DelayTime = 6
}


local SerializedCases = setmetatable({
	False = false,
	True = true
}, {
	__index = function(self, key)
		return tonumber(key)
	end
})

for _, EnumHolder in next, {Enum.EasingStyle, Enum.EasingDirection} do
	for _, EnumObj in next, EnumHolder:GetEnumItems() do
		SerializedCases[EnumObj.Name] = EnumObj
	end
end

function DecodeTween(SerializedInfo)
	local Struct = {}
	for Name, Idx in next, Order do
		local DeserializedVal = SerializedCases[string.match(SerializedInfo, Name .. ':(%S+)')]
		
		Struct[Idx] = DeserializedVal
	end

return TweenInfo.new(unpack(Struct))
end

local StreamRemote = script.Remote.Value
assert(StreamRemote and StreamRemote.ClassName == 'RemoteEvent', 'No remote has been set for StreamTween! have you made sure to set the Remote ObjectValue to a RemoteEvent?')
StreamRemote.OnClientEvent:Connect(function(ServerSync, Object, TweenInformation, Properties)
	TweenInformation = DecodeTween(TweenInformation)
	local Delay = os.time() - ServerSync
	local UselessVar

	local Time = TweenInformation.Time
	local EasingStyle = TweenInformation.EasingStyle
	local EasingDir = TweenInformation.EasingDirection
	
	local Alpha = TweenService:GetValue(Delay / Time, EasingStyle, EasingDir)

	local StartValues = {}
	for PropName in next, Properties do
		StartValues[PropName] = Object[PropName]
	end
	
	local function TweenObj(InReverse)
		Alpha = TweenService:GetValue(Delay / Time, EasingStyle, EasingDir)

		for PropName, PropVal in next, Properties do
			Object[PropName] = LerpObj(StartValues[PropName], PropVal, Alpha)
		end
	end

	for i = 1 * math.sign(TweenInformation.RepeatCount), TweenInformation.RepeatCount do
		while Alpha < 1 do
			TweenObj()
			local Time, NewDelay = Stepped:Wait()
			Delay += NewDelay
		end
		if TweenInformation.Reverses then
			Alpha = 1
			while Alpha > 0 do
				TweenObj(true)
				local Time, NewDelay = Stepped:Wait()
				Delay -= NewDelay
			end
		end

		Alpha = 0
		if TweenInformation.DelayTime then
			wait(TweenInformation.DelayTime)
		end
	end
end)

ClientTween.rbxl (24.1 KB)

17 Likes

Made this post at 03:30 AM, sorry for any typos I’ve made…
Correct me if you find any!

Only found one “typo” unless I’m being dumb

Much a lot of…confusion Not sure if this didn’t make sense to just me or is a smart people language that I’m too dumb to understand.

also i has questions bc im dumb :stuck_out_tongue:
Also I’ve seen documentation on quite a few posts, would the documentation be the thing listing all of its functions, properties, etc.?

Would the ExampleTween be set up like a normal tween where you put the part you’re tweening, the TweenInfo, and then the Goals?

Sorry I’m not a programmer so I’m still learning

This requires some basic knowledge on tweening and network replication, so it’d be hard to explain this for those that aren’t programmers.

I mean I know how to tween a part but I’ll go learn about network replication.

Edit: By “learn” I mean bother my dad, computer teacher, or watch a video and get confused