Dialog box : print one word at a time until the whole conversation is shown

Example at time 2:47

https://www.youtube.com/watch?v=thdoE3ni8RM&t=7177s

Text appearing word by word slowly inside a text box

How can this be implemented? At the moment I have dialog appearing in a dialog box on collision with specific objects, but do not know how to implement printing word by word in a dialog so that it appears nicer. 

Right now the dialog appears all at once without any text transition from left to right.

In addition when a button is pressed the next group of words in the dialog will be shown.

Please suggest. Thanks.

That could be implemented as:

  • display.newContainer() to hold all of following
  • image or rect as backround
  • Individual text objects for each single line of text.
  • Timer or transition code to change text line value character by character
  • When complete, delay for a bit, then transition line up
  • draw new line and repeat.
  • when done, delete container and all text will be destroyed

Drawback to technique.  You’d have to test every line of text to ensure it did not clip.

Hi,

A possible solution.

--############################################################################# --# Text to output --############################################################################# local text = "You need to find the Gilded Sword, so get to it!" --############################################################################# --# Split utility --############################################################################# function split(inputstr) local t={} ; i=1 for str in string.gmatch(inputstr, "([^%s]+)") do t[i] = str i = i + 1 end return t end --############################################################################# --# Render words --############################################################################# local words = split(text) local word local outputTimer = timer.performWithDelay(300, function() word = table.remove(words, 1, #words-1) print(word) --add each word to text container here end, #words)

-dev

Thanks to both of you for quick feedback. I am grateful to be part of such a helpful corona community. I shall try this on my code and provide feedback soon. Cheers!

Remember, if you get stuck and need someone to make it for you, I do offer a service: https://forums.coronalabs.com/topic/69420-hire-a-hitman-problem-solver-is-back/

The text is not given word by word. It is actually letter by letter.   

Modifying develephants code a bit, and using a little short cut:

Instead of placing each letter in a position I let the newText object handle it, and just change the text adding a letter every time the timer fires.

This method may or may not work so well in your game. 

--############################################################################# --# Text to output --############################################################################# local text = {letters="You need to find \nthe Gilded Sword,\nso get to it!", index = 1} local rect = display.newRect(0,0,200,200) rect.x = display.contentCenterX rect.y = display.contentCenterY rect:setFillColor(.2,.2,.2) local txtbox = display.newText("", 0, 0) txtbox.x = display.contentCenterX txtbox.y = display.contentCenterY local function spit() local c = string.sub(text.letters, 1, text.index) txtbox.text = c text.index = text.index + 1 end  --############################################################################# --# Render words --############################################################################# local outputTimer = timer.performWithDelay(50, spit, string.len(text.letters))

Hi sidermaniac,

Aplologies for delayed response. This actually looks quite good! The text alignment feels much better than what I implemented at the moment.I will use this instead, looks much better. Thanks for it. Everyday is a learning day for me.

With regards,

Ahmed

I am learning as well.  

I have been looking at this problem and working on a better solution. I want to understand Lua and Corona SDK better.  

My previous solution is not great. It adjusts every time a line is added and looks kind of off.  

I have a much better solution.  

Each letter is placed in as a newText object into a fixed size dialog. The letters are spaced evenly.

You are going to want to use a mono spaced font so that it looks better but in this code I use what the simulator defaults to.  

Here is a link to Legend of Zelda fonts you might want to use. 

This code limits the message to 3 lines of 32 characters by truncating extra letters and lines. In your game it would be better to just ensure that the message fits the dialog rather than cutting it off. 

I am very new to Lua and Corona so the code can most likely be improved on. But it looks way better than my previous example. 

----------------------------------------------------------------------------------------- -- -- main.lua -- ----------------------------------------------------------------------------------------- -- Your code here local function split\_lines(input\_string) local \_lines = {} for line in string.gmatch(input\_string, "([^\r\n]+)") do table.insert(\_lines, line) end return \_lines end local function split\_words(input\_string) local \_words={} for word in string.gmatch(input\_string, "([^%s]+)") do table.insert(\_words, word) end return \_words end local function get\_truncated\_text(text) -- dialog holds 32 chars nicely so we clip the rest if string.len(text) \> 32 then text = string.sub(text,1, 32) end return text end local function get\_truncated\_array(array) -- return only the first three items of the array while #array \> 3 do table.remove(array) end return array end --============================================== -- dialog class local Dialog = {} Dialog.\_\_index = Dialog function Dialog.close\_dialog(event) Dialog.\_spit\_timer = nil Dialog.rect:removeSelf() Dialog.rect = nil for i,v in ipairs(Dialog.\_l) do Dialog.\_l[i]:removeSelf() Dialog.\_l[i] = nil end Dialog.\_l = nil end function Dialog.new(message) Dialog.rect = display.newRect(0,0, 300, 100) -- this is the black background dialog box Dialog.rect.x = display.contentCenterX Dialog.rect.y = display.contentCenterY / 2 Dialog.rect:setFillColor(0,0,0) Dialog.rect.isVisible = false Dialog.rect:addEventListener("close", Dialog.close\_dialog) Dialog.\_text = {letters=message or "", index = 1, letter\_count = 0} -- this holds the message, char index and number of letters Dialog.\_l={} -- table containing the newText objects for each letter Dialog.\_x = Dialog.rect.contentBounds.xMin + 3 -- \_x and \_y are the char pixel locations within the dialog box (rect) Dialog.\_y = Dialog.rect.contentBounds.yMin + 3 Dialog.\_line\_index = 1 -- the dialog allows 3 lines and this tells us what line we are working on Dialog.\_char\_index = 1 -- the current character being displayed -- this routine splits the message up into lines based on the \n's found in the message -- each line is truncated to 32 chars max -- anything beyond the 3rd line is also truncated local \_lines = split\_lines(message) for i,line in ipairs(\_lines) do --truncate the line \_lines[i] = get\_truncated\_text(line) Dialog.\_text.letter\_count = Dialog.\_text.letter\_count + #\_lines[i] --get the words local \_words = split\_words(\_lines[i]) end Dialog.\_lines = \_lines return setmetatable({}, Dialog) end function Dialog.\_spit() -- this function creates a newText object for each letter and displays that text local m = display.newText(string.sub(Dialog.\_lines[Dialog.\_line\_index], Dialog.\_char\_index, Dialog.\_char\_index), Dialog.\_x, Dialog.\_y) m.anchorY = 0 m.anchorX = 0 table.insert(Dialog.\_l, m) -- store the display object for later release Dialog.\_x = Dialog.\_x + 15 Dialog.\_char\_index = Dialog.\_char\_index + 1 if Dialog.\_char\_index \> #Dialog.\_lines[Dialog.\_line\_index] then -- do the next line if there is one Dialog.\_line\_index = Dialog.\_line\_index + 1 if Dialog.\_line\_index \> #Dialog.\_lines then -- thats all there is for letters so activate the dialog for a tap event timer.cancel(Dialog.\_spit\_timer) -- probably don't need to do this Dialog.rect:addEventListener("tap", Dialog.close\_dialog) return end -- there is another line Dialog.\_x, Dialog.\_y, Dialog.\_char\_index = Dialog.rect.contentBounds.xMin + 3, Dialog.\_y + 15, 1 end m = nil end function Dialog.show() Dialog.rect.isVisible = true Dialog.\_spit\_timer = timer.performWithDelay(50, Dialog.\_spit, Dialog.\_text.letter\_count) end setmetatable(Dialog, { \_\_call = function(\_, ...) return Dialog.new(...) end }) -- end dialog class --=========================================== display.setDefault("background", .7,.7,.7) local d = Dialog("You need to find \nthe Gilded Sword,\nso get to it!") d.show()

After the message is printed you can click on the dialog to close it.  

Fantastic! I can’t wait to try it out tonight

Hi Sidermaniac,

At the moment I can see the text overlapping happening between the first line and the second line

I am using Iphone 6s as the viewing device in simulator, can the lua be adjusted so that there is no text overlapping when different devices are selected?

Also would it be possible to have like a simple button which when pressed will goto the next line of text, rather than all sentences showing at the same time?

The overlapping is because of the font size.   

There are two variables that control the spacing for the fonts Dialog._x and Dialog._y.  

Dialog._y is the location along the y axis that the line is printed. So adding 15 to it, moves it to the next line. 

On line 103 change 15 to a higher value and it will start to separate. 

Dialog.\_x, Dialog.\_y, Dialog.\_char\_index = Dialog.rect.contentBounds.xMin + 3, Dialog.\_y + 15, 1

But that isn’t an across the board solution. You are going to have to set the font used rather than using the default font.  

That way you know how big the font will be and can use the same value on every phone to get to the next line.   

Line 93 controls what happens when the a line is done printing. 

if Dialog.\_char\_index \> #Dialog.\_lines[Dialog.\_line\_index] then 

Inside that if statement you could use timer.pause() and on a button click use timer.resume() to print the next line. 

Thanks sidermaniac, this code will be quite useful for me. I will need to reuse it everytime there is a dialogue involved. Thanks.

That could be implemented as:

  • display.newContainer() to hold all of following
  • image or rect as backround
  • Individual text objects for each single line of text.
  • Timer or transition code to change text line value character by character
  • When complete, delay for a bit, then transition line up
  • draw new line and repeat.
  • when done, delete container and all text will be destroyed

Drawback to technique.  You’d have to test every line of text to ensure it did not clip.

Hi,

A possible solution.

--############################################################################# --# Text to output --############################################################################# local text = "You need to find the Gilded Sword, so get to it!" --############################################################################# --# Split utility --############################################################################# function split(inputstr) local t={} ; i=1 for str in string.gmatch(inputstr, "([^%s]+)") do t[i] = str i = i + 1 end return t end --############################################################################# --# Render words --############################################################################# local words = split(text) local word local outputTimer = timer.performWithDelay(300, function() word = table.remove(words, 1, #words-1) print(word) --add each word to text container here end, #words)

-dev

Thanks to both of you for quick feedback. I am grateful to be part of such a helpful corona community. I shall try this on my code and provide feedback soon. Cheers!

Remember, if you get stuck and need someone to make it for you, I do offer a service: https://forums.coronalabs.com/topic/69420-hire-a-hitman-problem-solver-is-back/

The text is not given word by word. It is actually letter by letter.   

Modifying develephants code a bit, and using a little short cut:

Instead of placing each letter in a position I let the newText object handle it, and just change the text adding a letter every time the timer fires.

This method may or may not work so well in your game. 

--############################################################################# --# Text to output --############################################################################# local text = {letters="You need to find \nthe Gilded Sword,\nso get to it!", index = 1} local rect = display.newRect(0,0,200,200) rect.x = display.contentCenterX rect.y = display.contentCenterY rect:setFillColor(.2,.2,.2) local txtbox = display.newText("", 0, 0) txtbox.x = display.contentCenterX txtbox.y = display.contentCenterY local function spit() local c = string.sub(text.letters, 1, text.index) txtbox.text = c text.index = text.index + 1 end  --############################################################################# --# Render words --############################################################################# local outputTimer = timer.performWithDelay(50, spit, string.len(text.letters))

Hi sidermaniac,

Aplologies for delayed response. This actually looks quite good! The text alignment feels much better than what I implemented at the moment.I will use this instead, looks much better. Thanks for it. Everyday is a learning day for me.

With regards,

Ahmed

I am learning as well.  

I have been looking at this problem and working on a better solution. I want to understand Lua and Corona SDK better.  

My previous solution is not great. It adjusts every time a line is added and looks kind of off.  

I have a much better solution.  

Each letter is placed in as a newText object into a fixed size dialog. The letters are spaced evenly.

You are going to want to use a mono spaced font so that it looks better but in this code I use what the simulator defaults to.  

Here is a link to Legend of Zelda fonts you might want to use. 

This code limits the message to 3 lines of 32 characters by truncating extra letters and lines. In your game it would be better to just ensure that the message fits the dialog rather than cutting it off. 

I am very new to Lua and Corona so the code can most likely be improved on. But it looks way better than my previous example. 

----------------------------------------------------------------------------------------- -- -- main.lua -- ----------------------------------------------------------------------------------------- -- Your code here local function split\_lines(input\_string) local \_lines = {} for line in string.gmatch(input\_string, "([^\r\n]+)") do table.insert(\_lines, line) end return \_lines end local function split\_words(input\_string) local \_words={} for word in string.gmatch(input\_string, "([^%s]+)") do table.insert(\_words, word) end return \_words end local function get\_truncated\_text(text) -- dialog holds 32 chars nicely so we clip the rest if string.len(text) \> 32 then text = string.sub(text,1, 32) end return text end local function get\_truncated\_array(array) -- return only the first three items of the array while #array \> 3 do table.remove(array) end return array end --============================================== -- dialog class local Dialog = {} Dialog.\_\_index = Dialog function Dialog.close\_dialog(event) Dialog.\_spit\_timer = nil Dialog.rect:removeSelf() Dialog.rect = nil for i,v in ipairs(Dialog.\_l) do Dialog.\_l[i]:removeSelf() Dialog.\_l[i] = nil end Dialog.\_l = nil end function Dialog.new(message) Dialog.rect = display.newRect(0,0, 300, 100) -- this is the black background dialog box Dialog.rect.x = display.contentCenterX Dialog.rect.y = display.contentCenterY / 2 Dialog.rect:setFillColor(0,0,0) Dialog.rect.isVisible = false Dialog.rect:addEventListener("close", Dialog.close\_dialog) Dialog.\_text = {letters=message or "", index = 1, letter\_count = 0} -- this holds the message, char index and number of letters Dialog.\_l={} -- table containing the newText objects for each letter Dialog.\_x = Dialog.rect.contentBounds.xMin + 3 -- \_x and \_y are the char pixel locations within the dialog box (rect) Dialog.\_y = Dialog.rect.contentBounds.yMin + 3 Dialog.\_line\_index = 1 -- the dialog allows 3 lines and this tells us what line we are working on Dialog.\_char\_index = 1 -- the current character being displayed -- this routine splits the message up into lines based on the \n's found in the message -- each line is truncated to 32 chars max -- anything beyond the 3rd line is also truncated local \_lines = split\_lines(message) for i,line in ipairs(\_lines) do --truncate the line \_lines[i] = get\_truncated\_text(line) Dialog.\_text.letter\_count = Dialog.\_text.letter\_count + #\_lines[i] --get the words local \_words = split\_words(\_lines[i]) end Dialog.\_lines = \_lines return setmetatable({}, Dialog) end function Dialog.\_spit() -- this function creates a newText object for each letter and displays that text local m = display.newText(string.sub(Dialog.\_lines[Dialog.\_line\_index], Dialog.\_char\_index, Dialog.\_char\_index), Dialog.\_x, Dialog.\_y) m.anchorY = 0 m.anchorX = 0 table.insert(Dialog.\_l, m) -- store the display object for later release Dialog.\_x = Dialog.\_x + 15 Dialog.\_char\_index = Dialog.\_char\_index + 1 if Dialog.\_char\_index \> #Dialog.\_lines[Dialog.\_line\_index] then -- do the next line if there is one Dialog.\_line\_index = Dialog.\_line\_index + 1 if Dialog.\_line\_index \> #Dialog.\_lines then -- thats all there is for letters so activate the dialog for a tap event timer.cancel(Dialog.\_spit\_timer) -- probably don't need to do this Dialog.rect:addEventListener("tap", Dialog.close\_dialog) return end -- there is another line Dialog.\_x, Dialog.\_y, Dialog.\_char\_index = Dialog.rect.contentBounds.xMin + 3, Dialog.\_y + 15, 1 end m = nil end function Dialog.show() Dialog.rect.isVisible = true Dialog.\_spit\_timer = timer.performWithDelay(50, Dialog.\_spit, Dialog.\_text.letter\_count) end setmetatable(Dialog, { \_\_call = function(\_, ...) return Dialog.new(...) end }) -- end dialog class --=========================================== display.setDefault("background", .7,.7,.7) local d = Dialog("You need to find \nthe Gilded Sword,\nso get to it!") d.show()

After the message is printed you can click on the dialog to close it.  

Fantastic! I can’t wait to try it out tonight