跳到主要内容

运营活动

运营活动涵盖限时活动、排行榜、成就、云存档、自动备份和模组支持。这些系统能够在发布后持续提供内容并保持玩家参与度。

LiveOps 中心

活动、排行榜和成就集中在一个界面中。进行中活动的倒计时使用的是服务器时间,而不是客户端的时钟 —— 这正是阻止玩家通过修改系统日期来提前结束活动的原因。

架构概览​

类文件职责
EventServicePersistence/PersistenceAndSystems.cs季节性/限时活动管理
LeaderboardServicePersistence/PersistenceAndSystems.cs带分数提交功能的排名排行榜
AchievementServicePersistence/PersistenceAndSystems.cs成就跟踪和解锁系统
CloudSaveServicePersistence/PersistenceAndSystems.cs带冲突检测功能的云存档上传/下载
BackupSchedulerAntiCheat/AntiCheatManager.cs自动定期云备份
ModLoaderPersistence/PersistenceAndSystems.cs运行时社区模组加载

通过 NakamaManager.Instance 访问:

var events = NakamaManager.Instance.Events;
var leaderboards = NakamaManager.Instance.Leaderboards;
var achievements = NakamaManager.Instance.Achievements;
var cloudSave = NakamaManager.Instance.CloudSave;
var mods = NakamaManager.Instance.Mods;

事件服务​

EventService 管理限时运营活动(社区日、季节性内容、特殊遭遇)。

获取活跃事件​

List<LiveEvent> activeEvents = await events.GetActiveEventsAsync();

foreach (var evt in activeEvents)
{
// evt.Id -- 唯一事件 ID
// evt.Name -- 显示名称
// evt.Description -- 事件详情
// evt.StartTime -- 事件开始的 Unix 时间戳(毫秒)
// evt.EndTime -- 事件结束的 Unix 时间戳(毫秒)
// evt.RewardData -- JSON 编码的奖励配置
}

事件生命周期​

events.OnEventStarted += (LiveEvent evt) =>
{
Debug.Log($"Event started: {evt.Name}");
// 显示活动横幅、启用活动遭遇等
};

events.OnEventEnded += (string eventId) =>
{
Debug.Log($"Event ended: {eventId}");
// 移除活动内容、分发参与奖励
};

LiveEvent 数据模型​

字段类型描述
Idstring唯一事件标识符
Namestring显示名称
Descriptionstring事件描述文本
StartTimelong开始时间戳(Unix 毫秒)
EndTimelong结束时间戳(Unix 毫秒)
RewardDatastringJSON 编码的奖励配置

事件通过 Nakama 控制台在服务器端配置。客户端轮询活跃事件并对开始/结束通知做出反应。

排行榜服务​

LeaderboardService 提供带分数提交和排名查询的排行榜。

提交分数​

await leaderboards.SubmitScoreAsync(
boardId: "pvp_season_1",
score: 1850);

await leaderboards.SubmitScoreAsync(
boardId: "dex_completion",
score: 151);

查询排名​

// 获取顶级玩家
List<LeaderboardEntry> top100 = await leaderboards.GetTopPlayersAsync(
boardId: "pvp_season_1",
limit: 100);

foreach (var entry in top100)
{
// entry.Rank, entry.DisplayName, entry.Score, entry.UserId
}

// 获取我的排名
LeaderboardEntry myRank = await leaderboards.GetMyRankAsync("pvp_season_1");

刷新数据​

await leaderboards.RefreshAsync();

LeaderboardEntry 数据模型​

字段类型描述
UserIdstring玩家的 Nakama 用户 ID
DisplayNamestring玩家的显示名称
Scorelong分数值
Rankint排行榜上的位置

排行榜支持赛季重置——每个赛季创建新的排行榜 ID(例如 pvp_season_2),旧的存档。

成就服务​

AchievementService 跟踪玩家的成就,支持进度功能。

列出成就​

List<Achievement> all = await achievements.GetAchievementsAsync();

foreach (var ach in all)
{
// ach.Id, ach.Name, ach.Description
// ach.CurrentProgress, ach.MaxProgress
// ach.IsUnlocked, ach.RewardData
}

解锁成就​

// 即时解锁
await achievements.UnlockAsync("first_capture");

// 基于进度(例如:"捕捉 100 只怪物")
await achievements.UpdateProgressAsync("centurion_catcher", progress: 47);

成就事件​

achievements.OnAchievementUnlocked += (Achievement ach) =>
{
Debug.Log($"Achievement unlocked: {ach.Name}!");
// 显示解锁弹窗,从 ach.RewardData 发放奖励
};

刷新数据​

await achievements.RefreshAsync();

Achievement 数据模型​

字段类型描述
Idstring唯一成就 ID
Namestring显示名称
Descriptionstring成就描述
CurrentProgressint当前进度值
MaxProgressint完成所需的目标进度
IsUnlockedbool成就是否已完成
RewardDatastringJSON 编码的奖励配置

云存档服务​

CloudSaveService 将 MCE 现有的基于 JSON 的存档系统与 Nakama 服务器存储连接起来,实现跨设备游戏。

上传存档​

string saveJson = GetLocalSaveJson(); // 你的存档序列化
bool success = await cloudSave.UploadSaveAsync(saveJson);

// cloudSave.HasCloudSave -- 成功上传后为 true
// cloudSave.LastSyncTimestamp -- 上次同步的 Unix 时间戳

下载存档​

string saveJson = await cloudSave.DownloadSaveAsync();
// 反序列化并应用到本地游戏状态

冲突检测​

保存时,系统可以检测本地和云端数据之间的冲突:

SaveConflictInfo conflict = await cloudSave.CheckSaveConflictAsync(localTimestamp);

if (conflict.HasConflict)
{
// conflict.CloudTimestamp vs conflict.LocalTimestamp
// 显示 UI 让玩家选择保留哪个存档
}

专项同步​

// 同步 PC 存储箱(怪物存储)
await cloudSave.SyncPCStorageAsync(pcStorageJson);

// 同步进度数据(徽章、图鉴、任务)
await cloudSave.SyncProgressionAsync(progressionJson);

备份调度器​

BackupScheduler 是一个 MonoBehaviour,用于创建定期自动云备份。

配置​

字段默认值描述
autoSaveIntervalMinutes5自动备份间隔(分钟)
maxBackupSlots10最大备份历史大小(FIFO 轮换)

备份的内容​

备份会上传调度器的 SaveDataProvider(一个 Func<string>)所返回的内容。如果你没有设置它,调度器会使用 LocalSaveDataProvider,它读取磁盘上最近一次 SavegameManager 的存档。如果没有提供者、磁盘上没有存档或没有连接,备份会被跳过并触发 OnBackupFailed:不会上传任何内容,也不会写入历史条目。

手动备份​

backupScheduler.CreateBackupAsync();

从备份恢复​

string saveJson = await backupScheduler.RestoreBackupAsync(backupId);

备份事件​

backupScheduler.OnBackupCreated += (BackupEntry entry) =>
{
// entry.Id, entry.Timestamp, entry.Type, entry.SizeBytes
};

backupScheduler.OnBackupFailed += (string reason) =>
{
Debug.LogWarning($"Backup failed: {reason}");
};

备份类型​

类型描述
Auto定期自动备份
Manual玩家发起的备份
PreBattle进入战斗前的快照
PreTrade执行交易前的快照

模组支持​

ModLoader 允许在运行时加载社区创建的内容包。这由 OnlineConfig.EnableModSupport(默认:false)控制。

加载模组​

启用模组支持后,已安装的模组在连接期间会自动加载:

await mods.LoadInstalledModsAsync();

安装和移除模组​

bool installed = await mods.InstallModAsync("/path/to/mod.zip");
bool removed = await mods.UninstallModAsync(modId);

列出已加载的模组​

IReadOnlyList<ModManifest> loadedMods = mods.LoadedMods;

foreach (var mod in loadedMods)
{
// mod.Id, mod.Name, mod.Author, mod.Version
// mod.Description, mod.IsEnabled
// mod.ContentTypes -- 例如 ["monsters", "maps", "items", "moves"]
}

模组事件​

mods.OnModLoaded += (ModManifest manifest) =>
{
Debug.Log($"Mod loaded: {manifest.Name} v{manifest.Version}");
};

mods.OnModError += (string modId, string error) =>
{
Debug.LogError($"Mod error ({modId}): {error}");
};

ModManifest 数据模型​

字段类型描述
Idstring唯一模组标识符
Namestring显示名称
Authorstring模组作者
Versionstring语义化版本
Descriptionstring模组描述
ContentTypesstring[]内容类型:"monsters"、"maps"、"items"、"moves"
IsEnabledbool模组当前是否激活

反作弊系统​

反作弊层与运营活动并行运行,以维护游戏完整性。请参阅 AntiCheat/AntiCheatManager.cs 中的专用反作弊类:

  • SpeedValidator:通过将移动速度与可配置的最大值(默认:8 格/秒)进行比较来检测速度作弊。
  • ActionRateLimiter:使用可配置的最小间隔(默认:0.3 秒)防止操作垃圾信息。
  • InventoryAuditor:通过将客户端报告的库存与服务器快照进行比较来检测物品复制和非法物品。
  • AntiCheatManager:使用违规阈值协调所有验证器——10 次违规触发踢出,25 次触发封禁。