Guidance tools
Three tools do the actual showing: the spotlight, the world pointer and the camera. Every tutorial gets one of each, shared by all of its steps, and every step is guaranteed to start with all three cleared.
You normally drive them through actions. Reach for the objects directly only when you want guidance outside a tutorial.
| Action | Tool | Available on the context as |
|---|---|---|
FocusUI.to | Spotlight | context.Spotlight |
PointWorld.to | WorldPointer | context.Pointer |
Cinematic.focus, Cinematic.tour | CameraTour | context.Camera |
Spotlight
Dims the screen except one GuiObject.
FocusUI.to(shopButton, { Padding = Vector2.new(8, 8), Advance = true })
It follows the target
The overlay tracks the target every frame, so it works on a button that animates in, an entry in a scrolling list, or a panel that resizes. Focusing a second target slides the hole across rather than cutting.
Finding the click
Three fallbacks, in order:
- The target itself, if it is a
GuiButton. - The first descendant
GuiButton. - Raw
InputBeganon the frame.
That third one is why FocusUI.to works on a plain Frame, and it covers mouse and touch.
Putting your own text on the overlay
GetOverlay() returns the root frame, which is where anything that must render above the dim goes:
Custom.new(function(context)
const ui = TutorialKit.UI.scoped(context.Janitor)
ui("TextLabel", {
Text = "Tap here to open the shop",
Size = UDim2.fromOffset(320, 40),
Position = UDim2.fromScale(0.5, 0.8),
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundTransparency = 1,
Parent = context.Spotlight:GetOverlay(),
})
end)
Why the dim is a CanvasGroup
Worth knowing, because it is the bug everyone writes on their first attempt.
The obvious build for "dim everything except this rectangle" is four semi-transparent frames around a hole. Their alpha stacks wherever they touch, and that overlap renders as a visible seam along the edge of the hole. Adding padding does not help, because the problem is overlap, not spacing.
A CanvasGroup flattens its descendants into a single image and applies GroupTransparency to the result, so the panels can be fully opaque and overlap freely without ever double-darkening a pixel.
The highlight stroke and the pointer sit outside that group on purpose: a UIStroke parented to a CanvasGroup ignores GroupTransparency, so it would not fade with the rest.
WorldPointer
Beam, floating icon, outline.
PointWorld.to(questGiver.HumanoidRootPart, { Outline = questGiver })
Turning pieces off
-- Outline only.
PointWorld.to(chest.PrimaryPart, { Outline = chest, Beam = false, Icon = false })
The icon needs configuring
There is no built-in image, so the floating icon only appears if you set one on the tutorial:
Tutorial.build(steps, {
Pointer = { Icon = "rbxassetid://1234567", IconSize = Vector2.new(96, 96) },
})
Your own beam art
BeamTemplate is the hook for anything the built-in beam cannot express. The kit clones it and only sets the attachments and the name, so textures, curves and particles survive:
Tutorial.build(steps, {
Pointer = { BeamTemplate = ReplicatedStorage.Assets.TutorialBeam },
})
The beam needs a character to start from. If the player is dead or still loading when the step opens, the beam is skipped rather than erroring, and it does not retry. For a step that can open during a respawn, point from a fixed part with From instead.
CameraTour
Flies the camera and puts it back.
Cinematic.tour({
{ Target = shopModel.PrimaryPart, Hold = 2 },
{ Target = arenaGate, Hold = 3, Distance = 60 },
{ Target = questBoard, OnArrive = function() SoundService:PlayLocalSound(chime) end },
}, {
Distance = 35,
FocusDuration = 1.8,
Advance = true,
})
Each stop overrides only what makes it different; everything else falls back to the tour config.
Restoration is the point
Camera state is captured when the tour starts and restored when it ends, whether it finished or was cut short. Completed fires in both cases, which is what makes Advance = true safe to attach: a player who leaves mid-flight still moves on rather than waiting on a callback that will never come.
If a stop's target is destroyed mid-tour, the tour stops there and the camera is restored, instead of the player watching nothing.
Lining things up with the flight
OnArrive runs once the camera settles on a stop, which is where a caption or a sound goes.
Using them without a tutorial
All three have a create function, and if you call it you own the object:
const spotlight = TutorialKit.Spotlight.create({ DimTransparency = 0.5 })
spotlight:Focus(shopButton)
task.wait(3)
spotlight:Clear()
spotlight:Destroy()
Inside a tutorial you never do this. Tutorial.build creates all three and Tutorial:Destroy disposes of them.