简单状态同步在Unity使用的学习小结

222000305陈宇焜 学生 2023-06-05 17:56:09

目录

  • 一、技术概述
  • 二、技术详述
  • 客户端
  • 服务端
  • 三、学习问题总结与解决
  • 服务端如何管理角色不同数据?
  • 对优化的思考
  • 四、总结
  • 五、参考资料

一、技术概述

多人在线游戏需要同步玩家数据,许多公司都拥有至少一款此类游戏。对于状态同步,首先需要掌握两层的设计架构思想以及计算机网络的原理,其难点在于:1. 网络延迟。2. 数据量.3. 安全性。
状态同步和帧同步的不同这里不做额外说明,可在参考资料中了解。


二、技术详述

这里不涉及客户端的本地服务器和服务端的实现,只涉及对状态同步的学习小结,包括角色的添加/删除/更新,并省略Debug与校验等工作。

客户端

为了实现和使用状态同步,首先要应用ECS架构对实体进行设计,将数据与操作分离,并维护一个实体的管理器,方便对从服务端接收的实体数据进行更新,所学的客户端的一种实现如下:

img

  • 在图中,客户端的服务监听服务端发送的实体数据,根据类型可分为实体的生成与销毁、实体更新两类操作。
    • 当需要 增加 角色时,首先更新CharacterManager中维护的角色定义数据Dictionary(策划所配的表),
      CharacterManager.AddCharacter():
          Character character = new Character(cha);
            this.Characters[cha.Id] = character;
      
      调用EntityManager添加新的游戏实体,
      CharacterManager.AddCharacter():
          EntityManager.Instance.AddEntity(character);
      
      同时,需要在地图中生成对应的角色,通过GameObjectManager统一管理所有的游戏对象并根据预制体生成。由于层次结构的设计,Manager层只关心同层的其他Manager,而不直接与GameObjectManager(处于实体层)交互,通过事件进行解耦。
      CharacterManager.AddCharacter():
              if(OnCharacterEnter!=null)
              {
                  // 定义的公共委托,GameObjectManager注册其生成方法
                  OnCharacterEnter(character);
              }
      
      这里要注意的是,网络中传输整个表数据会浪费带宽,因此传输的时表内容的Id属性,客户端拥有角色定义,根据Id使用不同的角色信息,但我认为会遭到数据篡改,因此可能需要进行额外的安全性校验。
      另外,玩家多次进入与离开可能会产生多次游戏对象的创建/删除工作,可以使用对象池优化。
      具体何时调用增加/删除/更新操作,此处不做探讨。删除的思路和添加同理。
    • 当只需要更新数据时,则不需要通过CharacterManager,可直接调用EntityManager,进而同步对应的游戏对象,因为不需要增加与删除操作,同时也与实体层进行解耦,具体流程和上述过程类似。
      MapService:
      private void OnMapEntitySync(object sender,MapEntitySyncResponse response)
          {
              foreach(var entity in response.entitySyncs)
              {
                  Managers.EntityManager.Instance.OnEntitySync(entity);
              }
          }
      
      EntityManager.OnEntitySync:
                      if (notifiers.ContainsKey((int)data.Id))
                      {
                          // 基于接口的观察者模式,调用字典中注册的处理者来更新数据
                          notifiers[entity.entityId].OnEntityChanged(entity);
                          notifiers[entity.entityId].OnEntityEvent(data.Event);
                      }
      

服务端

img

  • 服务端的同步工作,是保存客户端发来的状态信息并转发,所以实际操作和客户端类似,只不过不需要渲染出真正的游戏对象,所以只需要维护一份角色数据,在角色刚进入游戏时即可在CharacterManager中创建,只需要更新状态信息,并需要向其他客户端发送所更新的信息。

    • 当收到客户端发送的添加角色请求,分为进入游戏进入地图两个阶段,进入游戏时和客户端一样,交给CharacterManager-》EntityManager生成新的角色数据,并返回给客户端所生成的角色数据,保证一定的安全性,这里是独立的。

    • 而进入游戏后,进入地图时,又会让MapService接收到信息,将character信息交给MapManager处理:
      MapService:

             MapManager.Instance[request.mapId].CharacterEnter(sender, character);
      

      Map:

              // 向Response添加需要增加的角色
              message.Response.mapCharacterEnter.Characters.Add(character.Info);
              // 添加地图中的其他角色并向其他玩家告知角色增加
              foreach (var kv in this.MapCharacters)
              {
      
                  // 添加地图中的其他角色
                  message.Response.mapCharacterEnter.Characters.Add(kv.Value.character.Info);
                  // 向这个角色告知角色添加至地图
                  this.SendCharacterEnterMap(kv.Value.connection, character.Info);
              }
      
  • 当收到客户端发送的状态更新请求:

    • MapService回调更新请求的角色
      MapService:
          void OnMapEntitySync(NetConnection<NetSession> sender, MapEntitySyncRequest request)
          {
              Character character=sender.Session.Character;
              // 更新对应地图Map中的信息
              MapManager.Instance[character.Info.mapId].UpdateEntity(request.entitySync);
          }
      
      Map:
          internal void UpdateEntity(NEntitySync entitySync)
          {
              // 遍历当前地图的角色
              foreach (var character in this.MapCharacters)
              {  
                  // 如果是需要更新的角色则更新
                  if (character.Value.character.entityId == entitySync.Id)
                  {
                      // 更新位置、方向等
                      ……
                  }
                  else
                  {
                      // 如果是其他客户端的角色,则直接发送更新消息
                      MapService.Instance.SendEntityUpdate(character.Value.connection, entitySync);
                  }
              }
          }
      

三、学习问题总结与解决

实现的同步功能比较基础,未进行难点的实现,故没有遇到较大困难。

服务端如何管理角色不同数据?

由于客户端所生成的角色定义是服务端传递的,故在角色刚进入游戏时,会依次发送两次请求

  • 进入游戏请求
    • 在这个请求处理时,服务器接收客户端传递的角色数据id,并在服务端维护一份对应的数据,而角色离开时又会从中移除,这份数据保存了角色的数据库信息、存档、道具等。
  • 进入地图请求
    • 根据目前所见,进入地图时,服务端将角色的同步数据交给另一条数据流处理,这条数据流将频繁地更新数据,所以独立于其他数据进行处理。

对优化的思考

  • 优化网络传输
    • 使用算法优化网络的传输效率,如对消息进行序列化与反序列化(protobuf)、减少字符串的传输等。
    • 客户端实现状态的预测,根据上次状态和本次状态预测分析下一次的状态来减少延迟(所以怎么预测?)

四、总结

状态同步需要同步客户端的状态数据,如位置、状态、伤害等,其主要逻辑在服务端,但也有些状态同步基于客户端,比如小型的联机游戏。
状态同步虽然只是网络游戏中的一种同步方式,想要完全掌握还是很复杂,特别是要考虑网络的各种情况,需要一定的经验。而其他的同步方式比如帧同步、状态帧同步,似乎水更是深。作为一名客户端程序,也要明白其大概模型才能更好的和后端人员沟通。

五、参考资料

状态同步与帧同步
谷歌Protobuf通信详解
如有问题,欢迎指正!

...全文
239 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

686

社区成员

发帖
与我相关
我的任务
社区描述
2023年福州大学软件工程实践课程W班的教学社区
软件工程团队开发软件构建 高校 福建省·福州市
社区管理员
  • FZU_SE_teacherW
  • aboutazhang
  • 郭渊伟
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧