【问题标题】:Unet Spawnable prefab not following Player Prefab on Client SideUnet Spawnable prefab 不跟随客户端的 Player Prefab
【发布时间】:2019-03-23 17:50:24
【问题描述】:

我遇到了 Gun 不粘在 Player 上的问题。它只发生在客户身上。正如您在屏幕上看到的,枪位对主机(右侧的玩家)来说很好。 Gun Prefab 具有检查本地玩家权限的网络身份和网络转换,与玩家相同。 这是我的 Player 代码:

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,也许我不明白。

我知道枪的位置是错误的,但我会在处理完这个问题后改变它。现在我只想让它在客户端和主机端都坚持播放器。

【问题讨论】:

  • 截图是客户端还是主机提供的?
  • @Ryolu 标题写着on Client Side

标签: unity3d networking game-development unity3d-unet


【解决方案1】:

问题

这个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);

除了将它的位置和旋转一直更新到玩家的 tranfroms 并通过 NetworkTransforms 单独传输它们之外,您可以简单地使用例如使其成为 Player 对象本身的子对象。服务器上的Instantiate(Object original, Vector3 position, Quaternion rotation, Transform parent);

 playerGun = (GameObject)Instantiate(gunPrefab, gunLocalPositionOffset, gunLocalRotationOffset, transform);

这应该始终将其保持在正确的位置,而无需 Update 并始终同步其位置和旋转,并且无需任何进一步的传输方法和值。

您所要做的就是再次拥有一个ClientRpc,以便告诉客户也让这把枪成为您的玩家的孩子,例如使用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

如需解决后来连接的客户端问题,请参阅this answer

它建议使用[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

要克服这个问题,您可以将hook method 用于我们的SyncVar。类似的东西

// 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 处理。

【讨论】:

  • 有效!非常感谢。你解释得很清楚,但现在我又遇到了一个问题,每个新连接的玩家都看不到已经连接的玩家的枪粘在他们身上。新玩家看到其他玩家在他们连接之前的最后一个位置开枪。
  • 请看我的更新:OnStartClient 用于连接时有一些行为,SyncVar 用于将值从服务器同步到客户端
  • 感谢您的快速响应。在您的示例中,我遇到了一个错误,无法处理。错误:OnStartClient 中的异常:对象引用未设置为 C:\buildslave\unity\build\Extensions\ 中 UnityEngine.Networking.NetworkIdentity.OnStartClient () [0x0008d] 的 Player.OnStartClient () [0x0000e] 处的对象实例Networking\Runtime\NetworkIdentity.cs:371 UnityEngine.Networking.NetworkIdentity:UNetStaticUpdate()
  • 你能设置断点并调试它吗? playerGunNetId 对象可能是在 Player 对象之后生成的,因此在客户端启动时还不可用。不幸的是,我现在不知道如何解决这个问题,但是你必须“等待”直到 NetworkServer 也生成了 gun 对象。也许你可以使用协程(IEnumerator)来等待一些秒/帧等
  • 查看我的 Update2(我只是在我的智能手机上破解了这个,但是)我想我找到了一个可以安全地用于新连接的解决方案。
猜你喜欢
  • 2021-10-22
  • 2022-09-29
  • 1970-01-01
  • 2019-10-22
  • 1970-01-01
  • 2018-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多