游戏 场景同步 实现(状态同步)
来源:互联网 发布:网络机柜搬迁流程 编辑:程序博客网 时间: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); }}
看了上面这些应该了解并且能实现一个简单的场景状态同步了。下一篇文章给大家讲解相对复杂的帧同步原理
- 游戏 场景同步 实现(状态同步)
- 移动游戏 状态同步 帧同步
- 游戏后台状态同步与帧同步
- 游戏 帧同步 实现
- Kinect+Unity 实现虚拟人物动作同步(羽毛球场景)
- 状态同步模式下的游戏掉线重连
- 状态同步模式下的游戏掉线重连
- 帧同步和状态同步(一)
- 游戏服务器之场景和网关之间的消息同步
- (转)游戏同步问题
- 线程同步的场景
- zookeeper 场景:配置文件同步
- 状态同步与帧同步
- 状态同步与帧同步
- 帧同步和状态同步
- 状态同步与帧同步
- 帧同步和状态同步
- 游戏中帧同步的实现
- Jstorm概述
- 个人学习Mysql中的一些笔记
- SSS1629|音频设计方案|台湾鑫创一级代理商
- Kafka深度解析
- HTML5+Canvas手机拍摄,本地压缩上传图片,案例实测报告。
- 游戏 场景同步 实现(状态同步)
- 关于const 使用
- Windows Server 2008 R2忘记管理员密码后的解决方法
- Sikulix工具类
- TiledDynamicRESTLayer上获取属性信息
- [OS] Shell
- 【笔记】表插入排序
- 复选框回显
- redis的Sentinel模式(哨兵模式)的windows安装