Lobby
UILobbyPlayer
Within a lobby, each row entry represents a player. Player objects have the UILobbyPlayer
component attached to them and are spawned by the server, then parented to that player list. Each player can only change their own options.
For visual representation, the UILobbyPlayer
component makes use of all UI controls that have been added to the UILobbyPlayer prefab. For more information on what controls are referenced, please see the Scripting Reference. The entry that can be changed by the local player is highlighted with an additional border element.
For each UI control, the UILobbyPlayer
script contains a NetworkVariable that sychronizes the modified selection to other players automatically. Therefore, each control's OnValueChanged callback invokes a method that sends an RPC to the server with the new UI element value. Meaning, there is a 1-1-1 relationship between control-NetworkVariable-RPC method.
Data Persistence
Variable values like the player name, color and class would usually only exist in this Lobby scene, where the UILobbyPlayer prefab is instantiated. However, we also want them to be available in the map scenes: for example when displaying the Stats window for each player with their name, but also for checking their selected class when placing new towers. For this reason, we somehow need to persist these references and values from the Lobby to the map scenes.
As mentioned in the GameManager, we already know how to manage currency and scores in networked lists that can hold a built-in data type, like an integer. However, for persisting a combined set of data for each player, we would want to synchronize multiple values of the UILobbyPlayer
class. Since we cannot send class references or collections over the network (List< UILobbyPlayer > or NetworkList< UILobbyPlayer > does not work), we convert each UILobbyPlayer
reference to a struct: the PlayerStruct
.
It has to be a struct that implements INetworkSerializable
and its corresponding methods to be accepted into a RPC method. In the PlayerStruct
, located at the bottom of the UILobbyPlayer
class, we take over the variables we are interested in. Note that string cannot be used as a type, since it has a variable length. Instead we have to use a fixed length type like FixedString32Bytes, for example for the player name.
Before starting a match, we let the server get all current UILobbyPlayer
references in the lobby and saves them temporarily in a List< PlayerStruct > in the UILobby
script. This list is then sent as an array to all clients in a RPC, including the server, to persist it in the NetworkManagerCustom
component (which lives until the map scene). The reason we do this just before starting the match is because we want to persist only players who actually participate in the match, not some who joined the lobby and left again.
Maybe you are wondering, why are we not using a NetworkList< PlayerStruct> that would be synchronized to clients automatically? It would definitely work and this is what you could do in another case - but not here. Since NetworkList
synchronization needs a NetworkObject
component on the same game object that contains it, it is not possible to add it to the NetworkManagerCustom
component because of the underlying NetworkManager
interface preventing it. Therefore, when using a NetworkList
on the NetworkManagerCustom
, it would not be synchronized at all. However the NetworkManagerCustom
component is the only script that is not destroyed when switching scenes between the Lobby and Map, so in order to be able to use it, we are going that RPC route.