Justified text displaying one character at a time
Forum rules
Before you make a thread asking for help, read this.
Before you make a thread asking for help, read this.
-
- Prole
- Posts: 3
- Joined: Tue Jan 31, 2017 11:50 pm
Justified text displaying one character at a time
I'm trying to implement the effect you see in many games where text is printed a character at a time. (See the original Dragon Quest for an example.) On its own this is easy: all one needs to do is keep a counter then use string.sub and utf8.offset to print a substring that gets longer and longer. The trouble comes from another limitation I have: I'd like to use printf's "justify" alignment. The naive implementation of this looks weird, as though text is swooping in from the right. The reason for this is clear: the justification changes the position of text that's already printed. I'm not really sure how to solve this problem though, and I wasn't able to find anyone else posting about it.
The only workaround I can think of is to print all the text in one go, properly justified, but have it start out covered by black rectangles over each line, then shrink the rectangles a character at a time until all the text is revealed. This should work, but it seems a bit convoluted and might cause problems later down the line if, say, I want a semi-transparent textbox.
Is there a simpler solution?
The only workaround I can think of is to print all the text in one go, properly justified, but have it start out covered by black rectangles over each line, then shrink the rectangles a character at a time until all the text is revealed. This should work, but it seems a bit convoluted and might cause problems later down the line if, say, I want a semi-transparent textbox.
Is there a simpler solution?
Re: Justified text displaying one character at a time
Just use left-alignment instead of justification. Even if it worked as you expect, it would have to justify the whole line every time one is completed, and if you're still reading that, it's uncomfortable.
If you absolutely want to try it, you can try printing all lines except the last one justified, and the last one left-aligned.
If you absolutely want to try it, you can try printing all lines except the last one justified, and the last one left-aligned.
Re: Justified text displaying one character at a time
Try to make it with love.graphics.print( coloredtext) https://love2d.org/wiki/love.graphics.print
and than change whiteString / blackString without update the table -- )
See my solution:
I've tried, but without success. (Update: my failure was: I have forgot that I cannot use text as linked object astable coloredtext
A table containing colors and strings to add to the object, in the form of {color1, string1, color2, string2, ...}.
table color1
A table containing red, green, blue, and optional alpha components to use as a color for the next string in the table, in the form of {red, green, blue, alpha}.
string string1
A string of text which has a color specified by the previous color.
table color2
A table containing red, green, blue, and optional alpha components to use as a color for the next string in the table, in the form of {red, green, blue, alpha}.
string string2
A string of text which has a color specified by the previous color.
tables and strings ...
Additional colors and strings.
Code: Select all
coloredText = {{1,1,1}, whiteString, {0,0,0}, blackString}
See my solution:
- Attachments
-
- appearing-letters_01.love
- (946 Bytes) Downloaded 377 times
Last edited by darkfrei on Mon Dec 11, 2023 4:26 pm, edited 5 times in total.
-
- Prole
- Posts: 3
- Joined: Tue Jan 31, 2017 11:50 pm
Re: Justified text displaying one character at a time
Great idea! I modified the love.graphics.print in your example to get the exact effect I was looking for:
For anyone else looking at this, the key idea is to print all the text at once, but make the part that isn't display yet transparent. This way there's no weirdness from the justification moving stuff around.
Code: Select all
love.graphics.printf({{1,1,1,1}, whiteString, {1,1,1,0}, blackString}, 10, 10, 200, "justify")
I've already handled the problem with the last line. This is part of a much larger system, which is why I didn't post any code snippets. Though the solution you describe is exactly what I'd used.pgimeno wrote: ↑Mon Dec 11, 2023 12:12 pm Just use left-alignment instead of justification. Even if it worked as you expect, it would have to justify the whole line every time one is completed, and if you're still reading that, it's uncomfortable.
If you absolutely want to try it, you can try printing all lines except the last one justified, and the last one left-aligned.
Re: Justified text displaying one character at a time
I don't like how the line "about it" looks out:
-
- Prole
- Posts: 3
- Joined: Tue Jan 31, 2017 11:50 pm
Re: Justified text displaying one character at a time
I solved this by printing a paragraph at a time, left aligning the last line for each paragraph. For basic text printing it's a lot of extra work, but I was already handling paragraphs to get text to wrap around images.
The justification still isn't perfect though, which goes to show pgimeno is probably right about it not being worth the effort in most cases. How difficult was it to write your own justifier? The algorithms I see after a quick google search don't look that complicated, though that doesn't always mean the implementation is easy.
By the way darkfrei, I really like your idea of using math.random() rather than using a timer. The tiny variations in the speed of the text makes it feel more natural.
Re: Justified text displaying one character at a time
Not justification, but you cannot see it:
With
The better result can be given if you write words with the sx = 1, but the spaces have different sx, based on amount of spaces and width of text and width of text field.
https://love2d.org/wiki/Text:addf
Code: Select all
blackString = [[I'm trying to implement the effect you see in many games where text is printed a character at a time. (See the original Dragon Quest for an example.) On its own this is easy: all one needs to do is keep a counter then use string.sub and utf8.offset to print a substring that gets longer and longer. The trouble comes from another limitation I have: I'd like to use printf's "justify" alignment. The naive implementation of this looks weird, as though text is swooping in from the right. The reason for this is clear: the justification changes the position of text that's already printed. I'm not really sure how to solve this problem though, and I wasn't able to find anyone else posting about it.
The only workaround I can think of is to print all the text in one go, properly justified, but have it start out covered by black rectangles over each line, then shrink the rectangles a character at a time until all the text is revealed. This should work, but it seems a bit convoluted and might cause problems later down the line if, say, I want a semi-transparent textbox.]]
local font = love.graphics.getFont ()
local width = 400
local words = {}
for w in blackString:gmatch("%S+") do table.insert (words, w) print (w) end
local strings = {}
local tempSTR = ""
for i, w in ipairs (words) do
local temp
if tempSTR == "" then temp = w else temp = tempSTR..' '..w end
if (i == #words) then
table.insert (strings, {
str = temp,
sx = 1
}
)
elseif width < font:getWidth(temp) then
table.insert (strings, {
str = tempSTR,
sx = width/font:getWidth(tempSTR)
}
)
tempSTR = w
else
tempSTR = temp
end
end
function love.draw()
-- love.graphics.rectangle ('line', 10,10, width, 200) -- to be sure
for i, s in ipairs (strings) do
love.graphics.printf({{1,1,1}, s.str}, 10, 10+(i-1)*font:getHeight(), width, "left", 0, s.sx, 1)
end
end
Code: Select all
love.graphics.setDefaultFilter( "nearest", "nearest")
The better result can be given if you write words with the sx = 1, but the spaces have different sx, based on amount of spaces and width of text and width of text field.
https://love2d.org/wiki/Text:addf
Code: Select all
print (str)
local words = {}
for w in str:gmatch("%S+") do table.insert (words, w) end
local spacesAmount = #words - 1
local stringWidth = font:getWidth (str)
local spaceWidth = font:getWidth (' ')
local noSpacesStringWidth = stringWidth - spacesAmount * spaceWidth
local newSpaceWidth = (width - noSpacesStringWidth)/spacesAmount
local sxSpace = newSpaceWidth/spaceWidth
Re: Justified text displaying one character at a time
The fake and self made justification:
Code: Select all
blackString = [[I'm trying to implement the effect you see in many games where text is printed a character at a time. (See the original Dragon Quest for an example.) On its own this is easy: all one needs to do is keep a counter then use string.sub and utf8.offset to print a substring that gets longer and longer. The trouble comes from another limitation I have: I'd like to use printf's "justify" alignment. The naive implementation of this looks weird, as though text is swooping in from the right. The reason for this is clear: the justification changes the position of text that's already printed. I'm not really sure how to solve this problem though, and I wasn't able to find anyone else posting about it.
The only workaround I can think of is to print all the text in one go, properly justified, but have it start out covered by black rectangles over each line, then shrink the rectangles a character at a time until all the text is revealed. This should work, but it seems a bit convoluted and might cause problems later down the line if, say, I want a semi-transparent textbox.]]
love.graphics.setDefaultFilter( "nearest", "nearest")
local font = love.graphics.getFont ()
local width = 380
local words = {}
for w in blackString:gmatch("%S+") do table.insert (words, w) end
local stringLines = {}
local strings = {}
local tempSTR = ""
for i, w in ipairs (words) do
local temp
if tempSTR == "" then temp = w else temp = tempSTR..' '..w end
if (i == #words) then
table.insert (stringLines, {
str = temp,
sx = 1
}
)
table.insert (strings, temp)
elseif width < font:getWidth(temp) then
table.insert (stringLines, {
str = tempSTR,
sx = width/font:getWidth(tempSTR)
}
)
table.insert (strings, tempSTR)
tempSTR = w
else
tempSTR = temp
end
end
loveText = love.graphics.newText( font, "" )
for i, str in ipairs (strings) do
print (str)
local words = {}
for w in str:gmatch("%S+") do table.insert (words, w) end
local spacesAmount = #words - 1
local stringWidth = font:getWidth (str)
local spaceWidth = font:getWidth (' ')
local noSpacesStringWidth = stringWidth - spacesAmount * spaceWidth
local newSpaceWidth = (width - noSpacesStringWidth)/spacesAmount
local sxSpace = newSpaceWidth/spaceWidth
if i == #strings then
-- last line has no justification
newSpaceWidth = spaceWidth
end
print (spacesAmount, stringWidth, noSpacesStringWidth, spaceWidth, newSpaceWidth, sxSpace)
local x = 0
local y = (i-1)*font:getHeight()
for j, w in ipairs (words) do
loveText:add( w, x, y)
x = x + font:getWidth (w)
if (j == #words) then
-- do nothing
else
x = x + newSpaceWidth
end
end
end
function love.draw()
love.graphics.rectangle ('line', 10,10, width, 260)
for i, s in ipairs (stringLines) do
love.graphics.printf({{1,1,1}, s.str}, 10, 10+(i-1)*font:getHeight(), width, "left", 0, s.sx, 1)
end
love.graphics.rectangle ('line', 410,10, width, 260)
love.graphics.draw (loveText, 410, 10)
end
function love.keypressed(key, scancode, isrepeat)
if false then
elseif key == "escape" then
love.event.quit()
end
end
Re: Justified text displaying one character at a time
Just for academic interest, instead of using black rectangles, you can totally mask with transparency using a shader: you have 2 float uniforms, both in screen-space, which represent 2 "rays" that divide the screen: one being a horizontal ray that moves from top to bottom and above which all pixels are shown (for text lines already completed), and the other being a vertical ray that moves from left to right and to the left of which all pixels are shown (for the text line being revealed). Then the alpha of the pixel is this, with "texturecolor" coming from Löve's effect() pixel shader function:saddleblasters wrote: ↑Mon Dec 11, 2023 11:50 am I'm trying to implement the effect you see in many games where text is printed a character at a time.
(...)
have it start out covered by black rectangles over each line, then shrink the rectangles a character at a time until all the text is revealed. This should work, but it seems a bit convoluted and might cause problems later down the line if, say, I want a semi-transparent textbox.
Code: Select all
texturecolor.a = max(1.0-step(verticalRay, screen_coords.x), 1.0-step(horizontalRay, screen_coords.y));
All of that said, .print() and .printf() are very fast and even if the text mesh is being generated per-frame by Löve as you reveal each new character, it's going to be way easier & faster to just use these functions than making a static Text object and animating the character reveal in the shader like described above.
But anyway, it's good to know that it can be done that way -- if you wanted a soft reveal like some JRPGs have it, where the characters fade-in with transparency, then it needs to be done that way in the shader using the screen-space distance from the pixel towards the vertical ray.
I'd also like to suggest a different approach to the text speed.
The text speed as shown in darkfrei's great demo, being random, gives it a "typewriter" effect indeed, but I found it a bit distracting.
I think a constant text speed (user-adjustable in the game settings) that has small delays on each punctuation mark (also toggable in the settings) gives it a nice a "speaker" effect instead, like you can almost listen to the voice while reading.
Like using punctuation characters like these to create delays:
Code: Select all
, . ; : ! ? –
Re: Justified text displaying one character at a time
Shaders are not so easy, can you please make the .love file with the simplest realization?RNavega wrote: ↑Sat Dec 16, 2023 6:25 pm
Just for academic interest, instead of using black rectangles, you can totally mask with transparency using a shader: you have 2 float uniforms, both in screen-space, which represent 2 "rays" that divide the screen: one being a horizontal ray that moves from top to bottom and above which all pixels are shown (for text lines already completed), and the other being a vertical ray that moves from left to right and to the left of which all pixels are shown (for the text line being revealed).
Who is online
Users browsing this forum: Ahrefs [Bot] and 6 guests