Before we wrap up our journey together, I’d like to spend a bit of time talking about how we enable interactions between players and the world.
In this chapter, we’re going to look at how to handle proximity-based interactions and how to display interactions when they happen.
It’s a little difficult to show these concepts without the context of a game, because they are so dependent on the specifics of the game; but we’ll try anyway.
I’ll talk through different code approaches, but I don’t expect you to create a dedicated experiment for the concepts in this chapter. The approach you take will definitely depend on the game you're building.
Managing Interactions
Think back to when we made our own version of Invasion. The player can move through the map, encountering survivors.
Interacting with survivors
The way we coded it was that the survivors would immediately start to follow the player when they were close enough to each other. We could make the game a lot deeper by giving the player a choice to rescue or abandon the survivor.
One practical way to do this would be to use Area2D nodes to detect the approach of the player. If the player is in range of something that can be interacted with, we can present them with a prompt to let them know that interaction options are available. Many games do this, including one of my favorites:
Interacting with things in Forager
In Forager, when you are close enough to interact with something, it gets this square indicator. Sometimes, you’ll need to press a specific button to begin the interaction. Other times you’ll be able to whack at the tree or stone with the tool you're holding.
The way I usually code this sort of thing is to have a dedicated node, called Interactable:
Making a reusable interaction manager
These Area2D-based nodes should have their collision layers and masks set to a layer dedicated to this purpose.
Setting an unused layer for interactables
The base Interactable node doesn’t have a collision shape or collision polygon defined, because this should be set as it is added to another node.
Keeping it simple, we can use a CollisionShape2D:
Creating an example to assess functionality
If we were to switch to remote view while running code like this, we'd see the Other Interactable variable is null:
Not collisions…
If we then moved the player so that it collided with the NPC, we’d see the link created:
…Until we move the player closer
At this point, we could show that interactions are possible. If the player pressed the appropriate key, we could even cause the interaction to begin.
The way the player moves closer depends on the game. It could be via keyboard input, like we had in Bouncy Cars. It could be from click-to-move navigation, like we had in Invasion. The point is that the collision detection and behavior are no longer in the players’ scripts, but rather in a dedicated node.
As we saw in Invasion, there is still the issue of CharacterBody2D collision shape avoidance preventing us from using arbitrary Area2D nodes in this way. We’d need to change the survivors to not be CharacterBody2D nodes if we wanted to retrofit this interaction model on to Invasion.
Since we’re using signals, we can attach listeners to run code when the signals are emitted. A signal for receiving the interaction (from Player → NPC) could show a new conversation or start some NPC code:
Handling initiated interactions
How This Could Apply to Invasion
Let’s think about how this approach might work if we added it to Invasion. The screenshot I showed earlier was from my first build of the game and is a bit more feature-rich than the version we built together.
If it already includes soldiers and when you get close enough to them, they chase you down. You can escape them by moving to another screen. If they catch you and you have a survivor already following you, then the survivor freezes in place and you can no longer rescue them.
Allowing the player to decide whether they rescue survivors they encounter. Perhaps some survivors will slow the player down or cost the player something to rescue. Allowing the player to make the decision to rescue would be more interesting than the survivor immediately following the player.
Allowing the player to decide what to do when a soldier catches up to the player. Could you choose what happens to the survivor? Could you keep the survivor by giving the solider something else? What if a currency spawned inside the map and you could trade it for safe passage?
Having Conversations
A common way for interactions to happen in games is for the main characters to have conversations. This could be a detective asking questions during an investigation, or a boss monologging before their untimely demise.
The original version of Invasion included dialog between the player, survivors, and soldiers:
Conversations with survivors
On spawning, I assigned each soldier and survivor with a portrait. When the player walked in range of a survivor, I’d select and play a random survivor line. When a soldier caught up to the player, I’d play a random soldier line.
I handled all the dialog with a third-party add-on, called Dialogic. You can find the add-on and installation instructions over at GitHub. There’s also a companion website, with links to documentation:
Dialogic website
Dialogic has a custom editor, which allows you to preconfigure different conversations. The interface changes from time to time, but it should resemble this layout:
The Dialogic interface
This is where you can create new conversations, with custom portraits, sound, and decision trees. There’s a lot of depth to this add-on, but I want to focus on the simplest of setups.
You need to create a few characters.
You need to set up timelines.
You can start a timeline with the Dialogic.start_timeline method.
I thought it might be useful to show you what I did for the first version of Invasion, so you have a sense of what’s possible.
Dialog in Invasion
I followed the first two steps in that list so that I had a full set of characters and some example timelines for my game:
Setting up characters and timelines
It’s worth noting that this screenshot is from Godot 3.x and Dialogic 1.x. The interface and methods with which we start timelines will be different to Godot 4.x and Dialogic 2.x.
As you can see, there are a ton of soldier profile pictures, around 30 in total. I created the soldier profile pictures with a red hue and then re-colored them to yellow for the survivor profile pictures.
The example timelines were a sequence of events and text that model the typical dialog structures I wanted for Invasion:
Creating example timelines
Dialogic has methods we can use to play these timelines. Playing a pre-created timeline is ok, but I wanted to achieve something a bit more dynamic. Fortunately, these timelines are saved in text files, and inspecting them allowed me to set up dynamic conversations:
Dynamic conversations
A lot of this data is what I call “magic variables,” inasmuch as they’re static internal values that Dialogic understands. The interesting thing is that creating these timelines dynamically means we can substitute the characters and lines at runtime.
In this version of Invasion, I randomly selected the soldier and survivor profiles when the soldiers and survivors were added to each room. Remembering what each survivor looks like means we can show the same profile pictures for them over the course of a level.
Summary
I hope you have a better sense of some of the things you can add to your games, which will help them feel more immersive and interactive. I’m sure they will be useful in the final game we’re going to make.