【unity开发】FishNet网络框架学习总结 (持续更新
FishNet网络框架学习总结
引言
随着网络游戏和多人应用程序的普及,学习和掌握网络编程变得越来越重要。Unity作为一个强大的游戏开发平台,提供了多种网络解决方案。
而FishNet作为一款新兴的网络框架,以其高性能和易用性受到了开发者的青睐。
另外这是本人第一次学习Unity网络框架的使用。
这篇随笔仅作为学习总结,不建议作为入门教程来使用。
建议有一些网络开发的常见知识(如Socket,序列化等)作为前置再来学习网络框架。
这样你不仅能更加理解FishNet的设计原理和工作机制,还能更快上手解决实际问题。
1.常用组件
1.NetworkManger
单例对象,本联机框架的基础,在场景中只能有一个。
可以通过InstanceFinder.NetworkManager来快速访问场景中第一个NetworkManger。
1.介绍
NetworkManager 的角色: NetworkManager 是在 FishNet 框架中处理网络通信的核心组件。它负责在客户端和服务器之间进行数据传输和通信配置,确保网络功能正常运行。
不能作为网络对象: 虽然 NetworkManager 负责网络通信,但它自身不应该是网络同步的对象。也就是说,你不应该在 NetworkManager 所在的 GameObject 上,或者它的父对象中添加 NetworkObject 组件。
2.参数
Run In Background: 当设置为 true 时,fishnet在后台也能运行。对于服务器来说,这通常是必需的,因为服务器需要在没有用户界面操作的情况下继续运行。对于客户端,这也可以确保在切换应用程序窗口时不会断开连接。
Don't Destroy On Load: 如果启用,此设置将确保 NetworkManager 在场景切换时不会被销毁。如果你的项目只使用一个 NetworkManager,通常建议将其保持为 true。
Persistence: 这个选项指定了在同时生成多个 NetworkManager 时的行为。例如,可以选择允许删除较新的那个 NetworkManager。
Logging: 允许你指定在不同环境下(如构建、编辑器和无头模式)记录哪些操作。如果不设置,系统会使用默认的日志记录设置。你可以通过 Fish-Networking -> Logging -> Logging Configuration 自定义日志设置。
Spawnable Prefabs: 指定用于网络对象的预制件集合。默认情况下,该字段自动设置为 DefaultPrefabObjects(位于Assets/)。通常不需要进行手动修改和添加。
Object Pool: 指定要使用的对象池脚本。如果没有设置,系统会自动在挂载了NetworkManager组件下的GameObject添加 DefaultObjectPool。你可以继承 ObjectPool 类来创建自己的对象池管理方式。
Refresh Default Prefabs: 当设置为 true 时,每次进入播放模式都会刷新
DefaultPrefabCollection。通常,这个选项不需要启用,但如果你的预制件集合经常因连接而损坏,启用它可能会有用。
2.Tugboat
Tugboat 是 FishNet 框架中默认的传输协议,基于 LiteNetLib 实现,支持可靠和不可靠的消息传递。它是一个跨平台的传输协议,兼容性广泛,适用于多种操作系统和游戏主机平台。
建议直接添加到NetworkManager挂载的GameObject上。
1.组件设置
Dont Route: 强制套接字直接向网络适配器接口发送数据,而不通过其他服务(如路由器)进行路由。这通常只在使用多个网络适配器时需要。
Unreliable MTU: 设置不可靠数据包的最大大小。当单个出站数据超过这个值时,将以可靠方式发送。较小的不可靠数据会自动分成多个不可靠发送包。
IPv4 Bind Address: 指定服务器绑定的 IPv4 地址。如果设置了此值而 IPv4 不可用,服务器启动时会引发套接字错误。如果是进行P2P联机则不需要设置该值。
Enable IPv6: 启用 IPv6 绑定(如果可用)。在某些情况下,如果你有一个不想使用的 IPv6 接口,可能需要禁用此选项。
IPv6 Bind Address: 指定服务器绑定的 IPv6 地址。如果设置了此值而 IPv6 不可用,服务器启动时会引发套接字错误。
Port: 服务器监听的端口,客户端也会通过此端口连接。在某些情况下,你可能需要在运行时使用 TransportManager.SetPort() 更改此端口。
Maximum Clients: 设置传输协议允许的最大活动客户端数,在达到此上限后,传输协议将开始拒绝连接。可以用来当作游戏房间最大的人数。
Client Address: 指定客户端要连接的服务器地址,默认情况下,为本地测试设置为 localhost。
3.ServerManger
主要用于处理客户端的验证以及一些仅适用于服务器的设置。
networkManager.ServerManager或者InstanceFinder.ServerManager来访问
1.介绍
ServerManager能够创建服务端的连接。
对服务端状态变化,远程连接状态变化,客户端被踢出等事件进行监听。
还能够负责管理服务器端的一些设置和功能,如客户端的验证、同步频率、以及服务器的性能优化等。
2.创建连接
serverManager.StartConnection
具有两种重载。
无参重载是以设定的传输协议(上面的tugboat便是默认的传输协议)上的端口号进行启动
有参重载则是以特定的端口号进行启动
3.事件监听
如上图共有四种监听事件:
直接通过+=来监听
1.OnServerConnectionState:
服务连接状态发送变化时调用
通过var state = obj.ConnectionState;得到变化的状态。
然后可以通过switch来进行枚举的判断
这里举的例子为,当服务器关闭时将_playersCon列表的所有值赋为空
2.特性
注意!这些特性仅适用于继承了NetworkBehaviour的脚本使用1.[ServerRpc]
1.介绍
是一个用于定义服务端远程过程调用的方法特性。简单来说就是从客户端调用[ServerRpc]标记的方法,是在服务端上执行其代码,并不会在客户端执行这段代码
它通常用于在客户端和服务端之间进行通信,使客户端能够请求服务端执行某些操作。
你还可以使用 NetworkConnection 类型作为最后一个参数来知道是哪个连接调用了RPC。
例如
[ServerRpc(RequireOwnership = false)]
private void RpcSendChat(string msg, NetworkConnection conn = null)
{
Debug.Log($"Received {msg} on the server from connection {conn.ClientId}.");
}
2.使用场景
客户端到服务端的通信当客户端需要向服务端发送某些数据或请求服务端执行某些操作时,可以通过调用带有[ServerRpc]特性的方法来实现。
例如只给主机(一个客户端同时也作为服务端)仅有的踢人面板同步信息
验证和授权
服务端可以在接收到客户端的请求后进行验证和授权,确保只有经过验证的客户端才能执行特定的操作。
例如VIP,房主用户权限,白名单,黑名单
同步游戏状态
在多玩家游戏中,可以讲游戏里各种数据存在服务端,然后再由客户端通过[ServerRpc]向服务端发送状态更新,服务端接收到这些更新后,再广播(例如下面的ObserverRpc特性)给其他客户端,保持游戏状态的一致性,防止玩家修改客户端数据来作弊。
3.参数
1.RequireOwnership
该参数决定了调用此[ServerRpc]方法的客户端是否必须拥有调用该方法的对象的所有权(Ownership)。该参数默认为true,意思是只有拥有对象所有权的客户端才能调用该方法
简单点来说就是相当于这个方法前面多了一行判断
以下面的代码为例:
点击查看代码
[ServerRpc(RequireOwnership=false)]
void Fun1()
{
//原本的逻辑
}
[ServerRpc(RequireOwnership=true)]
void Fun2()
{
//原本的逻辑
}
void OnStartClient()
{
if(base.IsOwner)
Fun1();
//两者等效
Fun2();
}
2.RunLocally
RunLocally参数适用于所有RPC方法 RunLocally参数用于指定方法是否也在调用它的客户端本地运行。 该参数默认为false,意味着ServerRpc方法一般只在服务端端执行。如果RunLocally = true:方法在调用它的客户端本地也会执行。
这在需要客户端立即响应某些操作时很有用,而无需等待服务端的响应。
2.[ObserversRpc]
1.介绍
[ObserversRpc] 特性标记的方法是一个远程过程调用,当 **服务端** 调用该方法时,FishNet 确保在所有当前观察该对象的客户端上执行,用于将更新广播到所有相关客户端。如果不是服务端而是客户端直接调用该方法将无法执行。
所以如果想在客户端执行一般需要配合着有[ServerRpc]标记的方法使用。
2.使用场景
1.同步状态在多人游戏中,你可能需要同步玩家或物体的状态,如位置、旋转、健康值等。
当这些状态改变时,使用 [ObserversRpc] 可以确保所有观察该对象的客户端都能接收到更新。
2.广播事件
某些事件(例如物品生成、特效触发等)需要通知到所有相关客户端。
使用 [ObserversRpc] 可以高效且简单地实现事件的广播。
3.[TargetRpc]
1.介绍
与上文的[ObserversRpc]类似,需要服务端来调用该方法才能执行。与[ObserversRpc]不同的是,仅向特定客户端发送消息或执行逻辑。
例如,当一个玩家拾取到物品时,只通知该玩家,而不通知其他玩家。
它允许服务器向特定客户端发送消息并在该客户端上执行指定的方法。
该特性标记的方法的第一个参数必须是NetworkConnection类型,这个参数指定数据发送到哪个连接。
2.与[ObserverRpc]的区别
TargetRpc执行对象:特定客户端。
调用方式:由服务器端调用。
用途:用于仅向特定客户端发送消息或执行逻辑。
例如,当一个玩家死亡后需要复活时,只复活该玩家,而不复活其他玩家。
实现细节:方法标有[TargetRpc],第一个参数必须是NetworkConnection类型,指定消息要发送到的连接(不然服务端也不知道要复活那个玩家呀~)。
ObserverRpc
执行对象:所有观察此对象的客户端。
调用方式:由服务器端调用。
用途:用于向所有观察此对象的客户端广播消息或执行逻辑。
例如,当某个物体在游戏世界中被破坏时,通知所有看到此物体的玩家。
实现细节:方法标有[ObserverRpc],不需要指定特定连接,因为消息会发送给所有观察此对象的客户端。
4.[Server]
1.介绍
[Server] 特性是 FishNet 框架中的一个特性,用于限制某个方法只能在服务端运行。如果客户端尝试调用这个方法,将不会执行,并且可以选择记录警告或错误消息。
其实就是类似给方法开头隐式加了一段判断如:if(!IsServer) return;
2.使用场景
例如生成物品在游戏中,服务器通常负责生成物品,并将这些物品同步到所有客户端。例如触发器,碰撞器的回调函数,只在服务端做模拟,然后同步到所有客户端。
最直观的就是节省代码量,可以节省判断的代码的过程
3.参数
Logging [Server] 特性还具有一个 Logging 参数客户端直接调用标记方法时,将会弹出消息(默认为警告消息)
可以选择记录警告、错误或者不记录任何消息。
例如[Server(Logging = LoggingType.Off)]
标记的方法将不会输出任何消息
LoggingType 包含以下选项:
Off:不记录任何消息。
Warning:记录一条警告消息。(默认)
Error:记录一条错误消息。
3.广播
1.IBroadcast接口
命名空间:using FishNet.Broadcast;在 FishNet 中,IBroadcast 接口是一个用于定义广播消息的接口。
广播允许您向一个或多个对象发送消息,而无需 NetworkObject 组件。
这对于在不一定联网的对象(如聊天系统)之间进行通信比较合适。
定义一个广播消息类,该类实现 IBroadcast 接口,然后,你可以在服务端上发送该广播消息
广播消息是一种在服务端和客户端之间发送的消息,它们可以通过网络传输并在所有连接的客户端上接收和处理。
FishNet 使用这种机制来实现高效的消息传递和事件通知。
举例:
自定义广播消息类
//(注意只能用结构体来发送消息)
public struct ChatMessage : IBroadcast
{
public string Sender;
public string Message;
}
在服务器上发送广播消息
public class ChatManager : MonoBehaviour
{
public void SendChatMessage(string sender, string message)
{
ChatMessage chatMessage = new ChatMessage
{
Sender = sender,
Message = message
};
// 发送广播消息到所有客户端
InstanceFinder.ServerManager.Broadcast(chatMessage);
}
}
在服务器上接收广播消息
public class ServerListener : MonoBehaviour
{
private void OnEnable()
{
// 注册广播消息的处理方法
InstanceFinder.ServerManager.RegisterBroadcast<ChatMessage>(OnChatMessageReceived);
}
private void OnDisable()
{
// 注销广播消息的处理方法
InstanceFinder.ServerManager.UnregisterBroadcast<ChatMessage>(OnChatMessageReceived);
}
private void OnChatMessageReceived(NetworkConnection networkConnection, ChatMessage chatMessage, Channel channel)
{
// 处理接收到的广播消息
}
}
在客户端上接收广播消息
public class ChatListener : MonoBehaviour
{
private void OnEnable()
{
// 注册广播消息的处理方法
InstanceFinder.ClientManager.RegisterBroadcast<ChatMessage>(OnChatMessageReceived1);
}
private void OnDisable()
{
// 注销广播消息的处理方法
InstanceFinder.ClientManager.UnregisterBroadcast<ChatMessage>(OnChatMessageReceived1);
}
private void OnChatMessageReceived1(ChatMessage chatMessage, Channel channel)
{
// 处理接收到的广播消息
Debug.Log($"Message from {chatMessage.Sender}: {chatMessage.Message}");
// 在这里更新 UI 或执行其他逻辑
}
}
在客户端上发送广播消息
public void SendChatMessage(string sender, string message)
{
ChatMessage chatMessage = new ChatMessage
{
Sender = sender,
Message = message
};
// 发送广播消息到服务端
InstanceFinder.ClientManager.Broadcast(chatMessage);
}
其实将上面这些结合起来,就能做一个简单的供玩家之间聊天的窗口了。
大概思路为:
首先,客户端将消息广播给服务端。
然后,服务端接收消息再广播给所有客户端。
最后,客户端在根据接收到的消息生成消息有关的UI预制体即可。
且全程不需要使用NetworkBehaviour,只需要在服务端和客户端实例注册事件即可