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

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

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

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

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

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

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

首先下面是用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(playerSyn
  • 4
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Unity的状态同步是指在多个客户端之间保持游戏状态的一致性。在多人游戏中,每个客户端都需要知道其他玩家的位置、动作等信息,以便正确地显示和处理游戏场景。 Unity提供了几种状态同步的方法,其中最常用的是基于网络的状态同步。以下是一些常见的状态同步技术: 1. 客户端-服务器模型:这是最常见的状态同步方法。在这种模型中,一个服务器负责接收和处理所有客户端的输入,并将结果广播给所有其他客户端。客户端通过发送自己的输入到服务器,并接收其他玩家的状态更新来保持同步。 2. 帧同步:在帧同步中,所有客户端按照相同的时间间隔进行更新。每个客户端在每一帧中发送自己的输入到服务器,并接收其他玩家的输入。服务器根据接收到的输入计算游戏状态,并将结果广播给所有客户端,以便它们在下一帧中进行更新。 3. 插值和预测:为了减少网络延迟对游戏体验的影响,可以使用插值和预测技术。插值是指在两个已知状态之间进行平滑插值,以减少状态更新的抖动。预测是指在等待服务器响应时,客户端根据自己的输入预测其他玩家的状态,以提前显示和处理游戏场景。 4. 状态压缩:为了减少网络带宽的使用,可以对状态进行压缩。例如,可以只传输位置和方向信息,而不是每一帧都传输完整的游戏状态

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值