[Open Source] StringLabel Module (TextLabels with per-character formatting.)

Heyo,

I’d like to share a module that I’ve used for my own games. With this module it’s possible to create pieces of text which can be customized down to the individual characters. Each character’s properties are individually modifiable.

GIFs
Random effects on SurfaceGui

Imgur: The magic of the Internet

Text Drawing on ScreenGui

Imgur: The magic of the Internet

Random effects on BillboardGui

Imgur: The magic of the Internet

Sorry for the lag in some of the videos, my computer is a bit of a potato especially when recording.

How do I use it? + Examples

It’s quite simple to use, you require the module and create a new instance using Module.new(TextLabel). The TextLabel would be your placeholder TextLabel you put in your Gui object of choice. Once you’ve done that you can use the new instance’s :GetCharacters() function to modify each character in your text sequence. Here’s an few examples.

Simple Randomization
-- Modules
local StringLabel = require(game.ReplicatedStorage:WaitForChild("StringLabel"))

-- Variables
local stringLabel = StringLabel.new(script.Parent:WaitForChild("TestLabel"))

local characters = stringLabel:GetCharacters()
local numCharacters = #characters

local fontTable = { Enum.Font.Gotham, Enum.Font.GothamSemibold, Enum.Font.GothamBold, Enum.Font.GothamBlack }

-- Script Body
while true do
	local color1 = Color3.fromRGB(math.random(255), math.random(255), math.random(255))
	local color2 = Color3.fromRGB(math.random(255), math.random(255), math.random(255))
	
	for index, character in pairs(characters) do
		stringLabel:SetCharacterTextColor(index, color1:Lerp(color2, index / numCharacters))
		stringLabel:SetCharacterTextTransparency(index, math.random(100) / 100)
		
		wait()
	end 
	
	wait()
end
Effects from the GIFs
-- Services
local runService = game:GetService("RunService")

-- Modules
local StringLabel = require(game.ReplicatedStorage:WaitForChild("StringLabel"))

-- Variables
local stringLabel = StringLabel.new(script.Parent:WaitForChild("TestLabel"))

local characters = stringLabel:GetCharacters()
local numCharacters = #characters

local fontTable = { Enum.Font.Gotham, Enum.Font.GothamSemibold, Enum.Font.GothamBold, Enum.Font.GothamBlack }

local baseColor = Color3.fromRGB(255, 255, 255)

---- Functions
-- Util
function shuffle(tab)
	local size = #tab
	
	for i = size, 1, -1 do
		local random = math.random(size)
		
    	tab[i], tab[random] = tab[random], tab[i]
  	end

  	return tab
end

local function reset()
	stringLabel:SetTextTransparency(0)
	stringLabel:SetTextColor(baseColor)
	stringLabel:SetRotation(0)
end

-- Effects
local function rainbowWave()
	local textLength = string.len(stringLabel:GetText())
	local offset = 0
	local counter = 0
	
	local mult = 2
	local baseSize = 1 / (textLength * mult)
	
	while counter < 150 do
		for index, character in pairs(characters) do
			stringLabel:SetCharacterTextColor(index, Color3.fromHSV(baseSize * ((index + offset) % (textLength * mult)), 1, 1))
		end
		
		offset = offset + 1
		counter = counter + 1
		
		runService.RenderStepped:Wait()
	end
end

local function randomGradient()
	local color1 = Color3.fromRGB(math.random(255), math.random(255), math.random(255))
	local color2 = Color3.fromRGB(math.random(255), math.random(255), math.random(255))
	
	for index, character in pairs(characters) do
		stringLabel:SetCharacterTextColor(index, color1:Lerp(color2, index / numCharacters))
		
		runService.RenderStepped:Wait()
	end
end

local function randomColorAndTransparency()
	for index, character in pairs(characters) do
		stringLabel:SetCharacterTextColor(index, Color3.fromRGB(math.random(255), math.random(255), math.random(255)))
		stringLabel:SetCharacterTextTransparency(index, math.random(100) / 100)
		
		runService.RenderStepped:Wait()
	end
end

local function randomCharacterHighlight()
	local randomCharacterIndices = {}
	
	for index = 1, numCharacters do
		table.insert(randomCharacterIndices, index)
	end
	
	shuffle(randomCharacterIndices)
	
	for _, index in pairs(randomCharacterIndices) do
		stringLabel:SetCharacterTextColor(index, Color3.fromRGB(0, 0, 0))
		
		runService.RenderStepped:Wait()
	end
	
	for _, index in pairs(randomCharacterIndices) do
		stringLabel:SetCharacterTextColor(index, Color3.fromRGB(255, 255, 255))
		
		runService.RenderStepped:Wait()
	end
end

local function randomWaveGradient()
	local color1 = Color3.fromRGB(math.random(255), math.random(255), math.random(255))
	local color2 = Color3.fromRGB(math.random(255), math.random(255), math.random(255))
	local fadeSize = 25
	
	for i = 1, 50 do
		stringLabel:SetTextColor(baseColor:Lerp(color1, i / 50))
		
		runService.RenderStepped:Wait()
	end
	
	stringLabel:SetTextColor(color1)
	
	for index = 1, numCharacters + fadeSize do
		for i = 1, fadeSize do
			if index - i >= 1 then
				stringLabel:SetCharacterTextColor(index - i, color2:Lerp(color1, i / fadeSize))
			end
		end
		
		if index <= numCharacters then
			stringLabel:SetCharacterTextColor(index, color2)
		end
		
		for i = 1, fadeSize do
			if index + i <= numCharacters then
				stringLabel:SetCharacterTextColor(index + i, color2:Lerp(color1, i / fadeSize))
			end
		end
		
		runService.RenderStepped:Wait()
	end
end

local function randomRotation()
	for index, character in pairs(characters) do
		stringLabel:SetCharacterRotation(index, math.random(-225, 225) / 10)
		
		runService.RenderStepped:Wait()
	end
end

-- Script Body
local effects = { rainbowWave, randomGradient, randomColorAndTransparency, randomCharacterHighlight, randomWaveGradient, randomRotation }
local counter = 1

while true do
	for _, callback in pairs(effects) do
		reset()
		callback()
		
		wait(1)
	end
end
Text Drawing
-- Services
local replicatedStorage = game:GetService("ReplicatedStorage")
local userInputService = game:GetService("UserInputService")
local runService = game:GetService("RunService")

-- Modules
local StringLabel = require(replicatedStorage:WaitForChild("StringLabel"))

-- Variables
local text = script.Parent:WaitForChild("Text")
local drawStringLabel = nil

local mouseDown = false
local lastPosition = nil

local player = game.Players.LocalPlayer
local mouse = player:GetMouse()

-- Functions
local function onMouseDown()
	while mouseDown do
		local position = Vector2.new(mouse.X, mouse.Y)
		
		if position ~= lastPosition then
			lastPosition = position
			
			local absolutePositions, absoluteSizes = drawStringLabel:GetCharacterAbsolutePositionsAndSizes()
			for index, character in pairs(drawStringLabel:GetCharacters()) do
				local absolutePosition = absolutePositions[index]
				local absoluteSize = absoluteSizes[index]
				
				if position.X > absolutePosition.X and position.X < absolutePosition.X + absoluteSize.X then
					if position.Y > absolutePosition.Y and position.Y < absolutePosition.Y + absoluteSize.Y then
						drawStringLabel:SetCharacterTextColor(index, Color3.fromRGB(255, 255, 255))
					end
				end
			end
		end
		
		runService.RenderStepped:Wait()
	end
end

local function setup()
	drawStringLabel = StringLabel.new(text)

	spawn(function()
		local checkTime = tick()
		
		while true do
			if tick() - checkTime >= 15 then
				drawStringLabel:SetTextColor(Color3.fromRGB(0, 0, 0))
					
				checkTime = tick()
			end
			
			wait(1)
		end
	end)

	userInputService.InputBegan:Connect(function(input, gameProcessedEvent)
		if not gameProcessedEvent then
			if input.UserInputType == Enum.UserInputType.MouseButton1 then
				mouseDown = true
				
				onMouseDown()
			end
		end
	end)
	
	userInputService.InputEnded:Connect(function(input, gameProcessedEvent)
		if not gameProcessedEvent then
			if input.UserInputType == Enum.UserInputType.MouseButton1 then
				mouseDown = false
			end
		end
	end)
end

-- Body
setup()

I made it open source so anyone can use it, modify it, etc. to their liking or needs. I enabled copying on the Roblox page.

Download

https://www.roblox.com/library/3961979695/StringLabel

Thanks for reading, I hope I didn’t mess up the spelling and grammar too much (English isn’t my native language) and I hope it’ll be useful to some people. If there’s any issues or if you have any questions (related to my module, not Roblox or LUA.) then let me know in the replies.

Disclaimer:
There may be a few bugs that I haven’t caught yet, like I said this was primarily for private use and I tested it the best I could, but may have missed something. In which case leave a reply and I’ll try to fix them.

Edit:
I forgot to mention that this module only works locally. (Client-side)

53 Likes

This is a really neat TextLabel module (I love that people are really getting into the more nitty-gritty of text labels and allowing the community to benefit from the strides they are making). This looks really cool and I can’t wait to take a more in depth look at your code and see how you approached this!

3 Likes

I’d suggest making documentation for those who don’t have the ability to scan through a ModuleScript and know exactly what to do.

2 Likes

Yeah, this module wasn’t really intended to be public at first, I made it public because I wanted to share my ways of accomplishing these effects. So I never really gone into documenting or explaining how stuff works, which is why I did provide a few examples.

I will probably write a documentary in the near future, at the moment people can just mess around with the examples or read through the module. This isn’t really a module beginner scripters should/or in cases could use. It requires a fair understanding of basics of LUA. It’s mainly just TextLabel sugar more experienced devs can use to play around with and add to their projects to increase customizability and aesthetics.

I’ll probably port it to GitHub with a readme attached for some basic documentation when I get to it.

1 Like

Edited the original thread, I forgot to mention that this module only works locally. (Client-side)
So when using this keep that in mind.

This was good at the time, but is now obsolete due to RichText.

1 Like