Introduction
In my (first) tutorial, I picked this topic because it really seems to be looked over as a cardinal sin by many developers. I have seen many uses (including within some popular games) of a painful amount of Event spam (i.e. firing an event on every mouse move). This is bad practice for a number of reasons, including:
-
Gives room to server lag - While doing this doesn’t inherently always create additional lag (despite the spam requests often occupying both client and server resources), it allows for the server to ‘choke’ on the requests coming through (some requests take longer to go through due to network speed fluctuation, request size etc) and generally become de-synchronised (if you are rapidly firing events, it will make the movement you want to achieve generally choppier and will have a very noticable delay)
-
Often illudes to an insecure game - Firing events rapidly often means that you have some sort of wild issue with your Client-Server Model (read more about that here). This means it’s more likely for an exploiter to be able to find vulnerabilities in your code.
-
Inefficient - 90% of the time, there is a better way to replicate something to others without the use of RemoteEvents. This is what I hope to inform you of later on.
So how do I go about fixing this?
It’s actually fairly simple. There are two main options you should consider, both have different uses and reasons as to why you may or may not use them. Anyway, without further or do here are the explanations of both:
Method A: Local Replication
This is an interesting method which encompasses two different topics: Remote Events, and Local Parts. Both have good wiki tutorials which I have linked.
So, what is the use of locally replicating parts? Well, the answer is generally when you need to have something replicate, but you don’t want the user to have full control over it. An example of this is a block bullet. If you use Method B (Discussed later), you will end up giving the sending user complete control over where that bullet hits. They could teleport it to any player instantly, and do that player damage without even aiming.
Anyhow, here is the quick diagram I have made for this, using the Bullet analogy as an example:
Now, onto actually coding it. Firstly, we need to write the Server that routes all the requests so that it can be handled by each client. This should be in ServerScriptService.
I wrote some demo code for this here:
local RemoteEvent = Instance.new("RemoteEvent")
RemoteEvent.Name = "BulletReplicator"
RemoteEvent.Parent = game.ReplicatedStorage
--Creating a new RemoteEvent called 'BulletReplicator' in the ReplicatedStorage Service.
RemoteEvent.OnServerEvent:connect(function(Player,HitLocation)
if not HitLocation --If no Hit Location (Player Mouse location)
or not Player.Character --Or the Players' character doesn't exist
or not Player.Character:FindFirstChild("Weapon") --Or the player isn't holding a weapon
then return end --Then don't procede from here
--Otherwise, do this:
for _,NetworkPlayer in pairs(game.Players:GetPlayers()) do
if Player ~= NetworkPlayer then
RemoteEvent:FireClient(NetworkPlayer,Player.Character:FindFirstChild("Weapon").Handle,HitLocation)
end
end
--Fires every client with the Player Name, The Origin and the Target.
end)
Next up is the receiving client. This should always and only be in the StarterPlayerScripts.
Here is the demo code for that:
local RemoteEvent = game.ReplicatedStorage:WaitForChild("BulletReplicator")
--Wait for Bullet Replicator Event in ReplicatedStorage
RemoteEvent.OnClientEvent:connect(function(OriginInstance,HitVector) --This function is called when RemoteEvent::FireClient is called on the player.
--[[
Insert Bullet Creation & Damage Code Here from OriginInstance.Position and HitVector
--]]
end)
Finally, we need the tool that does the initial request. The code for that is here:
local Player = game.Players.LocalPlayer
--Declare Player for easier access
local Mouse = Player:GetMouse()
--Declare Mouse for easier access
local Tool = script.Parent
local RemoteEvent = game.ReplicatedStorage:WaitForChild("BulletReplicator")
--Wait for Bullet Replicator Event in ReplicatedStorage
Mouse.Button1Down:connect(function()
if not Tool.Parent == Player.Character then return end -- Check if Tool is equipped (without Events)
local HitPosition = Mouse.Hit.p -- Declare Hit Pos so that there is no change in value
RemoteEvent:FireServer(HitPosition) -- Fire Server
--[[
Insert Bullet Creation & Damage Code Here from OriginInstance.Position and HitVector
--]]
end)
WARNING: THIS IS NOT ALWAYS THE MOST EFFICIENT WAY AND CAN CAUSE A NOTICEABLE DE-SYNCHRONISATION BETWEEN DIFFERENT CLIENTS
Now, with that over, we move onto:
Method B: Network Ownership
Network Ownership is a very interesting topic in itself. The Wiki will do a far better explaination than I can do here, however the tl;dr is that you can control certain properties of unanchored parts (i.e. CFrame) if the server grants you permission.
Why is this useful? Mostly for these two main reasons:
-
Other clients are blind to anything you do clientside until replicated - this means that until you tell them, clients have no idea what you are doing (mouse position, keys down etc) until they are told. With this, they can stay blind to your precise inputs and instead just see what is going on.
-
The client has complete control over the property without any lag - even though this can be a vulnerability in some cases (discussed in Method A), it can also be a great advantage. An example of this would be moving a part towards the mouse. Other players have no idea where the mouse is, and as discussed in the Introduction spamming an event every time the mouse moves is inefficient.
Changing a Part’s Network Owner can be done via this method:
Part:SetNetworkOwnership(PlayerInstance NewNetworkOwner)
Note: All character limbs are already network owned by the Player - or you couldn’t move around or play animations!
For the example script, we will be making a part which will travel across the map. In order to do this we will need a RemoteFunction, a Server Script and a LocalScript.
Here is the demo code for the Server Script, which again should always be in ServerScriptService:
local RemoteFunction = Instance.new("RemoteFunction")
RemoteFunction.Name = "NetworkReplicator"
RemoteFunction.Parent = game.ReplicatedStorage
--Creating a new RemoteFunction called 'NetworkReplicator' in the ReplicatedStorage Service.
function RemoteFunction.OnServerInvoke(Player)
--When RemoteFunction::InvokeServer is called on the RemoteFunction by a client
if not Player.Character then return end --We need a character to do this!
local Part = Instance.new("Part")
Part.Parent = Player.Character --Create an Anchored Part in the Players' Character
Part.Anchored = true
Part.CFrame = Player.Character.Torso.CFrame * CFrame.new(0,0,-3)
--Set the Part's CFrame to 3 studs infront of the Players' Character CFrame.
Part:SetNetworkOwner(Player)
--Set the network owner to that player
return Part
--Return that to the client
end
Next up is the client. This should be in StarterCharacterScripts or PlayerGui. Code:
local RemoteFunction = game.ReplicatedStorage:WaitForChild('NetworkReplicator') --Wait for RemoteFunction
local Player = game.Players.LocalPlayer --Declare player for easy Access
local Character = Player.Character or Player.CharacterAdded:wait() --Either index Players' Character or wait for it to be added
while wait(3) do --Do this every 3 seconds:
pcall(function() -- Basically means that the while loop doesn't wait for the for loop (making it run even if the for loop is still running)
local Part = RemoteFunction:InvokeServer() --Call RemoteFunction::InvokeServer on the RemoteFunction
for i = 1,50 do --Do this 50 times before stopping:
Part.CFrame = Part.CFrame * CFrame.new(0,0,-3) --Move the part 3 studs infront of its previous position
wait()
--Wait for ~1/15th of a second
end
end)
end
We end up with this result:
https://gyazo.com/6f873f3b84eca51d9abd31758809f41b
WARNING: THIS CAN HAVE BAD CONSEQUENCES ON YOUR GAME IF NOT USED PROPERLY! ALWAYS CONSIDER EVERY ANGLE BEFORE IMPLEMENTING!
Anyway, thank you for reading my first tutorial. I’d really appreciate some feedback on anything like grammar, layout or readability. I hope you found this useful!