# GameDesigner
**Repository Path**: myhbgit/GameDesigner
## Basic Information
- **Project Name**: GameDesigner
- **Description**: GDNet双端Rpc网络框架用于Unity3D(Client,Server),窗体程序(客户端,服务器)和控制台项目(Service)开发, 高效稳定, 高性能高并发, P2P, Nat穿透, 各种协议一键切换: gudp, udx, kcp, tcp, web, enet, 内部支持热更新ilruntime, ggphys帧同步物理引擎, ecs模块, mvc模块, 动作设计,蓝图可视化编辑器
- **Primary Language**: C#
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 154
- **Created**: 2024-11-03
- **Last Updated**: 2024-11-03
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## GDNet
(Game Designer Network)专为游戏而设计的网络框架和动作状态机,使用C#开发,支持.NetFramework和Core版本,目前主要用于Unity3D,Form窗体程序和控制台项目开发。扩展性强,支持新协议快速扩展,当前支持tcp,gcp, udx, kcp, web网络协议。简易上手. api注释完整。
## GDNet6.0版本说明
**GDNet6.0版本对GDNet5.0做了一些修改如下:**
1.对5.0版本的Send, SendRT方法统一改为Call方法, 服务器也统一将Send, SendRT改成Call
2.RPCModel.methodHash字段和func字段合并为protocol,去掉func字段和methodHash字段
3.对5.0版本的Call则更名为Request, 可await等待结果, 服务器收到请求后需要将player.Token记录, 当服务器发给客户端则需要Response响应并需要传入Token参数
```
private async UniTaskVoid Login(NetPlayer client, string account, string password)
{
var token = client.Token; //先保存现场,下面代码有await会切换线程,会导致token丢失
var node = loadBalance.GetHash(account); //获取此账号负载均衡DB服务器节点
var dbClient = node.Token; //拿到DB服务器的连接
var (code, user) = await dbClient.Request((uint)ProtoType.Login, 1000 * 30, account, password); //向DB服务器发出请求, 查询数据库账号
Response(client, (int)ProtoType.Login, token, code, user); //响应客户端,客户端用await等等
//Call(client, (int)ProtoType.Login, code, user); //如果客户端用Call发起,则用Call回应,不需要token
//LoginHandler(client);
}
```
> 上面代码演示了客户端Request发起请求, 并且设置超时时间为30秒, 等待返回两个参数(int,UserData)
当服务器Response响应客户端时传入了token参数, 可以确保await的正确性, 比如一个客户端一秒Request请求相同的ProtoType.Login协议10万次
在5.0中会导致await等待的结果可能是正确的或乱序的, 比如Request(Login, "123")和Request(Login, "456")两个请求同时发出并await
服务器响应的结果可能是Request(Login, "123")收到的是Request(Login, "456")的结果, Request(Login, "456")收到Request(Login, "123")的结果, 也可能是正确的, 6.0版本解决了这个问题, 可以同时发出一个协议n次, await的结果都是正确的
4.新增了分布式案例和分布式的几个核心类
>
1. ConsistentHashing 一致性哈希类, 包含虚拟节点, 哈希环处理
2. LoadBalance 负载均衡类, 包含一致性哈希, 客户端池, 当访问数据库时使用一致性哈希获取虚拟节点去路由到物理节点, 当访问其他服务器节点则可以用轮询方式
3. UniqueIdGenerator 分布式唯一ID生成器, 类似雪花ID, 使用的计数不是时间tick来计算ID, 而是使用自定义自增ID, 只要机器ID是唯一, 分布式节点就不会生成相同ID, 机器ID和计数ID占用多少位可以自行设置, 比如机器ID占用16位, 计数ID占用48位
5.将客户端框架Framework更名为GameCore, 并优化了安装步骤
6.状态机案例, 状态机案例中使用了代码方式创建状态, 添加动作和行为, 可用excel配置技能和动态配置技能状态
## 模块图
## 使用
1.下载GameDesigner, 解压GameDesigner.zip, 打开Unity菜单Window/PackageManager管理器,点击+号的第一项add package on disk
2.选择解压的路径xx/GameDesigner/GameDesigner/package.json即可导入gdnet包
3.如果前面没有问题,最终显示的包界面
4.打开BuildSettings->ProjectSettings->OtherSettings->设置 ApiCompatibilityLevel* = .NET 4.x 和 AllowUnsafeCode勾上,2021版本后是ApiCompatibilityLevel* = .NET Framework
5.创建服务器项目,使用控制台或窗体程序都可以,也可以统一在unity的Assembly-CSharp项目里添加新建服务器项目
在unity随便创建个脚本,双击进入VS代码编辑器, 然后右键解决方案,必须右键解决方案,必须右键解决方案,必须右键解决方案 重要的问题说三遍
6.选择添加服务器项目,使用控制台项目
7.定义服务器名称,并且选择项目路径到你的unity项目根目录,和Assets同级的文件夹目录
8.右键解决方案,必须右键解决方案,必须右键解决方案,必须右键解决方案 重要的问题说三遍, 添加现有方案,现有方案,现有方案
9.选择解压的GameDesigner目录,里面有GameDesigner.csproj文件
10.右键Server的引用,弹出选项,选择添加引用
11.选择项目选项,选择GameDesigner,确定即可
12.新建一个Service脚本文件, 这个就是你的服务器类
```
internal class Client : NetPlayer//你的客户端类
{
}
internal class Scene : NetScene//你的游戏场景类
{
}
class Service : TcpServer//你的服务器类
{
protected override bool OnUnClientRequest(Client unClient, RPCModel model)
{
Console.WriteLine(model.pars[0]);
//你也可以理解为返回true则是输入的账号密码正确, 返回false则是账号或密码错误
return true;//100%必须理解这个, 返回false则一直在这里被调用,无法调用带有[Rpc]特性的方法。 返回true后下次客户端SendRT("方法名",参数)才能调用下面那些[Rpc]特性的方法
}
[Rpc(cmd = NetCmd.SafeCall)]//使用SafeCall指令后, 第一个参数插入客户端对象, 这个客户端对象就是哪个客户端发送,这个参数就是对应那个客户端的对象
void test(Client client, string str)
{
Console.WriteLine(str);
SendRT(client, "test", "服务器rpc回调");//服务器回调
}
}
```
13.main入口方法写上
```
var server = new Service();//创建服务器对象
server.Log += Console.WriteLine;//打印服务器内部信息
server.Run(9543);//启动9543端口
while (true)
{
Console.ReadLine();
}
```
14.创建客户端控制台项目, 跟服务器项目创建一样,看上面创建服务器项目教程
15.定义一个Test类, 用来测试rpc过程调用
```
class Test
{
[Rpc]
void test(string str)
{
Console.WriteLine(str);
}
}
```
16.在main入口方法写上,这是控制台项目, 不要把这段代码用在unity,会死循环,卡死unity
```
TcpClient client = new TcpClient();
client.Log += Console.WriteLine;
Test test = new Test();
client.AddRpcHandle(test);
client.Connect("127.0.0.1", 9543).Wait();
client.SendRT("test", "第一次进入服务器的OnUnClientRequest方法");
client.SendRT("test", "客户端rpc请求");
while (true)
{
Console.ReadLine();
}
```
到此基本使用完成
## 网关转发Nginx
如果需要网关服务器的, 可使用Nginx代理转发到真实服务器, 加了Nginx代理后,服务器和客户端流程为 客户端:client->Nginx->gameServer, 服务器:gameServer->Nginx->client
```
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
#客户端连接数量
worker_connections 10240;
}
stream{
upstream gameServer{
#这里是真实的游戏服务器,如果在阿里云,腾讯云,填写对应的ip即可
server 127.0.0.1:6667;
}
server{
#nginx的监听端口,客户端连接的端口
listen 9543;
proxy_pass gameServer;
}
}
```
详情请看:https://www.cnblogs.com/knowledgesea/p/6497783.html
## MySql使用
1.mysql安装可到gdnet群:825240544群文件去下载, 或者官网下载:http://c.biancheng.net/view/2391.html
2.mysql可视化界面工具Navicat, 到这里下载:https://www.cnblogs.com/yx-man/p/13220878.html
3.以上两步好后, 打开Navicat创建mysql数据库, 创建表和字段后, 即可用mysql orm工具生成*.cs类文件:https://gitee.com/leng_yue/my-sql-data-build
4.orm工具生成的文件可拖入unity项目, 使用unity菜单GameDesigner/Network/ExternalReference工具添加文件引用
## 对象池
gdnet提供BufferPool二进制数据对象池和ObjectPool类对象池, 在网络代码内部采用了BufferPool对象池, 使得网络可以高速读写处理数据, 而不是每次要创建一个byte[]来处理!
```
var seg = BufferPool.Take(65535);//申请65535字节的内存片
seg.WriteValue(123);//写入4字节的值
BufferPool.Push(seg);//压入内存片,等待下次复用
var seg1 = BufferPool.Take(65535);//这次的申请内存片,实际是从BufferPool中弹出seg对象,在这个过程中不会再创建byte[65535]
seg1.WriteValue(456);
BufferPool.Push(seg1);//再次压入
```
## 极速序列化
gdnet内部实现了极速序列化, 速度远超出protobuff 5-10倍, 在案例1测试中就采用了极速序列化适配器, 可以同步1万个cube, 如果用protobuff的话,只能同步2500个cube
内部的序列化已经有三个版本, 一个是之前的NetConvertOld字符串序列化,这个版本性能是非常糟糕的,性能远不及Newtonsoft.Json, 而第二版本的序列化NetConvertBinary二进制序列化则超越protobuff的性能, 体积也和protobuff一样, 为什么比protobuff快? protobuff内部实现还是使用的反射field.GetValue这种方法,而NetConvertBinary则是采用了dynamic动态语法实现的,在获取值和写值时比反射field.GetValue要快5倍. 这个NetConvertBinary版本已经超越protobuff了,为什么还要开发极速序列化NetConvertFast2? 主要还是为了框架的高性能处理.
NetConvertFast2极速序列化的使用:
1.要生成绑定类型, 在unity中有生成绑定类型工具, 也可以在这里生成:[绑定类型工具](https://gitee.com/leng_yue/fast2-build-tool)
```
public class Test //序列化的类型
{
public int num;
public string str;
}
static void Main(string[] args)
{
NetConvertFast2.AddSerializeType3();//绑定Test为可序列化类型
var seg = NetConvertFast2.SerializeObject(new Test());//序列化Test类
var obj = NetConvertFast2.DeserializeObject(seg);//反序列化Test类
}
```
你也可以调用此api直接生成绑定类型
```
Fast2BuildToolMethod.Build(typeof(Test), AppDomain.CurrentDomain.BaseDirectory);//生成test绑定类型
Fast2BuildToolMethod.BuildArray(typeof(Test), AppDomain.CurrentDomain.BaseDirectory);//生成test数组绑定类型
Fast2BuildToolMethod.BuildGeneric(typeof(Test), AppDomain.CurrentDomain.BaseDirectory);//生成test泛型绑定类型
```
新版本中增加了运行时动态编译绑定类型
```
Fast2BuildMethod.DynamicBuild(BindingEntry.GetBindTypes());//动态编译指定的类型列表
```
详细信息请打开案例: GameDesigner\Example\SerializeTest\Scenes\example3.unity 查看
## 全网最快的序列化
极速序列化3版本,我可以说是全网最快序列化,没有人反对吧?
问:为什么快?
答:极速序列化3版本使用内存地址复制方式,直接拷贝整块对象内存, 无需一个一个字段读写
问:如果类或结构有string或者自定义类或其他类呢? 什么处理?
答:当类里面没有基础类(byte,int,long...)字段会额外占用8字节的内存指针地址, 然后另外记录string的数据或其他自定义类数据
问:如果类里面只有基础类(byte,int,long...)字段的话是非常快的, 如果类有自定义类字段和基础类字段, 是不是占用比其他序列化要多一些,如:fastbuff,MemoryPack
答:是的,如果类有自定义类型字段则需要额外占用8个字节的数据, 你可以压缩后再进行其他操作
经过内存访问, 我们可以得知, 结构类只需要一级指针, 而类则需要二级指针
```
var address = Unsafe.AsPointer(ref value); //结构类只需要一级指针
```
```
var address = Unsafe.AsPointer(ref value); //类是引用地址, 一级指针得到引用地址
address = (void*)(Unsafe.Read(address) + 8); //二级指针才得到数据地址
```
除了基础类(byte,int,long...)以外,都是二级指针, 如string,DateTime,decimal...数组等等
数组的length记录是long类型, 后面跟着值
上面的代码可以得到实例对象的数据地址, 然后我们就可以通过Unsafe.CopyBlock进行内存复制
```
fixed (byte* ptr = &stream.Buffer[stream.Position])
{
int offset = 154; //这里会计算整个类的所有字段的大小
Unsafe.CopyBlock(ptr, address, (uint)offset); //直接全部拷贝
}
```
以下是测试代码, 我们进行各种字段测试
```
[MemoryPackable]
public partial class Test
{
public byte f1;
public sbyte f2;
public bool f3;
public short f4;
public ushort f5;
public char f6;
public int f7;
public uint f8;
public float f9;
public long f10;
public ulong f11;
public double f12;
public DateTime f13;
public decimal f14;
public string f15;
public byte xf1;
public sbyte xf2;
public bool xf3;
public short xf4;
public ushort xf5;
public char xf6;
public int xf7;
public uint xf8;
public float xf9;
public long xf10;
public ulong xf11;
public double xf12;
public DateTime xf13;
public decimal xf14;
public string xf15;
//public byte[] fa1;
//public sbyte[] fa2;
//public bool[] fa3;
//public short[] fa4;
//public ushort[] fa5;
//public char[] fa6;
//public int[] fa7;
//public uint[] fa8;
//public float[] fa9;
//public long[] fa10;
//public ulong[] fa11;
//public double[] fa12;
//public DateTime[] fa13;
//public decimal[] fa14;
//public string[] fa15;
//public Test test;
//public Test[] testArray;
//public List testList;
//public FastList testList1;
//public List fl1;
//public List fl2;
//public List fl3;
//public List fl4;
//public List fl5;
//public List fl6;
//public List fl7;
//public List fl8;
//public List fl9;
//public List fl10;
//public List fl11;
//public List fl12;
//public List fl13;
//public List fl14;
//public List fl15;
//public FastList ffl1;
//public FastList ffl2;
//public FastList ffl3;
//public FastList ffl4;
//public FastList ffl5;
//public FastList ffl6;
//public FastList ffl7;
//public FastList ffl8;
//public FastList ffl9;
//public FastList ffl10;
//public FastList ffl11;
//public FastList ffl12;
//public FastList ffl13;
//public FastList ffl14;
//public FastList ffl15;
//public Dictionary fd1;
//public Dictionary fd2;
//public Dictionary fd3;
//public Dictionary fd4;
//public Dictionary fd5;
//public Dictionary fd6;
//public Dictionary fd7;
//public Dictionary fd8;
//public Dictionary fd9;
//public Dictionary fd10;
//public Dictionary fd11;
//public Dictionary fd12;
//public Dictionary fd13;
//public Dictionary fd14;
//public Dictionary fd15;
//public Dictionary fda1;
//public Dictionary fda2;
//public Dictionary fda3;
//public Dictionary fda4;
//public Dictionary fda5;
//public Dictionary fda6;
//public Dictionary fda7;
//public Dictionary fda8;
//public Dictionary fda9;
//public Dictionary fda10;
//public Dictionary fda11;
//public Dictionary fda12;
//public Dictionary fda13;
//public Dictionary fda14;
//public Dictionary fda15;
//public Dictionary> fdl1;
//public Dictionary> fdl2;
//public Dictionary> fdl3;
//public Dictionary> fdl4;
//public Dictionary> fdl5;
//public Dictionary> fdl6;
//public Dictionary> fdl7;
//public Dictionary> fdl8;
//public Dictionary> fdl9;
//public Dictionary> fdl10;
//public Dictionary> fdl11;
//public Dictionary> fdl12;
//public Dictionary> fdl13;
//public Dictionary> fdl14;
//public Dictionary> fdl15;
}
```
以下是测试序列化代码, 测试前需要设置Release模式或者勾上优化编码选项
```
using Binding;
using MemoryPack;
using Net.Event;
using Net.Serialize;
using Net.System;
using System.Diagnostics;
class Program
{
unsafe static void Main()
{
NDebug.BindConsoleLog();
if (!Directory.Exists(@"..\..\..\Binding\"))
{
Directory.CreateDirectory(@"..\..\..\Binding\");
Fast2BuildMethod.BuildAll(@"..\..\..\Binding\", SerializeMode.MemoryCopy, 1, typeof(Test)); //生成绑定类型文件
return;
}
var test = new Test()
{
f1 = 123,
f2 = 123,
f3 = true,
f4 = 125,//4567,
f5 = 6842,
f6 = 'k',
f7 = 4567891,
f8 = 456478971,
f9 = 1234.4564f,
f10 = 47489745665,
f11 = 4564654123123,
f12 = 123.456456,
f13 = DateTime.Now,
f14 = 456123.45676465m,
f15 = "John-你好啊",
xf1 = 123,
xf2 = 123,
xf3 = true,
xf4 = 4567,
xf5 = 6842,
xf6 = 'k',
xf7 = 4567891,
xf8 = 456478971,
xf9 = 1234.4564f,
xf10 = 47489745665,
xf11 = 4564654123123,
xf12 = 123.456456,
xf13 = DateTime.Now,
xf14 = 456123.45676465m,
xf15 = "Johnxxxxxxxxxzzzwwq",
//fa1 = new byte[] { 1, 2, 3 },
//fa10 = new long[] { 1, 2, 3 },
//fa15 = new string[] { "John", "John", "John", },
//fl1 = new List { 1, 2, 3 },
//fl10 = new List { 1, 2, 3 },
//fl15 = new List { "John", "John", "John", },
//fl1 = new List { 1, 2, 4 },
/*fd1 = new Dictionary { { 1, 5 }, { 2, 3 }, { 5, 8 } },
test = new Test()
{
f1 = 123,
f2 = 123,
f3 = true,
f4 = 4567,
f5 = 6842,
f6 = 'k',
f7 = 4567891,
f8 = 456478971,
f9 = 1234.4564f,
f10 = 47489745665,
f11 = 4564654123123,
f12 = 123.456456,
f13 = DateTime.Now,
f14 = 456123.45676465m,
f15 = "John",
},
testArray = new Test[]
{
new Test()
{
f1 = 123,
f2 = 123,
f3 = true,
f4 = 4567,
f5 = 6842,
f6 = 'k',
f7 = 4567891,
f8 = 456478971,
f9 = 1234.4564f,
f10 = 47489745665,
f11 = 4564654123123,
f12 = 123.456456,
f13 = DateTime.Now,
f14 = 456123.45676465m,
f15 = "John",
},
new Test()
{
f1 = 123,
f2 = 123,
f3 = true,
f4 = 4567,
f5 = 6842,
f6 = 'k',
f7 = 4567891,
f8 = 456478971,
f9 = 1234.4564f,
f10 = 47489745665,
f11 = 4564654123123,
f12 = 123.456456,
f13 = DateTime.Now,
f14 = 456123.45676465m,
f15 = "John",
}
},
testList = new List()
{
new Test()
{
f1 = 123,
f2 = 123,
f3 = true,
f4 = 4567,
f5 = 6842,
f6 = 'k',
f7 = 4567891,
f8 = 456478971,
f9 = 1234.4564f,
f10 = 47489745665,
f11 = 4564654123123,
f12 = 123.456456,
f13 = DateTime.Now,
f14 = 456123.45676465m,
f15 = "John",
},
new Test()
{
f1 = 123,
f2 = 123,
f3 = true,
f4 = 4567,
f5 = 6842,
f6 = 'k',
f7 = 4567891,
f8 = 456478971,
f9 = 1234.4564f,
f10 = 47489745665,
f11 = 4564654123123,
f12 = 123.456456,
f13 = DateTime.Now,
f14 = 456123.45676465m,
f15 = "John",
}
},*/
};
Task.Run(() =>
{
while (true)
{
var stopwatch = Stopwatch.StartNew();
for (int i = 0; i < 10000000; i++)
{
var bin = MemoryPackSerializer.Serialize(test);
var val = MemoryPackSerializer.Deserialize(bin);
}
stopwatch.Stop();
Console.WriteLine("MemoryPack 1000万次:" + stopwatch.Elapsed);
}
});
Task.Run(() =>
{
BufferPool.SegmentType = SegmentType.Segment;
while (true)
{
var stopwatch = Stopwatch.StartNew();
//var bin = BufferPool.Take();
for (int i = 0; i < 10000000; i++)
{
//bin.Flush();
var bin = NetConvertFast2.SerializeObject(test);
//NetConvertFast2.SerializeObject(test, bin);
//bin.Flush();
var val = NetConvertFast2.DeserializeObject(bin);
//var val = NetConvertFast2.DeserializeObject(bin, false);
}
stopwatch.Stop();
Console.WriteLine("NetConvertBinary 1000万次:" + stopwatch.Elapsed);
}
});
Console.ReadLine();
}
}
```
这是测试结果:
## ECS模块
ECS模块类似unity的gameObject->component模式, 在ecs中gameObject=entity, component=component, system类执行, ecs跟gameObject模式基本流程是一样的, 只是ecs中的组件可以复用, 而gameObject的component则不能复用, 在创建上万个对象时, gameObject就得重新new出来对象和组件, 而ecs调用Destroy时是把entity或component压入对象池, 等待下一次复用.实际上对象没有被释放,所以性能高于gameObject的原因
```
//ecs时间组件
public class TimerComponent : Component, IUpdate //继承IUpdate接口后就会每帧调用Update方法
{
private DateTime dateTime;
public override void Awake()
{
dateTime = DateTime.Now.AddSeconds(5);//在初始化时,把当前时间推到5秒后
}
public void Update()
{
if (DateTime.Now >= dateTime)//当5秒时间到, 则删除这个时间组件, 实际上是压入对象池
{
Destroy(this);
}
}
public override void OnDestroy()//当销毁, 实际是压入对象池前调用一次
{
}
}
static void Main(string[] args)
{
var entity = GSystem.Instance.Create();//创建实体对象,这会在对象池中查询,如果对象池没有对象,则会new, 有则弹出entity
entity.AddComponent();//添加时间组件,也是从对象池查询,没有则new, 有则弹出TimerComponent对象
while (true)
{
Thread.Sleep(30);
GSystem.Instance.Run();//每帧执行ecs系统
}
}
```
## MVC模块
mvc模块:模型,控制,视图分离, mvc模块适应于帧同步游戏, model定义了对象字段,属性,事件, controller执行业务逻辑, view显示结果
在帧同步中, mvc是分离的, 各自处理各自的, 做到可以不相关的地步, 比如view卡住, controller还是一直执行, 互不影响!
热更新FieldCollection组件使用:当在热更新项目中, 字段无需使用Find各种查找, 使用FieldCollection组件即可自动帮你处理完成字段收集引用, 一键生成即可写你的功能代码
## HybridCRL热更新
在客户端框架支持全部热更新, 使用非常简单, 已经无法形容了
## [SyncVar]字段或属性同步特性
变量同步案例:Assets/GameDesigner/Example/Example1/Scenes/SyncVarDemo.unity
可以同步大部分类型, 基元类型和普通类型, 泛型的只支持List和Dictionary, 如果想要支持更多, 则需要额外处理, 分写SyncVarHandlerGenerate或继承
与场景内的玩家进行变量同步: 原理是检查字段值有没有改变, 改变了就会往服务器发送, 服务器转发给场景内的所有客户端, 以identiy值取到对应的对象, 进行变量设置, 达到变量同步效果!
客户端与服务器进行变量同步: 原理是检查字段值改变后, 发送字段的id和值到服务器, 服务器检查NetPlayer的变量管理列表取出对应的对象, 进行变量设置, 达到p2p变量同步效果
[SyncVar]//在字段定义这个特性, 则为玩家之间变量
[SyncVar(authorize = false)]//这是你实例化的网络物体, 其他玩家不能改变你的对象变量, 即使改变了也不会发生同步给其他玩家, 只能由自己控制变量变化后才会同步给其他玩家
[SyncVar(id = 1)]//这是p2p 客户端只与服务器的netplayer之间变量同步, 开发者要保证id必须是唯一的 详情请看案例1的Example1.Client类定义
如果使用[SyncVar]则需要在Unity点击菜单GameDesigner/Network/InvokeHepler打开调用帮助窗口, 启用OnReloadInvoke
以下代码是[SyncVar]额外写的代码案例, 可进行参考
```
using UnityEngine;
internal partial class SyncVarHandlerGenerate
{
internal virtual bool Equals(Rect a, Rect b) //[syncvar] Rect类型时没有判断Equals, 所以需要自己写一下
{
if (a.x != b.x) return false;
if (a.y != b.y) return false;
if (a.width != b.width) return false;
if (a.height != b.height) return false;
return true;
}
}
internal class SyncVarHandler : SyncVarHandlerGenerate
{
//只执行最大值的对象, 也就是SyncVarHandler类被执行, SyncVarHandlerGenerate类将不会执行
public override int SortingOrder => 100;
//如果生成的Equals方法代码不对, 你可以在下面重写已生成的Equals方法, 自己进行判断
}
```
## 百万级别RPC小数据测试
这里我们测试了100万次从客户端到服务器的请求并响应, 所需要的时间是4.67秒
需要引用这些命名空间
```
using Net.Client;
using Net.Config;
using Net.Event;
using Net.Server;
using Net.Share;
using Net.System;
using System;
using System.Diagnostics;
using System.Threading;
```
```
internal class Program
{
static Stopwatch stopwatch;
static void Main(string[] args)
{
NDebug.BindLogAll(Console.WriteLine);
BufferStreamShare.Size = 1024 * 1024 * 100;//服务器每个客户端可以缓存的数据大小
//此处是服务器部分, 可以复制到另外一个控制台项目
var server = new TcpServer();
server.LimitQueueCount = 10000000;//测试小数据的快速性能, 可以设置这里, 默认限制在65536
server.PackageLength = 10000000;//小数据包封包合包大小, 一次性能运送的小数据包数量
server.PackageSize = 1024 * 1024 * 50;//接收缓存数据包的最大值, 如果超出则被丢弃
server.AddAdapter(new Net.Adapter.SerializeAdapter3());//采用极速序列化进行序列化rpc数据模型
server.AddAdapter(new Net.Adapter.CallSiteRpcAdapter(server));//采用极速调用rpc方法适配器
server.Run();
//此处是客户端部分, 可以复制到另外一个控制台项目
var client = new TcpClient();
client.LimitQueueCount = 10000000;
client.PackageLength = 10000000;
client.PackageSize = 1024 * 1024 * 50;
client.AddAdapter(new Net.Adapter.SerializeAdapter3());
client.AddAdapter(new Net.Adapter.CallSiteRpcAdapter(client));
client.AddRpcHandle(new Program());
client.Connect().Wait();
client.SendRT(new byte[1]);//先进入服务器
Thread.Sleep(500);
stopwatch = Stopwatch.StartNew();
for (int i = 0; i < 1000000; i++)
{
client.SendRT(NetCmd.LocalRT, 1, i);
if (i % 10000 == 0)
Thread.Sleep(50);
}
Console.ReadLine();
}
[Rpc(hash = 1)]
void test(int i)
{
if (i % 10000 == 0)
Console.WriteLine(i);
if (i >= 999999)
{
stopwatch.Stop();
Console.WriteLine(stopwatch.Elapsed);
}
}
}
```
## MMORPG多人游戏AOI九宫格同步
提供aoi九宫格同步模块, 服务器,客户端都可使用(使用时请在unity可视化调整九宫格网格或大小视图后,再将值修改到服务器,解决了服务器没有可视化图形的问题), 当万人同步时, 如果全部同步的话,带宽直接爆炸! 为了解决带宽问题, 使用了九宫格同步法, 只同步在9个格子之间的玩家或怪物, 这样就可以解决带宽的大多数问题. 详情请看自带的aoi案例
## Gcp可靠协议
**gcp协议的实现原理:**
gcp协议的4个关键函数Send,Update, Input,Receive组成:Send由用户调用,向gcp内部传递发送的数据,然后用户需要每帧调用Update进行数据更新,内部处理数据超时重传等更新。
用户需要通过OnSender事件进行socket.Send发送gcp数据,当对方socket通过socket.ReceiveFrom接收到数据后需要把数据传递给Input方法,然后调用Receive方法进行接收gcp的数据。
**gcp功能:**
流量控制:MTPS属性控制每秒可发送多少字节的数据
超时重传:RTO属性控制在多少毫秒如果没有收到确认帧,则进行重传
最大传输单元:MTU属性控制每次只能发送多少个字节
数据帧:每个数据帧会有frame标志(byte), package号(uint),serialNo数据帧序号(int),count数据长度(int),总共头部为13字节
多帧确认:在一秒内可以发送很多数据帧,无需顺序,到达对方后会进行字典接收,然后返回Ack确认帧给客户端进行超时重传移除(不再重传),如果Ack发送过程丢失,则客户端会重新发起数据帧,直到Ack到客户端。当对方接收到的帧数据package号大于当前packageLocal号时,放入字典等待即可,等真正的packageLocal号确认完成后,一拼调用。
```
static void Main(string[] args)
{
var gcp = new GcpKernel();
var gcp1 = new GcpKernel();
gcp.OnSender += (b) => {
gcp1.Input(b);//socket.send(b);
};
gcp1.OnSender += (b) => {
gcp.Input(b);//socket.send(b);
};
gcp.Send(new byte[1235522]);
gcp1.Send(new byte[1235522]);
while (true)
{
gcp.Update();
gcp1.Update();
if (gcp.Receive(out var buffer) != 0)
{
Console.WriteLine($"接收到的数据:{buffer.Length}");
}
if (gcp1.Receive(out var buffer1) != 0)
{
Console.WriteLine($"接收到的数据:{buffer1.Length}");
}
}
}
```
## 常见问题总汇
这里是开发者遇到的问题, 我都会在这里详细写出来, 这样大家遇到的问题都可以先在这里查看
## TapTap游戏
使用GDNet开发的游戏已有上百款, 为了保护游戏不被恶意攻击或看GDNet不爽的人恶意对游戏评论或差评, 从此将不再收集游戏名单!
## 使用到的第三方库和一些推荐的库
1.udx: 超强的一个udp可靠协议库,实力大于名气 www.goodudx.com
2.kcp: 超多使用的可靠udp协议库 https://github.com/skywind3000/kcp
3.json: 超快的json解析库 https://github.com/JamesNK/Newtonsoft.Json
4.dnlib: 巨强C#源码反编译库 https://github.com/0xd4d/dnlib
5.fleck: websocket服务器网络库 https://github.com/statianzo/Fleck
6.unityWebSocket: 支持webgl网页连接的库 https://github.com/psygames/UnityWebSocket
以下开发者会使用到的库:
1.mysqlBuild: 快速生成mysql或sqlite数据库的对象映射,傻瓜式使用 https://gitee.com/leng_yue/my-sql-data-build
2.MySql: 使用量前5的强大数据库 NuGet: MySql https://dev.mysql.com/downloads/
3.redis: 超强的内存数据库 https://redis.io/download/
4.mongodb: 使用json, bson存储的内存数据库 https://www.mongodb.com/
5.ilruntime: 运行热更新框架 https://github.com/Ourpalm/ILRuntime
6.hybridclr(原名huatuo): 运行热更新il2cpp https://focus-creative-games.github.io/hybridclr/start_up/
7.nginx: 强大的负载均衡,集群网关 https://github.com/nginx/nginx
8.unitask: 微软巨佬的单线程Task https://github.com/Cysharp/UniTask
9.protobuf: 超强的序列化工具 https://github.com/protocolbuffers/protobuf
10.log4net: 超牛的日志记录工具 https://logging.apache.org/log4net/download_log4net.html
## 致谢
感谢对此框架的支持,如果有其他问题,请加QQ群:825240544讨论
您的支持就是我不懈努力的动力。打赏时请一定留下您的称呼
感谢以下人员对gdnet捐款:
1 vsmile ¥ 10
2 南归 ¥ 10
3 王者心,懂么? ¥ 10
4 郭少 ¥ 5000
5 思念天边的你 ¥ 52
6 娟子 ¥ 1000
7 Slarvens ¥ 30
8 达西莉莉 ¥ 200
9 扬神无敌 ¥ 100
10 29.8°C ¥ 30
11 走在冷风中. ¥ 1000
12 非非有非非无 ¥ 100
13 克里斯 ¥ 200 + 1000 + 200
14 Maple ¥ 200 + 200
15 蓝色冰点 ¥ 10
不留名的大佬们 微信总资助 ¥ 653
