Chuyển tới nội dung chính

Screen contract

Lobby, Character, Alliance, Campaign và Shop mỗi màn có một screen contract: một interface nhỏ mà trang theo dõi và gọi. Dùng nó thay cho presenter của màn. Trang vẽ (render) một view và gửi command (lệnh); việc tải, thử lại và bấm hai lần đã được xử lý sẵn cho bạn.

MànNamespace (SilverTiger.ProjectV.Unity.Presentation.*)ViewCommand
IProjectVLobbyScreen.LobbyProjectVLobbyViewkhông có
IProjectVShopScreen.ShopProjectVShopViewPurchaseAsync(offerId, quantity)
IProjectVCampaignScreen.CampaignProjectVCampaignViewBeginMissionAsync(missionId), CancelSceneLoadAsync()
IProjectVCharacterScreen.CharacterProjectVCharacterViewSelectCharacterAsync, LevelUpAsync, UpgradeSkillAsync, UpgradeCastSkillAsync, ResetSkillsAsync, ResetLevelAsync
IProjectVAllianceScreen.AllianceProjectVAllianceViewSelectAllyAsync, LevelUpAsync, UpgradeWeaponSkillAsync, ResetLevelAsync

Startup, Authentication, Account, Header và Gameplay data không có screen contract; dùng presenter của chúng. Trên Character, trang bị, túi đồ, skin và các hộp thoại xác nhận chưa có trong contract; dùng IProjectVCharacterPresenter cho những phần đó.

Hình dạng​

XxxView Current { get; } // the last confirmed data, never null
IDisposable Observe(Action<XxxView> render); // render now, then again when the data changes
Task<ProjectVResult> SomeCommandAsync(...); // one request per tap
  • Observe vẽ ngay, sau đó chỉ vẽ lại khi dữ liệu thay đổi. Nó không bao giờ gọi bạn khi một request đang chạy, và render luôn chạy trên main thread của Unity. Nó cũng tự tải dữ liệu khi cần, nên đừng gọi refresh. Dispose (giải phóng) kết quả khi trang đóng.
  • View luôn hiện dữ liệu đã xác nhận gần nhất. IsLoading chỉ là true cho tới khi dữ liệu đầu tiên về; LoadError giải thích lần tải đầu thất bại, để hiện lời nhắc thử lại. Các bản xem trước (EXP, reset cấp) cũng dùng dữ liệu đã xác nhận, nên không bị tụt về 0 trong lúc request.
  • Command trả về ProjectVResult. IsSuccess cho biết server đã xác nhận hành động hay chưa. Message là chữ cho người chơi, rỗng khi thành công; hiện nguyên văn. Dữ liệu mới về qua Observe, không qua kết quả.
  • Bấm lặp lại sẽ nhập vào command đang chạy và nhận cùng kết quả. Bạn không cần tắt nút hay debounce.
  • Command không nhận CancellationToken. Đóng trang không huỷ hành động trên server. Kiểm tra trang còn tồn tại trước khi dùng kết quả.
  • Character và Alliance không cần nút Sync hay Refresh. Nếu một kết quả không xác nhận được, các màn này tự đồng bộ lại và vẽ lại. Trên Shop, hiện thông báo và để người chơi tự quyết có thử lại không.

Ví dụ: Shop​

Lấy từ ShopUIManager của ProjectV-Client:

private IProjectVShopScreen shop = null!;
private IDisposable? subscription;

[Inject]
public void Construct(IProjectVShopScreen shop)
{
this.shop = shop ?? throw new ArgumentNullException(nameof(shop));
}

private void Start()
{
// Renders the current catalog now, then again only when the offers change.
subscription = shop.Observe(ApplyShopView);
}

private void OnDestroy()
{
subscription?.Dispose();
subscription = null;
}

private void ApplyShopView(ProjectVShopView view)
{
if (this == null)
{
return;
}
if (view.IsLoading)
{
ShowLoading(view.LoadError);
return;
}
RenderOffers(view.Offers);
}

private async Task BuyAsync(string offerId, int quantity)
{
// The listing and balances update through ApplyShopView once the server confirms.
var result = await shop.PurchaseAsync(offerId, quantity);
if (this != null && !result.IsSuccess)
{
ShowMessage(result.Message);
}
}

private void ShowLoading(string? loadError) { }
private void RenderOffers(IReadOnlyList<ProjectVShopOfferView> offers) { }
private void ShowMessage(string message) { }

Ví dụ: bắt đầu một nhiệm vụ​

Dựa trên UICampaignManager của ProjectV-Client, có thêm cờ chặn bấm vào nhiệm vụ khác:

private async Task BeginAndLoadAsync(ProjectVCampaignMission mission, string sceneName, CancellationToken token)
{
// Set the guard before awaiting: a tap on another mission while one starts
// fails with "Another mission is starting.", so ignore taps until this one is done.
if (starting)
{
return;
}
starting = true;

var result = await campaign.BeginMissionAsync(mission.MissionId.Value);
if (!result.IsSuccess)
{
starting = false;
ShowMessage(result.Message);
return;
}

if (token.IsCancellationRequested || this == null)
{
return;
}

if (string.IsNullOrEmpty(sceneName))
{
await campaign.CancelSceneLoadAsync();
starting = false;
return;
}

// starting stays true: the scene loads once for this run.
SceneLoader.Instance.LoadScene(sceneName); // game-owned loader
}

Host không dùng VContainer, và test​

ProjectVPresentationLifetimeScope đăng ký một instance của mỗi màn cho cả phiên; hãy inject interface. Tool và test có thể dựng một màn trên một presenter bằng ProjectVScreens.Lobby(presenter), .Shop, .Campaign, .Character hoặc .Alliance. Tham số tuỳ chọn dispatch chuyển việc vẽ về main thread; không có nó, render chạy ngay tại chỗ. ProjectVScreens.CharacterView(snapshot) dựng một view Character từ một snapshot, không cần presenter.

Tiếp theo: Tham chiếu presenter.