路线阶段:Unity WebGL 小游戏实战第 11 章。
本章目标:在不做实时PVP的前提下,构建可持续竞争体验。
学习目标
完成本章后,你应该能做到:
- 设计异步竞技核心环:成绩上传、榜单拉取、幽灵回放对战。
- 复用回放系统生成“可重演”的挑战数据。
- 建立成绩校验与防作弊基础策略。
- 用赛季重置和分段奖励维持长期活跃。
为什么先做异步竞技
相较实时联机,异步竞技优势:
- 技术成本低,WebGL兼容性更好。
- 对网络实时性要求低。
- 可复用已有回放与统计系统。
适合中小团队快速上线竞争玩法。
核心数据结构
成绩记录
[Serializable]
public sealed class LeaderboardEntry
{
public string PlayerId;
public string DisplayName;
public int Score;
public int StageId;
public long TsMs;
public string ReplayId;
public string BuildVersion;
}
幽灵摘要
[Serializable]
public sealed class GhostSummary
{
public string ReplayId;
public string PlayerId;
public int Score;
public int DurationFrame;
public int StageId;
public int Version;
}
排行榜接口抽象
public interface ILeaderboardService
{
IEnumerator SubmitScore(LeaderboardEntry entry, Action<bool, string> done);
IEnumerator FetchTop(int stageId, int limit, Action<List<LeaderboardEntry>, string> done);
IEnumerator FetchAroundMe(int stageId, string playerId, int radius, Action<List<LeaderboardEntry>, string> done);
}
幽灵回放流程
- 玩家完成关卡后上传成绩 + 回放摘要。
- 客户端拉取榜单前 N 名的
GhostSummary。 - 选择一个目标作为幽灵。
- 下载对应回放数据,驱动
ReplayGhostRunner。
幽灵驱动器
public sealed class ReplayGhostRunner : IUpdatable
{
public int Order { get { return 180; } }
private readonly GhostActor _ghost;
private readonly ReplayFrameStream _stream;
private bool _started;
public ReplayGhostRunner(GhostActor ghost, ReplayFrameStream stream)
{
_ghost = ghost;
_stream = stream;
_started = false;
}
public void Start()
{
_started = true;
_ghost.Show(true);
}
public void Tick(float dt, float unscaledDt)
{
if (!_started)
{
return;
}
ReplayFrame frame;
if (!_stream.TryReadNext(out frame))
{
_ghost.Show(false);
_started = false;
return;
}
_ghost.SetTransform(frame.Position, frame.Rotation);
_ghost.SetAction(frame.ActionTag);
}
}
成绩可信度校验
客户端提交前最少校验:
- 版本一致(防旧包刷榜)。
- 回放长度与成绩逻辑一致。
- 关键指标不越界(如伤害异常高)。
public sealed class ScoreValidator
{
public bool Validate(StageResult result, ReplaySummary replay, out string reason)
{
if (result == null || replay == null)
{
reason = "null_input";
return false;
}
if (result.StageId != replay.StageId)
{
reason = "stage_mismatch";
return false;
}
if (result.Score <= 0)
{
reason = "invalid_score";
return false;
}
if (result.KillCount > 99999)
{
reason = "kill_outlier";
return false;
}
reason = "ok";
return true;
}
}
服务端可进一步做:
- 签名校验
- 回放抽样复跑
- 异常分数封禁策略
排行榜展示与交互
UI 至少包含:
- Top10 榜单
- 我的名次附近
- 一键挑战某名玩家幽灵
- 刷新与赛季剩余时间
赛季机制
建议每 7~14 天一季:
- 榜单清空或软重置。
- 按最终排名发放赛季奖励。
- 记录历史最佳成绩与段位。
与现有系统联动
- 回放系统:复用回放文件生成幽灵。
- 埋点系统:记录挑战转化和复战率。
- 经济系统:赛季奖励发放。
- 活动系统:配置“挑战榜单任务”。
WebGL 注意点
- 回放下载应异步并有超时重试。
- 幽灵表现资源需轻量,避免高性能开销。
- 网络失败时保留离线挑战兜底(本地历史幽灵)。
验收清单
- 可提交成绩并拉取排行榜。
- 可加载并挑战幽灵回放。
- 成绩异常输入能被本地校验拦截。
- 赛季切换后榜单与奖励逻辑正确。
常见坑
坑 1:幽灵与本地玩法逻辑耦合过深
幽灵应是纯表现层,不参与本局数值结算。
坑 2:排行榜刷新无节制
会造成请求风暴。需加缓存与刷新冷却。
坑 3:成绩只信客户端
容易被篡改。至少做多层校验与异常检测。
本月作业
实现“好友幽灵挑战”:
- 展示好友最佳记录。
- 选择好友幽灵进入挑战。
- 挑战成功后生成分享文案与回放链接。
下一章进入 Unity WebGL 小游戏实战 12:UI/UX最终打磨与可访问性优化(新手引导、反馈层级、低配适配)。