Good morning, developers!
To improve my game’s performance, I moved many object tweens client-side by placing them in the StarterCharacterScripts
folder.
At first, I encountered the issue of script re-execution upon player death, causing the tweens to mess up the starting position, which was being overwritten by the current one at the moment of the character’s respawn.
To fix this, I moved the scripts to StarterPlayerScripts
.
Everything seemed to be working great until I noticed that, on some occasions, the scripts were still being executed twice for some reason.
This issue causes the tweens to run two or more times simultaneously, which of course creates unwanted effects. I believe this happens during long-distance teleports.
My game covers large areas, and on computers with low performance or poor connection, this issue may occur, especially when the ‘Game Paused’ window appears briefly.
Here’s an example of how I handle the tweens:
local TweenService = game:GetService("TweenService")
local level = workspace:WaitForChild("ObbyStructure"):WaitForChild("EasyLevels"):WaitForChild("L009")
local plasmaSoundEffect = game:GetService("ReplicatedStorage"):WaitForChild("SoundEffects"):WaitForChild("Plasma")
local laserSoundEffect = game:GetService("ReplicatedStorage"):WaitForChild("SoundEffects"):WaitForChild("Laser")
local minFloatingDuration = 1
local minMovementDuration = 0.2
local damage = 20
-- Funzione per creare e avviare un tween su un oggetto specificato
local function createAndPlayTween(object, target)
local movingSound = plasmaSoundEffect:Clone()
movingSound.Parent = object
local laserSound = laserSoundEffect:Clone()
laserSound.Parent = object
local seed = object.Position.X * object.Position.Y * object. Position.Z * 10e6
if seed < 0 then seed = seed * -1 end
local random = Random.new(seed)
local tweenInfoFloating = TweenInfo.new(
minFloatingDuration + random:NextNumber(-0.05,0.05),
Enum.EasingStyle.Linear,
Enum.EasingDirection.Out,
0, -- Numero di ripetizioni (-1 per ripetere all'infinito)
true, -- Reverse (true per invertire il tween)
0 -- Tempo di attesa prima di iniziare
)
local tweenInfoMovement = TweenInfo.new(
minMovementDuration + random:NextNumber(-0.2,0.2), -- Durata
Enum.EasingStyle.Linear,
Enum.EasingDirection.Out,
0, -- Numero di ripetizioni (-1 per ripetere all'infinito)
false, -- Reverse (true per invertire il tween)
0 -- Tempo di attesa prima di iniziare
)
local objectFloatingProperties = {
Position = object.Position + Vector3.new(0, 1, 0)
}
local targetFloatingProperties = {
Position = target.Position + Vector3.new(0, 1, 0)
}
local objectProperties = {
Position = object.Position -- Torna alla posizione originale
}
local targetProperties = {
Position = target.Position -- Specifica la proprietà da tweenare (es. Position, Size, Transparency, ecc.)
}
-- Verifica collisione con giocatori
object.Touched:Connect(function(hit)
local humanoid = hit.Parent:FindFirstChildOfClass("Humanoid")
if humanoid and humanoid.Health > 0 then
laserSound:Play()
humanoid:TakeDamage(damage)
end
end)
local TweenObjectFloat = TweenService:Create(object, tweenInfoFloating, objectFloatingProperties)
local TweenTargetFloat = TweenService:Create(object, tweenInfoFloating, targetFloatingProperties)
local tweenForward = TweenService:Create(object, tweenInfoMovement, targetProperties)
local tweenBackward = TweenService:Create(object, tweenInfoMovement, objectProperties)
tweenForward.Completed:Connect(function(PlaybackState)
movingSound:Play()
TweenTargetFloat:Play()
TweenTargetFloat.Completed:Wait()
movingSound:Play()
tweenBackward:Play()
end)
tweenBackward.Completed:Connect(function(PlaybackState)
movingSound:Play()
TweenObjectFloat:Play()
TweenObjectFloat.Completed:Wait()
movingSound:Play()
tweenForward:Play()
end)
movingSound:Play()
tweenForward:Play()
end
-- Applica il tween agli oggetti già caricati
for _, child in pairs(level:GetChildren()) do
if child:IsA("Part") and child.Name == "Part" then
local target = child:WaitForChild("Target")
createAndPlayTween(child, target)
end
end
-- Ascolta l'aggiunta dinamica di nuove rocce
level.ChildAdded:Connect(function(child)
if child:IsA("Part") and child.Name == "Part" then
local target = child:WaitForChild("Target")
createAndPlayTween(child, target)
end
end)
Besides the problem already described, there’s another issue.
As you can see at the end of the script, to manage objects from the client, I iterate over those already present and then check for the ones added afterward.
This also caused problems because, on rare occasions, while iterating over the present objects, some get added, and their animation is ‘lost’.
I also tried reversing the approach by connecting the addition event before the for
loop, but in that case, some objects end up being tweened twice—once upon addition and once in the loop. Finally, I tried saving the existing objects in a table, connecting the addition event, and then iterating over the table, but even this caused some objects to be tweened twice for some reason.
Ultimately, what I’m kindly asking for is help to understand if there’s an alternative method to manage these things and avoid these issues. Specifically, the solutions I’m looking for is:
- How can I ensure that objects are animated client-side once and only once, with the certainty that the animation will never restart in any scenario?
The alternative would be to move the scripts back to StarterCharacterScripts
, but this would slow down performance and require saving the original positions of objects server-side, something I wouldn’t be able to do in a local script that gets restarted every time, overwriting previous information.
Thank you in advance for your attention