Help optimizing Local Script to show GUIs on proximity

The NPCs in my game have a surrounding circle, called ‘Proximity Aura’. When you enter it, the designed PopUp (UI) of that NPC should appear in your screen. My code works, but it’s too slow. Ideas as to why? Help is appreciated ;D

Proximity local script:

local Fetch: { [string]: () -> () } = _G.Fetch;
local S, GUI, Notify, Run = Fetch.Script({ 'Services', 'GUIManager', 'Notifier', 'Runner' });

local WS: Workspace, P: Players = S.WS, S.P;


local Player = P.LocalPlayer;
local Character = Player.Character or Player.CharacterAdded:Wait();

local Interface = Player.PlayerGui:WaitForChild('Interface', 10.0);

local Information = { };



local function CheckDistance(ID: number): ()
        local Distance = (Character.HumanoidRootPart.Position - Information[ID][1]).Magnitude;

        local P = Information[ID][2];
        local PopUp = Interface.PopUps:FindFirstChild(P);

        if PopUp then
                if Distance <= 11.0 then
                        print(Distance)
                        GUI.Open(P, false);
                else
                        if PopUp.ManuallyOpened.Value == false then
                                GUI.Close(P);
                        end
                end
        else
                if Distance <= 11.0 then
                        Notify(Player, 'Coming Soon', 'Come back again later in order to uncover the secrets behind this feature!'); 
                end
        end
end



for i, Object in ipairs(WS:GetDescendants()) do
        if Object.Name == 'Proximity Aura' then
                Information[i] = { Object.Details.Position, Object.Details.Parent.Parent.Name };
                
                Run(CheckDistance, 'CheckDistance', 1.0, 'Heartbeat', { i });
        end
end

GUIManager module:

local GUI: { [string]: () -> () } = { };



local ed = Enum.EasingDirection;
local es = Enum.EasingStyle;


local Fetch: { [string]: () -> () } = _G.Fetch;
local MouseInteraction =  Fetch.Event({ 'MouseInteraction' });
local S, Debounce, Database, Check = Fetch.Script({ 'Services', 'Debouncer', 'DatabaseManager', 'Checker' });

local RSS: ReplicatedStorage, TS: TweenService, P: Players = S.RSS, S.TS, S.P;


local TI1 = TweenInfo.new(0.5, es.Circular, ed.Out);
local TI2 = TweenInfo.new(0.5, es.Circular, ed.Out);
local TI3 = TweenInfo.new(0.50, es.Circular, ed.Out);

-- local TI4 = TweenInfo.new(5.0, es.Quad, ed.Out);
-- local TI5 = TweenInfo.new(4.0, es.Quad, ed.Out);
local TI4 = TweenInfo.new(0.8, es.Quad, ed.Out);

local TI5 = TweenInfo.new(1.0, es.Quint, ed.InOut);
local TI6 = TweenInfo.new(0.3, es.Quint, ed.InOut);

local TweenSettings1 = { Position = UDim2.new(-0.150, 0, -0.3640, 0) };
local TweenSettings2 = { Position = UDim2.new(0.5920, 0, -0.3640, 0) };

local TweenSettings3 = { Size = UDim2.new(0.30, 0, 0.05, 0) }
local TweenSettings4 = { Position = UDim2.new(0.50, 0,  0.05, 0) }
local TweenSettings5 = { Size = UDim2.new(0.6, 0, 0.6, 0) }
local TweenSettings6 = { Position = UDim2.new(0.5, 0,  0.5, 0) };

local TweenSettings7 = { Position = UDim2.new(1.31, 0, 0.50, 0) };
local TweenSettings8 = { Position = UDim2.new(1.16, 0, 0.05, 0) };


local OriginalSizeY;
local TransparencyTable = { };
local FadeTweens = { }; 
local PropertyMap = {
        [' O'] = 'Transparency',
        [' I'] = 'ImageTransparency',
        [' T'] = 'TextTransparency',
        [' TS'] = 'TextStrokeTransparency',
        [' BG'] = 'BackgroundTransparency',
        [' SB'] = 'ScrollBarImageTransparency',
};


local Sounds = RSS:WaitForChild('Library', 10.0).Sounds;        

local Player = P.LocalPlayer;



local function Play(Player: Player, Sound: string): ()
        local SoundObject = Sounds:WaitForChild(Sound, 10.0);
        local SFX = Fetch.Setting(Player, { 'SFX' });

        if SFX then
                SoundObject:Play();
        end
end

local function AddTween(Object: Instance, Key: string, IsMinimized: boolean, Type: string): ()
        local Settings = { };
        local DefaultValue = 1.0;

        if IsMinimized then
                DefaultValue = TransparencyTable[Key] or 1.0;
        end

        local Property = PropertyMap[Type];

        if Property then
                Settings[Property] = DefaultValue;

                pcall(function(): boolean
                        table.insert(FadeTweens, TS:Create(Object, TI4, Settings));
                end);
        end
end

local function TweenSlider(Button: Instance): ()
        if Debounce('SettingsState Change', 0.5) then return end

        local GFX = GUI.GetInfo();

        local Circle = Button.Parent.Circle;
        local SettingName = Circle.Parent.Parent.Name;

        local Setting = Fetch.Setting(Player, { SettingName });
        local PlayerSettings = RSS:WaitForChild("Players", 10.0):WaitForChild(Player.UserId, 10.0);
        local SettingObject = PlayerSettings.Settings.Audiovisual:WaitForChild(SettingName, 10.0);

        local Enabled = SettingObject.Value;

        if GFX then
                local Tween1 = TS:Create(Circle, TI1, TweenSettings1);
                local Tween2 = TS:Create(Circle, TI2, TweenSettings2);

                local function AnimateColor(ColorN: Color3): ()
                        local TweenSettings3 = {
                                BackgroundColor3 = ColorN
                        };                                
                        local Tween3 = TS:Create(Circle, TI3, TweenSettings3);

                        Tween3:Play();
                end

                if Enabled then
                        AnimateColor(Color3.fromRGB(188.0, 137.0, 225.0));
                        Tween1:Play()
                else 
                        AnimateColor(Color3.fromRGB(119.0, 225.0, 207.0));
                        Tween2:Play()
                end
        else
                if Enabled then
                        Circle.Position = UDim2.new(-0.150, 0.0, -0.3640, 0.0);
                        Circle.BackgroundColor3 = Color3.fromRGB(188.0, 137.0, 225.0);
                else
                        Circle.Position = UDim2.new(0.5920, 0.0, -0.3640, 0.0);
                        Circle.BackgroundColor3 = Color3.fromRGB(119.0, 225.0, 207.0);
                end
        end

        SettingObject.Value = not SettingObject.Value;        
end

local function Shadering(Shader: Frame, GFX: boolean, Transparency: number, Visibility: boolean): ()
        task.spawn(function(): ()
                Shader.Visible = true;
                
                if GFX then
                        Shader.BackgroundTransparency = 1.0;
                        
                        local Tween = TS:Create(Shader, TI6, { Transparency = Transparency });
                        Tween:Play();
                        
                        task.wait(TI6.Time);
                else
                        Shader.BackgroundTransparency = Transparency;
                end
                
                Shader.Visible = Visibility;
        end);
end

function GUI.EnableShader(Shader: Frame, GFX: boolean): ()
        Shadering(Shader, GFX, 0.30, true);
end

function GUI.DisableShader(Shader: Frame, GFX: boolean): ()
        Shadering(Shader, GFX, 1.0, false);
end

function GUI.GetInfo(PopUpName: string?): any
        local GFX = Fetch.Setting(Player, { 'GFX' });
        if not PopUpName then return GFX end

        local PopUp, Shader = Fetch.UI(Player, { PopUpName, 'Shader' }, 'Unpacked');

        return GFX, PopUp, Shader; 
end

function GUI.Resize(Object: Instance, Amplification: number,  Time: number?): ()
        Time = Time or 0.2;

        local SizeX = Object.Parent.Size.X.Scale;
        local SizeY = OriginalSizeY or Object.Parent.Size.Y.Scale;
        OriginalSizeY = SizeY;

        Object.Parent:TweenSize(UDim2.new(SizeX * Amplification, 0, SizeY * Amplification, 0), ed.InOut, es.Quint, Time);
end

local function ChangeState(PopUpName: string, SizeSettings: { }, PositionSettings: { }, SizeX: number, SizeY: number, Text: string): ()
        if Debounce('PopUpState Change', 2.0) then return end

        local GFX, Frame, Shader = GUI.GetInfo(PopUpName);

        local CloseFrame, MinimizeFrame, MinimizeButton, Minimized, Title = Frame.Close, Frame.Minimize, Frame.Minimize.MinimizeButton, Frame.Minimize.Minimized, Frame.Title;
        local Separator, UICorner, ManuallyOpened = Frame.Separator, Frame.UICorner, Frame.ManuallyOpened;

        local IsMinimized = Minimized.Value;

        local SizeTween = TS:Create(Frame, TI4, SizeSettings);
        local PositionTween = TS:Create(Frame, TI4, PositionSettings);
        local SeparatorTween = TS:Create(Separator, TI4, { Size = UDim2.new(SizeX, 0, 0, 0) });

        local AdditionalTweens = {
                TS:Create(CloseFrame, TI4, { Size = UDim2.new(0.1, 0, SizeY, 0) }),
                TS:Create(MinimizeFrame, TI4, { Size = UDim2.new(0.1, 0, SizeY, 0) }),
                TS:Create(Title, TI4, { Size = UDim2.new(0.76, 0, SizeY, 0) }),
                TS:Create(UICorner, TI4, { CornerRadius = UDim.new(SizeY, 0) }),
        };

        OriginalSizeY = SizeY;

        for _, Object in ipairs (Frame:GetDescendants()) do
                local Parent = Object.Parent or '';

                if (Parent ~= Frame and Parent ~= CloseFrame and Parent ~= MinimizeFrame and Parent ~= Separator) then
                        for Suffix, Property in pairs (PropertyMap) do
                                local Attribute = Object:GetAttribute('ID');
                                local HasProperty, Value = table.unpack(Check.ForProperty({ Object }, Property));

                                if  HasProperty then
                                        local Key = Object.Name .. Attribute .. Suffix;

                                        AddTween(Object, Key, IsMinimized, Suffix);
                                        TransparencyTable[Key] = Value;
                                end
                        end
                end
        end

        if IsMinimized and IsMinimized ~= 'NaN' then
                Database.SetFolderData(Player, { ['Opened'] = 'NaN' });
                Database.SetFolderData(Player, { ['Minimized'] = (Frame.Name)} );

                PositionTween:Play();
                PositionTween.Completed:Wait();

                SizeTween:Play();

                task.wait(TI4.Time / 8);

                Separator.Visible = true;
                SeparatorTween:Play();

                for i, Tween in ipairs(AdditionalTweens) do
                        Tween:Play();

                        if #AdditionalTweens == i then
                                Tween.Completed:Wait();
                        end
                end

                MinimizeButton.Text = Text;

                for i, Tween in ipairs(FadeTweens) do
                        Tween:Play();
                end
        else
                Database.SetFolderData(Player, { ['Opened'] = (Frame.Name) } );
                Database.SetFolderData(Player, { ['Minimized'] = 'NaN' });
                
                SeparatorTween:Play();

                for i, Tween in ipairs(FadeTweens) do
                        Tween:Play();

                        if #FadeTweens == i then
                                Tween.Completed:Wait();
                        end
                end

                SizeTween:Play();

                for i, Tween in ipairs(AdditionalTweens) do
                        Tween:Play();

                        if #AdditionalTweens == i then
                                Tween.Completed:Wait();
                        end
                end

                MinimizeButton.Text = Text;

                SizeTween.Completed:Wait();
                PositionTween:Play();
        end

        table.clear(FadeTweens);
        table.clear(AdditionalTweens);

        task.wait(TI4.Time / 4);

        if IsMinimized then
                GUI.EnableShader(Shader, GFX);
        else
                Separator.Visible = false;

                GUI.DisableShader(Shader, GFX);     
        end

        Minimized.Value = not Minimized.Value;
end

function GUI.Minimize(PopUpName: string)
        ChangeState(PopUpName, TweenSettings3, TweenSettings4, 0.0, 1.0, '+');
end

function GUI.Maximize(PopUpName: string)
        ChangeState(PopUpName, TweenSettings5, TweenSettings6, 0.95, 0.1, '-');
end

function GUI.Close(PopUpName: string): ()
        if Debounce('PopUp', TI6.Time * 2.0) then return end
        
        local GFX, Frame, Shader = GUI.GetInfo(PopUpName);
        
        local ManuallyOpened = Frame.ManuallyOpened;
        local Minimized = Frame.Minimize.Minimized;
        local IsMinimized = Minimized.Value;
        
        local Tween;
        
        ManuallyOpened.Value = false;

        if IsMinimized then
                Database.SetFolderData(Player, { ['Minimized'] = 'NaN' });
                Tween = TS:Create(Frame, TI6, TweenSettings8);                         
        else
                Database.SetFolderData(Player, { ['Opened'] = 'NaN' });
                Tween = TS:Create(Frame, TI6, TweenSettings7);
        end

        Tween:Play();

        GUI.DisableShader(Shader, GFX);

        task.spawn(function(): ()
                task.wait(1.5)

                if IsMinimized then
                        GUI.Maximize(PopUpName);
                end

                Frame.Visible = false;
        end);
end

function GUI.Open(PopUpName: string, Manual: boolean): ()
        if Debounce('PopUp', TI6.Time * 2.0) then return end
        
        local GFX, Frame, Shader = GUI.GetInfo(PopUpName);
        local ManuallyOpened = Frame.ManuallyOpened;
        local IsMinimized = Frame.Minimize.Minimized.Value;
        
        local O, M = Fetch.Setting(Player, { 'Open', 'Minimized' });

        if O ~= 'NaN' then
                if M ~= 'NaN' then
                        GUI.Close((Frame.Parent:FindFirstChild(M).Name));
                end
                
                local FrameO = Frame.Parent:FindFirstChild(O);
                
                if not FrameO.ManuallyOpened.Value then
                        GUI.Close(FrameO.Name);
                else
                        GUI.Minimize(FrameO.Name);
                end

                Database.SetFolderData(Player, { ['Opened'] = (Frame.Name)} );
                Database.SetFolderData(Player, { ['Minimized'] = O});
        else
                Database.SetFolderData(Player, { ['Opened'] = (Frame.Name) });
        end

        ManuallyOpened.Value = Manual;

        Frame.Position = TweenSettings7.Position;
        Frame.Visible = true;

        GUI.EnableShader(Shader, GFX);

        if IsMinimized then
                GUI.Maximize(PopUpName);
                
                task.wait(1.5);
        end
        
        local Tween = TS:Create(Frame, TI6, TweenSettings6);
        Tween:Play();
end

function GUI.Animate(CallType: number, Button: Instance, PopUpName: string, EventName: string?, Arguments: { }?): ()
        if CallType == 0.0 then
                 GUI.Resize(Button, 1.050);
        elseif CallType == 2.0 then
                GUI.Resize(Button, 0.950);
        elseif CallType >= 1.0  then
                GUI.Resize(Button, 1.0);

                if CallType == 1.5 then
                        Play(Player, 'Click');
                        
                        if EventName == 'Open' then
                                GUI.Open(PopUpName, true);
                        elseif EventName == 'Close' then
                                GUI.Close(PopUpName);
                        elseif EventName == 'Maximize' then
                                GUI.Maximize(PopUpName);
                        elseif EventName == 'Minimize' then
                                GUI.Minimize(PopUpName);
                        elseif EventName == 'Slide' then
                                TweenSlider(Button);
                        else
                                if Arguments then
                                        local NewArguments = { EventName, table.unpack(Arguments) };

                                        MouseInteraction:FireServer(NewArguments); 
                                else
                                        MouseInteraction:FireServer({ EventName });
                                end
                        end
                end
        end
end



return GUI;
1 Like

My only suggestion as of current is to use Tags on your Proximity Aura objects and use CollectionService to find them as opposed to iterating over every descendant in the workspace and checking name.

It’s a little hard for us to understand your code fully without seeing the code behind the things like Fetch, Run, Notify, etc.

Additionally, could you maybe do some performance profiling to see when you’re losing frames? Or use a timer within your script to try find the slowest execution times in your code.

2 Likes