前段时间,研究了一下UNet,通过项目实践,大体整理了下遇到的问题。服务器
源码Bitbucket:须要说明的是,这个项目只包含上层的包装,一些低层的网络实如今Unity内部,如NetworkTransport类等并不包含。网络
Spawn:简单来讲,把服务器上的GameObject,根据上面的NetworkIdentity组件找到对应监视链接,在监视链接里生成相应的GameObject.异步
Command:客户端调用,服务器执行,这样客户端调用的参数必须要UNet能够序列化,这样服务器在执行时才能把参数反序列化。须要注意,在客户端须要有权限的NetworkIdentity组件才能调用Command命令。ide
ClientRpc:服务端调用,客户端执行,同上,服务端的参数序列化到客户端执行,通常来讲,服务端会找到上面的NetworkIdentity组件,肯定那些客户端在监视这个NetworkIdentity,Rpc命令会发送给全部的监视客户端。函数
Server/ServerCallback:只在服务器端运行,Callback是Unity内部函数。动画
Client/ClientCallback:同上,只在客户端运行,Callback是Unity内部函数。spa
SyncVar:服务器的值能自动同步到客户端,保持客户端的值与服务器同样。客户端值改变并不会影响服务器的值。设计
上面的大部分特性都会转化成相应的MsgType,其中服务器调用,客户端执行对应MsgType有如Spawn,ClientRpc,SyncVar对应的MsgType分别为ObjectSpawn,Rpc,UpdateVars,这些都是NetworkServer调用,客户端获得相应消息,执行相应方法。客户端调用,服务器执行的MsgType有如Command,客户端发送,服务器检测到相应消息后执行。orm
NetworkIdentity组件介绍:网络物体最基本的组件,客户端与服务器确认是不是一个物体(netID),也用来表示各个状态,如是不是服务器,是不是客户端,是否有权限,是不是本地玩家等。对象
一个简单例子,A是Host(又是服务器,又是客户端),B是一个Client,A与B分别有一个玩家PlayA与PlayB.在机器A上,playA与playB isServer为true,isClent为true,其中playA有权限,是本地玩家,B没权限,也不是本地玩家。在机器B上,playA与playB isServer为false,isClent为true,其中playB有权限,是本地玩家,A没权限,也不是本地玩家。A与B上的PlayA的netID相同,A与B上的PlayB的netID也相同,其中netID用来表示他们是同一网络物体在不一样的机器上。
在下面用网络物体来表示带有NetworkIdentity组件的GameObject.
NetworkConnection:定义一个客户端与服务器的链接,包含当前客户端监视那些服务器上的网络物体,以及封装发送和接收到服务器的消息。
NetworkClient:主要持有当前NetworkConnection对象与全部NetworkClient列表的静态对象,处理一些默认客户端的消息。
网络物体上的监视者就是一个或多个NetworkConnection,用来表示一个或多个客户端对这个网络物体保持监视,那么当这个网络物体在服务器上更新后,会自动更新对全部监视者的对应的网络物体。
NetworkScene:简单来讲,1Server与Client须要维护一个网络物体列表,Server能够遍历全部网络物体发送消息等,而且维持Server与Client上的网络物体保持同步,而且客户端记录须要注册的prefab列表.其中NetworkServer与ClientScene都包含一个NetworkScene对象,引用网络物体列表。
NetworkServer:主要持有一个NetworkScene而且作一些只有在服务器上才能对网络服务作的事,如spawn, destory等。以及维护全部客户端链接。
ClientScene:主要持有一个静态NetworkScene对象,用于注册网络物体的prefab列表,以及客户端场景上已经有的网络物体列表,处理SyncVar,Rpc,SyncEvent特性等,还有以及ObjectSpawn,objectDestroy,objectHide消息等。
1 当服务器spawn一个网络物体时,网络物体调用OnStartServer,分配netID.并注册到相应服务器上的的NetworkScene的网络物体列表中,更新如isServer为true等信息。
2 查找全部客户端链接,查看每一个客户端链接是否须要监视这个网络物体,若是为true,那么给这个客户端上一个消息MsgType.ObjectSpawn或是MsgType.ObjectSpawnScene(这种通常是服务场景变换后自动调用),并传递上面的netID.
3 当客户端接受到ObjectSpawn消息,会在注册的prefab里查找,查找到后Instantiate个网络物体,当接受到ObjectSpawnScene时,会在场景里查找这个网络物体,而后都注册到ClientScene里的NetworkScene的网络物体列表中,并更新netID与服务器的同样。更新如isClent为true等信息。
咱们手动spawn一个物体时,调用的是ObjectSpawn消息,客户端接到这个消息处理获得一个assetID,咱们要根据prefabe实例一个新对象,只有客户端注册了相应的prefabe信息才能根据对应的assetID找到prefabe.
当服务器与客户端的netID相同,表示他们是同一物体,相应标示如SyncVar,服务器变了,对应客户端上相同的netID的网络物体,更新成服务器上的数据,Rpc,Commandg 通常也是相同的netID之间调用。
分配通常发生在服务器spawn一个网络物体时,网络物体调用OnStartServer时发生产生netID。
在客户端接受相应的ObjectSpawn消息,会把服务器上的对应物体的netID传递过来,产生新的网络物体并赋这个netID。
当网络物体并非spawn产生在服务器与客户端,而是在服务器与客户端场景自己就有时,咱们也须要在服务器与客户端之间创建联系,这种物体会有一个sceneID来标示,这种模型通常是服务器场景变换完成后,NetworkServer调用spawnObjects会把这种网络物体与全部客户端同步,当spawn完成后事后,相应客户端会产生一个和服务端相同的netID。
以下顺序由于有异步操做,并不能肯定,以下顺序只是通常可能的顺序。
1。服务器异步调用场景,发送给全部客户端开始切换场景。MsgType.Scene
2。客户端接受MsgType.Scene,开始切换场景。
3。服务器场景完成,会查找全部的网络物体,而后spawn这些网络物体,这样各个网络物体经过相同的netID联系起来。
4。客户端场景完成后,再次调用OnClientConnect,通常来讲,不执行任何操做。
通常来讲,当spawn某个服务器上的网络物体后,服务器有它的权限,客户端并不能更改这个网络物体,或是说更改这个网络物体相应的属性后并不能同步到服务器和别的客户端上,只是本机上能看到改变。
那么我若是须要能改变这个网络物体上的状态,并能同步到全部别的客户端上,咱们须要拥有这个网络物体的权限,由于这样才能在本机上发送Command命令,才能告诉服务器我改变了状态,服务器也才能告诉全部客户端这个网络物体改变了状态。
其中本地player在建立时,当前客户端对本地player有权限。客户端上有权限的网络物体上的SyncVar改变后,也并不会能同步到服务器,服务器根本没有注册UpdateVars消息,这种仍是须要客户端本身调用Command命令。
UNet常见的封装状态同步状态方法有二种。
一是经过ClientRpc与Command是封装发送消息。客户端与服务端一方调用,而后序列化相应的参数,而后到服务器与客户端反序列化参数执行。
二是网络内置的序列化与反序列化,序列化服务器的状态,而后客户端反序列化相应的值,如SyncVar经过相应的OnSerialize,OnDeserialize.这种只能同步服务器到客户端。
这二种本质都是客户端与服务器互相发送MsgType消息,对应的服务器与客户端注册相应消息处理。NetworkAnimator 服务器上的动画改变,会发消息通知全部客户端相应状态改变了,如Rpc。NetworkTransform 服务器经过OnSerialize序列化相应的值,而后客户端反序列化相应的值。
若是客户端有对应NetworkTransform与NetworkAnimator网络物体的权限。NetworkAnimator 相应客户端提交状态到服务器上,而后分发到全部客户端,至关于调用了Command,并在Command里调用了Rpc方法。NetworkTransform 相应客户端发送消息到服务器上,服务器更新相应位置,方向。而后经过反序列化到全部客户端。
因此若是客户端有受权,那么NetworkAnimator与NetworkTransform在服务器或是有受权的客户端的状态改变都能更新到全部客户端,注意这二个组件对localPlayerAuthority的处理不一样,在NetworkTransform中,localPlayerAuthority为false时,客户端不能更新到全部客户端,在NetworkAnimator中,localPlayerAuthority为true时,服务器不能更新到客户端上。
其中注意SyncVar特性,就算客户端受权,客户端改变后,也不会同步到别的机器上。
因此若是咱们本身设计相似的网络组件,须要考虑客户端受权的相应处理,就是差很少添加一个Command命令。
通常物体的权限都在服务器上,若是要对网络物体受权给客户端,通常经过SpawnWithClientAuthority实现,这样在相应客户端上的hasAuthority为true,其中相应的playerControllerID为-1。
而本地player受权localPlayerAuthority,在相应的网络物体上的Local Player Authority勾选上,在对这个网络物体的全部监视客户端上,本地player受权都是true,这种通常用于玩家,或是玩家控制位移的物体,playerControllerID大于等于0。
因此客户端受权针对是某个客户端,在这个客户端上的这个网络物体的hasAuthority为true,而本地player针对是某个网络物体,在全部客户端上的这个网络物体的localPlayerAuthority都为true.
经过NetworkProximityChecker,这样每桢检测当前网络物体的监视链接,肯定那些客户端须要这个网络物体。一样,想实现更复杂的能够本身实现相似。
必须是网络物体,且最好能在服务器调用,调用时,发给全部的监视Connect,销毁对应网络物体,而后服务器销毁。请看MsgType.ObjectDestroy消息流程.
须要注意的是在服务器中,Destroy某网络物体,会自动调用NetworkServer.Destroy。代码在NetworkIdentity.OnDestroy.
当客户端链接服务器时,设置自动建立角色后,会自动建立角色。
1 服务器添加一个player,设定playercontrollerID
2 设置当前conn的ready为true.而后检测当前的conn是否须要监视服务器上NetworkScene的网络物体列表的各个网络物体,其中客户端上的isspawnFinished表示NetworkScene的网络物体列表是否检测完成。
3 把服务器的player的spawn下去,设定对应网络物体记录的本地权限客户端为当前客户端,相应的playercontrollerid发送到客户端。
networkServer开始监听后,设定active为true。
networkClient链接上服务器后,设定为true。
当有些消息发送,或是Rpc与Command等的调用时,时机可能会在active以前,引起错误。
1 服务器更新,处理一些如客户端连接与丢失连接,还有接收消息并找到对应事件处理,以及序列化服务器网络物体要更新的数据。
2 客户端更新,如上服务器的处理,主要也是相应消息处理。
3 检查服务器与客户端的场景是否加载完成。
最后,想象一下,在网络环境下,咱们拉开弓箭,生成箭,箭在客户端上缓缓拉开,咱们应该如何作?
首先弓箭要让全部客户端看的到,咱们要在服务器上生成,而后spawn分发到相应多个客户端,而后当前客户端还须要当前箭的权限,这样当前用户才能控制这把箭,并把当前用户控制箭产生的新位置同步给全部的客户端。
其次若是采用Valve的LabRender渲染器,须要在开始服务器时关闭,等到对应的角色加载后,再经过localplayer打开各自对应的valveCamer,否则服务器上的valveCamer可能得不到正确的阴影图。
若是有分析不对的地方,欢迎你们指出。