Проблема преобразования сети Unity - клиент создает объекты в неправильном месте

Я работаю над игрой в стиле Fortnite для Unity.

Игрок появляется, имеет возможность создавать кубики для создания «базы».

Все работает отлично в моно, но у меня странная проблема с сетью. На сервере мой игрок может создавать кубики идеально в соответствии с хитпоинтом Raycast, а на клиенте, даже если игрок является клоном префаба Player, порожденные объекты всегда либо заканчиваются в мировом происхождении, а не в хитпоинте Raycast - или - если я удалю if (!isPlayLocal) {return;} из сценария игрока, содержащего информацию о Raycast, куб будет появляться неточно и без соответствующего материала.

Я попытаюсь точно определить код, чтобы разместить его здесь, но я предполагаю, что это может быть несколько вещей.

Проверка подлинности локального игрока отключена для сборных элементов спавна, и все префабы были зарегистрированы в диспетчере сети.

введите здесь описание изображения

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

public class BuildingSystemNew : NetworkBehaviour
{

    [SerializeField] private Camera playerCamera; 
    [SerializeField] private GameObject blockTemplatePrefab; 
    [SerializeField] private GameObject blockPrefab; 
    [SerializeField] private Material templateMaterial;  
    [SerializeField] private LayerMask buildableSurfacesLayer;

    private bool buildModeOn = false;
    private bool canBuild = false;
    private bool crossHairOn = false;
    private BlockSystem bSys;
    public Texture2D crosshairImage;
    private int blockSelectCounter = 0;
    private GameObject weapon;
    private Vector3 buildPos;
    private GameObject currentTemplateBlock;  

    private void Start()
    {
        bSys = GetComponent<BlockSystem>();
    }

    private void Update()
    {
        if (isLocalPlayer == false)
            return;  

        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;

        if (Input.GetKeyDown("e"))
        {
            buildModeOn = !buildModeOn;

            if (buildModeOn)
            {
                // weapon.SetActive(false);
                crossHairOn = true;
            }
            else
            {
                // weapon.SetActive(true);
                crossHairOn = false;
            }
        }

        if (Input.GetKeyDown("r"))
        {
            blockSelectCounter++;
            if (blockSelectCounter >= bSys.allBlocks.Count) blockSelectCounter = 0;
        }

        if (buildModeOn)
        {
            RaycastHit buildPosHit;

            if (Physics.Raycast(playerCamera.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0)), out buildPosHit, 10, buildableSurfacesLayer))
            {
                Vector3 point = buildPosHit.point;
                buildPos = new Vector3(Mathf.Round(point.x), Mathf.Round(point.y), Mathf.Round(point.z));
                canBuild = true;
            }
            else
            {
                Destroy(currentTemplateBlock.gameObject);
                canBuild = false;
            }
        }

        if (!buildModeOn && currentTemplateBlock != null)
        {
            Destroy(currentTemplateBlock.gameObject);
            canBuild = false;
        }

        if (canBuild && currentTemplateBlock == null)
        {
            currentTemplateBlock = Instantiate(blockTemplatePrefab, buildPos, Quaternion.identity);
            currentTemplateBlock.GetComponent<MeshRenderer>().material = templateMaterial;
        }

        if (canBuild && currentTemplateBlock != null)
        {
            currentTemplateBlock.transform.position = buildPos;

            if (Input.GetMouseButtonDown(0))
            {
                CmdPlaceBlock();
            }
            else if (Input.GetMouseButtonDown(1))
            {
                CmdDestroyBlock();
            }
        }
    }

    [Command]
    public void CmdPlaceBlock()
    {
        GameObject newBlock = Instantiate(blockPrefab, buildPos, Quaternion.identity);
        Block tempBlock = bSys.allBlocks[blockSelectCounter];
        newBlock.name = tempBlock.blockName + "-Block";
        newBlock.GetComponent<MeshRenderer>().material = tempBlock.blockMaterial;
        NetworkServer.SpawnWithClientAuthority(newBlock, connectionToClient);  
    }

    [Command]
    private void CmdDestroyBlock()
    {
        RaycastHit hit;
        Ray ray = playerCamera.ScreenPointToRay(Input.mousePosition);

        if (Physics.Raycast(ray, out hit)) 
        {
            var objectHit = hit.collider.gameObject;

            if (hit.collider.gameObject.tag == "Block")
            {
                Destroy(objectHit);
            }
        }
    }

    void OnGUI()
    {
        if (crossHairOn == true) 
        {
            float xMin = (Screen.width / 2) - (crosshairImage.width / 2);
            float yMin = (Screen.height / 2) - (crosshairImage.height / 2);
            GUI.DrawTexture(new Rect(xMin, yMin, crosshairImage.width, crosshairImage.height), crosshairImage);
        }
    }
}

person pfbarnet    schedule 29.01.2019    source источник
comment
Вы пробовали провести какое-нибудь исследование? Я недостаточно продвинут, чтобы ответить на этот вопрос, но я знаю, что небольшое исследование имеет большое значение.   -  person    schedule 29.01.2019
comment
Ха-ха, да, я исследовал ...   -  person pfbarnet    schedule 29.01.2019


Ответы (1)


Проблема

Вы звоните

[Command]
public void CmdPlaceBlock()
{
    GameObject newBlock = Instantiate(blockPrefab, buildPos, Quaternion.identity);
    Block tempBlock = bSys.allBlocks[blockSelectCounter];
    newBlock.name = tempBlock.blockName + "-Block";
    newBlock.GetComponent<MeshRenderer>().material = tempBlock.blockMaterial;
    NetworkServer.SpawnWithClientAuthority(newBlock, connectionToClient);
}

без параметра.

Command вызывается на клиенте, но выполняется на сервере

= ›С локальными переменными Сервера!

Так, например, buildPos всегда будет иметь значение по умолчанию 0,0,0 на сервере, поскольку из-за

if(!isLocalPlayer) return;

позже линия

buildPos = new Vector3(Mathf.Round(point.x), Mathf.Round(point.y), Mathf.Round(point.z));

никогда не выполняется на сервере. То же самое относится, например, на blockSelectCounter и, возможно, другие значения, от которых зависит ваш CmdPlaceBlock.


Решение

Вы должны передать значение buildPos вашего клиента (а также все другие значения, которые отличаются на клиенте и сервере) команде сервера, чтобы сервер знал, в каком правильном положении следует разместить новый объект:

//...
    if (Input.GetMouseButtonDown(0))
    {
        CmdPlaceBlock(buildPos);
    }
//...



[Command]
public void CmdPlaceBlock(Vector3 spawnPosition)
{

    GameObject newBlock = Instantiate(blockPrefab, spawnPosition, Quaternion.identity);
    Block tempBlock = bSys.allBlocks[blockSelectCounter];
    newBlock.name = tempBlock.blockName + "-Block";
    newBlock.GetComponent<MeshRenderer>().material = tempBlock.blockMaterial;
    NetworkServer.SpawnWithClientAuthority(newBlock, connectionToClient);

}

Я добавил только пример позиции, зная, что она всегда будет отличаться на клиенте и сервере. Но это также может относиться к другим вашим значениям, например, blockSelectCounter.

Сделайте это изменение для всех значений, которые должны быть значениями клиента, а не сервера.

Обратите внимание, что типы, которые могут передаваться между сетевыми методами, ограничены! Вы не можете пройти, например. любые ссылки на компоненты.

Допустимые типы аргументов:

  • Базовый тип (byte, int, float, string, UInt64 и т. Д.)
  • Встроенный математический тип Unity (Vector3, Quaternion и т. Д.),
  • Массивы базовых типов
  • Структуры, содержащие допустимые типы
  • NetworkIdentity
  • NetworkInstanceId
  • NetworkHash128
  • GameObject с прикрепленным компонентом NetworkIdentity.

Дополнительные подсказки

Для удобочитаемости и количества строк вы должны изменить такие вещи, как, например,

if (buildModeOn)
{
    // weapon.SetActive(false);
    crossHairOn = true;
}
else
{
    // weapon.SetActive(true);
    crossHairOn = false;
}

просто

// weapon.SetActive(!buildModeOn);
crossHairOn = buildModeOn;

и проверить bools не нравится

if (isLocalPlayer == false)

скорее

if(!isLocalPlayer)

Просто легче читать / писать;)

person derHugo    schedule 29.01.2019
comment
Спасибо, в этом есть большой смысл! - person pfbarnet; 30.01.2019