实现一个简单的Unity网络同步引擎——netgo

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

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

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

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

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

数据通信格式

数据通信格式的定义是整个项目的基石。我们这里的客户端和服务端是跨平台,跨语言通信。因此要定义一种语言无关,平台无关并且简单易用,高效不费流量的数据格式。这里我们选用了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/netgo-framework/netgo.git
安装依赖
go get -d ./...
更新监听IP

打开main.go

tcpAddr, err := net.ResolveTCPAddr("tcp", "192.168.0.104: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
  • .....

原文地址:https://www.cnblogs.com/harlanc/p/12103801.html

时间: 2024-08-26 22:58:42

实现一个简单的Unity网络同步引擎——netgo的相关文章

Kivy a to Z -- 一个简单的通过adb同步Android系统文件的工具

来兴趣时写了些Kivy的代码,调试却总感觉不是很方便.直接打包到public.mp3的方式太繁锁,用文件共享的软件又发现没有一个好用的, 用samba filesharing本来也只是慢,但是更新的版本之后就一直提示说wifi没有tethering,意思是wifi热点没有打开,但是打开了还是提示没有tethering. 找了个叫什么卓*力的文件管理器,下载了samba插件后输入用户名和密码死活不对,被搞得实在恼火,花了点时间写了个通过adb同步安卓文件的工具,用着也挺爽的. 事件为什么总是要搞得

利用原始套接字实现一个简单的采集网络数据包

//利用原始套接字实现一个简单的采集网络数据包,并进行反向解析IP,MAC地址#include <stdio.h>#include <sys/socket.h>#include <unistd.h>#include <sys/types.h>#include <linux/if_ether.h>#include <linux/in.h> #define BUFFER_MAX 2048 int main(int argc, char *

#DDBMS#构建一个简单的docker网络

拓扑图如下: host1:10.20.10.70 host2:10.20.10.71 分别在主机1和主机2上创建一个ovs交换机: ovs-vsctl add-br ovs0 ip link set ovs0 up 分别为ovs0添加一个port(br0在同一网段): ovs-vsctl add-port ovs0 br0 在主机1创建通往host2的gre隧道: ovs-vsctl add-port ovs0 gre0 -- set interface gre0 type=gre option

一个简单的 JavaScript 的模板引擎

比较简单,直接贴代码吧: (function (global) { var _version = '1.0.0', _setting = { openTag: '<#', /*逻辑代码的开始标签*/ closeTag: '#>', /*逻辑代码的结束标签*/ maskOpenTag: '<!-', /*注释的开始标签*/ maskCloseTag: '-!>' /*注释的结束标签*/ }, _templateCache = {}, _escapeHTML = function (s

一个简单的MySQL数据库同步脚本

Talk is cheap,show you code. 1 mysqldump -h[remoteHost] -u[username] -p[password] -P[port] --databases [databasename1] [databasename2] > [D:\mysqlsync\temp.sql] 2 echo "备份完成"; 3 mysql -u[root] -p[123456] -h[localhost] < [D:\mysqlsync\temp.

一个简单的Python网络爬虫(抓图),针对某论坛.

1 #coding:utf-8 2 import urllib2 3 import re 4 import threading 5 6 #图片下载 7 def loadImg(addr,x,y,artName): 8 data = urllib2.urlopen(addr).read() 9 f = open(artName.decode("utf-8")+str(y)+'.jpg', 'wb') 10 f.write(data) 11 f.close() 12 13 #具体帖子页面解

Hyperledger Fabric 建立一个简单网络

p { margin-bottom: 0.25cm; line-height: 120% } Building you first network 网络结构: 2个Orgnizations(每个Org包含2个peer节点)+1个solo ordering service 打开fabric-sample下的示例first-network p { margin-bottom: 0.25cm; line-height: 120% } 其中byfn.sh为启动这个网络的启动脚本,启动脚本中除建立一个包含

实时pvp(皇室战争)网络同步研究

实时pvp的关键在于对抗延迟和网络同步. 这里先分享几篇非常经典的文章 http://www.skywind.me/blog/archives/131 https://www.zhihu.com/question/36258781 http://www.skywind.me/blog/archives/1048 之前我也分享过一篇关于帧同步的思考,这篇文章最后也分享了几篇非常不错的英文文章. http://blog.csdn.net/langresser_king/article/details

Source引擎多人模式网络同步模型

转自:http://gad.qq.com/program/translateview/7168875 Source引擎的多人游戏使用基于UDP通信的C/S架构.游戏以服务器逻辑作为世界权威,客户端和服务器通过UDP协议(20~30packet/s)通信.客户端从服务器接收信息并基于当前世界状态渲染画面和输出音频.客户端以固定频率发送操作输入到服务器.客户端仅与游戏服务器,而不是彼此之间通信.多人游戏必须处理基于网络消息同步所带来的一系列问题. 网络的带宽是有限的,所以服务器不能为每一个世界的变化