How do I sync non-player GameObject properties in UNet/Unity5?

17,965

Solution 1

I found my answer. And it was very difficult, as almost every single question, post, example, etc... I could find was about player objects, and not non-player objects.

So, I needed to use the AssignClientAuthority function. Which I had tried a couple of times, but wasn't using it correctly. Here is the functioning C# script to apply to the player:

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

public class Player_Paint : NetworkBehaviour {

    private int range = 200;
    [SerializeField] private Transform camTransform;
    private RaycastHit hit;
    [SyncVar] private Color objectColor;
    [SyncVar] private GameObject objectID;
    private NetworkIdentity objNetId;

    void Update () {
        // only do something if it is the local player doing it
        // so if player 1 does something, it will only be done on player 1's computer
        // but the networking scripts will make sure everyone else sees it
        if (isLocalPlayer) {
            CheckIfPainting ();
        }
    }

    void CheckIfPainting(){
        // yes, isLocalPlayer is redundant here, because that is already checked before this function is called
        // if it's the local player and their mouse is down, then they are "painting"
        if(isLocalPlayer && Input.GetMouseButtonDown(0)) {
            // here is the actual "painting" code
            // "paint" if the Raycast hits something in it's range
            if (Physics.Raycast (camTransform.TransformPoint (0, 0, 0.5f), camTransform.forward, out hit, range)) {
                objectID = GameObject.Find (hit.transform.name);                                    // this gets the object that is hit
                objectColor = new Color(Random.value, Random.value, Random.value, Random.value);    // I select the color here before doing anything else
                CmdPaint(objectID, objectColor);    // carry out the "painting" command
            }
        }
    }

    [ClientRpc]
    void RpcPaint(GameObject obj, Color col){
        obj.GetComponent<Renderer>().material.color = col;      // this is the line that actually makes the change in color happen
    }

    [Command]
    void CmdPaint(GameObject obj, Color col) {
        objNetId = obj.GetComponent<NetworkIdentity> ();        // get the object's network ID
        objNetId.AssignClientAuthority (connectionToClient);    // assign authority to the player who is changing the color
        RpcPaint (obj, col);                                    // usse a Client RPC function to "paint" the object on all clients
        objNetId.RemoveClientAuthority (connectionToClient);    // remove the authority from the player who changed the color
    }
}

!!!Important!!! Each object that you want to affect must have a NetworkIdentity component, and it must be set to LocalPlayerAuthority

So this script is just to change a random color, but you should be able to change the actual stuff to apply this to any change in the materials or anything else you want to network with a non-player object. "Should" being the optimal word - I haven't tried with any other functionality yet.

EDIT - added more comments for learning purposes.

Solution 2

Unity 5.3.2p3 Assign Client Authority to Non Player Objects

For anyone interested in setting this up this is my approach

Client Side OnLocalPlayer component -> Call commands to assign and remove object authority by passing through the objects NetworkInstanceId. You can add any UI to call these methods on this component

Server Side

    [Command]
    void CmdAssignObjectAuthority(NetworkInstanceId netInstanceId)
    {
        // Assign authority of this objects network instance id to the client
        NetworkServer.objects[netInstanceId].AssignClientAuthority(connectionToClient);
    }

    [Command]
    void CmdRemoveObjectAuthority(NetworkInstanceId netInstanceId)
    {
        // Removes the  authority of this object network instance id to the client
        NetworkServer.objects[netInstanceId].RemoveClientAuthority(connectionToClient);
    }  

Client Side 3. Object component ->
OnStartAuthority() - allowed to send commands to server OnStopAuthority() - not allowed to send commands to server

That's all there is to it!

Solution 3

For 2018:

Rather than using "Assign Object Authority",

I really recommend simply using

.SpawnWithClientAuthority

It's really very easy.

In fact it's this simple!

  [Command]
  void CmdPleaseSpawnSomething() {
 
        GameObject p = Instantiate(some_Prefab);
        NetworkServer.SpawnWithClientAuthority(p, connectionToClient);
    }

{In that code, note that "connectionToClient" is magically available with no effort - it means "the client" who called this command.}

On the client (the one you want to "own" the thing), just call CmdPleaseSpawnSomething().

I mean - that's all there is to it, thank goodness.

There's a long clear explanation here:

https://forum.unity.com/threads/assign-authority-to-local-client-gameobject.371113/#post-3592541

Share:
17,965
Michael S
Author by

Michael S

Musician, Composer, Coder/hacker/technologist. I augment acoustic instruments with electronics - through custom software effects and hardware technologies. I have recently begun to harness the power of the web to add to the interactivity of my own work and the work of artists I know.

Updated on June 16, 2022

Comments

  • Michael S
    Michael S almost 2 years

    I'm working on and learning some basics of Unity 5, UNET, and networking. I made a simple 3D game where you go around and change the colors of objects. But I want to make it multiplayer now, and I am having lots of trouble figuring out how to send the changes over the network so all players can see a single player's color change.

    Part of the issue is that it has been difficult to find the answer using the newer UNET networking engine. And sometimes I come across answers that are for the older way.

    So the main question is, how do I network non-player GameObject property changes? Color, shape, size, etc..

    Here is some code I have now - and I've had many different versions so I'll just post the current one:

     using UnityEngine;
     using System.Collections;
     using UnityEngine.Networking;
    
     public class Player_Paint : NetworkBehaviour {
    
         private int range = 200;
         [SerializeField] private Transform camTransform;
         private RaycastHit hit;
         [SyncVar] private Color objectColor;
         [SyncVar] private GameObject objIdentity;
    
         void Update () {
             CheckIfPainting();
         }
    
         void CheckIfPainting(){
             if(Input.GetMouseButtonDown(0)) {
                 if (Physics.Raycast (camTransform.TransformPoint (0, 0, 0.5f), camTransform.forward, out hit, range)) {
                     string objName = hit.transform.name;
                     CmdPaint(objName);
                 }
             }
         }
    
         [ClientRpc]
         void RpcPaint(){
             objIdentity.GetComponent<Renderer>().material.color = objectColor;
         }
    
         [Command]
         void CmdPaint(string name) {
             objIdentity = GameObject.Find (name);  //tell us what was hit
             objectColor = new Color(Random.value, Random.value, Random.value, Random.value);
             RpcPaint ();
         }
     }
    

    I've tried a bunch more solutions, including writing a separate script on the objects whose color I want to change and including [SyncVar] and hook functions. I've also tried Debug.Log on each of the functions I'm expecting to update the objects on the clients and they are executing with the expected data.

    I really don't know what else to do. I feel like it is a VERY simple thing I want to do, but I haven't come across syncing non-player GameObject's in any questions, tutorials, or other resources. Any ideas at all would be helpful, thank you.