Khởi động (Startup)
Startup chạy trong scene Login. Nó kiểm tra server có chấp nhận bản build game
này không, đăng nhập lại cho người chơi từ lần chơi trước khi có thể, và tải
dữ liệu game của người chơi. Scene Login hiện một màn cho mỗi bước và mở game
khi startup báo Ready.
| Bạn dùng | Nó cho bạn |
|---|---|
IProjectVStartupPresenter | Bắt đầu startup và theo dõi từng bước |
ProjectVStartupSnapshot | Cần vẽ gì: bước hiện tại và lý do nó dừng |
ProjectVStartupStatus | Các bước, mỗi bước một màn hoặc một thông báo |
ProjectVStartupError | Lý do startup dừng: Category, Code, RequestId, LocalizationKey |
ProjectVStartupCommandResult | Kết quả của StartAsync |
Startup không có screen contract. Scene Login dùng trực tiếp presenter, cùng với presenter Authentication cho các nút đăng nhập.
Bắt đầu nhanh
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) { }
}
Đó là toàn bộ luồng startup: đăng ký nhận (subscribe), bắt đầu, vẽ (render)
từng bước. Sau khi người chơi đăng nhập, gọi lại StartAsync(true, token) và
listener sẽ đi qua Hydrating tới Ready.
Listener của SubscribeAsync có thể chạy ngoài main thread của Unity. Hãy
chuyển về main thread trước khi đụng vào object Unity, như RenderAsync làm
với UniTask.SwitchToMainThread().
Cần vẽ gì
Listener nhận một
ProjectVStartupSnapshot:
| Member | Kiểu | Dùng để |
|---|---|---|
Status | ProjectVStartupStatus | Bước hiện tại; chọn màn theo bảng bên dưới |
IsReady | bool | Status là Ready: mở game |
RequiresLogin | bool | Status là AwaitingLogin: hiện các lựa chọn đăng nhập |
IsBlocking | bool | Startup đã dừng và không tự đi tiếp được. Hiện thông báo tương ứng |
Error | ProjectVStartupError? | Lý do startup dừng, hoặc null. LocalizationKey chọn thông báo, RequestId đưa vào báo cáo hỗ trợ |
startup.Current giữ cùng snapshot đó; giá trị ban đầu là WaitingForContent.
Với mỗi ProjectVStartupStatus:
| Status | Hiện gì |
|---|---|
WaitingForContent | Màn đang tải của bạn; startup chưa bắt đầu |
Admitting | "Connecting…" (đang kết nối) |
RestoringSession | Màn đang tải; người chơi đang được đăng nhập lại |
AwaitingLogin | Các lựa chọn đăng nhập (Guest, Google, Apple, mật khẩu) |
Hydrating | Màn đang tải; dữ liệu game của người chơi đang tải |
Ready | Không gì cả; mở game |
Maintenance | Thông báo bảo trì; người chơi thử lại sau |
UpdateRequired | Yêu cầu người chơi cập nhật t ừ store |
ContractMismatch | Yêu cầu người chơi cập nhật game |
Offline | Thông báo mất kết nối kèm nút thử lại (Retry) |
Failed | Thông báo lỗi kèm nút Retry |
Năm status cuối là các status chặn (IsBlocking là true).
Bắt đầu startup
Task<ProjectVStartupCommandResult> StartAsync(bool contentReady, CancellationToken cancellationToken)
| Tham số | Kiểu | Giá trị |
|---|---|---|
contentReady | bool | true khi nội dung riêng của game đã tải xong. false không bắt đầu gì nếu startup chưa chạy |
cancellationToken | CancellationToken | Dừng việc bạn chờ kết quả. Bản thân startup vẫn chạy tiếp và vẫn gửi cập nhật tới listener |
Trả về một
ProjectVStartupCommandResult:
IsSuccess | Snapshot.Status | Chuyện gì đã xảy ra |
|---|---|---|
true | Ready | Người chơi đã đăng nhập và dữ liệu game đã tải. Mở game |
true | AwaitingLogin | Không có phiên đăng nhập đã lưu. Hiện các lựa chọn đăng nhập |
true | WaitingForContent | Bạn truyền contentReady: false và startup chưa chạy; không có gì bắt đầu |
false | một status chặn | Startup đã dừng. Hiện thông báo cho status đó (xem bên dưới) |
- Gọi
StartAsynckhi startup đang chạy sẽ nhập vào lượt chạy đó và trả về kết quả của nó; không bắt đầu lượt thứ hai, kể cả vớicontentReady: false. - Gọi lại
StartAsync(true, token)để thử lại sau một status chặn, và sau khi đăng nhập thành công. - Khi token của bạn kích hoạt trước, kết quả là
Failedvới categoryCancelled. Kết quả đó chỉ của riêng bạn: listener vàCurrentvẫn theo lượt ch ạy thật.
Khi nó dừng — hiện thông báo cho status, dùng Error.LocalizationKey
(projectv.startup.<category>) khi Error có giá trị:
| Tình huống | Status | Người chơi thấy gì |
|---|---|---|
| Server đang bảo trì | Maintenance | Thông báo bảo trì. Thử lại sau |
| Phiên bản game này quá cũ | UpdateRequired | Yêu cầu người chơi cập nhật, kèm link store |
| Bản build này và server không khớp | ContractMismatch | Yêu cầu người chơi cập nhật. Khi phát triển, điều này nghĩa là bản build Client và môi trường backend lệch nhau; không phải lỗi của Client |
| Không kết nối được server | Offline | Thông báo mất kết nối kèm Retry (Error.Category là Network hoặc Unavailable) |
| Mọi trường hợp khác | Failed | Thông báo lỗi kèm Retry. Các code gồm PROJECTV_STARTUP_BACKEND_FAILED và PROJECTV_STARTUP_UNEXPECTED_FAILURE |
| Token của bạn kích hoạt trước khi startup xong | Failed (chỉ ở kết quả) | Không gì cả; trang đang đóng |
| Phiên đăng nhập đã lưu không còn được chấp nhận | AwaitingLogin với Error có giá trị | Các lựa chọn đăng nhập. IsSuccess vẫn là true |
Không bao giờ mở game khi đang ở một status chặn.
Quy tắc
- Chỉ truyền
contentReady: truekhi nội dung riêng của game đã tải xong. - Chuyển màn dựa trên
IsReady, không bao giờ chỉ dựa trên việc đăng nhập thành công. - Startup không xếp hàng command (lệnh) như các presenter khác: mọi lời gọi đều nhập vào cùng một lượt chạy.
Error.Codedùng để rẽ nhánh và hỗ trợ, không phải chữ hiển thị cho người chơi. Hiện thông báo theoLocalizationKey, vàRequestIdtrong một panel hỗ trợ.
Lỗi thường gặp
- Coi
StartAsyncbị huỷ là đã dừng startup. Nó chỉ dừng việc bạn chờ; hãy vẽ từ listener, không chỉ từ kết quả. - Gọi
StartAsynctừ mọi nút. Gọi một lần khi scene mở, lại một lần sau khi đăng nhập, và từ nút Retry. - Đụng vào object Unity trong listener mà không chuyển thread. Listener có thể chạy ngoài main thread.
- Mở Lobby ngay sau khi đăng nhập. Gọi
StartAsync(true, token)và chờReadytrước, để dữ liệu của người chơi được tải. - Không dispose subscription. Dispose (giải phóng) nó trong
OnDisablehoặcOnDestroy.
Helper gắn UI
startup.SubscribeUiAsync(ProjectVUiBindingId.LoginStartup, apply, token)
hoạt động như SubscribeAsync và còn đánh dấu trong log của SDK khi apply
của bạn đã trả về (xem UI subscriptions và
ProjectVUiSubscriptions).
Nó không chuyển thread: apply chạy trên thread gửi bản cập nhật, nên bạn
tự chuyển về main thread.