4、数值校验体系:游戏内经济系统校验、属性增长曲线校验、概率类事件校验

数值校验,说白了就是给游戏世界里的数字上把锁。

我见过太多项目,上线第一天就被刷子们把经济系统搞崩了。嗯,这里要说的不是简单的「数值范围检查」,而是一整套校验体系。它要能挡住脚本、堵住逻辑漏洞、还能在性能开销和安全性之间找到平衡点。

4.1 经济系统校验:别让金币变成废纸

游戏经济系统一旦崩了,玩家信任也就没了。我经历过一个MMO项目,上线第三天就发现某个副本产出金币异常——原因是客户端提交的掉落数据没做二次校验,脚本直接改了掉落包。

经济系统校验,核心要盯住三个环节:

  • 产出校验:每个金币来源都要有「合法身份」。任务奖励、怪物掉落、交易收入,各自有独立的产出ID和上限。
  • 流通校验:玩家之间交易时,物品和金币的流向必须可追溯。我习惯在交易完成时记录一条「交易流水」,包含双方ID、物品、金额、时间戳。
  • 总量监控:服务器端定时统计全服金币总量。如果某小时增量超过阈值(比如正常产出的3倍),立刻触发告警。
核心原则:客户端永远不可信。所有涉及经济变动的操作,服务器必须重新计算一遍。

举个例子,玩家出售物品给商店:

// 伪代码:服务器端出售校验
function sellItem(player, itemId, quantity) {
    // 1. 检查物品是否存在
    item = getItemFromDB(player.id, itemId);
    if (!item || item.quantity < quantity) return ERROR_ITEM_NOT_FOUND;

    // 2. 计算总价(服务器算,不用客户端传的)
    price = getItemSellPrice(itemId) * quantity;

    // 3. 检查玩家当日出售次数上限
    if (getDailySellCount(player.id) >= MAX_DAILY_SELL) return ERROR_SELL_LIMIT;

    // 4. 扣除物品,增加金币
    removeItem(player.id, itemId, quantity);
    addGold(player.id, price);

    // 5. 记录流水
    logTransaction(player.id, "sell", itemId, quantity, price);
}

你想想看,如果第2步用了客户端传过来的价格,那脚本直接传个天价,经济系统瞬间就崩了。

4.2 属性增长曲线校验:别让角色变成超人

属性增长曲线,说白了就是角色从1级到满级,各项属性应该怎么涨。这个曲线一旦被篡改,就会出现「1级号一刀秒BOSS」的荒唐事。

我建议在服务器端维护一份「属性增长基准表」:

等级区间 攻击力基准 防御力基准 血量基准 允许偏差
1-10 10-50 5-25 100-500 ±5%
11-30 50-200 25-100 500-2000 ±3%
31-60 200-800 100-400 2000-8000 ±2%
61-100 800-3000 400-1500 8000-30000 ±1%

每次玩家属性发生变化(升级、装备更换、技能学习),服务器都要做一次「属性合规检查」:

function validateAttributes(player) {
    expected = getBaseAttributes(player.level);
    actual = player.getAttributes();

    // 检查每个属性是否在允许范围内
    for (attr in ["attack", "defense", "hp"]) {
        deviation = abs(actual[attr] - expected[attr]) / expected[attr];
        if (deviation > ALLOWED_DEVIATION[player.level]) {
            // 触发异常,回滚属性
            logCheatAttempt(player.id, "attribute_overflow", attr, deviation);
            rollbackAttributes(player);
            return false;
        }
    }
    return true;
}
注意:属性校验不能只在升级时做。装备、buff、技能都可能临时改变属性。我建议在每次属性变更操作完成后,都触发一次校验。

我曾经遇到过一个案例:外挂通过修改内存中的装备强化等级,把+5的武器改成了+20。因为服务器只在装备穿戴时校验一次,结果这个假+20武器在玩家手里用了三天才被发现。后来我们改成「每次战斗前校验装备属性」,才堵住这个漏洞。

4.3 概率类事件校验:别让抽卡变成必中

概率类事件,比如抽卡、暴击、掉落,是外挂的重灾区。外挂通常通过两种方式作弊:

  • 重放攻击:抽到好东西就提交,没抽到就断线重连
  • 篡改随机种子:直接修改客户端随机数,让「1%概率」变成「100%概率」

我的做法是:所有概率事件的结果,由服务器生成,客户端只负责展示

具体流程是这样的:

  1. 客户端发起抽卡请求,带上本次操作的「流水号」
  2. 服务器用流水号+玩家ID+时间戳,生成一个「确定性随机数」
  3. 服务器根据这个随机数,决定抽卡结果
  4. 结果直接写入数据库,再返回给客户端
// 服务器端抽卡逻辑
function drawCard(player, poolId) {
    // 1. 检查抽卡次数和资源
    if (!canDraw(player, poolId)) return ERROR_LIMIT;

    // 2. 生成确定性随机数
    seed = hash(player.id + "_" + poolId + "_" + getDrawCount(player, poolId));
    random = pseudoRandom(seed);

    // 3. 根据概率表决定结果
    result = getResultByProbability(poolId, random);

    // 4. 发放奖励,记录日志
    giveReward(player, result);
    logDraw(player.id, poolId, result, seed);

    return result;
}
小技巧:确定性随机数还有一个好处——方便查证。如果玩家投诉「我抽了100次没出SSR」,你可以用他的抽卡记录重新计算一遍随机数,验证概率是否正常。

另外,概率事件还要做「宏观校验」。比如某个SSR的出货率设定为1%,如果服务器统计发现某玩家连续1000抽都没出,虽然单次概率没问题,但整体概率已经偏离了预期。这时候需要触发「保底机制」或者告警。

4.4 数值校验体系架构图

下面这张图展示了数值校验体系的整体架构。你可以看到,所有客户端请求都要经过三层校验,才能到达游戏逻辑层。

数值校验体系架构图 客户端请求 第一层:经济系统校验 产出校验 → 流通校验 → 总量监控 第二层:属性增长曲线校验 等级基准 → 装备加成 → 实时合规检查 第三层:概率类事件校验 服务器随机数 → 宏观概率监控 → 保底机制 游戏逻辑层(安全) 校验要点 • 客户端数据不可信 • 服务器重新计算 • 流水日志可追溯 • 宏观异常监控 • 性能与安全平衡 • 确定性随机数 • 保底与告警机制

这张图里,三层校验是串行的。任何一层校验失败,请求都会被拦截,不会到达游戏逻辑层。这样做的好处是:即使某一层有漏洞,还有后面两层兜底。

总结一下:数值校验不是一次性工作,而是需要持续迭代的。每次发现新的作弊手段,都要在对应层级打上补丁。我见过最成功的反外挂项目,他们的数值校验代码量甚至超过了游戏逻辑代码本身。

嗯,数值校验这块就聊这么多。记住一句话:服务器永远不要相信客户端传来的任何数值。所有数字,自己算一遍,准没错。