Skip to main content

TutorialKit

This item only works when running on the client. Client

A self-contained onboarding module for Roblox.

A tutorial is data: an ordered list of Steps, each holding actions that start together. Entering a step gives its actions a shared Janitor; leaving the step destroys that Janitor and clears the spotlight, the pointer and the camera, so no step has to clean up after the one before it.

This page is the facade: everything you require in one place. Start with Tutorial.build and Step.new, then pick actions from FocusUI, PointWorld, Cinematic, Dialog and Custom.

The whole thing in one screen

const TutorialKit = require(ReplicatedStorage.TutorialKit)
const Tutorial, Step = TutorialKit.Tutorial, TutorialKit.Step
const FocusUI = TutorialKit.FocusUI

Dialogs are the one thing the kit does not draw, so you bind your own renderer once, at boot, and get back a Dialog constructor bound to it. Extra is your type, not the kit's: whatever your dialog system needs beyond text and buttons. Annotating request is what fixes Extra to it.

type GuideDialog = { Template: string }

const Dialog = TutorialKit.createDialogs(function(
	request: TutorialKit.DialogRequest<GuideDialog>
): TutorialKit.DialogHandle?
	const extra = request.Extra
	const accept = request.Choices[1]

	const session = DialogModule:Open({
		Template = if extra then extra.Template else "Default",
		NpcName = request.Speaker,
		Lines = request.Lines,
		AcceptText = if accept then accept.Text else nil,
	})
	if accept then
		session.Accept:Connect(accept.Activate)
	end

	return { Close = function() session:Destroy() end }
end, { MaxChoices = 2 })

The tutorial itself is then plain data. Each entry in Choices is one button; pressing it calls that choice's Activate, and Advance = true is what makes Activate move the tutorial forward. Here the dialog's Continue button ends step 1, and the spotlight on the shop button ends step 2.

const tutorial = Tutorial.build({
	Step.new(1, {
		Dialog.new({
			Speaker = "Guide",
			Lines = { "Welcome, traveler!" },
			Choices = { { Text = "Continue", Advance = true } },
			Extra = { Template = "Wooden" },
		}),
	}),

	Step.new(2, {
		FocusUI.to(shopButton, { Advance = true }),
	}),
})

tutorial:Start()

:::caution The server half is a separate require This facade pulls the spotlight, the pointer and ezvisualz, so it is client only. Saving progress lives in TutorialServer, which you require directly:

const TutorialServer = require(Packages.TutorialKit.Server)

:::

The kit ships its own janitor and ezvisualz under Packages, so it never touches the host game's dependency tree.

Properties

Tutorial

This item is read only and cannot be modified. Read Only
TutorialKit.Tutorial: Tutorial

The runtime. Tutorial.build is where a tutorial starts.

Step

This item is read only and cannot be modified. Read Only
TutorialKit.Step: Step

One step of a flow. See Step.new.

FocusUI

This item is read only and cannot be modified. Read Only
TutorialKit.FocusUI: FocusUI

Dim the screen around a GuiObject. See FocusUI.to.

PointWorld

This item is read only and cannot be modified. Read Only
TutorialKit.PointWorld: PointWorld

Beam and icon toward something in the world. See PointWorld.to.

Cinematic

This item is read only and cannot be modified. Read Only
TutorialKit.Cinematic: Cinematic

Camera flights. See Cinematic.focus and Cinematic.tour.

Custom

This item is read only and cannot be modified. Read Only
TutorialKit.Custom: Custom

Your own logic as an action. See Custom.new.

Client

This item is read only and cannot be modified. Read Only
TutorialKit.Client: TutorialClient

The client half of the server bridge. Driven for you when a tutorial replicates.

UI

This item is read only and cannot be modified. Read Only
TutorialKit.UI: UI

Declarative instance builder, for writing a dialog renderer without a UI framework.

Signal

This item is read only and cannot be modified. Read Only
TutorialKit.Signal: Signal

The signal implementation behind every event the kit exposes.

Spotlight

This item is read only and cannot be modified. Read Only
TutorialKit.Spotlight: Spotlight

The dim overlay, for use outside a tutorial.

WorldPointer

This item is read only and cannot be modified. Read Only
TutorialKit.WorldPointer: WorldPointer

World guidance, for use outside a tutorial.

CameraTour

This item is read only and cannot be modified. Read Only
TutorialKit.CameraTour: CameraTour

Camera flights, for use outside a tutorial.

Functions

createDialogs

constructor
TutorialKit.createDialogs(
rendererDialogRenderer<Extra>,--

Draws a request with your own UI

infoRendererInfo?--

What your renderer can draw

) → Dialogs<Extra>--

A Dialog constructor bound to that renderer

Binds your dialog renderer and returns a typed Dialog constructor.

Documented in full on Dialog.createDialogs.

Show raw api
{
    "functions": [
        {
            "name": "createDialogs",
            "desc": "Binds your dialog renderer and returns a typed `Dialog` constructor.\n\nDocumented in full on [Dialog.createDialogs].",
            "params": [
                {
                    "name": "renderer",
                    "desc": "Draws a request with your own UI",
                    "lua_type": "DialogRenderer<Extra>"
                },
                {
                    "name": "info",
                    "desc": "What your renderer can draw",
                    "lua_type": "RendererInfo?"
                }
            ],
            "returns": [
                {
                    "desc": "A Dialog constructor bound to that renderer",
                    "lua_type": "Dialogs<Extra>"
                }
            ],
            "function_type": "static",
            "tags": [
                "constructor"
            ],
            "source": {
                "line": 197,
                "path": "src/init.luau"
            }
        }
    ],
    "properties": [
        {
            "name": "Tutorial",
            "desc": "The runtime. [Tutorial.build] is where a tutorial starts.",
            "lua_type": "Tutorial",
            "readonly": true,
            "source": {
                "line": 96,
                "path": "src/init.luau"
            }
        },
        {
            "name": "Step",
            "desc": "One step of a flow. See [Step.new].",
            "lua_type": "Step",
            "readonly": true,
            "source": {
                "line": 104,
                "path": "src/init.luau"
            }
        },
        {
            "name": "FocusUI",
            "desc": "Dim the screen around a GuiObject. See [FocusUI.to].",
            "lua_type": "FocusUI",
            "readonly": true,
            "source": {
                "line": 112,
                "path": "src/init.luau"
            }
        },
        {
            "name": "PointWorld",
            "desc": "Beam and icon toward something in the world. See [PointWorld.to].",
            "lua_type": "PointWorld",
            "readonly": true,
            "source": {
                "line": 120,
                "path": "src/init.luau"
            }
        },
        {
            "name": "Cinematic",
            "desc": "Camera flights. See [Cinematic.focus] and [Cinematic.tour].",
            "lua_type": "Cinematic",
            "readonly": true,
            "source": {
                "line": 128,
                "path": "src/init.luau"
            }
        },
        {
            "name": "Custom",
            "desc": "Your own logic as an action. See [Custom.new].",
            "lua_type": "Custom",
            "readonly": true,
            "source": {
                "line": 136,
                "path": "src/init.luau"
            }
        },
        {
            "name": "Client",
            "desc": "The client half of the server bridge. Driven for you when a tutorial replicates.",
            "lua_type": "TutorialClient",
            "readonly": true,
            "source": {
                "line": 144,
                "path": "src/init.luau"
            }
        },
        {
            "name": "UI",
            "desc": "Declarative instance builder, for writing a dialog renderer without a UI framework.",
            "lua_type": "UI",
            "readonly": true,
            "source": {
                "line": 152,
                "path": "src/init.luau"
            }
        },
        {
            "name": "Signal",
            "desc": "The signal implementation behind every event the kit exposes.",
            "lua_type": "Signal",
            "readonly": true,
            "source": {
                "line": 160,
                "path": "src/init.luau"
            }
        },
        {
            "name": "Spotlight",
            "desc": "The dim overlay, for use outside a tutorial.",
            "lua_type": "Spotlight",
            "readonly": true,
            "source": {
                "line": 168,
                "path": "src/init.luau"
            }
        },
        {
            "name": "WorldPointer",
            "desc": "World guidance, for use outside a tutorial.",
            "lua_type": "WorldPointer",
            "readonly": true,
            "source": {
                "line": 176,
                "path": "src/init.luau"
            }
        },
        {
            "name": "CameraTour",
            "desc": "Camera flights, for use outside a tutorial.",
            "lua_type": "CameraTour",
            "readonly": true,
            "source": {
                "line": 184,
                "path": "src/init.luau"
            }
        }
    ],
    "types": [],
    "name": "TutorialKit",
    "desc": "A self-contained onboarding module for Roblox.\n\nA tutorial is data: an ordered list of [Step]s, each holding actions that start together.\nEntering a step gives its actions a shared Janitor; leaving the step destroys that Janitor and\nclears the spotlight, the pointer and the camera, so no step has to clean up after the one\nbefore it.\n\nThis page is the facade: everything you require in one place. Start with [Tutorial.build] and\n[Step.new], then pick actions from [FocusUI], [PointWorld], [Cinematic], [Dialog] and [Custom].\n\n### The whole thing in one screen\n\n```lua\nconst TutorialKit = require(ReplicatedStorage.TutorialKit)\nconst Tutorial, Step = TutorialKit.Tutorial, TutorialKit.Step\nconst FocusUI = TutorialKit.FocusUI\n```\n\nDialogs are the one thing the kit does not draw, so you bind your own renderer once, at boot,\nand get back a `Dialog` constructor bound to it. `Extra` is your type, not the kit's: whatever\nyour dialog system needs beyond text and buttons. Annotating `request` is what fixes `Extra`\nto it.\n\n```lua\ntype GuideDialog = { Template: string }\n\nconst Dialog = TutorialKit.createDialogs(function(\n\trequest: TutorialKit.DialogRequest<GuideDialog>\n): TutorialKit.DialogHandle?\n\tconst extra = request.Extra\n\tconst accept = request.Choices[1]\n\n\tconst session = DialogModule:Open({\n\t\tTemplate = if extra then extra.Template else \"Default\",\n\t\tNpcName = request.Speaker,\n\t\tLines = request.Lines,\n\t\tAcceptText = if accept then accept.Text else nil,\n\t})\n\tif accept then\n\t\tsession.Accept:Connect(accept.Activate)\n\tend\n\n\treturn { Close = function() session:Destroy() end }\nend, { MaxChoices = 2 })\n```\n\nThe tutorial itself is then plain data. Each entry in `Choices` is one button; pressing it calls\nthat choice's `Activate`, and `Advance = true` is what makes `Activate` move the tutorial\nforward. Here the dialog's Continue button ends step 1, and the spotlight on the shop button\nends step 2.\n\n```lua\nconst tutorial = Tutorial.build({\n\tStep.new(1, {\n\t\tDialog.new({\n\t\t\tSpeaker = \"Guide\",\n\t\t\tLines = { \"Welcome, traveler!\" },\n\t\t\tChoices = { { Text = \"Continue\", Advance = true } },\n\t\t\tExtra = { Template = \"Wooden\" },\n\t\t}),\n\t}),\n\n\tStep.new(2, {\n\t\tFocusUI.to(shopButton, { Advance = true }),\n\t}),\n})\n\ntutorial:Start()\n```\n\n:::caution The server half is a separate require\nThis facade pulls the spotlight, the pointer and ezvisualz, so it is client only. Saving progress\nlives in [TutorialServer], which you require directly:\n\n```lua\nconst TutorialServer = require(Packages.TutorialKit.Server)\n```\n:::\n\nThe kit ships its own janitor and ezvisualz under `Packages`, so it never touches the host game's\ndependency tree.",
    "realm": [
        "Client"
    ],
    "source": {
        "line": 88,
        "path": "src/init.luau"
    }
}