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 NetworkIdentity, 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] (localPlayer authority and all that), 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<SpriteRenderer>().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.