Skip to main content

Tutorial

This item only works when running on the client. Client

The runtime that walks the steps.

It owns the presentation, not the truth. When a step ends the runtime destroys the step Janitor and clears the spotlight, the pointer and the camera, so every step starts on a clean screen and no step can leak visuals into the next one.

const tutorial = Tutorial.build({
	Step.new(1, { FocusUI.to(shopButton, { Advance = true }) }),
	Step.new(2, { PointWorld.to(fountainPart) }),
})

tutorial:Start()

If tutorial progress lives on your server, do not call Advance from the client. Let the server decide and mirror it with GoTo, or set Replicate = true and let the kit do it.

Types

TutorialConfig

interface TutorialConfig {
SpotlightSpotlightConfig?--

Styling for the dim overlay and the highlight

PointerWorldPointerConfig?--

Styling for the beam, the floating icon and the outline

CameraCameraTourConfig?--

Framing defaults for cinematics

Replicateboolean?--

Mirror progress with the server. Defaults to false

}

The optional second argument to Tutorial.build.

Replicate = true reports every step and the completion to the server, and follows the steps the server sends back. It requires require(TutorialKit.Server):Start() on the server, and it makes build yield until the bridge has replicated.

Properties

Spotlight

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

The dim overlay shared by every step. Also on every ActionContext, so an action rarely needs to reach for this one.

Pointer

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

The world guidance shared by every step: beam, floating icon and outline.

Camera

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

The cinematic camera shared by every step.

StepChanged

events
Tutorial.StepChanged: Signal<number,number?>

Fires on entering a step, with the new order and the one left behind. The second argument is nil on the first step.

tutorial.StepChanged:Connect(function(step, previousStep)
	print("now on", step, "coming from", previousStep)
end)

Completed

events
Tutorial.Completed: Signal<()>

Fires once, when the tutorial walks past its last step or Tutorial:Complete is called.

Functions

build

constructor
Tutorial.build(
steps{Step},--

The steps, in any order. They are sorted by their order number

configTutorialConfig?--

Styling and replication

) → Tutorial

Builds a tutorial from a list of steps.

Steps may be listed in any order, because the kit sorts them by their order number. Order numbers do not have to be contiguous, which lets you leave gaps for steps you expect to insert later:

const tutorial = Tutorial.build({
	Step.new(10, { Dialog.new({ Lines = { "Welcome!" } }) }),
	Step.new(20, { FocusUI.to(shopButton, { Advance = true }) }),
})

Nothing runs until you call Tutorial:Start.

CAUTION

With Replicate = true this yields until the kit's RemoteEvent has replicated, so call it from somewhere that can yield.

Errors

TypeDescription
"TutorialKit is client only"Called from the server
"TutorialKit.build needs at least one step"The list was empty
"TutorialKit step order N is duplicated"Two steps share an order number

Start

lifecycle
Tutorial:Start(
stepnumber?--

Order number to open on. Defaults to the first step

) → ()

Enters the first step and runs its actions.

Passing a step is how you resume: read the saved order from your data store and open there instead of replaying the whole flow.

const saved = DataService:Get(player, "TutorialStep")
tutorial:Start(saved)

GoTo

Tutorial:GoTo(
stepnumber--

Order number of the step to enter

) → boolean--

false when no step carries that order

Jumps to a step, forwards or backwards.

Idempotent: going to the step already open does nothing and returns true, so mirroring the same server message twice is harmless. An unknown order warns and returns false rather than erroring, because a step removed from a newer build should not break an old client.

Advance

Tutorial:Advance() → boolean--

false when the tutorial is not running

Moves to the next step, or completes the tutorial when called on the last one.

Most steps never call this directly. Advance = true on an action is the shorthand, and ActionContext:Advance is what an action calls when it decides for itself.

Complete

lifecycle
Tutorial:Complete() → ()

Ends the flow early and fires Tutorial.Completed.

Use it for a skip button. The tutorial stays usable afterwards, so Start can run it again.

GetStep

Tutorial:GetStep() → number?--

Order of the open step, or nil when not running

IsActive

Tutorial:IsActive() → boolean--

Whether a step is currently open

Destroy

lifecycle
Tutorial:Destroy() → ()

Tears down everything: the open step, the overlay, the pointer, the camera and the signals.

Call it when the player leaves the tutorial for good. Every method turns into a no-op afterwards, so a late callback cannot resurrect the flow.

Show raw api
{
    "functions": [
        {
            "name": "build",
            "desc": "Builds a tutorial from a list of steps.\n\nSteps may be listed in any order, because the kit sorts them by their order number. Order\nnumbers do not have to be contiguous, which lets you leave gaps for steps you expect to insert\nlater:\n\n```lua\nconst tutorial = Tutorial.build({\n\tStep.new(10, { Dialog.new({ Lines = { \"Welcome!\" } }) }),\n\tStep.new(20, { FocusUI.to(shopButton, { Advance = true }) }),\n})\n```\n\nNothing runs until you call [Tutorial:Start].\n\n:::caution\nWith `Replicate = true` this yields until the kit's RemoteEvent has replicated, so call it from\nsomewhere that can yield.\n:::",
            "params": [
                {
                    "name": "steps",
                    "desc": "The steps, in any order. They are sorted by their order number",
                    "lua_type": "{Step}"
                },
                {
                    "name": "config",
                    "desc": "Styling and replication",
                    "lua_type": "TutorialConfig?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Tutorial"
                }
            ],
            "function_type": "static",
            "tags": [
                "constructor"
            ],
            "errors": [
                {
                    "lua_type": "\"TutorialKit is client only\"",
                    "desc": "Called from the server"
                },
                {
                    "lua_type": "\"TutorialKit.build needs at least one step\"",
                    "desc": "The list was empty"
                },
                {
                    "lua_type": "\"TutorialKit step order N is duplicated\"",
                    "desc": "Two steps share an order number"
                }
            ],
            "source": {
                "line": 164,
                "path": "src/Core/Tutorial.luau"
            }
        },
        {
            "name": "Start",
            "desc": "Enters the first step and runs its actions.\n\nPassing a step is how you resume: read the saved order from your data store and open there\ninstead of replaying the whole flow.\n\n```lua\nconst saved = DataService:Get(player, \"TutorialStep\")\ntutorial:Start(saved)\n```\n\t",
            "params": [
                {
                    "name": "step",
                    "desc": "Order number to open on. Defaults to the first step",
                    "lua_type": "number?"
                }
            ],
            "returns": [],
            "function_type": "method",
            "tags": [
                "lifecycle"
            ],
            "source": {
                "line": 325,
                "path": "src/Core/Tutorial.luau"
            }
        },
        {
            "name": "GoTo",
            "desc": "Jumps to a step, forwards or backwards.\n\nIdempotent: going to the step already open does nothing and returns true, so mirroring the\nsame server message twice is harmless. An unknown order warns and returns false rather than\nerroring, because a step removed from a newer build should not break an old client.\n\t",
            "params": [
                {
                    "name": "step",
                    "desc": "Order number of the step to enter",
                    "lua_type": "number"
                }
            ],
            "returns": [
                {
                    "desc": "false when no step carries that order",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 337,
                "path": "src/Core/Tutorial.luau"
            }
        },
        {
            "name": "Advance",
            "desc": "Moves to the next step, or completes the tutorial when called on the last one.\n\nMost steps never call this directly. `Advance = true` on an action is the shorthand, and\n[ActionContext:Advance] is what an action calls when it decides for itself.\n\t",
            "params": [],
            "returns": [
                {
                    "desc": "false when the tutorial is not running",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 347,
                "path": "src/Core/Tutorial.luau"
            }
        },
        {
            "name": "Complete",
            "desc": "Ends the flow early and fires [Tutorial.Completed].\n\nUse it for a skip button. The tutorial stays usable afterwards, so `Start` can run it again.\n\t",
            "params": [],
            "returns": [],
            "function_type": "method",
            "tags": [
                "lifecycle"
            ],
            "source": {
                "line": 356,
                "path": "src/Core/Tutorial.luau"
            }
        },
        {
            "name": "GetStep",
            "desc": "\t",
            "params": [],
            "returns": [
                {
                    "desc": "Order of the open step, or nil when not running",
                    "lua_type": "number?"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 361,
                "path": "src/Core/Tutorial.luau"
            }
        },
        {
            "name": "IsActive",
            "desc": "\t",
            "params": [],
            "returns": [
                {
                    "desc": "Whether a step is currently open",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 366,
                "path": "src/Core/Tutorial.luau"
            }
        },
        {
            "name": "Destroy",
            "desc": "Tears down everything: the open step, the overlay, the pointer, the camera and the signals.\n\nCall it when the player leaves the tutorial for good. Every method turns into a no-op\nafterwards, so a late callback cannot resurrect the flow.\n\t",
            "params": [],
            "returns": [],
            "function_type": "method",
            "tags": [
                "lifecycle"
            ],
            "source": {
                "line": 376,
                "path": "src/Core/Tutorial.luau"
            }
        }
    ],
    "properties": [
        {
            "name": "Spotlight",
            "desc": "The dim overlay shared by every step. Also on every [ActionContext], so an action rarely needs\nto reach for this one.",
            "lua_type": "Spotlight",
            "readonly": true,
            "source": {
                "line": 70,
                "path": "src/Core/Tutorial.luau"
            }
        },
        {
            "name": "Pointer",
            "desc": "The world guidance shared by every step: beam, floating icon and outline.",
            "lua_type": "WorldPointer",
            "readonly": true,
            "source": {
                "line": 78,
                "path": "src/Core/Tutorial.luau"
            }
        },
        {
            "name": "Camera",
            "desc": "The cinematic camera shared by every step.",
            "lua_type": "CameraTour",
            "readonly": true,
            "source": {
                "line": 86,
                "path": "src/Core/Tutorial.luau"
            }
        },
        {
            "name": "StepChanged",
            "desc": "Fires on entering a step, with the new order and the one left behind. The second argument is\nnil on the first step.\n\n```lua\ntutorial.StepChanged:Connect(function(step, previousStep)\n\tprint(\"now on\", step, \"coming from\", previousStep)\nend)\n```",
            "lua_type": "Signal<number, number?>",
            "tags": [
                "events"
            ],
            "source": {
                "line": 101,
                "path": "src/Core/Tutorial.luau"
            }
        },
        {
            "name": "Completed",
            "desc": "Fires once, when the tutorial walks past its last step or [Tutorial:Complete] is called.",
            "lua_type": "Signal<()>",
            "tags": [
                "events"
            ],
            "source": {
                "line": 109,
                "path": "src/Core/Tutorial.luau"
            }
        }
    ],
    "types": [
        {
            "name": "TutorialConfig",
            "desc": "The optional second argument to [Tutorial.build].\n\n`Replicate = true` reports every step and the completion to the server, and follows the steps\nthe server sends back. It requires `require(TutorialKit.Server):Start()` on the server, and it\nmakes `build` yield until the bridge has replicated.",
            "fields": [
                {
                    "name": "Spotlight",
                    "lua_type": "SpotlightConfig?",
                    "desc": "Styling for the dim overlay and the highlight"
                },
                {
                    "name": "Pointer",
                    "lua_type": "WorldPointerConfig?",
                    "desc": "Styling for the beam, the floating icon and the outline"
                },
                {
                    "name": "Camera",
                    "lua_type": "CameraTourConfig?",
                    "desc": "Framing defaults for cinematics"
                },
                {
                    "name": "Replicate",
                    "lua_type": "boolean?",
                    "desc": "Mirror progress with the server. Defaults to false"
                }
            ],
            "source": {
                "line": 55,
                "path": "src/Core/Tutorial.luau"
            }
        }
    ],
    "name": "Tutorial",
    "desc": "The runtime that walks the steps.\n\nIt owns the presentation, not the truth. When a step ends the runtime destroys the step\nJanitor and clears the spotlight, the pointer and the camera, so every step starts on a\nclean screen and no step can leak visuals into the next one.\n\n```lua\nconst tutorial = Tutorial.build({\n\tStep.new(1, { FocusUI.to(shopButton, { Advance = true }) }),\n\tStep.new(2, { PointWorld.to(fountainPart) }),\n})\n\ntutorial:Start()\n```\n\nIf tutorial progress lives on your server, do not call `Advance` from the client. Let the\nserver decide and mirror it with `GoTo`, or set `Replicate = true` and let the kit do it.",
    "realm": [
        "Client"
    ],
    "source": {
        "line": 24,
        "path": "src/Core/Tutorial.luau"
    }
}