Spawn a player and set their name

Hi, Sorry all I’m stuck on something really basic!

If I have a player prefab with their name on it via this component:

public class PlayerInfo : NetworkBehaviour {
[SyncVar]
public string Name;
}

When a player is spawned on the server, how do I get the name from the client?? ANd to ensure it is synced prior to the players prefabs being instantiated on the remote clients.

class MyManager : NetworkManager {
public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId) {
GameObject player = (GameObject)Instantiate(playerPrefab, Vector3.Zero, Quaternion.Identity);
player.GetComponent().Name = ??? how does server know this ???;
NetworkServer.AddPlayerForConnection(conn, player, playerControllerId);
}
}

TIA!

Here's how I currently have it, but it doesn't feel completely correct.

public class PlayerInfo : NetworkBehaviour {
[SyncVar(hook="OnSyncMyNameHook")]
public string MyName;

void OnSyncMyNameHook(string value) {
// make sure we save the value. On remove clients if we have a hook the value isn't set for us!
MyName = value;
GetComponentInChildren().text = value;
}

[Command]
voidCmdSetMyName(string name) {
MyName = name;
}

void Start () {
// because SyncVar hook is not fired on startup only for future changes?
OnSyncMyNameHook(MyName);
}

public override void OnStartLocalPlayer () {
CmdSetMyName("Mary Or Whatever");
}

So this works with the following caveats from what I can see:

  1. When a player prefab is instantiated for a remote client, I do not get the OnSyncMyNameHook() fired, but the name has consistently been synced prior to Start() firing, so I re-use my hook to get the 3d text to display:
    Q: Is that guaranteed? Will syncvar values from remote client always be valid prior to Start calling? What about on a slow or unreliable network?

  2. When on the local client, then Start() the name is invalid but I do get OnSyncMyNameHook() fired once it is set.

1 Like

More playing with 1 above I can see it is not guaranteed. Sometimes it is not set in Start. And immeadiately after I get the OnSyncMyNameHook() fired with the value.

On top of that, on the remote client I see the OnSyncMyNameHook() called before the Start() method sometimes. so need to watch that also by making sure the hook sets the syncvar value in the hook.

So, is the best practise that you should send command (or use a syncvar) to setup your players prefab before you display it? Obviously it is instantiated already, but it could just be not visible (or empty) until it gets that setup completed? eg; [syncvar] bool isReady ?

It would be more helpful if when calling OnSyncMyNameHook() we could pass params that are guranteed to be setup before Start() is called ?

To summarize, I see all these scenarios during instantiation of the player prefab by server onto a client:

  1. SyncVar has already been set, No Hook fired. Variable all good in Start()
  2. SyncVar has not been set. Start() shows blank. Immeadiately get a Hook fired afterwards with the value.
  3. SyncVar has not been set. I get a Hook fired before Start() is called. Then Start() is called. (This scenario I find troubling b/c I don't want to start accessing my gameobject/components before Start() is called)

Is scenario 3 a bug maybe?

use OnStartClient() instead of Start()

Thanks, I'll try that. However, the docs say OnStartClient() is "Called when the client connects to a server." and I am already connected to the server and then later I add the player.

OnStartClient on NetworkBehaviour, not on NetworkManager

Right, when I override it I see it get called. But in a few tests it is getting called before Start() and before the SyncVar is valid.

Here's one flow: I'm on a client (not host), and another client spawns a player, I get this:

Players Prefab instantiated:
Awake() Name=""
OnStartClient() Name=""
Hook() value = “Players Name”
Start() Name = "" (so name is not set, unless I save it in my Hook() event)

Note: Different flow if we connect to the server after the player is already created:

Awake() Name=""
OnStartClient() Name="Players Name"
Start() value = “Players Name”

Wouldn't it make sense to not call the hook if the Start() method isn't called yet.

This is doing my head in!

The docs here say:

The state of SyncVars is applied to objects on clients before OnStartClient() is called, so the state of the object is guaranteed to be up-to-date inside OnStartClient().

And so I guess because i set the players name in OnStartLocalPlayer() via a Command, then it is technically correct that the remote clients see a player instantiated that doesn't have the name set yet. However, it is frustrating that the players name then pops in via the hook event before Start() is called.

OK, I have figured out a solution that works for me:

Goal: How to instantiate a new player with their name.

Original Approach above: Client calls ClientScene.AddPlayer(), which then instantiates the player prefab, which I have OnStartLocalPlayer() call a server Command to set the name in a syncvar MyName.
Original Problem: So the above works, but depending on whether you are a remote client, local client, etc the name may or may not be set prior to the StartClient(), or Start() method, and you may or may not get the syncvar hook event, and if you get the hook event you may get it before Start() where I have some initialization.

New Method: To avoid the timing issues, instead of using the ClientScene.AddPlayer() built-in call, I register my own network message that can be sent to the server with all the parameters (eg: players name), and then get the server to instatiate and link it to the player.

public class MyNetworkManager : NetworkManager {

    public class MyAddPlayerMessage : MessageBase
    {
        public static short MSG_TYPE = 2000;
        public string name = "";
        public short playerControllerId = 0;
    }

    public override void OnStartServer ()
    {
        base.OnStartServer();
        NetworkServer.RegisterHandler(MyAddPlayerMessage.MSG_TYPE, OnServerMyAddPlayerMessage);
    }

    public override void OnStopServer ()
    {
        base.OnStopServer ();
        NetworkServer.UnregisterHandler(MyAddPlayerMessage.MSG_TYPE);
    }


    public static void MyAddPlayer(string name, short playerControllerId)
    {
        singleton.client.Send(MyAddPlayerMessage.MSG_TYPE, new MyAddPlayerMessage() { name = name, playerControllerId = playerControllerId });
    }

    private void OnServerMyAddPlayerMessage(NetworkMessage netMsg)
    {
        MyAddPlayerMessage msg = netMsg.ReadMessage<MyAddPlayerMessage>();
        GameObject player = (GameObject)Instantiate(playerPrefab, Vector3.zero, Quaternion.identity);
        player.GetComponent<PlayerSetup>().MyName = msg.name;
        NetworkServer.AddPlayerForConnection(netMsg.conn, player, msg.playerControllerId);

    }

}

Hope that helps someone else.

Note: The hook still may get called before Start() though! Geez. But I’m scrapping the hook b/c my players can’t change names without re-spawning.

2 Likes

for networked objects, dont use Start() at all. Look a the Object Creation Flow section of the “Object Spawning” page of the manual.

It is amazing that this stuff isn’t better explained in the documentation. The sample projects are horrible and all over the place.

Forgive me for my venting/frustration, but my goodness this is nothing like the rest of Unity’s features. It was a lot easier to understand and implement Unity’s multiplayer before 5.1. (What little I did, anyway).

I’ve read the entire multiplayer documentation four times now. Very slowly and thoroughly, trying to grasp networking from very little networking experience (although I have some understanding of networking and some experience dealing with servers).

Googling for Unet is almost non-existent right now since it’s so new, so we’re left with just the documentation and examples. May god have mercy on our souls…

I’ve spent the last 6 hours and 39 minutes trying to figure out how to add a player and customize them via a simple button choice (instantiate a different Sprite and parent it under the playerPrefab’s “Graphic” child).

How do you give a client’s spawned object localAuthority without making it another instance of a player or without replacing the playerPrefab?

I have so many questions, and absolutely no idea how to engineer a system to do all this. I find myself trying all kinds of different strategies, clearly demonstrating I am not understanding some of the basics.
How do I get access to the Client’s connection, from a script only Clients have? I am having trouble just figuring out how to get the information needed to fill parameters for various functions.

What’s the difference between OnStartClient() in NetworkManager and NetworkBehaviour?
What the HELL is ClientScene? (ex. ClientScene.AddPlayer() )

Where are the more detailed examples of the excerpts in the documentation? (ex. setting a tree’s leaves or character’s color.)

Do clients/games have a persistent/unique identifier besides IP address (which would be idiotic to rely on for character persistent on a server)? I assume we have to create this ourselves, but sending Messages/Commands needs more examples or better explanation. (Trying to register a new player character so the server can save it via some identifier is not the simplest thing).

This should be so simple or so easily explained in the docs, but where are the examples for this simple stuff? (I know I am close. Literally if I had an example or two, this would have taken a few minutes rather than endless hours only to delete everything and start over.)

Even worse are all the bugs or at least quirks. (NetworkProximityChecker not even working by giving an error in a new empty project is a great example of how NOT ready this beta Unet is. I dislike how early Unity releases its major updates. It’s not ready yet, especially with no (good) example project).

Anyway, I’ll start again tomorrow to try to figure all of this out- but I wanted to post my grievance somewhere. I mean, seriously…6 hours and 40 minutes later and I actually have LESS than I did earlier. At least the auto-spawn of a good playerprefab works easily right out of the box. Doing anything else is met with lots of frustration.

Doesn’t help that I have to go over the documentation repeatedly just to understand a general idea. This is very uncommon for me. I usually can skim documentation and understand it entirely. I rarely have trouble like this, especially when it comes to programming. I wanted to emphasize how incomplete the documentation is.

5 Likes

Yes, the documentation is not really that fleshed out yet.

The thing is that the new networking solution is a brand new thing and not many people are working with networking. It’s also only the first phase. So there isn’t all that much to find and all of us have to establish our own best practices for the time being.

NetworkManager has some default behaviours already built-in while for NetworkBehaviour it’s just a Callback as far as I can tell.

ClientScene is the client-side equivalent of NetworkServer which handles establishment of collections, registration of NetworkMessages and some other basic connection stuff.

I have a simple game where you can customize your color in the menu and it will be propagated as your color over the network.
I don’t use NetworkManager and have built my own implementation of the basic connection logic.
I’ve simply created my own NetworkMessage type and I send that in OnClientConnect which is m Callback for when the Client connects.

Clients have an ID but it’s not a persistent UUID.

You can simply do something like:

private class ExtMsgType : MsgType {
    public const short PlayerID = 64;
}

Register the MessageType so it can be received.

NetworkServer.RegisterHandler(ExtMsgType.PlayerID, OnServerPlayerIDReceived);

For registering it on the Client use ClientScene instead of NetworkServer.

Somewhere use something like:

client.Send(ExtMsgType.PlayerID, new IntegerMessage(playerID));

Where client is a NetworkClient with an established connection.

And to receive it:

public void OnServerPlayerIDReceived(NetworkMessage msg) {
    IntegerMessage message = msg.ReadMessage<IntegerMessage>();
    playerID = message.value;
}

The same thing can be done with a StringMessage for names.

Yes, it’s buggy and stuff, but I like getting things as early as possible to play around with and nobody is forcing you to use it, the legacy stuff is still around and working…

It’s a dynamic release cycle, things change and the UNET documentation is a perfect example of how sometimes some outdated information gets left on live systems.

1 Like

I have to agree with you @CarterG81 . It is very difficult to work out once you get passed the functionality of the basic quick start autospawn a player prefab. The timing of events is not consistent making it difficult.

Why is StartClient() even needed? UNet should be consistent and have everything done prior to Start() unless they need to inform of something specific.

So, both you and @Necromantic used the same approach as me in the end - send a custom message from the client to the server?

If that is the case, then please I wish Unity would add an optional NetworkMessage to ClientScene.AddPlayer() :

Proposed API improvement - Add optional NetworkMessage

// ClientScene:
public static bool AddPlayer(Networking.NetworkConnection readyConn, short playerControllerId, NetworkMessage msg);

// NetworkManager:
public void OnServerAddPlayer(Networking.NetworkConnection conn, short playerControllerId, NetworkMessage msg);

I don't want to be overlay negative, the UNet looks very promising and very exciting architecture. But it is hard to get your head around. Well, back to more explorations today.

2 Likes

It is a utility method solely used in the NetworkManager for establishing a connection and maybe doing some initialization dependent on that. Aside from being a method that can be extended it really doesn’t have all that much in common with the Start callback.

I think once people stop using the NetworkManager and actually build their own implementation they will learn to understand those things better.

That is definitely true, but right now all the demos and HLAPI talk steers developers to start with NetworkManager…

Thank you very much. All of that helps immensely.

I didn’t realize exactly how “New” this is. Last time I “came back to Unity” there were quite a lot of changes. The biggest and most latest one being the new 2D system. Perhaps it wasn’t as new as I thought, because when I came around it was really easy to understand and much better documented. The experience was wonderful- nothing like this :stuck_out_tongue:

Maybe I can do some good and write up a crappy tutorial for others. I don’t really know what I’m doing, but at least I can figure it out. Especially with the help of this wonderful community. Then maybe help others do the same- or at least provide a crappy (but working) example for others to see.

15 hours total trying to figure just this one thing out (how to add a player and customize a single variable).

Unfortunately, I can’t even get this far to even try the solutions presented in this thread. I am failing to spawn the characters on both the server and client.

I’m not really sure what to do. I usually resolve any problem in a few minutes to a 4-6 hours max.
15 hours just trying to do a simple thing? I am clearly not getting this. Without assistance, I’m pretty sure I will just spend another 15 doing the same things over and over- failing again and again.

Maybe I should pay someone to tutor me? Maybe I should just forget multiplayer until much later in the project? I am getting pretty desperate.

The worst part is that all the different ways I’m trying seem to be working great in the UnityEditor. However anytime I build, it is nothing like the results hitting “Play” in the UnityEditor. I finally got it to not freeze up (no idea why it did this when I sent a command) but only the server sees the player and the variable does not sync as it should because I’m not handling my playerPrefab correctly (all players are spawning only on the server, and take the host client’s information to determine its sprite/color/name/whatever. Although that is a bit more clear on why that is happeing, but I haven’t even tried to touch fixing that since I can’t even get spawning the players right. What’s the point in customization if they dont even spawn?).

Any links to tutorials or examples or assistance in this thread would be very much appreciated.

I got it to work. I took a break from the 16 hour 2-day marathon of learning and just relaxed for the day. Came back to it with a notepad & pen, and went through the documentation slowly- reading everything for the Nth time.

Their example in the documentation is total crap, but what I finally understood despite how horribly it is explained and how incomplete it is. As the OP stated, the server doesn't know the player's color. To rectify this, I made my own solution.

We start with our own custom NetworkManager. I call mine "GameNetworkManager" which inherits from "NetworkManager". Be careful - you only want ONE NetworkManager, so replace your NetworkManager with GameNetworkManager.

In our own GameNetworkManager, we customize OnClientConnect. What I do is create a "Client" gameobject which stores a lot of relevant information. (It also handles multiple GUI's for 4-player splitscreen and I use it for some global variables too.)

First thing I do is create the "Client" gameobject, set its name to not have (Clone) in it so it is correctly named "Client". Remember to set it on Don'tDestroyOnLoad, because after this is instantiated, it will load the multiplayer Scene. This seemed to be a requirement.

Irrelevant Details (Skip): (You could also have this or something similar in the multiplayer Scene, but I advice against it. Let's just instantiate it when it's needed rather than have it in the scene when loaded. This "Client" gameobject is also destroyed in OnStopClient. I also have an object called "Server" which does the same thing, but in OnStartServer and OnStopServer.)

After this, I determined if it was a NewGame or not. So I then instantiate the CharacterSelectionGUI so the user can choose their character in-game. THIS IS NOT REQUIRED. If you don't have persistent characters on the server, just skip the conditional (IF) statement.

Finally, I call base.OnStartClient(client) because I have no idea what that does, and just wanted to be safe in case I missed something.

public class GameNetworkManager : NetworkManager
{

public GameObject ClientObject; //The prefab with a script that holds your player's choice/customization variables.
public GameObject CharacterSelectGUI; //The prefab CANVAS with all the GUI. Remember you also need an EventSystem for UnityEngine.UI stuff to work.

public override void OnStartClient(NetworkClient client)
  {
  Debug.Log("Starting Client...");

  thisClientObject = (GameObject)Instantiate(ClientObject, Vector3.zero, Quaternion.identity);
  thisClientObject.name = ClientObject.name; //Could also be thisClientObject.name = "Client";
  DontDestroyOnLoad(thisClientObject);

  if (isNewGame)
  {
  if (!GameObject.Find(CharacterSelectGUI.name)) //Create the Character Select GUI to make a new Character.
  {
  thisCharacterSelectGUI = (GameObject)Instantiate(CharacterSelectGUI, Vector3.zero, Quaternion.identity);
  thisCharacterSelectGUI.name = CharacterSelectGUI.name;
  DontDestroyOnLoad(thisCharacterSelectGUI);
  }
  }
  else
  {
    //Request Load player from Server
  }

  base.OnStartClient(client);
  Debug.Log("I am Client.");
  }

}

This is the important bit:

```
**public GameObject ClientObject; //The prefab with a script that holds your player's choice/customization variables.

public override void OnStartClient(NetworkClient client)
{
thisClientObject = (GameObject)Instantiate(ClientObject, Vector3.zero, Quaternion.identity);
thisClientObject.name = ClientObject.name; //Could also be thisClientObject.name = "Client";
DontDestroyOnLoad(thisClientObject);**
```

Once OnStartClient is called, the player is now connected and NOT ready. Being "Not Ready" means no network data being sent yet across the network. If you want the network to start the gameworld/send data from everything on the server with NetworkIdentit*y, then set the client to Ready.*

Now that the CharacterSelectGUI is created, we have some buttons to click to choose a character. I have two buttons: "Character1" button, and "Character2" button. Both have UI Button "OnClick()" events set to this script: CharacterSelectMenu .cs, calling this function: SelectCharacterButton(string name).

public class CharacterSelectMenu : NetworkBehaviour
{

public void SelectCharacterButton(string name)
   {
     LoadCharacterData(name); //Load all the PlayerData for that character. (ex. Name, Stats, Special Starter Items, etc.)

     //SpawnPlayer on Server - WARNING - This may not be correct.
  ClientScene.AddPlayer(0); //Sends the message to the server asking to spawn playerobject for this client.
     //Maybe requires ClientScene.AddPlayer(ClientScene.conn, 0);

  //Delete this GUI
  Destroy(this.gameObject);
  }

  void LoadCharacterData(string name)
  {
  GameObject multiplayerClient = GameObject.Find("Client");
  multiplayerClient.GetComponent<MultiplayerClient>().myPlayerData.characterName = name;

  //MultiplayerClient.playerData.BackPack
  //MultiplayerClient.playerData.CurrentHealth
  //MultiplayerClient.playerData.CurrentHunger
  //MultiplayerClient.playerData.CurrentThirst
  //MultiplayerClient.playerData.Equipment
  //MultiplayerClient.playerData.inMouseHand
  //MultiplayerClient.playerData.myPosition
  }
}

Click button "Character1" will call SelectCharacterButton("Carter") because that is the string I put in the button's parameter (in the inspector - "Carter").

What this does is finds the "Client" gameobject we instantiated in our first call to OnStartClient(). This "Client" gameobject has a script attached to it which holds a string. Personally, I call my "Client" script "MultiplayerClient" and have a class called "PlayerData" which holds all possible playercharacter information (health, hunger, thirst, items they have, their name, etc.) THIS IS NOT REQUIRED, but I wanted to keep it in to show you how I do more complex character loading. (PlayerData is serialized, so it can be Saved/Loaded to file as well as sent across the network in whole).

The final call to Destroy(this.gameObject) is to get rid of the CharacterSelectGUI. The user clicked the button to choose their character, so we are ready to start the game. If you don't want it to start yet, then don't call ClientScene.AddPlayer().

Remember- ClientScene.AddPlayer() sends a message from the Client to the Server. The server then runs OnServerAddPlayer().

Remember - NetworkServer.AddPlayerForConnection() will put the client in a Ready state.

But you could do something as simple as this:
```
**public class myClient() : Monobehaviour
{
public string characterName;
}

public class CharacterSelectMenu : NetworkBehaviour
{
public void SelectCharacterButton(string name)
{
GameObject myClientGOB = GameObject.Find("Client"); //Find the "Client" GameObject we instantiated earlier in OnStartClient().
myClientGOB.GetComponent().characterName = name; //Set the string (characterName) to what the player chose (name).

 ClientScene.AddPlayer(0); //Sends the message to the server asking to spawn playerobject for this client.

}
}**
```

Now, on the playerPrefab we instantiate for players, we need to have a script to ready the character as well as send [Commands].

Remember- Only the playerPrefab can send Commands, and it needs a NetworkIdentity.

public class PlayerNetworkSetup : NetworkBehaviour
{
GameObject multiplayerClient;

  [SyncVar]
  public string myCharacterName;

  bool doneOnce = false;

  void LateUpdate()
  {
  if (!doneOnce)
  {
    //This instantiates the player character graphic based on the user's character choice, then parents it to the graphic child of the playerObject. This is my own way.
  GameObject playerGOB = (GameObject)Instantiate(Resources.Load("Prefabs/Player/" + myCharacterName));
  playerGOB.transform.SetParent(graphic.transform);
  doneOnce = true;
  }
  }


  public override void OnStartLocalPlayer()
  {
  base.OnStartLocalPlayer();

  if(isLocalPlayer) //Is not the player's character.
  {
  //this.GetComponent<PlayerInputMovement>().enabled = true;
  //playerCamera.SetActive(true);

  GameObject myClient = GameObject.Find("Client");
  Cmd_SetCharacterName(myClient.GetComponent<MultiplayerClient>().myPlayerData.characterName);
  }
   }

  [Command]
  void Cmd_SetCharacterName(string name)
  {
  myCharacterName = name;
  }

Since the "Client" gameobject is only available to the localplayer (not part of the network), we need to make sure that only localPlayers sync their name.

This class is attached to the playerPrefab. "myCharacterName" is a [SyncVar] so it syncs across the server.

OnStartLocalPlayer is called first. It finds that "Client" gameobject and then sends a command to the server.

Remember - Commands are sent by the Client, TO the Server. Then the Server runs the code.
The [Command] Cmd_SetCharacterName(string name) sets the [SyncVar]'d variable to the string chosen by the player.
The very last thing called is LateUpdate(), which runs a single time, doing the customization data. It could be as simple as this:
```
** void LateUpdate()
{
if (!doneOnce)
{
if(myCharacterName == "Carter")
{
GetComponent().color = Color.red; //whatever
}
else
{
//leave it at the default blue. You aren't a Carter!
}
doneOnce = true;
}
}**
```
The only problem with this is that my server instantiates all the characters great, but the Clients only see themselves. I assume this has to deal with ClientScene.AddPlayer(0) or something I'm not calling in OnServerAddPlayer(). But the syncing of variables to customize the player's color/name/whatever should all be working in this example.

2 Likes

I am in a hurry and dont' have much time, so I tried to just bold the important parts.

Sorry for not making a better example. This isn't perfect (it syncs the variable, but I'm not spawning the player correctly) so I didn't want to waste too much time on this.

TLDR: Just use a non-network gameobject ("Client") to hold the variable, and when the playerPrefab is spawned, grab that non-network gameobject ("Client") and send a [Command] to do whatever.