☰
Unity Mirror 个人笔记(基础)
2026/9/29 3:30:38 网站建设 项目流程

一、服务器端

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.GetComponent<NetPlayer>().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的identity(NetworkIdentity)做些什么,就应该在 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端一般用KcpTransport(KCP是实现了稳定传输的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.Send<T>(T message) 发送给服务器。 csharp // 客户端脚本中 void SendMyIdentity() { // 构造消息 PlayerIdentityMessage msg = new PlayerIdentityMessage { playerName = "Player_123", skinIndex = 1 }; // 发送消息给服务器 NetworkClient.Send(msg); } 3. 服务端接收数据 在服务端脚本的初始化方法(如 Awake 或 OnStartServer)中,需要用 NetworkServer.RegisterHandler<T>() 注册对应的处理函数。 csharp public class MyServerManager : NetworkManager { private void Awake() { // 在服务端注册消息处理器,泛型指定为你的消息类型 NetworkServer.RegisterHandler<PlayerIdentityMessage>(OnReceivePlayerIdentity); } // 接收消息的处理函数 private void OnReceivePlayerIdentity(NetworkConnectionToClient conn, PlayerIdentityMessage msg) { // conn 代表发送消息的客户端连接 // msg 包含了客户端发送的数据 Debug.Log($"收到来自 {conn.connectionId} 的玩家信息:{msg.playerName},皮肤索引:{msg.skinIndex}"); // 在这里处理收到的数据,比如存储起来等待生成玩家时使用 } } ⚠️ 关键细节提醒 接收函数的签名:处理函数的参数必须是 (NetworkConnectionToClient conn, YourMessageType msg) 这种形式,否则注册会报错。 注册顺序:一定要确保服务端先执行了 RegisterHandler,客户端再发送消息,否则发送的消息可能会被丢掉。 数据通道:默认走可靠通道(Reliable),保证数据一定会到达。如果不指定通道类型,就是安全的可靠传输。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询