MaxVisibleGraphemes & RichText sound problem

Hi developers! So I’ve created a dialogue system using the ‘MaxVisibleGraphemes’ feature, in which every grapheme plays a sound. The thing is, my dialogue has RichText, which is why I decided to use this method instead of ‘string.sub’, and it plays a sound for every character inside the RichText, if that makes sense, instead of playing a sound for every grapheme shown. This is what I’m referring to:

The sound keeps playing after the sentence has finished, as if instead of ‘pineapple pizza’, it was playing as '<font color='#ffc637'>pinapple pizza</font>' or smth. Here is the code I made for the typewriter effect:

local function setText(text, object)
	
	text = text:gsub("<br%s*/>", "\n")
	text:gsub("<[^<>]->", "")
	
	local symbols = {",", "%.", "!", "?", ":", ";"}
	local detectedSymbol = nil
	
	for i = 1, #text do
		object.Text = text
		object.MaxVisibleGraphemes = i
		
		for _, symbol in pairs(symbols) do
			if string.sub(text, i, i):match(symbol) then
				detectedSymbol = symbol
			end
		end
		
		game.SoundService.clickSound:Play()
		
		if detectedSymbol == "," then
			task.wait(0.2)
		elseif detectedSymbol == "%." or detectedSymbol == "!" or detectedSymbol == "?" then
			task.wait(0.5)
	
		elseif detectedSymbol == nil then
			task.wait(0.01)
		end
		
		detectedSymbol = nil
	end
end

So I was wondering if there was a possible way to make it play just the number of characters shown. I would really appreciate any help! I’m not very good at scripting so yeah, maybe it’s a dumb mistake or whatever. I’ll be reading you guys, thank you! :two_hearts:

3 Likes
text = text:gsub("<br%s*/>", "\n")
text:gsub("<[^<>]->", "")

Here you seem to be attempting to remove rich text tags to properly count the length, but on the second line you don’t set text to the result of the gsub. This means that rich text tags aren’t actually removed.

You could fix it, but an easier way to remove rich text tags is to use TextLabel.ContentText, which gives the text as it is rendered (with tags removed and escape forms replaced).


Additionally, while this is’t the exact problem you posted, I notice a small flaw with your code. Your code simply iterates over the string 1 byte at a time. While this isn’t an issue when using ASCII characters, if you put Unicode characters in the text, it will start having issues again.

This is because Unicode codepoints (individual characters) consist of multiple bytes, while string operators like string.sub and the # operator act on individual bytes. This means that when you do #text, it will give the number of bytes, which may be higher than the actual number of visible characters.

It is even worse due to the fact that multiple Unicode codepoints may appear as one grapheme (a grapheme is a symbol that gets rendered as a whole), so you actually would want to iterate over graphemes, not codepoints.

Luckily, the utf8 library gives us an easy way to work with codepoints and graphemes. Specifically, we want utf8.graphemes, which allows us to iterate over the graphemes in a string. When iterating, it gives 2 numbers, which are the first and last byte in the grapheme. You can use this with string.sub to get the entire grapheme.


The full fixed code would be as follows:

local function setText(text, object)
	local symbols = {",", "%.", "!", "?", ":", ";"}
	local detectedSymbol = nil

	object.Text = text
	local contentText = object.ContentText

	local count = 0
	for first, last in utf8.graphemes(contentText) do
		count += 1
		object.MaxVisibleGraphemes = count

		local currentGrapheme = string.sub(contentText, first, last)
		for _, symbol in pairs(symbols) do
			if currentGrapheme:match(symbol) then
				detectedSymbol = symbol
			end
		end

		game.SoundService.clickSound:Play()

		if detectedSymbol == "," then
			task.wait(0.2)
		elseif detectedSymbol == "%." or detectedSymbol == "!" or detectedSymbol == "?" then
			task.wait(0.5)

		elseif detectedSymbol == nil then
			task.wait(0.01)
		end

		detectedSymbol = nil
	end
end
6 Likes

omg, my bad. Thank you so much!!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.