Optimizing a gui compass script

Hello,

Recently, i have created a gui compass script. It functions perfectly but there’s only one problem with it, it takes up quite a bit of script activity. 0.2 - 0.5% when camera does not move or rotate, and spiking up to 9 - 11% when i rotate the camera quickly. So I’ve come here to ask anyone good with optimizations on identifying this potential memory eating calculation, and maybe help reducing the script activity.

The Code:

local Run = game:GetService("RunService")

local Camera = workspace.CurrentCamera

local ScreenGui = script.Parent.Parent
local CompassHolder = ScreenGui.CompassHolder

local TemplatePointer = CompassHolder.Template

local Pointers = {}

local Directions = {
	N = -Vector3.zAxis,
	W = Vector3.xAxis,
	S = Vector3.zAxis,
	E = -Vector3.xAxis ,
	NW = (-Vector3.zAxis + Vector3.xAxis),
	SW = (Vector3.zAxis + Vector3.xAxis),
	NE = (-Vector3.zAxis - Vector3.xAxis),
	SE = (Vector3.zAxis - Vector3.xAxis) 
}

local function ViewportPointToGuiOffset(PointX: number, PointY: number, Gui: GuiObject)	
	local x = PointX - Gui.AbsolutePosition.X
	local y = PointY - Gui.AbsolutePosition.Y
	
	return x, y
end

local function AddPointer(Name: string, PointerText: string, NewDirection: Vector3?)
	local newPointer = TemplatePointer:Clone()
	newPointer.Name = Name
	newPointer.TextLabel.Text = PointerText
	newPointer.Parent = CompassHolder
	
	if NewDirection and not Directions[Name] then
		Directions[Name] = NewDirection
	end

	table.insert(Pointers, newPointer)
end

local function Update(delta: number)
	local lookDir = Camera.CFrame.LookVector
	
	for _, pointer : Frame in Pointers do
		local direction = Directions[pointer.Name]
		local screenPoint : Vector3, _ = Camera:WorldToViewportPoint(Camera.CFrame.Position + direction)
		local x, _ = ViewportPointToGuiOffset(screenPoint.X, screenPoint.Y, CompassHolder)
		
		pointer.Position = UDim2.new(0, x, 1, 0)
		
		if lookDir:Dot(direction) > 0.1 then
			pointer.Visible = true
		else
			pointer.Visible = false
		end
	end
end

Run:BindToRenderStep("PositionUpdate", Enum.RenderPriority.Camera.Value + 1, Update)

--//One time initializations at runtime
--//Initial 8
for name, _ in Directions do
	AddPointer(name, name)
end

--//Every 15th degree
local BlockedNumbers = {
	0,
	90,
	180,
	270,
	360
}

for idx = 15, 360, 15 do
	if not table.find(BlockedNumbers, idx) then
		local rotated = CFrame.identity * CFrame.Angles(0, math.rad(idx), 0) * CFrame.new(0, 0, -1)
		AddPointer("Pointer" .. idx, "", rotated.Position)
	end
end

Thanks.

You’re recalculating the position for each pointer. The compass only has one rotation that only needs determined once per update. Try storing each pointers rotation offset from north then just calculating the north direction each time and rotating each pointer around that by each offset.

I cleaned up the code, but I do recommend restructuring it bit to implement the feedback that @azqjanna had.

Anything commented has been edited from the original.

local RunService = game:GetService("RunService") -- Use complete variable names for clarity

local Camera = workspace.CurrentCamera

local ScreenGui = script:FindFirstAncestorOfClass("ScreenGui") -- Edit made here. Avoid repeating .Parent
local CompassHolder = ScreenGui:WaitForChild("CompassHolder") -- Don't just do .CompassHolder since it is not guaranteed to be there immediately

local TemplatePointer = CompassHolder:WaitForChild("Template") -- Again, not guaranteed to be there

local Pointers = {}

local Directions = {
	N = -Vector3.zAxis,
	W = Vector3.xAxis,
	S = Vector3.zAxis,
	E = -Vector3.xAxis ,
	NW = (-Vector3.zAxis + Vector3.xAxis),
	SW = (Vector3.zAxis + Vector3.xAxis),
	NE = (-Vector3.zAxis - Vector3.xAxis),
	SE = (Vector3.zAxis - Vector3.xAxis) 
}

local function ViewportPointToGuiOffset(PointX: number, PointY: number, Gui: GuiObject)	
	local x = PointX - Gui.AbsolutePosition.X
	local y = PointY - Gui.AbsolutePosition.Y
	
	return x, y
end

local function AddPointer(Name: string, PointerText: string, NewDirection: Vector3?)
	local newPointer = TemplatePointer:Clone()
	newPointer.Name = Name
	newPointer.TextLabel.Text = PointerText
	newPointer.Parent = CompassHolder
	
	if NewDirection and not Directions[Name] then
		Directions[Name] = NewDirection
	end

	table.insert(Pointers, newPointer)
end

local function Update(delta: number)
	local lookDir = Camera.CFrame.LookVector
	
	for _, pointer : Frame in Pointers do
		local direction = Directions[pointer.Name]
		local screenPoint : Vector3, _ = Camera:WorldToViewportPoint(Camera.CFrame.Position + direction)
		local x, _ = ViewportPointToGuiOffset(screenPoint.X, screenPoint.Y, CompassHolder)
		
		pointer.Position = UDim2.new(0, x, 1, 0)
		
		if lookDir:Dot(direction) > 0.1 then
			pointer.Visible = true
		else
			pointer.Visible = false
		end
	end
end

RunService:BindToRenderStep("PositionUpdate", Enum.RenderPriority.Camera.Value + 1, Update)

--//One time initializations at runtime
--//Initial 8
for name, _ in Directions do
	AddPointer(name, name)
end

--//Every 15th degree
-- local BlockedNumbers = {
-- 	0,
-- 	90,
-- 	180,
-- 	270,
-- 	360
-- }

-- Don't reinvent the wheel. To check if it is divisble by 90, use modulo (%)

for idx = 15, 360, 15 do
	if idx % 90 ~= 0 then -- Edit, using modulo. If the remainder is not 0, not a multiple of 90
		local rotated = CFrame.identity * CFrame.Angles(0, math.rad(idx), 0) * CFrame.new(0, 0, -1)
		AddPointer("Pointer" .. idx, "", rotated.Position)
	end
end
2 Likes