Netcode/Steam/Steamworks.Net Close but not working..

I’m close to getting this working I think. I have 2 computers and 2 steam accounts to test. The appid is set and steam does populate it in chat/friends view. Any assistance would be greatly appreciated
Unity Version 2022.3.14f1
Netcode For Gamobjects 1.7.1
SteamManager // Version: 1.0.12
“name”: “com.rlabrecque.steamworks.net”,
“displayName”: “Steamworks.NET”,
“version”: “20.2.0”

When I start from editor as a host, it spawns 2 players that I can’t move. If I execute the build exe from another steam account and enter that lobby id and press join it seems like it joins just doesnt spawn the player. The player prefab has a network object, and heres how I have networkmanager set.

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

public class SteamNetworkManager : NetworkBehaviour
{
[SerializeField] private TextMeshProUGUI lobbyIDTextMeshPro; // Reference to your TextMeshPro UI element

public GameObject playerPrefab;
public Button hostButton;
public Button joinButton;
public TMP_InputField lobbyIDInputField;
public Canvas mainMenuCanvas;

private Callback<LobbyCreated_t> lobbyCreated;
private Callback<LobbyEnter_t> lobbyEntered;
private bool isAuthenticated = false; // Flag to track Steam authentication

private void Awake()
{
// Ensure Steamworks is initialized
if (!SteamManager.Initialized)
{
Debug.LogError(“Steam Manager not initialized.”);
return;
}

// Setup Steam lobby callbacks
lobbyCreated = Callback<LobbyCreated_t>.Create(OnLobbyCreated);
lobbyEntered = Callback<LobbyEnter_t>.Create(OnLobbyEntered);
}

private void Start()
{
// Add a listener to the host button
hostButton.onClick.AddListener(() =>
{
if (isAuthenticated) // Check if the user is authenticated before hosting a game
{
StartCoroutine(HostGame());
}
else
{
Debug.LogWarning(“User not authenticated. Please log in with Steam.”);
// You can display a message to the user to log in with Steam before hosting a game
}
});

// Add a listener to the join button
joinButton.onClick.AddListener(() =>
{
if (isAuthenticated) // Check if the user is authenticated before joining a game
{
StartCoroutine(JoinGame());
}
else
{
Debug.LogWarning(“User not authenticated. Please log in with Steam.”);
// You can display a message to the user to log in with Steam before joining a game
}
});

// Check if the user is authenticated with Steam
StartCoroutine(CheckAuthentication());

// Any additional logic from other Start methods should be added below.
// Make sure that the logic does not conflict and is compatible to run together.

// Example of additional logic:
// Initialize other systems if necessary
// …

// Register the callback for when clients connect, if you have a method set up for that
NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnected;

// Any other initialization code you may have
// …
}

private IEnumerator CheckAuthentication()
{
yield return new WaitUntil(() => SteamManager.Initialized); // Wait for Steam to initialize

isAuthenticated = SteamUser.BLoggedOn(); // Check if the user is logged in to Steam

if (isAuthenticated)
{
Debug.Log("Player authenticated. Username: " + SteamFriends.GetPersonaName());
}
else
{
Debug.LogWarning(“User not authenticated. Please log in with Steam.”);
// You can display a message to the user to log in with Steam
}
}

private IEnumerator HostGame()
{
// Create a Steam lobby
SteamMatchmaking.CreateLobby(ELobbyType.k_ELobbyTypeFriendsOnly, 4);

// Wait for the lobby to be created (handled in OnLobbyCreated)
yield return new WaitUntil(() => lobbyCreated != null);

// Start the Netcode host
NetworkManager.Singleton.StartHost();

// Wait for the host to initialize
yield return new WaitUntil(() => NetworkManager.Singleton.IsHost && NetworkManager.Singleton.IsServer);

// Hide the main menu and initialize the host player
mainMenuCanvas.gameObject.SetActive(false);
// Spawn the player for the host using the local client ID
SpawnPlayer(NetworkManager.Singleton.LocalClientId);
}

private void OnLobbyCreated(LobbyCreated_t callback)
{
if (callback.m_eResult == EResult.k_EResultOK)
{
Debug.Log("Lobby created successfully! Lobby ID: " + callback.m_ulSteamIDLobby);
// Ensure the TextMeshPro component is assigned
if (lobbyIDTextMeshPro != null)
{
// Update the UI element with the lobby ID
lobbyIDTextMeshPro.text = "Lobby ID: " + callback.m_ulSteamIDLobby.ToString();
// Copy the lobby ID to the clipboard
GUIUtility.systemCopyBuffer = callback.m_ulSteamIDLobby.ToString();
}
else
{
Debug.LogError(“TextMeshPro component for lobby ID is not assigned.”);
}
}
else
{
Debug.LogError(“Failed to create a lobby.”);
}
}

private IEnumerator JoinGame()
{
if (!ulong.TryParse(lobbyIDInputField.text, out ulong lobbyID))
{
Debug.LogError(“Invalid Lobby ID”);
yield break;
}

SteamMatchmaking.JoinLobby(new CSteamID(lobbyID));

// Wait for the lobby to be joined (handled in OnLobbyEntered)
yield return new WaitUntil(() => lobbyEntered != null);

// Start the Netcode client
NetworkManager.Singleton.StartClient();

// Wait for the client to connect
yield return new WaitUntil(() => NetworkManager.Singleton.IsClient);

// Hide the main menu
mainMenuCanvas.gameObject.SetActive(false);
// We don’t spawn the player here; the server will handle it
}

private void OnLobbyEntered(LobbyEnter_t callback)
{
if (NetworkManager.Singleton.IsClient)
{
Debug.Log("Joined lobby successfully, Lobby ID: " + callback.m_ulSteamIDLobby);
// Lobby has been joined, you might want to handle lobby data here
}
else
{
Debug.LogError(“Failed to join the lobby.”);
}
}

private void OnClientConnected(ulong clientId)
{
// Check if the client is not the host or if the host has not already spawned a player
if (!NetworkManager.Singleton.IsHost || !hasHostSpawned)
{
SpawnPlayer(clientId);
if (NetworkManager.Singleton.IsHost)
{
hasHostSpawned = true; // Set a flag to indicate the host player has been spawned
}

// Add a debug log to indicate that a client has connected
Debug.Log("Client connected to host. Client ID: " + clientId);
}
}

// Add this field to your class
private bool hasHostSpawned = false;

private void SpawnPlayer(ulong clientId)
{
GameObject playerInstance = Instantiate(playerPrefab);
NetworkObject networkObject = playerInstance.GetComponent();

if (networkObject != null)
{
networkObject.SpawnAsPlayerObject(clientId);
// Apply any additional player setup here, such as customization
}
else
{
Debug.LogError(“The player prefab does not have a NetworkObject component attached.”);
}
}

// Unregister the callback when this object is destroyed
private void OnDestroy()
{
if (NetworkManager.Singleton != null)
{
NetworkManager.Singleton.OnClientConnectedCallback -= OnClientConnected;
}
}

// Ensure proper cleanup on application quit
private void OnApplicationQuit()
{
if (NetworkManager.Singleton != null)
{
NetworkManager.Singleton.Shutdown();
}
SteamAPI.Shutdown();
}

public class PlayerCustomization : NetworkBehaviour
{
private NetworkVariable hairColor = new NetworkVariable();
private NetworkVariable armorType = new NetworkVariable();

// Call this on the server to update customization
public void UpdateCustomization(Color newHairColor, string newArmorType)
{
hairColor.Value = newHairColor;
armorType.Value = newArmorType;
}

// On the client, subscribe to changes
private void OnEnable()
{
hairColor.OnValueChanged += OnHairColorChanged;
armorType.OnValueChanged += OnArmorTypeChanged;
}

private void OnDisable()
{
hairColor.OnValueChanged -= OnHairColorChanged;
armorType.OnValueChanged -= OnArmorTypeChanged;
}

private void OnHairColorChanged(Color oldColor, Color newColor)
{
// Apply color to the player’s hair
}

private void OnArmorTypeChanged(string oldType, string newType)
{
// Change the player’s armor
}
}
}

*--------------------------------------------------------------------------------------------

Code for player controller, before I integrated steam I was able to move around and do what this script intented.
using UnityEngine;
using Unity.Netcode;

public class PlayerController : NetworkBehaviour
{
// Movement variables
public float walkSpeed = 5f;
public float runSpeed = 10f;
public float jumpForce = 10f;
public float gravity = -20f;
private Vector3 moveDirection = Vector3.zero;
private CharacterController characterController;
private bool isGrounded;

// Camera variables
//public Transform playerCamera;
public float mouseSensitivity = 2f;
private float verticalRotation = 0f;
public Camera playerCamera; // Assign this in the inspector with your prefab camera

// Weapon variables
public GameObject meleeWeaponPrefab;
public GameObject rangedWeaponPrefab;
public GameObject throwableWeaponPrefab;
public Transform weaponSpawnPoint; // Spawn point for weapons
public Transform ammoSpawnPoint; // Spawn point for ammo
private GameObject currentWeapon;
private enum WeaponType { Melee, Ranged, Throwable };
private WeaponType currentWeaponType;

// Stamina variables
public float maxStamina = 100f;
private float currentStamina;
public float staminaRegenRate = 5f;

// Animator variables
public Animator animator;
private const string ANIM_PARAM_MOVE_SPEED = “MoveSpeed”;
private const string ANIM_PARAM_IS_JUMPING = “IsJumping”;
private const string ANIM_PARAM_IS_MELEE_ATTACKING = “IsMeleeAttacking”;
private const string ANIM_PARAM_IS_RANGED_ATTACKING = “IsRangedAttacking”;
private const string ANIM_PARAM_IS_THROWING = “IsThrowing”;

public GameObject bulletPrefab;
public float bulletSpeed = 10f;
public GameObject grenadePrefab;

private Camera playerCam; // For caching the Camera component

void Start()
{
characterController = GetComponent();
currentStamina = maxStamina;
EquipWeapon(WeaponType.Melee);

// Cache the Camera component at the start
if (playerCamera != null)
{
playerCam = playerCamera.GetComponent();
}
}

void Update()
{
if (IsOwner)
return;

// Movement controls
isGrounded = characterController.isGrounded;
if (isGrounded)
{
moveDirection = new Vector3(Input.GetAxis(“Horizontal”), 0, Input.GetAxis(“Vertical”));
moveDirection = transform.TransformDirection(moveDirection);
moveDirection *= Input.GetKey(KeyCode.LeftShift) && currentWeaponType != WeaponType.Ranged ? runSpeed : walkSpeed;

if (Input.GetButtonDown(“Jump”))
{
moveDirection.y = jumpForce;
animator.SetBool(ANIM_PARAM_IS_JUMPING, true);
}
else
{
animator.SetBool(ANIM_PARAM_IS_JUMPING, false);
}
}
moveDirection.y += gravity * Time.deltaTime;
characterController.Move(moveDirection * Time.deltaTime);

// Update animator parameters for movement
float moveSpeed = moveDirection.magnitude / (Input.GetKey(KeyCode.LeftShift) && currentWeaponType != WeaponType.Ranged ? runSpeed : walkSpeed);
animator.SetFloat(ANIM_PARAM_MOVE_SPEED, moveSpeed);

// Camera rotation
float mouseX = Input.GetAxis(“Mouse X”) * mouseSensitivity;
float mouseY = -Input.GetAxis(“Mouse Y”) * mouseSensitivity;
verticalRotation += mouseY;
verticalRotation = Mathf.Clamp(verticalRotation, -90f, 90f);
playerCamera.transform.localRotation = Quaternion.Euler(verticalRotation, 0, 0);

transform.Rotate(Vector3.up * mouseX);

// Weapon switching
if (Input.GetAxis(“Mouse ScrollWheel”) > 0)
SwitchWeapon(1);
else if (Input.GetAxis(“Mouse ScrollWheel”) < 0)
SwitchWeapon(-1);

// Stamina logic
if (Input.GetKey(KeyCode.LeftShift) && currentWeaponType != WeaponType.Ranged)
currentStamina -= Time.deltaTime;
else if (currentStamina < maxStamina)
currentStamina += staminaRegenRate * Time.deltaTime;

currentStamina = Mathf.Clamp(currentStamina, 0, maxStamina);

// Update animator parameters for attacks
UpdateAttackAnimations();

// Ranged weapon functionality
if (currentWeaponType == WeaponType.Ranged)
{
if (Input.GetMouseButtonDown(0))
{
ShootBullet();
}
else if (Input.GetMouseButtonDown(1))
{
// Add zoom functionality here
}
}

// Throwable weapon functionality
if (currentWeaponType == WeaponType.Throwable)
{
if (Input.GetMouseButtonDown(0))
{
ThrowGrenade();
}
}
}

void EquipWeapon(WeaponType weaponType)
{
// Destroy the current weapon if it exists
if (currentWeapon != null)
{
NetworkObject weaponNetworkObject = currentWeapon.GetComponent();
if (weaponNetworkObject != null)
{
weaponNetworkObject.Despawn();
}
else
{
Destroy(currentWeapon);
}
}

// Instantiate the new weapon
switch (weaponType)
{
case WeaponType.Melee:
currentWeapon = Instantiate(meleeWeaponPrefab, weaponSpawnPoint.position, weaponSpawnPoint.rotation, weaponSpawnPoint);
// Adjust the rotation of the melee weapon if needed
currentWeapon.transform.localRotation = Quaternion.Euler(0f, 0f, 0f); // Adjust as needed
break;
case WeaponType.Ranged:
currentWeapon = Instantiate(rangedWeaponPrefab, weaponSpawnPoint.position, weaponSpawnPoint.rotation, weaponSpawnPoint);
// Adjust the rotation of the ranged weapon if needed
currentWeapon.transform.localRotation = Quaternion.Euler(0f, 90f, 0f); // Adjust as needed
break;
case WeaponType.Throwable:
currentWeapon = Instantiate(throwableWeaponPrefab, weaponSpawnPoint.position, weaponSpawnPoint.rotation, weaponSpawnPoint);
// Adjust the rotation of the throwable weapon if needed
currentWeapon.transform.localRotation = Quaternion.Euler(0f, 0f, 0f); // Adjust as needed
break;
}
currentWeaponType = weaponType;

// Spawn the weapon on the network
NetworkObject newWeaponNetworkObject = currentWeapon.GetComponent();
if (newWeaponNetworkObject != null)
{
newWeaponNetworkObject.Spawn();
}
}

void SwitchWeapon(int direction)
{
int newWeaponType = ((int)currentWeaponType + direction) % 3;
if (newWeaponType < 0)
newWeaponType = 2;

EquipWeapon((WeaponType)newWeaponType);
}

void UpdateAttackAnimations()
{
if (Input.GetMouseButtonDown(0))
{
if (currentWeaponType == WeaponType.Melee)
animator.SetTrigger(ANIM_PARAM_IS_MELEE_ATTACKING);
else if (currentWeaponType == WeaponType.Ranged)
animator.SetTrigger(ANIM_PARAM_IS_RANGED_ATTACKING);
else if (currentWeaponType == WeaponType.Throwable)
animator.SetTrigger(ANIM_PARAM_IS_THROWING);
}
}

void ShootBullet()
{
// Ensure that the current weapon is a ranged weapon
if (currentWeaponType != WeaponType.Ranged)
return;

// Define the point in the game world where the player aims
Vector3 aimPoint;

// Create a ray from the camera through the mouse cursor
Ray ray = playerCamera.GetComponent().ScreenPointToRay(Input.mousePosition);

RaycastHit hit;

// Check if the ray intersects with the game world
if (Physics.Raycast(ray, out hit))
{
// If the ray hits something, set the aim point to the point of intersection
aimPoint = hit.point;
}
else
{
// If the ray doesn’t hit anything, set the aim point to a point far in the distance
aimPoint = ray.GetPoint(1000); // Adjust the distance as needed
}

// Get the direction towards the aim point from the ammo spawn point
Vector3 direction = (aimPoint - ammoSpawnPoint.position).normalized;

// Instantiate the bullet at the ammo spawn point
GameObject bullet = Instantiate(bulletPrefab, ammoSpawnPoint.position, Quaternion.identity);
Rigidbody bulletRigidbody = bullet.GetComponent();

// Set the velocity of the bullet to simulate shooting towards the aim point
bulletRigidbody.velocity = direction * bulletSpeed;

// Destroy the bullet after a certain duration
Destroy(bullet, 2f);
}

public float throwForce = 40f; // Adjust the throw force to match your game’s scale and desired grenade arc.

void ThrowGrenade()
{
// Instantiate the grenade at the weapon spawn point.
GameObject grenade = Instantiate(grenadePrefab, weaponSpawnPoint.position, weaponSpawnPoint.rotation);

// Ensure your grenade prefab has a Rigidbody component attached for physics simulation.
Rigidbody rb = grenade.GetComponent();

if (rb != null)
{
// Calculate the direction to throw the grenade towards where the player is aiming.
Vector3 throwDirection = CalculateThrowDirection();

// Apply the throw force to the grenade’s Rigidbody component.
rb.AddForce(throwDirection * throwForce, ForceMode.VelocityChange);
}
else
{
Debug.LogWarning(“Grenade prefab does not have a Rigidbody component.”);
}
}

Vector3 CalculateThrowDirection()
{
// Assuming you have a camera that follows the player and is the perspective from which the player aims.
// This can be your main camera or a camera attached to the player character.

// Create a ray from the camera to a point far ahead in the direction it’s facing.
//Ray aimRay = new Ray(playerCamera.position, playerCamera.forward);

// Alternatively, to aim towards the mouse cursor on the screen, use:
Ray aimRay = playerCamera.GetComponent().ScreenPointToRay(Input.mousePosition);

// You can adjust the direction based on the player’s aim or any other criteria.
Vector3 aimPoint = aimRay.GetPoint(10); // Example: Get a point 10 units in front of the camera along its forward direction.

// The direction to throw the grenade is from the weapon spawn point towards the aim point.
Vector3 throwDirection = (aimPoint - weaponSpawnPoint.position).normalized;

// Add an upward bias to simulate a throwing arc. Adjust the vector to control the arc’s height.
throwDirection += Vector3.up * 0.5f; // Modify this value to achieve the desired arc.

return throwDirection;
}

}

Cant edit this from phone well… I also added unitys authentication package and set up in project settings with correct appid and web key… then went to unity dashboard and did same thing. My second machine is in a virtual machine on same computer logged in with 2nd steam account. Ive tried host from editor or exe and vise versa for client.

Please put the code in code tags. It’s unreadable as is, and rather long too (code tags hide most code under a “more” link).

Question: does it work without steam? I mean players joining and moving. If you haven’t verified this, take a step back and fix the netcode part for this.

I also wonder about the various coroutines. Is the Steam API not awaitable?

using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using Steamworks;
using Unity.Netcode;
using TMPro;
using System;
using System.Collections.Generic; // Add this namespace

public class SteamNetworkManager : NetworkBehaviour
{
    [SerializeField] private TextMeshProUGUI lobbyIDTextMeshPro; // Reference to your TextMeshPro UI element
    [SerializeField] private GameObject yourCustomPlayerPrefab;

    public GameObject playerPrefab;
    public Button hostButton;
    public Button joinButton;
    public TMP_InputField lobbyIDInputField;
    public Canvas mainMenuCanvas;

    private Callback<LobbyCreated_t> lobbyCreated;
    private Callback<LobbyEnter_t> lobbyEntered;
    private bool isAuthenticated = false; // Flag to track Steam authentication
    private const int maxLobbyPlayers = 4;
    private bool hostPlayerSpawned = false;
    private bool isHosting = false; // Add this line
    private bool isPlayerSpawned = false; // Add this line

    // Add this field to your class
    private Dictionary<ulong, bool> playerSpawnedDict = new Dictionary<ulong, bool>();

    private void Awake()
    {
        // Ensure Steamworks is initialized
        if (!SteamManager.Initialized)
        {
            Debug.LogError("Steam Manager not initialized.");
            return;
        }

        // Setup Steam lobby callbacks
        lobbyCreated = Callback<LobbyCreated_t>.Create(OnLobbyCreated);
        lobbyEntered = Callback<LobbyEnter_t>.Create(OnLobbyEntered);
    }

    private void Start()
    {
        // Add a listener to the host button
        hostButton.onClick.AddListener(StartHosting);

        // Add a listener to the join button
        joinButton.onClick.AddListener(() =>
        {
            if (isAuthenticated) // Check if the user is authenticated before joining a game
            {
                StartCoroutine(JoinGame());
            }
            else
            {
                Debug.LogWarning("User not authenticated. Please log in with Steam.");
                // You can display a message to the user to log in with Steam before joining a game
            }
        });

        // Check if the user is authenticated with Steam
        StartCoroutine(CheckAuthentication());

        // Register the callback for when clients connect, if you have a method set up for that
        NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnected;
    }

    private IEnumerator CheckAuthentication()
    {
        yield return new WaitUntil(() => SteamManager.Initialized); // Wait for Steam to initialize

        isAuthenticated = SteamUser.BLoggedOn(); // Check if the user is logged in to Steam

        if (isAuthenticated)
        {
            Debug.Log("Player authenticated. Username: " + SteamFriends.GetPersonaName());
        }
        else
        {
            Debug.LogWarning("User not authenticated. Please log in with Steam.");
            // You can display a message to the user to log in with Steam
        }
    }

    // This function is called when the "Host Game" button is pressed.
    public void StartHosting()
    {
        // Check if Steamworks is initialized and the player is authenticated before trying to host a game.
        if (SteamManager.Initialized && SteamUser.BLoggedOn())
        {
            StartCoroutine(HostGame());
        }
        else
        {
            Debug.LogWarning("Steam is not initialized, or the player is not logged in.");
        }
    }

    public override void OnNetworkSpawn()
    {
        if (IsServer) // Only the server (host) should spawn players
        {
            Debug.Log("[OnNetworkSpawn] Server is spawning the player for the host.");
            SpawnPlayer(NetworkManager.Singleton.LocalClientId);
        }
    }

    private IEnumerator HostGame()
    {
        // Check if hosting is already in progress or if already a server
        if (isHosting || NetworkManager.Singleton.IsServer)
        {
            yield break; // Exit the coroutine if already hosting or already a server
        }

        isHosting = true; // Set the flag to indicate that hosting is starting

        if (!isAuthenticated)
        {
            Debug.LogWarning("User not authenticated. Please log in with Steam.");
            isHosting = false; // Reset the flag
            yield break; // Exit the coroutine if not authenticated
        }

        // Create a Steam lobby.
        SteamMatchmaking.CreateLobby(ELobbyType.k_ELobbyTypeFriendsOnly, 4);
        // Wait for lobby to be created and for lobbyCreated callback to be set.
        yield return new WaitUntil(() => lobbyCreated != null);

        // Once the lobby is created, start the host.
        NetworkManager.Singleton.StartHost();

        // Wait until the NetworkManager is ready and the host is started.
        yield return new WaitUntil(() => NetworkManager.Singleton.IsListening && NetworkManager.Singleton.IsHost && NetworkManager.Singleton.IsServer);

        // Hide the UI Canvas after the NetworkManager is ready.
        mainMenuCanvas.gameObject.SetActive(false);

        // Spawn the player for the host.
        if (!hostPlayerSpawned)
        {
            SpawnPlayer(NetworkManager.Singleton.LocalClientId);
            hostPlayerSpawned = true; // Set the flag to true after spawning the host
        }

        isHosting = false; // Reset the flag after hosting is complete
    }

    private void OnLobbyCreated(LobbyCreated_t callback)
    {
        if (callback.m_eResult == EResult.k_EResultOK)
        {
            Debug.Log("[OnLobbyCreated] Lobby creation callback received with result: k_EResultOK");
            Debug.Log("[OnLobbyCreated] Lobby created successfully! Lobby ID: " + callback.m_ulSteamIDLobby);
            lobbyIDTextMeshPro.text = "Lobby ID: " + callback.m_ulSteamIDLobby.ToString();
            SteamMatchmaking.SetLobbyJoinable(new CSteamID(callback.m_ulSteamIDLobby), true);
            // Do not call StartHost here. It will be called in HostGame coroutine.
        }
        else
        {
            Debug.LogError("[OnLobbyCreated] Failed to create a lobby.");
        }
    }


    private IEnumerator JoinGame()
    {
        if (!isAuthenticated)
        {
            Debug.LogWarning("User not authenticated. Please log in with Steam.");
            yield break; // Exit the coroutine if not authenticated
        }

        if (!ulong.TryParse(lobbyIDInputField.text, out ulong lobbyID))
        {
            Debug.LogError("Invalid Lobby ID");
            yield break;
        }

        SteamMatchmaking.JoinLobby(new CSteamID(lobbyID));
        yield return new WaitUntil(() => lobbyEntered != null);
        NetworkManager.Singleton.StartClient();
        yield return new WaitUntil(() => NetworkManager.Singleton.IsClient);
        mainMenuCanvas.gameObject.SetActive(false);
        // The server will spawn the player.
    }

    private void OnLobbyEntered(LobbyEnter_t callback)
    {
        Debug.Log("Joined lobby successfully, Lobby ID: " + callback.m_ulSteamIDLobby);

        // Start the Netcode client if this instance isn't already a host/server and has joined a lobby
        if (!NetworkManager.Singleton.IsClient && !NetworkManager.Singleton.IsHost && !NetworkManager.Singleton.IsServer)
        {
            NetworkManager.Singleton.StartClient();
        }
    }



    private void OnClientConnected(ulong clientId)
    {
        Debug.Log($"[OnClientConnected] Client connected with client ID: {clientId}");

        if (IsServer && clientId != NetworkManager.ServerClientId)
        {
            // We check if the player is already spawned in SpawnPlayer method
            Debug.Log($"[OnClientConnected] Server is spawning the player for client ID: {clientId}");
            SpawnPlayer(clientId);
        }
    }


    private void SpawnPlayer(ulong clientId)
    {
        Debug.Log($"[SpawnPlayer] Attempting to spawn player for client ID: {clientId}");

        // Prevent the host from spawning more than once
        if (clientId == NetworkManager.Singleton.LocalClientId && isPlayerSpawned)
        {
            Debug.LogWarning("[SpawnPlayer] Host player is already spawned. Aborting spawn.");
            return;
        }

        // Prevent non-host players from being spawned multiple times
        if (playerSpawnedDict.TryGetValue(clientId, out bool isSpawned) && isSpawned)
        {
            Debug.LogWarning($"[SpawnPlayer] Player for client ID {clientId} is already spawned. Aborting spawn.");
            return;
        }

        // Instantiate and spawn the player object
        GameObject playerInstance = Instantiate(playerPrefab);
        NetworkObject networkObject = playerInstance.GetComponent<NetworkObject>();

        if (networkObject != null)
        {
            Debug.Log($"[SpawnPlayer] Spawning network object for client ID: {clientId}");
            networkObject.SpawnAsPlayerObject(clientId);
            playerSpawnedDict[clientId] = true; // Record that the player has been spawned

            // Mark the host as spawned
            if (clientId == NetworkManager.Singleton.LocalClientId)
            {
                isPlayerSpawned = true;
            }
        }
        else
        {
            Debug.LogError("[SpawnPlayer] The player prefab does not have a NetworkObject component attached.");
        }
    }

    // Unregister the callback when this object is destroyed
    public override void OnDestroy()
    {
        // Dispose of your lobby callbacks
        lobbyCreated.Dispose();
        lobbyEntered.Dispose();

        // Safely unsubscribe from the OnClientConnectedCallback event
        if (NetworkManager.Singleton != null)
        {
            NetworkManager.Singleton.OnClientConnectedCallback -= OnClientConnected;
        }

        // Call the base class OnDestroy method
        base.OnDestroy();
    }

    // Ensure proper cleanup on application quit
    private void OnApplicationQuit()
    {
        if (NetworkManager.Singleton != null)
        {
            NetworkManager.Singleton.Shutdown();
        }
        SteamAPI.Shutdown();
    }
}

Not sure… I’ve updated the code and attached. It seems like the host always works and generates a lobby id correctly and shows my steam id authenticated. (I have lobby id print to canvas so I can put in other steam account to join) when it joins it just shows the main camera. If I had to guess when it goes to spawn the client its trying to spawn it as client ID:0 which is the host…

[OnNetworkSpawn] Server is spawning the player for the host.
UnityEngine.Debug:Log (object)
SteamNetworkManager:OnNetworkSpawn () (at Assets/Scripts/SteamNetworkManager.cs:107)
Unity.Netcode.NetworkBehaviour:VisibleOnNetworkSpawn () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Core/NetworkBehaviour.cs:637)
Unity.Netcode.NetworkObject:InvokeBehaviourNetworkSpawn () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Core/NetworkObject.cs:1369)
Unity.Netcode.NetworkSpawnManager:SpawnNetworkObjectLocallyCommon (Unity.Netcode.NetworkObject,ulong,bool,bool,ulong,bool) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Spawning/NetworkSpawnManager.cs:726)
Unity.Netcode.NetworkSpawnManager:SpawnNetworkObjectLocally (Unity.Netcode.NetworkObject,ulong,bool,bool,ulong,bool) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Spawning/NetworkSpawnManager.cs:629)
Unity.Netcode.NetworkSpawnManager:ServerSpawnSceneObjectsOnStartSweep () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Spawning/NetworkSpawnManager.cs:954)
Unity.Netcode.NetworkManager:HostServerInitialize () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Core/NetworkManager.cs:1083)
Unity.Netcode.NetworkManager:StartHost () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Core/NetworkManager.cs:1035)
SteamNetworkManager/d__20:MoveNext () (at Assets/Scripts/SteamNetworkManager.cs:135)
UnityEngine.SetupCoroutine:InvokeMoveNext (System.Collections.IEnumerator,intptr)

[SpawnPlayer] Attempting to spawn player for client ID: 0
UnityEngine.Debug:Log (object)
SteamNetworkManager:SpawnPlayer (ulong) (at Assets/Scripts/SteamNetworkManager.cs:220)
SteamNetworkManager:OnNetworkSpawn () (at Assets/Scripts/SteamNetworkManager.cs:108)
Unity.Netcode.NetworkBehaviour:VisibleOnNetworkSpawn () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Core/NetworkBehaviour.cs:637)
Unity.Netcode.NetworkObject:InvokeBehaviourNetworkSpawn () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Core/NetworkObject.cs:1369)
Unity.Netcode.NetworkSpawnManager:SpawnNetworkObjectLocallyCommon (Unity.Netcode.NetworkObject,ulong,bool,bool,ulong,bool) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Spawning/NetworkSpawnManager.cs:726)
Unity.Netcode.NetworkSpawnManager:SpawnNetworkObjectLocally (Unity.Netcode.NetworkObject,ulong,bool,bool,ulong,bool) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Spawning/NetworkSpawnManager.cs:629)
Unity.Netcode.NetworkSpawnManager:ServerSpawnSceneObjectsOnStartSweep () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Spawning/NetworkSpawnManager.cs:954)
Unity.Netcode.NetworkManager:HostServerInitialize () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Core/NetworkManager.cs:1083)
Unity.Netcode.NetworkManager:StartHost () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Core/NetworkManager.cs:1035)
SteamNetworkManager/d__20:MoveNext () (at Assets/Scripts/SteamNetworkManager.cs:135)
UnityEngine.SetupCoroutine:InvokeMoveNext (System.Collections.IEnumerator,intptr)

[SpawnPlayer] Spawning network object for client ID: 0
UnityEngine.Debug:Log (object)
SteamNetworkManager:SpawnPlayer (ulong) (at Assets/Scripts/SteamNetworkManager.cs:242)
SteamNetworkManager:OnNetworkSpawn () (at Assets/Scripts/SteamNetworkManager.cs:108)
Unity.Netcode.NetworkBehaviour:VisibleOnNetworkSpawn () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Core/NetworkBehaviour.cs:637)
Unity.Netcode.NetworkObject:InvokeBehaviourNetworkSpawn () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Core/NetworkObject.cs:1369)
Unity.Netcode.NetworkSpawnManager:SpawnNetworkObjectLocallyCommon (Unity.Netcode.NetworkObject,ulong,bool,bool,ulong,bool) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Spawning/NetworkSpawnManager.cs:726)
Unity.Netcode.NetworkSpawnManager:SpawnNetworkObjectLocally (Unity.Netcode.NetworkObject,ulong,bool,bool,ulong,bool) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Spawning/NetworkSpawnManager.cs:629)
Unity.Netcode.NetworkSpawnManager:ServerSpawnSceneObjectsOnStartSweep () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Spawning/NetworkSpawnManager.cs:954)
Unity.Netcode.NetworkManager:HostServerInitialize () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Core/NetworkManager.cs:1083)
Unity.Netcode.NetworkManager:StartHost () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Core/NetworkManager.cs:1035)
SteamNetworkManager/d__20:MoveNext () (at Assets/Scripts/SteamNetworkManager.cs:135)
UnityEngine.SetupCoroutine:InvokeMoveNext (System.Collections.IEnumerator,intptr)

[OnClientConnected] Client connected with client ID: 0
UnityEngine.Debug:Log (object)
SteamNetworkManager:OnClientConnected (ulong) (at Assets/Scripts/SteamNetworkManager.cs:207)
Unity.Netcode.NetworkConnectionManager:InvokeOnClientConnectedCallback (ulong) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Connection/NetworkConnectionManager.cs:87)
Unity.Netcode.NetworkManager:HostServerInitialize () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Core/NetworkManager.cs:1091)
Unity.Netcode.NetworkManager:StartHost () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.8.0/Runtime/Core/NetworkManager.cs:1035)
SteamNetworkManager/d__20:MoveNext () (at Assets/Scripts/SteamNetworkManager.cs:135)
UnityEngine.SetupCoroutine:InvokeMoveNext (System.Collections.IEnumerator,intptr)

There are 2 audio listeners in the scene. Please ensure there is always exactly one audio listener in the scene.

[SpawnPlayer] Attempting to spawn player for client ID: 0
UnityEngine.Debug:Log (object)
SteamNetworkManager:SpawnPlayer (ulong) (at Assets/Scripts/SteamNetworkManager.cs:220)
SteamNetworkManager/d__20:MoveNext () (at Assets/Scripts/SteamNetworkManager.cs:146)
UnityEngine.SetupCoroutine:InvokeMoveNext (System.Collections.IEnumerator,intptr)

[SpawnPlayer] Host player is already spawned. Aborting spawn.
UnityEngine.Debug:LogWarning (object)
SteamNetworkManager:SpawnPlayer (ulong) (at Assets/Scripts/SteamNetworkManager.cs:225)
SteamNetworkManager/d__20:MoveNext () (at Assets/Scripts/SteamNetworkManager.cs:146)
UnityEngine.SetupCoroutine:InvokeMoveNext (System.Collections.IEnumerator,intptr)

[OnLobbyCreated] Lobby creation callback received with result: k_EResultOK
UnityEngine.Debug:Log (object)
SteamNetworkManager:OnLobbyCreated (Steamworks.LobbyCreated_t) (at Assets/Scripts/SteamNetworkManager.cs:157)
Steamworks.Callback`1<Steamworks.LobbyCreated_t>:OnRunCallback (intptr) (at Assets/com.rlabrecque.steamworks.net/Runtime/CallbackDispatcher.cs:291)
Steamworks.CallbackDispatcher:RunFrame (bool) (at Assets/com.rlabrecque.steamworks.net/Runtime/CallbackDispatcher.cs:191)
Steamworks.SteamAPI:RunCallbacks () (at Assets/com.rlabrecque.steamworks.net/Runtime/Steam.cs:112)
SteamManager:Update () (at Assets/Scripts/Steamworks.NET/SteamManager.cs:170)

[OnLobbyCreated] Lobby created successfully! Lobby ID: 109775243058676912
UnityEngine.Debug:Log (object)
SteamNetworkManager:OnLobbyCreated (Steamworks.LobbyCreated_t) (at Assets/Scripts/SteamNetworkManager.cs:158)
Steamworks.Callback`1<Steamworks.LobbyCreated_t>:OnRunCallback (intptr) (at Assets/com.rlabrecque.steamworks.net/Runtime/CallbackDispatcher.cs:291)
Steamworks.CallbackDispatcher:RunFrame (bool) (at Assets/com.rlabrecque.steamworks.net/Runtime/CallbackDispatcher.cs:191)
Steamworks.SteamAPI:RunCallbacks () (at Assets/com.rlabrecque.steamworks.net/Runtime/Steam.cs:112)
SteamManager:Update () (at Assets/Scripts/Steamworks.NET/SteamManager.cs:170)

Joined lobby successfully, Lobby ID: 109775243058676912
UnityEngine.Debug:Log (object)
SteamNetworkManager:OnLobbyEntered (Steamworks.LobbyEnter_t) (at Assets/Scripts/SteamNetworkManager.cs:194)
Steamworks.Callback`1<Steamworks.LobbyEnter_t>:OnRunCallback (intptr) (at Assets/com.rlabrecque.steamworks.net/Runtime/CallbackDispatcher.cs:291)
Steamworks.CallbackDispatcher:RunFrame (bool) (at Assets/com.rlabrecque.steamworks.net/Runtime/CallbackDispatcher.cs:191)
Steamworks.SteamAPI:RunCallbacks () (at Assets/com.rlabrecque.steamworks.net/Runtime/Steam.cs:112)
SteamManager:Update () (at Assets/Scripts/Steamworks.NET/SteamManager.cs:170)