游戏 匹配算法 实现(基于ELO分数、等待时长)

去年拒绝了上家公司的提干和股票,毅然投身游戏行业,加入了一家创业公司,经历了一年多比996强度还高的加班,我们的第一款游戏终于顺利上线了。我全程参与了我们游戏服务器的开发,从架构到业务。现在游戏上线稳定了,也有时间总结下开发的的一些经验了。

我们游戏的是ARPG游戏,客户端用unity3d,服务器用java。我们有主城,做了场景同步;有组队战斗,做了帧同步;有排位赛,做了匹配。场景同步、帧同步、匹配的服务器都是由我做的,先记录下这几个吧。

我们的匹配规则和大家熟悉的moba游戏一样,根据玩家的分数(一般叫ELO值、或RANK值)匹配实力相近的玩家,并且根据匹配的等待时间扩大匹配范围,直到匹配到足够的人(或者超过一定时间还没凑够人,则补AI机器人)。

不啰嗦,直接上简版代码

 /**
     * 匹配线程
     */
    private static ScheduledExecutorService sec = Executors.newSingleThreadScheduledExecutor();

    /**
     * 每个人需要匹配到的玩家数量
     */
    private static int NEED_MATCH_PLAYER_COUNT = 1;
    /**
     * 匹配池
     */
    private static ConcurrentHashMap playerPool = new ConcurrentHashMap();


    static{
        sec.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                matchProcess(playerPool);
            }
        }, 1,1, TimeUnit.SECONDS);//每隔1秒匹配一次
    }

     /**
     * 把玩家放入匹配池
     * @param playerId
     * @param rank
     * @return
     */
    public static void putPlayerIntoMatchPool(int playerId, int rank){
        MatchPoolPlayerInfo playerInfo = new MatchPoolPlayerInfo(playerId, rank);
        playerPool.put(playerId, playerInfo);
    }

    /**
     * 把玩家从匹配池移除
     * @param playerId
     */
    public static void removePlayerFromMatchPool(int playerId){
        playerPool.remove(playerId);
    }

    private static void matchProcess(ConcurrentHashMap playerPool) {
        long startTime = System.currentTimeMillis();
        log.debug("执行匹配开始|开始时间|"+startTime);
        try{
            //先把匹配池中的玩家按分数分布
            TreeMap> pointMap = new TreeMap>();
            for (MatchPoolPlayerInfo matchPlayer : playerPool.values()) {
                //在匹配池中是时间太长,直接移除
                if((System.currentTimeMillis()-matchPlayer.getStartMatchTime())>60 * 60 * 1000){
                    log.warn(matchPlayer.getPlayerId()+"在匹配池中是时间超过一个小时,直接移除");
                    removePlayerFromMatchPool(matchPlayer.getPlayerId());
                    continue;
                }
               HashSet set = pointMap.get(matchPlayer.getRank());
                if(set==null){
                    set = new HashSet();
                    set.add(matchPlayer);
                    pointMap.put(matchPlayer.getRank(), set);
                }else{
                    set.add(matchPlayer);
                }
            }

            for (HashSet sameRankPlayers: pointMap.values()) {
                boolean continueMatch = true;
                while(continueMatch){
                    //找出同一分数段里,等待时间最长的玩家,用他来匹配,因为他的区间最大
                    //如果他都不能匹配到,等待时间比他短的玩家更匹配不到
                    MatchPoolPlayerInfo oldest = null;
                    for (MatchPoolPlayerInfo playerMatchPoolInfo : sameRankPlayers) {
                        if(oldest==null){
                            oldest = playerMatchPoolInfo;
                        }else if(playerMatchPoolInfo.getStartMatchTime() matchPoolPlayer = new ArrayList();
                    //从中位数向两边扩大范围搜索
                    for(int searchRankUp = middle,searchRankDown = middle; searchRankUp <= max||searchRankDown>=min;searchRankUp++,searchRankDown--){
                        HashSet thisRankPlayers = pointMap.getOrDefault(searchRankUp,new HashSet());
                        if(searchRankDown!=searchRankUp&&searchRankDown>0){
                            thisRankPlayers.addAll(pointMap.getOrDefault(searchRankDown,new HashSet()));
                        }
                        if(!thisRankPlayers.isEmpty()){
                            if(matchPoolPlayer.size() it = thisRankPlayers.iterator();  
                                while (it.hasNext()) {
                                    MatchPoolPlayerInfo player = it.next();
                                    if(player.getPlayerId()!=oldest.getPlayerId()){//排除玩家本身
                                        if(matchPoolPlayer.size() sameRankPlayer = pointMap.get(player.getRank());
                            sameRankPlayer.add(player);
                        }
                    }
                }
            }
        }catch(Throwable t){
            log.error("match|error",t);
        }
        long endTime = System.currentTimeMillis();
        log.debug("执行匹配结束|结束时间|"+endTime+"|耗时|"+(endTime-startTime)+"ms");
    }

    private static class MatchPoolPlayerInfo{
        private int playerId;//玩家ID
        private int rank;//玩家分数
        private long startMatchTime;//开始匹配时间


        private MatchPoolPlayerInfo(int playerId, int rank) {
            super();
            this.playerId = playerId;
            this.rank = rank;
            this.startMatchTime = System.currentTimeMillis();
        }

        public int getPlayerId() {
            return playerId;
        }

        public int getRank() {
            return rank;
        }

        public long getStartMatchTime() {
            return startMatchTime;
        }
    }

简单说下实现思路吧:把参与匹配的玩家都丢入匹配池,每个玩家记录两个属性(分数、开始匹配的时间),每秒遍历匹配池中所有分数段,找出每个分数上等待时间最长的玩家,用他的范围来进行匹配(因为匹配范围会因为等待时间边长而增加,等待时间最长的的玩家匹配范围最大,如果连他都匹配不够,那同分数段的其他玩家就更匹配不够了)。如果匹配到了足够的人,那就把这些人从匹配池中移除,匹配成功;如果匹配人到的人数不够并且没有达到最大匹配时间,则跳过等待下一秒的匹配;如果达到最大匹配时间,还是没匹配到足够的人,则给这个几个人凑机器人,提交匹配成功。

你可能感兴趣的:(游戏-moba-匹配,游戏)