Gui drawing system (that doesn't lag)

so basically i want to make a gui drawing system for my game but the on ive made currently lags after just a couple of strokes. does anyone have any suggestions?

game for reference: doodl_io - Roblox

my current code:

-- 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)]]
1 Like

I assume you are just copying a pre-made asset script.Parent.Brush. Consider using EditableImages instead of just putting thousands of images on the screen.

1 Like

Adding onto this, there’s a library which makes this easy to do whilst being quite performant:

1 Like

Unfortunately i cant uae those because you have to verify with you id. Thanks though.

Wait, do you know if theyre going to release the feature to everyone later though or just keep.it verify only.

They will probably keep it id verified only.

oh, what a shame. it would’ve been so useful

1 Like