An efficient, robust, open-source text rendering module for
Roblox, featuring custom fonts and advanced text control.
More creativity!
A new way of rendering and editing text is here.
- Custom fonts: Use any font file, whether completely custom or one found online.
-
Advanced control:
- Easily transform and style any individual character, word or line with freedom.
- Utilize awesome text styling like ‘justified’ alignments and spacing controls.
- Effortlessly setup text scaling, so that it’s the perfect size for all devices.
-
Differs from the
TextScaled
property on TextLabels. Learn more in the tutorial.
-
Differs from the
Check it out below!
🎬 Showcases
Dialogue
A really simple ‘dialogue’, featuring:
- Custom font.
- Text appear and disappear animation.
- ‘Shining’ word animation.
Text explosion
A neat text explosion effect.
Features a custom font too.
Efficient, robust, intuitive.
- Blazingly fast speeds.
- Clear error feedback.
- Typing & documentation.
Learn how to use it!
🚀 Fundamentals
Getting the module
Let’s first get the module. There are two ways:
-
Get it from the creator store:
- Click
Get
at the top of this post. - Click
Get Model
on the store. - Open the ToolBox in Roblox Studio.
- Go to the
Inventory
tab. - Click on
Text+
.
- Click
-
Get it from GitHub:
- Click
Git
at the top of this post. - Go to
Releases
. - Download the latest
.rbxm
file. - Find the file in your file explorer.
- Drag the file into Roblox Studio.
- Click
Find a great place for the module in your explorer.
Basics
You’ll be creating text using frames as parents and boundaries, like this:
local textPlus = require(script.TextPlus)
local frame = path.to.frame -- You should actually get or create a frame.
textPlus.Create(
frame, -- Parent and boundary.
"This text is awesome!", -- Text.
)
The text will be wrapped inside of the frame.
Note that it will ignore any UIPadding.
You can get the text at any time like this:
textPlus.GetText(frame) -- Will return a string.
Line breaks
You can manually break lines by using multiline strings.
There are two ways to do this.
Use \n
You can use \n
to break to the next line at any place.
\n
will not be shown in the text.
"First line\nSecond line"
Use multiline strings
Using [[]]
instead of ""
will allow you to create a string that takes up multiple lines.
You simply make an actual line break, and it will work.
[[First line
Second line]]
Customization
The customization works with a table, where you can provide any customizations you want.
You don’t have to provide all, or even any, because they all have defaults.
You use it like this:
textPlus.Create(
frame, -- Parent and boundary.
"This text is awesome!", -- Text.
{ -- Customization (optional).
Size = 24,
Color = Color3.fromRGB(255, 255, 255),
XAlignment = Enum.TextXAlignment.Center,
YAlignment = Enum.TextYAlignment.Center,
}
)
Full list of customization options:
-
Font: Font | CustomFont
- Size: number
-
ScaleSize: “X” | “Y” | “XY”
- Color: Color3
-
Transparency: number
-
Pixelated: boolean
- Offset: Vector2
-
Rotation: number
- StrokeSize: number
- StrokeColor: Color3
-
StrokeTransparency: number
- ShadowOffset: Vector2
- ShadowColor: Color3
-
ShadowTransparency: number
- LineHeight: number
-
CharacterSpacing: number
-
Truncate: “Clear” | “Cut”
- XAlignment: “Left” | “Center” | “Right” | “Justified”
-
YAlignment: “Top” | “Center” | “Bottom” | “Justified”
- WordSorting: boolean
-
LineSorting: boolean
- Dynamic: boolean
You can get the customization for a frame at any time like this:
textPlus.GetCustomization(frame) -- Will return a table.
This customization will always be valid, and will contain all customization options except false
booleans and a select few customizations that are not required in the list.
Modification
You can also modify a text frame after the creation. It’s done identical to initial creation:
textPlus.Create(
frame, -- Frame that already has text created within it.
"This text has been modified!" -- New text.
)
You can even modify the customization after the creation like this:
textPlus.Create(
frame,
"This text has been modified!",
{ -- New customization (optional).
Size = 12 -- Overwrite size.
-- Everything else will stay like before!
}
)
It will keep all previous customization options, only overwriting with those you provide in the table.
If you want to get rid of any of the options, simply set it to false
or any kind of invalid value.
Fonts
The font is one of the customization options, and works mostly like all of the others.
It takes in either:
- A
Font
object, usually created withFont.new()
. - A custom font table. Refer to the
Custom Fonts
tutorial section.
You can use it like this:
textPlus.Create(
frame,
"This text is awesome!",
{
Font = Font.new(
"rbxasset://fonts/families/Arial.json", -- Family.
Enum.FontWeight.Regular, -- Weight.
Enum.FontStyle.Normal -- Style.
)
}
)
Built-in fonts
You can find a lot of fonts on the documentation page.
Simply copy the asset id from the font list and paste it into the Font
object’s Family
(first argument).
Creator store fonts
Alternatively, browse many more fonts at the creator store.
Click Get Font
.
Create a TextLabel
in Roblox Studio and apply the font to it.
Make sure you have the TextLabel selected and run this in the command bar:
print(game.Selection:Get()[1].FontFace.Family)
It will output the asset id, that you need.
Simply copy and paste it into the Font
object’s Family
(first argument).
Custom fonts
If it’s still not enough, custom fonts offer endless possibilities.
But that has it’s own section (also in this post).
Scale-size
There are three scale-size options, which are inputted in the customization:
- “X”
- “Y”
- “XY”
Enabling scale-size will take Size
as a percentage of the screen size instead of a pixel amount.
It will use the specified axis — for XY
it will get the in-between of the two axis sizes.
Visualizing & calculating size
You can easily find the Size
that maintains the ratio you are seeing in studio using the code snippets I’ve made for you.
Run the code for your GUI root in the command bar:
ScreenGui
local scaleSize = "X" local textLabelSize = 14 local viewportSize = workspace.CurrentCamera.ViewportSize if scaleSize == "XY" then viewportSize = (viewportSize.X + viewportSize.Y)/2 else viewportSize = viewportSize[scaleSize] end warn(math.round(textLabelSize/viewportSize*100*1000)/1000)
SurfaceGui
Make sure to select your SurfaceGui before running.
local scaleSize = "X" local textLabelSize = 14 local surfaceGui = game.Selection:Get()[1] local adornee = surfaceGui.Adornee if not adornee then adornee = surfaceGui.Parent end local viewportSize = nil local face = surfaceGui.Face local pixelsPerStud = surfaceGui.PixelsPerStud local partSize = adornee.Size if face == Enum.NormalId.Front or face == Enum.NormalId.Back then viewportSize = Vector2.new(partSize.X*pixelsPerStud, partSize.Y*pixelsPerStud) elseif face == Enum.NormalId.Left or face == Enum.NormalId.Right then viewportSize = Vector2.new(partSize.Z*pixelsPerStud, partSize.Y*pixelsPerStud) else viewportSize = Vector2.new(partSize.X*pixelsPerStud, partSize.Z*pixelsPerStud) end if scaleSize == "XY" then viewportSize = (viewportSize.X + viewportSize.Y)/2 else viewportSize = viewportSize[scaleSize] end warn(math.round(textLabelSize/viewportSize*100*1000)/1000)
Make sure to input your desired ScaleSize
type/axis and the TextLabel’s size.
It should print out a number. This is what you want to input for the Size
to maintain the ratio you are seeing in studio.
Truncation
There are two truncation options, which are inputted in the customization:
-
“Clear”
Will completely remove the truncated word, and append...
at the end. -
“Cut”
Will cut off the truncated word, so that there is just enough space for...
at the end.
Truncation will still introduce line-breaks when exceeding the horizontal boundary.
It will truncate when the vertical boundary is exceeded.
Text bounds
It will automatically calculate the text bounds when you create text.
You can always get the text bounds of a frame like this:
textPlus.GetTextBounds(frame) -- Will return a Vector2.
⚙️ Fine-control
Introduction
The sorting options in the customization are crucial to fine-control.
Using word and line sorting, you can not only modify individual characters, but whole words and lines.
Use the sorting customization options like this:
textPlus.Create(
"Text",
frame,
{
LineSorting = true,
WordSorting = true
}
)
By default, line and word sorting will both be off, meaning your frame will contain pure characters.
Note that everything is named numerically, relative to its parent.
Both lines and words will be sorted using folders:
(Both sortings enabled)
Full looping
It’s pretty simple to loop through. Just make sure you respect your sorting.
Simple way
It’s clean and intuitive to use the GetCharacters
function, which will iterate through all characters.
This function is optimized and respects all sorting types.
You can simply iterate through the characters like this:
for characterNumber, character in textPlus.GetCharacters(frame) do
print("Character "..characterNumber) -- "Character 1", "Character 2" etc.
-- For Roblox fonts, 'character' will be a TextLabel.
-- For custom fonts, 'character' will be an ImageLabel.
end
Advanced way
You can also manually loop through.
This may be useful in certain cases.
Here’s an example with both sortings enabled:
for lineNumber, line in frame:GetChildren() do
print("Line "..lineNumber) -- "Line 1", "Line 2" etc.
-- 'line' will be a folder.
for wordNumber, word in line:GetChildren() do
print("Word "..wordNumber) -- "Word 1", "Word 2" etc.
-- 'word' will be a folder.
for characterNumber, character in word:GetChildren() do
print("Character "..characterNumber) -- "Character 1", "Character 2" etc.
-- For Roblox fonts, 'character' will be a TextLabel.
-- For custom fonts, 'character' will be an ImageLabel.
end
end
end
If you have only one of the sorting types enabled, there will only be one layer of folders, and you’ll have to do something like this:
for wordNumber, word in frame:GetChildren() do
print("Word "..wordNumber) -- "Word 1", "Word 2" etc.
-- 'word' will be a folder.
for characterNumber, character in word:GetChildren() do
print("Character "..characterNumber) -- "Character 1", "Character 2" etc.
-- For Roblox fonts, 'character' will be a TextLabel.
-- For custom fonts, 'character' will be an ImageLabel.
end
end
Specific indexing and looping
You can always access the exact line, word or character number you want by indexing like this:
frame["1"]
With some sorting enabled, you’ll be able to loop through specific lines and words.
Character (requires you to have no sorting)
for _, character in frame:GetChildren() do
-- For Roblox fonts, 'character' will be a TextLabel.
-- For custom fonts, 'character' will be an ImageLabel.
end
Word (requires word sorting)
local word = frame["1"] -- Word sorting.
local word = frame["1"]["1"] -- Line and word sorting.
for _, character in word:GetChildren() do
-- For Roblox fonts, 'character' will be a TextLabel.
-- For custom fonts, 'character' will be an ImageLabel.
end
Line (requires line sorting)
local line = frame["1"]
-- Line and word sorting:
for _, word in line:GetChildren() do
-- 'word' will be a folder.
for _, character in word:GetChildren() do
-- For Roblox fonts, 'character' will be a TextLabel.
-- For custom fonts, 'character' will be an ImageLabel.
end
end
-- Line sorting:
for _, character in line:GetChildren() do
-- For Roblox fonts, 'character' will be a TextLabel.
-- For custom fonts, 'character' will be an ImageLabel.
end
Transform and style
All characters are instances, that can be directly modified.
As I’ve noted many times already:
- Roblox fonts: TextLabels
- Custom fonts: ImageLabels
And don’t forget to be creative!
🛠️ Custom fonts
Font generation tool
We’re going to be using SnowB to generate the necessary font files.
It’s a free, simple bitmap font generation website.
Character selection
You’ll have to specify which characters you want to include in your custom font.
This is done at the Glyphs
section in the top-left corner.
By default, there should be all the characters you’ll need.
But it’s crucial you get rid of the №
character, because it is not supported.
If you’re going to be using any more characters, feel free to add them.
But you can use only ASCII characters.
Font settings
Head over to the Font
section, located right below Glyphs
.
Get a .ttf
or .otf
file (a font file), whether it’s one of your own or one you found online.
Press ADD FONT FILE
, and select your font file.
Choose the smallest possible Font Size
that covers your largest intended use case. Avoid extreme values (like 1,000px) unless absolutely necessary, since the images have to be replicated from server to client.
Fill settings
Head over to the Fill
section, located in the top-right corner.
Set Color
to white. Make sure it’s fully white (255, 255, 255, 100).
Export the files
Click Export
in the top bar.
Input a File Name
for your font.
For the Export Type
, select one of the two BMFont XML
options.
Press Save
in the bottom-right corner of the pop-up.
It should save a .xml
(or .fnt
) and a .png
file.
Convert XML to Lua format
We’re going to be using LuaXML (my custom converter) to convert to Text+'s desired font format.
Press Browse...
and select your font file.
Now copy the output. We’re going to use it later.
Upload the image and get its id
Upload the image at the creator hub.
Then go to your images.
Find your image, click the three dots on it, and then click Copy Asset ID
.
Import font data to Text+
Find the TextPlus
module in your explorer.
Open the CustomFonts
module located inside of it.
Add your font like this:
return {
MyFont = -- Paste converted XML here.
}
Add your image id for the font in the same table as the Size
and Characters
, like this:
MyFont = {
Image = 0, -- Image id.
-- Converted XML:
Size = 32,
Characters = {
}
}
For fonts that have multiple weights and/or styles, it’s recommended to use the following format:
return {
-- Fonts.
MyFont = {
-- Weights.
Bold = {
-- Styles.
Italic = {
Image = 0, -- Image id.
-- Converted XML:
Size = 32,
Characters = {
}
}
}
}
}
Use the custom font
Using custom fonts is extremely simple.
Instead of providing a Font
object, directly reference the custom font table like this:
textPlus.Create(
frame,
"Text",
{
Font = textPlus.CustomFonts.MyFont -- Don't require the fonts module!
}
)
Don’t forget to stay up-to-date!
It’s highly recommended to have the latest version at all times. This ensures:
- Newest features.
- Best performance.
- Little to no bugs.
You can view all releases (versions) and what they contain at the GitHub repository.
Major updates will be posted here, in the replies section, too.
Use this post as documentation for the module.
All documentation will stay up-to-date in this post.
Share your thoughts and creations!
I’d love to see what you guys are able to do with this, so consider sharing your works!
But most importantly:
- Report any bugs or mistakes you find for this asset and post!
- Consider providing feedback to help me and the asset improve!
Special thanks to:
- @tututuana for webifying LuaXML.
Like what you're seeing?
Check out more from me!
∙ Tween+|Optimized & advanced tweening∙ Signal+|Insanely optimized script signal
Tags
module text rendering custom fonts advanced fine control dialogue animation creativity features open source scripting ui gui graphical interface design quality performance optimization customization