InputChange to fire at a higher frequency

Hello,

I’m creating a drawing canvas. The issue I’m facing is that the InputChange function doesn’t fire fast enough, as such, there are weird gaps in my drawings as seen in this video:

Here’s how my code looks:

local player = game.Players.LocalPlayer
local frame = script.Parent

local dragging = false

-- turn on dragging when the mouse is pressed or the screen is touched
frame.InputBegan:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
		dragging = true
	end
end)

-- turn off dragging when its released
frame.InputEnded:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
		dragging = false
	end
end)

-- helper function to draw one point at the given x,y position
local function DrawAt(x, y)
	-- need the parent's position so we can subtract it from the child's
	local topLeft = frame.AbsolutePosition

	local point = Instance.new("Frame")
	point.Size = UDim2.fromOffset(10, 10)
	-- ... like this
	point.Position = UDim2.fromOffset(x - topLeft.X - 5, y - topLeft.Y - 5)
	point.BackgroundColor3 = Color3.new(0.991119, 0.549386, 0.255436)
	point.BorderSizePixel = 0
	point.Parent = frame
	
	local rounded = Instance.new("UICorner")
	rounded.CornerRadius = UDim.new(0.5, 0)
	rounded.Parent = point
end

-- fired when the mouse or finger moves
frame.InputChanged:Connect(function(input)
	if dragging and input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch then
		DrawAt(input.Position.X, input.Position.Y)
	end
end)
``

The problem is that you simply move your mouse too fast for ROBLOX to perfectly keep track. Even if you use some workarounds like RunService.Heartbeat, you will still see gaps in your line. It will probably decrease the size of the gaps, so it is definitely recommendable.

I recommend you to use RunService:BindToRenderstep(‘CanvasUpdate’, Enum.RenderPriority.Input.Value + 1, yourFunctionHere) with interpolation. Also, you keep adding new points with DrawAt. Maybe this can be optimized.

Or you could just use Runservice.Renderstepped to draw the frames. Every time the event fires, check if the current mouse position is different from the last mouse position, and if it is then use the mouses position to draw the frame.

Replace:

With:


local mouse = player:GetMouse()
local lastX, local lastY = 0,0
game:GetService("RunService").RenderStepped:Connect(function()
      if mouse.X ~= lastX and mouse.Y ~= lastY and dragging then
            lastX, lastY = mouse.X, mouse.Y
           DrawAt(mouse.X, mouse.Y)
      end
end)

I would use heartbeat or renderstepped instead of inputchanged for mouse/finger movement. It will fire at the fps of your screen so it should line up with the mouse better. Something like this but I would make sure to check that the position doesn’t equal the previous position. This is just a rough idea.

local mouse = game:GetService("Players").LocalPlayer:GetMouse()
game:GetService("RunService").Heartbeat:Connect(function()
	if dragging then
		DrawAt(mouse.X, mouse.Y)
	end
end)

The only thing that you will need to calculate now because it isn’t using frame input for the mouse but actually the mouse object within the player itself is that you will need to calculate where the mouse position on your screen is relative to the drawing frame and then do the necessary calculations to line up the draw spot. Otherwise you will need to do interpolation as Brickman suggested.

Just an update on how I solved this issue:

I have tried RunService.Heartbeat and RunService.RenderStepped but there were still gaps between my points. I resorted to creating filler lines between the points. Here’s how I did it:

-- helper function to draw one point at the given x,y position
local function DrawAt(x, y)
	-- need the parent's position so we can subtract it from the child's
	local topLeft = frame.AbsolutePosition
	
	if previousPosition then
		local fillerLine = Instance.new("Frame")
		
		local previousPointX = previousPosition.X
		local previousPointY = previousPosition.Y
		
		if previousPointX == x and previousPointY == y then
			return
		end
		
		local length = math.sqrt((x - previousPointX)^2 + (y - previousPointY)^2)
		local position = Vector2.new((x + previousPointX)/2, (y + previousPointY)/2)
		local opposite = y - previousPointY
		local adjacent = x - previousPointX
		local angleRadian = math.atan(opposite/adjacent)
		local angleDegree = angleRadian*180/math.pi
		
		fillerLine.Size = UDim2.fromOffset(length, strokeThickness)
		fillerLine.Position = UDim2.fromOffset(position.X - topLeft.X - length/2, position.Y - topLeft.Y - strokeThickness/2)
		fillerLine.Rotation = angleDegree
		fillerLine.BackgroundColor3 = strokeColor
		fillerLine.BorderSizePixel = 0
		fillerLine.Parent = frame
		
		local rounded = Instance.new("UICorner")
		rounded.CornerRadius = UDim.new(0.5, 0)
		rounded.Parent = fillerLine
	end

	previousPosition = Vector2.new(x, y)

	local newPoint = Instance.new("Frame")
	newPoint.Size = UDim2.fromOffset(strokeThickness, strokeThickness)
	newPoint.Position = UDim2.fromOffset(x - topLeft.X - strokeThickness/2, y - topLeft.Y - strokeThickness/2)
	newPoint.BackgroundColor3 = strokeColor
	newPoint.BorderSizePixel = 0
	newPoint.Parent = frame

	local rounded = Instance.new("UICorner")
	rounded.CornerRadius = UDim.new(0.5, 0)
	rounded.Parent = newPoint
end
3 Likes