- Home /
Problem fixed
Unity Networking: Object only spawning on the Host client.
Hello,
So I'm trying to spawn an object on all the clients connected in a game yet it only spawns on the Host. It used to work perfectly now I'm not quite sure what happened for it to stop working.
The Spear prefab has a Network Identity (Local Player Authority) and a Network Behavior Script.
The Spear prefab is registered as a spawnable prefab.
Channel 0 is "Reliable Sequenced".
I'm using the NetworkServer.Spawn() through a Command call to spawn the prefab.
I am getting the Warning: Did not find target for sync message for X.
At this point after hours of browsing this forum I have yet to find the issue that caused my prefab to stop spawning on clients.
Here's the small bit of code that's used to spawn the prefab:
void Update()
{
if (!isLocalPlayer)
return;
if (Input.GetKeyDown(KeyCode.Space) && Time.time > timeToFire)
{
anim.SetTrigger("Spear Attack");
timeToFire = Time.time + spearCD / reductionCD;
mousePosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, Input.mousePosition.z - Camera.main.transform.position.z));
CmdThrowSpear(mousePosition, name);
}
}
[Command]
void CmdThrowSpear(Vector3 _mousePosition, string _player)
{
if (spearPrefab == null)
{
Debug.Log("No gameobject attached to 'spear' in Warrior Combat Script");
}
else
{
GameObject _spear = (GameObject)Instantiate(spearPrefab, transform.position, transform.rotation);
_spear.transform.eulerAngles = new Vector3(0, 0, Mathf.Atan2((_mousePosition.y - transform.position.y), (_mousePosition.x - transform.position.x)) * Mathf.Rad2Deg - 180);
_spear.GetComponent<WarriorSpear>().player = _player;
NetworkServer.Spawn(_spear);
}
}
If anyone has insight on this issue I would highly appreciate it.
Thank you.
Alright so I found out that my issue wasn't Network related... but I still don't really understand why the issue is happening only client side.
Here's the troubling bit of code:
//Collision detection for damage and to destroy object.
void OnTriggerEnter2D(Collider2D _col)
{
Player _player = _col.GetComponent<Player>();
if (_player != null)
{
if (_player.name == player)
return;
_player.DamagePlayer(damage);
Destroy(gameObject);
}
}
The "Destroy(gameObject);" destroys the spear as soon as its instantiated on client sides... but not server side. Not quite sure if it's the Destroy functionality that's messed up with networking or another issue I'm not aware of.
If I remove the "Destroy(gameObject);" everything works fine.
*Will fix the code text once I get back from work.
Put
Debug.Log(col.gameObject.name);
in the function to get notified when the spear strikes something.
The Debug.Log gives me the expected result for the host side if I leave the "Destroy(gameObject);" call. But on the Client side if doesn't give me any log info. I'm quite confused how the Destroy call causes this issue when it's nested through IF statements. I'll just find a workaround to destroy the Spear on impact.
Answer by meat5000 · Apr 17, 2016 at 10:27 AM
You should use NetworkIdentity and the isLocalPlayer attribute to identify players. Relying on tags and names in a multiplayer environment will eventually fall over itself.
I think the proper way to do this, if you want the client to have control, is to use the SpawnWithClientAuthority method.
Basically, you grab the NetworkIdentity of your 'gun' or Player or whatever is firing the spawn code and use netID.connectionToClient
to grab your own connection reference. You then stick that into the spawn call. Dont put any object specific stuff in this part of the call as this part should only be running on the server.
Basically what we've got here is a Client telling the Server to Spawn an Object and give control to that Client. Notice at the end of the Command code there is an RPC call. This is the server then, telling ALL clients to run that code on the Spear (of which we have passed the object reference).
If the spear prefab has its own movement script then this is ideal as they, largely, wont even need to be synced on the network. I use that RPC call, as an example, to activate a movement script for my bullets. All bullets have the same script so all make the same movements. No position syncing involved. This only works for simple scripted, non-reactive movements.
[Command]
void CmdThrowSpear(Vector3 _mousePosition)
{
if (spearPrefab == null)
{
Debug.Log("No gameobject attached to 'spear' in Warrior Combat Script");
}
else
{
GameObject _spear = (GameObject)Instantiate(spearPrefab, transform.position, transform.rotation);
NetworkServer.SpawnWithClientAuthority(_spear, netID.connectionToClient);
if(isServer) OtherStuff(_spear);
}
}
[ClientRpc]
void OtherStuff(GameObject projObject)
{
projObject.transform.eulerAngles = new Vector3(0, 0, Mathf.Atan2((_mousePosition.y - transform.position.y), (_mousePosition.x - transform.position.x)) * Mathf.Rad2Deg - 180);
//projObject.GetComponent<WarriorSpear>().player = _player;
//Activate a script perhaps, fire a function...whatever.
}
If you do want the spear to be synced on the network, the spear should have its own sync script. Thats the only place you will need the SyncVars.
I think you might not seem to understand the code I presented.
The spear is simply a projectile that can be created by any Warrior class and that can hit anything as long as it has the "Player" script attached to it (meaning other players and monsters). However, the name check (which is actually the Network Identity + a specific string) is simply used so that the spear doesn't hit the spear's owner. The client has no control whatsoever on the spear once it has been spawned, everything is handled server side.
By simply adding a SyncVar on the string "player" I managed to keep everything server sided (ex: Damaging players and enemies) while letting the clients understand that the spear can be destroyed if it hits something.
Thank you for you input.
Answer by Klounas · Apr 16, 2016 at 08:36 AM
Alright so to make this short, the string variable "Player" that had to check whether or not it was the owner of the spear that threw the spear (so he wouldn't get damaged) had to be a SyncVar since the Spawn method cannot on its own update variable values for the client side. Also made the Destroy gameObject call only if (isServer) so you wont get the "Warning: Did not find target for sync message for X" because the server is trying to update things for a matter of small frames on an object that's already destroyed client side.
TLDR: [SyncVar] everything and handle object Destruction server side as much as possible.
Hope this helped.
"[SyncVar] everything" Not good practise. Too much Bandwidth.
The other way to Sync data is using the Arguments of a Command or RPC function. When those functions are called, whatever is placed in to the arguments (parameters) is synced across the network to ensure that the Server/Client has the correct data to work with.
Yet I added the string Player variable in the Command call in order to create the spear object and assign the right value to Player and it wouldn't sync until I [SyncVar]ed it.
Follow this Question
Related Questions
UNet Parent System 0 Answers
Spaw dynamic (unregistered) object on network (UNET) 0 Answers
Unet Instantiate 0 Answers
Spawn Prefab inside another Prefab 0 Answers
UNET SyncVar on a NetworkInstanceId SyncList structure not updating across network 1 Answer