[原创]游戏中的实时排行榜实现

目录

  • 1. 前言
  • 2. 排行榜分类
  • 3. 思路
  • 4. 实现 复合排序
    • 4.1 等级排行榜
    • 4.2 通天塔排行榜
    • 4.3 坦克排行榜
  • 5. 排名数据的动态更新
  • 6. 取排行榜
  • 7. Show The Code

1. 前言

前段时间刚为项目(手游)实现了一个实时排行榜功能, 主要特性:

  • 实时全服排名
  • 可查询单个玩家排名
  • 支持双维排序

数据量不大, 大致在 1W ~ 50W区间(开服, 合服会导致单个服角色数越来越多).

2. 排行榜分类

按照排行主体类型划分, 主要分为:

  • 角色
  • 军团(公会)
  • 坦克

该项目是个坦克手游, 大致情况是每个角色有N辆坦克, 坦克分为多种类型(轻型, 重型等), 玩家可加入一个军团(公会).

具体又可以细分为:

  • 角色

    • 等级排行榜(1. 等级 2.战力)
    • 战斗力排行榜(1. 战斗 2.等级)
    • 个人竞技场排行榜(1. 竞技场排名)
    • 通天塔排行榜(1.通天塔层数 2.通关时间)
    • 威望排行榜(1.威望值 2.等级)
  • 军团(公会)
    • 军团战斗力排行榜(1. 军团总战斗力 2.军团等级)
    • 军团等级排行榜(1.军团等级 2.军团总战斗力)
  • 坦克(1.坦克战斗力 2.坦克等级)
    • 轻型坦克战斗力排行榜
    • 中型
    • 重型
    • 反坦克炮
    • 自行火炮

      ↑ 括号内为排序维度

3. 思路

基于实时性的考虑, 决定使用Redis来实现该排行榜.

文章中用到的redis命令如有不清楚的, 可参照 Redis在线手册.



需要解决如下问题:

  1. 复合排序(2维)
  2. 排名数据的动态更新
  3. 如何取排行榜

4. 实现 复合排序

基于Redis的排行榜主要使用的是Redis的 有序集合(SortedSet)来实现

添加 成员-积分 的操作是通过Redis的zAdd操作
ZADD key score member [[score member] [score member] ...]

默认情况下, 若score相同, 则按照 member 的字典顺序排序.

4.1 等级排行榜

首先以等级排行榜(1. 等级 2.战力)为例, 该排行榜要求同等级的玩家, 战斗力大的排在前. 因此分数可以定为:
**分数 = 等级*10000000000 + 战斗力**

游戏中玩家等级范围是1~100, 战力范围0~100000000.

此处设计中为战斗力保留的值范围是 10位数值, 等级是 3位数值, 因此最大数值为 13位.
有序集合的score取值是是64位整数值或双精度浮点数, 最大表示值是 9223372036854775807, 即能完整表示18位数值, 因此用于此处的 13位score 绰绰有余.

4.2 通天塔排行榜

另一个典型排行榜是 通天塔排行榜(1.层数 2.通关时间), 该排行榜要求通过层数相同的, 通关时间较早的优先.

由于要求的是通关时间较早的优先, 因此不能像之前那样直接 **分数=层数*10^N+通关时间**.

我们可以将通关时间转换为一个相对时间, 即 **分数=层数*10^N + (基准时间 - 通关时间)
很明显的, 通关时间越近(大), 则
基准时间 - 通关时间** 值越小, 符合该排行榜要求.

基准时间的选择则随意选择了较远的一个时间 2050-01-01 00:00:00, 对应时间戳2524579200

最终, **分数 = 层数*10^N + (2524579200 - 通过时间戳)
上述分数公式中, N取10, 即保留10位数的相对时间.

4.3 坦克排行榜

坦克排行榜跟其他排行榜的区别在于, 有序集合中的 member 是一个复合id, 由 uid_tankId 组成.
这点是需要注意的.

5. 排名数据的动态更新

还是以等级排行榜为例

游戏中展示的等级排行榜所需的数据包括(但不限于):

  • 角色名
  • Uid
  • 战斗力
  • 头像
  • 所属公会名
  • VIP等级

由于这些数据在游戏过程中是会动态变更的, 因此此处不考虑将这些数据直接作为 member 存储在有序集合中.
用于存储玩家等级排行榜有序集合如下

-- s1:rank:user:lv ---------- zset --
| 玩家id1 | score1
| ...
| 玩家idN | scoreN
-------------------------------------

member为角色uid, score为复合积分

使用hash存储玩家的动态数据(json)

-- s1:rank:user:lv:item ------- string --
| 玩家id1 | 玩家数据的json串
| ...
| 玩家idN |
-----------------------------------------

使用这种方案, 只需要在玩家创建角色时, 将该角色添加到等级排行榜中, 后续则是当玩家 等级\战斗力 发生变化时需实时更新s1:rank:user:lv该玩家的复合积分即可. 若玩家其他数据(用于排行榜显示)有变化, 则也相应地修改其在 s1:rank:user:lv:item 中的数据json串.

6. 取排行榜

依旧以等级排行榜为例.

目的
: 需要从 s1:rank:user:lv 中取出前100名玩家, 及其数据.

用到的Redis命令
: ZRANGE key start stop [WITHSCORES]
: 时间复杂度: O(log(N)+M), N 为有序集的基数,而 M 为结果集的基数。

步骤

  1. zRange("s1:rank:user:lv", 0, 99) 获取前100个玩家的uid
  2. hGet("s1:rank:user:lv:item", $uid) 逐个获取前100个玩家的具体信息

具体实现时, 上面的步骤2是可以优化的.

分析
: zRange时间复杂度是O(log(N)+M) , N 为有序集的基数,而 M 为结果集的基数
: hGet时间复杂度是 O(1)
: 步骤2由于最多需要获取100个玩家数据, 因此需要执行100次, 此处的执行时间还得加上与redis通信的时间, 即使单次只要1MS, 最多也需要100MS.

解决
: 借助Redis的事务, 整个过程可以降低到只与redis通信2次, 大大降低了所耗时间.

以下示例为php代码

// $redis
$redis->multi(Redis::PIPELINE);
foreach ($uids as $uid) {
    $redis->hGet($userDataKey, $uid);
}
$resp = $redis->exec(); // 结果会一次性以数组形式返回

7. Show The Code

<?php
class RankList
{
    protected $rankKey;
    protected $rankItemKey;
    protected $sortFlag;
    protected $redis;

    public function __construct($redis, $rankKey, $rankItemKey, $sortFlag=SORT_DESC)
    {
        $this->redis = $redis;
        $this->rankKey = $rankKey;
        $this->rankItemKey = $rankItemKey;
        $this->sortFlag = SORT_DESC;
    }

    /**
     * @return Redis
     */
    public function getRedis()
    {
        return $this->redis;
    }

    /**
     * @param Redis $redis
     */
    public function setRedis($redis)
    {
        $this->redis = $redis;
    }

    /**
     * 新增/更新单人排行数据
     * @param string|int $uid
     * @param null|double $score
     * @param null|string $rankItem
     */
    public function updateScore($uid, $score=null, $rankItem=null)
    {
        if (is_null($score) && is_null($rankItem)) {
            return;
        }

        $redis = $this->getRedis()->multi(Redis::PIPELINE);
        if (!is_null($score)) {
            $redis->zAdd($this->rankKey, $score, $uid);
        }
        if (!is_null($rankItem)) {
            $redis->hSet($this->rankItemKey, $uid, $rankItem);
        }
        $redis->exec();
    }

    /**
     * 获取单人排行
     * @param string|int $uid
     * @return array
     */
    public function getRank($uid)
    {
        $redis = $this->getRedis()->multi(Redis::PIPELINE);
        if ($this->sortFlag == SORT_DESC) {
            $redis->zRevRank($this->rankKey, $uid);
        } else {
            $redis->zRank($this->rankKey, $uid);
        }
        $redis->hGet($this->rankItemKey, $uid);
        list($rank, $rankItem) = $redis->exec();
        return [$rank===false ? -1 : $rank+1, $rankItem];
    }

    /**
     * 移除单人
     * @param $uid
     */
    public function del($uid)
    {
        $redis = $this->getRedis()->multi(Redis::PIPELINE);
        $redis->zRem($this->rankKey, $uid);
        $redis->hDel($this->rankItemKey, $uid);
        $redis->exec();
    }

    /**
     * 获取排行榜前N个
     * @param $topN
     * @param bool $withRankItem
     * @return array
     */
    public function getList($topN, $withRankItem=false)
    {
        $redis = $this->getRedis();
        if ($this->sortFlag === SORT_DESC) {
            $list = $redis->zRevRange($this->rankKey, 0, $topN);
        } else {
            $list = $redis->zRange($this->rankKey, 0, $topN);
        }

        $rankItems = [];
        if (!empty($list) && $withRankItem) {
            $redis->multi(Redis::PIPELINE);
            foreach ($list as $uid) {
                $redis->hGet($this->rankItemKey, $uid);
            }
            $rankItems = $redis->exec();
        }
        return [$list, $rankItems];
    }

    /**
     * 清除排行榜
     */
    public function flush()
    {
        $redis = $this->getRedis();
        $redis->del($this->rankKey, $this->rankItemKey);
    }
}

这就是一个排行榜最简单的实现了, 排行项的积分计算由外部自行处理.

原文地址:https://www.cnblogs.com/youjiaxing/p/10310617.html

时间: 2024-10-11 17:29:30

[原创]游戏中的实时排行榜实现的相关文章

Unity3d 游戏中的实时降噪-对Square Enix文档的研究与实现

看到SE的技术文档关于降噪的决定研究一下,本次试验场景: 文章中提到了3中主要滤波方法,最后一种方法又有三种方式分别为Conventional geometry-aware ?ltering,Distribution-Aware Filtering,Specular Lobe-Aware Filtering and Upsampling根据公式做了一些,自己弄得除噪方法不知道是否正确,虽然有效果=  =: 博主的步骤:1.    找出噪波(包括噪波与贴图上的细节)2.    算出specular

[原创]ActionScript3游戏中的图像编程(连载三十)

2.2.3 Photoshop/Flash中的投影品质 与Photoshop不同,Flash的滤镜在输出的作品中仍会实时通过FlashPlayer进行渲染,所以性能显得尤为重要,在迫不得已的情况下还要以牺牲品质作为代价.所以,Flash的品质下拉框引起了我的注意,我试着把品质调整为“高”,效果就可以跟Photoshop的媲美了.(图 2.18) 对于品质,Flash的帮助文件也给出了解释,品质的高低差别在FlashPlayer内部是通过对低品质滤镜的使用次数不同来进行控制的,低品质只模糊1次,高

原创教程“ActionScript3.0游戏中的图像编程”开始连载啦!

        经过近两年的不懈努力,笔者的原创教程“ActionScript3游戏中的图像编程”终于在今日划上了完美的句号!这当中记录着笔者多年来在游戏制作,尤其是当中图像处理方面的经验心得.虽然使用的语言是目前可能正在没落的ActionScript3.0,但由于其中渗透着深刻的编程思想和实用技巧,因此其意义已经远远超越了ActionScript本身.您可以把从本书学到的知识轻松移植到其他支持图像编程的开发语言中(我也想换个语言重写,可惜精力不足).         此外,本书还包含了一些颜色

[原创]ActionScript3游戏中的图像编程(连载三十二)

2.2.5 投影距离的模拟 Photoshop投影样式面板的下一个属性是距离,它也存在于Flash的投影滤镜选项中.两者初始值一致,经笔者测试,两者在效果实现和数值意义方面基本一致.Flash不需要对默认参数进行更改. 下一项是扩展,乍一看,在Flash中并没有找到对应项.但仔细观察,在Photoshop投影样式的基础选项里,除了alpha以外,就只剩该属性用了百分比. [原创]ActionScript3游戏中的图像编程(连载三十二),布布扣,bubuko.com

[原创]ActionScript3游戏中的图像编程(连载二十九)

2.2.2 Photoshop投影大小的模拟 投影没有之前那么浓了,但是跟Photoshop里的效果差别还挺大,因为在Photoshop里我们还设置了另外一个属性:大小. Flash里似乎找不到它的影子,我们用排除法来进行定位,Photoshop投影样式的大小属性以像素为单位,Flash投影滤镜的选项只有距离和那对被“手铐”扣住的模糊属性符合条件,而Photoshop里也有一个距离,所以我们定位到模糊属性(图 2.15). 图 2.15 Flash投影的模糊属性 分别调整Photoshop的大小

[原创]ActionScript3游戏中的图像编程(连载三十一)

2.2.4 Photoshop转Flash的投影角度换算 继续往下看,Photoshop不透明度选项下面是投影角度,默认值是120°,而Flash里角度是45°,两者相差将近90°,投影角度却相近,为什么?让我们把Flash的投影滤镜也设置成120°.(图 2.20) 图 2.20 角度值等于120的Flash投影 投影跑左边去了,x方向与Photoshop里的投影位置截然相反.经观察,Flash投影的角度与ActionScript的坐标系完全一致,它等于当前方向与水平线的夹角,顺时针为正,逆时

原创图书《ActionScript3.0游戏中的图像编程》开始连载啦!

        经过近两年的不懈努力,笔者的原创图书<ActionScript3游戏中的图像编程>终于在今日划上了完美的句号!这当中记录着笔者多年来在游戏制作,尤其是当中图像处理方面的经验心得.虽然使用的语言是目前可能正在没落的ActionScript3.0,但由于其中渗透着深刻的编程思想和实用技巧,因此其意义已经远远超越了ActionScript本身.您可以把从本书学到的知识轻松移植到其他支持图像编程的开发语言中(我也想换个语言重写,可惜精力不足).         此外,本书还包含了一些颜

[原创图书]《ActionScript3.0游戏中的图像编程》连载——前言

1946年,计算机在第三次工业革命的推动下得以诞生并发展.从此地球上多了一类人,他们的世界很简单,每天不是和0接触,就是跟1来往.他们低调做人,高调做事,他们务实为民,不求名利,他们,就是可爱可人,可歌可泣的IT工作者--程序员! 都说程序员不善表达,没错,离开0和1,程序员就几乎不会说话了.与此同时,程序员也恰恰是最善于表达的人,他们只用0和1这两个数字,就可以准确无误地将用户的需求传达给电脑并使其正确运行,其语言之简炼,其算法之精准,其逻辑之严谨,其结果之准确,试问除了程序员,还有谁可以做得

[原创]ActionScript3游戏中的图像编程(连载二十八)

2.2.1 投影颜色与透明度的模拟 默认的投影滤镜看着比Photoshop的投影样式浑浊,究其原因,似乎是颜色太深所致.(图 2.11). 图 2.11 设置默认投影滤镜后的效果 下面回到Photoshop查看其默认的投影参数(图 2.12). 图 2.12 Photoshop默认投影样式的参数 茫茫人海,我还是第一眼发现了你——颜色样本块.同是黑色,RGB都等于0,怎么差别还这么大?看来是A通道——不透明度惹的祸.从图 2.12中,我们看到Photoshop里对阴影设置了75%的不透明度,那F