This is a bit of a read, because I have a specific question related to implementation and want to provide appropriate context for my work. So buckle up.
I’m building a 3D networked game. I have a Login scene and a Lobby scene. The Login scene handles parsing of the player credentials with a function that looks like this:
public void OnLoginButtonPressed()
{
if (DBConnectionManager.Instance == null)
{
Debug.LogError("DBConnectionManager.Instance is null. Make sure it exists in the scene.");
return;
}
Debug.Log("OnLoginButtonPressed called");
string username = usernameInputField.text;
string password = passwordInputField.text;
if (DBConnectionManager.Instance.ValidateLogin(username, password))
{
NetworkPlayerStatsManager.Instance.LoadStatsFromDatabase(username);
PlayerStats loadedStats = NetworkPlayerStatsManager.Instance.GetPlayerStats(username);
if (loadedStats != null)
{
// Add the loaded stats to the global dictionary
GlobalPlayerData.Instance.AddPlayerStats(username, loadedStats);
StartCoroutine(FadeOut(loginCanvasGroup, fadeDuration, null));
if (loadedStats.hasSelectedClass && loadedStats.hasSelectedFaction)
{
StartCoroutine(TransitionToGame());
}
else if (!loadedStats.hasSelectedClass)
{
StartCoroutine(TransitionToClassSelection());
}
else if (!loadedStats.hasSelectedFaction)
{
StartCoroutine(TransitionToFactionSelection());
}
}
else
{
Debug.LogError("Failed to load player stats.");
}
}
else
{
Debug.LogError("Invalid username or password.");
}
}
Username and Password are validated, and player stats are fetched from the database. The supporting DB connection functions work, I’m not concerned with those.
Once player stats are fetched, they are added to a global dictionary that holds player stats from all active players (to allow connection of multiple players asynchronously without mixing up player stats). This part is a new implementation that I’m still testing for efficiency and effectiveness.
If the player has already selected class and faction, they are transitioned to the game scene using the following function:
private IEnumerator TransitionToGame()
{
// Fade out the ClassSelectionCanvas if it is visible
if (classSelectionCanvasGroup.alpha > 0)
{
yield return StartCoroutine(FadeOut(classSelectionCanvasGroup, fadeDuration, null));
}
// Fade out the FactionSelectionCanvas if it is visible
if (factionSelectionCanvasGroup.alpha > 0)
{
yield return StartCoroutine(FadeOut(factionSelectionCanvasGroup, fadeDuration, null));
}
// Fade in the black screen, which will stay visible during the scene load
yield return StartCoroutine(FadeIn(blackScreenCanvasGroup, fadeDuration));
// Load the NetworkLobbyScene
SceneManager.LoadScene("NetworkLobbyScene");
// Wait for one frame to ensure the scene has fully loaded
yield return null;
// Fade out the BlackScreenCanvas after the scene has loaded
yield return StartCoroutine(FadeOut(blackScreenCanvasGroup, fadeDuration, null));
}
Thus, the Lobby scene is loaded. The Lobby scene, being a networked multiplayer scene, has a CustomNetworkManager script that I have written to handle player connection and disconnection. It appropriately handles checking if a server exists, and whether to connect as host or client. Next comes loading appropriate player data unique to the player that logged in the Login scene, including prefab and material. This is my existing OnServerAddPlayer function, and this is where I’m running into issues:
public override void OnServerAddPlayer(NetworkConnectionToClient conn)
{
string playerName = null;
PlayerStats loadedStats = GlobalPlayerData.Instance.GetPlayerStats(playerName);
if (loadedStats == null)
{
Debug.LogError($"No player stats found for player: {playerName}");
return;
}
Vector3 spawnPosition = GetSpawnPositionForPlayer(loadedStats.faction);
GameObject playerPrefab = Resources.Load<GameObject>($"Prefabs/{loadedStats.className}Class");
if (playerPrefab == null)
{
Debug.LogError($"Player prefab for class {loadedStats.className} not found in Resources/Prefabs!");
return;
}
GameObject playerInstance = Instantiate(playerPrefab, spawnPosition, Quaternion.identity);
NetworkServer.AddPlayerForConnection(conn, playerInstance);
NetworkPlayerSetup playerSetup = playerInstance.GetComponent<NetworkPlayerSetup>();
if (playerSetup != null)
{
playerSetup.CmdRequestSpawnPlayer();
}
else
{
Debug.LogError("NetworkPlayerSetup component not found on the player prefab.");
}
}
Notice the string playerName = null. I put it there as a placeholder, but I need to figure out a way to pass the username from the Login scene to that specific value to retrieve the stats tied to that specific username from the global stats dictionary.
Can I just create a global variable in the Lobby scene, and after the Lobby scene is loaded in the login scene via:
// Load the NetworkLobbyScene
SceneManager.LoadScene("NetworkLobbyScene");
can I just do something like:
CustomNetworkManager.playerName = username;
Or will that create continuity issues and confuse player data?
Is there a better solution here to managing the player data between the two scenes? In some previous tests, I was able to instantiate multiple players, but the prefabs were not loaded correctly for anyone but the first player to connect, because all subsequent players would not be able to appropriately detect the faction/class name, so they would show up as the prefab for the first class, untextured because the faction name would not be read correctly from the database.
Am I on the right track? I’m going to continue testing and developing, but I would greatly appreciate any assistance anyone could offer as I feel I have lost the forest for the trees here.
Thanks in advance.