Измените материал в GameObjects с помощью Unet в Unity

Я хочу изменить материал GameObject на всех клиентах, когда я нажимаю на него в любом клиенте. Я новичок в UNET и полагаю, что у меня есть концептуальный недостаток. Итак, в основном я пытаюсь сделать следующее:

  1. Направить луч на NetworkPlayer на объект в сцене.
  2. Отправить [Command] от игрока
  3. В этом [Command] вызовите [ClientRpc] на объекте
  4. В [ClientRpc]измените материал этого объекта

Мой плеер:

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();
        }
    }
}

Мой объект:

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();
        }
    }

}

Происходит следующее: текст на объекте меняется на соответствующий цвет, но материал никогда не меняется. Как мне сменить материал?

Дополнительный вопрос: если кто-нибудь знает хорошее руководство по созданию концепции игры UNET, пожалуйста, дайте мне знать.


person Klaus Ullrich    schedule 30.08.2018    source источник
comment
Попробуйте отредактировать sharedMaterial   -  person Massimo Frasson    schedule 31.08.2018
comment
@MassimoFrasson Нет! Это изменит материал для всех объектов, использующих этот материал.   -  person derHugo    schedule 03.09.2018
comment
@derHugo, ты прав! Извините! В любом случае, вы можете загрузить свой полный проект куда угодно?   -  person Massimo Frasson    schedule 03.09.2018
comment
@MassimoFrasson здесь нет, но, например, используя Gitlab или что-то подобное   -  person derHugo    schedule 03.09.2018


Ответы (2)


Ваша проблема в том, что вы вычисляете значение curColOfThisObject на стороне клиента, но в то же время используете для него [SyncVar].

Из [SyncVar] Docu:

Значения этих переменных будут синхронизированы от сервера к клиентам.

-> Не меняйте значения на клиентах в RpcNextColor, а уже на сервере в CmdNextColor. В противном случае curColOfThisObject будет немедленно перезаписан глухим значением, которое никогда не менялось на сервере. Я бы передал значение клиентам как параметр в [ClientRpc], так что технически вам вообще не понадобится [SyncVar].

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];
}

Если вы хотите придерживаться [SyncVar], вы также можете полностью пропустить ClientRpc и вместо этого сделать hook для [SyncVar]:

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];
}

Небольшая дополнительная информация: я бы проверил наличие RPC_ColorChange компонента перед отправкой материалов по сети.

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);
        }
    }
}

Имейте в виду, что вы можете ударить ребенка или родителя, а не реальный объект, который вы хотели бы ударить ... так что evtl. вам нужно будет искать RPC_ColorChange компонент в дочерних или родительских элементах, используя GetComponentInChildren или GetComponentInParent.

person derHugo    schedule 03.09.2018

Это немного противоречит интуиции, что RpcFunctions вызывается после обработки командных функций:

На префабе плеера:

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

            // change color +1 on the clients
            colorChange.RpcNextColor(curColor);

            // assign a SyncVar with the current color
            colorChange.SyncColorVar();
        }
    } 

Теперь функции вызываются в следующем порядке:
1) SyncColorVar ()
2) RpcNextColor ()

Тестовый проект: gitlab.com/KlausUllrich/networkTest

person Klaus Ullrich    schedule 06.09.2018