Search Unity

  1. We are migrating the Unity Forums to Unity Discussions by the end of July. Read our announcement for more information and let us know if you have any questions.
    Dismiss Notice
  2. Dismiss Notice

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

Discussion in 'Netcode for GameObjects' started by pacificeve, Mar 9, 2024.

  1. pacificeve

    pacificeve

    Joined:
    Jan 6, 2018
    Posts:
    9
    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. upload_2024-3-9_18-19-31.png

    upload_2024-3-9_18-17-49.png





    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<NetworkObject>();

    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<Color> hairColor = new NetworkVariable<Color>();
    private NetworkVariable<string> armorType = new NetworkVariable<string>();

    // 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<CharacterController>();
    currentStamina = maxStamina;
    EquipWeapon(WeaponType.Melee);

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

    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<NetworkObject>();
    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<NetworkObject>();
    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<Camera>().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<Rigidbody>();

    // 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<Rigidbody>();

    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<Camera>().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;
    }

    }
     
  2. pacificeve

    pacificeve

    Joined:
    Jan 6, 2018
    Posts:
    9
    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.
     
  3. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    7,019
    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?
     
  4. pacificeve

    pacificeve

    Joined:
    Jan 6, 2018
    Posts:
    9
    Code (CSharp):
    1. using System.Collections;
    2. using UnityEngine;
    3. using UnityEngine.UI;
    4. using Steamworks;
    5. using Unity.Netcode;
    6. using TMPro;
    7. using System;
    8. using System.Collections.Generic; // Add this namespace
    9.  
    10. public class SteamNetworkManager : NetworkBehaviour
    11. {
    12.     [SerializeField] private TextMeshProUGUI lobbyIDTextMeshPro; // Reference to your TextMeshPro UI element
    13.     [SerializeField] private GameObject yourCustomPlayerPrefab;
    14.  
    15.     public GameObject playerPrefab;
    16.     public Button hostButton;
    17.     public Button joinButton;
    18.     public TMP_InputField lobbyIDInputField;
    19.     public Canvas mainMenuCanvas;
    20.  
    21.     private Callback<LobbyCreated_t> lobbyCreated;
    22.     private Callback<LobbyEnter_t> lobbyEntered;
    23.     private bool isAuthenticated = false; // Flag to track Steam authentication
    24.     private const int maxLobbyPlayers = 4;
    25.     private bool hostPlayerSpawned = false;
    26.     private bool isHosting = false; // Add this line
    27.     private bool isPlayerSpawned = false; // Add this line
    28.  
    29.     // Add this field to your class
    30.     private Dictionary<ulong, bool> playerSpawnedDict = new Dictionary<ulong, bool>();
    31.  
    32.     private void Awake()
    33.     {
    34.         // Ensure Steamworks is initialized
    35.         if (!SteamManager.Initialized)
    36.         {
    37.             Debug.LogError("Steam Manager not initialized.");
    38.             return;
    39.         }
    40.  
    41.         // Setup Steam lobby callbacks
    42.         lobbyCreated = Callback<LobbyCreated_t>.Create(OnLobbyCreated);
    43.         lobbyEntered = Callback<LobbyEnter_t>.Create(OnLobbyEntered);
    44.     }
    45.  
    46.     private void Start()
    47.     {
    48.         // Add a listener to the host button
    49.         hostButton.onClick.AddListener(StartHosting);
    50.  
    51.         // Add a listener to the join button
    52.         joinButton.onClick.AddListener(() =>
    53.         {
    54.             if (isAuthenticated) // Check if the user is authenticated before joining a game
    55.             {
    56.                 StartCoroutine(JoinGame());
    57.             }
    58.             else
    59.             {
    60.                 Debug.LogWarning("User not authenticated. Please log in with Steam.");
    61.                 // You can display a message to the user to log in with Steam before joining a game
    62.             }
    63.         });
    64.  
    65.         // Check if the user is authenticated with Steam
    66.         StartCoroutine(CheckAuthentication());
    67.  
    68.         // Register the callback for when clients connect, if you have a method set up for that
    69.         NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnected;
    70.     }
    71.  
    72.     private IEnumerator CheckAuthentication()
    73.     {
    74.         yield return new WaitUntil(() => SteamManager.Initialized); // Wait for Steam to initialize
    75.  
    76.         isAuthenticated = SteamUser.BLoggedOn(); // Check if the user is logged in to Steam
    77.  
    78.         if (isAuthenticated)
    79.         {
    80.             Debug.Log("Player authenticated. Username: " + SteamFriends.GetPersonaName());
    81.         }
    82.         else
    83.         {
    84.             Debug.LogWarning("User not authenticated. Please log in with Steam.");
    85.             // You can display a message to the user to log in with Steam
    86.         }
    87.     }
    88.  
    89.     // This function is called when the "Host Game" button is pressed.
    90.     public void StartHosting()
    91.     {
    92.         // Check if Steamworks is initialized and the player is authenticated before trying to host a game.
    93.         if (SteamManager.Initialized && SteamUser.BLoggedOn())
    94.         {
    95.             StartCoroutine(HostGame());
    96.         }
    97.         else
    98.         {
    99.             Debug.LogWarning("Steam is not initialized, or the player is not logged in.");
    100.         }
    101.     }
    102.  
    103.     public override void OnNetworkSpawn()
    104.     {
    105.         if (IsServer) // Only the server (host) should spawn players
    106.         {
    107.             Debug.Log("[OnNetworkSpawn] Server is spawning the player for the host.");
    108.             SpawnPlayer(NetworkManager.Singleton.LocalClientId);
    109.         }
    110.     }
    111.  
    112.     private IEnumerator HostGame()
    113.     {
    114.         // Check if hosting is already in progress or if already a server
    115.         if (isHosting || NetworkManager.Singleton.IsServer)
    116.         {
    117.             yield break; // Exit the coroutine if already hosting or already a server
    118.         }
    119.  
    120.         isHosting = true; // Set the flag to indicate that hosting is starting
    121.  
    122.         if (!isAuthenticated)
    123.         {
    124.             Debug.LogWarning("User not authenticated. Please log in with Steam.");
    125.             isHosting = false; // Reset the flag
    126.             yield break; // Exit the coroutine if not authenticated
    127.         }
    128.  
    129.         // Create a Steam lobby.
    130.         SteamMatchmaking.CreateLobby(ELobbyType.k_ELobbyTypeFriendsOnly, 4);
    131.         // Wait for lobby to be created and for lobbyCreated callback to be set.
    132.         yield return new WaitUntil(() => lobbyCreated != null);
    133.  
    134.         // Once the lobby is created, start the host.
    135.         NetworkManager.Singleton.StartHost();
    136.  
    137.         // Wait until the NetworkManager is ready and the host is started.
    138.         yield return new WaitUntil(() => NetworkManager.Singleton.IsListening && NetworkManager.Singleton.IsHost && NetworkManager.Singleton.IsServer);
    139.  
    140.         // Hide the UI Canvas after the NetworkManager is ready.
    141.         mainMenuCanvas.gameObject.SetActive(false);
    142.  
    143.         // Spawn the player for the host.
    144.         if (!hostPlayerSpawned)
    145.         {
    146.             SpawnPlayer(NetworkManager.Singleton.LocalClientId);
    147.             hostPlayerSpawned = true; // Set the flag to true after spawning the host
    148.         }
    149.  
    150.         isHosting = false; // Reset the flag after hosting is complete
    151.     }
    152.  
    153.     private void OnLobbyCreated(LobbyCreated_t callback)
    154.     {
    155.         if (callback.m_eResult == EResult.k_EResultOK)
    156.         {
    157.             Debug.Log("[OnLobbyCreated] Lobby creation callback received with result: k_EResultOK");
    158.             Debug.Log("[OnLobbyCreated] Lobby created successfully! Lobby ID: " + callback.m_ulSteamIDLobby);
    159.             lobbyIDTextMeshPro.text = "Lobby ID: " + callback.m_ulSteamIDLobby.ToString();
    160.             SteamMatchmaking.SetLobbyJoinable(new CSteamID(callback.m_ulSteamIDLobby), true);
    161.             // Do not call StartHost here. It will be called in HostGame coroutine.
    162.         }
    163.         else
    164.         {
    165.             Debug.LogError("[OnLobbyCreated] Failed to create a lobby.");
    166.         }
    167.     }
    168.  
    169.  
    170.     private IEnumerator JoinGame()
    171.     {
    172.         if (!isAuthenticated)
    173.         {
    174.             Debug.LogWarning("User not authenticated. Please log in with Steam.");
    175.             yield break; // Exit the coroutine if not authenticated
    176.         }
    177.  
    178.         if (!ulong.TryParse(lobbyIDInputField.text, out ulong lobbyID))
    179.         {
    180.             Debug.LogError("Invalid Lobby ID");
    181.             yield break;
    182.         }
    183.  
    184.         SteamMatchmaking.JoinLobby(new CSteamID(lobbyID));
    185.         yield return new WaitUntil(() => lobbyEntered != null);
    186.         NetworkManager.Singleton.StartClient();
    187.         yield return new WaitUntil(() => NetworkManager.Singleton.IsClient);
    188.         mainMenuCanvas.gameObject.SetActive(false);
    189.         // The server will spawn the player.
    190.     }
    191.  
    192.     private void OnLobbyEntered(LobbyEnter_t callback)
    193.     {
    194.         Debug.Log("Joined lobby successfully, Lobby ID: " + callback.m_ulSteamIDLobby);
    195.  
    196.         // Start the Netcode client if this instance isn't already a host/server and has joined a lobby
    197.         if (!NetworkManager.Singleton.IsClient && !NetworkManager.Singleton.IsHost && !NetworkManager.Singleton.IsServer)
    198.         {
    199.             NetworkManager.Singleton.StartClient();
    200.         }
    201.     }
    202.  
    203.  
    204.  
    205.     private void OnClientConnected(ulong clientId)
    206.     {
    207.         Debug.Log($"[OnClientConnected] Client connected with client ID: {clientId}");
    208.  
    209.         if (IsServer && clientId != NetworkManager.ServerClientId)
    210.         {
    211.             // We check if the player is already spawned in SpawnPlayer method
    212.             Debug.Log($"[OnClientConnected] Server is spawning the player for client ID: {clientId}");
    213.             SpawnPlayer(clientId);
    214.         }
    215.     }
    216.  
    217.  
    218.     private void SpawnPlayer(ulong clientId)
    219.     {
    220.         Debug.Log($"[SpawnPlayer] Attempting to spawn player for client ID: {clientId}");
    221.  
    222.         // Prevent the host from spawning more than once
    223.         if (clientId == NetworkManager.Singleton.LocalClientId && isPlayerSpawned)
    224.         {
    225.             Debug.LogWarning("[SpawnPlayer] Host player is already spawned. Aborting spawn.");
    226.             return;
    227.         }
    228.  
    229.         // Prevent non-host players from being spawned multiple times
    230.         if (playerSpawnedDict.TryGetValue(clientId, out bool isSpawned) && isSpawned)
    231.         {
    232.             Debug.LogWarning($"[SpawnPlayer] Player for client ID {clientId} is already spawned. Aborting spawn.");
    233.             return;
    234.         }
    235.  
    236.         // Instantiate and spawn the player object
    237.         GameObject playerInstance = Instantiate(playerPrefab);
    238.         NetworkObject networkObject = playerInstance.GetComponent<NetworkObject>();
    239.  
    240.         if (networkObject != null)
    241.         {
    242.             Debug.Log($"[SpawnPlayer] Spawning network object for client ID: {clientId}");
    243.             networkObject.SpawnAsPlayerObject(clientId);
    244.             playerSpawnedDict[clientId] = true; // Record that the player has been spawned
    245.  
    246.             // Mark the host as spawned
    247.             if (clientId == NetworkManager.Singleton.LocalClientId)
    248.             {
    249.                 isPlayerSpawned = true;
    250.             }
    251.         }
    252.         else
    253.         {
    254.             Debug.LogError("[SpawnPlayer] The player prefab does not have a NetworkObject component attached.");
    255.         }
    256.     }
    257.  
    258.     // Unregister the callback when this object is destroyed
    259.     public override void OnDestroy()
    260.     {
    261.         // Dispose of your lobby callbacks
    262.         lobbyCreated.Dispose();
    263.         lobbyEntered.Dispose();
    264.  
    265.         // Safely unsubscribe from the OnClientConnectedCallback event
    266.         if (NetworkManager.Singleton != null)
    267.         {
    268.             NetworkManager.Singleton.OnClientConnectedCallback -= OnClientConnected;
    269.         }
    270.  
    271.         // Call the base class OnDestroy method
    272.         base.OnDestroy();
    273.     }
    274.  
    275.     // Ensure proper cleanup on application quit
    276.     private void OnApplicationQuit()
    277.     {
    278.         if (NetworkManager.Singleton != null)
    279.         {
    280.             NetworkManager.Singleton.Shutdown();
    281.         }
    282.         SteamAPI.Shutdown();
    283.     }
    284. }
    285.  
    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/<HostGame>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/<HostGame>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/<HostGame>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/<HostGame>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/<HostGame>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/<HostGame>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)