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;
}
}