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

简介: 使用Scoket TCP和Protobuf通信协议实现多人在线。

🎈 Protobuf

🔸 简介

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

🔸 使用

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

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

🎯 编写.proto文件

语法规则如下:

  • 使用message定义类,相当于c#中的class
  • 使用三种字段修饰符修饰字段:

    • required 表示是一个必选字段,必须初始化;
    • optional 表示是一个可选字段,可以不进行初始化;
    • repeated 表示该字段可以包含多个元素,可以看作是在传递一个数组的值;
  • 字段类型,与C#的对应关系如下:
proto c# 备注
bool bool 布尔类型
string string 字符串类型
double double 64位浮点数
float float 32位浮点数
int32 int 32位整数
uint32 uint 无符号32位整数
int64 long 64位整数
uint64 ulong 无符号64位整数
sint32 int 编码时比通常的int32高效
sint64 long 编码时比通常的int64高效
fixed32 uint 无符号32位整数
fixed64 ulong 无符号64位整数
sfixed32 int 总是4个字节
sfixed64 long 总是8个字节
bytes ByteString 字节数据
  • 字段标识号
每个字段都有唯一的 标识号,这些标识是用来在消息的二进制格式中识别各个字段的,使用后便 不能更改。[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;
}

🎯 编译.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框架开源地址: https://github.com/136512892/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();
Timer模块为 SKFramework框架中的计时类工具,也可以通过框架中的 Packaga Manager下载, EverySeconds表示每隔多少秒执行一次回调函数,这里我们将 internal设为 0.025,也就是1秒将发送40次数据,可适当调整。

🔸 客户端接收Avatar数据

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

//抛出消息
Messenger.Publish(msg.name, msg.content);
Messenger则是 SKFramework框架中的事件系统, Publish表示发布消息,第一个参数表示消息的 主题,第二个参数表示消息的 内容

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

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

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);
}

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

using UnityEngine;
using UnityEngine.UI;

using SK.Framework;

namespace Metaverse
{
    /// <summary>
    /// Avatar实例
    /// </summary>
    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;

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="ap"></param>
        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;
        }

        /// <summary>
        /// 接收数据
        /// </summary>
        /// <param name="ap"></param>
        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);
        }
    }
}
Face2CameraSKFramework中的一个组件工具,其功能是使物体始终 朝向相机

PackageManager - Face2Camera
Face2Camera
同步效果:

Avatar同步

目录
相关文章
|
5月前
|
JSON C# 图形学
【Unity 3D】利用C#、Unity和Socket实现简单的在线聊天室工具(附源码 简单易懂)
【Unity 3D】利用C#、Unity和Socket实现简单的在线聊天室工具(附源码 简单易懂)
53 0
|
5月前
|
网络协议 Unix Linux
【Unity 3D】C#中Socket及TCP三次握手与四次挥手详解(超详细 图文解释)
【Unity 3D】C#中Socket及TCP三次握手与四次挥手详解(超详细 图文解释)
60 0
|
Web App开发 开发工具 图形学
|
网络协议 图形学
Unity【Socket TCP】- 服务端与客户端通讯的简单示例
Unity【Socket TCP】- 服务端与客户端通讯的简单示例
516 0
Unity【Socket TCP】- 服务端与客户端通讯的简单示例
|
前端开发 Linux 开发工具
Unity Metaverse(四)、接入环信IM SDK 实现用户登录注册
Unity接入环信IM SDK,实现用户登录与注册功能。
197 1
Unity Metaverse(四)、接入环信IM SDK 实现用户登录注册
|
图形学
Unity Metaverse(二)、Mixamo & Animator 混合树与动画融合
Blend Tree混合树的使用与动画融合的实现
219 1
Unity Metaverse(二)、Mixamo & Animator 混合树与动画融合
|
开发工具 图形学
|
5月前
|
开发框架 Java C#
【Unity逆向】玩游戏遇到的“飞天锁血”是怎么实现的?
【Unity逆向】玩游戏遇到的“飞天锁血”是怎么实现的?
93 0