Проблема
Этот метод 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 вы можете просто сделать его дочерним по отношению к самому объекту 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;
}
Update1
Для преодоления проблемы с подключенными позже клиентами см. этот ответ .
Он предлагает использовать [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
.Что-то вроде
// 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
как для уже подключенных, так и для вновь подключенных клиентов.