Unity 5.1 Networking - Spawn an object as a child for the host and all clients
Asked Answered
T

3

6

I have a player object who can equip multiple weapons. When a weapon is equipped, its transform's parent is set to its hand. I have messed around with this for some time and cannot get this to work for both the host and the client. Right now I am trying to equip the weapon on the server, and tell all the clients to set their parents transforms.

public NetworkInstanceId weaponNetId; 

   [Command]
    void Cmd_EquipWeapon()
    {
        var weaponObject = Instantiate (Resources.Load ("Gun"),
                                        hand.position,
                                        Quaternion.Euler (0f, 0f, 0f)) as GameObject;

        weaponObject.transform.parent = hand;

        NetworkServer.Spawn (weaponObject);

        //set equipped weapon
        var weapon = weaponObject.GetComponent<Weapon> () as Weapon;

        weaponNetId = weaponObject.GetComponent<NetworkIdentity> ().netId;
        Rpc_SetParentGameobject (weaponNetId);
    }

    [ClientRpc]
    public void Rpc_SetParentGameobject(NetworkInstanceId netID)
    {   
        weaponNetId = netId;
    }

And in the update I am updating the weapons transform

    void Update () {

    // set child weapon tranform on clients
    if (!isServer) {
        if (weaponNetId.Value != 0 && !armed) {
            GameObject child = NetworkServer.FindLocalObject (weaponNetId);


            if (child != null) {
                child.transform.parent = hand;
            }
        }
    }

I know this isn't the most optimized way to do this..but right now I am just trying to get this to work any way possible and then work on tweaking it. Seems like it should be a simple task.

Thurstan answered 11/7, 2015 at 17:7 Comment(0)
L
16

We do a similar thing in our multiplayer game. There are a few things you need to do to get this working. Firstly, the concept:

Setting the weapon's parent on the server is trivial, as you have found. Simply set the transform's parent as you would normally in Unity. However, after spawning this object on the server with NetworkServer.Spawn, it will later be spawned on clients in the root of the scene (hierarchy outside of the spawned prefab is not synchronised).

So in order to adjust the hierarchy on the client I would suggest that you:

  • Use a SyncVar to synchronise the netID of the parent object between the server and client.
  • When the object is spawned on the client, find the parent using the synchronised netID and set it as your transform's parent.

Therefore, I would adjust your code to look something like this. Firstly, set the weapon's parent netId before you spawn it. This will ensure that when it is spawned on clients, the netId will be set.

[Command]
void Cmd_EquipWeapon()
{
    var weaponObject = Instantiate (Resources.Load ("Gun"),
                                    hand.position,
                                    Quaternion.Euler (0f, 0f, 0f)) as GameObject;
    weaponObject.parentNetId = hand.netId; // Set the parent network ID
    weaponObject.transform.parent = hand; // Set the parent transform on the server

    NetworkServer.Spawn (weaponObject); // Spawn the object
}

And then in your weapon class:

  • Add a parentNetId property.
  • Mark it as [SyncVar] so that it synchronises between server and client copies.
  • When spawned on a client, find the parent object using the netId and set it to our transform's parent.

Perhaps something like:

[SyncVar]
public NetworkInstanceId parentNetId;

public override void OnStartClient()
{
    // When we are spawned on the client,
    // find the parent object using its ID,
    // and set it to be our transform's parent.
    GameObject parentObject = ClientScene.FindLocalObject(parentNetId);
    transform.SetParent(parentObject.transform);
}
Ldopa answered 14/7, 2015 at 21:4 Comment(5)
i'm attempting to use this method. is there any reason why the netId would change? i'm only setting it, as in the example given, in OnStartClient.Forearm
Can't believe that this is the way to do this.. damn!Telepathist
Why did you revert my edit? I added code highlighting making your code much better to read.Noyade
Didn't mean to mate. I rejected by accident and can't revert now as don't have high enough rep points. Could you submit again? Cheers for the edit :)Ldopa
Good solution but netId is unique accross all clients and server so there might be no need of syncvar. docs.unity3d.com/2018.2/Documentation/ScriptReference/…Luciana
C
6

I found this post really useful but I have a small addendum.

The netId will be on the root of the hierarchy so its useful to know that you can traverse down the hierarchy using transform.Find.

Something like this ..

GameObject parentObject = ClientScene.FindLocalObject(parentNetId);
    string pathToWeaponHolder = "Obj/targetObj";

    transform.SetParent(parentObject.transform.Find(pathToWeaponHolder));
Cosma answered 15/9, 2016 at 19:27 Comment(0)
O
1

After reading Andy Barnard's solution, I came up with this slightly modified solution. Instead of a SyncVar, there is a Client RPC for the server to call any time a NetworkIdentity needs to change parents. This does require the parent to also have a NetworkIdentity (though it need not be a registered prefab).

public void Server_SetParent (NetworkIdentity parentNetworkIdentity) {
    if (parentNetworkIdentity != null) {
        // Set locally on server
        transform.SetParent (parentNetworkIdentity.transform);
        // Set remotely on clients
        RpcClient_SetParent (parentNetworkIdentity.netId, resetTransform);
    }
    else {
        // Set locally on server
        transform.SetParent (null);
        // Set remotely on clients
        RpcClient_SetParent (NetworkInstanceId.Invalid, resetTransform);
    }
}

[ClientRpc]
void RpcClient_SetParent (NetworkInstanceId newParentNetId) {
    Transform parentTransform = null;
    if (newParentNetId != NetworkInstanceId.Invalid) {
        // Find the parent by netid and set self as child
        var parentGobj = ClientScene.FindLocalObject (newParentNetId);
        if (parentGobj != null) {
            parentTransform = parentGobj.transform;
        }
        else {
            Debug.LogWarningFormat ("{0} Could not find NetworkIdentity '{1}'.", gameObject.name, newParentNetId.Value);
        }
    }
    transform.SetParent (parentTransform);
}

These two are part of a NetworkBehavior, which obviously RequiresComponent(typeof(NetworkIdentity)). If you really need this behavior on a client to server, I'd suggest creating a command that passed the NetworkIdentity to the server, which would just call the server's public method. It's one set of network messages more than optimal, but meh.

Oro answered 18/10, 2017 at 16:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.