Mirror icon indicating copy to clipboard operation
Mirror copied to clipboard

Allow NetworkBehaviour on child objects of a NetworkIdentity

Open gitdev481 opened this issue 5 years ago • 5 comments

Please explain the suggested feature in detail. Right now, the way Mirror is programmed, it is not possible to have child game objects that inherit from NetworkBehaviour), where the parent object also inherits from NetworkBehaviour.

I would like to organise my SyncVars by utilising Unity's object Hierarchy and spreading them out among multiple children with multiple scripts, rather than having to have all the scripts with SyncVars on the parent.

gitdev481 avatar Sep 26 '20 16:09 gitdev481

I would also find this to be a great addition. Especially for scenarios like an FPS, where one would like to separate the weapon related code from the movement related one into two different components. The current limitation lead to a lot of boiler plate code in order to pass down variables like hasAuthority from the main NetworkBehaviour to normal MonoBehaviours, which contain some encapsulated logic around a specific funcitonality. This also makes triggering commands and RPCs a bit of a hassle, since I have to grab the main NetworkBehaviour call the method there and relay it back to the individual component. It leads to an architecture like this:

GunComponent -- call command --> MainNetworkBehaviour -- call method --> GunComponent
GunComponent -- trigger event --> MainNetworkBehaviour -- call method --> GunComponent

It would be much more convenient and clean in my opinion to not have to interact with the MainNetworkBehaviour and skip that step entirely.

Maybe something like a ChildNetworkBehaviour component could be added, to avoid using multiple NetworkIdentity components, while still allowing for the code to be split up into individual contained parts?

Some prototyping

For a simple setup without too much hassle, something like this could already be a great start:

using Mirror;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

public class ParentNetworkBehaviour : NetworkBehaviour
{
    [Header("Network Settings")]
    [SerializeField] ChildNetworkBehaviour[] childNetworkBehaviours;

    protected void Awake() {
        int index = 0;
        foreach (ChildNetworkBehaviour childNetworkBehaviour in childNetworkBehaviours) {
            childNetworkBehaviour.ParentNetworkBehaviour = this;
            childNetworkBehaviour.childId = index;
            index++;
        }
    }
    public override void OnStartAuthority() {
        base.OnStartAuthority();

        foreach(ChildNetworkBehaviour childNetworkBehaviour in childNetworkBehaviours) {
            childNetworkBehaviour.OnStartAuthority();
        }
    }

    public override void OnStartServer() {
        base.OnStartServer();

        foreach (ChildNetworkBehaviour childNetworkBehaviour in childNetworkBehaviours) {
            childNetworkBehaviour.OnStartServer();
        }
    }

    [Command]
    public void CmdRunCommand(string commandName, int childId) {
        ChildNetworkBehaviour child = childNetworkBehaviours[childId];
        MethodInfo method = child.GetType().GetMethod(commandName);
        method.Invoke(child, new object[] { });
    }

}



using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ChildNetworkBehaviour : MonoBehaviour
{
    public ParentNetworkBehaviour ParentNetworkBehaviour;
    public int childId;
    protected bool hasAuthority { get; private set; }

    public virtual void OnStartAuthority() {
        hasAuthority = true;
    }

    public virtual void OnStartServer() {
        
    }

    public void TriggerCommand(string actionName) {
        ParentNetworkBehaviour.CmdRunCommand(actionName, childId);
    }
}

A command could then be triggered like this from any ChildNetworkBehaviour:

    private void CreateProjectile() {
        TriggerCommand(nameof(CmdInstantiateProjectile));
    }

    public void CmdInstantiateProjectile() {
        GameObject bullet = Instantiate(BulletPrefab, ProjectileSpawnPoint.position, ProjectileSpawnPoint.rotation);
        NetworkServer.Spawn(bullet);
        Debug.Log("Command run");
    }

While there are many flaws (including security issues) like having to assign the ChildNetworkBehaviours via the inspector, using some not so nice child id assignment, etc. It is quite a nice start and workaround while a fully fleged feature like this is still missing.

Kellojo avatar Nov 14 '20 13:11 Kellojo

I see this as the biggest problem when working with Mirror. Can anyone please provide update on the plans/progress on this?

WarlordMSM avatar Jan 08 '22 01:01 WarlordMSM

Yes sadly, mirror has many design problems. I always complain about mirror (also unet) and still it is same. I also had problems when I worked with bigger games they switched to other network systems or wrote own because of this small design problems.

But the best possible solution was for me to use a script just for important commands and rpcs and use the reference in normal Monobehaviour which can be in the child.

So your weapon script would use a reference and use something like networkScript.CmdFire(projectile) and so on.

Sometimes I use this trick if I want to make singleplayer + multiplayer possible.

MaZyGer avatar Jan 11 '22 18:01 MaZyGer

I agree, we need this feature!

lexika979 avatar Jan 12 '22 12:01 lexika979

@vis2k Is this planned to be implemented in the future? A little status update on this would be awesome :)

lexika979 avatar Jan 13 '22 07:01 lexika979

considerations from discord for future reference:

  • imer: GetComponentsInChildren order not deterministic? https://stackoverflow.com/questions/42375242/unity-getcomponentsinchildrent-return-order
  • don't allow runtime removal/adding. just like before.
  • MrG: will also need to decide if we includeInactive and when adding a NB to any object we'll need to search the parent and up for an NI to be present before adding one to that object

miwarnec avatar May 14 '23 04:05 miwarnec

hi everyone. we are working on this now, expect it this month! https://github.com/MirrorNetworking/Mirror/pull/3486

miwarnec avatar May 14 '23 04:05 miwarnec

  • imer: GetComponentsInChildren order not deterministic? https://stackoverflow.com/questions/42375242/unity-getcomponentsinchildrent-return-order

that stackoverflow is from 6 years ago, the GetComponentsInChildren docs now say what the order will be:

This method checks the GameObject on which it is called first, then recurses downwards through all child GameObjects using a depth-first search, until it finds a matching Component of the type T specified.


For what it is worth, this is the solution we have in Mirage:

https://github.com/MirageNet/Mirage/blob/6476e1d4812312d42bc3270ec64a5c1a18028644/Assets/Mirage/Runtime/NetworkIdentity.cs#L287-L316

and NetworkBehaviour.Identity uses GetComponentInParent to find the first parent. This avoids problems where someone has a NetworkIdentity in a child object.

James-Frowen avatar May 17 '23 12:05 James-Frowen

hi everyone. we are working on this now, expect it this month! #3486

Any news? I see an error is still thrown if child NetworkIdentities are in a scene. https://github.com/MirrorNetworking/Mirror/commit/f84a385212196752a6a7563610170dfeff00b4ce

There should only be one NetworkIdentity, and it must be on the root object. Please remove the other one.

LoneDev6 avatar Jul 07 '23 14:07 LoneDev6

@LoneDev6 this issue was opened about child NetworkBehaviours, which we completed a while ago. closing this

miwarnec avatar Oct 31 '23 21:10 miwarnec