2 min read

Dialog System

Dialog System

A quick look at the dialog system we've implemented in Moonfarer.


We're using the Ink scripting language to handle dialog and cutscenes. The flexibility of Ink allows us to orchestrate cutscenes and some minor game logic.

We've created a large single tree of dialog that covers the entire game (so far). NPCs define their entry point into the dialog at runtime. This has some benefits:

  • One Ink project to work with simplifies finding specific events.
  • Variables and external functions defined in Ink are standard and can always be used.
  • It's easier to cross over between separate events since they are still in the same project.

Cutscenes and Cutscene Actions

After implementing dialogs we needed some way of interacting with the game world for triggering events. That's where Cutscene Actions come in.

When the Ink dialog object is created a number of external functions are added. These external functions add a Cutscene Action to a queue which is then executed sequentially.

Some examples:

  • Walk to location
  • Emote
  • Manage portraits
  • Set game flags
  • Add items
  • Start trades
  • Call arbitrary functions on game objects

A simple cutscene looks like this:

~ selectNpc("Mum")
~ walkTo("point:home.mumstanding")
~ faceDirection("north")
~ delay(0.5)
~ emote("surprise")
~ setPortraitSlot("Mum", 0, "default")
~ execute()
Oi, wake up! You're going to be late!

When the function 'execute()' is called the cutscene will start. First the character 'Mum' is selected as the target for the next actions. Then Mum will walk to a point in the world, face north, wait, then emote. At this point the NPC portrait is shown in the UI and the dialog is spoken.


One of the latest additions to the cutscene actions list is the ability to handle portraits during a dialog.

NPCs can be assigned to a slot (or multiple, see the vid below). Focus can be given to a specific portrait when a dialog line is spoken and the display name can be customized.