All aboard the signal bus!
My last post was ~3 weeks ago, so this is a smaller update. It feels like a good time for a post though, since I just implemented the thing I’ve been working on since we last chatted.
At the end of the last update, I had configured a very basic textbox system. When the player examines an object that can be examined, a text box will pop up, and some text will be printed to the screen. One issue the starter textbox system had was that while the text box that prints text for the player to read is open, I do not want inputs on the controller to move the player. Once the textbox opens, I want the player to think of their inputs as being ‘directed to’ the text box - if you hit confirm, for example, you’ll move to the next part of the text being printed. Some textboxes will allow directional movement of a cursor so that you can select an option from a displayed set of choices.
Here is a video showing the state of the textbox system at the end of our last update:
So, let’s talk about node structure in godot for a second. In godot, everything is a node. All those nodes are structured like a tree to make up your game. Everything in the tree has a path that can be traced up to the origin of the tree, called the “root viewport”. I just call it the root for short.
In the video above, the code is basically structured with these three things on the “top level” of that tree, right below the root:
Dialogue Manager - code responsible for creating text boxes and querying the text data file to see what the game should print in the textbox. Dialogue Manager will start with no children, but textboxes will become children of the Dialogue Manager upon creation.
Global Manager - Switches which scene we’re looking at. I intend to have this pick up additional tasks as I continue.
The Current Scene - this is the top level node of whatever scene we’re currently in. Notably, the player node will always be in the current scene. So when our character is walking around the dream world in the video above, all of the things you can see on the screen are children of some top level node that represents the entire dream world ‘scene’. If the main character walks through a door to their bedroom, the current scene would change (the Global Manager changes it), and we would then see the main character in their room.
The tricky thing about this setup, is that the current scene is going to be changing all the time. As we play the game, we’ll go in and out of all sorts of entries and exits, and the current scene will change each time. I also mentioned that the player node is in the current scene - this means that the parent of the player node is always subject to change.
Why do we need to know the parent of the player node? Because communication is done up and down the tree. Here’s a visual:
In the above, the blue boxes are our three major nodes that we talked about. The yellow boxes are the children of those nodes. Now, let’s say that the we want to program this game such that if there’s a textbox on screen, the player will no longer be allowed to move (the original problem we were trying to solve). If everything in the game were static, the textbox would tell its parent the dialogue manager to pass a message along. The Root would be used to locate the Current Scene blue box, and pass it the message. The Current Scene blue box would then pass the message to the player - ‘hey, usually you move around when a direction is pressed on the controller, but for now, stop doing that.’
The yellow boxes cannot talk directly to each other without the information flowing through the blue boxes. This is why not knowing the parent of the player is a tricky situation - whoever that parent is, that’s who needs to be involved in this information flow. It’s not easy to ask the root ‘hey, find us the parent of the player.’ We could technically program something using the get_node() function that looked for the player node’s location dynamically using our knowledge of the structure of our game, but I think the issue with a solution in that direction is that this activity of looking for the player node is really, really easy to break. Coding in a direct search for the player node is susceptible to stop working as we add more complexity to the project, and we’d need to recreate this intricate searching process if we ran into a similar issue in the future anyway (like, if we weren’t looking for the player, but someone else in the current scene).
This is precisely where I got stuck for a while. If you’ve been following along on this journey, in my last blog post I mentioned that the biggest thing that got me unstuck the last time I was majorly stuck was shifting toward reading the Godot API a lot more, and watching youtube tutorials and reading blogs posts or forum posts containing intricate instructions on how to implement something a lot less.
This time, the exact opposite is how I got unstuck. This is a case where the Godot API docs, as far as I could tell, really can’t directly help you. There’s no built in functionality that solves this issue. Rather, there’s a concept that we need to program into our game to do it. After getting really frustrated with not being able to solve this issue by reading through every scrap of information on signals I could find in the API documentation, a google search led me to discussions online about something called a ‘signal bus’. First, let’s talk about signals.
In the godot documentation, they describe a “signal” as godot’s version of ‘the observer pattern’. The observer pattern is a general programming term that refers to situations where instead of having two objects in your code talk directly to each other (which would tightly couple the two objects in your code, similar to the get_node() solution briefly discussed above), the first object will announce what is happening to it, with no care as to who is listening, and the second object will know what to listen for.
In order for a signal to work, you need the following:
Object 1 emits a signal when it does something
Object 2 is coded to look for that specific signal from that specific object
When Object 2 sees the signal, it runs a corresponding function that you define
Here is the issue I kept running into when I read the API docs. The whole idea of signals and the observer pattern is to decouple things in your code. But, as listed above, Object 2 still needs to know who it is listening to. It’s only sort of decoupled.
Going back to the problem at hand with the textboxes, the textbox could send a signal out that there’s text on the screen. Our intention would then be for the player to hear it, and in response, disable the ability to move until the text box is gone. But, the player would still need to be coded to listen to the signals of this textbox, and here we run into the exact same issue we just had. Textboxes only appear when they need to, so hard coding a reference connecting the player to the textbox isn’t really possible. Textboxes get created as you play the game.
This finally leads to what the signal bus actually is - it’s a place where we are grouping signals into a single, stable point of contact. Now the project looks like this:
Similar to the Dialogue Manager and the Global Manager, the Signal Bus is our third singleton or autoload of the project. Here is how things connect:
When something (e.g., the textbox) wants to send a signal out, instead of sending it themselves, they tell the Signal Bus to send a signal out.
When is this better than sending it out yourself? When you, the signal sender, are going to be difficult for recipients to find. In our example, since a textbox is only created at the moment it’s needed (e.g., when you talk to someone), and it also is in a totally different branch than the player, the textbox is difficult for the player to find. However, the Signal Bus is easy to find. It’s always there, will never change, and is simple and safe to be hard coded to
The Signal Bus sends out a signal. E.g., the signal bus sends out a signal that dialogue has started, and that there’s a textbox on screen. Also note that sending out signals is the only job the Signal Bus has. The only code in it are signal definitions.
The player knows to listen to the Signal Bus. In response to hearing about dialogue starting, the player now locks itself in place until the textbox is gone (at which point, the Signal Bus would send a second signal out).
I won’t bother posting the “after video” since it visually looks exactly like the “before” video, but I now have this implemented in my game, and am using it to prevent the player from wandering around while text is on the screen. Another baby step down!
Life updates - the last wedding
2023 has consisted of a bachelor party and four weddings that were all out of town. The last event happened in between this post and our last blog post - a wedding in Chicago!
One picture worth sharing is the wedding gift that my wife made for the couple that got married. The couple has a dog, and Erika made this doggie bowl at her pottery class, and then painted the face of the dog on it herself. I’m kind of blown away at how good it is. It seriously looks exactly like the couple’s dog.
Another fun activity from the past few weeks is Me, Erika and a few friends (as well as one of their 3 year old children), all went apple picking in Pittsburgh! I feel like we’ve talked about going apple picking each of the last few years but have never done it. It was a really fun time and also resulted in me making this deep dish wagon of an apple pie. I baked it in a springform pan to enable the deeper crust. This baby had over five pounds of local apples in it!
I actually also made a strawberry lemon cheesecake for a movie night at our house where I finally saw the Barbie movie, but I sadly forgot to take a picture. Imagine it please!
Thanks for reading! Talk to you next time :)