Your best bet is to somehow draw a line of pixels between the last pixel and the current pixel.
Probably the best way to do that is by creating a for loop that starts at the last pixel’s X
position and stops at the current X
, and just lerp between Y
's
-- in a server script
local lastPixel = Vector2.new()
local function DrawLine(currentX, currentY)
local currentPixel = Vector2.new(currentX, currentY)
local leftPixel, rightPixel
if lastPixel.X <= currentPixel.X then
leftPixel = lastPixel
rightPixel = currentPixel
else
leftPixel = currentPixel
rightPixel = lastPixel
end
local dist = rightPixel.X - leftPixel.X
for x = 0, dist do
local a = x/dist
-- lerp y
local y = (rightPixel.Y - leftPixel.Y) * a + leftPixel.Y
-- round y
y = math.floor(y + 0.5)
-- server's draw function here
self:Draw(x + leftPixel.X, y)
end
lastPixel = currentPixel
end
This is a nice function and all, but it should in theory only draw 1 pixel per X-position, which is not optimal if the player draws some tall lines. To fix this, we could just change perspective on which axis we are looping on. We can also initialize the lastPixel
value to the current value on the first time drawing a pixel so as not to form a line from a corner.
local lastPixel
local function DrawLine(currentX, currentY)
local currentPixel = Vector2.new(currentX, currentY)
if not lastPixel then -- lastPixel doesn't exist yet
lastPixel = currentPixel
self:Draw(currentPixel.X, currentPixel.Y)
return
end
local delta = currentPixel - lastPixel
local axis, depAxis
if delta == Vector2.zero then -- no line needs to be drawn
-- lastPixel already equals currentPixel
self:Draw(currentPixel.X, currentPixel.Y)
return
elseif math.abs(delta.X) >= math.abs(delta.Y) then -- x distance >= y distance
axis, depAxis = "X", "Y"
else
axis, depAxis = "Y", "X"
end
local smallPixel, bigPixel
if math.sign(delta[axis]) == -1 then -- last > current
smallPixel, bigPixel = currentPixel, lastPixel
else
smallPixel, bigPixel = lastPixel, currentPixel
end
local deltaAbs = math.abs(delta[axis])
for i = 1, deltaAbs do
local a = i/deltaAbs
-- lerp other axis
local z = (bigPixel[depAxis] - smallPixel[depAxis]) * a + smallPixel[depAxis]
-- round other axis
z = math.floor(z + 0.5)
if axis == "X" then
self:Draw(i + smallPixel[axis], z)
else
self:Draw(z, i + smallPixel[axis])
end
end
lastPixel = currentPixel
end
The other way of doing this is finding the Y distance between each change in X position by doing delta.X/delta.Y
and drawing this many Y pixels per X, but this might cause too few or extra pixels to be drawn based on whether you round, floor, or ceil the resulting number.
If there are any errors in this code, please let me know, as I have not tested it!
Edit: The current code is now glitchless, but it is surprisingly laggy.
Edit 2: Found the reason, it’s evaluating each pixel instead of each frame on my side. Just use a conversion algorithm first!
Edit 3: A bit more cleaned up since last time I updated the code:
local lastPosition
-- uses method Screen:Draw(position: { X: number, Y: number }, color: Color3?)
function Screen:DrawLine(position: Vector2, color: Color3?)
if lastPosition == nil then
lastPosition = position
self:Draw(position, color)
return
end
local delta = position - lastPosition
local axis, depAxis
if delta == Vector2.zero then -- no line needs to be drawn
-- lastPixel already equals currentPixel
self:Draw(position)
return
elseif math.abs(delta.X) >= math.abs(delta.Y) then -- x distance >= y distance
axis, depAxis = "X", "Y"
else
axis, depAxis = "Y", "X"
end
local smallPixel, bigPixel
if math.sign(delta[axis]) == -1 then -- last > current
smallPixel, bigPixel = position, lastPosition
else
smallPixel, bigPixel = lastPosition, position
end
local deltaAbs = math.abs(delta[axis])
for i = 0, deltaAbs do
local a = i/deltaAbs
-- lerp other axis
local depValue = bigPixel[depAxis] * a + smallPixel[depAxis] * (1 - a)
local midPosition = {
[axis] = i + smallPixel[axis],
[depAxis] = math.floor(depValue + 0.5)
}
self:Draw(midPosition, color)
end
lastPosition = position
end