实现一个简易的Unity网络同步引擎——netgo
实现一个简易的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
- .....
实现一个简易的Unity网络同步引擎——netgo的更多相关文章
- Unity - Photon PUN 本地与网络同步的逻辑分离 (二)
上篇实现了事件系统的设计,这篇就来结合发送RPC消息 并且不用标记 [PunRPC] 先来看下上编的代码 GameEnvent.cs private static Dictionary<Comm ...
- 最近研究了一个.NET的DHT网络搜索引擎,顺便重新整理了下引擎思路,供大家分享讨论下。
最近研究了一个.NET的DHT网络搜索引擎,顺便重新整理了下引擎思路,供大家分享讨论下.
- 使用 js 实现一个简易版的模版引擎
使用 js 实现一个简易版的模版引擎 regex (function test() { this.str = str; })( window.Test = ...; format() { let ar ...
- Unite 2017 | 从《闹闹天宫》看MOBA游戏里的网络同步技术
http://mp.weixin.qq.com/s/0v0EU79Q6rFafrh8ptlmhw 在Unite 2017 Shanghai案例分享专场,来自蓝港互动<闹闹天宫>项目组的主程 ...
- MOBA游戏的网络同步技术
转自:http://www.gameres.com/750888.html 在5月13日Unite 2017 案例分享专场上,蓝港互动<闹闹天宫>项目组的主程序陈实分享了MOBA游戏的网络 ...
- Tinywebserver:一个简易的web服务器
这是学习网络编程后写的一个练手的小程序,可以帮助复习I/O模型,epoll使用,线程池,HTTP协议等内容. 程序代码是基于<Linux高性能服务器编程>一书编写的. 首先回顾程序中的核心 ...
- 手游后台PVP系统网络同步方案总结
游戏程序 平台类型: 程序设计: 编程语言: 引擎/SDK: 概述 PVP系统俨然成为现在新手游的上线标配,手游Pvp系统体验是否优秀,很大程度上决定了游戏的品质.从最近半年上线的新手 ...
- Jsoup抓取网页数据完成一个简易的Android新闻APP
前言:作为一个篮球迷,每天必刷NBA新闻.用了那么多新闻APP,就想自己能不能也做个简易的新闻APP.于是便使用Jsoup抓取了虎扑NBA新闻的数据,完成了一个简易的新闻APP.虽然没什么技术含量,但 ...
- .NET Core的文件系统[5]:扩展文件系统构建一个简易版“云盘”
FileProvider构建了一个抽象文件系统,作为它的两个具体实现,PhysicalFileProvider和EmbeddedFileProvider则分别为我们构建了一个物理文件系统和程序集内嵌文 ...
随机推荐
- UNION JOIN 连接表
使用UNION JOIN进行多表连接,与9.3节介绍的各种表的连接类型不同,它并不对表中的数据进行任何匹配处理,而只是把来自一个源表中的行与另一个源表中的行联合起来,生成的结果表中包括第一个表中的所有 ...
- php设计模式之面向过程实现举报功能实例代码
html <html> <head> <meta charset="UTF-8"> <title>责任链模式</title&g ...
- TFTP服务[精简版]:简单文件传输协议
简单文件传输协议(Trivial File Transfer Protocol,TFTP)是一种基于 UDP 协议在客户端 和服务器之间进行简单文件传输的协议.顾名思义,它提供不复杂.开销不大的文件传 ...
- 【转载】SpringMVC配置文件详解
转自:https://my.oschina.net/happyBKs/blog/691502 web.xml文件是web应用的部署描述. 在上一节的springMVC示例中 ,idea下的Maven- ...
- 移动端CSS重置
移动端 CSS Reset 该怎么写 为了应对各大浏览器厂商对浏览器默认样式的不统一处理,我们往往会进行一个 css reset 操作,由于没有标准而且受个人偏好影响,每个公司实现的都不尽相同.关于 ...
- Python反编译调用有道翻译(附完整代码)
网易有道翻译是一款非常优秀的产品,他们的神经网络翻译真的挺无敌.无奈有道客户端实在是太难用了,而且在某些具体场景 (比如对网站进行批量翻译) 无法使用,而有道的云服务又特别的贵,一般人是无法 ...
- java: -source 1.5 中不支持 diamond 运算符 ,lambadas表达式 2018-03-13 22:43:47 eleven十一 阅读数 876更多
- bugku 头等舱
先查看题目发现页面是这一个 然后打开后台F12 发现后台什么也没有 然后使用抓包burpsuit 然后发现答案就在其中 (首先右键转到repeater然后点击go)
- Object 反射 List
public static object DeserializeModel<T>(object dataModel) { Type type = typeof(T); Object ent ...
- EVE-NG镜像模板资源占用统计
转:http://www.emulatedlab.com/forum.php?mod=viewthread&tid=432&extra=page%3D1 EVE Image fold ...