UE多用户协同编辑服务部署指南

在大世界项目以及疫情导致的远程办公需求,开发中的协同编辑显得格外重要。在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 EditingMulti-User Transactions选项:

Multi-User Editing

Multi-User Transactions

能够配置多用户协作的参数。

在开启插件之后,可以在编辑器Windows-Developer Tools中看到Multi-User Browser选项(也可以在Project Settings-Plugins-Multi-User Editing中开启Enable Multi-User Toolbar Button):

启动之后会自动搜索网络内可见的UnrealMultiUserServer服务器:

Multi-User Browser

也可以通过Launch a Server拉起一个UnrealMultiUserServer进程,在本地创建一个Server。通常情况下,会有一个独立的机器作为Server,供其他用户连接。

Mulit-User Browser作为Client连接Server时,使用的是UDP协议,默认连接端口号为6666Launch a Server启动默认是6666),也可以自己修改想要指定的端口号,Client会自动检测网络内可达的Server列表,创建Session时可以选择在哪个Server上创建。

Launch a 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下:

Saved/Config/Windows/Engine.ini
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[/Script/UdpMessaging.UdpMessagingSettings]
EnableTransport=True
bAutoRepair=True
bStopServiceWhenAppDeactivates=True
UnicastEndpoint=0.0.0.0:0
MulticastEndpoint=230.0.0.1:11111
MessageFormat=CborPlatformEndianness
MulticastTimeToLive=1
EnableTunnel=False
TunnelUnicastEndpoint=
TunnelMulticastEndpoint=

[/Script/Concert.ConcertClientConfig]
bInstallEditorToolbarButton=True

并不会存储在项目的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:0MulticastEndpoint保持为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
2
3
4
netsh advfirewall firewall add rule name="allowMultiUserServer" protocol=TCP dir=out localport=11111,11112 action=allow
netsh advfirewall firewall add rule name="allowMultiUserServer" protocol=TCP dir=in localport=11111,11112 action=allow
netsh advfirewall firewall add rule name="allowMultiUserServer" protocol=UDP dir=out localport=11111,11112 action=allow
netsh advfirewall firewall add rule name="allowMultiUserServer" protocol=UDP dir=in localport=11111,11112 action=allow

Mac和Linux可以使用ufw进行防火墙配置:

1
2
$ ufw allow 11111/udp
$ ufw allow 11111/tcp

独立的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进程的配置文件在:

Engine/Programs/UnrealMultiUserServer/Saved/Config/WindowsNoEditor/Engine.ini
1
2
3
4
5
6
7
8
[/Script/UdpMessaging.UdpMessagingSettings]
EnableTransport=True
UnicastEndpoint=0.0.0.0:0
MulticastEndpoint=230.0.0.1:11111
MulticastTimeToLive=1
EnableTunnel=False
TunnelUnicastEndpoint=
TunnelMulticastEndpoint=

与通过命令行指定是一样的。

独立的Server程序

因为UE的Multi-User Server会拉起一个独立的UnrealMultiUserServer进程,需要我们完整地把引擎Clone下载之后才能启动Multi-User Server程序,对于我们的需求而言,很浪费,因为我们并不需要完整的引擎功能,只是需要这个独立的Server程序而已,所以可以想办法把它抽离出来。

UnrealMultiUserServer是引擎中的一个Programs类型的程序,我之前有一些文章介绍相关的内容:Create A Standalone Application in UE4。它本质上是用了UE的代码库和插件的独立程序,和UE4Editor是一种类型的程序,但是默认情况下它的LinkTypeModular的,即代码中引用的的模块功能都在每个独立的DLL中,这也符合UE编辑器的设计模式。

所以,默认情况下UnrealMultiUserServer程序只有几十kb,具体的功能实现都是使用动态链接库方式调用DLL的,我们需要修改编译的模式,把它用到的引擎模块都编译到一个exe中,修改UnrealMultiUserServer.targetr.cs

1
2
3
4
// from
LinkType = TargetLinkType.Modular;
// to
LinkType = TargetLinkType.Monolithic;

改成Monolithic后再执行编译,UBT就会把该Program引用到的模块最终链接一个可执行程序。这部分内容在我之前的另一篇文章中有介绍:UE Build System:Target and Module

这个操作只能解决引擎内模块的引用,UnrealMultiUserServer还引用了其他的插件,在target.cs里开启了Program的插件支持:

1
2
bCompileWithPluginSupport = true;
bBuildDeveloperTools = true;

至于具体依赖了哪些插件,可以打开UnrealMultiUserServer.target查看其中的RuntimeDependencies项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
"RuntimeDependencies": [
{
"Path": "$(EngineDir)/Plugins/Messaging/UdpMessaging/UdpMessaging.uplugin",
"Type": "UFS"
},
{
"Path": "$(EngineDir)/Plugins/Developer/Concert/ConcertSync/ConcertSyncServer/ConcertSyncServer.uplugin",
"Type": "UFS"
},
{
"Path": "$(EngineDir)/Plugins/Developer/Concert/ConcertMain/ConcertMain.uplugin",
"Type": "UFS"
},
{
"Path": "$(EngineDir)/Plugins/Developer/Concert/ConcertSync/ConcertSyncCore/ConcertSyncCore.uplugin",
"Type": "UFS"
},
{
"Path": "$(EngineDir)/Plugins/Runtime/Database/SQLiteCore/SQLiteCore.uplugin",
"Type": "UFS"
}
]

表示UnrealMultiUserServer引用了这几个插件,需要把引擎中的这几个插件目录放到独立程序的相应目录下(与引擎中的相对路径保持一致)。

但是这样还不够,需要把插件加入到启用列表,不然程序启动时不会加载插件,导致模块加载错误:

1
2
3
4
5
6
7
8
9
10
LogPaths: Warning: No paths for game localization data were specifed in the game configuration.
LogInit: Warning: No paths for engine localization data were specifed in the engine configuration.
LogInit: WinSock: version 1.1 (2.2), MaxSocks=32767, MaxUdp=65467
LogUdpMessaging: Initializing bridge on interface 0.0.0.0:0 to multicast group 230.0.0.1:6666.
LogSyncServer: Error: ConcertSyncServer Module is needed for proper execution. Initialization failed!
LogSyncServer: Error: ConcertSyncServer Module is needed for proper execution. Initialization failed!
LogSyncServer: Display: Multi-User Editing Server Shutdown
LogSyncServer: Display: Multi-User Editing Server Shutdown
LogExit: Preparing to exit.
LogExit: Object subsystem successfully closed.

解决的办法就是在target.cs里添加EnablePlugins列表:

1
2
3
4
5
EnablePlugins.AddRange(new string[]
{
"UdpMessaging",
"ConcertSyncServer"
});

这样我们抽离出来的独立程序就会在程序启动时加载需要的这两个插件了。
最后,还需要把原本引擎中Engine/Content下的Internationalization/Localization两个文件夹提取出来。

我们也不需要代码和调试符号,可以删除多余的文件:

1
2
3
4
find . -name "Source"|xargs rm -rf
find . -name "Intermediate"|xargs rm -rf
find . -name "*.pdb"|xargs rm -rf
find . -name "*.modules"|xargs rm -rf

最终独立的UnrealMultiUserServer程序的目录结构为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
+---Binaries
| \---Win64
| UnrealMultiUserServer.exe
+---Content
| +---Internationalization
| +---Localization
|
\---Plugins
+---ConcertMain
| | ConcertMain.uplugin
| |
| \---Binaries
| \---Win64
| UE4Editor-Concert.dll
| UE4Editor-ConcertTransport.dll
| UnrealMultiUserServer-Concert.dll
| UnrealMultiUserServer-ConcertTransport.dll
|
+---ConcertSyncCore
| | ConcertSyncCore.uplugin
| |
| +---Binaries
| | \---Win64
| | UE4Editor-ConcertSyncCore.dll
| | UnrealMultiUserServer-ConcertSyncCore.dll
| |
| \---Config
| BaseConcertSyncCore.ini
|
+---ConcertSyncServer
| | ConcertSyncServer.uplugin
| |
| \---Binaries
| \---Win64
| UE4Editor-ConcertSyncServer.dll
| UnrealMultiUserServer-ConcertSyncServer.dll
|
+---SQLiteCore
| | SQLiteCore.uplugin
| |
| \---Binaries
| \---Win64
| UE4Editor-SQLiteCore.dll
| UnrealMultiUserServer-SQLiteCore.dll
|
\---UdpMessaging
| UdpMessaging.uplugin
|
+---Binaries
| \---Win64
| UE4Editor-UdpMessaging.dll
| UnrealFrontend-UdpMessaging.dll
| UnrealLightmass-UdpMessaging.dll
| UnrealMultiUserServer-UdpMessaging.dll
|
\---Resources
Icon128.png

执行效果:

1
2
3
4
5
6
7
8
C:\Users\lipengzha\Desktop\UnrealMultiUserServer\Engine\Binaries\Win64>UnrealMultiUserServer.exe
LogPaths: Warning: No paths for game localization data were specifed in the game configuration.
LogInit: Warning: No paths for engine localization data were specifed in the engine configuration.
LogInit: WinSock: version 1.1 (2.2), MaxSocks=32767, MaxUdp=65467
LogUdpMessaging: Initializing bridge on interface 0.0.0.0:0 to multicast group 230.0.0.1:6666.
LogTemp: IConcertSyncServerModule::Get().ParseServerSettings
LogSyncServer: Display: Multi-User Editing Server Initialized (Name: lipengzha-PC0, Version: 4.25, Role: MultiUser)
LogSyncServer: Display: Multi-User Editing Server Initialized (Name: lipengzha-PC0, Version: 4.25, Role: MultiUser

Session的配置存储

Multi-User Server启动之后,所有由用户创建的Session信息都会被保存在Server端,默认存储在以下路径中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
C:\Users\lipengzha\Desktop\UnrealMultiUserServer\Engine\Programs>tree /a /f
\---UnrealMultiUserServer
+---Intermediate
| \---MultiUser
| \---35E93D4D493990231B6D30B582298091
| Session.db
| Session.db-wal
| SessionInfo.json
|
\---Saved
+---Logs
| UnrealMultiUserServer.log
|
\---MultiUser
Repositories.json

当下次Server启动的时候这些Session会自动创建,可以避免需要重复创建的问题。

结语

本篇文章记录了UE中多用户编辑的启动和配置流程,并提取了独立的Multi-User Server程序,无需完整的引擎内容,能够实现非常方便地部署多用户编辑服务。后续有时间会深入分析一下内部的实现机制以及同步策略。

参考资料

全文完,若有不足之处请评论指正。

微信扫描二维码,关注我的公众号。

本文标题:UE多用户协同编辑服务部署指南
文章作者:查利鹏
发布时间:2021年08月05日 16时15分
本文字数:本文一共有4.4k字
原始链接:https://imzlp.com/posts/25226/
许可协议: CC BY-NC-SA 4.0
文章禁止全文转载,摘要转发请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!