)
一、服务器端NetworkManger当有客户端连接时三个回调函数①OnServerConnect连接上了还没在场景生成网络Player预制体可以在此验证客户端例如 “客户端是否在黑名单”、“房间是否已满”等等。②OnServerReady准备就绪了通过验证马上要在场景生成网络Player预制体了。③OnServerAddPlayer这个方法负责的就是在场景中生成网络Player预制体在源码中可以看到。如果说想就是在生成网络Player之前先设定一下网络Player索引编号再同步给客户端。使用下面代码public override void OnServerAddPlayer(NetworkConnectionToClient conn) { Transform startPos GetStartPosition(); GameObject player startPos ! null ? Instantiate(playerPrefab, startPos.position, startPos.rotation) : Instantiate(playerPrefab); player.name ${playerPrefab.name} [connId{conn.connectionId}]; int id RoomManager.GetFirstIndex(); //这里的RoomManager是自定义的编号获取功能对象 player.GetComponentNetPlayer().playerId id; //这里的NetPlayer是网络Player预制体上挂载的继承自NetworkBehaviour的自定义组件playerId是被[SyncVar]修饰的int变量。 NetworkServer.AddPlayerForConnection(conn, player);//核心代码同步给客户端 }这个还是很常用的就是说当有客户端连接时服务器端要给他分配一个身份ID客户端根据这个身份ID做对应的初始化。NetworkManger当有客户端断开连接时回调函数OnServerDisconnect注意一点在OnServerDisconnect 回调函数执行中此时场景中的对应的 网络Player并没有被立即摧毁由于使用的Destory本帧末尾才被摧毁。可以开个协程等待一帧后才能正确捕获场景中的所有网络Player。看一下方法完整签名 void OnServerDisconnect(NetworkConnectionToClient conn)。需要注意的一点是 传入的conn的identity属性NetworkIdentity就是对应网络Player的NetworkIdentity组件一致。并且注意在 base.OnServerDisconnect(conn) 这行方法执行后conn的identity就被设置为null了如果想在客户端断开连接后针对对应的conn的identityNetworkIdentity做些什么就应该在 base.OnServerDisconnect(conn);之前做NetworkManager小技巧先等待三帧再开启服务器protected new void Start() { base.Start(); StartCoroutine(CStartHost()); } private IEnumerator CStartHost() { yield return null; yield return null; yield return null; StartServer(); }这里等待3帧的目的就是为了等场景中的其他功能对象在Start初始化完毕后、事件都注册完毕后。场景中不属于任何玩家的独立网络对象的实现逻辑先认识 NetworkManager 的spawnPrefabs所有需要同步的网络对象都需要先注册到spawnPrefabs列表下服务器端、客户端都要同步注册到spawnPrefabs列表下并且保证预制体都完全一致.meta文件一致。spawnPrefabs列表下的对象必须挂载NetworkIdentity组件。NetworkManager代码示例//本代码实现【服务端】克隆网络对象在场景中客户端在连接后也会自动同步生成 public GameObject[] sharedPrefabs; public override void OnStartServer() { base.OnStartServer(); foreach (GameObject prefab in sharedPrefabs) { // 在指定位置生成这里用 Vector3.zero 举例 GameObject obj GameObject.Instantiate(prefab, Vector3.zero, Quaternion.identity); NetworkServer.Spawn(obj); } }示例代码中的sharedPrefabs列表里游戏对象都必须先注册到spawnPrefabs列表下。还有一种方法在场景中直接放置好对象然后再 NetworkServer.Spawn对应的对象就直接显示出来了场景中的NetworkIdentity对象默如果没被Spawn默认都是隐藏的。独立网络对象通过挂载自定义的NetworkBehaviour组件并借助[SyncVar]修饰的变量可以在新客户端加入后【服务端】自动同步变量数据给客户端。网络Player生成点位NetworkStartPosition在服务端场景中创建几个空Transform并挂载NetworkStartPosition组件就可作为网络Player预制体一开始的生成点位位置和旋转NetworkManager是借助Unity的Object.FindTypeObj这类API寻找场景中所有NetworkStartPosition对象。有随机选择一个点位生成、轮询选择一个点位生成两种方式。其中随机就是随机选择一个点位。轮询是像队列那样一个点位一个点位的依次选中确保每次选中的点位都和上次不一样。手动调用API是NetworkManager.singleton.GetStartPosition();关于 xxxTransport组件Transport组件 一般和 NetworkMangaer组件 绑在一起NetworkManagaer 组件的配置注意Port参数这个在服务端就是服务器的端口号而在客户端就是要连接的目标服务器的端口号。在NetworkManagaer组件上Network Address这个参数客户端要填的是要连接的目标服务器的IP地址服务器端可以留空不写。① Windows端一般用KcpTransportKCP是实现了稳定传输的UDP② WebGL端 用SimpleWebTransport关于[SyncVar]① 被[SyncVar]修饰的变量只有服务器端能修改并同步给所有客户端。客户端能修改但只是本地修改不会同步。② 使用[SyncVar(hook nameof(方法名))]的形式客户端可以监听到变量的值改动后调用对应的回调方法。注意只有在变量值改变的时候才会回调。注意服务器端回调不管用在变量值变动后没有任何反应。③ [SyncVar]是很重要的独立的网络对象被改动[SyncVar]变量后。新加入的网络Player也能在加入时立即同步一遍最新的[SyncVar]修饰的变量值。2026-09-08补充①有错误并非只能服务器端能修改。NetworkBehaviour对象中存在[SyncVar]变量在其Inspector面板就会发现SyncSettings修改SyncDirection为Client To Server在客户端就能直接修改[SyncVar]变量并同步给服务端和其他客户端但注意本身的客户端也不会调用hook 的方法示例代码public class NetCaiJiObj : NetworkBehaviour { [SyncVar(hook nameof(OnActiveChanged))] [System.NonSerialized] public bool isActive true; //这里使用[System.NonSerialized]是避免被Unity序列化导致默认值修改失效的问题。 ////如果需要服务器端也同步有回调可以再封装一个这个属性 //public bool ser_isActive //{ // get { return isActive; } // set // { // isActive value; // OnActiveChanged(false, value); // } //} public void OnActiveChanged(bool oldVal, bool newVal) { gameObject.SetActive(newVal); } }二、客户端关于 [Command]① 被[Command]修饰的方法表示的是在客户端发起调用具体是在服务器端执行的方法。注意是只能在客户端调用服务端调用会被忽略的。注意客户端调用[Command]的方法即使方法里有内容也不会像调C#方法那样直接调用Mirror已经重写底层逻辑。② 其中[Command(requiresAuthority false)]的形式修饰的方法表示着客户端所有游戏对象的代码都能调用这个方法来命令服务端执行。默认的[Command]是只有网络Player或其下子对象才有权调用的。Command的requiresAuthority参数具体是NetworkIdentity组件来影响的权限网络Player的NetworkIdentity在服务端初始化时就赋予了权限。网络Player预制体只需要在根对象上挂载一个NetworkIdentity组件其下的所有子对象的NetworkBehaviour都是通用这一个NetworkIdentity不需要每个需要同步的子对象都挂载一个NetworkIdentity会报错。注意客户端的任意对象只要通过引用找到目标对象的[Command]方法都能直接调用这个没有权限限制。③ Command 的用法就是客户端发起调用服务端执行客户端那边方法可以不用写任何内容服务端那边写方法内容内容一般就是修改[SyncVar]参数或 调用 网络Player的[ClientRpc]、 [TargetRpc]方法等来实现网络同步指令。关于 [ClientRpc]、[TargetRpc]① 这两种修饰的方法都是服务器端发起调用最终在客户端实际调用的。被这两个修饰的方法在服务器端一般留空不写在客户端具体实现方法内容。② 其中的ClientRpc表示能直接让所有客户端都同步调用一次。TargetRpc表示只让目标的客户端调用一次。具体例子服务端如果调用某个网络Player的ClientRpc修饰的方法所有客户端对应的目标网络Player都会调用一次。如果是服务端调用某个网络Player的TargetRpc修饰的方法只有对应的客户端的网络Player会调用一次。③ 用法一般就是在客户端的网络Player中写好对应的[ClientRpc]、[TargetRpc]的方法内容。并通过[Command]让服务器端再调用自身的[ClientRpc]、[TargetRpc]的方法。NetworkBehaviour 的OnStartClient、OnStartLocalPlayer方法示例代码这两个方法都是在网络Player都初始化完毕后[SyncVar]变量也同步完成后执行先执行OnStartClient、后执行OnStartLocalPlayer。都会比直接用Monobehaviour的Start方法可靠。客户端发送信息服务器接收信息NetworkClient.Send、NetworkServer.RegisterHandler 一对标准操作实现在Mirror中NetworkClient.Send 和 接收数据RegisterHandler 是一对标准操作。它的核心逻辑是先定义好消息结构在客户端注册好接收函数然后就能通过 Send 发送数据了。 下面是一个最基础的示例可以帮你快速上手。 1. 定义消息结构 消息必须是一个 struct结构体并实现 NetworkMessage 接口里面用公共字段存放数据。 csharp using Mirror; // 1. 定义消息结构体 public struct PlayerIdentityMessage : NetworkMessage { public string playerName; public int skinIndex; } 2. 客户端发送数据 在客户端脚本中准备好数据后通过 NetworkClient.SendT(T message) 发送给服务器。 csharp // 客户端脚本中 void SendMyIdentity() { // 构造消息 PlayerIdentityMessage msg new PlayerIdentityMessage { playerName Player_123, skinIndex 1 }; // 发送消息给服务器 NetworkClient.Send(msg); } 3. 服务端接收数据 在服务端脚本的初始化方法如 Awake 或 OnStartServer中需要用 NetworkServer.RegisterHandlerT() 注册对应的处理函数。 csharp public class MyServerManager : NetworkManager { private void Awake() { // 在服务端注册消息处理器泛型指定为你的消息类型 NetworkServer.RegisterHandlerPlayerIdentityMessage(OnReceivePlayerIdentity); } // 接收消息的处理函数 private void OnReceivePlayerIdentity(NetworkConnectionToClient conn, PlayerIdentityMessage msg) { // conn 代表发送消息的客户端连接 // msg 包含了客户端发送的数据 Debug.Log($收到来自 {conn.connectionId} 的玩家信息{msg.playerName}皮肤索引{msg.skinIndex}); // 在这里处理收到的数据比如存储起来等待生成玩家时使用 } } ⚠️ 关键细节提醒 接收函数的签名处理函数的参数必须是 (NetworkConnectionToClient conn, YourMessageType msg) 这种形式否则注册会报错。 注册顺序一定要确保服务端先执行了 RegisterHandler客户端再发送消息否则发送的消息可能会被丢掉。 数据通道默认走可靠通道Reliable保证数据一定会到达。如果不指定通道类型就是安全的可靠传输。