Sep 2, 2021.Knowledge
When creating a multiplayer game on a map using world composition, 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 through the floor, 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.
World composition automates distance-based level streaming, so only 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, world composition will load the levels near the player, 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: Networking Overview | Unreal Engine Documentation). 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 it’s 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.
Unfortunately, there is no official support for using listen servers with world composition. When using a dedicated server for a large map with many streaming levels, the server usually just keeps all streaming levels loaded, so it is 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 world composition map.
If keeping all levels loaded isn’t an option, another approach may be to implement some custom level streaming functionality on the host. World composition is just a layer on top of level streaming to automate distance-based streaming, so 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. The issue of the listen server’s loading and performance will still have to be addressed, but an approach like this will likely be much less expensive than keeping all levels loaded.