API reference
The Unity SDK’s entire public surface is one MonoBehaviour: PhoneControllerManager. This page lists every event, method, and property on it, plus the supporting types you’ll touch.
For task-oriented walkthroughs, see Quickstart, FlowAsset, and the per-widget pages.
PhoneControllerManager
Attach to a GameObject. Reference it from your game code via [SerializeField] or FindObjectOfType<PhoneControllerManager>().
Events
All events fire on the Unity main thread. Subscribe in Awake or Start; unsubscribe in OnDestroy to avoid leaks across scene loads.
| Event | Signature | Fires when |
|---|---|---|
OnPlayerJoin | Action<Player> | A controller joined the session (initial join or resume). |
OnPlayerLeave | Action<Player, LeaveReason> | A controller left. See LeaveReason. |
OnPlayerUpdate | Action<Player> | A connected player’s display name or color changed mid-session. |
OnInput | Action<Player, InputEvent> | A widget emitted an event. Catch-all; prefer OnWidgetInput with flows. |
OnPlayerScreenChange | Action<Player, string from, string to> | A player transitioned screens within a flow. |
OnPlayerScreenAcknowledged | Action<Player, string screen, long revision> | Phone confirmed a screen.show was applied locally. |
OnStatusChange | Action<ConnectionStatus> | Connection status changed. See ConnectionStatus. |
OnSessionChange | Action<string code, string joinUrl> | Fires on initial connect and on every reconnect (same code or a fresh one). |
OnError | Action<string> | Relay reported a system.error. The string is the human-readable message. |
OnResendFailed | Action<ResendFailure> | Fire-and-forget RequestResend couldn’t be honored. |
Connection methods
| Method | Notes |
|---|---|
Task ConnectAsync() | Called automatically if autoConnect is ticked on the inspector. |
Task DisconnectAsync() | Cancels every pending PromptAsync / RequestAsync first, then closes the socket. |
void ClearCachedSession() | Forget the persisted (code, host_id). Next connect allocates a fresh code. |
Sending events to phones
| Method | Notes |
|---|---|
void SendToPlayer(string playerId, string eventType, object payload = null) | Fire-and-forget. Errors via OnError. |
void BroadcastToAllPlayers(string eventType, object payload = null) | Fire-and-forget fan-out. |
Task<BroadcastResult> SendToPlayerAsync(...) | ACK-tracked. Result reports Delivered / Missed / TimedOut. |
Task<BroadcastResult> BroadcastToAllPlayersAsync(...) | ACK-tracked fan-out. |
Flow control (multi-screen sessions)
These require a FlowAsset registered on the manager. See FlowAsset.
| Method | Notes |
|---|---|
void UseFlow(Flow flow) | Register a flow at runtime (alternative to the Flow Asset inspector slot). |
void ShowScreen(string playerId, string screenName, object data = null) | Move one player to a screen. |
void ShowScreen(IEnumerable<string> playerIds, ...) | Move several players. |
void ShowScreenToAll(string screenName, object data = null) | Move every connected player. |
void UpdateScreenData(string playerId, string screenName, object patch) | Shallow-merge new data into a player’s current screen. |
void UpdateScreenDataForAll(string screenName, object patch) | Same, fan-out. |
Action OnWidgetInput(string screenName, string widgetId, Action<Player, InputEvent> handler) | Scoped subscription. Returns the unsubscribe delegate. |
Request/response
| Method | Notes |
|---|---|
Task<InputEvent> PromptAsync(...) | One player, one screen, one widget, one response. Flow only. Typed: PromptAsync<T>. See PromptAsync. |
Task<InputEvent> RequestAsync(...) | Same shape, no flow required. Modal overlay. Typed: RequestAsync<T>. See RequestAsync. |
void RequestResend(...) | Fire-and-forget lossy-stream replay (drawing canvas). |
Task<ResendResult> RequestResendAsync(...) | Awaitable resend with positive ACK. See RequestResendAsync. |
Properties
| Property | Type | Notes |
|---|---|---|
SessionCode | string | Current 4-character code. Empty until the first session arrives. |
JoinUrl | string | Full URL for QR-display. |
Status | ConnectionStatus | Current connection state. |
Players | IReadOnlyList<Player> | Currently connected controllers. |
Template | ControllerTemplate | The bound template asset (single-screen sessions). |
Capabilities | TransportCapabilities | Static metadata about the active transport. |
IsLanMode | bool | Convenience for Capabilities.RelayKind == RelayMode.Lan. |
RelayCapabilities | RelayCapabilities | What the relay actually advertised in session.created. Null until first session arrives. |
Supporting types
Player
Stable per-session identity for a connected phone.
| Field | Type | Notes |
|---|---|---|
Id | string | Stable identifier (p_1, p_2, …). Survives reconnect. |
DisplayName | string | Player-chosen name. |
Color | string | 7-digit hex assigned by the relay’s join-counter palette. |
JoinedAt | DateTimeOffset | When the relay first accepted this player. |
InputEvent
Per-widget event with typed accessors.
| Field | Type | Notes |
|---|---|---|
WidgetId | string | Matches the widget’s id. |
EventType | string | Per-widget event name (see widget pages for the catalog). |
RawJson | JObject | Full payload as Newtonsoft. Use as escape hatch. |
Typed accessors:
| Accessor | Returns |
|---|---|
GetVector2() | (x, y) for joystick events. |
GetString(string key) | A string field from the payload. Returns null if absent or wrong type. |
GetInt(string key) | An int field. |
GetBool(string key) | A bool field. |
GetStringArray(string key) | A string array (e.g. choice_ids from multi-select). |
As<T>() | Deserialize the raw payload to T. Throws InvalidOperationException on null. |
Per-widget conventions are summarized on each widget’s reference page (e.g. joystick → GetVector2(), text_input → GetString("text")).
LeaveReason
public enum LeaveReason{ Timeout, // Phone disconnected; reconnect grace window expired. UserLeft, // Phone sent session.leave with reason=user_left. HostClosed, // Host kicked / closed the session. Kicked, // Server-side enforcement (rate limit, abuse).}ConnectionStatus
public enum ConnectionStatus{ Disconnected, Connecting, Connected, Reconnecting, // Transient — exponential backoff in progress. Failed, // Reconnect-attempt budget exhausted (cloud transport only).}TransportCapabilities
Static metadata about the active transport, populated before connect from relayMode.
| Field | Type | Notes |
|---|---|---|
RelayKind | RelayMode | Cloud or Lan. |
SessionSurvivesHostRestart | bool | True on Cloud (Upstash), false on LAN (in-process state). |
EnforcesRateLimits | bool | True on Cloud, false on LAN. |
RelayCapabilities
What the running relay advertised in session.created. Use this to branch on optional features.
| Field | Type | Notes |
|---|---|---|
StrictPayloads | bool | If true, register payload validators (see Validation). |
Older relays that don’t include the field leave RelayCapabilities null.
BroadcastResult
Returned by BroadcastToAllPlayersAsync and SendToPlayerAsync.
| Field | Type | Notes |
|---|---|---|
Delivered | IReadOnlyList<string> | Player IDs that ACKed within the timeout. |
Missed | IReadOnlyList<string> | Player IDs the relay never delivered to. |
TimedOut | IReadOnlyList<string> | Player IDs that didn’t ACK in time. |
ResendStatus, ResendResult, ResendFailure
public enum ResendStatus { Accepted, Rejected, TimedOut }
public record ResendResult{ public ResendStatus Status; public int ReplayedCount; // Accepted: how many events the phone is replaying. public string? RejectReason; // Rejected: BufferExpired | WidgetNotReady | …}
public record ResendFailure{ public string PlayerId; public string WidgetId; public int StrokeId; public int FromSeq; public string Reason;}See RequestResendAsync.
Validation
Pre-send payload validators run in editor builds when the relay reports RelayCapabilities.StrictPayloads. A failing validator throws InvalidPayloadException synchronously, instead of the relay silently dropping the message during a playtest.
PayloadSchemas.RegisterControlEvent("vibrate", payload => payload["duration_ms"]?.Type == JTokenType.Integer ? null : "duration_ms is required and must be an int");Production builds compile out the check; the relay remains authoritative.
Threading
Every callback fires on the Unity main thread. MainThreadDispatcher (internal) marshals events from the WebSocket reader thread. You can mutate Transform, instantiate prefabs, etc. directly from OnInput without dispatch.
The async methods (PromptAsync, RequestAsync, *Async variants) integrate with Unity’s async/await — they continue on the main thread by default. If you await from a background thread, capture the relevant context manually.
What’s not in the public API
By design, none of these are public:
ITransportand its implementations (CloudRelayTransport,LocalLanTransport). Implementation detail; the seam exists for engine-agnostic refactoring.MainThreadDispatcher,ReconnectScheduler. Internal helpers.- The
Json.csNewtonsoft wrapper. Use Newtonsoft directly if you need it. - Generated protocol types under
Runtime/Protocol/. Used by the SDK; not for game code. UseWidgetDefinitionsto build widgets, not the raw types.