Creating many TextLabels with non-monospaced font

I want to animate characters individually, but that’s not possible without creating a textlabel for every character in a string of text.

There’s this reply to a post that has a collection of solutions for that, and this particular one does a good job. However, the text can look ugly when you aren’t using monospaced font:

Here’s sample code that uses it, assuming it is a LocalScript and it is parented to a ScreenGui with a Frame:

Sample Code
local TextService = game:GetService("TextService")
local TextBoxHandler = {} 

TextBoxHandler.Root = script.Parent.Frame
TextBoxHandler.Speaker = {
	Font = Font.fromEnum(Enum.Font.RobotoMono), 
	FontSize = 16,
	TextColor = Color3.new(0, 0, 0)
}
TextBoxHandler._currentPos = Vector2.new()
TextBoxHandler._defaultPos = Vector2.new()
TextBoxHandler._currentTextPos = 0
TextBoxHandler._textLabels = {}

function TextBoxHandler:AddText(text: string, effects: {TextEffect}?, ...: TextArguments)
	local textArguments = {...}

	local params = Instance.new("GetTextBoundsParams")
	-- params.Text = text
	params.Font = self.Speaker.Font
	params.Size = self.Speaker.FontSize
	params.Width = self.Root.AbsoluteSize.X

	local startPoint = self._currentTextPos
	local labelList = {}
	for word in text:gmatch("%S+") do
		word = word .. " "
		params.Text = word
		local textSize = TextService:GetTextBoundsAsync(params)
		if self._currentPos.X + textSize.X > self.Root.AbsoluteSize.X then
			self._currentPos = Vector2.new(self._defaultPos.X, self._currentPos.Y + self.Speaker.FontSize)
		end

		for i = 1, #word do
			self._currentTextPos += 1
			local c = word:sub(i, i)

			params.Text = c
			local textSize = TextService:GetTextBoundsAsync(params)

			local charLabel = Instance.new("TextLabel")
			charLabel.Size = UDim2.fromOffset(textSize.X, textSize.Y)
			charLabel.Position = UDim2.fromOffset(self._currentPos.X, self._currentPos.Y)
			charLabel.Text = c
			charLabel.Transparency = 1
			charLabel.FontFace = self.Speaker.Font
			charLabel.TextSize = self.Speaker.FontSize
			charLabel.TextColor3 = self.Speaker.TextColor
			charLabel.RichText = true
			charLabel.Name = tostring(self._currentTextPos)
			charLabel.Parent = (self.Root :: Frame) :: Instance
			table.insert(labelList, charLabel)


			self._textLabels[self._currentTextPos] = charLabel

			self._currentPos += Vector2.new(textSize.X, 0)
		end
	end

	self._textLabels[#self._textLabels]:Destroy()

	if effects then
		for i, effect in effects do
			effect(labelList, startPoint, table.unpack(textArguments[i] or {}))
		end
	end

	local charDelays = self.Speaker.CharDelays or {} :: {[string]: number}
	for _, label in labelList do
		label.Transparency = 0
		label.BackgroundTransparency = 1

		local charDelay = charDelays[label.Text]
		if charDelay then
			task.wait(charDelay)
		else
			task.wait(self.Speaker.SpeakingDelay)
		end
	end

	-- effect(labelList, startPoint, ...)
end

TextBoxHandler:AddText("This is a test phrase.", {
	
})

Monospaced Font: RobotoMono
image
It looks correctly spaced because it is monospaced.

Nonmonospaced Font: Gotham
image
This is close, but not right. You can see some characters have an uneven amount of spacing.

These results are done by just changing the font in Line 6.

So, is there any solution that can space these non-monospaced characters correctly?

1 Like