rek_kie

TweenService+ V1.1

Aug 9th, 2020 (edited)
1,693
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 16.34 KB | None | 0 0
  1. --[[
  2.  
  3. TweenService+ V1.1
  4. @rek_kie on 8/9/20
  5.  
  6. Please read the devforum post here!
  7. https://devforum.roblox.com/t/tweenservice-plus/716025
  8.  
  9. Update log [V1.1]:
  10.  
  11. :Play() now has a mainObject parameter
  12.  
  13.  -- You are now able to set the object to calculate distance from if you are using :Play() with a range. This is useful if
  14.     you are trying to tween something that isn't a BasePart, but you still need to calculate distance from something to tween
  15.     the object.
  16.  
  17. Setup instructions:
  18.  
  19.     Put this module into REPLICATEDSTORAGE, nowhere else.
  20.     To set up, all you have to do is require this module somewhere on a local script, and require it on a server script to be
  21.     ready for use on the server side.
  22.  
  23.  
  24. Documentation:
  25.  
  26. === FUNCTIONS ===
  27.  
  28. tweenServicePlus:Construct(instance Object, TweenInfo info, table Properties, number timeThreshold, bool debugMode, bool clientSync)
  29.  
  30.   Returns: tweenObject
  31.    
  32. - The "equivalent" to TweenService:Create(). The time threshold is the latency threshold if clientSync (latency compensation)
  33.   is enabled. The object is the object to tween, the TweenInfo is the info to use, the properties and are properties you
  34.   want to tween to.
  35.  
  36.  - debugMode defaults to false, and clientSync defaults to true.
  37.  
  38. tweenObject:Play(array/instance clients, number Range, string rootPart, BasePart mainObject)
  39.  
  40. - If clients are not specified, then the tween will play for all clients (the default setting). If you want to specify clients, either
  41.   pass in an ARRAY of clients, or pass in one individual client. These should be PLAYER INSTANCES.
  42.  
  43. - If a range (a NUMBER) is specified, then the tween will only play for clients (you can specify them as stated above) within the range
  44.   (in studs) of the object being tweened. EXTREMELY useful for optimization and reducing client load. You wouldn't need to tween
  45.   something for a client if they're somewhere like 1000 studs away from the object.
  46.  
  47. - rootPart: Only works when a range is specified. Defaults to HumanoidRootPart. If you specify this (has to be a string)
  48.   then the distance calculations will be from the distance from the tweened object to the specified rootPart. This uses a
  49.   recursive :FindFirstChild(), so make sure that your rootPart has a unique name so it isn't mistaken for something else in the
  50.   player.
  51.  
  52. -- mainObject: The object to calculate distance from if you are using :Play() with a range. Defaults to the original object you're trying to tween.
  53.    This is useful if you are trying to tween something that isn't a BasePart, but you still need to calculate distance from something
  54.    to tween the object. (Ex. You want to tween a pointlight and use a range of 50 studs, but a pointlight doesn't have a workspace
  55.    position. So, you can set the mainObject to a BasePart and then the range would be 50 studs within that part.)
  56.  
  57. tweenObject:Cancel(array/instance clients)
  58.  
  59. - This behaves like the normal TweenService. Documentation is on developer.roblox.com
  60.  
  61. - Cancels the tween. If clients are specified (either an ARRAY of player instances or one player instance), then the tween will only
  62.   cancel for the specified clients. Otherwise, it cancels for all clients.
  63.  
  64. - Always stays in sync with the server. If you cancel a tween and then call :Play() again, it will still have TweenService's normal
  65.   behavior and take the initial tween time to finish.
  66.  
  67. Tip: Cancelling for specific clients is not reccommended, though. It only has a few use cases and could lead to things getting
  68. out of sync between your clients and the server.
  69.  
  70. tweenObject:Pause(array/instance clients)
  71.  
  72. - This also behaves like the normal TweenService. Documentation is on developer.roblox.com
  73.  
  74. - Pauses the tween. If clients are specified (either an ARRAY of player instances or one player instance), then the tween will only
  75.   pause for the specified clients. Otherwise, it pauses for all clients. If a tween is paused while it wa
  76.  
  77. - Also always stays in sync with the server. If you pause a tween and then call :Play() again, it will still have TweenService's normal
  78.   behavior and resumes where the tween had left off when it was paused.
  79.  
  80. === EVENTS ===
  81.  
  82. - tweenObject.Cancelled -- Fires when a tween is cancelled.
  83. - tweenObject.Resumed -- Fires when a tween is played after being paused.
  84. - tweenObject.Paused -- Fires when a tween is paused.
  85. - tweenObject.Completed -- Fires when a tween is completed.
  86.  
  87. ]]--
  88.  
  89.  
  90. local tweenService = {}
  91.  
  92. local clock = require(script.SyncedTime)
  93.  
  94. local rs = game:GetService("RunService")
  95. local ts = game:GetService("TweenService")
  96. local http = game:GetService("HttpService")
  97.  
  98. local wrap = coroutine.wrap
  99.  
  100. local wait = function(n)
  101.     n = n or 1/30
  102.     local now = tick()
  103.     repeat rs.Heartbeat:Wait() until tick() - now >= n
  104. end
  105.  
  106. local tEvent = script:WaitForChild("TweenCommunication")
  107.  
  108. if rs:IsServer() then  
  109.     if not clock:IsSynced() then -- make sure our clock is synced
  110.         repeat
  111.             clock:Sync()
  112.             wait(.5)
  113.         until clock:IsSynced()
  114.     end
  115. end
  116.  
  117. local function infoToTable(tInfo)
  118.     local info = {}
  119.     info["Time"] = tInfo.Time or 1
  120.     info["EasingStyle"] = tInfo.EasingStyle or Enum.EasingStyle.Quad
  121.     info["EasingDirection"] = tInfo.EasingDirection or Enum.EasingDirection.Out
  122.     info["RepeatCount"] = tInfo.RepeatCount or 0
  123.     info["Reverses"] = tInfo.Reverses or false
  124.     info["DelayTime"] = tInfo.DelayTime or 0
  125.     return info
  126. end
  127.  
  128. local function assign(object, properties, debugMode)
  129.     if not object or not properties then return end
  130.    
  131.     for property, value in pairs(properties) do
  132.         object[property] = value
  133.        
  134.         if debugMode then
  135.             print("Set "..object.Name.. "'s "..property.." to ".. tostring(value)..".")
  136.         end
  137.     end
  138. end
  139.  
  140.  
  141. function tweenService:Construct(obj, info, properties, timeThreshold, debugMode, clientSync)
  142.    
  143.     if not obj then warn("This object doesn't exist.") return end
  144.     if not info then warn("Please provide some TweenInfo!") return end
  145.     if not properties then warn("Please provide some properties to tween to!") return end
  146.    
  147.     if timeThreshold and not type(timeThreshold) == "number" then warn("Latency threshold must be a number!") return end
  148.     if debugMode and not type(debugMode) == "boolean" then warn("The debugMode parameter must be true or false!") return end
  149.     if clientSync and not type(clientSync) == "boolean" then warn("The parameter clientSync must be true or false!") return end
  150.    
  151.     local startProperties
  152.    
  153.     if info.Reverses then
  154.         for property, value in pairs(properties) do
  155.             startProperties[property] = obj[property]
  156.         end
  157.     end
  158.    
  159.     debugMode = debugMode or false
  160.     clientSync = clientSync or true
  161.    
  162.     local events = {
  163.         ["Cancelled"] = true,
  164.         ["Completed"] = true,
  165.         ["Paused"] = true,
  166.         ["Resumed"] = true
  167.     }
  168.    
  169.     local tObject = {
  170.         ["PlaybackState"] = Enum.PlaybackState.Begin,
  171.         ["TweenId"] = http:GenerateGUID(false), -- so that we can identify each tween.
  172.         ["IsPaused"] = false,
  173.         ["IsCancelled"] = false,
  174.         ["LastPlay"] = clock:GetTime(),
  175.         ["TimeElapsed"] = 0
  176.     }
  177.    
  178.     local function changeState(state)
  179.         tObject.PlaybackState = state
  180.        
  181.         if debugMode then
  182.             print("Playback state changed. New playback state:", tostring(state))
  183.         end
  184.     end
  185.  
  186.     for name, event in pairs(events) do  -- Setting up the events to connect to
  187.         events[name] = Instance.new("BindableEvent")
  188.         tObject[name] = events[name].Event
  189.     end
  190.    
  191.     local tweenWait = function(n)
  192.         n = n or 1/30
  193.         local now = tick()
  194.         repeat
  195.             rs.Heartbeat:Wait()
  196.             if tObject.IsPaused == true or tObject.IsCancelled == true then
  197.                 if debugMode then
  198.                     print("Tween cancelled/paused server-side.")
  199.                 end
  200.                 return true
  201.             end
  202.         until tick() - now >= n
  203.     end
  204.    
  205.     local function completionWait(t)
  206.         local func = wrap(function()
  207.            
  208.             if tObject.IsPaused then
  209.                 t = t - tObject.TimeElapsed
  210.                
  211.                 if debugMode then
  212.                     print("Tween is resuming from a pause. Length:", t)
  213.                 end
  214.                
  215.                 events.Resumed:Fire()
  216.                 tObject.IsPaused = false
  217.             end
  218.            
  219.             if tObject.IsCancelled then
  220.                 tObject.IsCancelled = false
  221.             end
  222.            
  223.             if info.DelayTime > 0 then
  224.                 changeState(Enum.PlaybackState.Delayed)
  225.                 wait(info.DelayTime)
  226.             end
  227.                
  228.             tObject.LastPlay = clock:GetTime()
  229.            
  230.             changeState(Enum.PlaybackState.Playing)
  231.             local cancelled = tweenWait(t)
  232.            
  233.             print("Cancelled:", cancelled)
  234.            
  235.             if not cancelled then
  236.                 assign(obj, properties, debugMode)
  237.                
  238.                 if not info.Reverses then
  239.                     changeState(Enum.PlaybackState.Completed)
  240.                     events.Completed:Fire()
  241.                 elseif info.Reverses then
  242.                     wait(t)
  243.  
  244.                     if not tObject.IsPaused then
  245.                         assign(obj, startProperties, debugMode)
  246.                         changeState(Enum.PlaybackState.Completed)
  247.                         events.Completed:Fire()
  248.                     end
  249.                 end    
  250.             end
  251.            
  252.         end)
  253.        
  254.         func()
  255.     end
  256.    
  257.      
  258.    
  259.     function tObject:Play(clients, range, rootPart, mainObject)
  260.        
  261.         rootPart = rootPart or "HumanoidRootPart"
  262.        
  263.         if mainObject then
  264.             if not mainObject:IsA("BasePart") or not mainObject:IsDescendantOf(workspace) then warn("The mainObject must be a BasePart in the workspace.") return end
  265.         end
  266.            
  267.         mainObject = mainObject or obj
  268.        
  269.         if mainObject ~= obj and debugMode then
  270.             print("Set the main object to", mainObject.Name)
  271.         end
  272.        
  273.         if clients then
  274.            
  275.             clients = (type(clients) == "table") and clients or {clients} -- If you only provide a single player, it turns it into a table for you
  276.            
  277.             if range then
  278.                 for _, player in ipairs(clients) do
  279.                    
  280.                     if player and player:IsA("Player") and player.Character then
  281.                        
  282.                         local runTween = wrap(function()
  283.                             local root = player.Character:FindFirstChild(rootPart, true)
  284.  
  285.                             if root then
  286.                                
  287.                                 if debugMode then
  288.                                     print("Found root part of "..player.Name..":", rootPart)
  289.                                 end
  290.                                
  291.                                 local dist = (mainObject.Position - root.Position).magnitude
  292.                                
  293.                                 if dist <= range then  -- Tween it only for clients in the range
  294.                                     tEvent:FireClient(player, obj, infoToTable(info), properties, clock:GetTime(), tObject.TweenId, timeThreshold, debugMode, nil, clientSync) -- Tell the client to tween the object and the timestamp of when it was sent
  295.                                    
  296.                                     if debugMode then
  297.                                         print("Sent tween data to "..player.Name..". Distance from object:", dist)
  298.                                     end
  299.                                 end
  300.                             end
  301.                         end)
  302.                        
  303.                         runTween()
  304.                        
  305.                     end
  306.                 end
  307.             else   
  308.                
  309.                 for _, player in ipairs(clients) do
  310.                    
  311.                     if player and player:IsA("Player") and player.Character then
  312.                         local runTween = wrap(function()
  313.                             tEvent:FireClient(player, obj, infoToTable(info), properties, clock:GetTime(), tObject.TweenId, timeThreshold, debugMode, nil, clientSync)
  314.                         end)   
  315.                        
  316.                         runTween()
  317.                     end
  318.                    
  319.                 end
  320.            
  321.             end
  322.         else
  323.             if range then
  324.                 for _, player in ipairs(game.Players:GetPlayers()) do
  325.                    
  326.                     if player and player:IsA("Player") and player.Character then
  327.                        
  328.                         local runTween = wrap(function()
  329.                             local root = player.Character:FindFirstChild(rootPart, true)
  330.  
  331.                             if root then
  332.                                
  333.                                 if debugMode then
  334.                                     print("Found root part of "..player.Name..":", rootPart)
  335.                                 end
  336.                                
  337.                                 local dist = (mainObject.Position - root.Position).magnitude
  338.                                
  339.                                 if dist <= range then  -- Tween it only for clients in the range
  340.                                     tEvent:FireClient(player, obj, infoToTable(info), properties, clock:GetTime(), tObject.TweenId, timeThreshold, debugMode, nil, clientSync) -- Tell the client to tween the object and the timestamp of when it was sent
  341.                                    
  342.                                     if debugMode then
  343.                                         print("Sent tween data to "..player.Name..". Distance from object:", dist)
  344.                                     end
  345.                                 end
  346.                             end
  347.                         end)
  348.                        
  349.                         runTween()
  350.  
  351.                     end
  352.                    
  353.                 end
  354.             else
  355.                 tEvent:FireAllClients(obj, infoToTable(info), properties, clock:GetTime(), tObject.TweenId, timeThreshold, debugMode, nil, clientSync)
  356.             end
  357.         end
  358.        
  359.         completionWait(info.Time)
  360.        
  361.     end
  362.    
  363.     function tObject:Cancel(clients)
  364.        
  365.         if clients then
  366.             clients = (type(clients) == "table") and clients or {clients}
  367.            
  368.             for _, client in ipairs(clients) do
  369.                 tEvent:FireClient(client, nil, nil, nil, clock:GetTime(), tObject.TweenId, nil, debugMode, "Cancel")
  370.             end
  371.         else
  372.             tEvent:FireAllClients(nil, nil, nil, clock:GetTime(), tObject.TweenId, nil, debugMode, "Cancel")
  373.         end
  374.        
  375.         events.Cancelled:Fire()
  376.         tObject.IsCancelled = true
  377.         changeState(Enum.PlaybackState.Cancelled)  
  378.        
  379.     end
  380.    
  381.     function tObject:Pause(clients)
  382.        
  383.         if clients then
  384.             clients = (type(clients) == "table") and clients or {clients}
  385.            
  386.             for _, client in ipairs(clients) do
  387.                 tEvent:FireClient(client, nil, nil, nil, clock:GetTime(), tObject.TweenId, nil, debugMode, "Pause")
  388.             end    
  389.         else
  390.             tEvent:FireAllClients(nil, nil, nil, clock:GetTime(), tObject.TweenId, nil, debugMode, "Pause")
  391.         end
  392.        
  393.                
  394.         tObject.TimeElapsed = clock:GetTime() - tObject.LastPlay
  395.         tObject.LastPlay = clock:GetTime()
  396.        
  397.         events.Paused:Fire()
  398.         tObject.IsPaused = true
  399.         changeState(Enum.PlaybackState.Paused)
  400.     end
  401.    
  402.    
  403.     return tObject
  404. end
  405.  
  406. if rs:IsClient() then
  407.     local player = game.Players.LocalPlayer
  408.    
  409.     local tweens = {}
  410.    
  411.     tEvent.OnClientEvent:Connect(function(obj, info, properties, timestamp, tweenID, threshold, debugMode, modify, sync)
  412.        
  413.         if modify then
  414.             local tweenToEdit = tweens[tweenID]
  415.            
  416.             if not tweenToEdit then -- if the tween doesn't exist, just return and give a warning
  417.                 warn("The tween you tried to modify does not exist.")
  418.                 return
  419.             end
  420.            
  421.             if modify == "Cancel" then
  422.                
  423.                 tweenToEdit:Cancel()
  424.                
  425.                 if debugMode then
  426.                     local latency = clock:GetTime() - timestamp
  427.                     print("Cancelled a tween. Latency:", latency)
  428.                 end
  429.                
  430.                 return  
  431.                
  432.             elseif modify == "Pause" then
  433.                
  434.                 tweenToEdit:Pause()
  435.                
  436.                 if debugMode then
  437.                     local latency = clock:GetTime() - timestamp
  438.                     print("Paused a tween. Latency:", latency)
  439.                 end
  440.                
  441.                 return  
  442.             end
  443.         end
  444.        
  445.         local latency = clock:GetTime() - timestamp
  446.        
  447.         local newtime = sync and info.Time - latency or info.Time    -- If enabled, this syncs the tween up based on latency.
  448.                                                                      -- Ex. If the server told a client to do a 10 second tween and it
  449.                                                                      -- took .7 seconds to get to the client, then the time for the
  450.                                                                      -- client's tween would be 10 - .7 = 9.3 seconds.
  451.                                                                      -- I'm using Quenty's module to get a global timestamp, so
  452.                                                                      -- that server and client time are synced. This results in a
  453.                                                                      -- perfect sync between client and server tween completion.
  454.                                                                      -- To better see this for yourself, turn on debug mode and
  455.                                                                      -- look at the output to see how the server prints exactly
  456.                                                                      -- when the client tween ends. (If you are in Studio, Make sure you  
  457.                                                                      -- stay watching only on the client though, because switching to
  458.                                                                      -- the server  view pauses the client's game session on Play Solo)
  459.        
  460.         if debugMode then
  461.             print("Approximate latency for ".. player.Name ..": " ..latency .. " seconds. \n New tween time: ".. newtime .." seconds")
  462.         end
  463.        
  464.         threshold = threshold or 0 -- Defaults to 0. When you set this, this basically means the amount of time
  465.                                    -- the new tween time (after calculating latency) needs to be greater than
  466.                                    -- for the tween to play. If the new tween time after calculating latency is less than
  467.                                    -- or equal to than the threshold, the tween will not play.
  468.        
  469.        
  470.         if newtime > threshold and obj and properties and tweenID then -- some checks
  471.             local newInfo = TweenInfo.new(newtime, info.EasingStyle, info.EasingDirection, info.RepeatCount, info.Reverses, info.DelayTime)
  472.            
  473.             local trackTween = wrap(function()
  474.                
  475.                 if not tweens[tweenID] then
  476.                     tweens[tweenID] = ts:Create(obj, newInfo, properties)
  477.                 end
  478.                
  479.                 tweens[tweenID]:Play() 
  480.                 tweens[tweenID].Completed:Wait()
  481.                 tweens[tweenID] = nil
  482.             end)
  483.            
  484.             trackTween()   
  485.            
  486.             if debugMode then
  487.                 local printProperties = wrap(function()
  488.                     print("Currently tweening properties of "..obj.Name..":")
  489.                    
  490.                     for property, value in pairs(properties) do
  491.                         print(property .. " to "..tostring(value))
  492.                     end
  493.                 end)
  494.                
  495.                 printProperties()
  496.             end    
  497.         end
  498.     end)
  499. end
  500.  
  501. return tweenService
Add Comment
Please, Sign In to add comment