【问题标题】:Unity network transform problem - Client spawning objects at wrong locationUnity 网络转换问题 - 客户端在错误位置生成对象
【发布时间】:2019-06-22 16:55:44
【问题描述】:

我正在为 Unity 开发一款 Fortnite 风格的游戏。

玩家生成,有能力生成立方体来建立一个“基地”。

在单声道中一切正常,但我遇到了一个奇怪的网络问题。在服务器上,我的玩家可以在客户端上根据 Raycast 生命值完美地生成立方体,即使玩家是 Player 预制件的克隆,生成的对象也总是在世界原点结束,而不是 Raycast 生命值 -或者 - 如果我从包含 Raycast 信息的播放器脚本中删除 if (!isPlayLocal) {return;},立方体会不准确地生成并且没有相应的材料。

我会尝试找出代码,以便将其放在这里,但我想这可能是很多事情。

本地播放器身份验证在生成预制件上被选中,并且所有预制件都已在网络管理器中注册。

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

【问题讨论】:

  • 您是否尝试过进行任何研究?我还不够先进,无法回答这个问题,但我知道一点点研究会有很长的路要走。
  • 哈哈是的,我研究过了……

标签: unity3d transform multiplayer raycasting unity3d-unet


【解决方案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,但在服务器上执行

=> 带有 Server 的局部变量!

例如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、四元数等),
  • 基本类型数组
  • 包含允许类型的结构
  • 网络身份
  • NetworkInstanceId
  • NetworkHash128
  • 附加了 NetworkIdentity 组件的游戏对象。

其他提示

为了可读性和行数,您应该更改例如

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

简单

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

并检查不喜欢的布尔值

if (isLocalPlayer == false)

而是

if(!isLocalPlayer)

它只是读/写更容易;)

【讨论】:

  • 谢谢,这似乎很有道理!
猜你喜欢
  • 1970-01-01
  • 2019-06-13
  • 2018-02-03
  • 2020-12-15
  • 1970-01-01
  • 1970-01-01
  • 2019-03-17
  • 1970-01-01
  • 2018-02-24
相关资源
最近更新 更多