Tutorial: Defold Integration

This tutorial walks you through integrating plotknot into a Defold project from scratch. You’ll compile a story, load it in a Defold script, display text in a GUI, and present choices as buttons.

This tutorial assumes you’ve read the Programmer Tutorial and have a basic Defold project set up.

What you’ll build

A dialogue system: a GUI label shows story text, GUI buttons show choices, and pressing a button advances the story.

Step 1: Copy the runtime

Copy the plotknot runtime into your Defold project:

cp runtime/defold/plotknot.lua your_project/main/

The runtime is a single Lua file with no dependencies beyond Lua 5.1+ (which Defold includes).

Step 2: Write and compile a story

Create main/hello.plotknot:

# The Forest

You stand at the edge of a dark forest. The path ahead splits in two.

choice:
- Take the left path -> left_path
- Take the right path -> right_path

## Left Path

You follow the mossy trail deeper into the woods.

-> ending

## Right Path

The right path leads to a sunlit clearing.

-> ending

## Ending

Your journey through the forest ends here.

Compile it:

plotknot compile main/hello.plotknot --output main/
# compiled: main/hello.plotknot -> main/hello.lua

This produces main/hello.lua — a Lua table Defold can load as a module.

Step 3: Create a GUI for dialogue

In Defold, create a GUI scene (dialogue.gui) with:

  • A text node named story_text — displays the current narrative text
  • A box node named choice_1 with a text node — first choice button
  • A box node named choice_2 with a text node — second choice button
  • A box node named choice_3 with a text node — third choice button

Position them at the bottom of the screen. Hide the choice buttons by default.

Step 4: Create a script to drive the story

Create main/dialogue.script:

local plotknot = require("main.plotknot")
local story_data = require("main.hello")

function init(self)
  self.story = plotknot.load(story_data)
  self.story:start()

  -- Hide choice buttons initially
  gui.set_enabled(gui.get_node("choice_1"), false)
  gui.set_enabled(gui.get_node("choice_2"), false)
  gui.set_enabled(gui.get_node("choice_3"), false)

  show_current(self)
end

function show_current(self)
  local lines = self.story:current()
  local text_parts = {}
  local choices = nil

  for _, line in ipairs(lines) do
    if line.type == "text" then
      table.insert(text_parts, line.text)
    elseif line.type == "choice" then
      choices = line.choices
    end
  end

  -- Display text
  gui.set_text(gui.get_node("story_text"), table.concat(text_parts, "\n\n"))

  -- Display choices
  local buttons = { "choice_1", "choice_2", "choice_3" }
  for i, btn_name in ipairs(buttons) do
    local btn = gui.get_node(btn_name)
    if choices and choices[i] then
      gui.set_enabled(btn, true)
      gui.set_text(gui.get_node(btn_name .. "/text"), choices[i].text)
    else
      gui.set_enabled(btn, false)
    end
  end
end

function on_input(self, action_id, action)
  if action_id == hash("touch") and action.pressed then
    -- Check which choice button was pressed
    for i = 1, 3 do
      local btn = gui.get_node("choice_" .. i)
      if gui.is_enabled(btn) and gui.pick_node(btn, action.x, action.y) then
        self.story:advance(i)
        show_current(self)
        return true
      end
    end
  end
  return false
end

Step 5: Add the script to your collection

  1. Add the dialogue.gui scene to your main collection
  2. Attach dialogue.script to the GUI scene
  3. Make sure hello.lua and plotknot.lua are in the main/ directory

Step 6: Run it

Build and run your Defold project. You should see:

  1. “You stand at the edge of a dark forest. The path ahead splits in two.”
  2. Two choice buttons: “Take the left path” and “Take the right path”
  3. Pressing a button navigates to that stage and shows the next text

Step 7: Add game function calls

Register handlers for call statements in your story:

function init(self)
  self.story = plotknot.load(story_data)

  -- Register game functions
  self.story:on_call("play_sound", function(name)
    -- Play a Defold sound component
    msg.post("#sound", "play_sound", { sound = name })
  end)

  self.story:on_call("shake_camera", function(intensity)
    -- Trigger a camera shake
    msg.post("/camera#script", "shake", { intensity = intensity })
  end)

  self.story:start()
  show_current(self)
end

In your story:

## Enter

call play_sound("footsteps")
call shake_camera(2)

You descend into the darkness.

Step 8: React to stage changes

Use on_stage to trigger side effects when the story enters a stage:

self.story:on_stage("boss_room", function()
  msg.post("/music#script", "play_track", { track = "boss_theme" })
end)

self.story:on_stage("ending", function()
  msg.post("/ui#script", "show_credits")
end)

Step 9: Handle story end

Check story:stopped() to detect when the story has ended:

function show_current(self)
  if self.story:stopped() then
    gui.set_text(gui.get_node("story_text"), "The End")
    -- Hide all choice buttons
    for i = 1, 3 do
      gui.set_enabled(gui.get_node("choice_" .. i), false)
    end
    return
  end
  -- ... rest of show_current
end

What you’ve built

A complete Defold dialogue system:

  • Story text displayed in a GUI label
  • Choices presented as tappable buttons
  • Game function calls for sound and camera effects
  • Section-entry hooks for music and UI triggers
  • Story-end detection