Article

Unity WebGL 小游戏实战 11:异步竞技(幽灵回放与排行榜挑战)

路线阶段:Unity WebGL 小游戏实战第 11 章。
本章目标:在不做实时PVP的前提下,构建可持续竞争体验。

学习目标

完成本章后,你应该能做到:

  1. 设计异步竞技核心环:成绩上传、榜单拉取、幽灵回放对战。
  2. 复用回放系统生成“可重演”的挑战数据。
  3. 建立成绩校验与防作弊基础策略。
  4. 用赛季重置和分段奖励维持长期活跃。

为什么先做异步竞技

相较实时联机,异步竞技优势:

  1. 技术成本低,WebGL兼容性更好。
  2. 对网络实时性要求低。
  3. 可复用已有回放与统计系统。

适合中小团队快速上线竞争玩法。

核心数据结构

成绩记录

[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);
}

幽灵回放流程

  1. 玩家完成关卡后上传成绩 + 回放摘要。
  2. 客户端拉取榜单前 N 名的 GhostSummary
  3. 选择一个目标作为幽灵。
  4. 下载对应回放数据,驱动 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);
    }
}

成绩可信度校验

客户端提交前最少校验:

  1. 版本一致(防旧包刷榜)。
  2. 回放长度与成绩逻辑一致。
  3. 关键指标不越界(如伤害异常高)。
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;
    }
}

服务端可进一步做:

  1. 签名校验
  2. 回放抽样复跑
  3. 异常分数封禁策略

排行榜展示与交互

UI 至少包含:

  1. Top10 榜单
  2. 我的名次附近
  3. 一键挑战某名玩家幽灵
  4. 刷新与赛季剩余时间

赛季机制

建议每 7~14 天一季:

  1. 榜单清空或软重置。
  2. 按最终排名发放赛季奖励。
  3. 记录历史最佳成绩与段位。

与现有系统联动

  1. 回放系统:复用回放文件生成幽灵。
  2. 埋点系统:记录挑战转化和复战率。
  3. 经济系统:赛季奖励发放。
  4. 活动系统:配置“挑战榜单任务”。

WebGL 注意点

  1. 回放下载应异步并有超时重试。
  2. 幽灵表现资源需轻量,避免高性能开销。
  3. 网络失败时保留离线挑战兜底(本地历史幽灵)。

验收清单

  1. 可提交成绩并拉取排行榜。
  2. 可加载并挑战幽灵回放。
  3. 成绩异常输入能被本地校验拦截。
  4. 赛季切换后榜单与奖励逻辑正确。

常见坑

坑 1:幽灵与本地玩法逻辑耦合过深

幽灵应是纯表现层,不参与本局数值结算。

坑 2:排行榜刷新无节制

会造成请求风暴。需加缓存与刷新冷却。

坑 3:成绩只信客户端

容易被篡改。至少做多层校验与异常检测。

本月作业

实现“好友幽灵挑战”:

  1. 展示好友最佳记录。
  2. 选择好友幽灵进入挑战。
  3. 挑战成功后生成分享文案与回放链接。

下一章进入 Unity WebGL 小游戏实战 12:UI/UX最终打磨与可访问性优化(新手引导、反馈层级、低配适配)。