在大世界项目以及疫情导致的远程办公需求,开发中的协同编辑显得格外重要。在UE的资源机制中,地图是单个的资源,虽然可以使用Sub-level的形式拆分,但是最小元素仍然是单个资源,当不同的Designer修改相同的资源时,就会造成文件冲突,也没办法像文本那样合并。并且每个人负责编辑单个子关卡也没有办法实时预览整个场景的效果,在协同开发中,这是一个瓶颈。
在UE4.23之后,UE官方推出了一个多人协作机制,可以使多人共同地编辑同一份地图,并不会造成冲突,并且可以同步其他变动的资源并能够实时生效,作为版本控制的补充,能够比较完美地解决同步协作的问题。
本篇文章记录一下启用流程、使用规范以及网络策略等问题,也会提取独立的Server端程序而无需依赖完整的引擎,实现Server端的方便部署。
UE官方多用户编辑,其实现是一个C/S架构的方案,官方文档:Multi-User Editing。
本质上是创建一个Server进程,用于管理多用户编辑的Session,并向各个Client之间传递相互变动的数据。每个Session代表着协同编辑的一个“房间”,只有加入到这个“房间”的人,才能够实时地预览和同步其他人的提交,每个Server上可以创建多个Session,可以由不同的用户创建、加入。
注意:UE并不是直接把变动的资源拷贝到各个Client,而是通过传递变动的数据到各个Client实现相同的表现,在传输效率上更好,但会带来一个问题,对于相同操作的资源,对于uasset来说,并不完全相同,即A用户修改了Weapon.uasset,将会把变动的数据同步到B用户,如果A和B用户都提交了Weapon这个资源,他们的二进制并不完全相同,在合并提交时会有冲突,提交规范还是需要注意的。
使用流程
多用户编辑支持,依赖UE的Multi-User Editing
插件:
启用之后可以在Project Settings
-Plugins
中看到Multi-User Editing
和Multi-User Transactions
选项:
能够配置多用户协作的参数。
在开启插件之后,可以在编辑器Windows
-Developer Tools
中看到Multi-User Browser
选项(也可以在Project Settings
-Plugins
-Multi-User Editing
中开启Enable Multi-User Toolbar Button
):
启动之后会自动搜索网络内可见的UnrealMultiUserServer
服务器:
也可以通过Launch a Server
拉起一个UnrealMultiUserServer
进程,在本地创建一个Server。通常情况下,会有一个独立的机器作为Server,供其他用户连接。
Mulit-User Browser
作为Client连接Server时,使用的是UDP协议,默认连接端口号为6666
(Launch a Server
启动默认是6666),也可以自己修改想要指定的端口号,Client会自动检测网络内可达的Server列表,创建Session时可以选择在哪个Server上创建。
注意:Mulit-User Browser
依赖UDP Messaging
插件作为网络传输的方式,所以,如果我们想要修改默认连接的端口号,就需要修改UDP Messaging
中的默认端口号。
可以在Project Settings
-Plugins
-Networking
中修改(将230.0.0.1:6666
修改为230.0.0.1:XXXXX
即可):
注意:230.0.0.1是RFC 5771(IPV4)和RFC 4291(IPV6)定义的多播地址,不能改成其他的地址。
在编辑器中修改UDP Messaging
的端口设置,默认是被保存在Saved/Config/Windows/Engine.ini
下:
1 | [/Script/UdpMessaging.UdpMessagingSettings] |
并不会存储在项目的Config
中,如果想要该配置随着项目提交,可以把上面的配置添加到Config/DefaultEngine.ini
中(Enable Multi-ser Toolbar Button
也同理)。
修改完毕后,启动UnrealMultiUserServer即可使用变动后的端口:
网络策略
Multi-User Editing主要是面对相同的LAN或者VPN网络的,但是,针对同一LAN下的不同类型的网络类型,有些区别。
LAN的相同子网
如果Server和Client全部在相同的子网,如Server在192.168.2.123,Client在192.168.2.X,这种情况下,只需要把UnicastEndpoint
设置为0.0.0.0:0
或者192.168.2.X:0
,MulticastEndpoint
保持为230.0.0.1:11111
这种形式即可。
这种配置就能够顺利地在Client搜索到Server,也是前文中介绍的方法。
LAN的不同子网
对于Server与有些Client在同一子网,另一些Client又不在同一子网的情况,就复杂一些。
如:
Server:192.168.2.123
Client A:192.168.2.124
Client B:192.168.3.125
对于这种情况,如果使用上一节在相同子网的配置,会导致Client B搜索不到Server,针对这种情况,需要单独设置UnicastEndpoint
。
首先,Server端在启动时,不能再把UnicastEndpoint
设置为0.0.0.0:0
,需要指定当前Server的IP和链接端口,如:
1 | Engine/Binaries/Win64/UnrealMultiUserServer.exe -UDPMESSAGING_TRANSPORT_UNICAST=192.168.2.123:11112 -UDPMESSAGING_TRANSPORT_MULTICAST=230.0.0.1:11111 |
客户端则需要在UE编辑器的Project Settings
-Plugins
-UDP Messaging
中设置Server的Static EndPoints
:
Static EndPoints
的值要是ServerIP:UNICAST_PORT
这种形式,如上面的Server启动配置192.168.2.123:11112
。
进行这种配置之后就可以在同一LAN的不同子网搜索到启动的Server了(但也需要注意把UNICAST/MULTICAST的端口加入防火墙的放行端口)。
防火墙策略
注意,因为Multi-User Editing使用的是UDP协议,所以指定的端口必须被防火墙放行才能够检测到Server。
Windows可以使用以下批处理进行添加防火墙规则,保存为.bat
使用管理员权限执行即可:
1 | netsh advfirewall firewall add rule name="allowMultiUserServer" protocol=TCP dir=out localport=11111,11112 action=allow |
Mac和Linux可以使用ufw进行防火墙配置:
1 | $ ufw allow 11111/udp |
独立的Server进程
前面介绍是从UE编辑器中通过Multi-User Browser
中Launch a Server,但是对于Server而言,其实并不需要启动编辑器,UE也是通过拉起一个新的进程传递命令行参数来启动Server的,所以,我们可以自己使用命令行来启动Multi-User Server
:
1 | Engine/Binaries/Win64/UnrealMultiUserServer.exe -UDPMESSAGING_TRANSPORT_UNICAST=0.0.0.0:0 -UDPMESSAGING_TRANSPORT_MULTICAST=230.0.0.1:11111 |
Server的配置可以通过命令行参数或者配置文件的形式指定,独立的Server进程的配置文件在:
1 | [/Script/UdpMessaging.UdpMessagingSettings] |
与通过命令行指定是一样的。
独立的Server程序
因为UE的Multi-User Server会拉起一个独立的UnrealMultiUserServer
进程,需要我们完整地把引擎Clone下载之后才能启动Multi-User Server
程序,对于我们的需求而言,很浪费,因为我们并不需要完整的引擎功能,只是需要这个独立的Server程序而已,所以可以想办法把它抽离出来。
UnrealMultiUserServer
是引擎中的一个Programs
类型的程序,我之前有一些文章介绍相关的内容:Create A Standalone Application in UE4。它本质上是用了UE的代码库和插件的独立程序,和UE4Editor是一种类型的程序,但是默认情况下它的LinkType
是Modular
的,即代码中引用的的模块功能都在每个独立的DLL中,这也符合UE编辑器的设计模式。
所以,默认情况下UnrealMultiUserServer
程序只有几十kb,具体的功能实现都是使用动态链接库方式调用DLL的,我们需要修改编译的模式,把它用到的引擎模块都编译到一个exe中,修改UnrealMultiUserServer.targetr.cs
:
1 | // from |
改成Monolithic
后再执行编译,UBT就会把该Program引用到的模块最终链接一个可执行程序。这部分内容在我之前的另一篇文章中有介绍:UE Build System:Target and Module
这个操作只能解决引擎内模块的引用,UnrealMultiUserServer
还引用了其他的插件,在target.cs
里开启了Program的插件支持:
1 | bCompileWithPluginSupport = true; |
至于具体依赖了哪些插件,可以打开UnrealMultiUserServer.target
查看其中的RuntimeDependencies
项:
1 | "RuntimeDependencies": [ |
表示UnrealMultiUserServer
引用了这几个插件,需要把引擎中的这几个插件目录放到独立程序的相应目录下(与引擎中的相对路径保持一致)。
但是这样还不够,需要把插件加入到启用列表,不然程序启动时不会加载插件,导致模块加载错误:
1 | LogPaths: Warning: No paths for game localization data were specifed in the game configuration. |
解决的办法就是在target.cs
里添加EnablePlugins
列表:
1 | EnablePlugins.AddRange(new string[] |
这样我们抽离出来的独立程序就会在程序启动时加载需要的这两个插件了。
最后,还需要把原本引擎中Engine/Content
下的Internationalization
/Localization
两个文件夹提取出来。
我们也不需要代码和调试符号,可以删除多余的文件:
1 | find . -name "Source"|xargs rm -rf |
最终独立的UnrealMultiUserServer
程序的目录结构为:
1 | +---Binaries |
执行效果:
1 | C:\Users\lipengzha\Desktop\UnrealMultiUserServer\Engine\Binaries\Win64>UnrealMultiUserServer.exe |
Session的配置存储
当Multi-User Server
启动之后,所有由用户创建的Session信息都会被保存在Server端,默认存储在以下路径中:
1 | C:\Users\lipengzha\Desktop\UnrealMultiUserServer\Engine\Programs>tree /a /f |
当下次Server启动的时候这些Session会自动创建,可以避免需要重复创建的问题。
结语
本篇文章记录了UE中多用户编辑的启动和配置流程,并提取了独立的Multi-User Server程序,无需完整的引擎内容,能够实现非常方便地部署多用户编辑服务。后续有时间会深入分析一下内部的实现机制以及同步策略。