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:
[Command]
from the player[Command]
call an [ClientRpc]
on the object[ClientRpc]
change the material of this objectMy 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.
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
.