Sending instances through remotes for weapon systems?

I have been more than once, clearly instructed and incentivized to under absolutely no circumstances, send any instances through remotes, be it Server -> Client or Client -> Server, for several different reasons which I could go on about.

However, upon experimenting with a new weapon system, I hit a roadblock.
Using server for hit detection is really bad, since it has a noticeable delay during gameplay.
Therefore I did some research, and found that most games use client detection and then just validate it on the server.

Fair. Easily done.
However, fixing one problem brings another to light:
How to let the server know which players have been hit in the first place, so that it can propetly validate the interaction?

After some more research, I found that there are examples for what I’m looking in Roblox’s tutorial studio games. More specifically, “Laser Tag”. Upon reading its code, I have come to the realization that in that game, an official Roblox example, they send not just one, but more than one instance through a remote from the client to the server!

Here is a snippet of the code I found:

local function onShootEvent(
	player: Player,
	timestamp: number,
	blaster: Tool,
	origin: CFrame,
	tagged: { [string]: Humanoid }
)
-- Rest of the code...

From my understaning, in said tutorial game, what is done is:
The client makes the detection of what was hit and what wasn’t based on Player input using SphereCasts, and then makes a list of what players have been hit. It then sends the server all the humanoids that the client hit, with the additional information of where was the shot taken from (the origin position).
Based on that, the server then validates if the shot is possible, and if the tagged player has truly been hit using a distance check.

This is all very nice and reasonably doable, yeah. The problem here is not inherently the structure they have come up with, but instead that after so long being told that instances should never be sent through remotes, upon seeing it in an official Roblox example, I’m confused to whether this is the correct approach or not.

Now, if I ask myself: “Let’s suppose we don’t use humanoids then. Let’s use UserIds”. If that was the case, then another problem arises. You won’t be able to hit anything that has no UserId, such as an NPC.

So what gives? For a system such as this, UserIds? Humanoids?
After all, instances through remotes OR NOT?! And if so, WHEN?

1 Like

i didn’t know that, i always send instances through remotes… Can you show some reasons its bad? If you cant id just send the name thru then search for it in workspace

I have been taught that a few of the problems that can arise from it are reference leaks, instance spoofing, denial of service, race conditions, among other things such as the fact that an instance in the server does not always reliably exists in the client, and vice versa.
However upon seeing it in an original Roblox example, I’m starting to question the severity of those issues.

1 Like

instances take alot more data to send through remotes so people do not send instances.
what you would want to do is send the hit part, hit position and the userID.
then server side check if the shot was possible then locate and damage the target

This just outlines the system, for headshots and damage multipliers your likely want to have DataTargets = {
Model = NameOfPartHit,HitPos
}

Server:

local MainRemote = game.ReplicatedStorage:WaitForChild("MainRemote")

-- The Remote
local RemoteData = {}
MainRemote.OnServerInvoke:Connect(function(Player,Data)
    if not RemoteData[Player] then
       RemoteData[Player] = {}
    end
    RemoteData[Player]["MainDebounce"] = true
   
    
    -- if raycast has a hit return false
    local Targets = {}
    Targets["Players"] = {}
    Targets["NPCs"] = {}

   if Data["Targets"] then
      for i,v in pairs(Data["Targets"]) do
         if v:IsA("Model") then
            for i2,v2 in pairs(game.Players:GetChildren()) do
               if v2.Character and v2.Character == v then
                  Targets["Players"][v2] = v
                  break
               end
             end
           table.insert(Targets["NPCs"],v)

         else
           for _,v2 in pairs(game.Players:GetChildren()) do
              if v2.Name == v or v2.UserId == v then
                 Targets["Players"][v2] = v2.Character
              end
           end
         end
      end
   end
   
   for i,v in pairs(Targets["Players"] do
   -- Damage Players
   end
   for i,v in pairs(Targets["NPCs"] do
   -- Damage NPCs
   end
   RemoteData[Player]["MainDebounce"] = false
   return true
end)

Client:

local Data = {}
-- Ability Fires, Get Hit Parts
Data["HitPos"] = ^
Data["Targets"] = {}
for i,v in pairs(HitParts) do
   if v:FindFirstAncestorOfClass("Model") and not Data["Targets"][v:FindFirstAncestorOfClass("Model") ] then
      Data["Targets"][v:FindFirstAncestorOfClass("Model")]
   end
end

local Response = MainRemote:InvokeServer(Data)

That’s true, but I dont think you should send the hit position, as an exploiter can just send the position of any plsyer, right?

Wait nvm you can just validate on server

The less data is send through a remote event, the faster it is.

I couldn’t find the devforum post about it when I learned this so this is a different post and a video saying something similar

To summarize it, the less data is sent though a remote event, the faster it is. Theres not a noticeable delay if multiple instances are sent so dont worry too much, but if theres 20 players in a fast paced fighting game sending lots of remotes with multiple instances then you shouldn’t unnessesarily send instances. (ex. get the HumanoidRootPart from the player instance, send a string saying which instance)

After chatting with a colleague who is an industry professional, a solution has been reached. The details are the following:

Sending instances through remotes is viable for Roblox Studio standards. However, it is suboptimal, and by no means is it industry standard for most programming situations you will find yourself in.

That is not to say that it does not work reliably. It most likely will in most cases, however the optimal solution suggested by him was to attribute a specific unique ID (preferably using HTTPService) and tag labeling them as a combat unit (being able to be damaged) to each character (player or not) within the game, and then utilizing that to determine which of the players are being affected by combat.

While this solution sounds like a suboptimal workaround, the actual suboptimal workaround is the “instance through remotes” solution itself, since it is merely a Roblox Studio adaptation, and in most development circumstances only raw data can be sent from the server to the client and vice versa.
With that, the ID approach or a similar solution would be taken. (Which is the one I will be taking for my project)

Regarding the matter of the reliability on sending instances through remotes:
In Roblox Studio circumstances, it does work , but you must pay mind to some problems that might arise. I will list them.

  • For Server -> Client, you have to pay mind to streaming, since the client will receive “nil” instead of the instance that was sent if it is not streamed in. This also applies to Client -> Server, but in less cases, since most instances that are seen by the client are also available in the server. More on that below.

  • For Client -> Server, spoofing and sending instances that do not exist can become a problem, but can (and should) be mitigated by proper validation on the server. Instances that are from the server and appear in the client should always be available in the server, and instances that exist in the client but not on the server are usually created by exploiters, or your own client code.

Do note: When sending instances through remotes, only a reference for the instance gets sent in the form of an UniqueId, and not the instance itself. The receiving end takes that UniqueId, and translates it into an instance if it exists on their end. That said, exceeding the buffer limit for remotes is, in this case, usually not a concern.

Finally, the conclusion.

Sending instances through remotes is not inherently bad, and it does work, but when done, it should be done with caution and proper validation.
It is however not the most optimal way to have the server or the client get hold of an instance sent from the other, so if you want to improve your coding skills, be organized, or assure excellence and reliability for your game’s code, it’s definitely worth attempting a different approach, such as giving your instance IDs in order to find them, whether it be stored somewhere in a table or even a folder instance.

Please let me know if you find any mistakes here, and I hope this can benefit the community. Thank you for your time and replies.
Godspeed, fellow devs!

1 Like

When you send an instance through a remote, only its reference gets sent, not the whole instance. It costs the same as sending any regular number.

On the other side, there are only two possible outcomes: you either get the instance or nil if, for some reason, it’s not being replicated. That means in some cases you’ve got to check that it’s not nil.

So, it’s totally safe to send instances through remotes.

I can’t really think of a case where you’d want to use custom identifiers for instances. They’re mainly useful for tables, since tables don’t get replicated, they just get copied when sent. Using the instance reference will always be easier.

Even with custom identifiers, you still have to make that.

Not sure where this came from.

When you send an instance through a remote, only its reference gets sent, not the whole instance. It costs the same as sending any regular number.

Indeed, this is something you are absolutely right about! Instances get sent as references through remotes, and my buffer limit statement was incorrect, and therefore I will be making sure to remove it from my reply.

So, it’s totally safe to send instances through remotes.

To clear things up, I did not say it was totally unsafe! I simply specified the caveats of both approaches, and leaned toward the most optimal approach, naturally (do note that I said “optimal”, and not “easy”). Validation has to be done in both approaches, each in an appropriate manner for its use case.

I can’t really think of a case where you’d want to use custom identifiers for instances. They’re mainly useful for tables, since tables don’t get replicated, they just get copied when sent. Using the instance reference will always be easier.

I can think of many different cases where you’d want to use custom identifiers for instances and where they are useful for more than tables. Glad we can share our thoughts!
Do note, sending the instance reference is easier yes, that is a factual statement from your part. To clear things up once more, I did not say it was not easy. You do however become more easily subject to replication concerns and instance spoofing when choosing this approach.
Now those can be mitigated, yes, but then again, in the paragraph I wrote, I explicitly stated that they can.

Even with custom identifiers, you still have to make that.

With custom identifiers you have to do validation only depending on your use case. The whole point of custom identifiers is precisely that you have more control over what is going on when you want the server or the client to know what instance their opposites are referencing when sending a remote.

Not sure where this came from.

Simply stems from the fact that in a good chunk of network applications it is standard practice to send lightweight identifiers for objects instead of sending the object itself through the wire. “Isn’t that similar to what Studio does with sending only a Reference/UniqueIds?” Absolutely! But also much more similar to the “custom identifiers” approach in how it operates.

My main intent with my reply is helping the community and I strive to remove any incorrect information. Please let me know if you have any additional thoughts, or if anything else I have stated is factually incorrect, as I am here to learn, as much as anybody else!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.