Skip to main content

Startup

Startup runs on the Login scene. It checks that the server accepts this game build, signs the player back in from an earlier play when it can, and loads the player's game data. Your Login scene shows one screen per step and opens the game when startup reports Ready.

You useIt gives you
IProjectVStartupPresenterStart startup and observe every step
ProjectVStartupSnapshotWhat to draw: the current step and why it stopped
ProjectVStartupStatusThe steps, one screen or message each
ProjectVStartupErrorWhy startup stopped: Category, Code, RequestId, LocalizationKey
ProjectVStartupCommandResultThe outcome of StartAsync

Startup has no screen contract. The Login scene uses the presenter directly, together with the Authentication presenter for the sign-in buttons.

Quick start​

using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using SilverTiger.ProjectV.Unity.Presentation.Startup;
using UnityEngine;
using VContainer;

public sealed class LoginStartupView : MonoBehaviour
{
private IProjectVStartupPresenter startup = null!;
private IDisposable? subscription;
private CancellationTokenSource? lifetime;

[Inject]
public void Construct(IProjectVStartupPresenter presenter) => startup = presenter;

private async void OnEnable()
{
lifetime = new CancellationTokenSource();
var token = lifetime.Token;
var startupSub = await startup.SubscribeAsync(OnStartupChanged);
if (token.IsCancellationRequested)
{
// The page closed while subscribing.
startupSub.Dispose();
return;
}
subscription = startupSub;
// Call this once your own content (Addressables and so on) has loaded.
await startup.StartAsync(contentReady: true, token);
}

private void OnDisable()
{
subscription?.Dispose();
subscription = null;
lifetime?.Cancel();
lifetime?.Dispose();
lifetime = null;
}

// May be called off the Unity main thread.
private void OnStartupChanged(ProjectVStartupSnapshot snapshot) => RenderAsync(snapshot).Forget();

private async UniTaskVoid RenderAsync(ProjectVStartupSnapshot snapshot)
{
await UniTask.SwitchToMainThread();
if (this == null)
{
return;
}

if (snapshot.IsReady)
{
OpenLobby();
return;
}
if (snapshot.RequiresLogin)
{
ShowSignInOptions();
return;
}
if (snapshot.IsBlocking)
{
ShowBlocking(snapshot.Status, snapshot.Error?.LocalizationKey);
return;
}
ShowLoading(snapshot.Status);
}

// Retry button on the blocking screen.
public async void OnRetryClicked()
{
if (lifetime != null)
{
await startup.StartAsync(contentReady: true, lifetime.Token);
}
}

private void OpenLobby() { }
private void ShowSignInOptions() { }
private void ShowBlocking(ProjectVStartupStatus status, string? localizationKey) { }
private void ShowLoading(ProjectVStartupStatus status) { }
}

That is the whole startup flow: subscribe, start, draw each step. After the player signs in, call StartAsync(true, token) again and the listener walks through Hydrating to Ready.

Main thread

SubscribeAsync listeners may run off the Unity main thread. Switch to the main thread before touching Unity objects, as RenderAsync does with UniTask.SwitchToMainThread().

What to draw​

The listener receives a ProjectVStartupSnapshot:

MemberTypeUse it for
StatusProjectVStartupStatusThe current step; pick the screen from the table below
IsReadyboolStatus is Ready: open the game
RequiresLoginboolStatus is AwaitingLogin: show the sign-in options
IsBlockingboolStartup stopped and cannot go on by itself. Show the matching message
ErrorProjectVStartupError?Why startup stopped, or null. LocalizationKey picks the message, RequestId goes in a support report

startup.Current holds the same snapshot; it starts as WaitingForContent.

For each ProjectVStartupStatus:

StatusWhat to show
WaitingForContentYour loading screen; startup has not begun
Admitting"Connecting…"
RestoringSessionThe loading screen; the player is being signed back in
AwaitingLoginThe sign-in options (Guest, Google, Apple, password)
HydratingThe loading screen; the player's game data is loading
ReadyNothing; open the game
MaintenanceA maintenance message; the player tries again later
UpdateRequiredAsk the player to update from the store
ContractMismatchAsk the player to update the game
OfflineA no-connection message with a Retry button
FailedAn error message with a Retry button

The last five are the blocking statuses (IsBlocking is true).

Start startup​

Task<ProjectVStartupCommandResult> StartAsync(bool contentReady, CancellationToken cancellationToken)
ParameterTypeValue
contentReadybooltrue once the game's own content has loaded. false starts nothing when startup is not already running
cancellationTokenCancellationTokenStops your wait for the result. Startup itself keeps running and keeps publishing to the listener

Returns a ProjectVStartupCommandResult:

IsSuccessSnapshot.StatusWhat happened
trueReadyThe player is signed in and the game data is loaded. Open the game
trueAwaitingLoginNo saved sign-in. Show the sign-in options
trueWaitingForContentYou passed contentReady: false and startup was not running; nothing started
falsea blocking statusStartup stopped. Show the message for that status (see below)
  • Calling StartAsync while startup is running joins that run and returns its result; it does not start a second one, even with contentReady: false.
  • Call StartAsync(true, token) again to retry after a blocking status, and after a successful sign-in.
  • When your token fires first, the result is Failed with the Cancelled category. That result is only yours: the listener and Current keep following the real run.

When it stops — show the message for the status, using Error.LocalizationKey (projectv.startup.<category>) when Error is set:

SituationStatusWhat the player sees
The server is under maintenanceMaintenanceA maintenance message. Retry later
This game version is too oldUpdateRequiredAsk the player to update, with a store link
This build and the server do not matchContractMismatchAsk the player to update. In development it means the Client build and the backend environment are out of step; it is not a Client bug
The server cannot be reachedOfflineA no-connection message with Retry (Error.Category is Network or Unavailable)
Anything elseFailedAn error message with Retry. Codes include PROJECTV_STARTUP_BACKEND_FAILED and PROJECTV_STARTUP_UNEXPECTED_FAILURE
Your token fired before startup finishedFailed (result only)Nothing; the page is closing
A saved sign-in was no longer acceptedAwaitingLogin with Error setThe sign-in options. IsSuccess is still true

Never open the game past a blocking status.

Rules​

  • Pass contentReady: true only when the game's own content is loaded.
  • Navigate on IsReady, never on a successful sign-in alone.
  • Startup does not queue commands like the other presenters: every call joins the one run.
  • Error.Code is for branching and support, not text for the player. Show the message for LocalizationKey, and RequestId in a support panel.

Common mistakes​

  • Treating a cancelled StartAsync as stopping startup. It only stops your wait; draw from the listener, not only from the result.
  • Calling StartAsync from every button. Call it once when the scene opens, again after sign-in, and from Retry.
  • Touching Unity objects in the listener without switching threads. Listeners may run off the main thread.
  • Opening the Lobby straight after a sign-in. Call StartAsync(true, token) and wait for Ready first, so the player's data is loaded.
  • Not disposing the subscription. Dispose it in OnDisable or OnDestroy.

UI binding helper​

startup.SubscribeUiAsync(ProjectVUiBindingId.LoginStartup, apply, token) works like SubscribeAsync and also marks in the SDK log when your apply returned (see UI subscriptions and ProjectVUiSubscriptions). It does not switch threads: apply runs on the thread that delivers the update, so switch to the main thread yourself.