From the manual: "When an object is spawned, or a new player joins a game in progress, they are sent the latest state of all SyncVars on networked objects that are visible to them. " (http://docs.unity3d.com/Manual/UNetStateSync.html)
That is great and is working correctly, but my SyncVars have hook functions, and these functions are only being called when the variable is changed on the server. It isn’t being called when the client first logs in and gets the latest state of all SyncVars on the networked objects from the server.
Is this a known bug? Is there something I can do to call those hook functions when a client joins a game in progress and its networked objects’ syncvars are updated?
Yes the hook is not called when the object is instantiated - but is guaranteed to have syncvar at correct state by StartClient() call and from what I see always before Start() is called too. So you can call your hook during Start() or StartClient() ?
I also would like to see an option to automatically call hooks on start. Having to manually call all hooks for all SyncVars on client connect leaves room for error.
SyncVar Hooks are for changes in state, not for initial state. There is the OnStartClient callback for handling initial state.
There is no context during a SyncVar hook, so if they were called for initial state, the client would not be able to tell the difference between initialization of a variable and a change in the value of a variable. This causes un-expected results.
For example, for a “[SyncVar] int health” with a SyncVar hook that causes a client-side “blood-spurt” effect when the object takes damage, the object would play the blood-spurt for any objects with non-maximum health when joining a game in progress. The hook would be called with the new health value, which is different from the default health value, so the object thinks it has taken damage - so it plays its blood-spurt. But this is not what should happen. Setting the initial health of the object to its current value from the server should NOT play the effect.
This applies to all kinds of state changes, such as animation state, particles, etc. Initialization is different from incremental changes and the client-side code needs to know which is happening.
@seanr I understand the intention, though: Speaking for myself, I expect callbacks to be called everytime a value is updated. I do also want to do things like rebuild the UI to fit the objects current state.
So, would it be possible to pass an additional “isInit”-bool to the hook, that is set only once the value is initialized?
That way, I could do something like this:
[SyncVar (hook = "OnHealthUpdated")]
private int Health = 100;
private void OnHealthUpdated(int value, bool isInit)
{
if(!isInit)
{
if (value < Health)
Say("Ouch!");
else
Say("Ah, Thats Better!");
}
Health = value;
RefreshUI();
}
@Baroni1 It’s more a request concerning convenience. If you do it that way, you might forget calling a hook somewhere. The code would still compile, but you just produced a bug that might or might not be easy to track down. All the previous posters also seem to agree its counterintuitive, and thats not how Unity is meant to be.
An additional parameter in the deserialization part won’t hurt, and UNet is still in the makings after all.
In case I’m activating a shield in something like “OnShieldUpdated”, I would have to always check against the value passed in as I would not want to render a shield for players right at the beginning, when the value is 0 but the hook is called nonetheless. There are situations where I would like for hooks being called automatically (that’s why I’ve found this thread in the first place) but there are others where I don’t, so I’ve learned to live with that manual initialization.
@PhilippG
I’m not here to change your opinion. It is one thing to call something manually which isn’t there, but it is a completely different thing to work around something you can’t change.
You might not want to change my mind but, intentionally or not, you’re undermining my request. I tagged @seanr because I want to ask a unity staff member if this or a similar implementation might be an option for future releases. Still, thank you for your input.
This is working a little differently for me. Because the hook kind of hijacks the sync, my client variable is actually NOT initialized by the time OnStartClient() is called. I verified this by creating a new SyncVar variable without a hook. It was properly initialized while the hooked variable was still at it’s default.
Can you explain a little more how you are getting this to work?
For me, any SyncVars that are hook’ed are not updated by OnStartClient. Are you using two different Sync’ed variables? or are you doing something else? Also, my post above has an example of what my code looks like. Any suggestions? Thanks.
I’m having a problem with this SyncVar stuff: So the hook function is not called for initializations, that’s fine. So you suggested I should initialize the hook function from OnStartClient(). The problem with that is that it only calls the hook function the first time I join the server. If I leave then join back, the OnStartClient() is not called. This script is attached to an object in the scene called TestMachine (which is a Cube) not a player object.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class TestMachine : NetworkBehaviour
{
[SyncVar(hook = "UpdateColour")]
int colour;
void Start()
{
Debug.Log("Normal Start");
}
public override void OnStartServer()
{
Debug.Log("Start Server Test Machine!");
colour = 0;
UpdateColour(colour);
}
public override void OnStartClient()
{
Debug.Log("Start Client Test Machine!");
UpdateColour(colour);
}
void Update()
{
Debug.Log(colour);
}
public void Interact()
{
RpcChangeColour();
}
public void UpdateColour(int colour)
{
//Debug.Log(colour);
if (colour == 1)
{
GetComponent<MeshRenderer>().material.color = Color.blue;
}
else
{
GetComponent<MeshRenderer>().material.color = Color.red;
}
}
[ClientRpc]
public void RpcChangeColour()
{
if (colour == 0)
{
colour = 1;
}
else
{
colour = 0;
}
}
}
My hooks are def being called on client for initialized variables.
[SyncVar(hook = "OnRandomSeedSync")]
public int randomSeed;
public void OnRandomSeedSync(int newSeed) {
randomSeed = newSeed;
randGenerator = new System.Random(newSeed);
}
Putting a breakpoint in the OnRandomSeedSync it stops there every time a new enemy spawns in the client, but the randomSeed value is the same as the newSeed.
Also, it seems sometimes it’s not, as every 10ish enemy or so I will get an error that I tried to access randGenerator and it was null.
With SyncVar, Hook, Command, Rpc I can get changes in value in one client to be sent to all the others, but for new clients, there is no way to get them to all send the new client all of their current values.
For example Client C joins a game with a health of 100. Clients A and B have been fighting and have healths of less than 100.
None of the networking commands/rpcs etc seem to start Client C off already knowing the healths of A and B. Although A and B seem to be given the health of C when C starts.