前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Unity Metaverse(三)、Protobuf & Socket 实现多人在线

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

作者头像
CoderZ
发布2022-08-29 17:22:26
9760
发布2022-08-29 17:22:26
举报

🎈 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文件编写如下

代码语言:javascript
复制
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框架开源地址:Github - SKFramework[1]

🔸 客户端发送Avatar数据

代码语言:javascript
复制
//每间隔一定时长发送一次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数据

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

代码语言:javascript
复制
//抛出消息
Messenger.Publish(msg.name, msg.content);

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

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

代码语言:javascript
复制
//订阅AvatarProperty消息
Messenger.Subscribe<ByteString>(typeof(AvatarProperty).Name, OnAvatarPropertyMsgEvent);

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

代码语言:javascript
复制
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动画信息:

代码语言:javascript
复制
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同步

References

[1] Github - SKFramework: https://github.com/136512892/SKFramework

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-07-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 当代野生程序猿 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 🎈 Protobuf
    • 🔸 简介
      • 🔸 使用
        • 🎯 编写.proto文件
        • 🎯 编译.proto文件
    • 🎈 Socket
      • 🔸 客户端发送Avatar数据
        • 🔸 客户端接收Avatar数据
          • References
      相关产品与服务
      数据保险箱
      数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档