Simple example for a updating player list inGame

I try to create a list and update it, but i didnt get it managed.

Solution in:
https://forum.unity.com/threads/simple-example-for-a-updating-player-list-ingame.1355462/#post-8554556

I have a public GameObject nameTag; // try to add to a list
i use a private NetworkVariable<FixedString32Bytes> playerName = new NetworkVariable<FixedString32Bytes>();

The name Tag i show over the player with another value:

public override void OnNetworkSpawn()
    {
        playerName.OnValueChanged += OnPlayerNameChanged;
        if (IsOwner)
        {
            SetPlayerNameServerRpc(PlayerPrefs.GetString("BikerName", "Unnamed Player"));
            SetPlayerLobbyIdServerRpc(LobbyManager.singleton.GetCurPlayerId());


        } else {
            SetNameTag(playerName.Value.ToString());
            SetWattKG(playerWKG.Value.ToString());

        }
    }

I want the nameTag stored in a list and update the UI, but also removed if player leaves.
My idea was to call a PlayerlistUpdate() function with a ServerRPC when a client joins the session.

In public override void OnNetworkSpawn()

But how do i call a function on the client?
How do i have not double entries, and how do i remove the entry that leaves the session?

Call a function on the clients would be a good start.

The above won't work as playerName is updated after the player is spawned. What you can do is to make use of OnPlayerNameChanged.

For OnNetworkSpawn have:

    public override void OnNetworkSpawn()
    {
        playerName.OnValueChanged += OnPlayerNameChanged;
        if (IsLocalPlayer)
        {
            SetPlayerNameServerRpc(PlayerPrefs.GetString("BikerName", "Unnamed Player"));
            SetPlayerLobbyIdServerRpc(LobbyManager.singleton.GetCurPlayerId());
        }
    }

for OnPlayerNameChanged:

    private void OnPlayerNameChanged(FixedString32Bytes previousValue, FixedString32Bytes newValue)
    {
        Debug.Log("Player OnPlayerNameChanged playerName: " + newValue);

        // add player to list
        // display tag on UI
    }

For adding the player you could keep it simple and have a GameManager with a dictionary and use a playerId of some sort as the key:

public class GameManager : Singleton<GameManager>
{
    Dictionary<uint, Player> players = new Dictionary<uint, Player>();

    public Dictionary<uint, Player> Players { get => players; }
}

For updating the UI it depends on how you're set up to access it, I have a class that holds all the UI elements and another for updating it but that might be more complicated a setup than you have in mind.

For removing the player you can use OnNetworkDespawn:

    public override void OnNetworkDespawn()
    {
        GameManager.Instance().Players.Remove(playerId);

        // remove player from UI
    }

If you're only maintaining the player list for UI updates you can always have the list and UI update handling in the same class.

1 Like

I created my NetPlayerlist you suggest here with:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using Unity.Services.Lobbies.Models;

public class NetPlayerList : MonoBehaviour
{
    public static NetPlayerList singleton;

    Dictionary<uint, Player> players = new Dictionary<uint, Player>();

    public Dictionary<uint, Player> Players { get => players; }


}

in My NetworkBicyleScript i have:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
using Unity.Collections;
using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;
using Unity.Services.Relay;
using TMPro;

//Handles network specific functions for multiplayer bicycles
public class NetworkBicycle : NetworkBehaviour {

    public GameObject nameTag;  // try to add to a list
    public GameObject wkg;



    //Network variables can not be nullable, so we have to use a fixed string
    private NetworkVariable<FixedString32Bytes> playerName = new NetworkVariable<FixedString32Bytes>();
    private NetworkVariable<FixedString128Bytes> playerLobbyId = new NetworkVariable<FixedString128Bytes>();
    private NetworkVariable<FixedString32Bytes> playerWKG = new NetworkVariable<FixedString32Bytes>();


    public override void OnNetworkSpawn()
    {
        playerName.OnValueChanged += OnPlayerNameChanged;
        if (IsOwner)
        {
            SetPlayerNameServerRpc(PlayerPrefs.GetString("BikerName", "Unnamed Player"));
            SetPlayerLobbyIdServerRpc(LobbyManager.singleton.GetCurPlayerId());
        if (IsLocalPlayer)
        {
            SetPlayerNameServerRpc(PlayerPrefs.GetString("BikerName", "Unnamed Player"));
            SetPlayerLobbyIdServerRpc(LobbyManager.singleton.GetCurPlayerId());
        }

        } else {
            SetNameTag(playerName.Value.ToString());
            SetWattKG(playerWKG.Value.ToString());

        }
    }

    [ServerRpc]
    public void SetPlayerNameServerRpc(string name)
    {
        playerName.Value = name;
    }

    [ServerRpc]
    public void SetPlayerLobbyIdServerRpc(string id)
    {
        playerLobbyId.Value = id;
    }


    private void OnPlayerNameChanged(FixedString32Bytes previousValue, FixedString32Bytes newValue)
    {
        Debug.Log("Player OnPlayerNameChanged playerName: " + newValue);

        // add player to list
        // display tag on UI
    }

    private void SetNameTag(string name)
    {
        if (nameTag == null) {
            return;
        }
        nameTag.GetComponent<TextMeshPro>().text = name;

    }

    public void SetWattKG(string _wkg)
    {
        if (wkg == null)
        {
            return;
        }
        wkg.GetComponent<TextMeshPro>().text = playerWKG.Value.ToString();
        //Debug.Log("MoreValue" + playerWKG.Value.ToString());
    }

    private void Update()
    {
        if (IsOwner)
        {
            var bikeUI = GameObject.FindGameObjectWithTag("BikeComputer").GetComponent<BikeComputerUI>();
            SetWattKGServerRpc(bikeUI.wkg.text);
           // Debug.Log("UpdatedValue" + playerWKG.Value.ToString());

        }
        SetWattKG(playerWKG.Value.ToString());
    }


    [ServerRpc]
    public void SetWattKGServerRpc(string _wkg)
    {
        playerWKG.Value = _wkg;
      //  Debug.Log("We ve got value" + _wkg);
    }

    public override void OnNetworkDespawn()
    {
        string playerId =
        NetPlayerList.Instance().Players.Remove(playerId);
        // remove player from UI
    }

    private void OnDestroy() {
        if (IsServer) {
            LobbyService.Instance.RemovePlayerAsync(LobbyManager.singleton.GetCurLobby().Id, playerLobbyId.Value.ToString());
        }
        if (IsOwner) {
            LobbyManager.singleton.Shutdown(true);
        }
    }
}

changes like you suggested. but gives me a "no defination" for

NetPlayerList.Instance().Players.Remove(playerId);

I have also my LobbyManager posted here:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
using Unity.Netcode.Transports.UTP;
using Unity.Services;
using Unity.Services.Authentication;
using Unity.Services.Core;
using Unity.Services.Relay;
using Unity.Services.Relay.Models;
using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;
using ParrelSync;  ///Needs to be Disabled when you build
using System.Threading.Tasks;
using UnityEngine.SceneManagement;

[RequireComponent(typeof(NetworkManager))]
public class LobbyManager : MonoBehaviour {
    public static LobbyManager singleton;
    private string playerId;
    private UnityTransport transport;
    public const string joinCodeKey = "jc";
    public const string sceneNameKey = "scnm";
    public const string hostNameKey = "hname";

    private Lobby curLobby;

    private void Awake() {
        LobbyManager.singleton = this;
        transport = FindObjectOfType<UnityTransport>();
    }

    private void Start() {
        Authenticate();
    }

    private async Task Authenticate() {
        if (UnityServices.State == ServicesInitializationState.Uninitialized) {
            var options = new InitializationOptions();
        //Needs to be Disabled when you build

            #if UNITY_EDITOR
                        //Used to differentiate clients when using ParrelSync
                        options.SetProfile(ClonesManager.IsClone() ? ClonesManager.GetArgument() : "Primary");
         #endif

            await UnityServices.InitializeAsync(options);
        }
        if (!AuthenticationService.Instance.IsSignedIn) {
            await AuthenticationService.Instance.SignInAnonymouslyAsync();
        }
        playerId = AuthenticationService.Instance.PlayerId;
    }

    private static IEnumerator HeartbeatLobbyCoroutine(string lobbyId, float waitTimeSeconds) {
        var delay = new WaitForSecondsRealtime(waitTimeSeconds);
        while (true) {
            LobbyService.Instance.SendHeartbeatPingAsync(lobbyId);
            yield return delay;
        }
    }

    public async Task<List<Lobby>> GatherLobbies() {
        var options = new QueryLobbiesOptions { Count = 15, };
        var allLobbies = await LobbyService.Instance.QueryLobbiesAsync(options);
        return allLobbies.Results;
    }

    public async Task<Lobby> CreateLobby(string hostName) {
        try {
            string joinCode = await RelayManager.singleton.CreateGame();
            var options = new CreateLobbyOptions {
                Data = new Dictionary<string, DataObject> {
                    { joinCodeKey, new DataObject(DataObject.VisibilityOptions.Public, joinCode) },
                    { sceneNameKey, new DataObject(DataObject.VisibilityOptions.Public, SceneManager.GetActiveScene().name) },
                    { hostNameKey, new DataObject(DataObject.VisibilityOptions.Public, hostName)}
                }
            };
            var lobby = await LobbyService.Instance.CreateLobbyAsync("Lobby Name", RelayManager.singleton.maxPlayerCount, options);
            curLobby = lobby;
            StartCoroutine(HeartbeatLobbyCoroutine(lobby.Id, 15));
            return lobby;
        } catch (System.Exception e) {
            Debug.LogError("Failed to create lobby");
            Debug.LogError(e);
            throw;
        }
    }

    public async Task<Lobby> CreateLobbyDedServer(string hostName)
    {
        try
        {
            string joinCode = await RelayManager.singleton.CreateDedicatedServer();
            var options = new CreateLobbyOptions
            {
                Data = new Dictionary<string, DataObject> {
                    { joinCodeKey, new DataObject(DataObject.VisibilityOptions.Public, joinCode) },
                    { sceneNameKey, new DataObject(DataObject.VisibilityOptions.Public, SceneManager.GetActiveScene().name) },
                    { hostNameKey, new DataObject(DataObject.VisibilityOptions.Public, hostName)}
                }
            };
            var lobby = await LobbyService.Instance.CreateLobbyAsync("Lobby Name", RelayManager.singleton.maxPlayerCount, options);
            curLobby = lobby;
            StartCoroutine(HeartbeatLobbyCoroutine(lobby.Id, 15));
            //NetworkManager.Singleton.
            return lobby;
        }
        catch (System.Exception e)
        {
            Debug.LogError("Failed to create lobby");
            Debug.LogError(e);
            throw;
        }
    }

    public async Task JoinLobby(string lobbyId) {
        try {
            curLobby = await LobbyService.Instance.JoinLobbyByIdAsync(lobbyId);
            await RelayManager.singleton.JoinGame(curLobby.Data[LobbyManager.joinCodeKey].Value);
        } catch (System.Exception e) {
            Debug.LogError("Failed to join lobby");
            Debug.LogError(e);
            throw;
        }
    }

    //To be called by other scripts to shut down network services and optionally to return to menu
    public void Shutdown(bool returnToMenu) {
        if (GlobalValues.GetGameMode() == GlobalValues.GameMode.Single || curLobby == null) {
            return;
        }
       // Destroy(NetworkManager.Singleton.gameObject);
        NetworkManager.Singleton.Shutdown();
        if (returnToMenu) {
            ReturnToMenu();
        }

    }

    //Returns to menu
    private void ReturnToMenu() {
        Destroy(NetworkManager.Singleton.gameObject);
       // SceneManager.LoadScene(0);
    }

    public Lobby GetCurLobby() {
        return curLobby;
    }

    public string GetCurPlayerId() {
        return playerId;
    }
}

I am still struggle and i think i mix to much different places :(
The complete day i am busy with that :(

The playerId i grab now with:

string playerId = LobbyManager.singleton.GetCurPlayerId();

You’ll need to remove this from OnNetworkSpawn:

        } else {
            SetNameTag(playerName.Value.ToString());
            SetWattKG(playerWKG.Value.ToString());
 
        }

at least the SetNameTag call as playerName won’t have a value at this point.

I see the Remove but don’t see you adding to the player dictionary. I can’t say exactly what you want but OnPlayerNameChanged will need to look something like this:

    private void OnPlayerNameChanged(FixedString32Bytes previousValue, FixedString32Bytes newValue)
    {
        Debug.Log("Player OnPlayerNameChanged playerName: " + newValue);

        NetPlayerList.Instance().Players.Add(playerId, this);
        SetNameTag(playerName.Value.ToString());
    }

You’ll need to find or create a playerId to use as the key.

1 Like

Ok then i am more on it:

private void OnPlayerNameChanged(FixedString32Bytes previousValue, FixedString32Bytes newValue)
    {
        Debug.Log("Player OnPlayerNameChanged playerName: " + newValue);

        uint playerId = Convert.ToUInt32(LobbyManager.singleton.GetCurPlayerId());
        NetPlayerList.singleton.Players.Add(playerId, this);
        SetNameTag(playerName.Value.ToString());
    }

I need to convert, but how ever this

NetPlayerList.singleton.Players.Add(playerId, this);

brings me:

Assets\Scripts\Multiplayer\NetworkBicycle.cs(71,55): error CS1503: Argument 2: cannot convert from 'NetworkBicycle' to 'Unity.Services.Lobbies.Models.Player'

If NetworkBicycle is the class you're using to represent the player change the dictionary definition to this:Dictionary<uint, NetworkBicycle> players = Dictionary<uint, NetworkBicycle>();

'this' is just a way of getting the current object.

1 Like

Ok but i needed to replace uint to string, and i am here now:
My Future NetPlayerList:

public class NetPlayerList : MonoBehaviour
{
    public static NetPlayerList Instance { get; private set; }
    public Dictionary<string, NetworkBicycle> players = new Dictionary<string, NetworkBicycle>();

    private void Awake()
    {
        // If there is an instance, and it's not me, delete myself.
        if (Instance != null && Instance != this)
        {
            Destroy(this);
        }
        else
        {
            Instance = this;
        }
    }
}

And in my NetworkBicycle ia have now:

private void OnPlayerNameChanged(FixedString32Bytes previousValue, FixedString32Bytes newValue)
    {
       // Debug.Log("Player OnPlayerNameChanged playerName: " + newValue);
       // Debug.Log("Player OnPlayerNameChanged playerId: " + LobbyManager.singleton.GetCurPlayerId());
        var playerId = LobbyManager.singleton.GetCurPlayerId();
        NetPlayerList.Instance.players.Add(playerId, this );
        Debug.Log("Added: "+playerId, this);
        SetNameTag(playerName.Value.ToString());
        Debug.Log("Values =" + NetPlayerList.Instance.players.Keys + NetPlayerList.Instance.players.Values);
    }
void ShowDict()
    {
        foreach (KeyValuePair<string, NetworkBicycle> player in NetPlayerList.Instance.players)
        {
            Debug.Log("Key"+ player.Key);
            Debug.Log("Value"+ player.Value);
        }
    }

This dont look correct!?
And i am right, i need to read out the dictionary and then add the values to my wanted PlayerList?

My debugLog:

Key = XCYqLi55AtTxVr823viSjZbWasKa
Value = ValueBiycycle StandardYellow(Clone) (NetworkBicycle)

So after reading and not understanding i have:

using System;
using System.Collections.Generic;
using TMPro;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.UI;

public class NetPlayerList : NetworkBehaviour
{

    public TMP_Text LobbyText;

    private Dictionary<ulong, bool> m_ClientsInLobby;
    private string m_UserLobbyStatusText;

    public override void OnNetworkSpawn()
    {
        m_ClientsInLobby = new Dictionary<ulong, bool>();

        //Always add ourselves to the list at first
        m_ClientsInLobby.Add(NetworkManager.LocalClientId, false);

        //If we are hosting, then handle the server side for detecting when clients have connected
        //and when their lobby scenes are finished loading.
        if (IsServer)
        {
            //  m_AllPlayersInLobby = false;

            //Server will be notified when a client connects
            NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback;
            UpdateAndCheckPlayersInLobby();

        }

        //Update our lobby
        GenerateUserStatsForLobby();

    }
    /
    private void OnGUI()
    {
        if (LobbyText != null) LobbyText.text = m_UserLobbyStatusText;
    }


    private void GenerateUserStatsForLobby()
    {
        m_UserLobbyStatusText = string.Empty;
        foreach (var clientLobbyStatus in m_ClientsInLobby)
        {
            m_UserLobbyStatusText += PlayerPrefs.GetString("BikerName") + "\n";// + clientLobbyStatus.Key + "          \n";

            /
        }
    }

    private void UpdateAndCheckPlayersInLobby()
    {


        foreach (var clientLobbyStatus in m_ClientsInLobby)
        {
            SendClientReadyStatusUpdatesClientRpc(clientLobbyStatus.Key, clientLobbyStatus.Value);

        }

    }

     /// <summary>
    ///     OnClientConnectedCallback
    ///     Since we are entering a lobby and Netcode's NetworkManager is spawning the player,
    ///     the server can be configured to only listen for connected clients at this stage.
    /// </summary>
    /// <param name="clientId">client that connected</param>
    private void OnClientConnectedCallback(ulong clientId)
    {
        if (IsServer)
        {
            if (!m_ClientsInLobby.ContainsKey(clientId)) m_ClientsInLobby.Add(clientId, false);
            GenerateUserStatsForLobby();

            UpdateAndCheckPlayersInLobby();
        }
    }

    /// <summary>
    ///     SendClientReadyStatusUpdatesClientRpc
    ///     Sent from the server to the client when a player's status is updated.
    ///     This also populates the connected clients' (excluding host) player state in the lobby
    /// </summary>
    /// <param name="clientId"></param>
    /// <param name="isReady"></param>
    [ClientRpc]
    private void SendClientReadyStatusUpdatesClientRpc(ulong clientId, bool isReady)
    {
        if (!IsServer)
        {
            if (!m_ClientsInLobby.ContainsKey(clientId))
                m_ClientsInLobby.Add(clientId, isReady);
            else
                m_ClientsInLobby[clientId] = isReady;
            GenerateUserStatsForLobby();
        }
    }


}

Not perfect yet, and i need to remove the player when he/she despawns:
But works in a way.
I have COPIED and fit to my needs from:
https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/tree/main/Basic

The Invaders example
I still dont understand not all, but i will when time is come. :)

You didn't mention what the list was going to be used for so I only gave you a general example. Actually looking at it again it's not quite right but anyway. I've not used Lobby but if there's something you're stuck with I'll see if I can help.

1 Like

I am always happy for any help :slight_smile:
8554235--1143926--upload_2022-11-1_11-19-53.png

The adding works great.
Now i like to create a callBack what removes the player from the List when leave with :

public override void OnNetworkDespawn()
    {

        NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback;
        GenerateUserStatsForLobby();
        UpdateAndCheckPlayersInLobby();
    }
private void OnClientDisconnectCallback(ulong clientId)
    {
        if (!IsServer)
        {
            if (!m_ClientsInLobby.ContainsKey(clientId)) m_ClientsInLobby.Remove(clientId);

            GenerateUserStatsForLobby();
            UpdateAndCheckPlayersInLobby();
        }
    }

But the Player is still in List

many Thanks for your great help by the way! :slight_smile:

You'll want NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback; to be in OnNetworkSpawn.

One thing I would suggest is debug log everything, all method calls and relevant values so you have eyes on what's going on when the application is running.

1 Like

The debug says:

 Debug.Log("Removed: " + clientId);

[Netcode] Disconnect Event From 1

MayBe its disconnect from Dictionary, but dont removed from the list.

But when i leave and rejoin the Session, i have 3 entries, so the entry is probably not really removed with all the data?

I can't really say, if you need to have a dictionary and a list then you need to keep them in sync, I can't see enough to understand what's going on. You'll want to make sure everything is correct at each step, so start when a client connects check that all values are correct and when they disconnect you're effectively reversing what you did on connection.

1 Like

I understand:
My idea was to just remove all entries in the text:
https://github.com/Landixus/ToolsForUnity/blob/master/NetPlayerList.cs#L92

I pushed my file to github, because i think its easier to read:

And after cleared the List i just generate them new, so i dont need to reverse it.

Got it:

 private void OnClientDisconnectCallback(ulong clientId)
    {
        if (IsServer)
        {
            if (m_ClientsInLobby.ContainsKey(clientId)) m_ClientsInLobby.Remove(clientId);
            Debug.Log("Removed: " + clientId);
            m_UserLobbyStatusText = "";
            GenerateUserStatsForLobby();
            UpdateAndCheckPlayersInLobby();
        }
    }

The ! was to much.
It works now, if anybody surf here:

using System;
using System.Collections.Generic;
using TMPro;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.UI;

public class NetPlayerList : NetworkBehaviour
{

    [SerializeField]
    public TMP_Text LobbyText;

    private Dictionary<ulong, bool> m_ClientsInLobby;

    private string m_UserLobbyStatusText;

    public override void OnNetworkSpawn()
    {
        m_ClientsInLobby = new Dictionary<ulong, bool>();

        //Always add ourselves to the list at first
        m_ClientsInLobby.Add(NetworkManager.LocalClientId, false);

        //If we are hosting, then handle the server side for detecting when clients have connected
        //and when their lobby scenes are finished loading.
        if (IsServer)
        {
            //Server will be notified when a client connects
            NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback;
            NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback;
            UpdateAndCheckPlayersInLobby();
        }
        //Update our lobby
        GenerateUserStatsForLobby();
    }
    private void OnGUI()
    {
        if (LobbyText != null) LobbyText.text = m_UserLobbyStatusText;
    }
    private void GenerateUserStatsForLobby()
    {
        m_UserLobbyStatusText = string.Empty;
        foreach (var clientLobbyStatus in m_ClientsInLobby)
        {
            m_UserLobbyStatusText += PlayerPrefs.GetString("BikerName") + "\n";

        }
    }
    /// <summary>
    ///     UpdateAndCheckPlayersInLobby
    ///     Checks to see if we have at least 2 or more people to start
    /// </summary>
    private void UpdateAndCheckPlayersInLobby()
    {
        foreach (var clientLobbyStatus in m_ClientsInLobby)
        {
            SendClientReadyStatusUpdatesClientRpc(clientLobbyStatus.Key); // clientLobbyStatus.Value);
        }
    }

    /// <summary>
    ///     OnClientConnectedCallback
    ///     Since we are entering a lobby and Netcode's NetworkManager is spawning the player,
    ///     the server can be configured to only listen for connected clients at this stage.
    /// </summary>
    private void OnClientConnectedCallback(ulong clientId)
    {
        if (IsServer)
        {
            if (!m_ClientsInLobby.ContainsKey(clientId)) m_ClientsInLobby.Add(clientId, false);

            GenerateUserStatsForLobby();
            UpdateAndCheckPlayersInLobby();
        }
    }

    private void OnClientDisconnectCallback(ulong clientId)
    {
        if (IsServer)
        {
            if (m_ClientsInLobby.ContainsKey(clientId)) m_ClientsInLobby.Remove(clientId);
            Debug.Log("Removed: " + clientId);
            m_UserLobbyStatusText = "";
            GenerateUserStatsForLobby();
            UpdateAndCheckPlayersInLobby();
        }
    }

    /// <summary>
    ///     SendClientReadyStatusUpdatesClientRpc
    ///     Sent from the server to the client when a player's status is updated.
    ///     This also populates the connected clients' (excluding host) player state in the lobby
    /// </summary>
    /// <param name="clientId"></param>
    [ClientRpc]
  //  private void SendClientReadyStatusUpdatesClientRpc(ulong clientId, bool isReady)
    private void SendClientReadyStatusUpdatesClientRpc(ulong clientId)
    {
        if (!IsServer)
        {
            if (!m_ClientsInLobby.ContainsKey(clientId))
                m_ClientsInLobby.Add(clientId, false);
                GenerateUserStatsForLobby();
        }
    }

}
1 Like

I was totally wrong, i store always the same name in the list of the client on the side of the client.
I need to marry the client ID with the PlayerName:
This brings me the client ID to List with 0, false… 1,false…:

m_UserLobbyStatusText += $"{clientLobbyStatus.Key}: {clientLobbyStatus.Value}\n"

but no name :frowning:
https://github.com/Landixus/ToolsForUnity/blob/master/NetworkBicycle.cs#L169

I am lost here. :frowning:
My solution is not working.
I tried to get a more experienced coder and attract with some pocket money ends in zero success :frowning:
Player List with name must be a secret feature of netcode.

I have to go back to bit to understand what you actually need and correct something I said previously.

Have a collection for connected players:

public class NetPlayerList : NetworkBehaviour
{
    private Dictionary<ulong, NetworkBicycle> players;
}

Add to the player collection on spawn:

public class NetworkBicycle : NetworkBehaviour { 
    public override void OnNetworkSpawn()
    {
        NetPlayerList.instance.Players.Add(this.OwnerClientId, this);

        playerName.OnValueChanged += OnPlayerNameChanged;
    }
}

Add player name to UI:

private void OnPlayerNameChanged(FixedString32Bytes previousValue, FixedString32Bytes newValue)
    {
        SetNameTag(playerName.Value.ToString());
    }

Remove player from collection on despawn:

public class NetworkBicycle : NetworkBehaviour {  
    public override void OnNetworkDespawn()
    {
        NetPlayerList.instance.Players.Remove(this.OwnerClientId);

        playerName.OnValueChanged -= OnPlayerNameChanged;
    }
}

Is this essentially what you're after, as I'm a bit confused on how you're using the dictionaries at the moment.

1 Like

Thank you i try this when i am back at home and report back.

I tried but i get the Singleton not referenced:

I am here:

using System;
using System.Collections.Generic;
using TMPro;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.UI;

public class NetPlayerList : NetworkBehaviour
{
    public static NetPlayerList instance { get; private set; }

    [SerializeField]
    public TMP_Text LobbyText;

    private Dictionary<ulong, bool> m_ClientsInLobby;
    private string m_UserLobbyStatusText;

    public Dictionary<ulong, NetworkBicycle> players;


    private void Awake()
    {
        // If there is an instance, and it's not me, delete myself.

        if (instance != null && instance != this)
        {
            Destroy(this);
        }
        else
        {
            instance = this;
        }
    }

    public override void OnNetworkSpawn()
    {
        m_ClientsInLobby = new Dictionary<ulong, bool>();


        //Always add ourselves to the list at first
        m_ClientsInLobby.Add(NetworkManager.LocalClientId, false);

        //If we are hosting, then handle the server side for detecting when clients have connected
        //and when their lobby scenes are finished loading.
        if (IsServer)
        {
            //Server will be notified when a client connects
            NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback;
            NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback;
            UpdateAndCheckPlayersInLobby();
        }
        //Update our lobby
        GenerateUserStatsForLobby();
    }
    private void OnGUI()
    {
        if (LobbyText != null) LobbyText.text = m_UserLobbyStatusText;
    }
    private void GenerateUserStatsForLobby()
    {
        m_UserLobbyStatusText = string.Empty;

        foreach (var clientLobbyStatus in m_ClientsInLobby)
        {

            m_UserLobbyStatusText += $"{clientLobbyStatus.Key}: {clientLobbyStatus.Value}\n" + players.Values;
            /*
            if (IsLocalPlayer)
            {
                 m_UserLobbyStatusText += PlayerPrefs.GetString("BikerName") + "\n";
            }*/

            //  m_UserLobbyStatusText += networkBicyle.nameTag.GetComponent<TextMeshPro>().text;

        }
    }
    /// <summary>
    ///     UpdateAndCheckPlayersInLobby
    ///     Checks to see if we have at least 2 or more people to start
    /// </summary>
    private void UpdateAndCheckPlayersInLobby()
    {
        foreach (var clientLobbyStatus in m_ClientsInLobby)
        {
            SendClientReadyStatusUpdatesClientRpc(clientLobbyStatus.Key); // clientLobbyStatus.Value);
        }
    }

    /// <summary>
    ///     OnClientConnectedCallback
    ///     Since we are entering a lobby and Netcode's NetworkManager is spawning the player,
    ///     the server can be configured to only listen for connected clients at this stage.
    /// </summary>
    private void OnClientConnectedCallback(ulong clientId)
    {
        if (IsServer)
        {
            if (!m_ClientsInLobby.ContainsKey(clientId)) m_ClientsInLobby.Add(clientId, false);

            GenerateUserStatsForLobby();
            UpdateAndCheckPlayersInLobby();
        }
    }

    private void OnClientDisconnectCallback(ulong clientId)
    {
        if (IsServer)
        {
            if (m_ClientsInLobby.ContainsKey(clientId)) m_ClientsInLobby.Remove(clientId);
            Debug.Log("Removed: " + clientId);
            m_UserLobbyStatusText = "";
            GenerateUserStatsForLobby();
            UpdateAndCheckPlayersInLobby();
        }
    }

    /// <summary>
    ///     SendClientReadyStatusUpdatesClientRpc
    ///     Sent from the server to the client when a player's status is updated.
    ///     This also populates the connected clients' (excluding host) player state in the lobby
    /// </summary>
    /// <param name="clientId"></param>
    [ClientRpc]
  //  private void SendClientReadyStatusUpdatesClientRpc(ulong clientId, bool isReady)
    private void SendClientReadyStatusUpdatesClientRpc(ulong clientId)
    {
        if (!IsServer)
        {
            if (!m_ClientsInLobby.ContainsKey(clientId))
                m_ClientsInLobby.Add(clientId, false);
                GenerateUserStatsForLobby();
        }
    }

}

And my Bicycle:

using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;
using Unity.Netcode;
using Unity.Collections;
using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;
using Unity.Services.Relay;
using TMPro;

//Handles network specific functions for multiplayer bicycles
public class NetworkBicycle : NetworkBehaviour {

    public GameObject nameTag;  // try to add to a list
    public GameObject wkg;
    //Network variables can not be nullable, so we have to use a fixed string
    private NetworkVariable<FixedString32Bytes> playerName = new NetworkVariable<FixedString32Bytes>();
    private NetworkVariable<FixedString128Bytes> playerLobbyId = new NetworkVariable<FixedString128Bytes>();
    private NetworkVariable<FixedString32Bytes> playerWKG = new NetworkVariable<FixedString32Bytes>();


    private void Start()
    {
   //     LobbyText = GameObject.FindGameObjectWithTag("PLStart").GetComponent<TMP_Text>();
    }

    public override void OnNetworkSpawn()
    {
        playerName.OnValueChanged += OnPlayerNameChanged;
        if (IsOwner)
        {
            SetPlayerNameServerRpc(PlayerPrefs.GetString("BikerName", "Unnamed Player"));
            SetPlayerLobbyIdServerRpc(LobbyManager.singleton.GetCurPlayerId());
           //   Debug.Log("We ve got listname" + _listname);

            /*  if (IsLocalPlayer)
              {
                  SetPlayerNameServerRpc(PlayerPrefs.GetString("BikerName", "Unnamed Player"));
                  SetPlayerLobbyIdServerRpc(LobbyManager.singleton.GetCurPlayerId());
              }*/

        } else
        {
            SetNameTag(playerName.Value.ToString());
            SetWattKG(playerWKG.Value.ToString());
        }

        NetPlayerList.instance.players.Add(this.OwnerClientId, this);

        playerName.OnValueChanged += OnPlayerNameChanged;

    }

    public override void OnNetworkDespawn()
    {

        NetPlayerList.instance.players.Remove(this.OwnerClientId);

        playerName.OnValueChanged -= OnPlayerNameChanged;
        //  var playerId = LobbyManager.singleton.GetCurPlayerId();
        //  NetPlayerList.Instance.players.Remove(playerId);
        // remove player from UI
          Debug.Log("Removed: " + this.OwnerClientId);
    }

    [ServerRpc]
    public void SetPlayerNameServerRpc(string name)
    {
        playerName.Value = name;
    }

    [ServerRpc]
    public void SetPlayerLobbyIdServerRpc(string id)
    {
        playerLobbyId.Value = id;
    }


    private void OnPlayerNameChanged(FixedString32Bytes previousValue, FixedString32Bytes newValue)
    {

      SetNameTag(playerName.Value.ToString());

    }

    private void SetNameTag(string name)
    {
        if (nameTag == null) {
            return;
        }
        nameTag.GetComponent<TextMeshPro>().text = name;
       
    }

    public void SetWattKG(string _wkg)
    {
        if (wkg == null)
        {
            return;
        }
        wkg.GetComponent<TextMeshPro>().text = playerWKG.Value.ToString();
        //Debug.Log("MoreValue" + playerWKG.Value.ToString());
    }



    private void Update()
    {  
        if (IsOwner)
        {
            var bikeUI = GameObject.FindGameObjectWithTag("BikeComputer").GetComponent<BikeComputerUI>();
            SetWattKGServerRpc(bikeUI.wkg.text);
           // Debug.Log("UpdatedValue" + playerWKG.Value.ToString());
           
        }
        SetWattKG(playerWKG.Value.ToString());
    }


    [ServerRpc]
    public void SetWattKGServerRpc(string _wkg)
    {
        playerWKG.Value = _wkg;
      //  Debug.Log("We ve got value" + _wkg);
    }



       private void OnDestroy() {
        if (IsServer) {
            LobbyService.Instance.RemovePlayerAsync(LobbyManager.singleton.GetCurLobby().Id, playerLobbyId.Value.ToString());
        }
        if (IsOwner) {
            LobbyManager.singleton.Shutdown(true);
        }
    }
}

Gives me currently:
NullReferenceException: Object reference not set to an instance of an object at 49:
NetPlayerList.instance.players.Add(this.OwnerClientId, this);

But i thought a Singleton is always project wide active when awaked!?

Bit short on time but does NetPlayerList need to be a NetworkBehaviour, are you spawning it?