Campaign
Màn Campaign liệt kê các nhiệm vụ, cho thấy nhiệm vụ nào đang bị khoá, số sao và mốc của lượt chơi tốt nhất, những gì một lượt chơi có thể rơi ra, và bắt đầu nhiệm vụ trước khi scene gameplay được tải. Nhiệm vụ, khoá, chi phí stamina (thể lực) và sao đều do server quyết định.
| Bạn dùng | Nó cho bạn |
|---|---|
IProjectVCampaignScreen | Screen contract: theo dõi các nhiệm vụ, bắt đầu một nhiệm vụ, giải phóng nó |
ProjectVCampaignView | Những gì cần vẽ: trạng thái đang tải, nhiệm vụ, tổng số sao |
ProjectVCampaignMission | Một nhiệm vụ: trạng thái khoá, chi phí vào, sao, xem trước vật rơi |
ProjectVCampaignMissionStars | Mục tiêu sao, các mốc và lượt chơi tốt nhất của một nhiệm vụ |
ProjectVResult | Kết quả của một command (lệnh) |
Bản thân lượt chơi do scene gameplay chạy; xem Dữ liệu gameplay.
Bắt đầu nhanh
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using SilverTiger.ProjectV.Unity.Presentation.Campaign;
using UnityEngine;
using VContainer;
public sealed class CampaignPage : MonoBehaviour
{
private IProjectVCampaignScreen campaign = null!;
private IDisposable? observation;
private bool starting;
[Inject]
public void Construct(IProjectVCampaignScreen screen) => campaign = screen;
private void OnEnable() => observation = campaign.Observe(Render);
private void OnDisable()
{
observation?.Dispose();
observation = null;
}
private void Render(ProjectVCampaignView view)
{
if (view.IsLoading)
{
ShowLoading(view.LoadError); // LoadError is null while the first load is running
return;
}
ShowMissions(view.Missions, view.TotalStars);
}
public async void OnStartClicked(ProjectVCampaignMission mission)
{
// A second tap would get the first tap's result; start and load only once.
if (starting)
{
return;
}
starting = true;
var result = await campaign.BeginMissionAsync(mission.MissionId.Value);
if (this == null)
{
return;
}
if (!result.IsSuccess)
{
starting = false;
ShowMessage(result.Message);
return;
}
var sceneName = FindScene(mission.GameplayKey.Value);
if (string.IsNullOrEmpty(sceneName))
{
// No scene for this mission: release the run so it costs nothing.
await campaign.CancelSceneLoadAsync();
starting = false;
return;
}
LoadScene(sceneName); // your scene loader; on failure call CancelSceneLoadAsync
}
private void ShowLoading(string? error) { }
private void ShowMissions(IReadOnlyList<ProjectVCampaignMission> missions, long totalStars) { }
private void ShowMessage(string message) { }
private string? FindScene(string gameplayKey) => null;
private void LoadScene(string sceneName) { }
}
Đó là toàn bộ trang: theo dõi, vẽ, bắt đầu nhiệm vụ. Sau khi một lượt chơi kết
thúc bạn không phải làm gì; khi người chơi quay lại, IsCompleted, Stars và
stamina đã được cập nhật sẵn. Screen contract
cho thấy cùng luồng này từ ProjectV-Client.
Vẽ gì
Render nhận một ProjectVCampaignView:
| Member | Kiểu | Dùng để |
|---|---|---|
IsLoading | bool | True cho tới khi danh sách nhiệm vụ về lần đầu. Hiện spinner |
LoadError | string? | Được gán khi lần tải đầu tiên đó thất bại. Hiện nó kèm nút thử lại (Retry) đóng rồi mở lại trang |
Missions | IReadOnlyList<ProjectVCampaignMission> | Các nhiệm vụ, kèm tiến độ của người chơi ở từng nhiệm vụ |
TotalStars | long | Số sao đạt được trên tất cả nhiệm vụ |
Find(missionId) | ProjectVCampaignMission? | Tra một nhiệm vụ, ví dụ để cập nhật popup nhiệm vụ đang mở |
Với mỗi ProjectVCampaignMission:
| Để hiện | Đọc |
|---|---|
| Scene cần tải | GameplayKey.Value, ánh xạ sang một scene trong bảng của riêng bạn |
| Chi phí vào | EntryStamina |
| Giới hạn thời gian | DurationSeconds |
| Đã hoàn thành | IsCompleted |
| Đang có lượt chơi mở | IsActive |
| Bị khoá | IsLocked: đã đóng, chưa hoàn thành nhiệm vụ bắt buộc, hoặc chưa đủ tổng số sao. Tắt nút Start |
| Chưa mở | IsClosed; nhiệm vụ đã đóng thì luôn IsLocked |
| "Hoàn thành X trước" | RequiredMissionIds |
| Một lượt chơi có thể rơi gì | DropPreview: Kind, ResourceId, Amount (số tối đa một lượt chơi có thể rơi) |
| Sao | Stars (bên dưới) |
Với Stars, một ProjectVCampaignMissionStars:
| Để hiện | Đọc |
|---|---|
| "2/3 sao" | BestStars / MaxStars |
| Các mục tiêu | Objectives: ObjectiveId, Kind, Threshold; đánh dấu các mục có trong BestRunObjectiveIds |
| Phần thưởng mốc | Milestones: StarsRequired, Rewards, IsClaimed |
| "Cần 12 sao" | RequiresTotalStars, cùng IsLocked khi TotalStars còn thấp hơn mức đó |
Kind của mục tiêu là một trong complete_stage, duration_at_most_ms,
enemies_defeated_min, health_percent_min và stages_cleared_min;
Threshold tính theo đơn vị mà kind đó nêu, và là null với complete_stage.
Dùng kind để chọn text cho mục tiêu. Nhiệm vụ không có sao thì có
ProjectVCampaignMissionStars.None.
Khớp một nhiệm vụ với art và text của bạn bằng MissionId.Value trong một bảng
của riêng bạn; SDK không cung cấp tên hay icon.
Bắt đầu nhiệm vụ
Task<ProjectVResult> BeginMissionAsync(string missionId)
| Tham số | Kiểu | Giá trị |
|---|---|---|
missionId | string | mission.MissionId.Value của một nhiệm vụ không bị khoá trong view hiện tại |
Server bắt đầu một lượt chơi và kiểm tra stamina vào. Stamina bị trừ khi lượt chơi kết thúc, không phải lúc này.
Trả về một ProjectVResult:
IsSuccess | Message | Điều gì đã xảy ra |
|---|---|---|
true | rỗng | Server đã bắt đầu lượt chơi. Tải scene gameplay |
false | lý do, sẵn để hiện | Lượt chơi không bắt đầu, hoặc không thể xác nhận (xem bên dưới) |
Khi thất bại — hiện Message nguyên văn. Các lý do thường gặp:
| Tình huống | Người chơi thấy gì |
|---|---|
| Không đủ stamina | Thông báo của server (code STAMINA_INSUFFICIENT) |
| Nhiệm vụ bị khoá | Thông báo của server (code MISSION_RUN_LOCKED) |
| Chưa đủ tổng số sao | Thông báo của server (code MISSION_RUN_LOCKED_STARS) |
missionId null hoặc rỗng | "The request was not confirmed. Please try again." |
| Không có kết nối hoặc timeout | Một thông báo mạng. Không tự động bắt đầu lại |
Mỗi lần chỉ một lượt bắt đầu, cho mọi nhiệm vụ. Bấm lần hai vào cùng nhiệm vụ trong lúc nó đang bắt đầu sẽ nhận cùng kết quả. Bấm vào nhiệm vụ khác trong lúc một nhiệm vụ đang bắt đầu sẽ thất bại với "Another mission is starting." và không gửi gì. Chỉ tải scene gameplay một lần, cho nhiệm vụ đã bắt đầu; kết quả cũng có thể về sau khi trang đã đóng.
Giải phóng lượt chơi không tải được
Task<ProjectVResult> CancelSceneLoadAsync()
Gọi nó sau một lần BeginMissionAsync thành công khi bảng của bạn không có
scene cho GameplayKey của nhiệm vụ, hoặc khi scene tải thất bại. Lượt chơi đã
giải phóng không tốn stamina.
Trả về một ProjectVResult:
IsSuccess | Message | Điều gì đã xảy ra |
|---|---|---|
true | rỗng | Server đã giải phóng lượt chơi. Danh sách nhiệm vụ tự vẽ lại |
false | lý do, sẵn để hiện | Lượt chơi chưa được giải phóng; hiện Message và để người chơi thử lại |
Lần gọi thứ hai trong lúc một lần gọi đang chạy nhận cùng kết quả.
Stamina và sao
- Stamina vào bị trừ khi lượt chơi kết thúc: thắng, thua hoặc bỏ cuộc. Chơi lại trong scene gameplay, lượt chơi đã giải phóng, hoặc app bị tắt hay bị crash thì không tốn gì.
- Server chấm sao khi lượt chơi kết thúc. Campaign hiện
BestStarsvà các mốc đã nhận; scene gameplay hiện số sao của lượt chơi vừa chơi. - Hiện khoá từ
IsLockedvàStars.IsLocked. Đừng bao giờ tự tính từ tiến độ local; dù sao server cũng từ chối bắt đầu nhiệm vụ bị khoá. - Cho QA, backend staging có thể mở khoá mọi nhiệm vụ; khi đó Campaign hiện tất cả đều đã mở.
Lỗi thường gặp
- Tải scene gameplay hai lần. Một lần bấm được gộp có thể trả kết quả sau khi việc tải đã bắt đầu. Giữ một cờ và chỉ tải một lần.
- Bấm nhiệm vụ thứ hai khi nhiệm vụ đầu đang bắt đầu. Nó thất bại với "Another mission is starting."; hãy bỏ qua lượt bấm đó thay vì hiện lỗi.
- Để lượt chơi đã bắt đầu treo khi scene không tải được. Gọi
CancelSceneLoadAsync. - Trừ stamina trên màn hình ngay lúc Start. Stamina bị trừ khi lượt chơi kết thúc; Header tự cập nhật.
- Tự tính khoá hay sao từ dữ liệu local. Dùng view.
- Dùng kết quả sau khi trang đã đóng. Kiểm tra
this != nullsauawait. - Không dispose observation. Giữ
IDisposabletừObservevà dispose (giải phóng) nó trongOnDisablehoặcOnDestroy.
Presenter
IProjectVCampaignPresenter
là interface cấp thấp hơn nằm sau screen contract. Chỉ dùng nó khi bạn cần thứ
mà screen không cung cấp:
| Member | Dùng để |
|---|---|
SubscribeAsync(listener) | Mọi ProjectVCampaignSnapshot: Status (Empty, Refreshing, Ready, Beginning, Failed), Recovery, Data |
Current.Error | Một ProjectVCampaignError đầy đủ với Code và RequestId, ví dụ để tự chọn text theo từng code |
RefreshAsync(token) | Tải danh sách nhiệm vụ (trả về ngay nếu đã tải) |
BeginAsync(missionId, token) | Dạng presenter của BeginMissionAsync |
CancelSceneLoadAdmissionAsync(token) | Dạng presenter của CancelSceneLoadAsync |
Các command trả về một ProjectVCampaignCommandResult.
Listener của SubscribeAsync có thể chạy ngoài Unity main thread; chuyển về
main thread trước khi đụng tới Unity object. Observe của screen luôn vẽ trên
main thread.