This is called lag. It’s normal and avoidable.
Please, do not do movement mechanics on the server.
The client can move the player character however it likes at any time and it will automatically replicate to the server. [1]
You can create the BodyMover on the client and the player will immediately move in the correct direction with no problems whatsoever.
The server does not need to know any lookvector.
Local script:
local db = false
function abilityBegin(input, bool)
if userInputService:IsKeyDown(Enum.KeyCode.E) then
if not bool then
if db == false then
db = true
local diveForce = Instance.new("BodyVelocity")
diveForce.Parent = rootPart
diveForce.Velocity = rootPart.CFrame.LookVector * 90
diveForce.MaxForce = Vector3.new(35000, 50000, 35000)
wait(.4)
diveForce:Destroy()
wait(abilityCooldown - 0.4) -- quick hack to keep the timing right
db = false
end
end
end
end
userInputService.InputBegan:Connect(abilityBegin)
This will make the E button dive forward for a short time.
The particle effects, trail and animation are trickier.
If you fire the event and just let the server create and enable them, then they will show up shortly after the dash has begun, or perhaps even the dash has ended (if ping is over 400ms).
You have to enable them both on the server (so all players see them) and on the client (so that the player using the ability sees it immediately).
If you do exactly that, then the trail and sparkles might seemingly work correctly, but the sound and animation will play twice.
One way to solve this is to have another remote that means “this player used an ability”. This remote will run a function that enables the particles, trail etc.
The player who used an ability will call the same function that this remote would’ve fired to get the particles instantly. A script on the server side will fire this remote to all players except the player who used the ability so that everyone will see it, but the original player won’t see it twice.
It’s solid, but takes more annoying remote event code.
local remote = ReplicatedStorage.AbilityUsed
-- Server
remote.OnServerEvent:Connect(function(player, name)
for _,v in ipairs(Players:GetPlayers()) do
-- retransmit to all players except for the one who called it
if v ~= player then
remote:FireClient(v, name)
end
end
end)
-- Client
local function abilityDash(player)
-- enable trail, particles, animation... on player's character
end
remote.OnClientEvent:Connect(function(player, name)
if name == "Dash" then
abilityDash(player)
end
end)
-- when the player uses the dash ability, show effects on self
abilityDash(Players.LocalPlayer)
This isn’t rock solid, an exploiter might fire this event when they have no character to spam errors in console or something stupid like that, or create tons of lag for everyone.
It’s something to keep in mind with any remote at all, for that matter.
Another way I just thought of is to clone the trail, particles etc. on the client and delete the originals.
When the server enables the originals, all other players will see the effects as normal, but the effect-using player won’t (and shouldn’t, because it saw the effect before the server saw it)
The effects should be set up in advance (but dormant/disabled) instead of being created when the effect starts. If the server creates a new effect on the fly, then there was no point in deleting anything and the effect will run twice anyway.
-- I recommend writing all of this in a module script
local trail
local particles
-- etc.
local function shadowClone(instance)
local clone = instance:Clone()
instance.Parent = nil -- do not care, do not want
return clone
end
local function showEffects()
-- enable trail, show particles, play sound
-- wait
-- disable
end
local function initializeDash()
-- if on server
-- create/copy over effects
trail = script.Trail:Clone()
-- etc.
-- elseif on client
-- wait for server to set up effects, grab local copies
trail = shadowClone(torso:WaitForChild("Trail"))
-- etc.
-- end
end
This method is annoying because you cannot just create and destroy the effects, which was kinda convenient earlier.
There’s also a bunch of synchronization to worry about. What if multiple effects try to create a trail with the same name? A lot of things can go wrong. But it’s just another way to do it.
[1] An exploiter can also teleport to whereever they like instantly, they just set HumanoidRootPart.CFrame.
Think like an exploiter. Can you make your movement effect work client-side without any remotes? If so, then do it, don’t let the exploiter be better than you.