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

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:

What I need to do:


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.

3451064–273449–Network.cs (1.51 KB)
3451064–273450–Player.cs (2.57 KB)

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.

1 Like

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:

 // Weapon.cs 

public class Weapon
{
     private int networkID;

     public int Level;
     public int Value;
     public float Damage;

     // ============
     // Client code
     // ============
     public void WeaponUpgrade()    
     {

         // Let's assume that this magically invokes the Server_WeaponUpgrade function
         // on server-side instance of this class
         Network.Command(networkID, Server_WeaponUpgrade);
     }

     // ============
     // Server code
     // ============

     public void Server_WeaponUpgrade()
     {
         Value += 5;
         Damage += 5;

         // Now we need to replicate the Value and Damage fields to all clients - How?
     }
}

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.

 // Weapon.cs 

public class Weapon
{
     private int networkID; //A network ID idea, some class keeps track of all of these

     [NetworkVar(0)] public int Level;
     [NetworkVar(1)] public int Value;
     [NetworkVar(2)] public float Damage;

     // ============
     // Client code
     // ============
     public void WeaponUpgrade()    
     {
         // Let's assume that this magically invokes the Server_WeaponUpgrade function
         // on server-side instance of this class
         Network.Command(networkID, Server_WeaponUpgrade);
     }

     // ============
     // Server code
     // ============
     public void Server_WeaponUpgrade()
     {
         Value += 5;
         Damage += 5;

         // Now we need to replicate the Value and Damage fields to all clients
         // let's assume it would work like this: 
         Network.ReplicateVars(networkID, 1 | 2);
     }
}

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!

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.

1 Like

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): // **** Entity.cs ***public abstract class Entity{ public int networkID; - Pastebin.com
Network class example: // Network.cs // Basically this is the only class that is actually "synced" ac - Pastebin.com

I’d greatly appreciate if somebody could throw a quick glance and tell me if I’m doing anything wrong.

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://discussions.unity.com/t/604147

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.

1 Like

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.

@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.

I’m glad you found my comments useful :slight_smile: