Jump to content

[TOPIC: topicViewTemplate]
[GLOBAL: userSmallPhoto]
Photo

Dialog box : print one word at a time until the whole conversation is shown
Started by IndieEnthusiast Jun 28 2018 06:22 AM

11 replies to this topic
text conversation dialog

Best Answer sidermaniac , 28 June 2018 - 10:36 PM

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))

[TOPIC CONTROLS]
[/TOPIC CONTROLS]
[modOptionsDropdown]
[/modOptionsDropdown]
[reputationFilter]
[TOPIC: post.html]
#1

IndieEnthusiast

[GLOBAL: userInfoPane.html]
IndieEnthusiast
  • Contributor

  • 154 posts
  • Corona SDK

Example at time 2:47

 

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.



[TOPIC: post.html]
#2

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,380 posts
  • Corona SDK

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.


  • IndieEnthusiast likes this

[TOPIC: post.html]
#3

Develephant

[GLOBAL: userInfoPane.html]
Develephant
  • Corona Geek

  • 1,441 posts
  • Corona SDK

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


  • IndieEnthusiast likes this

[TOPIC: post.html]
#4

IndieEnthusiast

[GLOBAL: userInfoPane.html]
IndieEnthusiast
  • Contributor

  • 154 posts
  • Corona SDK

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!



[TOPIC: post.html]
#5

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,380 posts
  • Corona SDK

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/


  • IndieEnthusiast likes this

[TOPIC: post.html]
#6

sidermaniac

[GLOBAL: userInfoPane.html]
sidermaniac
  • Observer

  • 25 posts
  • Corona SDK

  Best Answer

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))

  • agramonte and IndieEnthusiast like this

[TOPIC: post.html]
#7

IndieEnthusiast

[GLOBAL: userInfoPane.html]
IndieEnthusiast
  • Contributor

  • 154 posts
  • Corona SDK

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

 

 

 

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))

 


  • IndieEnthusiast likes this

[TOPIC: post.html]
#8

sidermaniac

[GLOBAL: userInfoPane.html]
sidermaniac
  • Observer

  • 25 posts
  • Corona SDK

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.  


  • IndieEnthusiast likes this

[TOPIC: post.html]
#9

IndieEnthusiast

[GLOBAL: userInfoPane.html]
IndieEnthusiast
  • Contributor

  • 154 posts
  • Corona SDK

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



[TOPIC: post.html]
#10

IndieEnthusiast

[GLOBAL: userInfoPane.html]
IndieEnthusiast
  • Contributor

  • 154 posts
  • Corona SDK

Hi Sidermaniac,

 

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

- Video link : https://goo.gl/kNAVjg

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?



[TOPIC: post.html]
#11

sidermaniac

[GLOBAL: userInfoPane.html]
sidermaniac
  • Observer

  • 25 posts
  • Corona SDK

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. 


  • IndieEnthusiast likes this

[TOPIC: post.html]
#12

IndieEnthusiast

[GLOBAL: userInfoPane.html]
IndieEnthusiast
  • Contributor

  • 154 posts
  • Corona SDK

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




[topic_controls]
[/topic_controls]

Also tagged with one or more of these keywords: text, conversation, dialog