PVP 匹配
Category
system / 玩法系统。节点定位:评分体系与匹配池两层解耦、分别选型——评分选 ELO/Glicko/TrueSkill,撮合选窗口放宽、优先级队列与补位降级。组队与房间形态的姊妹节点是「队伍匹配」。
Definition
PVP 匹配是两层系统:评分体系(给每个玩家一个可比较的分数)与匹配池(用分数把谁和谁放一起)。两层可以独立选型。
Problem
评分与匹配混着改就会互相甩锅:分数不准怪窗口太宽,等待过长怪评分不灵。三条红线一旦破了,段位体系会被刷分与体验事故一起毁掉——评分必须服务端算、窗口放宽必须单调、人机必须标注。先解耦两层,再各选各的;上线后还要闭环:胜率回归速度与等待时长 P95 是匹配质量的两个硬指标。
Algorithm
经典模式:ELO 评分
ELO 源自国际象棋等级分体系,三行公式说完:
期望胜率:E_A = 1 / (1 + 10^((R_B - R_A) / 400))
赛后更新:R_A' = R_A + K · (S_A - E_A) S_A ∈ {1, 0.5, 0}代入一个可复算的例子:1200 分玩家对 1400 分玩家,E = 1/(1+10^(200/400)) ≈ 0.240;若爆冷获胜且 K=32,则 R' = 1200 + 32×(1 − 0.240) ≈ 1224.3(+24.3 分)。翻车方同理扣 24.3——分数总和近似守恒(差一个 K 的缩放),这是 ELO 对战类游戏的根基。
工程要点:
- K 值分层:新手期 K=64 快速定位,稳定期 K=32,长期不活跃回 K=64——K 就是「响应速度 vs 稳定性」的调节旋钮
- 防刷:同分段重复匹配限频、连胜封顶(单日收益上限)、异常对局(秒退/挂机)不计入或按负局处理——评分管线必须挂服务端,客户端只收结果(教程第 18 讲结算权威口径)
- 局限:ELO 假设实力静止,版本改动、赛季重置、长时间不玩都会让分数失真——这是变体的动机
变体:Glicko / Glicko-2 / TrueSkill
| 体系 | 核心扩展 | 回答的问题 | 计算量 |
|---|---|---|---|
| ELO | 一个分数 R | 你多强 | 常数 |
| Glicko | R + 评级偏差 RD | 你多强,以及多可信(新号 RD 高、久不玩 RD 涨) | 每玩家每周期 O(1) 批量更新 |
| Glicko-2 | 再加波动度 σ | 你的实力稳不稳定(状态起伏大的玩家) | 略高,仍是解析式 |
| TrueSkill | 高斯分布(μ, σ)对 | 队伍胜负下的实力估计(微软研究院 2005,Herbrich/Minka/Graepel) | 每局一次近似推断(截断高斯),常数级 |
选型口径:
- 纯 1v1、赛季制:Glicko 的 RD 直接解决「新赛季前几局该多谨慎」——匹配时按 RD 放宽或收紧窗口,比拍一个固定段位差科学
- 组队 2v2 以上:TrueSkill(或其简化)按队伍合成分布,避免「五人队平均分 vs 五人平均分相等但方差天差地别」
- 不要上复杂体系除非有对局数据支撑:Glicko-2/TrueSkill 的参数(RD 收敛速度、β、τ)需要大量对局标定,标不好比 ELO 更差——先 ELO 跑通,留好分数快照,再谈升级
匹配池:范围逐步放宽
评分只负责可比性,撮合靠动态窗口:
窗口: [avg - Δ(t), avg + Δ(t)]
Δ(t) = Δ0 + α·t # 线性放宽(每秒 α 分)
放宽档位:严格 → 跨小段 → 跨大段 → 不限分段(标注「匹配范围已放宽」)- 窗口放宽必须单调:收紧会造成「等了 30 秒被拒」的挫败;界面同步显示当前放宽档位
- 新手保护:低分段(如前 N 局或 R < 1500)不进放宽池,防止被拉去当高手的炮灰——这是留存问题不是公平问题
- 配对确认:窗口内选谁,用质量分(分数差、延迟差、胜率历史)做二次排序,取质量最高的一对
优先级队列实现
等待中的玩家进优先级队列,撮合器每轮弹出「最该被匹配」的人:
- 键设计:
priority = 等待时长权重 × w1 + 分数段稀缺度 × w2——等待越久优先级越高,保证没有人在窗口外饿死 - 结构:等待者按分数排序的平衡树/跳表(范围内找相邻候选 O(log N + k))+ 等待时长堆(弹出下个撮合对象 O(1)~O(log N)),两结构联动
- 每轮流程:弹出头部玩家 → 在分数窗口内取候选 k 个 → 算配对质量分 → 质量达标即配对,不达标放回、下轮窗口已放宽
- 复杂度:单次撮合 O(log N + k),k 为窗口内候选数——这就是「匹配池应该常驻内存」的原因(与排行榜节点同一结论:有序高频查询用内存结构)
机器人补位与秒进降级
- 触发阶梯:严格窗口超时 → 放宽分数 → 放宽延迟/地域 → 机器人补位(每档有配置化时长)
- 机器人规则:按真实分数区间模拟强度、行为库带难度曲线、界面必须标注人机(体验与合规的双重要求,教程第 18 讲有明确口径);新手局的人机是教学的一部分,高手局的人机突然出现是事故
- 秒进降级:休闲池不让人等——超时直接进任意匹配(含人机),把「等待」视为比「段位纯度」更高的成本
- 防滥用:人机局不计排位分或按低倍计,防止「只打人机刷分」的漏洞
对比表
| 维度 | ELO | Glicko(-2) | TrueSkill | 无评分(纯池匹配) |
|---|---|---|---|---|
| 每局计算 | 常数 | 常数(周期批量) | 常数(近似推断) | 无 |
| 可信度表达 | 无 | RD 显式 | σ 显式 | 无 |
| 队伍支持 | 平均分近似 | 可扩展 | 原生 | 无关 |
| 冷启动 | 新号跳变几局 | RD 大→窗口自动放宽 | 分布宽→自动放宽 | 首局即开 |
| 参数标定 | K 一个 | RD/收敛/波动多个 | β/τ/多参数 | 无 |
| 防刷面 | 分数守恒可审计 | 同左 | 同左 | 无分可刷 |
| 上手成本 | 低 | 中 | 高 | 低(无质量评估) |
取舍
排位和段位体系用「ELO/Glicko 打底 + 分桶匹配池 + 降级阶梯」三件套,齐了就完整;TrueSkill 只在队伍竞技且有对局数据做参数标定时才值得上,标定数据不够时它比 ELO 更差。休闲竞技可以完全不要评分(Elo-less,按 KD/胜率等行为信号分层),核心诉求是快开而非公平纯度。
架构上最值钱的一条是解耦:评分体系与匹配窗口分家,换评分(ELO→Glicko)不该重写匹配池——分数只是一个输入字段。上线后匹配质量要闭环:盯胜率向 50% 回归的速度与等待时长 P95(教程第 11 讲匹配质量双指标),分数反馈过强会出现匹配震荡,防震荡按同讲陷阱表执行。
适用规模
千级同时等待,内存池 + 线性放宽窗口,单进程撮合器足够,先做「ELO + 分桶 + 阶梯」。万级把撮合器拆成独立进程、按分段分片池,等待队列与窗口参数进配置中心热更(无重启调参)。十万级跨服用多撮合 worker + 一致性路由——同一玩家的重复请求落同一 worker,避免双撮合;人机补位按池分片配置。
Used By
- skynet:撮合器是一个常驻 actor,等待队列、窗口状态全在服务内存里串行维护(见 Skynet 源码解析 第 6 节);每轮撮合的节拍与超时降级挂时间轮(同篇第 7 节)
- Pitaya:撮合服务经集群 RPC 与对局服交接(服务发现与路由)
- KBEngine:对局实体与场景由 cellapp 承载,匹配器保持旁路服务
- 评分与匹配参数(K、α、阶梯时长)全部配置化 + 服务端持有——客户端只知道结果,不知道怎么算的(教程第 18 讲反作弊口径)
Related
关系链:评分体系(ELO/Glicko/TrueSkill)→ 匹配窗口 → 优先级队列 → 补位与降级 → 质量闭环(胜率回归、等待 P95)。逐段回答「为什么需要下一个」:
- 评分只回答「谁和谁可比」,撮合窗口回答「现在就把他们放一起」——窗口放宽速率是体验参数,不是评分参数。
- 窗口放宽了还配不上,就轮到优先级队列:等待时长进优先级,保证没有人在窗口外饿死。
- 队列也救不了冷门时段,补位与降级兜底:人机是体验的下限保障,不是评分体系的组成部分。
组队与房间形态见队伍匹配(队伍分口径与本页评分体系衔接);榜单与段位分桶的粒度见排行榜。
Reference
- ELO 体系源自国际象棋等级分(Arpad Elo);Glicko/Glicko-2 由 Mark Glickman 提出(哈佛统计系技术报告,1995/2012);TrueSkill 见微软研究院论文 Herbrich, Minka, Graepel, 《TrueSkill: A Bayesian Skill Rating System》(NIPS 2006)
- 术语对照:Skill Rating(评分体系)、Matchmaking Rating / MMR(匹配分)、Confidence Interval(评级偏差)、Skill Bucket(段位分桶)