- Home /
Multiplayer Object spawned by client does not show up on host
I am creating a multiplayer strategy game in which each player can create units from a building. The player clicks on their building, then presses a key, and the unit appears.
I am currently doing multiplayer using the built-in unity networking (using HLAPI), and having it so one player is hosting the game, and the other player is not. If the player that is hosting the game spawns a unit, it appears on both players' screens like it is supposed to, but if the client tries to spawn a unit, it will appear on their screen, but doesn't appear on the other player's screen. Which it should do. It seems like this must have something to do with code that runs correctly on the client but not the server, but I can't figure out what is wrong despite trying things various ways.
This is my code for spawning the units. I have it attached to the building gameObject, where the units are spawned:
GameObject g = Instantiate (unitsSpawned [s]);
g.transform.position = transform.position;
PlayerScript.players[team].CmdSetAuthority(gameObject.GetComponent<NetworkIdentity>(), PlayerScript.players[team].GetComponent<NetworkIdentity>());
PlayerScript.players [team].CmdSpawnUnit (g);
PlayerScript.players[team] gets the player who owns the building, and these are the two command methods (in the Player class) I use:
[Command]
public void CmdSpawnUnit(GameObject unit) {
NetworkServer.Spawn(unit);
}
[Command]
public void CmdSetAuthority(NetworkIdentity netId, NetworkIdentity playerID) {
netId.AssignClientAuthority (playerID.connectionToClient);
}
The units I am spawning are prefabs with a NetworkTransform and a NetworkIdentity. I have tried both with and without local player authority checked, and neither of them work.
I know that the code for spawning the unit runs and spawns the unit locally, because the player who spawned the unit can see it, but the other player can only see it if it was the host player that spawned the unit, so something must be wrong with the networking.
I am using unity 5.4.1
Any ideas about what I am doing wrong would be really appreciated! Thanks!
Answer by MarshallN · Feb 20, 2017 at 08:27 PM
On the prefab, make sure that on the NetworkIdentity (Script), Server Only is NOT checked - I don't believe that 'Local Player Authority' should be checked either, try it with both unchecked.
Also, make sure that the unit prefab is registered in your network manager script! More details on the first code block of this page.
EDIT: The problem ended up being that you were trying to send a GameObject as a parameter in a [Command], which can only accept serializeable objects from simple data types such as integers, strings and floats, as well as Unity types such as Vector3 and user-defined structs.
One way to fix it would be to make a Dictionary of registered prefabs in the Spawner Building script with the unit name as the key and the GameObject prefab to spawn as the value, that way you can just write
PlayerScript.players [team].CmdSpawnUnit (g.name, PlayerScript.players[team].GetComponent<NetworkIdentity>());
and it'll know what unit to spawn because the unit's name is the same as the key in the Dictionary.
I think that if "Local Player Authority" is checked, you need to not just Spawn(), but SpawnWithClientAuthority(), explained at the bottom of the link I posted :)
I tried SpawnWithClientAuthority as well, and that didn't work either.
Just to make sure, the building GameObject that spawns the units doesn't have "Server only" checked on its NetworkIdentity, right?
Ok, those print statements were actually very informative, thanks!
For the player whose units didn't work, it was trying to do the CmdSetAuthority on Null. For the player whose unit transferred correctly, it was not null.
Ok, so while that looks like it might be more correct, it still doesn't fix the underlying problem that the spawning command is getting passed in a null gameObject.
I'd hoped it was a null NetworkIdentity connection, rather than a null GameObject. Can you post the exact error you're getting?
@$$anonymous$$arshallN I tried it without local player authority on the objects, and nothing changed. The unit prefab is registered in the network manager.
Ok, but that couldn't be the problem. Here is my code:
GameObject g = Instantiate (unitsSpawned [s]);
g.transform.position = transform.position;
print ("Spawning unit " + g.ToString ());
PlayerScript.players [$$anonymous$$m].CmdSpawnUnit (g, PlayerScript.players[$$anonymous$$m].GetComponent<NetworkIdentity>()); PlayerScript.players[$$anonymous$$m].CmdSetAuthority(g.GetComponent<NetworkIdentity>(), PlayerScript.players[$$anonymous$$m].GetComponent<NetworkIdentity>());
I am printing out exactly the same thing I am passing in, and in this print, it is not null, and in the print inside of the command, it is. So it can't be a problem with the original object, because it isn't null in this code.
Oh dude, I found your issue. From the documentation, "SyncVars can be basic types such as integers, strings and floats. They can also be Unity types such as Vector3 and user-defined structs, but updates for struct SyncVars are sent as monolithic updates, not incremental changes if fields within a struct change. There can be up to 32 SyncVars on a single NetworkBehaviour script - this includes SyncLists."
[Command]s and [ClientRPC]s use the same data types as SyncVars - thus, you're trying to serialize a non-serializeable object, GameObject g
. One way to fix it would be to make a Dictionary of registered prefabs with the unit name as the key and the GameObject to spawn as the value, that way you can just write
PlayerScript.players [$$anonymous$$m].CmdSpawnUnit (g.name, PlayerScript.players[$$anonymous$$m].GetComponent<NetworkIdentity>());
and it'll know what unit to spawn because the unit's name is the same as the key in the Dictionary.