Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Voting for the Unity Awards are OPEN! We’re looking to celebrate creators across games, industry, film, and many more categories. Cast your vote now for all categories
    Dismiss Notice
  3. Dismiss Notice

Syncing vars over network for non-NetworkBehavior objects (architecture question)

Discussion in 'Multiplayer' started by Eudaimonium, Apr 5, 2018.

  1. Eudaimonium

    Eudaimonium

    Joined:
    Dec 22, 2013
    Posts:
    131
    Hi,

    I have begun exploring the possibilities of Unity Networking, and it's best practices/common use. What I have found is, that both Unet and plugins such as Photon heavily rely (and expect) that you will be syncing GameObject components such as transforms and MonoBehaviors.

    However, suppose the following game architecture philosophy: GameObject is a visual representation of a non-MonoBehavior class (which makes core gameplay logic classes considerably more lightweight).

    Example:
    - MeleeWeapon (which is a Weapon, which is an Entity) class has some information about itself such as, I dunno, gold value, level, damage, speed, quality of it's parts etc etc
    - MeleeProp (which is a Prop) is a MonoBehavior script on a pooled GameObject which accepts a Weapon type and changes GO's mesh renderer or textures or materials or whatever, to accurately represent given Weapon instance.

    Given this approach, the network-aware classes should be the non-MB(MonoBehavior) ones (Entity and descendants), not MB classes such as Props as these are created locally and do not need to be synced. What is the best approach to solve this?

    In other words, what would be adequate replacement for SyncVars, Commands and RPCs in non-MB classes? The concept and indeed, implementation of SyncVars and RPCs in NetworkBehaviors is very well done, but how can we use this for non-Monobehavior objects?

    Or am I completely going the wrong way about this? What is the usual approach in this scenario, if that's the case?

    EDIT:

    I wish to clarify what I'm trying to achieve. In short, I wish to sync non-MonoBehavior classes across network.

    What Unity currently does:

    Multiplayer_unity.png

    What I need to do:

    Multiplayer_concept.png
    As you can see I'm trying to avoid replication of resource-heavy GameObjects (or MonoBehavior components), and instead come up with a universal system for syncing some network-aware classes. Only the Network component is synced, and can (but doesn't have to) be a MonoBehavior.

    ~~I will come up with a pseudo-code prototype shortly, in the meantime~~, if you already know of a plugin or a native Unity method to achieve this, please do share your ideas! Otherwise, I'm prepared to come up with my own system built simply on nothing but Transport layer.

    I got some code ideas up. Please let me know if this is at least half-way decent approach.
     

    Attached Files:

    Last edited: Apr 5, 2018
  2. moco2k

    moco2k

    Joined:
    Apr 29, 2015
    Posts:
    294
    You could use an architecture design that aims at keeping the number of NetworkBehaviours as low as possible. For example, you could even get along with using only one single NetworkBehavior object to sync all the data. Therefore, you could implement unique identifiers for all your non-MB objects and a manager that is able to find those objects for given IDs. Eventually, if you want to sync information for one of your non-MB objects, you just need to pass the ID and data to respective functions in your single NetworkBehavior. With the ID, the same object is identifiable on server and clients. Of course, you will need a concept to ensure that IDs are the same on server and clients though.

    Also, you can always do something completely different and avoid HLAPI options like SyncVars, Commands, RPCs and just use messages, or build your own HLAPI on top of the LLAPI.
     
  3. Eudaimonium

    Eudaimonium

    Joined:
    Dec 22, 2013
    Posts:
    131
    That's exactly what I'm trying to do. I was also thinking along the route of "dedicated MB object that basically just syncs stuff" but the issue I'm having is code readability and maintainability. SyncVar-lookalike approach.

    Let's try a more specific example, see where it takes us:

    Suppose you have a Weapon type. Let's say a player wishes to upgrade his current weapon - this will result in a change to Weapon fields which need to be replicated across the network.

    Using Unity's nomenclature, I would guess the logic flow is as follows:
    - Player issues a Command to a server
    - Server executes changes on a Weapon instance
    - Server replicates the changed fields on all clients (only the changed ones, to save bandwith... maybe?)
    - Clients accept and write the changes into their Weapon instance.

    I'll try using some pseudocode to describe what I'd ideally like to achieve:

    Suppose a simplified version would look something like this:

    Code (CSharp):
    1.  // Weapon.cs
    2.  
    3. public class Weapon
    4. {
    5.      private int networkID;
    6.  
    7.      public int Level;
    8.      public int Value;
    9.      public float Damage;
    10.  
    11.      // ============
    12.      // Client code
    13.      // ============
    14.      public void WeaponUpgrade()    
    15.      {
    16.  
    17.          // Let's assume that this magically invokes the Server_WeaponUpgrade function
    18.          // on server-side instance of this class
    19.          Network.Command(networkID, Server_WeaponUpgrade);
    20.      }
    21.  
    22.      // ============
    23.      // Server code
    24.      // ============
    25.  
    26.      public void Server_WeaponUpgrade()
    27.      {
    28.          Value += 5;
    29.          Damage += 5;
    30.  
    31.          // Now we need to replicate the Value and Damage fields to all clients - How?
    32.      }
    33. }
    My current like of thinking would be to basically try to replicate what SyncVars do, minus the whole Mono CECIL reference swapping magic. Let's write an attribute that will map fields to some ID numbers (I guess you could auto-generate code that will basically do the boilerplate for you), and we can use these IDs to mark fields as dirty.

    Then, the server would somehow serialize only the needed fields and deliver them to clients.

    Code (CSharp):
    1.  // Weapon.cs
    2.  
    3. public class Weapon
    4. {
    5.      private int networkID; //A network ID idea, some class keeps track of all of these
    6.  
    7.      [NetworkVar(0)] public int Level;
    8.      [NetworkVar(1)] public int Value;
    9.      [NetworkVar(2)] public float Damage;
    10.  
    11.      // ============
    12.      // Client code
    13.      // ============
    14.      public void WeaponUpgrade()    
    15.      {
    16.          // Let's assume that this magically invokes the Server_WeaponUpgrade function
    17.          // on server-side instance of this class
    18.          Network.Command(networkID, Server_WeaponUpgrade);
    19.      }
    20.  
    21.      // ============
    22.      // Server code
    23.      // ============
    24.      public void Server_WeaponUpgrade()
    25.      {
    26.          Value += 5;
    27.          Damage += 5;
    28.  
    29.          // Now we need to replicate the Value and Damage fields to all clients
    30.          // let's assume it would work like this:
    31.          Network.ReplicateVars(networkID, 1 | 2);
    32.      }
    33. }
    Does this seem like a reasonable approach to variable syncing on non-MB types? Or am I trying to reinvent something that maybe already exists and I overlooked it?

    I'm going over LLAPI and Transport Layer documentation, and this should theoretically be doable.

    Thoughts and ideas welcome!
     
  4. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    NetworkBehaviour scripts and their helper features (SyncVar, SyncList, ClientRpc, Command, etc) are all optional, and just make it convenient to have information from a version of the same GameObject on one computer synced with the same GameObject on other computers without you having to worry much about how that data gets routed to the correct script on the correct GameObject.

    The HLAPI also has the Message functionality, where you can construct your own custom message classes and write your own handlers for dealing with the data however you like when the remote computer receives the message. If all you want from the HLAPI is the Message functionality you can in fact use NetworkServerSimple, which is still technically the HLAPI but doesn't include any of the functionality related to the Network Manager or NetworkBehaviours.

    As you already know, you can write your own higher level functionality on top of the LLAPI yourself as another option. I've personally found UNET Messages to be low level enough for anything I've needed so far, but YMMV.
     
    Eudaimonium likes this.
  5. Eudaimonium

    Eudaimonium

    Joined:
    Dec 22, 2013
    Posts:
    131
    Hmm I am not familiar with Messages or NetworkServerSimple, I'll do some research on that - in the meantime I provided my code idea samples in the opening post, under attachments.

    That's basically my vision of how I'd like to implement the idea from my graph - what an entity is (or a subclass of entity in such example) and what Network class is. I will try to see how the ideas you suggested can help me. Thank you!

    EDIT - Also uploaded them to Pastebin for convenience.

    Player.cs (Entity example): https://pastebin.com/iYE5mZvW
    Network class example: https://pastebin.com/BhQG6VY4

    I'd greatly appreciate if somebody could throw a quick glance and tell me if I'm doing anything wrong.
     
  6. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    I'm not surprised you hadn't heard of either, because Messages are only discussed on a single documentation page as somewhat of an afterthought in comparison to the NetworkBehaviour functionality, and NetworkServerSimple isn't in the documentation at all other than its scripting reference page.

    Here's the only NetworkServerSimple example I've ever found, but I've found it to be more than enough to learn how to use it:
    https://forum.unity.com/threads/chat-server-example.365938/

    Here's the documentation page for Messages;
    https://docs.unity3d.com/Manual/UNetMessages.html

    I've found using NetworkServerSimple very useful for server to server communication and connecting a client first to a separate login server. I've found Messages very useful, even when I'm using the rest of the HLAPI, when I want to send serialized complex class data, when I want to encrypt the data being sent, and when a client action needs to call a method on the server version of a GameObject with server authority.

    There's of course nothing wrong with ignoring all that and building your own stuff on top of the LLAPI though, so I'm not saying you "should" use anything I said.
     
    Last edited: Apr 5, 2018
    Eudaimonium likes this.
  7. Eudaimonium

    Eudaimonium

    Joined:
    Dec 22, 2013
    Posts:
    131
    Woah, hold up, this might just be exactly what I'm looking for, isn't it?

    My "Network" is basically NetworkServerSimple, and the functions within are messages, albeit my custom defined ones.

    How is this so obscure? It's beyond useful, especially for large procedural-gen games or such where syncing game objects simply isn't feasable.
     
  8. Eudaimonium

    Eudaimonium

    Joined:
    Dec 22, 2013
    Posts:
    131
    @Joe-Censored Thank you so much for the Simple server and Messages pointer. This indeed, was exactly what I needed. I've begun implementing it, and it appears as if it was made for my specific scenario I outlined with my pseudo-code and graphs in my opening post.

    In fact a lot of my pseudo-code turned out to be real code, that's how well this fits my use case. The "chat" example is also very clearly written and makes it easy to understand.

    Just to re-iterate, the approach uses "NetworkServerSimple" and "NetworkClient" as communication interface between server and client game. These classes are NOT base classes to derive from, they can simply be created with a new keyword anywhere in your code.
     
  9. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    I'm glad you found my comments useful :)