Back to Projects

DialogueKit

Released
Roblox
Luau
Dialogue
Open Source

DialogueKit is a free drop-in Roblox NPC dialogue system built around data instead of per-NPC scripts. Add a tagged NPC, give it one DialogueData module, choose Classic or Reimagined, and the shared client/server system handles the rest.

Two dialogue modes

Classic uses the reusable dGui screen template, locks the player in place, focuses a configurable camera on the NPC, and fades the local character to keep the dialogue readable.

Reimagined has no camera. It creates client-only world UI with a BillboardGui above the NPC and an AnswerPart for local answer buttons, so players do not fight over shared replicated dialogue controls.

DialogueData.lua
1return {
2    mode = "Classic",
3    options = {
4        distance = 2,
5        FOV = 90,
6    },
7    dialogue = {
8        start = {
9            speed = 0.03,
10            lines = {
11                "Hello, {player}.",
12                "This is a Classic dialogue.",
13            },
14            answers = {
15                { text = "Continue", next = "more" },
16                { text = "Goodbye" },
17            },
18        },
19    },
20}

Drop-in NPC setup

NPCs are discovered through CollectionService. The generic client script watches for the DialogueNPC tag, requires the model's DialogueData, registers it with DialogueSystem, waits for the server-created prompt, and starts dialogue when the prompt is triggered.

DialogueClient.lua
1local function setupNpc(model)
2    local data = require(model:WaitForChild("DialogueData"))
3    local mode = data.mode or "Classic"
4    local options = data.options or {}
5
6    DialogueSystem.Dialogues[model.Name] = data.dialogue
7    DialogueSystem:SetMode(mode, options, model.Name)
8
9    local torso = model:FindFirstChild("Torso")
10        or model:FindFirstChild("HumanoidRootPart")
11        or model.PrimaryPart
12
13    local prompt = torso and torso:WaitForChild("ProximityPrompt", 5)
14    if prompt then
15        prompt.Triggered:Connect(function()
16            DialogueSystem:StartDialogue(model.Name)
17        end)
18    end
19end

Data-driven dialogue

Each node can define typed lines, answer choices, follow-up nodes, and its own typewriter speed. Lower speed values type faster; higher values type slower. The {player} token is replaced with the local player's display name.

DialogueData.lua
1return {
2    mode = "Reimagined",
3    options = {},
4    dialogue = {
5        start = {
6            speed = 0.03,
7            lines = {
8                "Hi, I am using Reimagined mode.",
9                "My text appears above my head.",
10            },
11            answers = {
12                { text = "Show me more", next = "more" },
13                { text = "Goodbye" },
14            },
15        },
16
17        more = {
18            speed = 0.03,
19            lines = {
20                "Answer buttons appear in the world for this player only.",
21            },
22            answers = {
23                { text = "Back", next = "start" },
24                { text = "Goodbye" },
25            },
26        },
27    },
28}

Server-approved actions

Rewards and teleports stay server-side. The client only sends the NPC name, current node, and answer index. The server reloads the NPC's DialogueData, validates that answer, then runs a registered action handler if the answer has an action field.

DialogueData.lua
1answers = {
2    {
3        text = "Give me 100 Coins",
4        action = "GiveCoins",
5        amount = 100,
6        next = "coins",
7    },
8    {
9        text = "Teleport me to Town",
10        action = "TeleportToPad",
11        target = "TownTeleport",
12        next = "teleport",
13    },
14}
DialogueServer.lua
1local actionHandlers = {
2    GiveCoins = function(player, answer)
3        local amount = tonumber(answer.amount) or 0
4        local coins = player:FindFirstChild("leaderstats")
5            and player.leaderstats:FindFirstChild("Coins")
6
7        if coins then
8            coins.Value += amount
9        end
10    end,
11
12    TeleportToPad = function(player, answer)
13        local pads = workspace:FindFirstChild("TeleportPads")
14        local pad = pads and pads:FindFirstChild(answer.target)
15        local root = player.Character
16            and player.Character:FindFirstChild("HumanoidRootPart")
17
18        if pad and root then
19            root.CFrame = pad.CFrame + Vector3.new(0, 3, 0)
20        end
21    end,
22}

Adding an NPC

Put the model under Workspace.DialogueFolder, tag it DialogueNPC, add one DialogueData ModuleScript, and press Play. No per-NPC Script or LocalScript is needed.