Unet Spawnable 预制件不遵循客户端的玩家预制件
Unet Spawnable prefab not following Player Prefab on Client Side
我遇到枪不粘在玩家身上的问题。它只发生在客户身上。正如你在屏幕上看到的,枪的位置对主机(右边的玩家)来说很好。 Gun Prefab 具有选中本地玩家权限的网络身份和网络转换,与玩家相同。
这是我的播放器代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class Player : NetworkBehaviour
{
[SerializeField] float speed = 10f;
[HideInInspector] public GameObject playerGun;
public GameObject gunPrefab;
void Update()
{
if (!isLocalPlayer)
{
return;
}
Movement();
if (Input.GetKeyDown(KeyCode.I))
{
CmdGetGun();
}
if (playerGun)
CarryGun();
}
private void Movement()
{
Vector3 position = new Vector3(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")).normalized * Time.deltaTime * speed;
transform.position += position;
MouseMovement();
}
private void MouseMovement()
{
Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
mousePosition.Normalize();
float rotation_z = Mathf.Atan2(mousePosition.y, mousePosition.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0f, 0f, rotation_z);
}
[Command]
public void CmdGetGun()
{
Debug.Log("SPAWNING A GUN");
playerGun = (GameObject)Instantiate(gunPrefab, transform.position, transform.rotation);
NetworkServer.SpawnWithClientAuthority(playerGun, connectionToClient);
}
public void CarryGun()
{
Debug.Log("carring A GUN");
playerGun.transform.position = new Vector3(transform.position.x, transform.position.y, transform.position.z - 1);
playerGun.transform.rotation = transform.rotation;
}
}
我花了几天时间想弄明白。我是 Unity 的新手,尤其是 Unet,也许我有些不明白。
我知道枪的位置是错误的,但是我会在处理这个问题后改变它。现在我只希望它在客户端和主机端都坚持播放器。
问题
此 Command
方法始终在 服务器 上执行 .. 属性 在服务器 上也是如此。
含义:
您永远不会在
中仅在服务器端在客户端设置 playerGun
playerGun = Instantiate ...
因此,由于您的客户端从未获得其 playerGun
值集 CarryGun
永远不会在客户端上执行。
解决方案 1
为避免这种情况,您应该使用 ClientRpc
方法在所有客户端上也设置该值。
[Command]
public void CmdGetGun()
{
Debug.Log("SPAWNING A GUN");
playerGun = (GameObject)Instantiate(gunPrefab, transform.position, transform.rotation);
NetworkServer.SpawnWithClientAuthority(playerGun, connectionToClient);
// Tell the clients to set the playerGun reference.
// You can pass it as GameObject since it has a NetworkIdentity
RpcSetGunOnClients(playerGun);
}
// Executed on ALL clients
// (which does not mean on all components but just the one of
// this synched GameObject, just to be clear)
[ClientRpc]
private void RpcSetGunOnClients (GameObject gun)
{
playerGun = gun;
}
解决方案 2
这是一个替代解决方案,其步骤与上面的步骤基本相同 ,但是 它不再需要 CarryGun
方法和 NetworkTransform
,使得通过节省方法调用和网络带宽,您的代码更高效:
而不是在层次结构的顶层生成枪支到特定的全局位置和旋转
playerGun = (GameObject)Instantiate(gunPrefab, transform.position, transform.rotation);
而不是一直更新它的位置和旋转到玩家的传输并通过 NetworkTransform
s 单独传输它们,你可以简单地使它成为玩家 object 的 child本身使用例如Instantiate(Object original, Vector3 position, Quaternion rotation, Transform parent);
在服务器上:
playerGun = (GameObject)Instantiate(gunPrefab, gunLocalPositionOffset, gunLocalRotationOffset, transform);
这应该始终将其保持在正确的位置,而无需 Update
并始终同步其位置和旋转,并且无需任何进一步的传输方法和值。
您所要做的就是再次拥有一个 ClientRpc
以便告诉客户也让这把枪成为您玩家的 child 使用例如SetParent(Trasnform parent, bool worldPositionStays)
:
playerGun.transform.SetParent(transform, false);
并在必要时应用局部位置和旋转偏移。同样,您可以使用一个值,每个人都可以从服务器访问或将其传递给 ClientRpc
- 您的选择 ;)
所以你的方法现在看起来像
// In order to spawn the gun with an offset later
// It is up to you where those values should come from / be passed arround
// If you crate your Prefab "correctly" you don't need them at all
//
// correctly means: the most top GameObject of the prefab has the
// default values position(0,0,0) and rotation (0,0,0) and the gun fits perfect
// when the prefab is a child of the Player => You don't need any offsets at all
Vector3 gunLocalPositionOffset= Vector3.zero;
Quaternion gunLocalRotationOffset= Quaternion.identity;
[Command]
public void CmdGetGun()
{
Debug.Log("SPAWNING A GUN");
// instantiates the prefab as child of this gameObject
// you still can spawn it with a local offset position
// This will make its position be already synched in the Players own
// NetworkTransform -> no need for a second one
playerGun = (GameObject)Instantiate(gunPrefab, gunLocalPositionOffset, gunLocalRotationOffset, gameobject);
// you wouldn't need this anymore since you won't change the spoition manually
// NetworkServer.SpawnWithClientAuthority(playerGun, connectionToClient);
NetworkServer.Spawn(playerGun);
// Tell the clients to set the playerGun reference.
// You can pass it as GameObject since it has a NetworkIdentity
RpcSetGunOnClients(playerGun);
}
// Executed on all clients
[ClientRpc]
private void RpcSetGunOnClients (GameObject gun)
{
// set the reference
playerGun = gun;
// NetworkServer.Spawn or NetworkServer.SpawnWithClientAuthority doesn't apply
// the hierachy so on the Client we also have to make the gun a child of the player manually
// use the flag worldPositionStays to avoid a localPosition offset
playerGun.transform.SetParent(transform, false);
// just to be very sure you also could (re)set the local position and rotation offset later
playerGun.transform.localPosition = gunLocalPositionOffset;
playerGun.transform.localrotation = gunLocalRotationOffset;
}
更新 1
要解决后来连接的客户端的问题,请参阅。
它建议使用 [SyncVar]
并覆盖 OnStartClient
。采用的版本可能看起来像
// Will be synched to the clients
// In our case we know the parent but need the reference to the GunObject
[SyncVar] public NetworkInstanceId playerGunNetId;
[Command]
public void CmdGetGun()
{
Debug.Log("SPAWNING A GUN");
playerGun = (GameObject)Instantiate(gunPrefab, gunLocalPositionOffset, gunLocalRotationOffset, transform);
// Set the playerGunNetId on the Server
// SyncVar will set on all clients including
// newly connected clients
playerGunNetId = playerGun.GetComponent<NetworkIdentity>().netId;
NetworkServer.Spawn(playerGun);
RpcSetGunOnClients(playerGun);
}
public override void OnStartClient()
{
// When we are spawned on the client,
// find the gun object using its ID,
// and set it to be our child
GameObject gunObject = ClientScene.FindLocalObject(playerGunNetId);
gunObject.transform.SetParent(transform, false);
gunObject.transform.localPosition = gunLocalPositionOffset;
gunObject.transform.localrotation = gunLocalRotationOffset;
}
(注意您仍然需要ClientRpc
用于已连接的客户端。)
更新2
Update1 中的方法可能不起作用,因为在执行 OnStartPlayer
时可能尚未设置 playetGunNetId
。
为了克服这个问题,您可以为我们的 SyncVar
使用 hook method。像
// The null reference might somehow still come from
// ClientScene.FindLocalObject
[SyncVar(hook = "OnGunIdChanged"]
private NetworkInstanceId playerGunNetId;
// This method is executed on all clients when the playerGunNetId value changes.
// Works when clients are already connected and also for new connections
private void OnGunIdChanged (NetworkInstanceId newValue)
{
// Honestly I'm never sure if this is needed or not...
// but in worst case it's just redundant
playerGunNetId = newValue;
GameObject gunObject = ClientScene.FindLocalObject(playerGunNetId);
gunObject.transform.SetParent(transform, false);
gunObject.transform.localPosition = gunLocalPositionOffset;
gunObject.transform.localrotation = gunLocalRotationOffset;
}
现在您甚至不再需要 ClientRpc
,因为已经连接和新连接的客户端的一切都由 hook
处理。
我遇到枪不粘在玩家身上的问题。它只发生在客户身上。正如你在屏幕上看到的,枪的位置对主机(右边的玩家)来说很好。 Gun Prefab 具有选中本地玩家权限的网络身份和网络转换,与玩家相同。 这是我的播放器代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class Player : NetworkBehaviour
{
[SerializeField] float speed = 10f;
[HideInInspector] public GameObject playerGun;
public GameObject gunPrefab;
void Update()
{
if (!isLocalPlayer)
{
return;
}
Movement();
if (Input.GetKeyDown(KeyCode.I))
{
CmdGetGun();
}
if (playerGun)
CarryGun();
}
private void Movement()
{
Vector3 position = new Vector3(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")).normalized * Time.deltaTime * speed;
transform.position += position;
MouseMovement();
}
private void MouseMovement()
{
Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
mousePosition.Normalize();
float rotation_z = Mathf.Atan2(mousePosition.y, mousePosition.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0f, 0f, rotation_z);
}
[Command]
public void CmdGetGun()
{
Debug.Log("SPAWNING A GUN");
playerGun = (GameObject)Instantiate(gunPrefab, transform.position, transform.rotation);
NetworkServer.SpawnWithClientAuthority(playerGun, connectionToClient);
}
public void CarryGun()
{
Debug.Log("carring A GUN");
playerGun.transform.position = new Vector3(transform.position.x, transform.position.y, transform.position.z - 1);
playerGun.transform.rotation = transform.rotation;
}
}
我花了几天时间想弄明白。我是 Unity 的新手,尤其是 Unet,也许我有些不明白。
我知道枪的位置是错误的,但是我会在处理这个问题后改变它。现在我只希望它在客户端和主机端都坚持播放器。
问题
此 Command
方法始终在 服务器 上执行 .. 属性 在服务器 上也是如此。
含义: 您永远不会在
中仅在服务器端在客户端设置playerGun
playerGun = Instantiate ...
因此,由于您的客户端从未获得其 playerGun
值集 CarryGun
永远不会在客户端上执行。
解决方案 1
为避免这种情况,您应该使用 ClientRpc
方法在所有客户端上也设置该值。
[Command]
public void CmdGetGun()
{
Debug.Log("SPAWNING A GUN");
playerGun = (GameObject)Instantiate(gunPrefab, transform.position, transform.rotation);
NetworkServer.SpawnWithClientAuthority(playerGun, connectionToClient);
// Tell the clients to set the playerGun reference.
// You can pass it as GameObject since it has a NetworkIdentity
RpcSetGunOnClients(playerGun);
}
// Executed on ALL clients
// (which does not mean on all components but just the one of
// this synched GameObject, just to be clear)
[ClientRpc]
private void RpcSetGunOnClients (GameObject gun)
{
playerGun = gun;
}
解决方案 2
这是一个替代解决方案,其步骤与上面的步骤基本相同 ,但是 它不再需要 CarryGun
方法和 NetworkTransform
,使得通过节省方法调用和网络带宽,您的代码更高效:
而不是在层次结构的顶层生成枪支到特定的全局位置和旋转
playerGun = (GameObject)Instantiate(gunPrefab, transform.position, transform.rotation);
而不是一直更新它的位置和旋转到玩家的传输并通过 NetworkTransform
s 单独传输它们,你可以简单地使它成为玩家 object 的 child本身使用例如Instantiate(Object original, Vector3 position, Quaternion rotation, Transform parent);
在服务器上:
playerGun = (GameObject)Instantiate(gunPrefab, gunLocalPositionOffset, gunLocalRotationOffset, transform);
这应该始终将其保持在正确的位置,而无需 Update
并始终同步其位置和旋转,并且无需任何进一步的传输方法和值。
您所要做的就是再次拥有一个 ClientRpc
以便告诉客户也让这把枪成为您玩家的 child 使用例如SetParent(Trasnform parent, bool worldPositionStays)
:
playerGun.transform.SetParent(transform, false);
并在必要时应用局部位置和旋转偏移。同样,您可以使用一个值,每个人都可以从服务器访问或将其传递给 ClientRpc
- 您的选择 ;)
所以你的方法现在看起来像
// In order to spawn the gun with an offset later
// It is up to you where those values should come from / be passed arround
// If you crate your Prefab "correctly" you don't need them at all
//
// correctly means: the most top GameObject of the prefab has the
// default values position(0,0,0) and rotation (0,0,0) and the gun fits perfect
// when the prefab is a child of the Player => You don't need any offsets at all
Vector3 gunLocalPositionOffset= Vector3.zero;
Quaternion gunLocalRotationOffset= Quaternion.identity;
[Command]
public void CmdGetGun()
{
Debug.Log("SPAWNING A GUN");
// instantiates the prefab as child of this gameObject
// you still can spawn it with a local offset position
// This will make its position be already synched in the Players own
// NetworkTransform -> no need for a second one
playerGun = (GameObject)Instantiate(gunPrefab, gunLocalPositionOffset, gunLocalRotationOffset, gameobject);
// you wouldn't need this anymore since you won't change the spoition manually
// NetworkServer.SpawnWithClientAuthority(playerGun, connectionToClient);
NetworkServer.Spawn(playerGun);
// Tell the clients to set the playerGun reference.
// You can pass it as GameObject since it has a NetworkIdentity
RpcSetGunOnClients(playerGun);
}
// Executed on all clients
[ClientRpc]
private void RpcSetGunOnClients (GameObject gun)
{
// set the reference
playerGun = gun;
// NetworkServer.Spawn or NetworkServer.SpawnWithClientAuthority doesn't apply
// the hierachy so on the Client we also have to make the gun a child of the player manually
// use the flag worldPositionStays to avoid a localPosition offset
playerGun.transform.SetParent(transform, false);
// just to be very sure you also could (re)set the local position and rotation offset later
playerGun.transform.localPosition = gunLocalPositionOffset;
playerGun.transform.localrotation = gunLocalRotationOffset;
}
更新 1
要解决后来连接的客户端的问题,请参阅
它建议使用 [SyncVar]
并覆盖 OnStartClient
。采用的版本可能看起来像
// Will be synched to the clients
// In our case we know the parent but need the reference to the GunObject
[SyncVar] public NetworkInstanceId playerGunNetId;
[Command]
public void CmdGetGun()
{
Debug.Log("SPAWNING A GUN");
playerGun = (GameObject)Instantiate(gunPrefab, gunLocalPositionOffset, gunLocalRotationOffset, transform);
// Set the playerGunNetId on the Server
// SyncVar will set on all clients including
// newly connected clients
playerGunNetId = playerGun.GetComponent<NetworkIdentity>().netId;
NetworkServer.Spawn(playerGun);
RpcSetGunOnClients(playerGun);
}
public override void OnStartClient()
{
// When we are spawned on the client,
// find the gun object using its ID,
// and set it to be our child
GameObject gunObject = ClientScene.FindLocalObject(playerGunNetId);
gunObject.transform.SetParent(transform, false);
gunObject.transform.localPosition = gunLocalPositionOffset;
gunObject.transform.localrotation = gunLocalRotationOffset;
}
(注意您仍然需要ClientRpc
用于已连接的客户端。)
更新2
Update1 中的方法可能不起作用,因为在执行 OnStartPlayer
时可能尚未设置 playetGunNetId
。
为了克服这个问题,您可以为我们的 SyncVar
使用 hook method。像
// The null reference might somehow still come from
// ClientScene.FindLocalObject
[SyncVar(hook = "OnGunIdChanged"]
private NetworkInstanceId playerGunNetId;
// This method is executed on all clients when the playerGunNetId value changes.
// Works when clients are already connected and also for new connections
private void OnGunIdChanged (NetworkInstanceId newValue)
{
// Honestly I'm never sure if this is needed or not...
// but in worst case it's just redundant
playerGunNetId = newValue;
GameObject gunObject = ClientScene.FindLocalObject(playerGunNetId);
gunObject.transform.SetParent(transform, false);
gunObject.transform.localPosition = gunLocalPositionOffset;
gunObject.transform.localrotation = gunLocalRotationOffset;
}
现在您甚至不再需要 ClientRpc
,因为已经连接和新连接的客户端的一切都由 hook
处理。