连接 和断开连接
这个示例是一个简单的测试客户端与服务器连接、断开连接的功能。发布出的程序,既可以作为客户端,也可以作为服务器。所以在测试时候,需要运行两个程序实例。其初始界面如下:
上面两条是填写相应的IP地址和端口号,如果以服务器运行,就点Start Server,程序就会开始监听指定端口。
如果以客户端来运行,就点Connect as client。一般用Build后的程序来运行客户端,用编辑器来运行服务器,当有客户端成功连接到服务器,会在编辑器的Console窗口中出现类似如下信息:
Player connected from: 127.0.0.1:1350
实现过程中,需要在编辑器建立一个空的GameObject,这里起名叫Scripts。然后为其附加连接的脚本Connect.js。其中两个可设置的属性connectToIP、connectPort是脚本的两个公共变量,在脚本中声明如下:
var connectToIP : String = "127.0.0.1";
var connectPort : int = 25001;
然后在GUI的处理函数OnGUI中,先根据当前的网络状态决定不同的UI显示布局,网络状态可以用Network.peerType来获得,可以有4种状态Disconnected、Connecting、Client、Server,分别表示断开连接的状态、尝试连接服务器的状态、作为客户端运行的状态、和作为服务器运行的状态。这里在Disconnected状态下显示初始界面,这里比较重要的逻辑是Start Server按钮和connect as client按钮的处理。
如果点击了Start Server按钮,就开启服务器:
Network.useNat = false;
Network.InitializeServer(32, connectPort);
Network是Unity一个内置类,其中useNat表示是否启用NAT机制。之后在启动服务器的函数InitializeServer中传入两个参数,第一个表示允许最大的连接数量,第二个就代表端口号。
这样就可以开始监听指定端口了。
如果点击connect as client,就进行如下操作:
Network.useNat = false;
Network.Connect(connectToIP, connectPort);
这里的操作也很简单,只需要调用Network的Connect函数指定连接到的IP和端口号就可以连接到服务器端。
当服务器端程序接到来自一个客户端连接后,会触发unction OnPlayerConnected(player: NetworkPlayer)事件函数,所以为脚本添加这个函数,在这个函数处理中,就在编辑器的Console中显示之前看到的连接信息。
function OnPlayerConnected(player: NetworkPlayer) {
Debug.Log("Player connected from: " + player.ipAddress +":" + player.port);
}
其中的NetworkPlayer类的对象,就包含了这个连接到服务器上的客户端的信息,包括他的IP、端口、GUID、外部IP端口等。
状态同步:
这个示例实现了基本的状态同步的方法,客户端和服务器仍然用的相同的程序。
通过Start Server启动服务器的实例,然后可以用方向键或者wsad控制方块的移动,然后启动另一实例作为客户端,点击Connect as client连接到服务器。这时候通过服务器和客户端之间对筛子的状态同步,当服务器移动方块时候,可以看到客户端程序中的筛子在同步的移动。
实现中仍然先按照连接示例中的方法添加一个物体和附加连接Connect脚本:
然后在Unity创建方块的模型,给模型的对象添加Network View的组件:
为了使用状态同步机制,需要设置State Synchoronization,这里有三个选项一个Off,Reliable Delta Compressed和Unreliable。Off代表不想使用状态同步,后两种是状态同步的两种不同的传输模式,一个是基于差异的压缩,另一个是非可靠的机制。两种都可以实现所需功能,只是根据对带宽、延迟等要求不同决定具体用哪种。
下面是设置Observed,这个属性代表想要同步这个物体的哪一部分数据,其他部分并不会通过网络同步。这里为了保持方块的位置、朝向等信息一致,所以只需要将Transform的组件拖拽到Observed上就可以了。
然后需要实现Server对物体控制移动的逻辑,所以给方块的物体再增加一个脚本
在每帧都会调用的Update()函数中判断如果当前实例是Server,那么就处理移动的输入,更新物体的位移。这样只有Server会控制物体,客户端只根据Server物体的状态同步数据。
if(Network.isServer){
var moveDirection : Vector3 = new Vector3(-1*Input.GetAxis("Vertical"), 0,Input.GetAxis("Horizontal"));
var speed : float = 5;
transform.Translate(speed * moveDirection * Time.deltaTime); }
这样就完成了示例所演示的功能,实现了基本的状态同步的功能。
补充说明:
除此之外,也可以同步Animation、Rigidbody、脚本几种组件类型。
Animation组件同步时会同步动画的播放时间、权重、播放速度,以及是否启用等信息。
Rigidbody组件会同步位置、旋转、速度和角速度信息。
脚本同步中会调用OnSerializeNetworkView()函数,在这里实现自定义数据的收发。
底下的ViewID是只读的,代表这个NetworkView在网络中的唯一ID。每个到达客户端的封包需要应用与一个Network View. Network View ID唯一的区分这个封包属于哪个Network View,Unity会解压封包内容,将其应用到对应的Network View上.
Type显示这个Network View是存到场景中的,还是动态分配的。
RPC通信:
这一实例实现了和状态同步实例同样功能,但是用的机制是RPC通信。
开始的步骤仍然和以前一样,先增加连接的GameObject和附加脚本
然后同样对方块的模型附加Network View组件:
但是这次因为不用状态同步,所以在State Synchoronization和Observed分别设为Off和None。
然后同样对方块附加控制脚本
这次为了实现RPC,在这个脚本里除了要处理操作方块移动的逻辑,还要提供所被调用的RPC函数,以及处理调用的时机。
首先是声明一个RPC函数:
这里需要在函数上方增加@RPC标示,让编辑器认出这是RPC函数。参数的类型和数量是可以自定义的,这里只需要传输位置信息,所以只有一个Vector3的参数。接收到的客户端,会用新的Pos,来更新此物体transform组件的position信息。
在调用的情况下,因为这里只需要Server调用Client,所以在Update()函数中,先判断是不是Server如果是的话,处理完移动的逻辑后,然后才调用RPC
调用的方法是:networkView.RPC("SetPosition", RPCMode.Others, transform.position);
networkView就是给方块物体附加的NetworkView组件,其下的RPC函数就是调用远程方法用的。
第一个参数是调用的方法名称,第二个参数代表调用的对象,有几个枚举值,分别是:Server,Others,OthersBuffered,All,AllBuffered。
之后的参数就是需要传入调用RPC的参数。
这样就完成了RPC调用的实现,将位置信息实时传送到客户端。
补充说明:
RPC调用对象的枚举RPCMode:
Server:只调用Server的RPC
Others:会调用除发送者外的其他所有人
OthersBuffered:会调用除发送者外其他所有人,并且添加到缓冲区。
ALL:代表所有人,包括Server和Client
AllBuffered:的除了调用其他所有人外,同时添加到缓冲区。
缓冲RPC的作用是对于后来新来的连接,同样会接收到被缓冲的RPC调用。比如一个常见的情况是,一个Client加入到一个Server里,需要加载一个指定关卡,这样就发送RPC给Client和Server,同时添加到缓冲区。当有新的Client也加入进来时候,就会自动接收到刚才的RPC,加载同样的关卡。
欢迎到论坛参与提问与讨论!~~~~
原帖地址:http://bbs.9ria.com/thread-124775-1-1.html