Funciones Personalizadas de Generación (Spawn)
Acciones Remotas

Sincronización de Estados

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

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.

SyncLists

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:

  • SyncListString
  • SyncListFloat
  • SyncListInt
  • SyncListUInt
  • SyncListBool

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;
    }
}

Funciones Personalizadas de Serialización

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.

Flujo de Serialización

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:

  • Cada NetworkBehaviour tiene una mask dirty ( sucia). Esta mask (mascara) está disponible dentro de OnSerialize como syncVarDirtyBits
  • Cada SyncVar en un script NetworkBehaviour es asignado un bit en la mask dirty.
  • Cambiar el valor de SyncVars causa el bit para ese SyncVar en ser configurada en la mask dirty (sucia)
  • Alternativamente, el llamado de SetDirtyBit() escribe directamente a la mask dirty
  • Los objetos NetworkIdentity son marcados en el servidor como parte de su loop (bucle-ciclo) de actualización
  • Si cualquiera de los NetworkBehaviours (Comportamientos de red) en una NetworkIdentity están dirty (sucios), entonces un paquete de UpdateVars es creado para ese objeto
  • El paquete UpdateVars es poblado al llamar OnSerialize en cada NetworkBehaviour en el objeto
  • Los NetworkBehaviours (comportamientos en red) que NO están dirty (sucios) escriben un cero al paquete para sus bits dirty
  • Los NetworkBehaviours (Comportamientos en red) que están dirty (sucios) escriben su dirty mask, luego los valores de las SyncVars que han cambiado
  • Si OnSerialize devuelve true para un NetworkBehaviour (comportamiento de red) la dirty mas se re-inicia para ese NetworkBehaviour, por lo que no lo enviará nuevamente hasta que sus valor cambie.
  • El paquete UpdateVars es enviado para alistar a los clientes que están observando el objeto

En el cliente:

  • un paquete UpdateVars es recibido para un objeto
  • La función OnDeserialize se llama para cada script NetworkBehaviour en el objeto
  • Cada script NetworkBehaviour en el objeto lee una dirty mask.
  • Si la dirty mask para un NetworkBehaviour es cero, las funciones OnDeserialize devuelve sin leer más
  • If la dirty mask es un valor diferente a cero, entonces la función OnDeserialize lee los valores para las SyncVars que corresponden a los bits dirty (sucios) que están configurados
  • Si hay funciones hook (enganche) de SyncVar, estas son invocadas con su valor leído del flujo (stream).

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.

Funciones Personalizadas de Generación (Spawn)
Acciones Remotas