Categories
Dev Blog

Networked Child GameObjects Using Mirror in Unity

We’re using Unity and Mirror for Saleblazers. At the time, there wasn’t a networking solution I felt comfortable with committing to.

Photon’s great, but it has a high CCU cost over time. UNET was deprecated. I’m too dumb for custom solutions like RakNet. MLAPI was okay at the time, but seemed highly experimental. I think it’s called Netcode nowadays, but I’m unsure of its stability or community support as with all new features.

We’re planning on releasing our game in a premium fashion, so there aren’t any egregious microtranscations planned for our roadmap to support any recurring CCU costs. So, we decided to use Mirror paired with the various NAT punching and relay services that each store had to offer. If we have to pay 30% to Steam, we might as well take advantage of their services!

There are a few limitations to Mirror/UNET, but the open source nature of Mirror allows for quick and easy modificaitons.

One such modification is networked child GameObjects.

For a NetworkBehaviour to work, it needs to be attached to a GameObject with a NetworkIdentity. The NetworkBehaviour uses a GetComponent call to find a NetworkIdentity on its GameObject.

The simplest example we have in our project would be our GameManager:

This CalendarManager contains a networked script, but the GameManager is the GameObject with a NetworkIdentity.

Yes, we could’ve probably stuck all the scripts of each manager onto the root GameManager GameObject. Let’s ignore that, since this is just an example. Apologies for lack of code blocks.

Here’s how I did it:

  1. I added this variable to NetworkIdentity. This allows me to store any assignment requests from external NetworkBehaviours.
public sealed class NetworkIdentity : MonoBehaviour
{
        static readonly ILogger logger = LogFactory.GetLogger<NetworkIdentity>();

        //#ASBeginChange by Michael Duan adding support for child network behaviors
        [System.NonSerialized]
        public List<NetworkBehaviour> extraNetworkObjects = new List<NetworkBehaviour>();
        //#ASEndChange

        NetworkBehaviour[] networkBehavioursCache;

2. In NetworkIdentity.CreateNetworkBehavioursCache, I added this so that it’d combine the regular GetComponents call with these external requests.

void CreateNetworkBehavioursCache()
{
            //#ASBeginChange by Michael Duan, adding support for multiple network behaviors in children
            networkBehavioursCache = GetComponents<NetworkBehaviour>();

            if (extraNetworkObjects != null && extraNetworkObjects.Count > 0)
            {
                List<NetworkBehaviour> BehaviourList = new List<NetworkBehaviour>(networkBehavioursCache);

                for (int i = 0; i < extraNetworkObjects.Count; ++i)
                {
                    if(extraNetworkObjects[i])
                    {
                        BehaviourList.Add(extraNetworkObjects[i]);
                    }
                }

                networkBehavioursCache = BehaviourList.ToArray();
            }
            //#ASEndChange

            if (networkBehavioursCache.Length > byte.MaxValue)

3. In NetworkBehaviour, I removed the requirement for a NetworkIdentity and added itself to the previous extraNetworkObjects List.

public abstract class NetworkBehaviour : MonoBehaviour
{
        //#ASBeginChange by Michael Duan making child network objects better slightly (more future proof)
        public virtual void Awake()
        {
            if(OverrideOwningNetworkIdentity)
            {
                OverrideOwningNetworkIdentity.extraNetworkObjects.Add(this);
                OverrideOwningNetworkIdentity.bCacheDirty = true;
            }
        }
        //#ASEndChange

        static readonly ILogger logger = LogFactory.GetLogger(typeof(NetworkBehaviour));

That’s it for the code side! For implementation:

  1. Fill out the OverrideNetworkIdentity variable on the child NetworkBehaviour.
  2. In Edit -> Project Settings -> Script Execution Order, make sure that your child NetworkBehaviour executes before NetworkIdentity.

There are some more limitations — if you add/remove components, you’ll need to do some extra work to sync those up. All clients/servers need to have the same array of objects in the same.

It’s also helpful because if we wanted to add another networked object, we’d just add it to the existing NetworkIdentity in the prefab. Mirror requires resaving the level to assign an ID to NetworkIdentities in the scene. This way, we don’t have to go through all our levels and save them to assign new NetworkIdentity IDs since we’re reusing the same one.

Thanks for reading! I hope this helped someone.

Leave a Reply

Your email address will not be published. Required fields are marked *