Drawing Gui Optimisation Help

hello to anyone who’s seeing this. i’ve made a drawing gui in my roblox game however after a bit of usage it causes the game to lag A LOT. i’m pretty sure this is due to the excessive cloning and assets this scrip causes but i don’t know how to optimise/fix this.

please help!!

game for reference: doodl_io - Roblox

-- add max per layer

local userInput = game:GetService("UserInputService")

local isMouseDown = false -- Track the mouse state

local UserInputService = game:GetService("UserInputService")
local frame = script.Parent -- Replace with your target GUI object
local brushTemplate = script.Parent.Brush -- Reference to the brush template

local previousPosition = nil -- Store the previous position for interpolation

-- Detect when the mouse button is pressed
userInput.InputBegan:Connect(function(input, gameProcessed)
	if gameProcessed then return end -- Ignore inputs when UI or other game features consume them

	if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
		isMouseDown = true
		script.Parent.Held.Value = true

	end
end)

-- Detect when the mouse button is released
userInput.InputEnded:Connect(function(input, gameProcessed)
	if gameProcessed then return end

	if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
		isMouseDown = false
		script.Parent.Held.Value = false
		previousPosition = nil
	end
end)

-- Example: Check mouse state continuously
game:GetService("RunService").RenderStepped:Connect(function()
	if isMouseDown then
		script.Parent.Held.Value = true
	else
		script.Parent.Held.Value = false
		previousPosition = nil
	end
end)
game:GetService("RunService").RenderStepped:Connect(function()
	-- Drawing logic here
end)

UserInputService.InputChanged:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseMovement then

		local mouse = game.Players.LocalPlayer:GetMouse()
		local ContainerPosition = script.Parent.AbsolutePosition
		script.Parent.BrushStroke.Position = UDim2.new((mouse.X - ContainerPosition.X)/script.Parent.AbsoluteSize.X, 0, (mouse.Y - ContainerPosition.Y)/script.Parent.AbsoluteSize.Y, 0)

		if script.Parent.Held.Value == true then
			local mousePosition = UserInputService:GetMouseLocation()
			local containerPosition = frame.AbsolutePosition
			local containerSize = frame.AbsoluteSize

			-- Calculate the current position in UDim2
			local currentPosition = UDim2.new(
				(mouse.X - containerPosition.X) / containerSize.X,
				0,
				(mouse.Y - containerPosition.Y) / containerSize.Y,
				0
			)

			-- If there was a previous position, interpolate between it and the current position
			if previousPosition then
				local start = Vector2.new(
					previousPosition.X.Scale * containerSize.X,
					previousPosition.Y.Scale * containerSize.Y
				)
				local finish = Vector2.new(
					currentPosition.X.Scale * containerSize.X,
					currentPosition.Y.Scale * containerSize.Y
				)
				local distance = (finish - start).Magnitude
				local step = math.max(brushTemplate.AbsoluteSize.X / 5, distance / 100) -- Number of pixels between brush strokes

				-- Create interpolated brush strokes
				for i = 0, distance, step do
					local t = i / distance
					local interpolatedPosition = start:Lerp(finish, t)

					-- Convert interpolated position back to UDim2
					local interpolatedUDim2 = UDim2.new(
						interpolatedPosition.X / containerSize.X,
						0,
						interpolatedPosition.Y / containerSize.Y,
						0
					)

					if interpolatedUDim2.X.Scale < 1 or interpolatedUDim2.Y.Scale < 1 and interpolatedUDim2.X.Scale >= 0 or interpolatedUDim2.Y.Scale >= 0 then
						-- Create and position a brush clone
						local clone = brushTemplate:Clone()
						clone.Position = interpolatedUDim2
						clone.Parent = frame
					end					
				end
			end

			if currentPosition.X.Scale < 1 and currentPosition.Y.Scale < 1 and currentPosition.X.Scale >= 0 and currentPosition.Y.Scale >= 0 then
				-- Update the brush stroke and set the previous position
				local clone = brushTemplate:Clone()
				clone.Position = currentPosition
				clone.Parent = frame
				previousPosition = currentPosition
			end	

			script.Parent.BrushStroke.Visible = true

			-- Store for the next frame
		else
			previousPosition = nil
		end
	end
end)

UserInputService.TouchMoved:Connect(function(touch)
	print("touch moved")
	local touchPosition = Vector2.new(touch.Position.X, touch.Position.Y)
	local ContainerPosition = script.Parent.AbsolutePosition
	script.Parent.BrushStroke.Position = UDim2.new((touchPosition.X - ContainerPosition.X)/script.Parent.AbsoluteSize.X, 0, (touchPosition.Y - ContainerPosition.Y)/script.Parent.AbsoluteSize.Y, 0)

	if script.Parent.Held.Value == true then
		local containerPosition = frame.AbsolutePosition
		local containerSize = frame.AbsoluteSize

		-- Calculate the current position in UDim2
		local currentPosition = UDim2.new(
			(touchPosition.X - containerPosition.X) / containerSize.X,
			0,
			(touchPosition.Y - containerPosition.Y) / containerSize.Y,
			0
		)

		-- If there was a previous position, interpolate between it and the current position
		if previousPosition then
			local start = Vector2.new(
				previousPosition.X.Scale * containerSize.X,
				previousPosition.Y.Scale * containerSize.Y
			)
			local finish = Vector2.new(
				currentPosition.X.Scale * containerSize.X,
				currentPosition.Y.Scale * containerSize.Y
			)
			local distance = (finish - start).Magnitude
			local step =  math.max(brushTemplate.AbsoluteSize.X / 5, distance / 100) -- Number of pixels between brush strokes

			-- Create interpolated brush strokes
			for i = 0, distance, step do
				local t = i / distance
				local interpolatedPosition = start:Lerp(finish, t)

				-- Convert interpolated position back to UDim2
				local interpolatedUDim2 = UDim2.new(
					interpolatedPosition.X / containerSize.X,
					0,
					interpolatedPosition.Y / containerSize.Y,
					0
				)

				if interpolatedUDim2.X.Scale < 1 or interpolatedUDim2.Y.Scale < 1 and interpolatedUDim2.X.Scale >= 0 or interpolatedUDim2.Y.Scale >= 0 then
					-- Create and position a brush clone
					local clone = brushTemplate:Clone()
					clone.Position = interpolatedUDim2
					clone.Parent = frame
				end					
			end
		end

		if currentPosition.X.Scale < 1 and currentPosition.Y.Scale < 1 and currentPosition.X.Scale >= 0 and currentPosition.Y.Scale >= 0 then
			-- Update the brush stroke and set the previous position
			local clone = brushTemplate:Clone()
			clone.Position = currentPosition
			clone.Parent = frame
			previousPosition = currentPosition
		end	

		script.Parent.BrushStroke.Visible = true
	else
		previousPosition = nil
	end
end)

--[[UserInputService.InputChanged:Connect(function(input)
	print("Input Type:", input.UserInputType, "Position:", input.Position)
end)]]

--[[
		local mouse = game.Players.LocalPlayer:GetMouse()
		local ContainerPosition = script.Parent.AbsolutePosition
		script.Parent.BrushStroke.Position = UDim2.new((mouse.X - ContainerPosition.X)/script.Parent.AbsoluteSize.X, 0, (mouse.Y - ContainerPosition.Y)/script.Parent.AbsoluteSize.Y, 0)

		if script.Parent.Held.Value == true then
			local mousePosition = UserInputService:GetMouseLocation()
			local containerPosition = frame.AbsolutePosition
			local containerSize = frame.AbsoluteSize

			-- Calculate the current position in UDim2
			local currentPosition = UDim2.new(
				(mouse.X - containerPosition.X) / containerSize.X,
				0,
				(mouse.Y - containerPosition.Y) / containerSize.Y,
				0
			)

			-- If there was a previous position, interpolate between it and the current position
			if previousPosition then
				local start = Vector2.new(
					previousPosition.X.Scale * containerSize.X,
					previousPosition.Y.Scale * containerSize.Y
				)
				local finish = Vector2.new(
					currentPosition.X.Scale * containerSize.X,
					currentPosition.Y.Scale * containerSize.Y
				)
				local distance = (finish - start).Magnitude
				local step = math.max(brushTemplate.AbsoluteSize.X / 5, distance / 100) -- Number of pixels between brush strokes

				-- Create interpolated brush strokes
				for i = 0, distance, step do
					local t = i / distance
					local interpolatedPosition = start:Lerp(finish, t)

					-- Convert interpolated position back to UDim2
					local interpolatedUDim2 = UDim2.new(
						interpolatedPosition.X / containerSize.X,
						0,
						interpolatedPosition.Y / containerSize.Y,
						0
					)

					if interpolatedUDim2.X.Scale < 1 or interpolatedUDim2.Y.Scale < 1 and interpolatedUDim2.X.Scale >= 0 or interpolatedUDim2.Y.Scale >= 0 then
						-- Create and position a brush clone
						local clone = brushTemplate:Clone()
						clone.Position = interpolatedUDim2
						clone.Parent = frame
					end					
				end
			end

			if currentPosition.X.Scale < 1 and currentPosition.Y.Scale < 1 and currentPosition.X.Scale >= 0 and currentPosition.Y.Scale >= 0 then
				-- Update the brush stroke and set the previous position
				local clone = brushTemplate:Clone()
				clone.Position = currentPosition
				clone.Parent = frame
				previousPosition = currentPosition
			end	

			script.Parent.BrushStroke.Visible = true

			-- Store for the next frame
		else
			previousPosition = nil
		end]]

--[[-- Function to calculate the mouse's UDim2 position
local function getMouseUDim2()
	-- Get the mouse's absolute position
	local mousePos = UserInputService:GetMouseLocation()

	-- Get the frame's position and size
	local framePos = frame.AbsolutePosition
	local frameSize = frame.AbsoluteSize

	-- Calculate the relative offset position
	local relativeX = mousePos.X - framePos.X
	local relativeY = mousePos.Y - framePos.Y

	-- Ensure the position is within the frame
	if relativeX < 0 or relativeY < 0 or relativeX > frameSize.X or relativeY > frameSize.Y then
		return nil -- Mouse is outside the frame
	end

	-- Calculate scale (percentage)
	local scaleX = relativeX / frameSize.X
	local scaleY = relativeY / frameSize.Y
	
	-- Construct a UDim2
	return UDim2.new(scaleX, 0, scaleY, 0)
end

UserInputService.InputChanged:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseMovement then
		-- Set the clone's position
		local mouseUDim2 = getMouseUDim2()
		if not mouseUDim2 then
			script.Parent.Held.Value = false
			previousPosition = nil
		end
	end
end)]]

also sorry the code is so long. i havent condesed it yet :sweat_smile:

i noticed that u haven’t included the code for drawing. assuming you’re using plain individual frames for this, i’ve seen others try that, but frames aren’t really designed for art games. i’d suggest using either canvas draw or roblox’s new editable images.

thanks, ive already been made away of editable images but unfortunately i cant use them becuase im cant verify my age.

do you think they’ll remove the restriction in the future though?

i’m not sure. i want to think that they will but they probably won’t, under the guise of “moderation and user safety”

oh :melting_face:
thanks anyways though, ill just find something else to work on