Delay the spawning of networked player objects. Why is this so difficult with UNet.....
Alright I'm about to lose my mind, this seems insanely trivial but I've spend 4 days and non stop googling to try and figure this out and I simply can't.
What I'm trying to accomplish: A simple lobby system, thats it, When players connect I DON'T WANT THEM TO SPAWNS THE PLAYER PREFABS. Okay, that's easy, just don't tick the box right? okay, now both players readied up, now I want the SERVER TO SPAWN ALL THE PLAYER CHARACTERS. I've tried everything and this is just stupidly hard.
The best suggestion I've seen for this is to create a "dummy" object and instantiate it in the OnServerAddPlayer method.
Alright find, that's stupid but sure, But, now Both players click their ready buttons, I want the server to instantiate the player objects. How? Once the last player hits the ready button, we call a [Command]
[Command]
void CmdSpawn()
{
for (int i = 0; i < m_Connections.Count; i++)
{
GameObject go = (GameObject)Instantiate(m_goPref, new Vector3(0, 3, 0), Quaternion.identity);
NetworkServer.SpawnWithClientAuthority(go, m_Connections[i]);
}
}
What am i supposed to do in this server method to access all players? Do I need to store an entire list of all the client connection to the server and then give them authority? oh wait a minute this doesn't actually work. isLocalPlayer is false on all instances of the spawned objects, even though I spawned it giving it local authority. So is there another way to do this?
Are there ANY tutorial on UNet that aren't using the built in instant spawn on connection?
Answer by lemonLimeBitta · Apr 23, 2018 at 01:53 AM
@PylonCrow Hey Mate,
I am currently in the hell you're living. Anything outside of a simple car in an otherwise empty world and there is almost no tutorials or help. UNET seems to be very much in it's infancy and not many people seem to know it in great depth. I've done some hunting on GitHub and found the code for some of the derived functions that we need to consider. As the LobbyManager derives from the NetworkLobbyManager which derives from the NetworkManager we can override the functions from either of those two parents which may help what you're trying to achieve. These functions in particular seem to be the key.
This here registers the transforms of the prefabs that you list as spawn locations in a list for use later
/// <summary>
/// <para>Registers the transform of a game object as a player spawn location.</para>
/// </summary>
/// <param name="start">Transform to register.</param>
public static void RegisterStartPosition(Transform start)
{
if (LogFilter.logDebug)
Debug.Log((object) ("RegisterStartPosition:" + (object) start));
NetworkManager.s_StartPositions.Add(start);
}
This function goes through that list and returns a position based off round robin or random.
/// <summary>
/// <para>This finds a spawn position based on NetworkStartPosition objects in the scene.</para>
/// </summary>
/// <returns>
/// <para>Returns the transform to spawn a player at, or null.</para>
/// </returns>
public Transform GetStartPosition()
{
if (NetworkManager.s_StartPositions.Count > 0)
{
for (int index = NetworkManager.s_StartPositions.Count - 1; index >= 0; --index)
{
if ((UnityEngine.Object) NetworkManager.s_StartPositions[index] == (UnityEngine.Object) null)
NetworkManager.s_StartPositions.RemoveAt(index);
}
}
if (this.m_PlayerSpawnMethod == PlayerSpawnMethod.Random && NetworkManager.s_StartPositions.Count > 0)
{
int index = UnityEngine.Random.Range(0, NetworkManager.s_StartPositions.Count);
return NetworkManager.s_StartPositions[index];
}
if (this.m_PlayerSpawnMethod != PlayerSpawnMethod.RoundRobin || NetworkManager.s_StartPositions.Count <= 0)
return (Transform) null;
if (NetworkManager.s_StartPositionIndex >= NetworkManager.s_StartPositions.Count)
NetworkManager.s_StartPositionIndex = 0;
Transform startPosition = NetworkManager.s_StartPositions[NetworkManager.s_StartPositionIndex];
++NetworkManager.s_StartPositionIndex;
return startPosition;
}
This function adds a player to the scene when called by.. something (if you're just using a network manager then it's probably calling it somewhere in there (if you have auto spawn turned off, you would have had to create your own spawn). If you're using a lobby like me then it is overridden by NetworkLobbyManager and is called when all players are ready and the sceneChange happens into the play scene (I still need to go through all that and work out wtf is going on.)
/// <summary>
/// <para>Called on the server when a client adds a new player with ClientScene.AddPlayer.</para>
/// </summary>
/// <param name="conn">Connection from client.</param>
/// <param name="playerControllerId">Id of the new player.</param>
/// <param name="extraMessageReader">An extra message object passed for the new player.</param>
public virtual void OnServerAddPlayer(NetworkConnection conn, short playerControllerId)
{
this.OnServerAddPlayerInternal(conn, playerControllerId);
}
private void OnServerAddPlayerInternal(NetworkConnection conn, short playerControllerId)
{
if ((UnityEngine.Object) this.m_PlayerPrefab == (UnityEngine.Object) null)
{
if (!LogFilter.logError)
return;
Debug.LogError((object) "The PlayerPrefab is empty on the NetworkManager. Please setup a PlayerPrefab object.");
}
else if ((UnityEngine.Object) this.m_PlayerPrefab.GetComponent<NetworkIdentity>() == (UnityEngine.Object) null)
{
if (!LogFilter.logError)
return;
Debug.LogError((object) "The PlayerPrefab does not have a NetworkIdentity. Please add a NetworkIdentity to the player prefab.");
}
else if ((int) playerControllerId < conn.playerControllers.Count && conn.playerControllers[(int) playerControllerId].IsValid && (UnityEngine.Object) conn.playerControllers[(int) playerControllerId].gameObject != (UnityEngine.Object) null)
{
if (!LogFilter.logError)
return;
Debug.LogError((object) "There is already a player at that playerControllerId for this connections.");
}
else
{
Transform startPosition = this.GetStartPosition();
GameObject player = !((UnityEngine.Object) startPosition != (UnityEngine.Object) null) ? (GameObject) UnityEngine.Object.Instantiate((UnityEngine.Object) this.m_PlayerPrefab, Vector3.zero, Quaternion.identity) : (GameObject) UnityEngine.Object.Instantiate((UnityEngine.Object) this.m_PlayerPrefab, startPosition.position, startPosition.rotation);
NetworkServer.AddPlayerForConnection(conn, player, playerControllerId);
}
}
As you can see through all that, you just need to override whatever is calling those spawn functions and write your own ones. Maybe you could have a flag in your lobby manager script that is passed on scene change and then call your player Spawn function (the last one) at that point in time.
I'm sure I wasn't that much help but i'm struggling through my self. Hopefully my research has helped clear something up for you.
Holy crap, sorry for the wall of text.
Your answer
Follow this Question
Related Questions
Networking concept and spawning questions 1 Answer
NetworkLobbyManager: How to spawn a lobbyprefab as the child of another GameObject. 1 Answer
What does this error mean? 1 Answer
NetworkServer.Spawn() only on Server (with registered prefabs) [Unity 5.2.3f1] 0 Answers
[UNET] Spawn object on server BUT delete on your client 1 Answer