• Unity Metaverse(三)、Protobuf & Socket 实现多人在线



    🎈 Protobuf

    🔸 简介

    Google Protocol Buffer(简称Protobuf)是Google公司一种轻便高效的结构化数据存储格式,可作为数据的序列化工具,经常被用于通讯协议,与JsonXML相比,Protobuf的优点在于性能更高,它更小、更快,以高效的二进制方式存储,生成的二进制消息非常紧凑,因此在网络上传输的字节数更少。

    🔸 使用

    我们使用Protobuf作为通信协议,创建一个协议类需要经过以下步骤:

    • 根据语法规则编写.proto文件;
    • 通过编译工具protoc.exe将.proto文件编译成.cs文件;

    🎯 编写.proto文件

    语法规则如下:

    • 使用message定义类,相当于c#中的class
    • 使用三种字段修饰符修饰字段:
      • required 表示是一个必选字段,必须初始化;
      • optional 表示是一个可选字段,可以不进行初始化;
      • repeated 表示该字段可以包含多个元素,可以看作是在传递一个数组的值;
    • 字段类型,与C#的对应关系如下:
    protoc#备注
    boolbool布尔类型
    stringstring字符串类型
    doubledouble64位浮点数
    floatfloat32位浮点数
    int32int32位整数
    uint32uint无符号32位整数
    int64long64位整数
    uint64ulong无符号64位整数
    sint32int编码时比通常的int32高效
    sint64long编码时比通常的int64高效
    fixed32uint无符号32位整数
    fixed64ulong无符号64位整数
    sfixed32int总是4个字节
    sfixed64long总是8个字节
    bytesByteString字节数据
    • 字段标识号

    每个字段都有唯一的标识号,这些标识是用来在消息的二进制格式中识别各个字段的,使用后便不能更改。[1,15]之内的标识号在编码的时候会占用1字节。[16,2047]之内的标识号则占用2字节,所以应该为那些频繁出现的消息元素保留[1,15]之内的标识号。
    :不可以使用[19000-19999]标识号,protobuf协议实现中对这些进行了预留。

    例如,我们需要定义AvatarProperty协议类来对Avatar人物的属性进行通讯,proto文件编写如下

    message AvatarProperty
    {
    	required string userId = 1;
    	required int32 avatarId = 2;
    	required string name = 3;
    	required float posX = 4;
    	required float posY = 5;
    	required float posZ = 6;
    	required float rotX = 7;
    	required float rotY = 8;
    	required float rotZ = 9;
    	required float speed = 10;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    🎯 编译.proto文件

    • 运行cmdcd 打开protoc.exe所在路径

    cd
    protoc.exe所在路径

    • 输入编译命令protoc -I=./ --csharp_out=./ AvatarProperty.proto

    编译命令
    编译成功后,可以看到AvatarProperty.cs文件已经生成到目录下,将其导入到Unity中即可。

    AvatarProperty.cs

    AvatarProperty.cs由protobuf的编译工具生成,导入到Unity后便不可修改

    🎈 Socket

    我们通过Socket TCP实现网络通讯,使用了我的小型开发框架SKFramework中的网络通讯模块:

    SKFramework PackageManager

    SKFramework框架开源地址:Github - SKFramework

    🔸 客户端发送Avatar数据

    //每间隔一定时长发送一次Avatar数据
    timer = Timer.EverySeconds(interval, () =>
    {
        if (GameServer != null)
        {
            var ap = new AvatarProperty()
            {
                UserId = UserId,
                PosX = AvatarController.transform.position.x,
                PosY = AvatarController.transform.position.y,
                PosZ = AvatarController.transform.position.z,
                RotX = AvatarController.transform.eulerAngles.x,
                RotY = AvatarController.transform.eulerAngles.y,
                RotZ = AvatarController.transform.eulerAngles.z,
                Speed = AvatarController.Instance.Speed,
            };
            //发送数据
            GameServer.Send(ap);
        }
    });
    timer.Launch();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    Timer模块为SKFramework框架中的计时类工具,也可以通过框架中的Packaga Manager下载,EverySeconds表示每隔多少秒执行一次回调函数,这里我们将internal设为0.025,也就是1秒将发送40次数据,可适当调整。

    🔸 客户端接收Avatar数据

    客户端接收到服务端的消息后,会将消息内容通过事件系统进行抛出:

    //抛出消息
    Messenger.Publish(msg.name, msg.content);
    
    • 1
    • 2

    Messenger则是SKFramework框架中的事件系统,Publish表示发布消息,第一个参数表示消息的主题,第二个参数表示消息的内容

    订阅主题为AvatarProperty的消息,当该主题的消息发布后,订阅事件OnAvatarPropertyMsgEvent将会被执行。

    //订阅AvatarProperty消息
    Messenger.Subscribe<ByteString>(typeof(AvatarProperty).Name, OnAvatarPropertyMsgEvent);
    
    • 1
    • 2

    OnAvatarPropertyMsgEvent事件中,根据消息的用户ID判断相应的Avatar人物实例是否存在,如果不存在则进行创建并初始化:

    private void OnAvatarPropertyMsgEvent(ByteString bs)
    {
        //反序列化
        var ap = AvatarProperty.Parser.ParseFrom(bs);
        if (!avatarDic.ContainsKey(ap.UserId))
        {
            //第一次接收该Avatar数据 首先创建Avatar人物
            var instance = Object.Instantiate(Resources.Load<AvatarInstance>(typeof(AvatarInstance).Name));
            //存入Avatar字典
            avatarDic.Add(ap.UserId, instance);
            //初始化
            instance.Init(ap);
        }
        //目标Avatar
        avatarDic[ap.UserId].Set(ap);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    AvatarInstance接收到数据后,使用插值方式计算坐标、旋转,以及同步Animator动画信息:

    using UnityEngine;
    using UnityEngine.UI;
    
    using SK.Framework;
    
    namespace Metaverse
    {
        /// 
        /// Avatar实例
        /// 
        public class AvatarInstance : MonoBehaviour
        {
            public string UserId { get; private set; }
    
            [SerializeField] private Animator animator;
            [SerializeField] private Canvas worldCanvas;
            private Vector3 targetPos;
            private Vector3 targetRot;
            private const float lerpSpeed = 5f;
    
            /// 
            /// 初始化
            /// 
            /// 
            public void Init(AvatarProperty ap)
            {
                UserId = ap.UserId;
                Camera mainCamera = Camera.main != null ? Camera.main : FindObjectOfType<Camera>();
                worldCanvas.worldCamera = mainCamera;
                //挂载Face2Camera组件 使其始终朝向相机
                worldCanvas.GetComponent<Face2Camera>().Set(mainCamera, false, false);
                worldCanvas.GetComponentInChildren<Text>().text = UserId;
            }
    
            /// 
            /// 接收数据
            /// 
            /// 
            public void Set(AvatarProperty ap)
            {
                targetPos = new Vector3(ap.PosX, ap.PosY, ap.PosZ);
                targetRot = new Vector3(ap.RotX, ap.RotY, ap.RotZ);
                animator.SetFloat("Speed", ap.Speed);
            }
    
            private void Update()
            {
                //插值运算
                transform.position = Vector3.Lerp(transform.position, targetPos, Time.deltaTime * lerpSpeed);
                transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.Euler(targetRot), lerpSpeed);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    Face2CameraSKFramework中的一个组件工具,其功能是使物体始终朝向相机

    PackageManager - Face2Camera
    Face2Camera
    同步效果:

    Avatar同步

  • 相关阅读:
    SQL教育行业案例:学员续费如何分析?(case when、窗口函数)
    Python学习小组课程P3-Python爬虫(1)HTML与Json解析
    一个Springboot配置顺序问题,让我直接回滚代码了
    2023年中国临床信息系统市场规模及细分市场结构分析[图]
    扩散模型:DDPM代码的学习(基于minist数据集)
    Nmap安装和使用详解
    python基础学习(自学案例)
    CSP模拟52联测14 A.长春花
    知识点2--Docker的安装
    21条最佳实践,全面保障 GitHub 使用安全
  • 原文地址:https://blog.csdn.net/qq_42139931/article/details/126043704