You should handle visual aspects locally, and it makes sense if you want a player to see fog when in an area, that only they see the fog. So, you should use a LocalScript
and put it in either StarterPlayerScripts or PlayerGui.
Rather than using a while loop, you should use other methods of collision checking. Here I see using Part.Touched
and Part.TouchEnded
as being arguably one of the most effective methods.
Because this is a LocalScript somewhere in the Player, we need to set a variable to the part we want to collision detect.
local FogPartOutside = workspace.ImportantAssetStorage.FogPartOutside
…
But if you have multiple of these it would be worth setting up a system where it loops through all of them, so I’ll do that too. This will loop through the ImportantAssetStorage folder and insert an element for each child named “FogPartOutside” into the FogParts
table.
What I will do then is loop through the table and connect .Touched
and .TouchEnded for each element, and store the connetions so we can disconnect the Touched initially, preventing stacking.
…
First we will set it up like this:
local Player = game:GetService("Players").LocalPlayer -- The player whose client is running the game
local Lighting = game:GetService("Lighting")
local FogParts = {} -- Fog parts which will be set after looping through ImportantAssetStorage
local Connections = {} -- We will store the .Touched connections here so we can disconnect them afterwards, making it only activate when initially touched
for i,v in pairs(workspace:WaitForChild("ImportantAssetStorage"):GetChildren()) do
if v.Name == "FogPartOutside" then
table.insert(FogParts, v)
end
end
From there we need to set up the functions we will be using, we can still use the old ones you were using before, but we need to add a bit of code to the EnableFog
function to disconnect the .Touched
connection of the part that called it.
local function EnableFog(Zone)
if Connections[Zone] then -- Ensures that the touch only happens initially
Connections[Zone]:Disconnect()
Connections[Zone] = nil
end
Lighting.FogColor = Color3.fromRGB(0, 0, 15)
Lighting.FogEnd = 750
Lighting.Brightness = 0
Lighting.Ambient = Color3.fromRGB(0, 0, 0)
Lighting.OutdoorAmbient = Color3.fromRGB(0, 0, 0)
Lighting:SetMinutesAfterMidnight(300)
end
local function DisableFog()
Lighting.FogColor = Color3.fromRGB(40, 70, 100)
Lighting.FogEnd = 1000
Lighting.FogStart = 0
Lighting.Brightness = 0
Lighting.Ambient = Color3.fromRGB(138, 138, 138) -- Added this because it wasn't reset before, you can change the default
Lighting.OutdoorAmbient = Color3.fromRGB(128, 128, 128) -- Added this because it wasn't reset before, you can change the default
-- Lighting:SetMinutesAfterMidnight(?) -- You might want to reset this as well
end
Now that we have this set up we can loop through the fog detection parts and connect their .Touched
and .TouchEnded
events.
for i,v in pairs(FogParts) do
v.CanTouch = true -- Make sure that the part can trigger .Touched and .TouchEnded events
-- Putting this in a function so we don't have to reuse code
local function Connect()
Connections[v] = v.Touched:Connect(function(Hit) -- When the player is in the part / stored in a table with the index being the part, so that it can be referenced in the EnableFog function
if Hit:IsDescendantOf(Player.Character) then -- Makes sure the LocalPlayer touched the part and not any other player
EnableFog(v) -- Enables the fog, passing an argument of the part, which is used in the function parameter to disable it's connection from the Connections table and prevent stacking
end
end)
end
Connect() -- Call the function once to create the initial .Touched event
v.TouchEnded:Connect(function(Hit) -- When the player is no longer inside the part
if Hit:IsDescendantOf(Player.Character) then -- Makes sure the LocalPlayer stopped touching the part and not any player
DisableFog() -- Disables the fog
Connect() -- Calls the function to create a new .Touched event, as the last one was disconnected after the EnableFog function was called
end
end)
end
…
So our entire script looks like this
local Player = game:GetService("Players").LocalPlayer
local Lighting = game:GetService("Lighting")
local FogParts = {}
local Connections = {}
for i,v in pairs(workspace.ImportantAssetStorage:GetChildren()) do
if v.Name == "FogPartOutside" then
table.insert(FogParts, v)
end
end
local function EnableFog(Zone)
if Connections[Zone] then -- Ensures that the touch only happens initially
Connections[Zone]:Disconnect()
Connections[Zone] = nil
end
Lighting.FogColor = Color3.fromRGB(0, 0, 15)
Lighting.FogEnd = 750
Lighting.Brightness = 0
Lighting.Ambient = Color3.fromRGB(0, 0, 0)
Lighting.OutdoorAmbient = Color3.fromRGB(0, 0, 0)
Lighting:SetMinutesAfterMidnight(300)
end
local function DisableFog()
Lighting.FogColor = Color3.fromRGB(40, 70, 100)
Lighting.FogEnd = 1000
Lighting.FogStart = 0
Lighting.Brightness = 0
Lighting.Ambient = Color3.fromRGB(138, 138, 138) -- Added this because it wasn't reset before, you can change the default
Lighting.OutdoorAmbient = Color3.fromRGB(128, 128, 128) -- Added this because it wasn't reset before, you can change the default
-- Lighting:SetMinutesAfterMidnight(?) -- You might want to reset this as well
end
for i,v in pairs(FogParts) do
v.CanTouch = true -- Make sure that the part can trigger .Touched and .TouchEnded events
local function Connect() -- Putting this in a function so we don't have to reuse code
Connections[v] = v.Touched:Connect(function(Hit) -- When the player is in the part / stored in a table with the index being the part, so that it can be referenced in the EnableFog function
if Hit:IsDescendantOf(Player.Character) then -- Makes sure the LocalPlayer touched the part and not any other player
EnableFog(v) -- Enables the fog, passing an argument of the part, which is used in the function parameter to disable it's connection from the Connections table and prevent stacking
end
end)
end
Connect() -- Call the function once to create the initial .Touched event
v.TouchEnded:Connect(function(Hit) -- When the player is no longer inside the part
if Hit:IsDescendantOf(Player.Character) then -- Makes sure the LocalPlayer stopped touching the part and not any player
DisableFog() -- Disables the fog
Connect() -- Call the function to create a new .Touched event, as the last one was disconnected after the EnableFog function was called
end
end)
end
Voila, regional fog! (Note the lighting in the video is different initially because I didn’t set the properties the same as your DisableFog function. This shouldn’t be a problem)
All with a single script, and we can easily add more fog parts in the future.

It is imperative that this is a LocalScript and put somewhere in the Player, like in StarterGui
If there are any issues with this please let me know. Hopefully I helped 