What problems could be causing parameters passed to [Command] to become null?

I seemed to have a weird problem where a GameObject becomes null when passed to a [Command].

public void CmdFunction(GameObject obj){
    //obj == null
    //Some other stuffs that can't be done when obj is null.
}

I went to see what could be the issue of why “obj” is null, when it is clearly not. As in, I’m passing in a GameObject that was instantiated immediately before calling the [Command] function.

I only know a few things that could happen:

  • When the game object running the script does not have local player authority.
  • When Instantiate() fails.
  • When the server never receives the message package sent from the client.

#1 seems to be the most likely reason. So I went back to check if I set all of my prefabs to have “Local Player Authority”. Then I wondered if Prefabs with the root GameObject containing NetworkIdentity, a few child GameObject without NetworkIdentity, then instantiating the root GameObject, and add NetworkIdentity component to each of the children, would that causes the parameter, “obj” to become null?

And yes, I understand that Prefabs where the root GameObject has a NetworkIdentity with Local Player Authority set to true will be spawned, along with the children. But the thing is, does the player have Local Player Authority on the child GameObjects in the Prefab where only the root GameObject has NetworkIdentity with Local Player Authority set to True?

The yellow line shown below is where the execution of the script is paused using breakpoints and stepping over.

Does anyone know what could cause this?

I could be totally wrong but I think you’re only allowed to send simple data types in commands, like vectors and ints and floats etc.

I think you could instead have that prefab referenced in the script and just use the command to instantiate it, I don’t really see a reason you would need to send the object over a network.

If you have an object on both and need to reference it when it is already instantiated, you can store it’s net ID and send that over the network using commands and rpc’s.

You are allowed to send GameObjects, according to the manual.

But it never say anything about Parent-Child GameObjects where combinations of NetworkIdentities with Local Player Authority enabled that are tacked on via Scripting, using AddComponent().

Ah, sorry for not being more helpful, only thing I can think is simplify and try running your command with just a simple empty gameobject and make sure your command is working with that, and then go from there to find out what’s causing it to become null?

I tried doing this in a test project, and the list given in the first post is what the test project tells me. However, Parent-Child GameObject with NetworkIdentities added to them as components, may give undefined behaviors that causes the prefab to unsuccessfully be referenced in the [Command].

I’m still experimenting, but be damned this bug is so error prone with no warnings/errors.

It turns out I need to add a 4th reason why this problem may occur:

  1. When a Prefab with NetworkIdentity is registered, and then you add a child GameObject, you will not be able to spawn Prefab without receiving the error, saying Object Asset ID is invalid.

I do not have a clue on how to resolve this, but it seems that you can add a NetworkIdentity to the child GameObject, set Local Player Authority to true, and resolve the Object Asset ID. I believed this is undefined behavior, or unintended behavior, since the end result is the prefab having all children to have NetworkIdentity, even though only the root GameObject is allowed to have NetworkIdentity.

My conclusion is if you encountered the following 2 similar error messages side by side:

  • Could not register ‘Sphere’ since it contains no NetworkIdentity component UnityEngine.Networking.NetworkManagerHUD:OnGUI()

  • Trying to send command for object without authority. UnityEngine.Networking.NetworkBehaviour:SendCommandInternal(NetworkWriter, Int32, String)
    asd:CallCmdSpawn() asd:Start() (at Assets/asd.cs:9)

It may have been an undefined behavior not intended for registering a prefab.

Another issue is that the length of SyncLists are sometimes 0 even when you have added an item to the SyncList using [Command] when the function is called from a LAN Host. When passing the supposed “item” in the SyncLists via [Command], the parameter will be null, because the “item” is not “synced” with the SyncLists on other clients.

It’s so confusing, but it did happen, and the Inspector isn’t updating the SyncLists to reflect upon the changes. I don’t know why at this moment.

Updating this thread again. It’s like I’m going down a rabbit hole of some sort…

Before continuing, I must say SyncLists have this unintended behavior of not entirely following what the Unity Manual would say. The Unity Manual says SyncLists are updated when the server updates them, and the updated data will sync the clients to the server. If you tried simplifying this meaning, you will have a bad time…

I made a few commits of code on my repository, where it is used for fixing the SyncListStruct not being able to add new items to the list, and have them show up on the client side. Sometimes it wouldn’t show up on the server side for some reasons, so it was investigated.

Strangely, the best way to come up with this is to use a mix of [ClientRpc] and [Command] to sync the action of adding new items to the SyncListStruct.

More findings need to be conducted, but this is what I know so far.

The follow code is conducted in a completely brand new project:

using UnityEngine;
using UnityEngine.Networking;

public struct A {
    public GameObject obj;
}

public class ASyncList : SyncListStruct<A> {
}

public class PrefabCode : NetworkBehaviour {
    public ASyncList list = new ASyncList();
    public GameObject prefab;

    public void Start() {
        if (!this.hasAuthority) {
            return;
        }
        CmdAdd();
    }

    [Command]
    public void CmdAdd() {
        A a = new A();
        a.obj = NetworkBehaviour.Instantiate<GameObject>(this.prefab);
        a.obj.name = this.isServer ? "Server" : "Client";
        NetworkServer.SpawnWithClientAuthority(a.obj, this.connectionToClient);
        this.list.Add(a);
    }
}

When this code is attached to a player prefab (set in the NetworkManager) as a component, and a separate prefab is set to this script, once the script executes, the SyncList are synced across both the host and the client.

I tried using similar code pattern in a bigger project (not some test projects or empty ones), but it fails immediately the moment the client connects to the host.

After further investigation, this is what I come up with that makes the SyncLists in my bigger project:

public class NewSpawner : NetworkBehaviour {
    public GameObject newGameUnitPrefab;
    public NetworkConnection owner;
    public SplitGroupSyncList splitList = new SplitGroupSyncList();
    public MergeGroupSyncList mergeList = new MergeGroupSyncList();
    public UnitsSyncList unitList = new UnitsSyncList();
    public UnitsSyncList selectedList = new UnitsSyncList();

    [Command]
    public void CmdAddUnit(GameObject obj, GameObject spawner) {
        NewSpawner newSpawner = spawner.GetComponent<NewSpawner>();
        if (newSpawner != null) {
            newSpawner.unitList.Add(new NewUnitStruct(obj));
            Debug.Log("Finished adding a new item to UnitList.");
        }
    }

    [ClientRpc]
    public void RpcAdd(GameObject obj, GameObject spawner) {
        if (this.hasAuthority) {
            CmdAddUnit(obj, spawner);
        }
    }

    [Command]
    public void CmdInitialize(GameObject spawner) {
        Debug.Log("Creating a new game object.");
        GameObject gameUnit = MonoBehaviour.Instantiate<GameObject>(this.newGameUnitPrefab);
        Debug.Log("Setting the game object to the parent, NewSpawner.");
        gameUnit.transform.SetParent(this.transform);
        Debug.Log("Setting the game object position to be at NewSpawner.");
        gameUnit.transform.position = this.transform.position;

        Debug.Log("Network spawning the game object.");
        NetworkServer.SpawnWithClientAuthority(gameUnit, this.connectionToClient);

        RpcAdd(gameUnit, spawner);

        RpcFilter();
    }

    [ClientRpc]
    public void RpcFilter() {
        NewGameUnit[] units = GameObject.FindObjectsOfType<NewGameUnit>();
        NewSpawner[] spawners = GameObject.FindObjectsOfType<NewSpawner>();
        for (int i = 0; i < spawners.Length; i++) {
            if (spawners[i].hasAuthority) {
                if (units[i].hasAuthority) {
                    units[i].transform.SetParent(spawners[i].transform);
                }
            }
            else {
                if (!units[i].hasAuthority) {
                    units[i].transform.SetParent(spawners[i].transform);
                }
            }
            continue;
        }
    }

    public void Start() {
        if (!this.hasAuthority) {
            return;
        }

        NetworkIdentity spawnerIdentity = this.GetComponent<NetworkIdentity>();
        if (!spawnerIdentity.localPlayerAuthority) {
            spawnerIdentity.localPlayerAuthority = true;
        }
        this.owner = this.isServer ? spawnerIdentity.connectionToClient : spawnerIdentity.connectionToServer;
        Debug.Log("This is " + (this.isServer ? " Server." : " Client."));

        if (this.minimapCamera == null) {
            GameObject obj = GameObject.FindGameObjectWithTag("Minimap");
            if (obj != null) {
                this.minimapCamera = obj.GetComponent<Camera>();
                if (this.minimapCamera == null) {
                    Debug.LogError("Failure to obtain minimap camera.");
                }
            }
        }

        if (Camera.main.gameObject.GetComponent<PostRenderer>() == null) {
            PostRenderer renderer = Camera.main.gameObject.AddComponent<PostRenderer>();
            renderer.minimapCamera = this.minimapCamera;
        }

        CmdInitialize(this.gameObject);
    }
}

In short, I would have to resort to calling on a mix of [ClientRpc] and [Command] to make sure the SyncLists are updated and synchronized on both the host and the client. It’s a mixed bag of spaghetti code, where it requires precise timing, but it sort of works.

I’m, however, not 100% positive it works. More testing is required, and I do hope a Unity staff could come in and share their side of information with me.