La sincronización de estados (state Synchronization) es hecha del servidor a los clientes remotos. El cliente local no tiene datos serializados a este, ya que comparte la escena con el servidor. Cualquier dato serializado a un cliente local sería redundante. Los ganchos (hooks) SyncVar son llamados en clientes locales.
Los datos no son sincronizados de clientes remotos al servidor. Esto es un trabajo para los comandos.
SyncVars son variables miembro de scripts de NetworkBehaviour que son sincronizadas desde el servidor a los clientes. Cuando un objeto es generado, o un nuevo jugador se une a un juego en progreso, estos son enviados los ultimos estados de todos los SyncVars en los objetos en red que están visibles para ellos. Las variables miembro están hechas por SyncVars al utilizar el atributo personalizado [SyncVar]:
class Player : NetworkBehaviour
{
[SyncVar]
int health;
public void TakeDamage(int amount)
{
if (!isServer)
return;
health -= amount;
}
}
El estado de SyncVars es aplicado a objetos en clientes antes de que OnStartClient() sea llamado, para que el estado del objeto sea garantizado en estar actualizado dentro de OnStartClient().
Los SyncVars pueden ser tipos básicos tal como enteros, strings y floats. Estos también pueden ser tipos de Unity tal como Vector3 y structs definidas por el usuario, pero actualizaciones para struct SyncVars son enviadas como actualizaciones monolíticas, sin cambios incrementales si los campos dentro de una struct cambian. Pueden haber hasta 32 SyncVars en un solo script de NetworkBehaviour - esto incluye SyncLists.
Las actualizaciones de SyncVar son enviadas automáticamente por el servidor cuando el valor de una SyncVar cambia. No hay necesidad de realizar cualquier ensuciamiento manual de los campos para SyncVars.
Los SyncLists son como SyncVars pero estas son listas de valores en vez de valores individuales. Los contenidos de las SyncList están incluidos en unas actualizaciones de estado iniciales con el estado SyncVar. SyncLists no requieren de los atributos de SyncVar, estas son especificas a clases. Hay tipos de SyncList integrados para tipos básicos:
También hay SyncListStruct que pueden ser utilizado para listas de structs definidas por el usuario. La clase struct derivida utilizada SyncListStruct puede contener miembros de tipos básicos, arreglos, y tipos comunes de Unity. No pueden contener clases complejas o contenedores genéricos.
SyncLists tienen un delegado nombrado callback SyncListChanged que le permite a los clientes ser notificados cuando el contenido de la lista cambia. Este delegado es llamado con el tipo de operación que ocurrió, y el índice del item para el cual fue la operación.
public class MyScript : NetworkBehaviour
{
public struct Buf
{
public int id;
public string name;
public float timer;
};
public class TestBufs : SyncListStruct<Buf> {}
TestBufs m_bufs = new TestBufs();
void BufChanged(SyncListStruct<Buf>.Operation op, int itemIndex)
{
Debug.Log("buf changed:" + op);
}
void Start()
{
m_bufs.Callback = BufChanged;
}
}
A menudo el uso de SyncVars es suficiente para scripts serializar el estado a clientes, pero en algunos casos requiere un código más complejo de serialización. Las funciones virtuales en NetworkBehaviour que son utilizadas para la serialización de SyncVar puede ser implementada por desarrolladores para realizar sus serializaciones propias personalizadas. Estas funciones son:
public virtual bool OnSerialize(NetworkWriter writer, bool initialState);
public virtual void OnDeSerialize(NetworkReader reader, bool initialState);
La flag initialState es útil para diferenciar entre la primera vez que un objeto es serializado y cuando las actualizaciones incrementales son enviadas. La primera vez que un objeto es enviada al cliente, este debe incluir una captura instantánea completa del estado, pero actualizaciones posteriores pueden ahorrar en ancho de banda al incluir solo cambios incrementales. Tenga en cuenta que la función de gancho (hook) de Syncvar no son llamada cuando initialState es true, solamente para actualizaciones incrementales.
Si una clase tiene SyncVars, entonces la implementación de estas funciones son agregadas automáticamente a la clase. Por lo que una clase que tiene SyncVars no pueden también tener funciones de serialización personalizadas.
La función OnSerialize debería devolver true para indicar que una actualización debería ser enviada. Si devuelve true, entonces los dirty bits (bits sucios) para ese script son configurados a cero, si devuelve false entonces los dirty bits no cambian. Esto permite a que multiplos cambios a un script sean acumulados en el tiempo y enviados cuando el sistema está listo, en vez de cada frame.
Los Game Objects con el componente NetworkIdentity pueden tener múltiples scripts derivados de NetworkBehaviour. El flujo para serializar estos objetos es:
En el servidor:
En el cliente:
Entonces para este script:
public class data : NetworkBehaviour
{
[SyncVar]
public int int1 = 66;
[SyncVar]
public int int2 = 23487;
[SyncVar]
public string MyString = "esfdsagsdfgsdgdsfg";
}
La función generado OnSerialize es algo así:
public override bool OnSerialize(NetworkWriter writer, bool forceAll)
{
if (forceAll)
{
// the first time an object is sent to a client, send all the data (and no dirty bits)
writer.WritePackedUInt32((uint)this.int1);
writer.WritePackedUInt32((uint)this.int2);
writer.Write(this.MyString);
return true;
}
bool wroteSyncVar = false;
if ((base.get_syncVarDirtyBits() & 1u) != 0u)
{
if (!wroteSyncVar)
{
// write dirty bits if this is the first SyncVar written
writer.WritePackedUInt32(base.get_syncVarDirtyBits());
wroteSyncVar = true;
}
writer.WritePackedUInt32((uint)this.int1);
}
if ((base.get_syncVarDirtyBits() & 2u) != 0u)
{
if (!wroteSyncVar)
{
// write dirty bits if this is the first SyncVar written
writer.WritePackedUInt32(base.get_syncVarDirtyBits());
wroteSyncVar = true;
}
writer.WritePackedUInt32((uint)this.int2);
}
if ((base.get_syncVarDirtyBits() & 4u) != 0u)
{
if (!wroteSyncVar)
{
// write dirty bits if this is the first SyncVar written
writer.WritePackedUInt32(base.get_syncVarDirtyBits());
wroteSyncVar = true;
}
writer.Write(this.MyString);
}
if (!wroteSyncVar)
{
// write zero dirty bits if no SyncVars were written
writer.WritePackedUInt32(0);
}
return wroteSyncVar;
}
Y la función OnDeserialize es algo así:
public override void OnDeserialize(NetworkReader reader, bool initialState)
{
if (initialState)
{
this.int1 = (int)reader.ReadPackedUInt32();
this.int2 = (int)reader.ReadPackedUInt32();
this.MyString = reader.ReadString();
return;
}
int num = (int)reader.ReadPackedUInt32();
if ((num & 1) != 0)
{
this.int1 = (int)reader.ReadPackedUInt32();
}
if ((num & 2) != 0)
{
this.int2 = (int)reader.ReadPackedUInt32();
}
if ((num & 4) != 0)
{
this.MyString = reader.ReadString();
}
}
Si una NetworkBehaviour tiene una clase base que también tiene funciones de serialización, las funciones de la clase base deberían también ser llamadas.
Tenga en cuenta que los paquetes UpdateVar creados para las actualizaciones de los estados de los objetos pueden ser agregados en buffers antes de que sean enviados al cliente, por lo que un paquete de una capa sencilla de transporte puede contener actualizaciones para múltiplos objetos.