I have a combat system that should gradually slow the player upon charging an attack. I use TweenService on the server to control the WalkSpeed, etc. properties of the Humanoid.
My thought process is, since the client has control on Humanoid properties already, wouldn’t it be more efficient to just tween them on the client and just “trust the client”? What is the best way to approach this? I would appreciate any help.
Client:
local function handleAttack(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin and not humanoid:GetAttribute("Cooldown") then
attackEvent:FireServer()
attackTrack:Play()
end
end
ContextActionService:BindAction("Attack", handleAttack, true, Enum.UserInputType.MouseButton1)
Server:
pushEvent.OnServerEvent:Connect(function(player)
local character = player.Character
local humanoid = character.Humanoid
if humanoid:GetAttribute("Cooldown") then return end
TweenService:Create(humanoid, TweenInfo.new(1, Enum.EasingStyle.Linear, Enum.EasingDirection.Out, 0, true), {WalkSpeed = 0, JumpPower = 50}):Play()
task.spawn(function()
humanoid:SetAttribute("Cooldown", true)
wait(2)
humanoid:SetAttribute("Cooldown", false)
end)
end)
Your comment is well appreciated. Apologies but my concern is whether its best to tween humanoid.WalkSpeed on client or server. I have read somewhere that tweening on the server would not be optimal performance-wise, so I’m asking if I can just tween humanoid properties on the client since the client has access to humanoid properties already so might as well just trust them and break the golden rule. Thank you for your prompt response.
well stuff like WS/JP can be done in client sided even if in what i remember tweenservice don’t work for client sided, (i can be wrong too), but for informations like Health/MaxHealth etc it needs to be done in serversided
not really since roblox are going to add hyperion to microsoft store and android and linux pretty soon so exploiters will be allot rarer than they are now because microsoft and android are the actual platform with the highest ammount of exploiters since 64bits roblox not exploitable anymore
I think you could mix it a little bit; Tween done on client-side, while waiting the tween’s completion and then setting the player’s speed to that amount on server to avoid desynchronization with other players or the server itself.
If the tween were done entirely on client-side without the server setting their new speed, other parts of the server wouldn’t know that player’s new speed because changes made locally don’t replicate to server or other players.
Putting this mixup in practice:
-- Local Script
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
local Client = Players.LocalPlayer
local Event = ReplicatedStorage.TweenSpeed -- Remote Function
-- It should be a remote function because there could occur some desynchronization between client-server
-- Which means that if we used an remote event, the client could tween animation too late
-- Providing him speed when he shouldn't have.
function Event.OnClientInvoke(NewSpeed: number?, Duration: number)
print(`Local: Got requested to tween speed to {NewSpeed} in {Duration}s`)
local Character = Client.Character;
if not Character then
-- Client's character probably died or has not been fully loaded yet.
return false
end
local Humanoid = Character.Humanoid
if not Humanoid then
-- The previous logic applies to this too
return false
end
local TweenInformation = TweenInfo.new(
Duration or 1, -- Time: 1 is the default when none is received.
Enum.EasingStyle.Quint, -- EasingStyle
Enum.EasingDirection.InOut -- EasingDirection
)
local Tween = TweenService:Create(Humanoid, TweenInformation, {
WalkSpeed = NewSpeed
})
Tween:Play()
Tween.Completed:Wait()
print(`Local: Finished tweening of speed to {NewSpeed}`)
return true
end
-- ServerScript
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local function TweenSpeed(Player: Player, Speed: number, Duration)
local Character = Player.Character
if not Character then
-- Player died?
return
end
local Humanoid = Character:FindFirstChild("Humanoid")
if not Humanoid then
-- Same logic
return
end
-- Be careful when using InvokeClient in synchronous code, remote functions may fail/be manipulated.
-- You can protect it using pcall, but since my example is based on asynchronous code, event code,
-- I didn't do it.
-- More informations: https://create.roblox.com/docs/scripting/events/remote
-- Example of using pcall to protect it
--[[
local Success, Result = pcall(function()
return ReplicatedStorage.TweenSpeed:InvokeClient(Player, Speed, Duration)
end)
if Success then
if Result == true then
Humanoid.WalkSpeed = Speed
end
else
warn(`Could not tween player {Player}'s speed to {Speed}. More details below`)
warn(Result) -- err
end
]]
ReplicatedStorage.TweenSpeed:InvokeClient(Player, Speed, Duration)
Humanoid.WalkSpeed = Speed -- Maintains speed synchronization with the server, and then, with all other players.
end
Players.PlayerAdded:Connect(function(Player)
Player.CharacterAdded:Wait()
task.wait()
TweenSpeed(Player, 50, 4)
TweenSpeed(Player, 16, 2)
end)