What is the most efficient way to trigger a script when a player is a certain distance from an object?

I have several objects with scripts I want to run whenever a player get’s within a certain distance from them.

As far as I have researched there are two ways to do this:

  1. Make an invisible “hitbox” part around the object and use Touched and TouchEnded events
  2. Make a while loop that continuously scans the distance to all players using magnitude()

Am I right in thinking that if you have a lot of objects #2 would soon take up a lot of resources compared to #1?

Are there even better ways to do this?

2 Likes

I suggest doing something like this

local plr = game.Players.LocalPlayer
local char = plr.Character or plr.CharacterAdded:Wait()
local hum = char:WaitForChild("Humanoid")

hum:GetPropertyChangedSignal("MoveDirection"):Connect(function()
    if (char.PrimaryPart.Position - SomePositionHere).mangitude < 50 then
       --do something here
    end
end)

That requires local script but works just fine

6 Likes

So if I have 100 objects and 16 players moving around. All 100 objects should run that code all the time? won’t that “clog up” the system? That would be like 1600 operations every frame. Seems way less efficient than having a Touch-Event that only fires when someone enters? What am I missing?

You should use :GetPropertyChangedSignal on the Character’s root CFrame, MoveDirection’s Changed RBXScriptSignal will only fire if the player starts walking, stops or changes it’s walking direction, so sometimes it won’t detect the player.
Keep in mind this should be done on the client.

Yes, no. 2 would be too slow if you have like 100s of 1000s of objects. Use no. 1 if you can, otherwise use a spatial partitioning scheme to trade a bit of memory usage for execution time, e.g. quadtree or octree.

1 Like

instead of putting all the load on the server each player should take responsibility for them self and tell the server when they are within range

and instead of blindly looping we can use the camera focus property to detect when the player has moved

we can also make it so that they have to move a few studs before checking the distance again in this demo i made them have to move 4 studs before checking the magnitude but that might be overkill because getting the magnitude to the object is not that expensive but i thought to include it just so you know how its done

-- server script
local event = game.ReplicatedStorage.RemoteEvent
event.OnServerEvent:Connect(function(player)
    print(player, "is now in range")
end)

-- local scipt
local event = game.ReplicatedStorage.RemoteEvent
local camera = workspace.CurrentCamera
local position = Vector3.new(math.huge, math.huge, math.huge)
local object = workspace.Part
local inRange = false
local distance = 50

camera:GetPropertyChangedSignal("Focus"):Connect(function()
    -- if the player has not moved more then 4 studs then exit the function
    if (position - camera.Focus.Position).Magnitude < 4 then return end
    -- if the player has moved more then 4 studs then update the position
    position = camera.Focus.Position

    -- get how far the player is from the object
    local magnitude = (object.Position - position)

    if inRange == false then
        if magnitude < distance then
            inRange = true
            event:FireServer()
        end
    else
        if magnitude > distance then
            inRange = false
        end
    end
end)

Hmm, I guess that could work, but again, if you have a lot of different parts then you would have to write some kind of code to get a list of all the relevant objects. Sounds like it could be pretty messy.

You just put all your “different parts” into a folder and do as following

local PartToDetectWithinRange = Folder:FindFirstChild(Part)

if PartToDetectWithinRange and (position - PartToDetectWithinRange.Position).Magniture < Range then
--Run code here, you can even assign the part some attributes to make some specific code for some parts.
end

Or you could predefine a dictionary and do: (In this case, it will rely on the part.name

local function FunctionA(ValueA,ValueB)
print(ValueA,ValueB)
end
local function FunctionA(ValueA,ValueB)
print(ValueA,ValueB)
end

local DictionaryOfParts = {
 ["Bed"] = {"SpecificFunction"=FunctionA}
 ["Checkpoint"] = {"SpecificFunction"=FunctionB}
 ["Mountain"] = {"SpecificFunction"=false}
}

local PartToDetectWithinRange = DictionaryOfParts[Part.Name]

if PartToDetectWithinRange and (position - PartToDetectWithinRange.Position).Magniture < Range then
 --Run code here
local SpecificFunction = DictionaryOfParts[Part.Name]["SpecificFunction"]
 if SpecificFunction then
  SpecificFunction("Hello", "World") -- This is a function we defined, as a test we send 2 values :)
 end
end

here is how to do it with multiple objects

-- server script
local event = game.ReplicatedStorage.RemoteEvent
event.OnServerEvent:Connect(function(player, part)
    print(player, "is now in range of", part.Name)
end)

-- local scipt
local event = game.ReplicatedStorage.RemoteEvent
local camera = workspace.CurrentCamera
local position = Vector3.new(math.huge, math.huge, math.huge)
local inRange = {}
local distance = 50

camera:GetPropertyChangedSignal("Focus"):Connect(function()
    -- if the player has not moved more then 4 studs then exit the function
    if (position - camera.Focus.Position).Magnitude < 4 then return end
    -- if the player has moved more then 4 studs then update the position
    position = camera.Focus.Position

    for i, child in ipairs(workspace.Folder:GetChildren()) do
        -- get how far the player is from the child
        local magnitude = (child.Position - position)

        if inRange[child] == nil then
            if magnitude < distance then
                inRange[child] = true
                event:FireServer(child)
            end
        else
            if magnitude > distance then
                inRange[child] = nil
            end
        end
    end
end)

its also possible to do a chunk system where you group parts into chunks so that you only loop the objects in the chunks around the player but that’s a bit more complex i hope to make a video on my YouTube channel in the future explaining how

2 Likes

Hi! Remember to make sure objects is present when the code runs. :slight_smile:

local event = game:GetService("ReplicatedStorage"):WaitForChild("RemoteEvent")

Please, please, please explain how I would do this. I know there are already modules on the site about it, but I want to know HOW. See my game is extremely laggy, because I have an incredible amount of parts and every frame I’m doing check to see whether they’re overlapping or not. part2.Position - part1.Position).Magnitude < part2.Size.Z / 2 + part1.Size.Z / 2 to the max.

And I’m doing this for on an average basis HUNDREDS AND HUNDREDS of parts every 1/60th of a second, I would rather use memory.

Use WorldRoot | Roblox Creator Documentation and only check parts returned by that. Should be lots faster because Roblox internally uses a spatial data structure for those types of queries (or they should, anyway).