更新日期:2025年10月9日。
项目源码:获取源码。
索引
- uNet游戏服务端框架
- 一、uNet源码结构
- 二、uNet架构设计
- 1.网络玩家 NetworkPlayer
- 2.网络实体 NetworkEntity
- 3.网络地图 NetworkMap
- 4.网络房间 NetworkRoom
- 5.启动服务端
uNet游戏服务端框架
uNet游戏服务端框架为使用.Net Core开发的高性能、高并发网络游戏服务端框架,基于async / await的多线程架构天然适应IO高并发环境,使用Protobuf进行网络数据交换能够极大的降低带宽和网络延迟,同时使用对象池技术管理服务端中的大多数实例能够显著的降低GC开销。
一、uNet源码结构
在uNet解决方案目录中,存在4个项目:

uNet.Core:uNet框架的核心代码模块。
uNet.Engine:uNet框架中与Unity引擎进行同步交互的功能实现模块。
uNet.Example.ChineseChess:中国象棋的游戏服务端。
uNet.Example.MMO:MMORPG的游戏服务端。
二、uNet架构设计
在uNet中,将一个游戏服务端程序中可能出现的所有实例划分为了如下的四类:
| 类型 | 描述 |
|---|---|
| 网络玩家 | 代表网络中的玩家客户端实例 |
| 网络实体 | 代表存在于地图、房间中的具备网络同步需求的实体(比如NPC、副本中的BOSS) |
| 网络地图 | 代表可同时容纳大量玩家、大量实体的游戏场景(也即是游戏中的主城) |
| 网络房间 | 代表可同时容纳少量玩家、少量实体的游戏场景(也即是游戏中的副本) |
1.网络玩家 NetworkPlayer
网络玩家由NetworkPlayer类表示,一个NetworkPlayer实例即代表了存在于当前服务器中的一名玩家客户端,他的基础属性如下:
/// <summary>/// 网络玩家
/// </summary>
public abstract class NetworkPlayer : IObjectPoolable
{
/// <summary>/// 玩家的ID
/// </summary>
public long ID { get; private set; }
/// <summary>/// 玩家姓名
/// </summary>
public string? Name { get; protected set; }
/// <summary>/// 在本次同步中是否已改变
/// </summary>
public bool IsDirty { get; set; } = false;
/// <summary>/// 心跳包校验码
/// </summary>
public abstract int HEARTBEAT { get; }
/// <summary>/// 常规信息校验码
/// </summary>
public abstract int NORMAL { get; }
//........
}
ID:玩家的ID,玩家在当前游戏服务器中的唯一标识符,重新登录后会改变。
Name:玩家的姓名。
IsDirty:在本次同步中是否已改变,在执行网络同步时,只有标记为改变的玩家才会被同步。
HEARTBEAT:心跳包校验码,在心跳机制中,用于鉴别心跳包的校验码。
NORMAL:常规信息校验码,区别于心跳包的常规信息数据包的校验码。
由于NetworkPlayer兼顾了与客户端进行双向通信的使命,所以他自身具备了区别心跳包和常规信息数据包的能力。
在NetworkPlayer中已封装了用于收、发数据的相关方法(使用Socket收、发),直接调用即可:
/// <summary>/// 发送数据到客户端
/// </summary>
/// <param name="bytes">数据内容</param>
/// <param name="token">用于取消异步的token</param>
/// <returns>是否发送成功</returns>
protected async Task<bool> SendDataAsync(byte[]? bytes, CancellationToken token){//代码后续讲解......}/// <summary>/// 从客户端接收数据/// </summary>
/// <param name="token">用于取消异步的token</param>
/// <returns>网络消息</returns>protected async Task<NetworkMessage?> ReceiveDataAsync(CancellationToken token){//代码后续讲解......}
2.网络实体 NetworkEntity
网络实体由NetworkEntity类表示,除了玩家以外的,存在于地图或副本中的其他需要进行网络同步的实例,都统称为网络实体。
比如副本中的小怪和BOSS,他们需要进行网络同步,以在不同的玩家客户端中表现为相同状态(比如相同的血量,相同的攻击姿态),所以他们是网络实体。
但某些NPC,站在原地不动,玩家仅仅能点击他进行对话或接受任务,他们不需要进行网络同步,所以他们不是网络实体。
NetworkEntity的基础属性如下:
/// <summary>/// 网络实体
/// </summary>
public abstract class NetworkEntity : IObjectPoolable
{
/// <summary>/// 实体的ID
/// </summary>
public long ID { get; private set; }
/// <summary>/// 在本次同步中是否已改变
/// </summary>
public bool IsDirty { get; set; } = false;
/// <summary>/// 实体名称
/// </summary>
public abstract string Name { get; }
/// <summary>/// 实体类型
/// </summary>
public abstract string Type { get; }
//......
}
ID:实体的ID,实体在当前地图或副本中的唯一标识符,不同类型的实体可能重复。
IsDirty:在本次同步中是否已改变,在执行网络同步时,只有标记为改变的实体才会被同步。
Name:实体的名称,同一类型的实体,他们一般具有相同的名称。
Type:实体的类型,用于在网络环境中区分实体(比如服务端的某个实体Type为Enemy,客户端的相同实体的Type也应该为Enemy,确保在网络同步时能够正确定位到该实体)。
3.网络地图 NetworkMap
网络地图由NetworkMap类表示,一个NetworkMap实例即代表了存在于当前服务器中的一个地图场景,在地图中可容纳大量玩家或实体,玩家与实体之间产生交互并经过网络同步,最终完成一整套的游戏玩法。
他的基础属性如下:
/// <summary>/// 网络地图(用于容纳大量网络玩家和大量网络实体)
/// </summary>
public abstract class NetworkMap : IObjectPoolable
{
/// <summary>/// 地图名称
/// </summary>
public abstract string Name { get; }
/// <summary>/// 地图类型
/// </summary>
public abstract string Type { get; }
/// <summary>/// 执行网络同步的间隔时间(毫秒)
/// </summary>
public abstract int SyncInterval { get; }
}
Name:地图的名称,同一类型的地图,他们一般具有相同的名称,且地图实例本身就是唯一的。
Type:地图的类型,用于在网络环境中区分地图(比如服务端的某个地图Type为Map1,客户端的相同地图的Type也应该为Map1,确保在网络同步时能够正确定位到该地图)。
SyncInterval:执行网络同步的间隔时间(毫秒),地图拥有自动对其中的玩家和实体进行网络同步的功能,此为2次同步之间的间隔时间,设置得越低同步频率越高,但同时服务端压力也越大,可能导致网络延迟卡顿。
注:通常情况下,地图的同步间隔时间可以设置较长,以降低性能开销。
4.网络房间 NetworkRoom
网络房间由NetworkRoom类表示,一个NetworkRoom实例即代表了存在于当前服务器中的一个房间场景,在房间中可容纳少量玩家或实体,玩家与实体之间产生交互并经过网络同步,最终完成一整套的游戏玩法。
由于同一类型的NetworkRoom是可以创建多个的,所以他们在大多数时候也被叫做副本。
他的基础属性如下:
/// <summary>/// 网络房间(用于容纳少量网络玩家和少量网络实体)
/// </summary>
public abstract class NetworkRoom : IObjectPoolable
{
/// <summary>/// 房间ID
/// </summary>
public long ID { get; private set; }
/// <summary>/// 房间名称
/// </summary>
public abstract string Name { get; }
/// <summary>/// 房间类型
/// </summary>
public abstract string Type { get; }
/// <summary>/// 执行网络同步的间隔时间(毫秒)
/// </summary>
public abstract int SyncInterval { get; }
}
ID:房间的ID,在当前服务器中的唯一标识符,不同类型的房间可能重复。
Name:房间的名称,同一类型的房间,他们一般具有相同的名称。
Type:房间的类型,用于在网络环境中区分房间(比如服务端的某个房间Type为Room1,客户端的相同房间的Type也应该为Room1,确保在网络同步时能够正确定位到该房间)。
SyncInterval:执行网络同步的间隔时间(毫秒),房间拥有自动对其中的玩家和实体进行网络同步的功能,此为2次同步之间的间隔时间,设置得越低同步频率越高,但同时服务端压力也越大,可能导致网络延迟卡顿。
注:通常情况下,房间的同步间隔时间可以设置较短,以带来更流畅的体验。
uNet的网络同步采用的是状态同步的方式,而网络同步的时机为固定频率同步,这不一定适合所有游戏,只是我们本系列教程所采用的方式,当然,其他方式(比如帧同步)后续可能也会考虑。
5.启动服务端
启动服务端的话将非常简单,比如通过查看MMORPG的服务端入口函数Main,将看到极简的代码:
namespace uNet.Example.MMO
{
class Program
{
static void Main(string[] args)
{
//定义服务端配置信息,比如监听的IP地址、端口号,玩家类型(继承至NetworkPlayer,同一游戏服务端中只能存在一种玩家类型)
ServerConfig config = new ServerConfig("127.0.0.1", 11000, typeof(MMO_Player), 10, 20);
//创建服务端入口
ServerEntry entry = new ServerEntry(config);
//创建一个地图(比如这里是初始地图:暴风要塞)
entry.CreateMap<MMO_BeginnerMap>();//启动服务端entry.Start();//启用常规日志的打印显示Log.IsEnableInfo = true;//按下ESC键退出程序ConsoleKeyInfo consoleKeyInfo = Console.ReadKey();while (consoleKeyInfo.Key != ConsoleKey.Escape){consoleKeyInfo = Console.ReadKey();}}}}
服务端为控制台程序,启动完成后如下:
