mmo游戏跨服玩法

2020/01/17 Mmo-Game

mmo游戏跨服玩法

目录

跨服玩法简单介绍

1.游戏里,一般各个服相互之间是屏蔽的。例如:001服务器玩家不能登录002服务器,
不同服之间玩家不能组队、聊天...
2.为了游戏运营以及增加玩法之间的乐趣,跨服活动就登场了。不同服的玩家可以在活动开启时,参加同一个
临时服务器,不同服务器的玩家在这里可以进行 pk, 组队,聊天...
3.当然跨服活动时会屏蔽很多玩法,毕竟最后还是要回到自己原先的服务器,所以类似加好友这种功能,交易...在跨服是会屏蔽
的,仅会开放有限的一些功能。

开始流程图

晋梁战场活动玩法流程

1.活动最终确认报名

1.world验证通过之后的回调,调用跨服模块

/**
* world验证通过之后的回调
*
* @param time
*/
public int callbackConfirmSignUp(long time)
{
    int result = getActor().getCrossModule()
            .clientCrossMatchRequest(ActivityType.WAR_GROUND, DictWarGroundConfig.getSceneDefineId());
}

2.发起跨服匹配请求

public int clientCrossMatchRequest(int matchTypeID, int activitySceneID)
{
    ...
     // 发送跨服匹配请求给server 线程
    BPSSRequestCrossMatch message = new BPSSRequestCrossMatch(MessageIDConst.SS_SERVER_MATCH_REQUEST);
    message.setMatchData(data);

    sendMessageToService(message, ServiceManager.getInstance().getServerService());
    crossStatus = ActorCrossStatusEnum.GAME_MATCHING_REQUEST;
    ...
}

1.设置状态为游戏服请求跨服匹配状态。
2.发送跨服匹配请求给server线程(内网线程)。
3.添加到内网线程的waitMatchMap待匹配列表。

3.内网线程向Match匹配服发送匹配请求

ServerCrossModule:
根据活动id分组,添加到map列表
/**
* 跨服匹配请求
*
* 游戏服
*/
public void matchRequest(ActorCrossMatchData matchData, AbstractService srcService)
{
    int activityID = matchData.getActivityID();
    ArrayList<ActorCrossMatchData> list = waitMatchMap.get(activityID);
    if (list == null)
    {
        list = new ArrayList<>();
        waitMatchMap.put(activityID, list);
    }
    list.add(matchData);
    BPLog.BP_CROSS.info("[跨服] 游戏服server收到客户端的跨服匹配请求 {}", matchData);
}

/**
* tick处理待匹配的数据
* @param interval
*/
private void tickWaitMatch(int interval)
{
    1.构建数据
    2.加入到待移除列表,等待匹配服响应后从列表删除。
    3.将构建好的数据发送的匹配服。
}

2.匹配服match逻辑

1.协议结构

repeated int64 actorID = 1; // 玩家id
repeated int64 crossSequenceID = 2; // 跨服请求的序列id,针对单个玩家递增
repeated string accountID = 3; // 账号id
repeated int32 gameServerID = 4; // 游戏服务器的id
required int32 activityID = 5; // 活动id
repeated int32 level = 6; // 玩家等级
repeated int32 fightForce = 7; // 玩家战斗力

2.匹配请求逻辑

/**
* 匹配请求
*
* @param srcServerID
* @param matchGroup
*/
public int matchRequest(int srcServerID, MatchGroup matchGroup)
{
    //根据server id 获取所在的逻辑分区id
    //这里是内网注册时broker从主服拿到数据下发到match服(主服是查数据库配置)
    int zoneLogicID = getZoneLogicIDByServerID(srcServerID);
    if (zoneLogicID < 0)
    {
        return MatchErrorCode.LOGIC_ZONE_ID_NOT_FOUND;
    }

    //加入待匹配组
    int result = addWaitingMatchGroup(zoneLogicID, matchGroup);
    if (result < 0)
    {
        return result;
    }

    return 0;
}
获取逻辑分区id,加入待匹配组

3.tick处理待匹配组

 /**
* tick处理匹配
*
* @param interval
*/
public void tickMatch(int interval)
{
    tickMatchRemainTime -= interval;
    if (tickMatchRemainTime <= 0)
    {
        // 匹配
        handleMatch();

        tickMatchRemainTime = MATCH_TICK_INTERVAL;
    }
}

每200毫秒统一从待匹配列表取出进行匹配。
最终匹配:

 /**
* 最终进行匹配处理
* 目前匹配代码比较简单,就是来一个就分配一个,实现不了PVP匹配,这个匹配
* 需要后期重构下面的代码来实现
*
* @param logicZoneID 逻辑大区ID,理论上应该只有同大区的才可以进行匹配
* @param activityID  活动ID
* @param groups
*/
private void matchByLogicZoneID(int logicZoneID, int activityID, HashSet<MatchGroup> groups)
{
    ...
     //匹配逻辑管理类(目前每次都会进)
    IMatchLogicManager manager = ReloadManagerContext.getInstance().getMatchLogicManager();
    if (manager != null)
    {
        //如果是延时匹配那么 策略为0。 如果是及时匹配,策略为1。
        //Enum类提供了一个ordinal()方法,用来返回枚举对象的序数。
        //如果是延时匹配
        if (manager.getMatchMode(activityID) == MatchLogicMatchMode.DELAY.ordinal())
        {
            matchLogic = activityMatchLogicMap.get(activityID);
            if (matchLogic == null)
            {
                //是否每次调用新建算法实例 0是,1不是
                if (manager.getMatchOnce(activityID) == MatchLogicLifeCycle.ONCE.ordinal())
                {
                    matchLogic = manager.getMatchLogicInstance(activityID);
                }
                else
                {
                    //构建算法伪单例
                    matchLogic = manager.getMatchLogicSingle(activityID);
                }
                if(matchLogic == null)
                {
                    groups.clear();
                    MatchLog.MATCH.error("没有找到活动对应的匹配处理类, activityID:{}", activityID);
                    return;
                }
                //设置创建时间戳
                matchLogic.setCreateTimestamp(MainClock.getInstance().currentTimeMillis());
                //添加到缓存匹配逻辑处理对象的map
                activityMatchLogicMap.put(activityID, matchLogic);
            }

            //当前时间 - 创建时间 > 超时时间
            long current = MainClock.getInstance().currentTimeMillis();
            //如果还不到延时时间,则等下一个tick去判断
            if (!matchLogic.isStartMatchTimeout(current))
            {
                return;
            }

        }
        else
        {
            if (manager.getMatchOnce(activityID) == MatchLogicLifeCycle.ONCE.ordinal())
            {
                matchLogic = manager.getMatchLogicInstance(activityID);
            }
            else
            {
                matchLogic = manager.getMatchLogicSingle(activityID);
            }
        }
    }

    上面都是创建策略对象。

    各个活动更加匹配规则,进行分组匹配。
    //进行匹配逻辑!目前只有战场匹配和debug匹配逻辑。
    MatchResult matchResult = matchLogic.match(groups);
    if (matchResult != null)
    {
        // 判断是否有超时的匹配
        ArrayList<MatchGroup> matchTimeoutGroups = matchResult.getMatchTimeoutGroups();
        if (matchTimeoutGroups != null)
        {
            int size = matchTimeoutGroups.size();
            for (int i = 0; i < size; i++)
            {
                MatchGroup matchGroup = matchTimeoutGroups.get(i);
                ArrayList<MatchActor> group = matchGroup.getGroup();
                int groupSize = group.size();
                for (int j = 0; j < groupSize; j++)
                {
                    MatchActor matchActor = group.get(j);
                    if (matchActor == null)
                    {
                        continue;
                    }
                    sendMatchTimeoutPacket(matchActor.getActorID(), matchActor.getCrossSequenceID(), matchActor
                            .getActivityID(), matchActor.getGameServerID());
                }
                // 从待匹配里删除
                groups.remove(matchGroup);
            }
        }
    }
    ...


    发送匹配成功消息给游戏

}

3.游戏服响应匹配成功

1.成功协议结构

required int64 actorID = 1;
required int32 mirrorServerID = 2; // mirror的server id
required int32 activityID = 3; // 活动id
required int64 crossSequenceID = 4; // 跨服请求的序列id,针对单个玩家递增
required string accountID = 5; // 账号id
required int32 result = 6; // 匹配结果 0:成功 负数:错误码
optional string mirrorServerIP = 7; // 镜像服服务器ip
optional int32 mirrorServerPort = 8; // 镜像服服务器port
optional int64 matchSequenceID = 9; // 匹配成功的匹配序列号
optional SpecificationMatchData matchData = 10; // 匹配的特化数据
repeated int64 actorIDList = 11; // 匹配成一组的所有actor id数据

2.游戏服内网收到匹配成功消息

内网模块:ServerCrossModule

/**
* 处理match服务的匹配结果
* 游戏服
* @param packet
*/
public void matchResponse(Global.MGMatchResponse packet)
{
    ActorCrossMatchData data = matchingMap.remove(actorID);
    if (data == null)
    {
        // 匹配已经被取消 打日志
        BPLog.BP_CROSS.info("跨服 [游戏服] 收到match匹配结果 match已经被取消, actor:{}, account:{}, result:{}",
                packet.getActorID(), packet.getAccountID(), packet.getResult());
        return;
    }

    ...
    // 放入匹配完成的map
    matchDoneMap.put(actorID, data);

    // 通知场景 匹配结果, 通过world
    BPSSCrossMatchResponse response = new BPSSCrossMatchResponse(MessageIDConst.SW_SERVER_MATCH_RESPONSE);
}

1.从正在匹配列表中移除。
2.放入匹配完成的map。
3.发送world,通过world通知场景匹配结果。

世界:

@MessageHandler(MessageIDConst.SW_SERVER_MATCH_RESPONSE)
public void handleServerMatchResponse(BPWorldService service, BPSSCrossMatchResponse message)
{
    message.modifyMessageID(MessageIDConst.SS_SERVER_MATCH_RESPONSE);

    service.sendMessageToActor(message.getActorID(), message);
}
只是做中转往场景转发协议。

场景:

/**
* 游戏服 match的匹配回复处理
* @param message
*/
public void matchResponse(BPSSCrossMatchResponse message)
{
    //失败


    //成功
    
}

Search

    Table of Contents