[UNET 5.2] assign authority of a scene object to a client at start

Hello,

I have a script that must run a client to compute some value, which must then be sent to another client. This script is attachted to a scene object, which is on the scene from the very beginning, and I do not see any reason to Spawn it at runtime.

I am trying to sync the computed values using both a [Command] to update the value from the first client to the server, and [SyncVar]s to sync the variables to the second client.

The problem is that I have an error telling me “trying to send command for object without authority”.

Here’s my code :

using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using UnityEngine.UI;

public class myClass : NetworkBehaviour
{
    [SyncVar]
    private int var1 = 0;
    private int var2 = 0;
    [SyncVar]
    private string var3 = "";
    [SyncVar]
    private float var4 = 0f;
    public override void OnStartServer()
    {
        // disable client stuff

        // switch off the GO if not necessary
        if (false) // some test here
        {
            gameObject.SetActive(false);
        }

    public override void OnStartClient()
    {
        if (true) // some test
        {
            Debug.Log("Will send Asking for Authority");
            CmdAssignAuthority();
        }
        else if (!true) // some test
            gameObject.SetActive(false);
    }

	// Update is called once per frame
	void Update () {
        // Computation
        if (true) // some test, including test which client is it
        {
			var1 = 1;
			var2 = 2;
			var3 = "3";
			var4 = 4;

			CmdUpdateVars(var1, var2);
        }
	}
    
    [Command]
    private void CmdUpdateVars(int v1, float v4)
    {
        
        var1 = v1;
		var4 = v4;

        v3 = (test) ? v1 : 0;
    }

    [Command]
    private void CmdAssignAuthority()
    {
        GetComponent<NetworkIdentity>().AssignClientAuthority(connectionToClient);
        Debug.Log("CmdAssignAuthority to " + connectionToClient.hostId);
    }
}

The attached NetworkIdentity have localPlayerAuthority set to true.

Thanks in advance !

Well, I was trying the new Networking as I did not manage to this with the old one, used in my current project (which is the continuation of an existing project). But I finally find a solution with the old Networking system.

I’ll start with what I have finally done to do so, and at the end of my anwser, I’ll tell you how I would do with UNET 5.2.

========== OLD NETWORKING SYSTEM ==========

I did not try to simplify it nor searched what was THE modification that did the trick, but here’s what I have done :

  • I have change the OnSerializeNetworkView() function this way (it was probably THE modification, but not sure) :

    // -------------- old version --------------- //
    protected void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
    {
        if (stream.isWriting)
        {
            // send variables
        }
        else
        {
            // receive variables
        }
    }
    
    // -------------- new version --------------- //
    protected void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
    {
        if (stream.isReading)
        {
            // receive variables
        }
        if (stream.isWriting)
        {
            // send variables
        }
    }
    

As you can see, the minor change is NOT relying on a “else” to know if you are writing or reading.
What I think is that the server must first receive the new values from the good client, update them in its own script, THEN send them to the other clients.
Having the “else” prevents the server to both read and write. By default it was always writing.

Other modifications :

  • I changed my object into a Prefab

  • I instantiate the prefab this way , in an “applicationController” script :

    void Start()
    {
    // do stuff
    if(IAmTheGoodClient){
    StartCoroutine(InstantiateMyScript());
    }
    // do stuff
    }

    private IEnumerator InstantiateMyScript()
    {
    while (Network.connections.Length == 0) // Not connected to server
    {
    yield return new WaitForSeconds(1f);
    }

      GameObject myScript = Network.Instantiate(myPrefab, Vector3.zero, Quaternion.identity, 1) as GameObject;
      GetComponent<NetworkView>().RPC("RPCAttachVariablesForMyScript", RPCMode.AllBuffered);
      myScript.GetComponent<MyScript>().Init();
    

    }

This way I am sure of who owns it.

On a second though, I’m pretty sure I could have let the script on a scene object instead of putting it in a prefab, and just do something like this in it:

void OnConnectedToServer()
    {
        if (IAmTheGoodClient) { SyncID(); }
    }

public void SyncID()
{
     NetworkViewID id = Network.AllocateViewID();
     GetComponent<NetworkView>().RPC("RPCSyncID", RPCMode.AllBuffered, new object[] { id });
}

[RPC]
public void RPCSyncID(NetworkViewID id)
{
     GetComponent<NetworkView>().viewID = id;
}

========== UNET 5.2 ==========

I did not tried with UNET 5.2 but here’s my thought about how I would try next.

  • First thing is that I would create a player (what I did not want to do at first) because in the end, if you have client which have particular role, it can be seen as different player.

So my player prefab object would contains the variable I use to recognize which client is it, and also informations about scene object it must managed / have authority on it.

As my player object will have right on itself, it will be able to send [Command] to the server !

  • So you add this kind of code in a script of the player GameObject :

     public override void OnStartClient()
     {
         if (IOwnSomething) 
         {
         	CmdAssignAuthority(netID.Value);
         }
     }
             
     [Command]
     private void CmdAssignAuthority(uint netID)
     {
     	NetworkServer.FindLocalObject(netID).AssignClientAuthority(connectionToClient);
     }
    

You could also create your own NetworkManager and do similar thing by overriding NetworkManager.OnClientConnect() for instance.

Finally, you could do something even more similar to what works for me currently : change your scene object into prefab, find a way to use NetworkServer.SpawnWithClientAuthority().

The problem is that I don’t think of an easy way to do so without creating a player, as the main issue is to know which client must own the scene object ; that’s the reason why the Instantiate / Spawn must be launched / commanded by the client. But here we are in first problem, which is the client would not be able to launch a [Command] from the applicationController script …

You could use a message from the client instead of a command.