unity-game-enginegameobjectunity3d-unet

Change material on GameObjects with Unet in Unity


I want to change the material of a GameObject on all clients when I click on it in any client. I am new to UNET and I assume I have a conceptional flaw. So basically what I am trying to do is:

  1. Shoot a ray on the NetworkPlayer to an object in the scene
  2. Send a [Command] from the player
  3. In this [Command] call an [ClientRpc] on the object
  4. In the [ClientRpc]change the material of this object

My player:

using UnityEngine;
using UnityEngine.Networking;

// This script is on my Game Player Prefab
// (removed the cam movement part)
public class CamMovement : NetworkBehaviour
{
    void Update()
    {
        if (!isLocalPlayer)
        {
            return;
        }

        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = cam.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
                CmdNextColor(hit.transform.gameObject);
        } 
    }

    [Command]
    public void CmdNextColor(GameObject hitObject)
    {
        RPC_ColorChange colorChange = hitObject.GetComponent<RPC_ColorChange>();
        if (colorChange != null)
        {
            colorChange.RpcNextColor();
        }
    }
}

My object:

using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

public class RPC_ColorChange : NetworkBehaviour {

    public Material[] material;
    [SyncVar]
    int curColOfThisObject;
    Text text;

    private void Start()
    {
        text = GetComponentInChildren<Text>();
    }


    [ClientRpc]
    public void RpcNextColor()
    {
        if (!isClient)
            return;

        if (material.Length > 0)
        {
            Material curMaterial = this.GetComponent<MeshRenderer>().material;

            curColOfThisObject++;
            if (curColOfThisObject >= material.Length)
                curColOfThisObject = 0;

            curMaterial = material[curColOfThisObject];
        }
    }

    private void Update()
    {
        if (isClient)
        {
            text.text = "new color of this object: " + curColOfThisObject.ToString();
        }
    }

}

What happens is: The text on the object changes to the appropriate color, but the material is never changed. How do I change the material?

Bonus question: If anyone knows a good tutorial on how to concept a UNET game please let me know.


Solution

  • Your problem is that you calculate the value of curColOfThisObject on the client side but at the same time use [SyncVar] for it.

    From the [SyncVar] Docu:

    These variables will have their values sychronized from the server to clients

    -> Don't change the Values on the clients in RpcNextColor but rather already on the server in the CmdNextColor. Otherwise curColOfThisObject will instantly get overwritten with the deafult value which was never changed on the server. I would than pass the value to the clients as parameter in the [ClientRpc] so technically you wouldn't even need the [SyncVar] at all.

    In CamMovement

    [Command]
    public void CmdNextColor(GameObject hitObject)
    {
        RPC_ColorChange colorChange = hitObject.GetComponent<RPC_ColorChange>();
        if (colorChange != null)
        {
            colorChange.NextColor();
            // after calculating a new curColOfThisObject send it to clients (doesn't require [SyncVar] anymore)
            colorChange.RpcNextColor(curColOfThisObject);
        }
    }
    

    In RPC_ColorChange

    // Make the calculation of the value on the server side
    [Server]
    private void NextColor()
    {
        if (material.Length > 0)
        {
            Material curMaterial = this.GetComponent<MeshRenderer>().material;
    
            curColOfThisObject++;
            if (curColOfThisObject >= material.Length)
                curColOfThisObject = 0;
    
            // set the material also on the server
            curMaterial = material[curColOfThisObject];
        }
    }
    
    [ClientRpc]
    public void RpcNextColor(int newValue)
    {
        if (!isClient) return;
    
        // easier to debug if you keep the curColOfThisObject variable
        curColOfThisObject = newValue;
    
        if(newValue=> material.Length)
        {
            Debug.LogError("index not found in material");
            return;
        }
    
        // instead of curColOfThisObject  you could also just use the newValue
        // but this is easier to debug
        curMaterial = material[curColOfThisObject];
    }
    

    If you want to stick to the [SyncVar] you could also completely skip the ClientRpc and make it a hook for the [SyncVar] instead:

    In CamMovement

    [Command]
    public void CmdNextColor(GameObject hitObject)
    {
        RPC_ColorChange colorChange = hitObject.GetComponent<RPC_ColorChange>();
        if (colorChange != null)
        {
            colorChange.NextColor();
        }
    }
    

    In RPC_ColorChange

    [SyncVar(hook = "OnNextColor")]
    private int curColOfThisObject;
    
    // Make the calculation of the value on the server side
    [Server]
    private void NextColor()
    {
        if (material.Length > 0)
        {
            Material curMaterial = this.GetComponent<MeshRenderer>().material;
    
            curColOfThisObject++;
            if (curColOfThisObject >= material.Length)
                curColOfThisObject = 0;
    
            // set the material also on the server
            curMaterial = material[curColOfThisObject];
        }
    }
    
    // This method automatically gets called when the value of
    // curColOfObject is changed to newValue on the server
    private void OnNextColor(int newValue)
    {
        if (!isClient) return;
    
        // easier to debug if you keep the curColOfThisObject  variable
        curColOfThisObject = newValue;
    
        if(newValue=> material.Length)
        {
            Debug.LogError("index not found in material");
            return;
        }
    
        // instead of curColOfThisObject  you could also just use the newValue
        // but this is easier to debug
        curMaterial = material[curColOfThisObject];
    }
    

    Little extra: I would check the existence of the RPC_ColorChange component before I send stuff through the network.

    if (Input.GetMouseButtonDown(0))
    {
        Ray ray = cam.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;
        if (Physics.Raycast(ray, out hit))
        {
            if(hit.GetComponent<RPC_ColorChange>()!=null)
            {
                CmdNextColor(hit.transform.gameObject);
            }
        }
    }
    

    Be aware that you might hit a child or parent instead of the actual object you would like to hit .. so evtl. you'll have to look for the RPC_ColorChange component in the childs or parent using GetComponentInChildren or GetComponentInParent.