I’m trying to get to grips with the new Unity networking system, but I’ve stumbled at almost the very first hurdle, namely SyncVar synchronization.
I’ve put the following script onto a cube gameobject.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.Networking;
public class Cube : NetworkBehaviour {
[SerializeField] Text m_healthText;
[SyncVar(hook = "OnHealthChanged")]
public int m_health;
public override void OnStartLocalPlayer ()
{
GetComponent<MeshRenderer>().material.color = Color.red;
m_health = 100;
}
// Update is called once per frame
void Update () {
if(isLocalPlayer)
{
if(Input.GetKeyDown(KeyCode.Space))
{
m_health = m_health - 10;
}
}
}
void OnHealthChanged(int newHealth)
{
m_health = newHealth;
m_healthText.text = "Health: " + m_health.ToString();
}
}
Basically all it does is when the player joins the game, he has his health initialised to 100, which should (via the SyncVar hook) update the attached Text component to reflect the health value.
Then, each time space is pressed, health is reduced by 10 and the label is updated.
This works perfectly well when playing as a host, however when playing as a client the SyncVar never sends its new value across the network.
Using debug mode in the inspector, when running as a client, I can see that ‘SyncVar Dirty Bits’ is 1, and there is no Last Send Time. Each time I press space, I can see the value of health change, but it never sends across the network.
I can’t figure out what, if anything, I’m doing wrong. Would someone be kind enough to point out what I’m missing?
Here are some screenshots that further illustrate the problem. I’m using Unity 5.2.3f1.
Well, I think I’ve solved it, and in doing so I’ve made the following observations, which I’d be grateful if someone could either confirm or correct me.
Observation 1. SyncVars can only be changed by the server
Observation 2. Granting an object local player authority doesn’t allow it to directly change a SyncVar and have it propagate the change across the network.
Observation 3: To correctly update a SyncVar across the network I need to use a [Command] function.
Anyway, this is my updated script which appears to be working properly, in as much as the health value and display is synchronized across all clients.
Does this look like the correct approach for handling SyncVars?
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.Networking;
public class Cube : NetworkBehaviour {
[SerializeField] Text m_healthText;
[SyncVar(hook = "OnHealthChanged")]
public int m_health;
public override void OnStartLocalPlayer ()
{
GetComponent<MeshRenderer>().material.color = Color.red;
CmdSetHealth(100);
}
public override void OnStartClient ()
{
OnHealthChanged(m_health);
}
// Update is called once per frame
void Update () {
if(isLocalPlayer)
{
if(Input.GetKeyDown(KeyCode.Space))
{
CmdSetHealth(m_health - 10);
}
}
}
[Command]
void CmdSetHealth(int health)
{
m_health = health;
}
void OnHealthChanged(int newHealth)
{
m_health = newHealth;
m_healthText.text = "Health: " + m_health.ToString();
}
}
Yes, to have a SyncVar variable change on every client, it must be changed on the server. UNet is a server authoritative system.
Correct, only the server can change a SyncVar and have sync to the clients.
Yes, using a Command is how a client object can run a function on the server version of that object. Changing a SyncVar inside the Command function will propagate the change to the clients, including the client that called the Command.
thanks for confirming that for me, it’s a relief to know that I’m approaching things in fundamentally the right way, after all the hair tearing out yesterday
This seems crazy to me. the value would have to go to server and come to client only to update it on the client? There should be some sort of easy way to achieve this without making a round trip. I wonder how the client can inform authoritatively about it’s position/rotation with NetworkTransform when it has “local player authority”