原创文章,转载请注明出处:https://blog.csdn.net/weixin_37864449/article/details/89096536
先上一张镇楼图:
一:UE4两种联机方式
ue4有两种联机方式,一种是局域网联机,另外一种是外网联机,需要有独立的服务器。但是无论是何种联机方式,都是只有一个服务器端,区别在于局域网联机中客户端和服务器端都同在CreateSession那台机器上,其他joinSessiion的为客户端,独立服务器的顾名思义客户端和服务器端是相互独立的。
二:联机之前需要了解的蓝图类
1.playerController类,键盘,鼠标等外界输入事件的发生都是由playerController这个类调用的,另外服务器端要找到特定的客户端只能通过playerController,上图的一对一映射关系说明了这一点,客户端找到服务器端通过下面即将说到的RPC远程调用方式实现。
2.gameMode类,gameMode类只存在于服务器端,即联机运行时gameMode类写的事件逻辑只在服务器端运行,控制整个游戏运行规则和流程。
3.playerState类,playerState相当于白皮书,,在worldSetting的gameMode属性中指定当前关卡的playerState类,游戏运行时白皮书会分发(实例化)到各个pawn中,也就是说各个pawn拿到的白皮书的内容(类中的属性变量等)是一样的,所以playerState适合于记录所有pawn都共有的属性或方法,如果指定了playerState类replicates属性为true,那么只要服务器端某pawn改变了自身的playerState属性,那么所有客户端中这个pawn的playerState中该属性都被改变了,当然replicates属性不仅存在于playerState中,playerController,actor,pawn,character类都有replicates属性,也就是说在类层面上设置replicates属性为true,那么服务器端一但产生这个类实例,所有客户端也能看到这个类的实例,除此之外这些类成员变量也有replicates属性,当设置为true时,只要服务器端改变这个成员变量取值,所有客户端该成员变量取值也会随之被改变,但在客户端改变成员变量取值,并不会自动对服务器或其他客户端该成员变量取值产生任何影响,如果想产生影响,需要用户手动通过RPC来同步这种改变。
4.gameInstance类,这个类存在于客户端和服务器端中,生命周期自游戏创建以来就一直存在,但没有同步机制,即改变服务器端gameinstance某个成员变量取值不会对客户端的有任何影响。
ps:上面说到的playerController,gameMode,playerState类都要在关卡worldSetting中GameMode栏设置,因为他们的生命周期仅存在关卡中,gameInstance类需要在Edit->Project Setting...->Maps&Modes中最下面的gameinstance栏设定。
关系图:
ps:这里的是否可复制表示服务器端的变化是否会复制到所有客户端,生命周期指的是不同生命周期类可以不一样,对于生命周期为关卡的类指定在每个关卡worldsetting中的gamemode栏设置。
三:联机流程
客户端一但joinSession到指定的服务器中,就会触发服务器端gameMode类中的eventOnPostLogin事件,eventOnPostLogin事件会实例化一个playerController类输出,该类和登录到服务器的那个客户端相对应(即映射),注意,这里实例化的一个playerController和对应登录的客户端的playerController不是同一个东西,即改变客户端的playerController某个成员变量取值再登录,服务器端实例的playerController该成员变量并不会受到影响,还是类定义时playerController中该成员变量取的默认值,因为是重新按类模板实例化后再和客户端对应起来的,这个playerController只能看到客户端登录后遗留在服务器端的占位符,借此与客户端的playerController建立连接。服务器端实例化后这个playerController是不会保存在引擎中的,所以需要在gameMode中定义一个playerController数组变量,每eventOnPostLogin一次就将输出的playerController加到数组变量中,以后就可以通过服务器端的playerController来和对应客户端的playerController通信了,反之也行。
四:通信机制
ue4最常用的客户端与服务器端或服务器端与客户端的通信是通过RPC(Remote Procedure Call)机制进行的,分三种通信方式:
- 客户端调用运行在服务器端的自定义事件(事件replicates属性设置为runOnServer)从而与服务器端通信
- 服务器端调用运行在指定客户端的自定义事件(事件replicates属性设置为runOnOwningClient)与客户端通信
- 服务器端调用广播自定义事件(事件replicates属性设置为multicast)和所有客户端通信
ps:可见客户端与客户端通信必须经过服务器端!!
- 所以以后所说的在客户端运行是指客户端playerController控制的对象逻辑或在服务器端定义的replicates属性为runOnOwningClient或multicast事件或客户端playerController中处理的逻辑过程
- 以下所说的在服务器端运行是客户端定义了一个replicates属性为runOnServer的事件或是在gameMode类中或服务器端的playerController中处理的逻辑过程
4.1 服务器端playerController和对应客户端的playerController相互通信:
首先在服务器端的playerController类中自定义一个事件,事件replicates属性设置为runOnOwningClient,因为服务器端playerController是和客户端的playerController一 一对应的,所以事件触发时它是知道应该在哪个客户端运行的,注意事件的触发运行是不能人为干预的,即只要服务器端一调用,相应的客户端就会自动触发该事件执行,事件定义好后应该在服务器端调用。同理客户端和服务器端通信时可以在客户端playerControler中自定义一个replicates属性为runOnServer的事件,然后调用该事件,这样该事件会在和客户端对应的服务器端的playerController中触发执行。
ps:事件replicates属性设置为runOnServer的事件及其调用
ps:事件replicates属性设置为runOnOwningClient的事件及其调用
4.2 客户端pawn和服务器端pawn相互通信。(当然不一定是pawn,actor类,character类也可以,这里以pawn来举例)
介绍通信之前首先介绍pawn产生机制,pawn可以通过spawnActorFromClass产生,pawn如果replicate属性设置为true,那么在服务器端产生一个pawn,相应的所有客户端也会产生这个pawn。另外如果playerController类中replicate属性设置为true,那么只要服务器端playerController调用Possess绑定一个pawn,那么和服务器端playerController对应的客户端playerController也会绑定这个pawn,所以pawn的产生和控制器的绑定只要在服务器端完成即可,如可在gameMode类中进行这些操作,或在客户端的playerController中自定义的replicate属性为runOnServer的事件中进行这些操作,那么就可以同步到所有客户端。
下面正式介绍客户端pawn和服务器端pawn的相互通信。和4.1所说的方式同理,都是RPC通信的过程,区别在于多了一个replicate属性为multicast即多播的自定义事件类型,少了一个属性为runOnOwningClient的自定义事件,因为服务器端产生n个pawn,那么每个客户端都会得到这n个pawn的副本,见最上面的镇楼图。所以服务器中某个pawn并不和某个客户端的pawn存在映射关系,而是和所有客户端该pawn都存在映射,通过多播自定义事件可以当服务器端pawn某个属性改变时及时同步所有客户端中这个pawn改变相关属性。
在pawn中定义的multicast属性事件:
在gamemode中调用该事件:
当然也可以在客户端定义的Replicate属性为runOnServer的自定义事件中调用replicate属性为multicast的自定义事件,因为上面红色字体第二点中说明了这均代表在服务器上运行。