Unet可产生的预制件不在客户端上跟随Player预制件

时间:2018-10-18 20:36:01

标签: unity3d networking game-development unity3d-unet

我遇到了枪不粘在玩家身上的问题。它仅对客户有效。正如您在屏幕上看到的那样,枪支位置适合主机(右侧播放器)。 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,也许我听不懂。

我知道枪的位置是错误的,但是在解决此问题后我会更改它。现在,我只希望它在客户端和主机端都坚持播放器。

enter image description here

1 个答案:

答案 0 :(得分:0)

问题

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分别转移它们,您可以简单地使它成为Player对象本身的子代,例如使用Instantiate(Object original, Vector3 position, Quaternion rotation, Transform parent);在服务器上:

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

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

您所要做的就是再次拥有ClientRpc,以便告诉客户也可以使用以下方法使这把枪成为您的Player的子代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;
}

Update1

有关克服以后连接的客户端问题的信息,请参见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。)


Update2

Update1中的方法可能不起作用,因为执行playetGunNetId时可能尚未设置OnStartPlayer

要解决此问题,您可以将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对于已连接和新连接的客户端都由<input type="text" name="content" id="content"/> 处理。