How-To: First-Run Setup Wizard
Goal: walk a player through a short setup sequence once, on their first launch - language, gamma calibration, whatever else the game needs decided up front - and never show it again.
1. Create the step widgets
One Widget Blueprint per page, parented to UCrimsonFirstRunStep (itself a UCommonActivatableWidget). A step declares no properties of its own - it is a page with four hooks.
| Hook | Kind | Purpose |
|---|---|---|
K2_OnStepActivated | BlueprintImplementableEvent | The page became visible. Start animations or auto-calibration here. |
K2_OnStepDeactivated | BlueprintImplementableEvent | Leaving the page. Commit anything pending here. |
CanAdvance | BlueprintNativeEvent, returns bool | Return false to block Next until the player has interacted. Defaults to true. |
GetStepTitle | BlueprintNativeEvent, returns FText | The page name for the progress indicator. Defaults to empty. |
GetStepTitle.2. Create the wizard widget
A Widget Blueprint parented to UCrimsonFirstRunWizard. In Class Defaults, fill Step Classes with your step classes in order - that array is the whole sequence, and adding a fourth page is a fourth entry and no graph edit.
Override K2_OnStepChanged (NewStepIndex, NewStep) and put NewStep into your layout container - removing the previous one first, or using a Common Activatable Widget Switcher, which deactivates the outgoing page and activates the incoming one for you.
Override K2_OnWizardComplete and K2_OnWizardSkipped to leave the screen.
Wire the navigation buttons: Button_Next OnClicked -> Go To Next Step, Button_Back OnClicked -> Go To Previous Step, Button_Skip OnClicked -> Skip Setup. Use Is At First Step to hide Back, and Is At Last Step to relabel Next as "Finish". Get Total Steps and Get Current Step Index drive a progress indicator.
| Native hook | On | Default |
|---|---|---|
NativeOnStepChanged(NewStepIndex, NewStep) | UCrimsonFirstRunWizard | fires K2_OnStepChanged |
NativeOnWizardComplete() | UCrimsonFirstRunWizard | fires K2_OnWizardComplete |
NativeOnWizardSkipped() | UCrimsonFirstRunWizard | fires K2_OnWizardSkipped |
NativeOnStepActivated() | UCrimsonFirstRunStep | fires K2_OnStepActivated |
NativeOnStepDeactivated() | UCrimsonFirstRunStep | fires K2_OnStepDeactivated |
K2_ hooks are BlueprintImplementableEvent, so a C++ subclass cannot override them - the same reason UCrimsonSettingScreen has NativeOnRestartRequired. CanAdvance and GetStepTitle are already BlueprintNativeEvent, so a C++ step overrides their _Implementation directly.3. Show it on first launch
Completion lives in the CrimsonSettings.FirstRun save slot (user index 0), a UCrimsonFirstRunState holding one bSetupCompleted flag. HasCompletedSetup() is a BlueprintCallable static that reads it.
In your main menu's Event On Activated (or Event BeginPlay for a test): Has Completed Setup -> Branch (False) -> Push Widget To Layer Stack (Layer = your Menu layer tag, Class = your wizard Blueprint).
Guard it with a "have I offered this already" boolean if the menu can be activated more than once.
bAutoSkipIfCompleted on (the default) the wizard deactivates itself on activation if setup is done, so it is safe to always push it. Asking HasCompletedSetup() before pushing is still the better call in a menu: a widget that is pushed and then deactivates on the same frame moves the layer stack and takes focus off whatever the player was looking at.FinishSetup() writes the save slot; SkipSetup() deliberately does not, so a skipped wizard is offered again next launch. Deleting Saved/SaveGames/CrimsonSettings.FirstRun.sav re-arms it, which is how you test the flow repeatedly.4. Make Skip a deliberate act (optional)
SkipSetup() has no guard of its own - the plugin does not decide how a player is asked to confirm leaving setup. The pattern worth copying:
- Give the Skip button its own hold Input Action rather than reusing the shared UI Back action. CommonUI reads a hold duration from the action's
UInputTriggerHoldand nowhere else, so putting that trigger on the shared Back action would turn every Back press in the game into a hold. - Map that action to the same keys Back answers on. Holds are processed before normal input and consume the key, so while the wizard is the active root a held press reaches Skip and a tapped one reaches nothing. Releasing early cancels silently.
- On completion of the hold, open a confirmation dialog rather than skipping outright, and call
SkipSetup()only on a confirmed result - deferred a tick, so leaving the wizard does not race the modal's teardown.
CommonUI.bEnhancedInputDynamicallyResolveHoldKeysForInputAction=1 to Config/DefaultEngine.ini. With it off - the engine default - the action router registers every Enhanced Input action as press-only no matter what triggers the asset carries, and the hold silently never happens.Do not also set
bRequiresHold on the button: that flag rejects any click while hold progress is below 100%, which kills the mouse click too. A mouse click on Skip should go straight to the dialog, which is what you want from a pointer.UCrimsonCoreFirstRunWizard with W_FirstRunWizard (/CrimsonCore/UserInterface/Settings/Wizard/) is a complete worked example of everything on this page: three steps in a UCommonActivatableWidgetSwitcher, progress pips built from GetTotalSteps(), bound CommonUI input actions on all three navigation buttons, and the hold-to-skip flow above. Its Blueprint supplies layout and nothing else.See also
- How-To: Brightness & Safe Zone Calibration - the screen a gamma step usually embeds.
- API Reference -> Presentation - the full wizard and step surface.