I’m learning scripting and I’ve made a somewhat jump/dash which uses applyimpulse to push a character forward. The problem is I don’t know how I should overall structure my code. I’m coming across posts saying changes to the player should be on the client but I don’t know how that would work with other posts saying they only use 1 remote event for their games. Below is me using a fireallclients which I imagine is what needs to be used with some sanity checks by the server. I’m still not sure if this is right and I’m looking for more improvements.
Server script:
local RS = game:GetService("ReplicatedStorage")
local debris = game:GetService("Debris")
RS.Dash.OnServerEvent:Connect(function(plr)
local function boost()
RS.Move:FireAllClients()
end
local function animate()
local animator = plr.Character.Humanoid.Animator
local anim = Instance.new("Animation")
anim.AnimationId = "http://www.roblox.com/asset/?id=15881777718"
local animtrack = animator:LoadAnimation(anim)
animtrack:Play()
end
task.spawn(animate)
task.spawn(boost)
end)
Client-Side script:
local CAS = game:GetService("ContextActionService")
local RS = game:GetService("ReplicatedStorage")
local name = "PressedE"
local plr = game.Players.LocalPlayer
local HRP = plr.Character.PrimaryPart
local hum = plr.Character.Humanoid
local lasttime = 0
local function bind(name,inputstate)
if name == name and inputstate == Enum.UserInputState.Begin then
if os.time() - lasttime >= 2 then
lasttime = os.time()
RS.Dash:FireServer()
end
end
end
CAS:BindAction(name,bind,true,Enum.KeyCode.E)
RS.Move.OnClientEvent:Connect(function()
if plr then
if hum:GetState() == Enum.HumanoidStateType.Freefall or hum:GetState() == Enum.HumanoidStateType.Jumping then
print("Success")
HRP:ApplyImpulse(HRP.CFrame.LookVector * 100 * HRP.AssemblyMass)
else
HRP:ApplyImpulse(HRP.CFrame.LookVector * 150 * HRP.AssemblyMass)
end
end
end)
Theres many correct answers to your question, but in my opinion, you want the least amount of stress on your server while still having tight security. The thing is, if you want tight security, you gotta to have some stress.
In the context of your dash/jump mechanic, it’s generally recommended to handle critical aspects, such as movement and physics, on the server. This helps prevent cheating and ensures fair gameplay. However, for responsive controls, you may also want to incorporate predictive movement on the client for a smoother player experience, as long as the calculations are verified and enforced on the server.
There are some things you can improve on:
Use Debounce for input: Instead of checking os.time() for cooldown, consider using a debounce mechanism to prevent rapid firing. This is especially important in a networked environment where input delays can vary.
Use RemoteEvent for Movement: Instead of firing a RemoteEvent (RS.Move:FireAllClients() ) and handling movement on the client, you can directly call a RemoteFunction to the server, and then apply the impulse on the server. This ensures that the server has control over player movement, preventing potential exploits. Example:
--server
local RS = game:GetService("ReplicatedStorage")
local MoveFunction = RS:WaitForChild("MoveFunction")
MoveFunction.OnServerInvoke = function(player)
-- Your movement code here
end
Client:
--local
RS.MoveFunction:InvokeServer()
Client-Side:
User Interface: Rendering and updating the player interface (UI) can be handled on the client.
Non-Critical Visual Effects: Cosmetic changes, particle effects, or animations that don’t affect gameplay significantly can be handled on the client.
Predictive Movement: If you want a more responsive feel for movements, you might perform some actions on the client and then replicate them to the server for verification.
Server-Side:
Critical Game Logic: Important game mechanics, calculations, and decisions should be handled on the server to prevent exploitation and cheating.
Player Movement and Physics: To ensure fair and consistent gameplay, essential movement and physics calculations should be performed on the server. Client input can be used for prediction, but the authoritative version should be computed on the server.
Data Validation: Always validate and sanitize data on the server to prevent exploits.
Remote Events/Functions:
Use RemoteEvents to signal actions from the client to the server, and from the server to the client.
RemoteFunctions can be used for two-way communication but should be used cautiously, especially when involving client-to-server calls, as they can introduce security vulnerabilities if not handled correctly.
Wouldn’t the best option for this to have sanity checks and fire every client from the server?
How would I render particles for every player on the client without firing every client?
I don’t think I should improve the security of my game because I haven’t found what structure I want to use between the client and server. My main issue stems from not knowing how to balance performance with work on the server. Is the structure of a code just a preference because exploiters can be “unavoidable”?
local SS = game:GetService("ServerStorage")
local debris = game:GetService("Debris")
local runservice = game:GetService("RunService")
local module = {
punch = function(plr, serveritems, debouncetable,punchsound)
local char = plr.Character or plr.CharacterAdded:Wait()--PLayer values
local HRP = char.PrimaryPart
local cd = 0.25
local newhitbox = serveritems.HitBox:Clone()--Created
newhitbox.Parent = workspace
newhitbox.CFrame = HRP.CFrame + HRP.CFrame.LookVector * 3
local filter = OverlapParams.new()
filter.FilterType = Enum.RaycastFilterType.Exclude
filter.FilterDescendantsInstances = {newhitbox, plr.Character}
local region = workspace:GetPartsInPart(newhitbox, filter)
for i,v in pairs(region) do
local enemyhum = v.Parent:FindFirstChild("Humanoid")
if enemyhum and v.Parent ~= plr.Character and not debouncetable[enemyhum] then
local core = coroutine.wrap(function()
punchsound:Play()
debouncetable[enemyhum] = enemyhum
enemyhum:TakeDamage(15)
task.wait(cd)
debouncetable[enemyhum] = nil
end)
core()
end
end
debris:AddItem(newhitbox,cd)--Destroyed
end,
animate = function(plr, punchanim, punchtable)
local char = plr.Character or plr.CharacterAdded:Wait() -- getting player values
local hum = char.Humanoid
local animation = Instance.new("Animation") -- creating animation
animation.Parent = char
animation.AnimationId = punchtable[punchanim]
local animtrack = hum:LoadAnimation(animation) --Playing and loading animation track
animtrack:Play()
end,
renderparticles = function(punchparticle, plr)
local part = Instance.new("Part")
local newpunchparticle = punchparticle:Clone()
local cd = 0.25
--plr values
local char = plr.Character or plr.CharacterAdded:Wait()
local HRP = char.PrimaryPart
local core = coroutine.wrap(function()
task.wait(0.1)
newpunchparticle:Emit(20)
end)
part.Parent = workspace
newpunchparticle.Parent = part
part.Transparency = 1
part.CanCollide = false
part.Anchored = true
part.CFrame = HRP.CFrame + HRP.CFrame.LookVector * 3
core()
debris:AddItem(part,cd)
debris:AddItem(newpunchparticle,cd)
end,
}
return module
Server Script
local RS = game:GetService("ReplicatedStorage")
local SS = game:GetService("ServerStorage")
local debris = game:GetService("Debris")
local modules = RS:WaitForChild("Modules")
local events = RS:WaitForChild("Events")
--Server etc
local serverItems = SS.Items
--Modules
local punchmodule = require(modules.PunchModule)
--Effects
local effects = RS.Effects
local punch = effects.PunchSound
--Debouncing
local debouncetable = {}
events.Punch.OnServerEvent:Connect(function(plr)
punchmodule.punch(plr, serverItems, debouncetable,punch)
events.Animate:FireAllClients(plr)
end)
Client-Side Script
local RS = game:GetService(“ReplicatedStorage”)
–Below services
local punchmodule = require(RS.Modules.PunchModule)
local events = RS:WaitForChild(“Events”)
–Player things
local plr = game.Players.LocalPlayer
local mouse = plr:GetMouse()
–Effects
local effects = RS:WaitForChild(“Effects”)
local puncheffect = effects.PunchParticle
local function click()
if plr then
local core = coroutine.wrap(function()
if not debounce then
debounce = true
events.Punch:FireServer()
task.wait(0.25)
debounce = false
end
end)
core()
end
end
mouse.Button1Down:Connect(click)
events.Animate.OnClientEvent:Connect(function(plrtochange)
if plrtochange then
punchmodule.animate(plrtochange, punchanim, punchtable)
punchmodule.renderparticles(puncheffect,plrtochange)
punchanim += 1
if punchanim == 3 then
punchanim = 1
end
end
end)
Note: I’m looking for improvements scripting-wise.
Note 2: I also noticed the hitbox moves because of the animation, is this major?
Note 3: When I use fireallclients I’m giving the server less control, but I don’t know how else I would load things such as animations, particles, etc.
I tested my dash mechanic with remote functions and it worked! It showed everything the client did on the server and on other clients! How did that happen?
Update: Nevermind, particles don’t load for everyone on remote functions, I guess there are no shortcuts lol.