Unity 网络转换问题 - 客户端在错误位置生成对象
Unity network transform problem - Client spawning objects at wrong location
我正在为 Unity 开发一款类似 Fortnite 的游戏。
玩家生成,可以生成方块来制作 "base"。
一切都在单声道中运行良好,但我在网络方面遇到了一个奇怪的问题。在服务器上,我的播放器可以在客户端上根据 Raycast 生命值完美地生成立方体,即使玩家是 Player 预制件的克隆,生成的对象总是在世界原点结束,而不是 Raycast 生命值 -或者 - 如果我从包含 Raycast 信息的播放器脚本中删除 if (!isPlayLocal) {return;}
,则立方体生成不准确并且没有相应的 material。
我会尝试找出代码,以便我可以将它放在这里,但我想它可能有很多东西。
在生成预制件上选中本地玩家身份验证,并且所有预制件都已在网络管理器中注册。
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);
}
}
}
问题
您正在呼叫
[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
.
对所有必须是客户端的值而不是服务器的值进行此更改。
请注意,网络方法之间可以传递的类型是有限的!你不能通过例如任何组件引用。
The allowed argument types are;
- Basic type (byte, int, float, string, UInt64, etc)
- Built-in Unity math type (Vector3, Quaternion, etc),
- Arrays of basic types
- Structs containing allowable types
- NetworkIdentity
- NetworkInstanceId
- NetworkHash128
- GameObject with a NetworkIdentity component attached.
其他提示
为了可读性和行数,你应该改变一些东西,例如
if (buildModeOn)
{
// weapon.SetActive(false);
crossHairOn = true;
}
else
{
// weapon.SetActive(true);
crossHairOn = false;
}
简单
// weapon.SetActive(!buildModeOn);
crossHairOn = buildModeOn;
并检查 bools 不像
if (isLocalPlayer == false)
而是
if(!isLocalPlayer)
它只是 reads/writes 更容易 ;)
我正在为 Unity 开发一款类似 Fortnite 的游戏。
玩家生成,可以生成方块来制作 "base"。
一切都在单声道中运行良好,但我在网络方面遇到了一个奇怪的问题。在服务器上,我的播放器可以在客户端上根据 Raycast 生命值完美地生成立方体,即使玩家是 Player 预制件的克隆,生成的对象总是在世界原点结束,而不是 Raycast 生命值 -或者 - 如果我从包含 Raycast 信息的播放器脚本中删除 if (!isPlayLocal) {return;}
,则立方体生成不准确并且没有相应的 material。
我会尝试找出代码,以便我可以将它放在这里,但我想它可能有很多东西。
在生成预制件上选中本地玩家身份验证,并且所有预制件都已在网络管理器中注册。
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);
}
}
}
问题
您正在呼叫
[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
.
对所有必须是客户端的值而不是服务器的值进行此更改。
请注意,网络方法之间可以传递的类型是有限的!你不能通过例如任何组件引用。
The allowed argument types are;
- Basic type (byte, int, float, string, UInt64, etc)
- Built-in Unity math type (Vector3, Quaternion, etc),
- Arrays of basic types
- Structs containing allowable types
- NetworkIdentity
- NetworkInstanceId
- NetworkHash128
- GameObject with a NetworkIdentity component attached.
其他提示
为了可读性和行数,你应该改变一些东西,例如
if (buildModeOn)
{
// weapon.SetActive(false);
crossHairOn = true;
}
else
{
// weapon.SetActive(true);
crossHairOn = false;
}
简单
// weapon.SetActive(!buildModeOn);
crossHairOn = buildModeOn;
并检查 bools 不像
if (isLocalPlayer == false)
而是
if(!isLocalPlayer)
它只是 reads/writes 更容易 ;)