Issues with Using Listen Servers with World Composition and World Partition
Article written by Alex K.
When creating a multiplayer game on a large map made up of streaming sublevels, you may run into problems when hosting this game using a listen server (an instance of a game with a locally controlled player that also acts as a server/host for other remote players). The most common problems tend to involve a client’s character ignoring collisions with meshes or even falling below the level, with these issues usually occurring when the client gets far away from the host’s location. This article will try to explain what is happening when these issues occur as well as offer workarounds for projects experiencing these problems.
When using an Unreal Engine distance-based level streaming approach such as World Partition or World Composition, usually only those levels near the locally controlled player are loaded. Because of this, when a remote player gets far enough away from the host, they’ll leave the levels that the listen server has loaded. Locally on that client, the levels near their local player will be loaded, but on the listen server those levels may not be loaded, leading to unexpected behavior on the client.
The Unreal Engine’s networking system uses a server-authoritative model, so the server always has authority over the game state, including the positions of all controlled pawns/characters, and will replicate this information to all connected clients. (You can find more information on the engine’s networking systems here. In the scenario where a remote player leaves the streaming levels the host has loaded, the listen server won’t be aware of any collisions that should be happening with the remote players’ pawns in that level. Because of this model, the server will update the remote player’s position without that level loaded, and this “incorrect” position will be replicated back to the client.
For example, a client may be in a streaming level with a floor underneath their character. However, since the listen server doesn’t have that level loaded, it is not aware of any floor at that position, so it will update that player’s position thinking that they are falling. The client will try to locally predict its character’s movement and position as it moves around this level, but it will eventually receive an authoritative update from the server saying that their position is beneath the floor, causing their character to suddenly begin falling below the level.
World Partition acts as a layer on top of distance-based level streaming, and there is currently no official support for using listen servers with World Partition. When using a dedicated or a listen server with World Partition, the server will by default keep all cells loaded, so it will be able to correctly update the positions of all clients no matter where they are in the map. This may be a viable approach for a dedicated server which won’t have a local player and runs headlessly, but this approach likely isn’t ideal or possible for a listen server depending on the size and complexity of the map.
If keeping all levels loaded isn’t an option, it may be possible to change this default behavior by modifying the World Partition streaming policy. This would require changes to the engine’s code, but it could allow listen servers to stream cells in the same way as a standalone or client instance. With this change, the listen server would also need to stream in cells near connected players’ pawns, and one new tool that may be useful in implementing an optimized listen server streaming solution is the Streaming Source Component. These components trigger the loading of cells around them, and could potentially be used to stream in cells around the connected players on the listen server. There would be performance considerations to make for an approach like this, as hosts would have to load more cells than clients. However, an approach like this would likely be much less expensive than keeping all cells loaded.
If your level is using World Composition, Unreal Engine 4’s distance-based streaming solution, this advice is more or less the same. World Composition also automates distance-based level streaming, and anything that World Composition does can be done manually. In game code, this can be done by setting the state of ULevelStreaming objects in the UWorld::StreamingLevels array based on your game’s needs. For example, rather than only loading the levels near the host’s local player, the listen server may check the positions of all connected players and ensure the streaming levels they are in are loaded as well. Again, the issue of the listen server’s loading and performance would still have to be addressed.
Get more answers on the Knowledge Base!