Write-up: How my dialogue (and NPCs) works

So in order to talk about the dialogue-system I have, I first have to talk about how my NPC’s work. It’s not really complicated at all and it’s not perfect either.. but I’ll continue working on this as I go along.

This write-up is only part of how it works, the actual dialogues are loaded from a JSON-file. (I may split it into different files in the future.) And loaded with the dialogue scene (which I only mention here). So I’ll have to do a “part 2” of this write-up in order to explain it all, I mean.. I probably wont, but maybe.. but I most likely won’t.

The NPC’s

The NPC has these nodes:

Most things should be self-explanatory. But I’ll walk through the important ones:

PlayerDetectionZone
This is an Area2D which is monitoring the player-layer. When the player enters this zone (or more precicely the CollisionShape2D’s circle shape) the node signals the NPC-script that the player entered the zone and it triggers the function that checks if the NPC has a dialogue connected to them. (More on this later on.)

RichTextLabel
This is where the “player recognision”-text is written. (“Hello?“)

ColorRect
Just a background color for the text.

The Global file

The NPC’s are loaded through script. I have a global file with information on where to spawn the NPC’s:

The key for the dict is the scene name, then I have:
“x”: x-position,
“y”: y-position,
“spawn”: should this NPC spawn? true/false, in case the quest is done and they’ve moved or they’re dead,
“sprite”: the name of the sprite to use,
“dialogue”: if they have a dialogue, if they don’t they’re not interactible and wont “recognize” if you walk close to them,
“id”: just a general ID that I use with the dialogues,
“gender”: self explanatory

The world scene

The world scene has a function which checks the global file with the NPC’s against the name of the scene:

func _spawn_npcs():
  if scene_basename in Global.npcs:
    for npc in Global.npcs[scene_basename]:
      if npc.spawn != false:
        _create_npc(npc)

This spawns all the npc’s using the _create_npc() function which also runs a npc.setup() that sets some params on the spawned instance of the current npc (x- and y-position, sprite, name, id, gender and if it has a dialogue). This let’s me easily control through the code which NPC should spawn and if someone shouldn’t anymore then I can just change the “spawn” flag to false.

Recognizing the player

The PlayerDetectionZones collision mask is looking for the player-layer, so when the player enters the CollisionShape2D the attached signal let’s the script know to run the function:

The NPC script checks if the NPC has a dialogue and if it does it triggers the tween to interpolate the properties to animate the appearance of the dialogue window.

Triggering the dialogue

Triggering the dialogue is done by checking if the “ui_accept”-button is pressed when the player is inside the “PlayerDetectionZone”. If the player is inside and the NPC has a dialogue: then I create a Dialogue-scene and append it to the NPC as a child node.

func _input(event):
  if has_dialogue:
    if event.is_action_pressed("ui_accept") and playerDetectionZone.can_see_player():
      ...

This can be done in a cleaner way, but for now it’s working well. I also had to set a flag that said that the dialogue is active, to not trigger it again when continuing the dialogue.

if not dialogue_player.is_dialogue_active:
  add_child(dialogue_player)

Conclusion

So this is how my dialogue system is patched together. A lot of moving parts, but I have complete control over every part of this through scripting which really helps in the long run. This means that I can easily change what the NPC is saying, (re)moving the NPC or stop the NPC from even recognizing the player if it’s played its part (i.e. the quest is done).

Something I plan on adding in the future (except better looking textures to the dialogue boxes) is the feature to toggle repeating or non-repeating dialogues. Sometimes you only want a NPC to say something once or twice, then easily toggle that particular text off (temporarily or permanently).