Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

OnSerialize and OnDeserialize.

Discussion in 'Multiplayer' started by LovattoStudio, Jun 15, 2015.

  1. LovattoStudio

    LovattoStudio

    Joined:
    Feb 11, 2015
    Posts:
    15
    Hi.

    I'm learning phase with UNET, I have a great experience with Photon (even that's beside the point), and I'm learning about custom Serialization uNet, I am trying to synchronize the position of a transform using the serialization of the position and rotation of this, for this I am using this simple code:
    Code (csharp):
    1.  
    2.     private Vector3 SyncedPos;
    3.     private Quaternion SyncedRot;
    4.  
    5. void FixedUpdate()
    6.     {
    7.        SmoothPosition();
    8.     }
    9.  
    10. void SmoothPosition()
    11.     {
    12.         if (isLocalPlayer)
    13.             return;
    14.  
    15.         //Smooth movement
    16.         base.m_Transform.position = Vector3.Lerp(base.m_Transform.position, SyncedPos, Time.deltaTime * PlayerSyncRate);
    17.         base.m_Transform.rotation = Quaternion.Lerp(base.m_Transform.rotation, SyncedRot, Time.deltaTime * PlayerSyncRate);
    18.  
    19.     }
    20.  
    21. public override void OnDeserialize(NetworkReader reader, bool initialState)
    22.     {
    23.         base.OnDeserialize(reader, initialState);
    24.  
    25.             SyncedPos = reader.ReadVector3();
    26.             SyncedRot = reader.ReadQuaternion();
    27.            // Debug.LogWarning("Receive Info");
    28.     }
    29.  
    30.     public override bool OnSerialize(NetworkWriter writer, bool initialState)
    31.     {
    32.  
    33.             writer.Write(base.m_Transform.position);
    34.             writer.Write(base.m_Transform.rotation);
    35.             //Debug.LogWarning("Send Info");
    36.  
    37.         return true;
    38.     }
    39.  
    But apparently the local client does not seem to receive the information (position and rotation) of other clients.

    Someone can clarify the proper use of this or tell me I'm making the error, any help is appreciated.
    Kind Regards.
     
  2. Necromantic

    Necromantic

    Joined:
    Feb 11, 2013
    Posts:
    116
    Is this contained inside a NetworkBehaviour?
     
  3. LovattoStudio

    LovattoStudio

    Joined:
    Feb 11, 2015
    Posts:
    15
    Yes,use NetworkBehaviour
    Code (csharp):
    1. public class bl_PlayerSync : NetworkBehaviour {
    and have a "NetworkIdentity" attached to the gameObject too.

    but still receive only the starting position (Spawn Position) is not updated.
    I'm sure the problem is with the code, need something more to add, but am not sure that because there are no examples or more detailed documentation on how to use them properly.
     
  4. liortal

    liortal

    Joined:
    Oct 17, 2012
    Posts:
    3,562
    Is this script attached to a game object that is registered to be network spawned?
    Also, can you verify that OnSerialize and OnDeserialize are getting called ? (using a debugger / Debug.Log calls)
     
  5. LovattoStudio

    LovattoStudio

    Joined:
    Feb 11, 2015
    Posts:
    15
    - This script is attached to playerPrefab.
    - Yes, I debugging this, with this results:

    Just LocalClient as Host (1 Connection), like 20s gamePlay:
    2 OnSerialize calls. (On Start)
    0 OnDeserialize calls.

    two connections (Debugging as Host), 25s gamePlay:
    2 OnSerialize (On Start) 1 more when other client join.
    0 OnDeserialize call.

    So, functions obviously are not constantly updated, leading to not send / receive any information ... the question is why?
     
  6. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    Data is not serialized from the host to the LocalClient. The local client is sharing the scene with the server, so the data is already "up-to-date" on a local client.

    (SyncVar hooks ARE called on a local client though).

    Data is not serialized from remote clients to the host either. In the HLAPI you use [Command]s for that.

    Serialization happens between the server and remote clients.
     
  7. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    State Synchronization

    State Synchronization is done from the Server to Remote Clients. The local client does not have data serialized to it, since it shares the scene with the server. Any data serialized to a local client would be redundant. SyncVar hooks however are called on local clients.

    Data is not synchronized from remote clients to the server. This is job of Commands.

    SyncVars
    SyncVars are member variables of NetworkBehaviour scripts that are synchronized from the server to clients. 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. Member variables are made into SyncVars by using the [SyncVar] custom attribute:

    Code (CSharp):
    1. class Player : NetworkBehaviour
    2. {
    3.  
    4.   [SyncVar]
    5.   int health;
    6.  
    7.   public void TakeDamage(int amount)
    8.   {
    9.     if (!isServer)
    10.       return;
    11.  
    12.     health -= amount;
    13.   }
    14. }
    The state of SyncVars is applied to objects on clients before OnStartClient() is called, so the state of the object is guaranteed to be up-to-date inside OnStartClient().

    SyncVars can be basic types such as integers, strings and floats. They can also be Unity types such as Vector3 and user-defined structs, but updates for struct SyncVars are sent as monolithic updates, not incremental changes if fields within a struct change.

    SycnVar updates are sent automatically by the server when the value of a SyncVar changes. There is no need to perform any manual dirtying of fields for SyncVars.

    SyncLists
    SyncLists are like SyncVars but they are lists of values instead of individual values. SyncList contents are included in initial state updates with SyncVar state. SyncLists do not require the SyncVar attributes, they are specific classes. There are built-in SyncList types for basic types:

    • SyncListString
    • SyncListFloat
    • SyncListInt
    • SyncListUInt
    • SyncListBool
    There is also SyncListStruct which can be used for lists of user-defined structs.

    Custom Serialization Functions
    Often the use of SyncVars is enough for scripts to serialize their state to clients, but some cases require more complex serialization code. The virtual functions on NetworkBehaviour that are used for SyncVar serialization can be implmented by developers to perform their own custom serialization. These functions are:

    public virtual bool OnSerialize(NetworkWriter writer, bool initialState);
    public virtual void OnDeSerialize(NetworkReader reader, bool initialState);

    The initialState flag is useful to differentiate between the first time an object is serialized and when incremental updates can be sent. The first time an object is sent to a client, it must include a full state snapshot, but subsequent updates can save on bandwidth by including only incremental changes. Note that SyncVar hook fucntion are not called when initialState is true, only for incremental updates.

    If a class has SyncVars, then implementations of these functions are added automatically to the class. So a class that has SyncVars cannot also have custom serialization functions.

    The OnSerialize function should return true to indicate that an update should be sent. If it returns true, then the dirty bits for that script are set to zero, if it returns false then the dirty bits are not changed. This allows multiple changes to a script to be accumulated over time and sent when the system is ready, instead of every frame.

    Serialization Flow
    Game objects with the NetworkIdentity component can have multiple scripts derived from NetworkBehaviour. The flow for serializing these objects is:

    On the server:

    • Each NetworkBehaviour has a dirty mask. This mask is available inside OnSerialize as syncVarDirtyBits
    • Each SyncVar in a NetworkBehaviour script is assigned a bit in the dirty mask.
    • Changing the value of SyncVars causes the bit for that SyncVar to be set in the dirty mask
    • Alternatively, calling SetDirtyBit() writes directly to the dirty mask
    • NetworkIdentity objects are checked on the server as part of it’s update loop
    • If any NetworkBehaviours on a NetworkIdentity are dirty, then an UpdateVars packet is created for that object
    • The UpdateVars packet is populated by calling OnSerialize on each NetworkBehaviour on the object
    • NetworkBehaviours that are NOT dirty write a zero to the packet for their dirty bits
    • NetworkBehaviours that are dirty write their dirty mask, then the values for the SyncVars that have changed
    • If OnSerialize returns true for a NetworkBehaviour, the dirty mask is reset for that NetworkBehaviour, so it will not send again until it’s value changes.
    • The UpdateVars packet is sent to ready clients that are observing the object
    On the client:

    • an UpdateVars packet is received for an object
    • The OnDeserialize function is called for each NetworkBehaviour script on the object
    • Each NetworkBehaviour script on the object reads a dirty mask.
    • If the dirty mask for a NetworkBehaviour is zero, the OnDeserialize functions returns without reading any more
    • If the dirty mask is non-zero value, then the OnDeserialize function reads the values for the SyncVars that correspond to the dirty bits that are set
    • If there are SyncVar hook functions, those are invoked with the value read from the stream.
    So for this script:

    Code (CSharp):
    1. public class data : NetworkBehaviour
    2. {
    3.  
    4.     [SyncVar]
    5.     public int int1 = 66;
    6.  
    7.     [SyncVar]
    8.     public int int2 = 23487;
    9.  
    10.     [SyncVar]
    11.     public string MyString = "esfdsagsdfgsdgdsfg";
    12. }
    The generated OnSerialize function is something like:
    Code (CSharp):
    1. public override bool OnSerialize(NetworkWriter writer, bool forceAll)
    2. {
    3.     if (forceAll)
    4.     {
    5.         // the first time an object is sent to a client, send all the data (and no dirty bits)
    6.         writer.WritePackedUInt32((uint)this.int1);
    7.         writer.WritePackedUInt32((uint)this.int2);
    8.         writer.Write(this.MyString);
    9.         return true;
    10.     }
    11.     bool wroteSyncVar = false;
    12.     if ((base.get_syncVarDirtyBits() & 1u) != 0u)
    13.     {
    14.         if (!wroteSyncVar)
    15.         {
    16.             // write dirty bits if this is the first SyncVar written
    17.             writer.WritePackedUInt32(base.get_syncVarDirtyBits());
    18.             wroteSyncVar = true;
    19.         }
    20.         writer.WritePackedUInt32((uint)this.int1);
    21.     }
    22.     if ((base.get_syncVarDirtyBits() & 2u) != 0u)
    23.     {
    24.         if (!wroteSyncVar)
    25.         {
    26.             // write dirty bits if this is the first SyncVar written
    27.             writer.WritePackedUInt32(base.get_syncVarDirtyBits());
    28.             wroteSyncVar = true;
    29.         }
    30.         writer.WritePackedUInt32((uint)this.int2);
    31.     }
    32.     if ((base.get_syncVarDirtyBits() & 4u) != 0u)
    33.     {
    34.         if (!wroteSyncVar)
    35.         {
    36.             // write dirty bits if this is the first SyncVar written
    37.             writer.WritePackedUInt32(base.get_syncVarDirtyBits());
    38.             wroteSyncVar = true;
    39.         }
    40.         writer.Write(this.MyString);
    41.     }
    42.  
    43.     if (!wroteSyncVar)
    44.     {
    45.         // write zero dirty bits if no SyncVars were written
    46.         writer.WritePackedUInt32(0);
    47.     }
    48.     return wroteSyncVar;
    49. }
    50.  
    And the OnDeserialize function is something like:

    Code (CSharp):
    1. public override void OnDeserialize(NetworkReader reader, bool initialState)
    2. {
    3.     if (initialState)
    4.     {
    5.         this.int1 = (int)reader.ReadPackedUInt32();
    6.         this.int2 = (int)reader.ReadPackedUInt32();
    7.         this.MyString = reader.ReadString();
    8.         return;
    9.     }
    10.     int num = (int)reader.ReadPackedUInt32();
    11.     if ((num & 1) != 0)
    12.     {
    13.         this.int1 = (int)reader.ReadPackedUInt32();
    14.     }
    15.     if ((num & 2) != 0)
    16.     {
    17.         this.int2 = (int)reader.ReadPackedUInt32();
    18.     }
    19.     if ((num & 4) != 0)
    20.     {
    21.         this.MyString = reader.ReadString();
    22.     }
    23. }
    24.  
    If a NetworkBehaviour has a base class that also has serialization functions, the base class functions should also be called.

    Not that the UpdateVar packets created for object state updates may be aggregated in buffers before being sent to the client, so a single transport layer packet may contain updates for multiple objects.
     
  8. MMOERPG

    MMOERPG

    Joined:
    Mar 21, 2014
    Posts:
    50
    Hi @seanr

    What is the source of your post? looks like it was copy/pasted from documentation somewhere. I'd love to have access to that documentation directory...

    Thanks for the information!
    Ian

    EDIT:
    Found it, is in the HLAPI documentation.
    https://docs.unity3d.com/Manual/UNetStateSync.html
     
    Last edited: Jan 1, 2017
  9. MMOERPG

    MMOERPG

    Joined:
    Mar 21, 2014
    Posts:
    50
    Hi @seanr

    I have three questions regarding Sync Vars, and custom OnSerialization() and OnDeSerialization().

    I'm instantiating the players character prefab on the server in OnServerAddPlayer(). I load the characters data from GameSparks, then instantiate the appropriate prefab, then set all the characters data to a "character" script that's attached to the instantiated prefab. I then use NetworkServer.AddPlayerForConnection() to instantiate the character prefab on the clients. My problem is that the data added is not applied to the script that's attached to the character prefab that is instantiated on the client. I know that I can set up sync vars for each variable and this should work however there are some variables that will never change (base attributes for instance.) I use both base attribute variables as well as current attribute variables with base and current values respectively.

    Question 1. Can I use a custom OnSerialization() and OnDeSerialization() function to serialize the entire character gameObject on the server before its spawned on the client then instantiate the prefab and pass this data to the client that deserializes the object and applies the data to the attached "character" script? I'm getting the idea from this post:
    https://forum.unity3d.com/threads/unet-saving-networking-character-data.436763/

    Question 2. If a Sync Var is in a script that shares a gameObject with an attached NetworkIdentity that is set to localPlayerAuthority == true, will the local player be able to update that variable that then updates on the server and then all other clients?

    Question 3. If Q2 is true then is there a way to make a character have server authority for sync vars but still use the isLocalPlayer bool in scripts that are attached to that character?

    I don't want the player to be able to "hack" the base character data and have it sync and update on the server. The current character data is currently calculated on the server from the base data plus modifying features such as buffs and equipment that the server has authority over.
     
    Last edited: Jan 1, 2017
  10. dbrizov

    dbrizov

    Joined:
    Feb 22, 2014
    Posts:
    10
    Every NetworkBehaviour has a dirty mask of 32 bits.
    You can have only 32 synced variables because of that.
    If a [SyncVar] is changed the corresponding bit is marked as dirty. The server observes such changed and makes a call to OnSerialize() to serialize the state of the changed NetworkBehavior. Then he forces a call to OnDeserialize() for each client so that the clients get the updated state of the game.

    You can have custom OnSerialize() and OnDeserialize() functions, however the default generated ones for the synced variables won't work.

    Straight from the documentation: "If a class has SyncVars, then implementations of these functions are added automatically to the class. So a class that has SyncVars cannot also have custom serialization functions."

    In your case OnSerialize() and OnDeserialize() are not called, because the dirty mask is never set dirty.
    You can make a call to the SetDirtyBit() function. This will force OnSerialize() on the server and the corresponding OnDeserialize() on the clients.

    Your code should look something like this:
    Code (CSharp):
    1. private const uint BIT_POSITION = 1u;
    2. private const uint BIT_ROTATION = 2u;
    3.  
    4. public override bool OnSerialize(NetworkWriter writer, bool initialState)
    5.     {
    6.         if (initialState)
    7.         {
    8.             writer.Write(this.transform.position);
    9.             writer.Write(this.transform.rotation);
    10.         }
    11.         else
    12.         {
    13.             if ((this.syncVarDirtyBits & BIT_POSITION) != 0u)
    14.             {
    15.                 writer.Write(this.transform.position);
    16.             }
    17.  
    18.             if ((this.syncVarDirtyBits & BIT_ROTATION) != 0u)
    19.             {
    20.                 writer.Write(this.transform.rotation);
    21.             }
    22.         }
    23.  
    24.         return true;
    25.     }
    26.  
    27.     public override void OnDeserialize(NetworkReader reader, bool initialState)
    28.     {
    29.         if (initialState)
    30.         {
    31.             this.position = reader.ReadVector3();
    32.             this.rotation = reader.ReadQuaternion();
    33.         }
    34.         else
    35.         {
    36.             if ((this.syncVarDirtyBits & BIT_POSITION) != 0u)
    37.             {
    38.                 this.position = reader.ReadVector3();
    39.                 this.transform.position = this.position;
    40.             }
    41.  
    42.             if ((this.syncVarDirtyBits & BIT_ROTATION) != 0u)
    43.             {
    44.                 this.rotation = reader.ReadQuaternion();
    45.                 this.transform.rotation = this.rotation;
    46.             }
    47.         }
    48.     }
    If you want to update the position only, you have to set dirty only the position bit.
     
    Last edited: Jun 7, 2017
    Deleted User likes this.
  11. Deleted User

    Deleted User

    Guest

    Hi, is there any way to Serialize with protobuf, json, bson or anything?
     
  12. dbrizov

    dbrizov

    Joined:
    Feb 22, 2014
    Posts:
    10
    I don't know mate. As much as I know you can't, but it's probably better for you to google it.
     
    Deleted User likes this.