EDIT: FULL SOLUTION FURTHER DOWN Just moved the var tween function to and onready variable as it was messing with the skip feature: (at the start of the script with the other onready variables. Also deleted it from the add text function so it only exists as an onready var) @onready var tween = get_tree().create_tween()
Having the tween variable as an onready statement allows the skip feature to function, but causes the dialog queue to have issues since it does not begin the tweening again after using the first skip. will edit this message if i can fix it EDIT: Solved the problem, it's just a couple more messages down in this thread
@@onepunchman4719 Personally, to make a global tween that allowed me to use the skipping, I had to use "@onready var tween = create_tween()" for some reason the get_tree() made it not work. - For the skip to work in Godot 4, you have to change "tween.remove_all()" to "tween.kill()" [YOU HAVE TO CREATE A NEW TWEEN AGAIN BEFORE TWEEN.TWEEN_PROPERTY] i'm not yelling - tween = create_tween() to get the tweening to work multiple times, you have to set your variable back to 0.0 (in your case "visible_characters" ). - You can do this in the hide show or display functions, anywhere before the tween starts (I set in my display function right before I call show). - If you want to use the "percent_visible" how the tutorial does, the new variable name is "visible_ratio" and in the inspector it is right below visible characters - tween.tween_property(label, "visible_ratio", 1.0, len(next_text) * CHAR_READ_RATE).set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_LINEAR)
Finally, a straight-forward textbox tutorial. Thank you. This taught me a LOT. Fun fact: you can move a player while the textboxes are up in this code. Simple update: change the pause mode to Process on the CanvasLayer in the editor and simply add this code to the match/State.ready line in func _process: get_tree().paused = true and then add to the hide_textbox function: get_tree().paused = false Keep up the good work!
i like that you made tutorial without 30 minutes long "hello guys today we are going to make text box in program called godot, so first we need to open the aplication..." and cringy music. HUGE THANK YOU for saving our time, and making this video 13 minutes long, not 50 minute long
This is so helpful, thank you so much! A small issue I ran into was that the margin feature didn't seem to be working, so in case anybody runs into this issue, just make sure that your anchor is set to 1 on the left, right, and bottom (and top if you want it to be perfectly centered), and that you're editing the MarginContainer node, not the Panel node.
While I had to play the video on 0.25 speed (And then rewound several times haha) the information in here is solid. You really learn some great concepts and how to get this style of text box. Appreciate it!
Wow, I memorized most of this stuff and I don't even have a computer, when it gets fixed I will use this vid as a reference because of how simple and precise it is
You're the best man, honestly. This video is very well done with fast explinations and no wasting time to get ad revenue and stuff. Respect, respect...
You're so quick, and precise, and all your programming is well explained! This is the dialogue tutorial godot needed. I was looking for this for forever! Subbed! :)
Nice call with the tweening; I've been using a timer which is a lot more processor-heavy. Only thing I'd change is instead of doing percent visible I'd do characters visible instead, and set the time to be a variable based on the total characters in the string so you get a consistent playback speed for each character. It also lets you set some methods to handle character tags that affect playback speed, which can be really nice to put emphasis on specific dialogue or add pauses with punctuation to mimic speech patterns.
This is awesome! I was about to implement something like this so the timing couldn't be better. Loved the pacing and explanations as you went. Thank you!
Really appreciate how straight to the point you are with your videos, you don't overexplain but you still get all of the needed info across. Definitely subscribing!
In 4.0 you want to do: if tween: tween.kill() tween = get_tree().create_tween() tween.tween_property(message, "visible_ratio", 1.0, len(message.text) * CHAR_READ_RATE) tween.connect("finished", on_tween_finished)
Tried this out. The 1.0 ratio doesn't do anything. Changing it to 0.0 got me somewhere, however, it's counting backwards which I obviously do not want. How on Earth do I fix this? (Godot's updates are ridiculous if they constantly make changes. They should've just kept tweens smh)
The flickering of the textbox. Could it be because of the hide_textbox() in State.FINISHED of _process? If so would adding if queue.empty(): hide_textbox() fix it?
I love Godot for its versatility and how you can change every tiny thing, but the implementation of a basic text box really needs to be streamlined somehow. There are so many moving parts to this for something seemingly simple. It's the only major issue I've had with the engine so far... well, that and having like six different ways to do the same thing depending on what version you have or if it covers the specific needs you have, like... character movement, and how to remember if you've been in a scene or opened a door or emptied a chest. I do have a question though. In a top-down game where my camera moves relative to my player's position in the scene, does the textbox keep its position relative to the current camera position?
There is an open source text box plugin that looks promising called Dialogic. Might be worth looking into: github.com/coppolaemilio/dialogic As for the camera movement, since the textbox is a child of a canvaslayer, you can think of it as sitting entirely on a different layer. It is painted above everything else and so won't interfere with camera movement on a layer below it.
This is awesome. Thank you so much! I'm a total beginner at this so this may be a silly question but how do I now use this in different scenes and inject different text? I replicated this perfectly but don't know how to implement it into different situations. Thanks so much!
You'll want to make the TextBox.tscn globally accessible by Autoloading it in the project settings (Project -> Project Settings -> AutoLoad -> Add TextBox.tscn (**not TextBox.gd)). Now you can reference it anywhere in the project via TextBox.push_text("Text to display here"). If you want to check out a sample project that uses the autoloaded global TextBox, you can reference this one: github.com/jontopielski/shade
I'm having an issue where if I use enter to skip READING state, next time I don't skip the animation, it makes the end of the text appear and disaper multiple times, this only happens when I skip text rather than waiting for it to finish by itself.
Make sure the textbox is a CanvasLayer node (or a child of a CanvasLayer node). Then it should appear over everything else, regardless of the current camera. If you must, you can also Autoload the textbox so it is preloaded at the start of every scene and it can be globally accessible.
Nice tutorial!! loved it. Your style of just get to the point is amazing! quick and easy I really appreciate it. Two things i changed for the stuttering: 1: label.percentvisible = 0 before you set the label.text = next_text 2. Finished state i added an if text_queue.empty(): hide_textbox()
I think a good way of doing it would be to start with the state machine, Im not totally familiar woth Godot, but instantiate a new textbox from the actors, so uou put the dialogue into the actors as you place them.
im having an error that i cant figure out the reasoning behind, "_build_interpolation: Tween target object has no property named: percent visible" any idea why this happens?
@@jontopielski6227 i did not, thanks! but i still am having an issue with the tweening, for me the text has appeared as soon as i play the scene and because of this the "end_symbol" doesnt show up (well i guess thats obvious since it shows up when tweening is complete and im having an issue with the tweening..)
@@lawsona i'm guessing the tweening isn't starting - double check the tweening parameters and don't forget to call $Tween.start(). for reference, i've uploaded the code from this tutorial so you can cross check the scene structure and scripts: github.com/jontopielski/rpg-textbox-tutorial also, i noticed a slight mistake in my tutorial - make sure you connect the Tween's tween_completed, NOT tween_all_completed signal. the reason is that when there are multiple texts the tween_all_completed() doesn't emit properly. i've noted it in the description. if you're still stuck after a while, feel free to upload your project somewhere publicly accessible and i'll take a look when i get the chance.
@@jontopielski6227 okay so the tweening still has some unidentified problem but the if you press "ui_accept" the text appears, but the problem is its just an empty box until you press ui accept.
rlly helpful video, could you make a tutorial on how to do an rpg party follwing system like in eartbound where the other party members follow behind the player?
heya I'm having a hard time trying to write the same code but within a different scene? Is there a way to fix this or make it easier, because when i try to copy and paste the code it just repeats the text instead of writing its own.
Yes - you just need to keep track of how many characters you have read so far. You can update the character counter in the tween_step signal and check if a new character has been read and if so, play a sound. Here is what an implementation might look like (assumes that you've setup an audiostreamplayer with the text typed sound and stored the body of your textbox label as the variable `label`): ... var current_char_count = 0 func queue_text(next_text): current_char_count = 0 ... func _on_Tween_tween_step(object, key, elapsed, value): var next_char_count = label.visible_characters if next_char_count > current_char_count: current_char_count = next_char_count $AudioStreamPlayer.play() ... It should be noted that sometimes you don't want to play the sound for EVERY character that is typed since it might be too many noises, depending on your text speed. In those cases, you'll want to do every other or every 3, but I'll leave it up to you to make those kinds of tweaks.
Well it seems simple enough. But its not beginner friendly. You're creating nodes and editing properties without explaining where to look. I agree with some that say its too fast. But thanks for the vid
Hey this was a great vid! I'd like to know how I can embed multiple dialog boxes in one scene, for example when (in my level) my character reaches interacts with an object I want the dialog box to appear again. However, when I try to embed the dialog box again it does not let me drag it into the position I want, instead it overlaps with the first dialog inside the beginning of my scene. First of all, I'd like to know how I can move the dialog box, or if not is there a way to trigger another dialog box when the next interaction begins. Much appreciated.
Hi, you shouldn't be moving the dialog box's position. It should stay in one position and so long as it is in its own CanvasLayer, it should rest on top of the other canvases without needing to move its position. You should make the dialog box global and only use 1 dialog box for your whole game. When you interact with an object, it should send the text to the one global dialog box
@@jontopielski6227 how could i make it so that with a button, the dialogue box’s position can flip to the top of the screen and then back when pressed again, in the vein of chrono trigger text boxes. what would be the easiest way to manage having it spawn at the top instead of the bottom sometimes?
Do you have suggestions for how to adapt this for Godot 4 since tweens got reworked and are no longer nodes? I did my best to follow along adapting for the new way to use tweens and I have something that KIND OF works, but then has a bunch of weird behavior (it'd take too long to explain what all's going wrong in this comment that I'm trying to keep short lol) and I'm still a beginner who doesn't know enough about tweens either in Godot 3 or 4 to know what I'm doing wrong 😅 if you'd be willing to take a look at the script I hacked together to check where I went wrong, let me know
@@Sungjinbot So in a Label, if you look under the inspector under "Displayed Text" there is a setting called "Visible Characters" that says "the number of characters to display. If set to -1, all characters are displayed." Based on that, here's what I have in my display_text() func instead of the tween stuff: while label.visible_characters len(next_text[2]) || label.visible_characters == -1: end_symbol.text = "v" current_state = state.FINISHED The await line is how I adjusted character display speed, so you can change that 0.02 to a different number for a different speed. I'm still a Godot newb and coding newb, so there's probably a better way to code all this, but so far this has been working for me without any weird behavior or bugs. Even tho you can't see the adjustments I made to the rest of the code, hopefully this is enough to help and give you a starting place to work from :)
@@Plund3rbunny I want to add that if someone uses your solution, make sure to change all Label.visible_ratio = 0 to Label.visible_characters = 0 where necessary. Thank you for your solution, I tried figuring it out myself but couldn't exactly get it to work.
whats the easiest way i could get the textbox to appear when i press ui accept and my character collision is touching the collision of an object? (basically like interacting with an object)
I'll do my best to explain one way to do this. First, make sure your TextBox scene is autoloaded so it's always in the scene and always accessible by code. You'll want to decide how exactly you're going to interact with objects. One way is to use a RayCast2D that is attached to the player and points out. It's not perfect, but let's go with that for this example. In the player, whenever "ui_accept" is pressed, check if the collider is colliding with any object that can be interacted with. If there is a collision, then grab the dialogue information from that object directly from the raycast collision check. To make the dialogue information visible from the collided object, try this - create a new scene called "Dialog" that is just an empty node that exports a single String called "dialogue". Then, attach it as a child to any StaticBody2D that the player is going to interact with. The hierarchy will look like this: - Sprite - StaticBody2D - Dialog - CollisionShape2D Then, when you check the raycast collision on the player, you can check if the body it collided with (in this case the StaticBody2D) has a child named "Dialog". If it does, grab the exported "dialog" string (which will be the text you want displayed), and push that text to the global TextBox. Because of how difficult this was to try and explain, I decided to publish a project that I stopped working on that has a working interaction system: github.com/jontopielski/shade Look at TextBox.tscn, Player.tscn, and Dialog.tscn to get a better understanding of how the systems work. Cheers!
A simpler way could be check the body raycast and use the has_method funtion to check if it has a specifyc function for that: if Input.is_action_just_pressed("ui_accept") and raycast.is_colliding(): var body = raycast.get_collider() if body.has_method("on_interact"): body.on_interact() Then inside the object function you send the message (+ any other interactions/result) that happens when you interact with the object, and idk if is possible something like virtual functions in godot to make this process as simple as overwrite a function
Trying to use this to implement it into a game, and needless to say, its a very well made tutorial, but I'm having one issue. I did exactly what you did, but despite that, I cant manage to get the start symbol, end symbol, or label text to be blank. my code is the exact same as yours, but when I run the scene, the text is the same and not blank
Heres my version of your script (gd 4): edit to add a signal because why not extends CanvasLayer signal dialogue_finished enum State { READY, READING, FINISHED } const CHAR_RATE = 0.075 @onready var label = $Container/TextContainer/Panel/Label @onready var bg = $Container/BG var tween: Tween var state: State func _ready(): _hide() state = State.READY func _process(delta): match state: State.READING: if Input.is_action_just_pressed("ui_select"): _finish() State.FINISHED: if Input.is_action_just_pressed("ui_select"): _hide() dialogue_finished.emit() state = State.READY func _hide(): label.text = "" label.visible_ratio = 0 bg.visible = false func _show(): bg.visible = true tween = create_tween() tween.finished.connect(_finish) func _finish(): tween.stop() label.visible_ratio = 1 state = State.FINISHED func _display_dialogue(lines): if state != State.READY: return label.text = lines _show() tween.tween_property(label,"visible_ratio",1,len(lines) * CHAR_RATE) state = State.READING
It is admittedly a little annoying and unhelpful and comes out kind of egotistical. Anyone can slow or speed down the video, anyway. With that said, the video is helpful, but a teacher should care about the students/viewers, not showing off how fast they can work. 😕
@@jontopielski6227 wow,thanks! it is the only tutorial about dialogs that include both animation and skipping.thank again,you deserve much more subscribers.
I have an Issue, every time I skip the dialog the text stops and the window minimizes itself. I did everthing like in the video and then I tried the UPDATES in the description. The error occoured even before I used "tween_completed" and "$Tween.remove_all()".
This code does exactly what the video shows. However, I think I should now add: - A signal for proceeding through a textbox, and a signal for emptying the queue. I can see myself awaiting these signals, so a character can say something, then after talking, do something. - A way to "reserve" the textbox, so characters cannot talk over each other, or try to enter a conversation during another conversation. - A bit more control. One thing I have in mind is to end a textbox without input, so a character can be interrupted. - A choice system. Or, a way to talk back. - Effects. Pictures and sounds. Make it a bit more "undertale styled".
fix for flickering box: func _process(delta): match currentState: State.READY: get_tree().paused = true if !textQueue.empty(): displayText() State.READING: if Input.is_action_just_pressed("ui_skip"): dialogue.percent_visible = 1.0 $Tween.remove_all() changeState(State.FINISHED) State.FINISHED: if Input.is_action_just_pressed("ui_accept"): changeState(State.READY) if !textQueue.empty(): showTextbox() else: hideTextbox() ### the code is pretty much the same just with an added if-else in the State.FINISHED, remember to use your equivalent function names for showTextbox and hideTextbox
Excellent tutorial! However, I have a question. I'm trying to implement multiple character voices per text box and per "session" (as in, the time during where the textbox is visible and is showing a clear conversation or narrative). However, in order to switch character voices on the fly I have to yield for until the State is set to FINISHED. I'm wondering if there's a better way to go about this or if this is all I can do, as I feel like the yielding, due to it freezing the entire script until a signal is called (in this case, the custom textCompleted signal), might cause performance issues in the near future. I can send code if necessary, here is the debug code in the meantime... func _on_Button_pressed(): _$Button.disabled = true _$Textbox.textSound = load("res://sounds/sans_placeholder2.wav") _$Textbox.queuetext("Teste") _yield($Textbox,"textFinished") _$Textbox.textSound = load("res://sounds/sans_placeholder.wav") _$Textbox.queuetext("Test2") _yield($Textbox,"textFinished") _$Textbox.textSound = load("res://sounds/sans_placeholder2.wav") _$Textbox.queuetext("Hello there....") _$Button.disabled = false
Okay, I think I've found a solution. It's a bit of a bodge and I have yet to test it, but I figure I can make the text array into a key-value (with two properties for text and voice) array and add an optional second argument to queuetext for the character voice. Hopefully it works...
@@roxwize I think that's a nice idea. I was going to suggest an additional array for managing the "speaker", but creating a key-value pair object and storing that object in the queue also works. You can add things like the text, the speaker, maybe a portrait image, etc. Good luck!
Jeez, i haven't seen such "straight to the point" tutorials for a long time. Keep it on!
I haven't even checked out what else he does but subscribing on this fact alone.
EDIT: FULL SOLUTION IN REPLIES
Godot 4.1 Here is how to use Tweening and have that pesky and symbol appear (I used "
EDIT: FULL SOLUTION FURTHER DOWN
Just moved the var tween function to and onready variable as it was messing with the skip feature:
(at the start of the script with the other onready variables. Also deleted it from the add text function so it only exists as an onready var)
@onready var tween = get_tree().create_tween()
Having the tween variable as an onready statement allows the skip feature to function, but causes the dialog queue to have issues since it does not begin the tweening again after using the first skip. will edit this message if i can fix it
EDIT: Solved the problem, it's just a couple more messages down in this thread
hey man, i just got mine working properly. lmk if you need help
@@ballere2003 Yes Please! I left it off last night and could definitely use a hand!
@@onepunchman4719
Personally, to make a global tween that allowed me to use the skipping, I had to use "@onready var tween = create_tween()" for some reason the get_tree() made it not work.
- For the skip to work in Godot 4, you have to change "tween.remove_all()" to "tween.kill()"
[YOU HAVE TO CREATE A NEW TWEEN AGAIN BEFORE TWEEN.TWEEN_PROPERTY] i'm not yelling
- tween = create_tween()
to get the tweening to work multiple times, you have to set your variable back to 0.0 (in your case "visible_characters" ).
- You can do this in the hide show or display functions, anywhere before the tween starts (I set in my display function right before I call show).
- If you want to use the "percent_visible" how the tutorial does, the new variable name is "visible_ratio" and in the inspector it is right below visible characters
- tween.tween_property(label, "visible_ratio", 1.0, len(next_text) * CHAR_READ_RATE).set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_LINEAR)
in case anyone is wondering, the margins are in theme overrides now. this took me way too long to figure out.
THANK YOU SO MUCH
TY!
Thanks sir!
Finally, a straight-forward textbox tutorial. Thank you. This taught me a LOT.
Fun fact: you can move a player while the textboxes are up in this code.
Simple update: change the pause mode to Process on the CanvasLayer in the editor and simply add this code to the match/State.ready line in func _process:
get_tree().paused = true
and then add to the hide_textbox function:
get_tree().paused = false
Keep up the good work!
i like that you made tutorial without 30 minutes long "hello guys today we are going to make text box in program called godot, so first we need to open the aplication..." and cringy music. HUGE THANK YOU for saving our time, and making this video 13 minutes long, not 50 minute long
You could also add to the finished state to check if it contains text queued up and to not hide the box if so. Great stuff!
This is so helpful, thank you so much! A small issue I ran into was that the margin feature didn't seem to be working, so in case anybody runs into this issue, just make sure that your anchor is set to 1 on the left, right, and bottom (and top if you want it to be perfectly centered), and that you're editing the MarginContainer node, not the Panel node.
1:00
When I try to write text in the Label, the margin container reset to its default size.
Does this happen to anyone else?
While I had to play the video on 0.25 speed (And then rewound several times haha) the information in here is solid. You really learn some great concepts and how to get this style of text box. Appreciate it!
Wow, I memorized most of this stuff and I don't even have a computer, when it gets fixed I will use this vid as a reference because of how simple and precise it is
You're the best man, honestly. This video is very well done with fast explinations and no wasting time to get ad revenue and stuff. Respect, respect...
You're so quick, and precise, and all your programming is well explained! This is the dialogue tutorial godot needed. I was looking for this for forever! Subbed! :)
Nice call with the tweening; I've been using a timer which is a lot more processor-heavy. Only thing I'd change is instead of doing percent visible I'd do characters visible instead, and set the time to be a variable based on the total characters in the string so you get a consistent playback speed for each character. It also lets you set some methods to handle character tags that affect playback speed, which can be really nice to put emphasis on specific dialogue or add pauses with punctuation to mimic speech patterns.
This is awesome! I was about to implement something like this so the timing couldn't be better. Loved the pacing and explanations as you went. Thank you!
Really appreciate how straight to the point you are with your videos, you don't overexplain but you still get all of the needed info across. Definitely subscribing!
Keep these tutorials coming and your channel will blow up
Love this tutorial! FYI, tweening is still in Godot 4, it's just not a node anymore. You can define a tween as a variable now.
Massively helpful! SO easy to understand and came with a lot of helpful features. This is the gold standard for tutorials.
Great tutorial, helped me understand the tween node better. And great dialogue system! Hope you make more tutorials for Godot.
Since I used Godot 4, I decided to use a timer to make the textbox instead of tweens. But it still helped me a little bit! :D
This is the most important kind of content for the Godot community, good job! Thank you so much! :)
Amaizing straight to the point, clear tutorial , keep it up !
Thanks! This is a really nice tutorial. Just what I was looking for. BTW, you type really fast!😁
In 4.0 you want to do:
if tween:
tween.kill()
tween = get_tree().create_tween()
tween.tween_property(message, "visible_ratio", 1.0, len(message.text) * CHAR_READ_RATE)
tween.connect("finished", on_tween_finished)
damn if i had scrolled like two ticks more i would've seen this two hours ago and it wouldn't be too late for me
Tried this out. The 1.0 ratio doesn't do anything. Changing it to 0.0 got me somewhere, however, it's counting backwards which I obviously do not want. How on Earth do I fix this? (Godot's updates are ridiculous if they constantly make changes. They should've just kept tweens smh)
So is there the exact same video but in godot 4 please ? Just so we know how to make the text appears with tween.
Just use animationplayer. Less code and more easy with animation. The name can be changed like visible_ratio not the other stuff.
this is the definition of "straight to the fucking point" excellent video
Great tutorial, simple but does the job perfectly. Thanks for sharing
Amazing tutorial can't stress it enough
Ayo that's some good stuff right here. Going to kind of merge my extensive C# typewriter cpde with this! Thanks a lot!
The flickering of the textbox. Could it be because of the hide_textbox() in State.FINISHED of _process? If so would adding if queue.empty(): hide_textbox() fix it?
I love Godot for its versatility and how you can change every tiny thing, but the implementation of a basic text box really needs to be streamlined somehow. There are so many moving parts to this for something seemingly simple. It's the only major issue I've had with the engine so far... well, that and having like six different ways to do the same thing depending on what version you have or if it covers the specific needs you have, like... character movement, and how to remember if you've been in a scene or opened a door or emptied a chest.
I do have a question though. In a top-down game where my camera moves relative to my player's position in the scene, does the textbox keep its position relative to the current camera position?
There is an open source text box plugin that looks promising called Dialogic. Might be worth looking into: github.com/coppolaemilio/dialogic
As for the camera movement, since the textbox is a child of a canvaslayer, you can think of it as sitting entirely on a different layer. It is painted above everything else and so won't interfere with camera movement on a layer below it.
@@jontopielski6227 So the camera could be positioned anywhere, and the box will always be in the same spot relative to the player view?
@@Gakusangi Correct
bro you are awesome .thx for the tutorials
This is awesome. Thank you so much!
I'm a total beginner at this so this may be a silly question but how do I now use this in different scenes and inject different text? I replicated this perfectly but don't know how to implement it into different situations. Thanks so much!
You'll want to make the TextBox.tscn globally accessible by Autoloading it in the project settings (Project -> Project Settings -> AutoLoad -> Add TextBox.tscn (**not TextBox.gd)). Now you can reference it anywhere in the project via TextBox.push_text("Text to display here"). If you want to check out a sample project that uses the autoloaded global TextBox, you can reference this one: github.com/jontopielski/shade
@@jontopielski6227 Thank you so much! I'll give it a try.
@@jontopielski6227 Just wanted to say thank you, I worked it out thanks to your help. Cheers!
I want to recreate an ds game and I struggle with the ui because it needs to be pixel perfect. and I do not know if that is possible with godot.
I'm having an issue where if I use enter to skip READING state, next time I don't skip the animation, it makes the end of the text appear and disaper multiple times, this only happens when I skip text rather than waiting for it to finish by itself.
Make sure you're stopping the tweening when you check for "ui_accept". If you share your code on somewhere like github, I'm happy to take a look.
@@jontopielski6227 I am not too much of an expert in github but I made a private repository and added your username to access list, is that okay?
You better believe me when I say Thank You Jon!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! god bless
Godot 4.3 here. I spend more time translating the old engine into 4.3, then actually work after the tutorial. Would be nice to have it updated.
This was super helpful, cheers! :)
I have a camera that follows the player, I don't know how to make the text box always show in the range of the camera
Make sure the textbox is a CanvasLayer node (or a child of a CanvasLayer node). Then it should appear over everything else, regardless of the current camera. If you must, you can also Autoload the textbox so it is preloaded at the start of every scene and it can be globally accessible.
Nice tutorial!! loved it. Your style of just get to the point is amazing! quick and easy I really appreciate it.
Two things i changed for the stuttering:
1: label.percentvisible = 0 before you set the label.text = next_text
2. Finished state i added an if text_queue.empty(): hide_textbox()
for Godot 4 this second point is helpful :D since the first one is required to be included in the tweening line.
@@onepunchman4719thanks im glad i could help!!
I have been fighting with this tutorial the whole day because tweens aren't nodes anymore in Godot 4
me too it's so frustrating. do you find workaround?
I think a good way of doing it would be to start with the state machine, Im not totally familiar woth Godot, but instantiate a new textbox from the actors, so uou put the dialogue into the actors as you place them.
Couple of things for GD4:
1. onready is now @onready
2. Visible percentage is under "Display Text" -> Visible Ratio
do you still have to make a tween cuz those seem to not work in godot 4 (at least within context of this tutorial)
@@lazy_gravity6088same difficulty
Really cool tutorial!!
Hello, thank you so much for this tutorial, that's awesome!
im having an error that i cant figure out the reasoning behind, "_build_interpolation: Tween target object has no property named: percent visible"
any idea why this happens?
Did you add an underscore? “percent_visible”
@@jontopielski6227 i did not, thanks! but i still am having an issue with the tweening, for me the text has appeared as soon as i play the scene and because of this the "end_symbol" doesnt show up (well i guess thats obvious since it shows up when tweening is complete and im having an issue with the tweening..)
@@lawsona i'm guessing the tweening isn't starting - double check the tweening parameters and don't forget to call $Tween.start(). for reference, i've uploaded the code from this tutorial so you can cross check the scene structure and scripts: github.com/jontopielski/rpg-textbox-tutorial
also, i noticed a slight mistake in my tutorial - make sure you connect the Tween's tween_completed, NOT tween_all_completed signal. the reason is that when there are multiple texts the tween_all_completed() doesn't emit properly. i've noted it in the description.
if you're still stuck after a while, feel free to upload your project somewhere publicly accessible and i'll take a look when i get the chance.
@@jontopielski6227 thanks for the help!! i’ll try and see what’s going on with it tmmrw
@@jontopielski6227 okay so the tweening still has some unidentified problem but the if you press "ui_accept" the text appears, but the problem is its just an empty box until you press ui accept.
U should make this textbox with options dialog. That would be useful!
rlly helpful video, could you make a tutorial on how to do an rpg party follwing system like in eartbound where the other party members follow behind the player?
Maybe!
They talk about not enough godot tutorials it's plenty
No size flags in Godot 4.3? Starts at 1:40
I ended up using "Container Sizing" settings to emulate this part of the video
heya I'm having a hard time trying to write the same code but within a different scene? Is there a way to fix this or make it easier, because when i try to copy and paste the code it just repeats the text instead of writing its own.
hey dude how you put some sfx when the text moving by tween?
Is it possible to somehow see how many queued texts are there? I just want the textbox to not disappear every time you change text.
Really good tutorial, I've got a question, Is there a way to play sound every time a letter appears?
Yes - you just need to keep track of how many characters you have read so far. You can update the character counter in the tween_step signal and check if a new character has been read and if so, play a sound. Here is what an implementation might look like (assumes that you've setup an audiostreamplayer with the text typed sound and stored the body of your textbox label as the variable `label`):
...
var current_char_count = 0
func queue_text(next_text):
current_char_count = 0
...
func _on_Tween_tween_step(object, key, elapsed, value):
var next_char_count = label.visible_characters
if next_char_count > current_char_count:
current_char_count = next_char_count
$AudioStreamPlayer.play()
...
It should be noted that sometimes you don't want to play the sound for EVERY character that is typed since it might be too many noises, depending on your text speed. In those cases, you'll want to do every other or every 3, but I'll leave it up to you to make those kinds of tweaks.
@@jontopielski6227 thanks for this. is there a way to pitch-shift the played sound randomly, in the vein banjo kazooie ?
@@jontopielski6227 for anyone in the future, reset current_char_count in display_text() and not queue_text()!!
Well it seems simple enough. But its not beginner friendly. You're creating nodes and editing properties without explaining where to look. I agree with some that say its too fast. But thanks for the vid
Hey this was a great vid! I'd like to know how I can embed multiple dialog boxes in one scene, for example when (in my level) my character reaches interacts with an object I want the dialog box to appear again. However, when I try to embed the dialog box again it does not let me drag it into the position I want, instead it overlaps with the first dialog inside the beginning of my scene. First of all, I'd like to know how I can move the dialog box, or if not is there a way to trigger another dialog box when the next interaction begins. Much appreciated.
Hi, you shouldn't be moving the dialog box's position. It should stay in one position and so long as it is in its own CanvasLayer, it should rest on top of the other canvases without needing to move its position. You should make the dialog box global and only use 1 dialog box for your whole game. When you interact with an object, it should send the text to the one global dialog box
@@jontopielski6227 how could i make it so that with a button, the dialogue box’s position can flip to the top of the screen and then back when pressed again, in the vein of chrono trigger text boxes. what would be the easiest way to manage having it spawn at the top instead of the bottom sometimes?
Thnx This really helped me out !
How can I use this dialog on an Npc?
hi jon, I would like to know if theres a way we can make the text to get the content from an external file?
great tutorial, thank you
Do you have suggestions for how to adapt this for Godot 4 since tweens got reworked and are no longer nodes? I did my best to follow along adapting for the new way to use tweens and I have something that KIND OF works, but then has a bunch of weird behavior (it'd take too long to explain what all's going wrong in this comment that I'm trying to keep short lol) and I'm still a beginner who doesn't know enough about tweens either in Godot 3 or 4 to know what I'm doing wrong 😅 if you'd be willing to take a look at the script I hacked together to check where I went wrong, let me know
Nevermind I figured out how to do the same thing without using a tween. Thanks for the tutorial!
@@Plund3rbunny how , im struggling to do it can you pls help
@@Sungjinbot So in a Label, if you look under the inspector under "Displayed Text" there is a setting called "Visible Characters" that says "the number of characters to display. If set to -1, all characters are displayed." Based on that, here's what I have in my display_text() func instead of the tween stuff:
while label.visible_characters len(next_text[2]) || label.visible_characters == -1:
end_symbol.text = "v"
current_state = state.FINISHED
The await line is how I adjusted character display speed, so you can change that 0.02 to a different number for a different speed. I'm still a Godot newb and coding newb, so there's probably a better way to code all this, but so far this has been working for me without any weird behavior or bugs. Even tho you can't see the adjustments I made to the rest of the code, hopefully this is enough to help and give you a starting place to work from :)
@@Plund3rbunny I want to add that if someone uses your solution, make sure to change all Label.visible_ratio = 0 to Label.visible_characters = 0 where necessary.
Thank you for your solution, I tried figuring it out myself but couldn't exactly get it to work.
This man is fast
I have a question. When i want to add another textbox after the first one finishes do i have to make this all over again or how should i do this?
Thank you!
could this be edited to have dialogue sounds too?
whats the easiest way i could get the textbox to appear when i press ui accept and my character collision is touching the collision of an object? (basically like interacting with an object)
I'll do my best to explain one way to do this. First, make sure your TextBox scene is autoloaded so it's always in the scene and always accessible by code.
You'll want to decide how exactly you're going to interact with objects. One way is to use a RayCast2D that is attached to the player and points out. It's not perfect, but let's go with that for this example.
In the player, whenever "ui_accept" is pressed, check if the collider is colliding with any object that can be interacted with. If there is a collision, then grab the dialogue information from that object directly from the raycast collision check. To make the dialogue information visible from the collided object, try this - create a new scene called "Dialog" that is just an empty node that exports a single String called "dialogue". Then, attach it as a child to any StaticBody2D that the player is going to interact with. The hierarchy will look like this:
- Sprite
- StaticBody2D
- Dialog
- CollisionShape2D
Then, when you check the raycast collision on the player, you can check if the body it collided with (in this case the StaticBody2D) has a child named "Dialog". If it does, grab the exported "dialog" string (which will be the text you want displayed), and push that text to the global TextBox.
Because of how difficult this was to try and explain, I decided to publish a project that I stopped working on that has a working interaction system: github.com/jontopielski/shade
Look at TextBox.tscn, Player.tscn, and Dialog.tscn to get a better understanding of how the systems work. Cheers!
A simpler way could be check the body raycast and use the has_method funtion to check if it has a specifyc function for that:
if Input.is_action_just_pressed("ui_accept") and raycast.is_colliding():
var body = raycast.get_collider()
if body.has_method("on_interact"):
body.on_interact()
Then inside the object function you send the message (+ any other interactions/result) that happens when you interact with the object, and idk if is possible something like virtual functions in godot to make this process as simple as overwrite a function
Trying to use this to implement it into a game, and needless to say, its a very well made tutorial, but I'm having one issue. I did exactly what you did, but despite that, I cant manage to get the start symbol, end symbol, or label text to be blank. my code is the exact same as yours, but when I run the scene, the text is the same and not blank
Heres my version of your script (gd 4):
edit to add a signal because why not
extends CanvasLayer
signal dialogue_finished
enum State {
READY,
READING,
FINISHED
}
const CHAR_RATE = 0.075
@onready var label = $Container/TextContainer/Panel/Label
@onready var bg = $Container/BG
var tween: Tween
var state: State
func _ready():
_hide()
state = State.READY
func _process(delta):
match state:
State.READING:
if Input.is_action_just_pressed("ui_select"):
_finish()
State.FINISHED:
if Input.is_action_just_pressed("ui_select"):
_hide()
dialogue_finished.emit()
state = State.READY
func _hide():
label.text = ""
label.visible_ratio = 0
bg.visible = false
func _show():
bg.visible = true
tween = create_tween()
tween.finished.connect(_finish)
func _finish():
tween.stop()
label.visible_ratio = 1
state = State.FINISHED
func _display_dialogue(lines):
if state != State.READY:
return
label.text = lines
_show()
tween.tween_property(label,"visible_ratio",1,len(lines) * CHAR_RATE)
state = State.READING
I was struggling to get the text to skip to the end on the newer version of Godot and this helps so thanks!
One thing I wanna ask is do we still need the print commands or no?
Thank you for the link to the scripting code you used. I can't see the scripting all too well in the video.
This is not the latest Godot version right ? Because some things appear to not be the same as in the video ^ ^ (thak you for making it tho)
where is the size flags in gd4
Also trying to find this...
I ended up using "Container Sizing" settings, seems to do what the video is demonstrating
@@fal_pal_ thanks but I already got it
Great tutorial no fr!cking around. Also 69th like
WHY SO FAST CHILLLL
It is admittedly a little annoying and unhelpful and comes out kind of egotistical. Anyone can slow or speed down the video, anyway. With that said, the video is helpful, but a teacher should care about the students/viewers, not showing off how fast they can work. 😕
0.5x gang rise up
Thats a good idea. But some parts he'll just click through properties and ill miss them still…
genius
is it possible to we skip the animation if button pressed while animating
Yes, I talk about that at 09:49 - if we press enter while it's reading, it will skip to the end and go into the finished state.
@@jontopielski6227 wow,thanks! it is the only tutorial about dialogs that include both animation and skipping.thank again,you deserve much more subscribers.
this is simple ?
you just create canvas layer WTF how ?
Create a new node and add CanvasLayer
guay me costo pero lo conseguí sacar gracias
Oh nooo. This is not Game Maker Studio 2
Can you make it a little slow,Ur so fast😅
hey, thanks for tutorial, but... it's so faster.. calm down please, chill bro.
I have an Issue, every time I skip the dialog the text stops and the window minimizes itself. I did everthing like in the video and then I tried the UPDATES in the description. The error occoured even before I used "tween_completed" and "$Tween.remove_all()".
OMFG I misspelled FINISHED multiple times... THX for the github code. Otherwise I wouldn't have found out what's up with it not working.
Working code for Godot 4.3:
extends CanvasLayer
@onready var textbox_container = %TextboxContainer
@onready var start = %Start
@onready var label = %Label
@onready var end = %End
enum BoxState {READY, READING, FINSIHED}
var _cur_state: BoxState = BoxState.READY
var _tween: Tween
var _text_queue: Array[String] = []
var speed := 40.0
func _ready() -> void:
_hide_textbox()
add_text("First text queued up!")
add_text("Second text queued up!")
add_text("Third text queued up!")
add_text("Fourth text queued up!")
func _process(delta: float) -> void:
match _cur_state:
BoxState.READY:
if !_text_queue.is_empty():
display_text()
BoxState.READING:
if !_tween.is_running():
label.visible_ratio = 1.0
end.text = "v"
change_state(BoxState.FINSIHED)
if Input.is_action_just_pressed("ui_accept"):
_tween.kill()
BoxState.FINSIHED:
if Input.is_action_just_pressed("ui_accept"):
_hide_textbox()
change_state(BoxState.READY)
func _hide_textbox() -> void:
start.text = ""
end.text = ""
label.text = ""
textbox_container.hide()
func _show_textbox() -> void:
start.text = "*"
textbox_container.show()
func change_state(state: BoxState) -> void:
_cur_state = state
func add_text(next_text: String) -> void:
_text_queue.push_back(next_text)
func display_text() -> void:
var next_text = _text_queue.pop_front()
var length = next_text.length()
label.visible_characters = 0
label.text = next_text
change_state(BoxState.READING)
_show_textbox()
_tween = create_tween()
_tween.tween_property(label, "visible_characters", length, length/speed)
This code does exactly what the video shows. However, I think I should now add:
- A signal for proceeding through a textbox, and a signal for emptying the queue. I can see myself awaiting these signals, so a character can say something, then after talking, do something.
- A way to "reserve" the textbox, so characters cannot talk over each other, or try to enter a conversation during another conversation.
- A bit more control. One thing I have in mind is to end a textbox without input, so a character can be interrupted.
- A choice system. Or, a way to talk back.
- Effects. Pictures and sounds. Make it a bit more "undertale styled".
fix for flickering box:
func _process(delta):
match currentState:
State.READY:
get_tree().paused = true
if !textQueue.empty():
displayText()
State.READING:
if Input.is_action_just_pressed("ui_skip"):
dialogue.percent_visible = 1.0
$Tween.remove_all()
changeState(State.FINISHED)
State.FINISHED:
if Input.is_action_just_pressed("ui_accept"):
changeState(State.READY)
if !textQueue.empty():
showTextbox()
else:
hideTextbox()
### the code is pretty much the same just with an added if-else in the State.FINISHED, remember to use your equivalent function names for showTextbox and hideTextbox
Excellent tutorial! However, I have a question. I'm trying to implement multiple character voices per text box and per "session" (as in, the time during where the textbox is visible and is showing a clear conversation or narrative). However, in order to switch character voices on the fly I have to yield for until the State is set to FINISHED. I'm wondering if there's a better way to go about this or if this is all I can do, as I feel like the yielding, due to it freezing the entire script until a signal is called (in this case, the custom textCompleted signal), might cause performance issues in the near future. I can send code if necessary, here is the debug code in the meantime...
func _on_Button_pressed():
_$Button.disabled = true
_$Textbox.textSound = load("res://sounds/sans_placeholder2.wav")
_$Textbox.queuetext("Teste")
_yield($Textbox,"textFinished")
_$Textbox.textSound = load("res://sounds/sans_placeholder.wav")
_$Textbox.queuetext("Test2")
_yield($Textbox,"textFinished")
_$Textbox.textSound = load("res://sounds/sans_placeholder2.wav")
_$Textbox.queuetext("Hello there....")
_$Button.disabled = false
Hell, I'll go ahead and send the code
extends CanvasLayer
const CHAR_READ_RATE = 0.05
export (AudioStream) var textSound #setget ,setsound
onready var container = $MarginContainer
onready var text = $MarginContainer/MarginContainer/HBoxContainer/Text
onready var end = $MarginContainer/MarginContainer/HBoxContainer/End
enum State {
READY,
READING,
FINISHED
}
signal textFinished
var currentState = State.READY
var textQueue = []
var charCount = 0
func _ready():
hidetxt()
setsound()
func _process(_delta):
match currentState:
State.READY:
if !textQueue.empty():
dsptxt()
State.READING:
if Input.is_action_just_pressed("ui_accept"):
text.percent_visible = 1
$Tween.stop_all()
end.text = "Z"
changestate(State.FINISHED)
State.FINISHED:
if Input.is_action_just_pressed("ui_accept"):
changestate(State.READY)
hidetxt()
func hidetxt():
text.text = ""
end.text = ""
container.hide()
func showtxt():
container.show()
func dsptxt():
setsound()
charCount = 0
var txt = textQueue.pop_front()
text.text = txt
text.percent_visible = 0
changestate(State.READING)
showtxt()
$Tween.interpolate_property(text,"percent_visible",0.0,1.0,len(txt)*CHAR_READ_RATE,Tween.TRANS_LINEAR,Tween.EASE_OUT)
$Tween.start()
func queuetext(txt):
textQueue.push_back(txt)
#func debug():
# queuetext($TextEdit.text)
func tween_finish(object, key):
end.text = "Z"
changestate(State.FINISHED)
func changestate(next):
currentState = next
if next == State.FINISHED:
emit_signal("textFinished")
func text_step(object, key, elapsed, value):
var nextCharCount = text.visible_characters
if nextCharCount > charCount:
charCount = nextCharCount
$TextSound.play()
func setsound():
$TextSound.stream = textSound
Okay, I think I've found a solution. It's a bit of a bodge and I have yet to test it, but I figure I can make the text array into a key-value (with two properties for text and voice) array and add an optional second argument to queuetext for the character voice. Hopefully it works...
@@roxwize I think that's a nice idea. I was going to suggest an additional array for managing the "speaker", but creating a key-value pair object and storing that object in the queue also works. You can add things like the text, the speaker, maybe a portrait image, etc. Good luck!
it dosent work , can you help ? , this is my code :
extends CanvasLayer
const speel_speed = 2
@onready var textbox_container = $textbox_container
@onready var lable = $textbox_container/MarginContainer/HBoxContainer/Label
@onready var tween = create_tween()
enum State {
READY,
READING,
FINISHED
}
var current_state = State.READY
var text_queue = []
func _ready():
$Timer.wait_time = speel_speed
_hide_text_box()
_queue_text("hello im lara")
_queue_text("hello im lara2")
_queue_text("hello im lara3")
_queue_text("hello im lara4")
pass
func _process(delta):
match current_state :
State.READY :
if !text_queue.is_empty() :
_diplay_text()
State.READING :
if Input.is_action_just_pressed("move_up") :
tween.stop()
lable.visible_ratio = 1.0
_change_state(State.FINISHED)
State.FINISHED :
if Input.is_action_just_pressed("move_up") :
_change_state(State.READY)
_hide_text_box()
func _queue_text(next_text):
text_queue.push_back(next_text)
pass
func _hide_text_box():
lable.text = ""
textbox_container.hide()
func _show_text_box():
textbox_container.show()
func _diplay_text():
var next_text = text_queue.pop_front()
_change_state(State.READING)
$Timer.start()
lable.text = next_text
_show_text_box()
tween.tween_property(lable , "visible_ratio", 1.0, speel_speed)
func _change_state(next_state):
current_state = next_state
match current_state :
State.READY :
pass
State.READING :
pass
State.FINISHED :
pass
func _on_timer_timeout():
$Timer.stop()
_change_state(State.FINISHED)
One question, how do I interact with the npc so that the text box appears? I don't know if I'm explaining myself clearly