App Lifecycle
Board apps run in a managed environment. The OS decides when your app is foregrounded, backgrounded, or torn down, and Board’s system overlays (the pause screen and the profile switcher) can suspend your input at any moment. This guide covers what happens at each lifecycle moment and how your game should react: starting up, losing and regaining the foreground, reacting to the system pause overlay, and quitting cleanly. The concepts are the same across the three SDKs; the way each engine surfaces them differs, so this guide shows the idiomatic setup for each.
New to the platform? Read Architecture for how apps, the OS, and the system overlays fit together.
What the SDK does and does not own
Board’s SDKs deliberately do not add a new app-lifecycle event system on top of the host engine. There is no onResume / onPause / onForeground callback in any of the three SDKs. Foreground and background transitions reach your game through the host engine’s native lifecycle hooks, and you use the SDK only for the things the OS genuinely owns: the pause overlay, the profile switcher, and a clean quit.
| Concern | Where it comes from |
|---|---|
| Startup / init | Engine entry point, plus a one-time SDK init where required (Godot) |
| Foreground / background | Host engine lifecycle hooks (not an SDK event) |
| Input cancellation on background | The touch stream itself: every active contact arrives with a Canceled phase |
| Pause overlay open / result | SDK pause channel (event, signal, or callback) |
| Quit | SDK quit call |
The single rule that ties these together: when your app leaves the foreground, every active contact is canceled. You receive a Canceled phase on each contact at the transition, regardless of SDK. Treat that as your signal to drop per-contact state. See Touch for the contact and phase model.
Startup
Bring the SDK up as early as your engine allows, and gate every SDK call behind the on-device check so the same code runs unmodified in the editor or a desktop build. The device-support flag has a different name in each SDK.
Unity initializes the SDK automatically before the first scene loads, so there is no init call to make: you only read BoardSupport.enabled to decide whether to drive Board features. Godot requires a one-time Board.initialize(app_id) before any session, save, avatar, or pause call, and gates on the Board.is_on_device property. Web has no init call either; it gates on Board.isOnDevice, which is true only inside a Board WebView.
using Board.Core;
using Board.Input;
using UnityEngine;
public class GameBootstrap : MonoBehaviour
{
void Awake()
{
// The SDK self-initializes before the first scene; no init call.
// Gate on BoardSupport.enabled (true on device and in the Editor).
if (!BoardSupport.enabled)
{
return;
}
// Configure OS-owned surfaces while the game is coming up.
BoardApplication.SetPauseScreenContext(applicationName: "My Game");
}
}
import { Board } from "@board.fun/web-sdk";
// No init call. Gate on Board.isOnDevice (true only inside a Board WebView).
if (Board.isOnDevice) {
// Register a pause context so the OS menu button has something to open.
Board.pause.setContext({ gameName: "My Game", offerSaveOption: true });
}
extends Node
const APP_ID := "00000000-0000-0000-0000-000000000000" # your app ID (a UUID)
func _ready() -> void:
if not Board.is_on_device:
return
# initialize() must run once before any session/save/avatar/pause call.
Board.initialize(APP_ID)
# Register a pause context so the OS menu button has something to open.
Board.pause.set_context({ "game_name": "My Game", "offer_save_option": true })
Off-device behavior differs by SDK. On Unity and Godot, SDK calls no-op or return defaults when not on device. On Web, most service calls throw when the
window.BoardSDKbridge is absent, so always branch onBoard.isOnDevicebefore calling them. (The one exception isBoard.input.getContacts(), which returns an empty array rather than throwing.)
Foreground and background
Your app is in the foreground when it is the active game on the device. It moves to the background when the system pause overlay or profile switcher is up, or when the OS is mid-transition to or from another app.
Pause game logic when you lose the foreground and resume when you return. The SDK does not deliver these transitions; you receive them through the host engine. The shared, cross-SDK signal that the OS has taken over input is the contact stream: at the moment you lose the foreground, every active contact is reported with a Canceled phase, so any code that already cleans up canceled contacts will release held Pieces and fingers correctly.
In Unity, use MonoBehaviour.OnApplicationPause and OnApplicationFocus. In Godot, handle the engine’s NOTIFICATION_APPLICATION_PAUSED / NOTIFICATION_APPLICATION_RESUMED notifications on any Node (Board does not route these through the SDK). On Web, the WebView is a browser context, so use the page Visibility API and the pagehide / pageshow events.
using UnityEngine;
public class LifecycleHandler : MonoBehaviour
{
void OnApplicationPause(bool paused)
{
if (paused)
{
PauseGameplay(); // lost the foreground
}
else
{
ResumeGameplay(); // back in the foreground
}
}
void OnApplicationFocus(bool hasFocus)
{
if (!hasFocus)
{
PauseGameplay();
}
}
}
// The WebView is a browser context — use standard page lifecycle events.
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
pauseGameplay(); // lost the foreground
} else {
resumeGameplay(); // back in the foreground
}
});
# Board does not wrap these — use the engine's own application notifications.
func _notification(what: int) -> void:
match what:
NOTIFICATION_APPLICATION_PAUSED:
pause_gameplay() # lost the foreground
NOTIFICATION_APPLICATION_RESUMED:
resume_gameplay() # back in the foreground
Whichever path you use, also handle the Canceled contacts you receive at the transition so no Piece or finger is left in a “held” state. The teardown is the same logic you already run when a contact ends.
// In your per-frame contact loop (see the Touch guide).
foreach (var contact in BoardInput.GetActiveContacts())
{
if (contact.phase == BoardContactPhase.Canceled)
{
ReleaseContactState(contact); // OS took over input; drop held state
}
}
import { Board, BoardContactPhase } from "@board.fun/web-sdk";
Board.input.subscribe((contacts) => {
for (const c of contacts) {
if (c.phase === BoardContactPhase.Canceled) {
releaseContactState(c); // OS took over input; drop held state
}
}
});
func _on_contacts(contacts: Array) -> void:
for c in contacts:
if c.phase_id == Board.input.PHASE_CANCELED:
release_contact_state(c) # OS took over input; drop held state
The pause overlay
Board’s pause overlay is its own lifecycle moment, and it is fully OS-owned. Do not draw your own pause UI. You register a pause context (the app name, whether to offer Save and Quit, custom buttons, audio sliders), and the OS renders the screen and shows or hides the system menu button across the activity lifecycle. Register the context early, during startup, so the button has something to open the first time the user taps it.
When the user makes a choice, the result reaches you through the SDK’s pause channel, and the channel shape is the headline difference between the SDKs:
- Unity delivers results through two C# events surfaced by an internal per-frame poller:
pauseScreenActionReceivedfor system actions (resume, exit saved, exit unsaved) andcustomPauseScreenButtonPressedfor custom buttons. - Godot emits a single signal,
pause_result_received, carrying aBoardPauseResultwhoseactionyou compare against theACTION_*constants. - Web uses a callback:
pause.onResult(cb), which returns an unsubscribe function. The result’sactionis a plain string.
using Board.Core;
using UnityEngine;
public class PauseHandler : MonoBehaviour
{
void OnEnable()
{
BoardApplication.pauseScreenActionReceived += OnPauseAction;
BoardApplication.customPauseScreenButtonPressed += OnCustomButton;
}
void OnDisable()
{
BoardApplication.pauseScreenActionReceived -= OnPauseAction;
BoardApplication.customPauseScreenButtonPressed -= OnCustomButton;
}
void OnPauseAction(BoardPauseAction action, BoardPauseAudioTrack[] audioTracks)
{
switch (action)
{
case BoardPauseAction.Resume:
ResumeGameplay();
break;
case BoardPauseAction.ExitGameSaved:
SaveThenExit();
break;
case BoardPauseAction.ExitGameUnsaved:
BoardApplication.Exit();
break;
}
}
void OnCustomButton(string customButtonId, BoardPauseAudioTrack[] audioTracks)
{
if (customButtonId == "restart")
{
RestartGame();
}
}
}
import { Board, type BoardPauseResult } from "@board.fun/web-sdk";
// onResult returns an unsubscribe function — keep it to detach later.
const unsubscribe = Board.pause.onResult((result: BoardPauseResult) => {
switch (result.action) {
case "resume":
resumeGameplay();
break;
case "save_and_quit":
saveThenQuit();
break;
case "quit":
Board.application.quit();
break;
case "custom_button":
if (result.customButtonId === "restart") {
restartGame();
}
break;
}
});
func _ready() -> void:
if not Board.is_on_device:
return
Board.pause.pause_result_received.connect(_on_pause_result)
func _on_pause_result(result: BoardPauseResult) -> void:
match result.action:
Board.pause.ACTION_RESUME:
resume_gameplay()
Board.pause.ACTION_SAVE_AND_QUIT:
save_then_quit()
Board.pause.ACTION_QUIT:
Board.application.quit()
Board.pause.ACTION_CUSTOM_BUTTON:
if result.custom_button_id == "restart":
restart_game()
The pause action values are not interchangeable across SDKs. Unity uses the
BoardPauseActionenum (Resume,ExitGameSaved,ExitGameUnsaved,CustomButton). Godot compares against theACTION_*constants (whose wire values differ from their names, so always compare against the constant, never a literal). Web compares against the plain strings"resume","save_and_quit","quit", and"custom_button". See Pause Menu for the full context schema, custom buttons, and audio tracks.
Quitting
Always quit through the SDK so the OS returns the user cleanly to the launcher. Do not call the engine’s raw quit on device.
Unity’s quit is BoardApplication.Exit(). Godot’s is Board.application.quit() (use it instead of get_tree().quit(), which would skip the OS notification). Web’s is Board.application.quit(). All three are fire-and-forget.
using Board.Core;
// Clean quit: removes the task and returns to the launcher.
BoardApplication.Exit();
import { Board } from "@board.fun/web-sdk";
// Close the web app and return to the launcher.
Board.application.quit();
# Clean quit: notifies BoardOS so the user lands back in the launcher.
# Use this on device, NOT get_tree().quit().
Board.application.quit()
Board is wall-powered and does not perform a graceful shutdown on power loss, so the OS may also tear your app down without warning. Treat a clean quit as the happy path, not a guarantee: persist state at meaningful checkpoints during play rather than relying on a quit hook to flush it. See Save Games for the save model.
The system menu button and the profile switcher
Two related surfaces sit just outside the lifecycle proper.
The system menu button (the affordance that opens the pause overlay) is shown and hidden by the OS automatically across the activity lifecycle. There is no SDK call to toggle it in any of the three SDKs. Your only responsibility is to register a pause context so the button has something to open: an empty context leaves the button inert.
The profile switcher is an OS overlay you can show or hide. After a switch, re-read the active profile rather than caching it; none of the SDKs raise a dedicated “profile changed” lifecycle event tied to the switcher itself. The call lives on BoardApplication in Unity, on Board.session in Godot, and on Board.application in Web (the Web Board.session variants exist but are deprecated).
using Board.Core;
BoardApplication.ShowProfileSwitcher();
// ...
BoardApplication.HideProfileSwitcher();
import { Board } from "@board.fun/web-sdk";
Board.application.showProfileSwitcher();
// ...
Board.application.hideProfileSwitcher();
Board.session.show_profile_switcher()
# ...
Board.session.hide_profile_switcher()
See Profile Switcher for the full flow and Player Management for re-reading the roster after a switch.
See Also
- Architecture — how apps, the OS, and the system overlays fit together
- Touch — the contact and phase model, including the
Canceledphase on background - Pause Menu — configuring and reacting to the system pause overlay
- Profile Switcher — showing the OS profile switcher
- Player Management — re-reading the roster after a profile switch
- Save Games — persisting state across sessions and at checkpoints
- Per-SDK API references: Unity, Godot, Web
</content> </invoke>