开发者社区> harlanc> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

实现一个简易的Unity网络同步引擎Netgo

简介: 实现一个简易的Unity网络同步引擎Netgo 目前GOLANG有大行其道的趋势,尤其是在网络编程方面。因为和c/c++比较起来,虽然GC占用了一部分机器性能,但是出错概率小了,开发效率大大提升,而且应用其原生支持的协程很容易就能开发出高并发的服务端程序。
+关注继续查看

实现一个简易的Unity网络同步引擎Netgo

目前GOLANG有大行其道的趋势,尤其是在网络编程方面。因为和c/c++比较起来,虽然GC占用了一部分机器性能,但是出错概率小了,开发效率大大提升,而且应用其原生支持的协程很容易就能开发出高并发的服务端程序。笔者接触VR行业两年有余,接触了一些商业unity网络引擎,总觉的用的东西都落伍了,于是自己写了一个简单的引擎。目前实现了的基本功能:

  • 支持房间概念。
  • 支持灵活的数据同步方式,包括帧同步和RPC。
  • 支持自定义事件的发送。

也实现了一个简单的demo,同步效果见下图,后面会有更详细的介绍。

项目地址:https://github.com/harlanc/netgo-unity-client
下面是一个简单的项目复盘。

数据通信格式

数据通信格式的定义是整个项目的基石。我们这里的客户端和服务端是跨平台,跨语言通信。因此要定义一种语言无关,平台无关并且简单易用,高效不费流量的数据格式。这里我们选用了Google的 Protobuf,详细介绍参考这篇帖子

Protobuf的C#代码库有两种选择,一种是protobuf-net,一种是protobuf-csharp-port,前者的接口书写更加符合C# 语法规范,会让人看起来更舒服一些。如果需要跨平台的话,推荐使用后者,因为不同语言的接口书写比较类似,开发起来会更容易一些。看看原作者的回复

定义proto文件

如何使用protobuf呢,首先要书写proto文件,定义自己的结构化数据,在netgo中,下面是netgo中定义的消息体的一部分:

enum CacheOptions{

AddToRoomCache = 0;
RemoveFromRoomCache = 1;
}

message NGVector3{

    float x = 1;
    float y = 2;
    float z = 3;
}

message NGQuaternion{

    float x = 1;
    float y = 2;
    float z = 3;
    float w = 4;
}

message NGColor{

    float r = 1;
    float g = 2;
    float b = 3;
    float a = 4;
}

完整定义参考

生成c#和golang API接口文件

更新好命名空间后,执行下面的命令生成API文件:

  • golang

    protoc --go_out=. *.proto

  • c#

    protoc --csharp_out=. *.proto

服务端网络模型

一个Unity网络同步引擎的实现包括服务端和客户端两部分。Nego 是Unity网络同步引擎的服务端,使用golang实现,充分利用了它的原生协程来实现高并发。其网络模型基于gotcp来实现。

参考上图,netgo会为每个socket链接建立一个协程,一个socket协程内部建立三个协程:

  • ReadLoop 用于从网络端读取数据并放入Channel中。
  • HandleLoop 用于解析应用层数据并完成相应处理,并将处理后的数据通过Channel发送给WriteLoop。
  • WriteLoop 负责将处理结果forward给其它客户端或者response给本客户端。

参考代码:

func (c *Conn) Do() {
    if !c.srv.callback.OnConnect(c) {
    return
}

asyncDo(c.handleLoop, c.srv.waitGroup)
asyncDo(c.readLoop, c.srv.waitGroup)
asyncDo(c.writeLoop, c.srv.waitGroup)
}

客户端代码结构

写API基本上是面向用户编程,笔者以为,清晰的代码结构,好的命名方式能省掉大部分注释,代码写的乱只能靠注释来拯救,代码结构看下图:

按照命名空间,分为 Library,网络层和应用层(以后用户接口层会分出来).

相关概念

数据同步

这里的同步是指一个房间内的数据同步,一个房间内存在着来自网络上的多个终端用户,每个Client都会将房间内其它人的数据在本地做一个Clone,而数据同步是指将你自己的数据同步到其他Cient你自己的Clone上面,因此发送范围是其它用户都会接收。

数据同步分为一下两种:

  • View Sync

View Sync是毫秒级别的数据同步。可用于虚拟角色动作同步。

  • RPC

每次同步由用户手动触发。可用于换装等同步。

Custom Event

Custom Event不是向所有其它Client的Clone实体发送同步消息,而是向一个或者几个指定的Client发送消息。

接口介绍

房间相关接口

请求接口
    
   //加入或者创建房间
   public static void JoinOrCreateRoom(string roomid,uint maxnumber)
   //创建房间
   public static void CreateRoom(string roomid, uint maxnumber)
   //加入房间
   public static void JoinRoom(string roomid)
   //离开房间
   public static void LeaveRoom()
回调接口
   //创建房间成功
    void OnGreatedRoom();
    //创建房间失败
    void OnGreateRoomFailed(string errmsg);
    //加入房间成功
    void OnJoinedRoom();
    //加入房间失败
    void OnJoinRoomFailed();
    //离开房间成功
    void OnLeftRoom();

Player相关接口

   //实例化一个物体
   public static void Instantiate(string prefabname, Vector3 position, Quaternion rotation, uint[] viewids)
  //有其它用户进入房间 
   void OnOtherPlayerEnteredRoom(NGPlayer player);
   //有其它用户离开房间
   void OnOtherPlayerLeftRoom(NGPlayer player);

CustomEvent接口

请求接口
    //发送事件
    public static void SendCustomEvent(uint eventid, uint[] targetpeerids, NGAny[] customdata)
    
回调接口
    //接收事件
    void OnCustomEvent(uint eventID, NGAny[] data);

View Sync

视图同步需要自己实现组件脚本,实现序列化反序列化接口,并且需要挂载到物体上:

public interface INGSerialize
{
    void SerializeViewComponent(NGViewStream stream);
    void DeserializeViewComponent(NGViewStream stream);
}

public class CubeViewComponent : NGIncomingEvent, INGSerialize
{
    public void SerializeViewComponent(NGViewStream stream)
    {
        stream.Send(this.transform.position);
        stream.Send(this.transform.rotation);
    }
    public void DeserializeViewComponent(NGViewStream stream)
    {
        mCorrentPosition = (NGVector3)stream.Receive();
        mCorrentRotation = (NGQuaternion)stream.Receive();
    }
}

Clone实体接受数据反序列化后在Update中实时更新即可:

void Update()
{
    if (!view.IsMine)
    {
        transform.position = mCorrentPosition;//Vector3.Lerp(transform.position, mCorrentPosition, Time.deltaTime * 5);
        transform.rotation = mCorrentRotation;//Quaternion.Lerp(transform.rotation, mCorrentRotation, Time.deltaTime * 5);
    }
}

RPC

使用RPC需要在视图脚本中写一个RPC函数:

[NGRPCMethod]
public void OnColor(NGAny[] c)
{
    mMat.color = c[0].NgColor;
}

调用下面的接口向其它Clone实体发送RPC调用:

public static void SendRPC(uint viewID, string methodname, RPCTarget target, params NGAny[] parameters)

有关RPC,View Sync和Custom Event 的详细使用方法 参考源码

Demo演示

服务端部署

Clone代码
git clone https://github.com/harlanc/netgo.git
安装依赖
go get -d ./...
更新监听端口号

打开main.go

tcpAddr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:8686")
启动服务
 go run main.go

客户端编译安装

客户端支持windows/MacOS/Andorid/IOS多平台。下面在Android和MacOS上测试:

配置IP和端口

切换Android平台

编译生成APK

安装APK后的初始化界面如下:

功能测试

两个Client进入同一个房间,每个Client会实例化出来两个Cube,一个为本机实体(Mine Cube),一个为对方的实体(Clone Cube)。

View SYnc

点击按钮Move后,会通过视图同步的方式进行postion和rotation同步。也就是文章刚开始的动图展示的样子:

RPC

点击Mine Cube之后,Cube的颜色会发生变化,同时同步到别的机器上,这里的颜色同步是通过RPC来实现的。

Custom Event

点击Clone Cube之后,会向对方实体发送消息,效果是对方的Mine Cube Scale会增加。

Road Map

接下来考虑会加入或者需要优化的功能:

  • 支持大厅功能
  • 支持负载均衡
  • 增加支持UDP等网络传输协议
  • 增加支持json等多种数据编码格式
  • View Sync数据传输优化
  • 支持跨房间Custom Event
  • .....

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Socket网络编程入门
Socket网络编程入门
43 0
GitHub在其网站实现中移除对jQuery的使用
7月25日,一则发自Github员工的Twitter推文引起了不少开发者的关注和评论,以下是推文原文: 乍一看这消息我挺震惊的,还以为Github把jQuery的代码仓库从它的网站上给删掉了呢,多大仇多大怨啊要干这事儿!再仔细一读,好吧,发现果然还是自己英文不好啊.
1038 0
二十二、网络编程
网络编程的一些基本概念: 1.地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议。  主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址。
1027 0
c#获取网络时间并同步本地时间
原文:c#获取网络时间并同步本地时间 通过TCP形式来获取NTP时间。主要代码如下: [DllImport("kernel32.dll")] private static extern bool SetLocalTime(ref Systemtime time); [StructLayout(LayoutKind.
2107 0
小明历险记:规则引擎drools教程一
小明是一家互联网公司的软件工程师,他们公司为了吸引新用户经常会搞活动,小明常常为了做活动加班加点很烦躁,这不今天呀又来了一个活动需求,我们大家一起帮他看看。 小明的烦恼 活动规则是根据用户购买订单的金额给用户送相应的积分,购买的越多送的积分越多,用户可以使用积分来兑换相应的商品,我们这次活动的力度很大,肯定会吸引很多的用户参加,产品经理小王兴高采烈唾液横飞的对小明讲到。
2070 0
《数据库技术原理与应用教程第2版》——3.2数据模型的四个世界
现实世界(real world)。用户为了某种需要,需将现实世界中的部分需求用数据库实现。此时,它设定了需求及边界条件,这为整个转换提供了客观基础和初始启动环境。此时,人们所见到的是客观世界中划定边界的一个部分环境,它称为现实世界。
1411 0
WPF 3D 小小小小引擎 - ·WPF 3D变换应用
原文:WPF 3D 小小小小引擎 - ·WPF 3D变换应用         WPF可以提供的3D模型使我们可以轻松地创建3D实体,虽然目前来看还很有一些性能上的问题,不过对于一些简单的3D应用应该是可取的,毕竟其开发效率高,而且也容易上手。
1193 0
[连载]《C#通讯(串口和网络)框架的设计与实现》- 9.插件引擎设计
目       录 第九章           插件引擎设计... 2 9.1           框架的契约-接口... 2 9.2           插件的雏形-抽象类... 3 9.3           实现接口... 4 9.4           反射机制... 5 9.5           反射工具类... 8 9.6           小结... 9   第九章     插件引擎设计 在介绍《第10章 宿主程序详细设计》之前对接口和插件的相关内容进行一下整体介绍,在设计宿主程序的时候会用到这些知识,也是宿主程序与插件之间交互的核心内容。
756 0
Linu网络编程
Linux网络编程入门 套接字学习教程之一 socket函数read write send和recv Linux下getsockopt/setsockopt 函数说明 套接口选项 读取 sock_opts getsockopt setsockopt 异步套接字基础:sele...
862 0
+关注
harlanc
VR公司资深开发工程师,主要关注的领域为音视频,软件安全。热衷开源,喜欢写作。
118
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载