2D Line Drawing Off

So I was working on a drawing line function for a map, and I threw together something from a few sources. For some reason, whenever I draw fast, the position and size starts to glitch and doesn’t stick to the correct position.

LMMTDS0fnF

Code:

function DrawLine(OldUD, NewUD)
	if OldUD and NewUD then
		local OldPos = V2New(OldUD.X.Offset, OldUD.Y.Offset)
		local NewPos = V2New(NewUD.X.Offset, NewUD.Y.Offset)
		
		local Grad = (NewPos.Y - OldPos.Y) / (NewPos.X - OldPos.X)
		local Dist = (NewPos - OldPos).Magnitude
		
		local SubX = (NewPos.X - OldPos.X)
		local SubY = (NewPos.Y - OldPos.Y)
		
		local Slope = SubY / SubX
		local RotRad = atan(Slope) * SubY / abs(SubY)
		
		local DrawFrame = INew("Frame")
		DrawFrame.Position = U2New(UNew(0, cos(RotRad) * Dist / 2 + OldPos.X - Dist / 2 + 1), UNew(0, sin(RotRad) * Dist / 2 + OldPos.Y))
		DrawFrame.Size = U2New(UNew(0, Dist), UNew(0, 10))
		DrawFrame.Rotation = atan(Grad) * 180 / pi
		DrawFrame.BorderSizePixel = 0
		
		DrawFrame.Parent = Background
		
		Debris:AddItem(DrawFrame, 3.5)
		
		delay(2, function()
			local TransparencyTween = TweenService:Create(DrawFrame, TINew(1.5), {BackgroundTransparency = 1})
			TransparencyTween:Play()
		end)
	end
end

Any help is appreciated.

2 Likes

You have to see all the code to be able to help you, it’s not enough.

Alright.

Client code:

local ContextActionService = game:GetService("ContextActionService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Debris = game:GetService("Debris")

local Background = script.Parent:WaitForChild("Background")
local Modal = Background:WaitForChild("Modal")

local Cursor = ReplicatedStorage:WaitForChild("Cursor")

local PlayerList = Players:GetPlayers()

local Player = Players.LocalPlayer
local Mouse = Player:GetMouse()

local EasingDirection = Enum.EasingDirection
local RenderPriority = Enum.RenderPriority
local UserInputState = Enum.UserInputState
local UserInputType = Enum.UserInputType
local EasingStyle = Enum.EasingStyle
local KeyCode = Enum.KeyCode

local MouseButton1 = UserInputType.MouseButton1

local InputPriority = RenderPriority.Input

local Begin = UserInputState.Begin

local Linear = EasingStyle.Linear
local Out = EasingDirection.Out

local M = KeyCode.M

local insert = table.insert
local remove = table.remove
local find = table.find

local TINew = TweenInfo.new
local INew = Instance.new
local V2New = Vector2.new
local U2New = UDim2.new
local UNew = UDim.new

local atan = math.atan
local sqrt = math.sqrt
local abs = math.abs
local cos = math.cos
local deg = math.deg
local sin = math.sin
local pi = math.pi

local sel = select

local t = tick

local HoldingMouse = false

local Update = .05
local LastTick = t()

local LastPositions = {}

local Easing =
{
	Direction = Out;
	Style = Linear;
}

local PseudoCursor = INew("TextLabel")
PseudoCursor.Size = U2New(UNew(0, 100), UNew(0, 100))
PseudoCursor.Name = "PseudoCursor"
PseudoCursor.Text = Player.Name
PseudoCursor.Position = U2New()
PseudoCursor.TextScaled = true

local CursorObj = INew("TextLabel")
CursorObj.Size = U2New(UNew(0, 100), UNew(0, 100))
CursorObj.Name = Player.Name
CursorObj.Position = U2New()
CursorObj.Visible = false

PseudoCursor.Parent = script.Parent
CursorObj.Parent = script.Parent

LastPositions[Player.Name] = U2New()

function CreateCursor(vPlayer)
	local CursorPlr = INew("TextLabel")
	CursorPlr.Size = U2New(UNew(0, 100), UNew(0, 100))
	CursorPlr.Name = vPlayer.Name
	CursorPlr.Text = vPlayer.Name
	CursorPlr.Position = U2New()
	CursorPlr.TextScaled = true
	CursorPlr.Visible = false
	
	CursorPlr.Parent = script.Parent
	
	LastPositions[vPlayer.Name] = U2New()
end

function DrawLine(OldUD, NewUD)
	if OldUD and NewUD then
		local OldPos = V2New(OldUD.X.Offset, OldUD.Y.Offset)
		local NewPos = V2New(NewUD.X.Offset, NewUD.Y.Offset)
		
		local Grad = (NewPos.Y - OldPos.Y) / (NewPos.X - OldPos.X)
		local Dist = (NewPos - OldPos).Magnitude
		
		local SubX = (NewPos.X - OldPos.X)
		local SubY = (NewPos.Y - OldPos.Y)
		
		local Slope = SubY / SubX
		local RotRad = atan(Slope) * SubY / abs(SubY)
		
		local DrawFrame = INew("Frame")
		DrawFrame.Position = U2New(UNew(0, cos(RotRad) * Dist / 2 + OldPos.X - Dist / 2 + 1), UNew(0, sin(RotRad) * Dist / 2 + OldPos.Y))
		DrawFrame.Size = U2New(UNew(0, Dist), UNew(0, 10))
		DrawFrame.Rotation = atan(Grad) * 180 / pi
		DrawFrame.BorderSizePixel = 0
		
		DrawFrame.Parent = Background
		
		Debris:AddItem(DrawFrame, 3.5)
		
		delay(2, function()
			local TransparencyTween = TweenService:Create(DrawFrame, TINew(1.5), {BackgroundTransparency = 1})
			TransparencyTween:Play()
		end)
	end
end

function MapToggle(ActionName, InputState, InputObject)
	if InputState == Begin then
		local NewEnable = not script.Parent.Enabled
		
		script.Parent.Enabled = NewEnable
		Modal.Visible = NewEnable
		
		Cursor:FireServer(1, NewEnable)
	end
end
ContextActionService:BindAction("MapToggle", MapToggle, true, M)

Mouse.Move:Connect(function()
	PseudoCursor.Position = U2New(UNew(0, Mouse.X), UNew(0, Mouse.Y + 37))
end)

UserInputService.InputBegan:Connect(function(Input)
	if Input.UserInputType == MouseButton1 then
		HoldingMouse = true
	end
end)
UserInputService.InputEnded:Connect(function(Input)
	if Input.UserInputType == MouseButton1 then
		HoldingMouse = false
	end
end)

Cursor.OnClientEvent:Connect(function(Type, PlayerName, ...)
	local CursorFound = script.Parent:FindFirstChild(PlayerName)
	if CursorFound then
		if Type == 1 then
			local LastPos = sel(2, ...)
			local NewPos = sel(1, ...)
			
			CursorFound:TweenPosition(NewPos, Easing.Dir, Easing.Style, Update, true)
			LastPositions[PlayerName] = LastPos
		else
			local LastPos = sel(2, ...)
			local NewPos = sel(1, ...)
			
			DrawLine(LastPos, NewPos)
		end
	end
end)

RunService:BindToRenderStep("MouseMovement", InputPriority.Value, function()
	for _,v in pairs(PlayerList) do
		if v ~= Player then
			local vCursor = script.Parent:FindFirstChild(v.Name)
			local MenuOpen = v:FindFirstChild("MenuOpen")
			
			if vCursor and MenuOpen then
				vCursor.Visible = MenuOpen.Value
			end
		end
	end
	
	local TickMath = t() - LastTick
	if TickMath >= Update and script.Parent.Enabled and LastPositions[Player.Name] ~= PseudoCursor.Position then
		--warn(("Update %s"):format(TickMath))
		
		LastPositions[Player.Name] = CursorObj.Position
		CursorObj.Position = U2New(UNew(0, Mouse.X), UNew(0, Mouse.Y + 37))
		
		LastTick = t()
		
		local LastPos = LastPositions[Player.Name]
		local Pos = CursorObj.Position
		
		Cursor:FireServer(2, Pos, LastPos)
		if HoldingMouse then
			Cursor:FireServer(3, Pos, LastPos)
		end
	end
end)

Players.PlayerAdded:Connect(function(vPlayer)
	insert(PlayerList, vPlayer)
	CreateCursor(vPlayer)
end)
Players.PlayerRemoving:Connect(function(vPlayer)
	remove(PlayerList, find(PlayerList, vPlayer))
	
	local vCursor = script.Parent:FindFirstChild(vPlayer.Name)
	if vCursor then
		vCursor:Destroy()
	end
end)

for _,v in pairs(PlayerList) do
	if v ~= Player then
		CreateCursor(v)
	end
end

Server code:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local Cursor = ReplicatedStorage:WaitForChild("Cursor")

local PlayerList = Players:GetPlayers()

local insert = table.insert
local remove = table.remove
local find = table.find

local INew = Instance.new

local sel = select

Cursor.OnServerEvent:Connect(function(Player, Type, ...)
	if Type == 1 then
		local MenuOpen = Player:FindFirstChild("MenuOpen")
		if MenuOpen then
			MenuOpen.Value = ...
		end
	elseif Type == 2 then
		for _,v in pairs(PlayerList) do
			if v ~= Player then
				local MenuOpen = v:FindFirstChild("MenuOpen")
				if MenuOpen and MenuOpen.Value == true then
					local NewPos = sel(1, ...)
					local OldPos = sel(2, ...)
					
					Cursor:FireClient(v, 1, Player.Name, NewPos, OldPos)
				end
			end
		end
	elseif Type == 3 then
		for _,v in pairs(PlayerList) do
			local MenuOpen = v:FindFirstChild("MenuOpen")
			if MenuOpen and MenuOpen.Value == true then
				local NewPos = sel(1, ...)
				local OldPos = sel(2, ...)
				
				Cursor:FireClient(v, 2, Player.Name, NewPos, OldPos)
			end
		end
	end
end)

Players.PlayerAdded:Connect(function(Player)
	insert(PlayerList, Player)
	
	local MenuOpen = INew("BoolValue")
	MenuOpen.Archivable = false
	MenuOpen.Name = "MenuOpen"
	MenuOpen.Value = false
	
	MenuOpen.Parent = Player
end)
Players.PlayerRemoving:Connect(function(Player)
	remove(PlayerList, find(PlayerList, Player))
end)
1 Like

Wow, it’s a whole lot of information, but from what I can see now I think it’s because your system doesn’t have enough time to paint your line enough to make it look beautiful. Try coroutines, they should be faster than normal functions. Here is the link: Click here

I do not see how using coroutines solves this issue. I think it’s more about the math and the way I’m storing positions.

1 Like

If it’s not, I have no idea. I’m sorry.

1 Like

This has been solved before in this post:

TL;DR
The mouse moves faster than RenderStepped, so we have to connect the two points that are given in each of the frames to create a smooth drawing effect.

4 Likes

Yeah, that’s what I wanted to say, just didn’t have the right words. Thanks for saying it on my court.

I’m not trying to go for a smooth drawing effect, though. The drawing updates every .05 seconds, and so I’m storing last positions so I can connect positions.

Your drawing function is kind of off. I think it was because of the Anchor Point not being set, but I might be wrong.
Here’s a working function

function DrawLine(OldUD, NewUD)
	if OldUD and NewUD then
		local OldPos = V2New(OldUD.X.Offset, OldUD.Y.Offset)
		local NewPos = V2New(NewUD.X.Offset, NewUD.Y.Offset)
		
		local Diff = OldPos - NewPos
		local Dist = Diff.Magnitude
		
		local Angle = atan(Diff.Y / Diff.X)
		
		local DrawFrame = INew("Frame")
		DrawFrame.Position = U2New(UNew(0, OldPos.X - Diff.X / 2), UNew(0, OldPos.Y - Diff.Y / 2))
		DrawFrame.Size = U2New(UNew(0, Dist), UNew(0, 10))
		DrawFrame.AnchorPoint = V2New(0.5, 0.5)
		DrawFrame.Rotation = Angle * 180 / pi
		DrawFrame.BorderSizePixel = 0
				
		DrawFrame.Parent = Background
		
		Debris:AddItem(DrawFrame, 3.5)
		
		delay(2, function()
			local TransparencyTween = TweenService:Create(DrawFrame, TINew(1.5), {BackgroundTransparency = 1})
			TransparencyTween:Play()
		end)
	end
end

4 Likes

It works perfectly now. Thank you.

1 Like

No problem, glad to know it works!

1 Like