游戏 场景同步 实现(状态同步)

来源:互联网 发布:网络机柜搬迁流程 编辑:程序博客网 时间:2024/05/18 02:13

多人同屏游戏,都需要场景同步。场景同步一般分两种,状态同步和帧同步。状态同步一般用于大世界地图,服务器只向玩家同步玩家视野范围内其他玩家的状态信息。帧同步一般用于moba之类场景内玩家并不多并且对同步要求比较高的情况。

状态同步同步是状态,比如在某个坐标出现了某个玩家身上带着某些状态,然后客户端直接显示出来。帧同步同步的是每个玩家的操作,每个客户端上报自己的操作,服务器收集合并之后,下发给每个客户端,客服端再执行操作逻辑,得到相应的表示。

先来个简单的场景状态同步示例讲解。比如只同步场景内玩家的移动位置信息,客户端每隔上报自己的坐标。服务器收到之后,转发给能看到该玩家的其他玩家。其他玩家收到同步信息后,把自己场景中的该角色向那个坐标移动。

那如何判断哪些玩家能看到该玩家呢?我们理所当然的想到以自己为圆心,半径为视野距离的模型。但这种实现起来很麻烦并且很低效,因为以每个玩家的视野都是一个圆。

合理并常用的做法,是把地图划分为无数个方格,服务器通过每个玩家上报的坐标维护玩家所处的方格。玩家的视野就是自己所处的九宫格(或者25宫格)。只需要把玩家的上报同步给所处9宫格的玩家就行了。

另外,如果是手游的话,同屏玩家过多会导致客户端很卡,我们还可以限制下发视野内玩家数量。
该同步模型同步要求不高,仅每秒同步一次,所有用TCP链接就行了(而帧同步基本都是用可靠UDP,每秒至少同步30个操作逻辑帧)

首先下面是用probuf定义的同步数据结构

//场景同步数据message SceneSyncData{    optional int32 zoneId = 1;//分区    optional int32 playerId = 2;//玩家ID    optional SceneType sceneType = 3 [default = DEFAULT_SCENE_TYPE];//场景类型    optional SceneSyncDataType  sceneSyncDataType= 5 [default = UNKNOW_SCENE_SYNC_TYPE];//同步类型    optional UnityVector3 position = 6;//位置坐标    optional UnityVector3 forward = 7;//朝向信息    optional int32 sceneId = 8 [default = 0];//场景Id,场景类型为军团时,此字段填军团ID;场景为主城时,默认为0    optional PlayerShowInfo playerShowInfo = 9;//玩家显示信息,只有进入场景第一帧或者玩家显示信息发生改变时填这个字段    optional int64 dataIndex = 10;//客户端用来排序}//玩家显示信息message PlayerShowInfo{    optional string nickName = 1;//玩家昵称    optional int32 vipLevel = 2;//vip等级    optional int32 titleId = 3;//佩戴的称号ID    optional int32 rankLevel = 4;//军衔等级    optional string groupName = 5;//军团名称    optional int32 groupDuty = 6;//军团职位    optional int32 mechaId = 7;//机甲ID    optional int32 playerLevel = 8;//玩家等级}//场景枚举enum SceneType{    DEFAULT_SCENE_TYPE = 0;//默认占位    MAIN_CITY = 1;//主城    GROUP = 2;//军团}//场景同步类型enum SceneSyncDataType{    UNKNOW_SCENE_SYNC_TYPE = 0 ;//占位    SCENE_SYNC_ENTER = 1;//进入场景    SCENE_SYNC_EXIT = 2;//退出场景    SCENE_SYNC_MOVE = 3;//同步移动操作    SCENE_SYNC_MOVE_STOP = 4;//同步停止移动操作    SCENE_SYNC_APPEAR = 5;//出现在视野里    SCENE_SYNC_LEAVE = 6;//离开视野    SCENE_SYNC_SHOW_INFO = 7;//玩家显示信息变更}

我们把场景分为分块场景和不需要分块的场景。比如主城比较大,就需要像之前说的把地图分块,玩家有视野范围和视野内显示的玩家数量。而军团场景比较小,且需要看到每一个人,就不分块。
先定义一个场景的基类。

/** * 场景抽象类 * @author lhx * */public abstract class AbstractScene {    private static LoggerWraper log = LoggerWraper.getLogger("AbstractScene");    /**     * 分区     */    private int zoneId;    /**     * 场景类型     */    private SceneType sceneType;     /**     * 场景ID     */    private int sceneId;    /**     * 场景的key     */    private String sceneKey;    /**     * 场景中的玩家     */    private ConcurrentSkipListSet<Integer> scenePlayers = new ConcurrentSkipListSet<Integer>();    /**     * 玩家最后上报的同步信息,用于玩家离开场景后保存玩家的位置信息     */    private ConcurrentHashMap<Integer,SceneSyncData> playerLastPosition = new ConcurrentHashMap<Integer,SceneSyncData>();    /**     * 玩家显示信息     */    private ConcurrentHashMap<Integer,PlayerShowInfo> playerShowInfo = new ConcurrentHashMap<Integer,PlayerShowInfo>();    public AbstractScene(int zoneId,SceneType sceneType, int sceneId) {        super();        this.zoneId = zoneId;        this.sceneType = sceneType;        this.sceneId = sceneId;        this.sceneKey = SceneManager.genSceneKey(zoneId,sceneType,sceneId);    }    public int getZoneId() {        return zoneId;    }    public SceneType getSceneType() {        return sceneType;    }    public int getSceneId() {        return sceneId;    }    public String getSceneKey() {        return sceneKey;    }    public ConcurrentSkipListSet<Integer> getScenePlayers() {        return scenePlayers;    }    public ConcurrentHashMap<Integer, SceneSyncData> getPlayerLastPosition() {        return playerLastPosition;    }    /**     * 往场景中添加玩家     * @param playerSyncData     */    public final void addPlayer(SceneSyncData playerSyncData){        if(!isInThisScene(playerSyncData.getPlayerId())){            if(isThisSceneData(playerSyncData)){                scenePlayers.add(playerSyncData.getPlayerId());                playerLastPosition.put(playerSyncData.getPlayerId(), playerSyncData);                if(playerSyncData.hasPlayerShowInfo()){//有显示信息就保存下来                    playerShowInfo.put(playerSyncData.getPlayerId(), playerSyncData.getPlayerShowInfo());                }                doOtherWhenAddPlayer(playerSyncData);                //发送该玩家进入                SceneSyncData positionWithShowInfo = genPositionWithShowInfo(playerSyncData.getPlayerId(), SceneSyncDataType.SCENE_SYNC_ENTER);                if(positionWithShowInfo!=null){                    sendSyncData(positionWithShowInfo);                }            }        }    };    /**     * 往场景中添加玩家的其他处理     * @param playerSyncData     */    public abstract void doOtherWhenAddPlayer(SceneSyncData playerSyncData);    /**     * 从场景中移除玩家     * @param playerSyncData     */    public final void removePlayer(Integer playerId){        if(isInThisScene(playerId)){            //发送该玩家离开            SceneSyncData playerSyncData = genPositionWithShowInfo(playerId, SceneSyncDataType.SCENE_SYNC_EXIT);            if(playerSyncData!=null){                sendSyncData(playerSyncData);            }            //移除该玩家            scenePlayers.remove(playerId);            playerLastPosition.remove(playerId);            playerShowInfo.remove(playerId);            doOtherWhenRemovePlayer(playerId);        }    };    /**     * 从场景中移除玩家的其他处理     * @param playerSyncData     */    public abstract void doOtherWhenRemovePlayer(Integer playerId);    /**     * 收到同步数据     * @param playerSyncData     */    public final void receiveSyncData(SceneSyncData playerSyncData){        if(scenePlayers.contains(playerSyncData.getPlayerId())){//玩家在该场景中            if(isThisSceneData(playerSyncData)){//上报的数据也是该场景的数据                playerLastPosition.put(playerSyncData.getPlayerId(), playerSyncData);                if(playerSyncData.getSceneSyncDataType().equals(SceneSyncDataType.SCENE_SYNC_SHOW_INFO)&&playerSyncData.hasPlayerShowInfo()){//有显示信息就保存下来                    playerShowInfo.put(playerSyncData.getPlayerId(), playerSyncData.getPlayerShowInfo());                }                doOtherWhenReceiveSyncData(playerSyncData);                sendSyncData(playerSyncData);            }        }    }    /**     * 添加玩家的同步数据的其他处理     * @param playerSyncData     */    public abstract void doOtherWhenReceiveSyncData(SceneSyncData playerSyncData);    /**     * 发送同步数据     */    public abstract void sendSyncData(SceneSyncData playerSyncData);    /**     * 判断玩家是否在该场景中     * @param playerId     * @return     */    public boolean isInThisScene(int playerId){        return scenePlayers.contains(playerId);    }    /**     * 判断数据是不是该场景的数据     * @param playerSyncData     * @return     */    public boolean isThisSceneData(SceneSyncData playerSyncData){        return this.sceneKey.equals(SceneManager.genSceneKey(playerSyncData.getZoneId(), playerSyncData.getSceneType(), playerSyncData.getSceneId()));    }    /**     * 生成服务器帧     * 用玩家最后一帧的位置+玩家显示信息     * @param playerId     * @return     */    public SceneSyncData genPositionWithShowInfo(int playerId,SceneSyncDataType sceneSyncDataType){        SceneSyncData positionData = playerLastPosition.get(playerId);        if(positionData==null){            log.error("genPositionWithShowInfo|positionData==null|playerId="+playerId+"|zoneId="+zoneId+"|SceneType="+sceneType+"|SceneSyncDataType"+sceneSyncDataType);            return null;        }        PlayerShowInfo showInfo = playerShowInfo.get(playerId);        if(showInfo==null){            log.error("genPositionWithShowInfo|showInfo==null|playerId="+playerId+"|zoneId="+zoneId+"|SceneType="+sceneType+"|SceneSyncDataType"+sceneSyncDataType);            return null;        }        return positionData.toBuilder().clearDataIndex().setPlayerShowInfo(showInfo).setSceneSyncDataType(sceneSyncDataType).build();    }}

军团之类不需要分块的简单场景实现类

/** * 完整场景(不分块) * @author lhx * */public class CompleteScene extends AbstractScene {    public CompleteScene(int zoneId,SceneType sceneType, int sceneId) {        super(zoneId,sceneType, sceneId);    }    @Override    public void doOtherWhenAddPlayer(SceneSyncData playerSyncData) {        //生成其他玩家的进入帧发给刚上来的玩家        IoSession ioSession = SessionManager.getSessionManager(playerSyncData.getZoneId()).getPlayerSession(playerSyncData.getPlayerId());        for(Integer playerId :getScenePlayers()){            if(playerId!=playerSyncData.getPlayerId()){                SceneSyncData positionWithShowInfo = genPositionWithShowInfo(playerId,SceneSyncDataType.SCENE_SYNC_ENTER);                if(positionWithShowInfo!=null){                    ioSession.write(positionWithShowInfo);                }            }        }    }    @Override    public void doOtherWhenRemovePlayer(Integer playerId) {        // TODO Auto-generated method stub    }    @Override    public void doOtherWhenReceiveSyncData(SceneSyncData playerSyncData) {        // TODO Auto-generated method stub    }    @Override    public void sendSyncData(SceneSyncData playerSyncData) {        for(Integer player:getScenePlayers()){            if(player!=playerSyncData.getPlayerId()){                IoSession ioSession = SessionManager.getSessionManager(this.getZoneId()).getPlayerSession(player);                ioSession.write(playerSyncData);            }        }    }}

需要分块场景的实现类

/** * 分块场景 * @author lhx * */public class BlockScene extends AbstractScene {    private static LoggerWraper log = LoggerWraper.getLogger("BlockScene");    /**     * 地图每个格子的边长     */    private int blockSize;    /**     * 视野玩家上限     */    private int seeMax;    /**     * 分块玩家分布     *      */    private ConcurrentHashMap<String, ConcurrentSkipListSet<Integer>> blockMap = new ConcurrentHashMap<String, ConcurrentSkipListSet<Integer>>();    /**     * 玩家所处的格子     */    private ConcurrentHashMap<Integer,String> playerBlock = new ConcurrentHashMap<Integer,String>();    /**     * 玩家看到哪些玩家     */    private ConcurrentHashMap<Integer,ConcurrentSkipListSet<Integer>> playerSee = new ConcurrentHashMap<Integer,ConcurrentSkipListSet<Integer>>();    public BlockScene(int zoneId,SceneType sceneType, int sceneId, int blockSize, int seeMax) {        super(zoneId,sceneType, sceneId);        this.blockSize = blockSize<20?20:blockSize;        this.seeMax = seeMax<9?9:seeMax;    }    @Override    public void doOtherWhenAddPlayer(SceneSyncData playerSyncData) {        String blockKey = getBlockKey(playerSyncData);        ConcurrentSkipListSet<Integer> blockPlayer = blockMap.get(blockKey);        if(blockPlayer == null){            blockPlayer = new ConcurrentSkipListSet<Integer>();            blockPlayer.add(playerSyncData.getPlayerId());            blockMap.put(blockKey, blockPlayer);        }else{            blockPlayer.add(playerSyncData.getPlayerId());        }        //玩家所处的分块        playerBlock.put(playerSyncData.getPlayerId(), getBlockKey(playerSyncData));        //玩家看到的人        ConcurrentSkipListSet<Integer> visionPlayers = getVisionPlayers(playerSyncData,null);        playerSee.put(playerSyncData.getPlayerId(), visionPlayers);        //生成看到的玩家的进入帧发给刚上来的玩家        IoSession ioSession = SessionManager.getSessionManager(playerSyncData.getZoneId()).getPlayerSession(playerSyncData.getPlayerId());        for(Integer playerId:visionPlayers){            SceneSyncData positionWithShowInfo = genPositionWithShowInfo(playerId,SceneSyncDataType.SCENE_SYNC_ENTER);            if(positionWithShowInfo!=null){                ioSession.write(genPositionWithShowInfo(playerId,SceneSyncDataType.SCENE_SYNC_ENTER));            }        }        //更新被看到的人        for(Integer playerId :visionPlayers){            ConcurrentSkipListSet<Integer> beSeen = playerSee.getOrDefault(playerId, new ConcurrentSkipListSet<Integer>());            beSeen.add(playerSyncData.getPlayerId());            playerSee.put(playerId, beSeen);        }    }    @Override    public void doOtherWhenRemovePlayer(Integer playerId) {        //从分块上移除        for(ConcurrentSkipListSet<Integer> blockPlayer:blockMap.values()){            blockPlayer.remove(playerId);        }        playerBlock.remove(playerId);        //视野移除        ConcurrentSkipListSet<Integer> seeSet = playerSee.get(playerId);        if(seeSet!=null){            for(Integer seePlayer:seeSet){                if(playerSee.get(seePlayer)!=null){                    playerSee.get(seePlayer).remove(playerId);                }            }        }        playerSee.remove(playerId);    }    @Override    public void doOtherWhenReceiveSyncData(SceneSyncData playerSyncData) {        if(!SceneSyncDataType.SCENE_SYNC_EXIT.equals(playerSyncData.getSceneSyncDataType())){//退出的就不用处理了            int playerId = playerSyncData.getPlayerId();            String newBlockKey = getBlockKey(playerSyncData);            String oldBlockKey = playerBlock.get(playerId);            if(!newBlockKey.equals(oldBlockKey)){//玩家走到其他分块了                //从旧分块上移除                blockMap.get(oldBlockKey).remove(playerId);                //加入新分块                playerBlock.put(playerId, newBlockKey);                ConcurrentSkipListSet<Integer> blockPlayer = blockMap.get(newBlockKey);                if(blockPlayer == null){                    blockPlayer = new ConcurrentSkipListSet<Integer>();                    blockPlayer.add(playerId);                    blockMap.put(newBlockKey, blockPlayer);                }else{                    blockPlayer.add(playerSyncData.getPlayerId());                }                //新旧视野处理                ConcurrentSkipListSet<Integer> newVisionPlayers = getVisionPlayers(playerSyncData,oldBlockKey);                ConcurrentSkipListSet<Integer> oldVisionPlayers = playerSee.get(playerId);                IoSession ioSession = SessionManager.getSessionManager(playerSyncData.getZoneId()).getPlayerSession(playerSyncData.getPlayerId());                //从视野里消失的                for (Integer beSeenPlayer : oldVisionPlayers) {                    if(!newVisionPlayers.contains(beSeenPlayer)){                        SceneSyncData genPositionWithShowInfo = genPositionWithShowInfo(beSeenPlayer,SceneSyncDataType.SCENE_SYNC_LEAVE);                        if(genPositionWithShowInfo!=null){                            ioSession.write(genPositionWithShowInfo);                        }                        playerSee.getOrDefault(beSeenPlayer,new ConcurrentSkipListSet<Integer>()).remove(playerId);                        IoSession beSeenSession = SessionManager.getSessionManager(this.getZoneId()).getPlayerSession(beSeenPlayer);                        if(beSeenSession!=null){                            SceneSyncData positionWithShow = genPositionWithShowInfo(playerId,SceneSyncDataType.SCENE_SYNC_LEAVE);                            if(positionWithShow!=null){                                beSeenSession.write(positionWithShow);                            }                        }else{                            log.error("beSeenSession==null");                        }                    }                }                //新进入视野的                for(Integer beSeenPlayer :newVisionPlayers){                    if(!oldVisionPlayers.contains(beSeenPlayer)){                        SceneSyncData genPositionWithShowInfo = genPositionWithShowInfo(beSeenPlayer,SceneSyncDataType.SCENE_SYNC_APPEAR);                        if(genPositionWithShowInfo!=null){                            ioSession.write(genPositionWithShowInfo);                        }                        ConcurrentSkipListSet<Integer> beSeen = playerSee.getOrDefault(beSeenPlayer, new ConcurrentSkipListSet<Integer>());                        beSeen.add(playerId);                        playerSee.put(beSeenPlayer,beSeen);                        IoSession beSeenSession = SessionManager.getSessionManager(this.getZoneId()).getPlayerSession(beSeenPlayer);                        if(beSeenSession!=null){                            SceneSyncData positionWithShowInfo = genPositionWithShowInfo(playerId,SceneSyncDataType.SCENE_SYNC_APPEAR);                            if(positionWithShowInfo!=null){                                beSeenSession.write(positionWithShowInfo);                            }                        }else{                            log.error("beSeenSession==null");                        }                    }                }                playerSee.put(playerId, newVisionPlayers);            }        }    }    @Override    public void sendSyncData(SceneSyncData playerSyncData) {        //只给看到自己的人发        ConcurrentSkipListSet<Integer> treeSet = playerSee.get(playerSyncData.getPlayerId());        if(treeSet!=null){            for (Integer playerId : treeSet) {                IoSession ioSession = SessionManager.getSessionManager(this.getZoneId()).getPlayerSession(playerId);                if(ioSession!=null){                    ioSession.write(playerSyncData);                }            }        }    }    /**     * 获取坐标所处分块的Key     * @param playerData     * @return     */    public String getBlockKey(SceneSyncData playerSyncData){        float x = playerSyncData.getPosition().getX();        float z = playerSyncData.getPosition().getZ();        return (int)(Math.floor(x/blockSize))+"|"+(int)(Math.floor(z/blockSize));    }    /**     * 获取九宫格视野块的keys     * @param blockKey     * @return     */    public TreeSet<String> getRangeBlockKeys(String blockKey){        TreeSet<String> blockKeys = new TreeSet<String>();        if(blockKey==null||"".equals(blockKey)){            return blockKeys;        }        String[] split = blockKey.split("\\|");        int originX = Integer.valueOf(split[0]);        int originZ = Integer.valueOf(split[1]);        blockKeys.add(originX+"|"+originZ);        blockKeys.add(originX+"|"+(originZ-1));        blockKeys.add(originX+"|"+(originZ-2));        blockKeys.add(originX+"|"+(originZ+1));        blockKeys.add(originX+"|"+(originZ+2));        blockKeys.add((originX+1)+"|"+originZ);        blockKeys.add((originX+1)+"|"+(originZ-1));        blockKeys.add((originX+1)+"|"+(originZ-2));        blockKeys.add((originX+1)+"|"+(originZ+1));        blockKeys.add((originX+1)+"|"+(originZ+2));        blockKeys.add((originX+2)+"|"+originZ);        blockKeys.add((originX+2)+"|"+(originZ-1));        blockKeys.add((originX+2)+"|"+(originZ-2));        blockKeys.add((originX+2)+"|"+(originZ+1));        blockKeys.add((originX+2)+"|"+(originZ+2));        blockKeys.add((originX-1)+"|"+originZ);        blockKeys.add((originX-1)+"|"+(originZ-1));        blockKeys.add((originX-1)+"|"+(originZ-2));        blockKeys.add((originX-1)+"|"+(originZ+1));        blockKeys.add((originX-1)+"|"+(originZ+2));        blockKeys.add((originX-2)+"|"+originZ);        blockKeys.add((originX-2)+"|"+(originZ-1));        blockKeys.add((originX-2)+"|"+(originZ-2));        blockKeys.add((originX-2)+"|"+(originZ+1));        blockKeys.add((originX-2)+"|"+(originZ+2));        return blockKeys;    }    /**     * 获取玩家视野范围内的其他玩家     * @param playerData     * @param oldBlock     * @return     */    private ConcurrentSkipListSet<Integer> getVisionPlayers(SceneSyncData playerSyncData,String oldBlock){        ConcurrentSkipListSet<Integer> visionPlayers = new ConcurrentSkipListSet<Integer>();        TreeSet<String> oldBlockKeys = getRangeBlockKeys(oldBlock);        TreeSet<String> newBlockKeys = getRangeBlockKeys(getBlockKey(playerSyncData));        ConcurrentSkipListSet<Integer> oldSee = playerSee.getOrDefault(playerSyncData.getPlayerId(), new ConcurrentSkipListSet<Integer>());        for (Integer playerId : oldSee) {            if(newBlockKeys.contains(playerBlock.get(playerId))){                visionPlayers.add(playerId);            }        }        oldBlockKeys.retainAll(newBlockKeys);//任然在视野内的格子        newBlockKeys.removeAll(oldBlockKeys);//新增在视野内的格子        int everyBlockNeedNum = (seeMax-visionPlayers.size())/newBlockKeys.size();//初始计算每个Block需要的玩家数        int lackNum = 0;//上个格子人没满,下个格子来补        ConcurrentSkipListSet<String> sufficientBlock = new ConcurrentSkipListSet<String>();//人足够多的格子,最后要是还不够,就用这些格子补人        for(String blockKey:newBlockKeys){            ConcurrentSkipListSet<Integer> blockPlayer = blockMap.get(blockKey);            if(blockPlayer!=null&&!blockPlayer.isEmpty()){                int addNum = 0;                for(Integer playerId : blockPlayer){                    if(playerId == playerSyncData.getPlayerId()){//自己                        continue;                    }                    if(addNum<everyBlockNeedNum+lackNum){                        if(playerSee.getOrDefault(playerId,new ConcurrentSkipListSet<Integer>()).size()<seeMax){//玩家视野还没达到上限才能加入                            visionPlayers.add(playerId);                            addNum++;                        }                    }else{                        break;                    }                }                if(addNum >= (everyBlockNeedNum+lackNum)){                    lackNum = 0;                    sufficientBlock.add(blockKey);                }else{                    lackNum = everyBlockNeedNum+lackNum - addNum;                }            }        }        if(visionPlayers.size()<seeMax){//缺人            for(String blockKey:sufficientBlock){                ConcurrentSkipListSet<Integer> blockPlayer = blockMap.get(blockKey);                for (Integer playerId : blockPlayer) {                    if(visionPlayers.size()>=seeMax){                        break;                    }else{                        if(playerId == playerSyncData.getPlayerId()){//自己                            continue;                        }                        if(playerSee.getOrDefault(playerId,new ConcurrentSkipListSet<Integer>()).size()<seeMax){                            visionPlayers.add(playerId);                        }                    }                }                if(visionPlayers.size()>=seeMax){                    break;                }            }        }        visionPlayers.remove(playerSyncData.getPlayerId());//再次确保移除自己        return visionPlayers;    }}

最后场景管理类

/** *场景管理类 * @author lhx *  */public class SceneManager {    private static LoggerWraper log = LoggerWraper.getLogger("SceneManager");    private static final ConcurrentHashMap<Integer, SceneManager> smMap = new ConcurrentHashMap<>();    private int zoneId = 0;    /**     * map<场景key,场景>     */    private ConcurrentHashMap<String, AbstractScene> sceneMap = new ConcurrentHashMap<String, AbstractScene>();    /**     * map<玩家Id,玩家所处场景key>     */    private ConcurrentHashMap<Integer,String> playerMap = new ConcurrentHashMap<Integer,String>();    private SceneManager(int zoneIdTmp) {        this.zoneId = zoneIdTmp;    }    public static SceneManager getSceneManager(int zoneId) {        SceneManager psm = smMap.get(zoneId);        if (psm == null) {            psm = new SceneManager(zoneId);            smMap.put(zoneId, psm);        }        return psm;    }    /**     * 生成场景KEY     * @param playerSyncData     * @return     */    public static String genSceneKey(int zoneId,SceneType sceneType,int sceneId){        return zoneId+"|"+sceneType+"|"+sceneId;    }    /**     * 放入同步数据到Manager     * @param ioSession     * @param playerSyncData     */    public void addSyncDataToManager(IoSession ioSession,SceneSyncData playerSyncData){        int playerId = playerSyncData.getPlayerId();        String dataSceneKey = genSceneKey(playerSyncData.getZoneId(), playerSyncData.getSceneType(), playerSyncData.getSceneId());        boolean needJoinScene = false;//需要加入场景        if(isInManger(playerId)){//已经在场景管理器中            if(!playerMap.get(playerId).equals(dataSceneKey)){//不是同一个场景了                //从旧场景中移除                sceneMap.get(playerMap.get(playerId)).removePlayer(playerId);                needJoinScene = true;            }else{                //放入同步数据到对应场景                sceneMap.get(playerMap.get(playerId)).receiveSyncData(playerSyncData);            }        }else{//没在场景管理器中            needJoinScene = true;        }        if(needJoinScene){            AbstractScene scene = sceneMap.get(dataSceneKey);            if(scene == null){                scene = genScene(playerSyncData);                sceneMap.put(scene.getSceneKey(), scene);            }            scene.addPlayer(playerSyncData);            playerMap.put(playerId,scene.getSceneKey());        }    }    /**     * 从Manager移除玩家     * @param playerId     */    public void removePlayerFromManager(int playerId){        //从玩家所在的场景移除玩家        String sceneKey = playerMap.get(playerId);        if(sceneKey!=null){            sceneMap.get(sceneKey).removePlayer(playerId);        }        //从玩家map中移除        playerMap.remove(playerId);    }    /**     * 玩家是否已经在manager里面     *      * @param playerId     * @return     */    private boolean isInManger(int playerId) {        return playerMap.containsKey(playerId);    }    private AbstractScene genScene(SceneSyncData playerSyncData){        CommonConfManager.doReloadAllConf();        int blockSize = CommonConfManager.getInt("scene_sync_block_size", zoneId, 30);        if(playerSyncData.getSceneType().equals(SceneType.MAIN_CITY)){            int seeMax = CommonConfManager.getInt("scene_sync_see_max", zoneId, 30);            return new BlockScene(playerSyncData.getZoneId(),playerSyncData.getSceneType(), playerSyncData.getSceneId(), blockSize, seeMax);        }else if(playerSyncData.getSceneType().equals(SceneType.GROUP)){            return new CompleteScene(playerSyncData.getZoneId(),playerSyncData.getSceneType(), playerSyncData.getSceneId());        }        return null;    }}

当然还有网络层链接的管理,贴一个handle类

public class SceneServerSessionHandler extends IoHandlerAdapter {    private static LoggerWraper logger = LoggerWraper.getLogger("SceneServerSessionHandler");    public SceneServerSessionHandler() {    }    public void messageReceived(final IoSession session, Object message) throws Exception {        logger.info(Thread.currentThread() + "\t|messageReceived == " + session);        SceneSyncData playerSyncData = (SceneSyncData) message;        Integer sessionPlayerId = (Integer)session.getAttribute(SessionKey.PLAYER_ID);        Integer sessionZoneId = (Integer)session.getAttribute(SessionKey.ZONE_ID);        if(sessionPlayerId==null||sessionZoneId==null){            session.setAttribute(SessionKey.PLAYER_ID, playerSyncData.getPlayerId());            session.setAttribute(SessionKey.ZONE_ID, playerSyncData.getZoneId());        }        SessionManager sessionManager = SessionManager.getSessionManager(playerSyncData.getZoneId());        SceneManager sceneManager = SceneManager.getSceneManager(playerSyncData.getZoneId());        IoSession oldSession = sessionManager.getPlayerSession(playerSyncData.getPlayerId());        if(oldSession!=null&&oldSession.getId()!=session.getId()){            sceneManager.removePlayerFromManager(playerSyncData.getPlayerId());        }        sessionManager.setPlayerSession(playerSyncData.getPlayerId(), session);        sceneManager.addSyncDataToManager(session, playerSyncData);    }    public void exceptionCaught(IoSession session, Throwable cause) throws Exception {        logger.error("exceptionCaught|"+session,cause);        session.closeOnFlush();    }    @Override    public void inputClosed(IoSession session) throws Exception {        logger.debug("inputClosed|{}", session);        super.inputClosed(session);    }    @Override    public void messageSent(IoSession session, Object message) throws Exception {        super.messageSent(session, message);    }    @Override    public void sessionClosed(IoSession session) throws Exception {        logger.debug("sessionClosed|{}", session);        Integer sessionPlayerId = (Integer)session.getAttribute(SessionKey.PLAYER_ID);        Integer sessionZoneId = (Integer)session.getAttribute(SessionKey.ZONE_ID);        if(sessionPlayerId!=null&&sessionZoneId!=null){            IoSession oldSession = SessionManager.getSessionManager(sessionZoneId).getPlayerSession(sessionPlayerId);            if(oldSession!=null&&session.getId()==oldSession.getId()){                SceneManager.getSceneManager(sessionZoneId).removePlayerFromManager(sessionPlayerId);                SessionManager.getSessionManager(sessionZoneId).removePlayerSesison(sessionPlayerId);            }        }        super.sessionClosed(session);    }    @Override    public void sessionCreated(IoSession session) throws Exception {        logger.debug("sessionCreated|{}", session);        super.sessionCreated(session);    }    @Override    public void sessionIdle(IoSession session, IdleStatus status) throws Exception {        super.sessionIdle(session, status);    }    @Override    public void sessionOpened(IoSession session) throws Exception {        logger.debug("sessionOpened|{}", session);        super.sessionOpened(session);    }}

最后一个玩家session管理类

public class SessionManager {    private static LoggerWraper log = LoggerWraper.getLogger("SessionManager");    private static final ConcurrentHashMap<Integer, SessionManager> smMap = new ConcurrentHashMap<>();    private int zoneId = 0;    /**     * 玩家session     */    private ConcurrentHashMap<Integer,IoSession> playerSessions = new ConcurrentHashMap<Integer,IoSession>();    private SessionManager(int zoneIdTmp) {        this.zoneId = zoneIdTmp;    }    public static SessionManager getSessionManager(int zoneId) {        SessionManager psm = smMap.get(zoneId);        if (psm == null) {            psm = new SessionManager(zoneId);            smMap.put(zoneId, psm);        }        return psm;    }    public IoSession getPlayerSession(int playerId){        IoSession ioSession = this.playerSessions.get(playerId);        if(ioSession==null){            log.warn("getPlayerSession==null");        }        return ioSession;    }    public void setPlayerSession(int playerId,IoSession session){        this.playerSessions.put(playerId, session);    }    public void removePlayerSesison(int playerId){        this.playerSessions.remove(playerId);    }}

看了上面这些应该了解并且能实现一个简单的场景状态同步了。下一篇文章给大家讲解相对复杂的帧同步原理