Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

"copy" an object to clients / instantiate only visuals

Discussion in 'Multiplayer' started by Tom163, Apr 1, 2011.

  1. Tom163

    Tom163

    Joined:
    Nov 30, 2007
    Posts:
    1,290
    In my current project, the server runs all physics, collision detection, etc. etc. - so all I want on the clients is a visual representation of the objects and the scripts I need to run RPC calls.

    There must be a better way than using "if (Network.isServer)" everywhere, destroying components in OnNetworkInstantiate() and such.

    But using Network.Instantiate() on a sub-object containing only the mesh renderer also gives me a copy on the server, where I want the original to remain. Besides, it wants prefabs, not existing objects.

    Is there no way to "copy" an existing object to the network clients?

    Specific example: I have a spaceship with all its internal systems, AI logic, etc. - all that should only run once, on the server. Clients see the mesh, but never interact with it - if a client fires a weapon, that's an RPC call and the collision detection is done on the server, which then, say, network.destroys the ship and network.instantiates an explosion.

    I can't be the first one to have this problem. So my hopes that someone here has an elegant solution.
     
  2. PrimeDerektive

    PrimeDerektive

    Joined:
    Dec 13, 2009
    Posts:
    3,090
    To my knowledge, that's much the only way to do it (Network.Instantiate the enemy, disable the AI script and collider if !Network.isServer in OnNetworkInstantiate()).

    The only ways you can communicate are RPC's and serialization, so I'm not sure how else you would do it. The only way to copy an existing object to clients without instantiating it would be if both the server and clients had an existing "pool" of ai ships offscreen and you just moved them into place with RPC calls, I guess.
     
  3. Tom163

    Tom163

    Joined:
    Nov 30, 2007
    Posts:
    1,290
    My current solution is this, in case anyone else has the same problem, or wants to improve on it:

    This script is attached to all objects that need to have a "network ghost":
    Code (csharp):
    1.  
    2.  
    3. var Ghost:GameObject;
    4. var RPCGroup:int = 7;
    5.  
    6. function Start() {
    7.     var MyGhost = Network.Instantiate(Ghost, transform.position, transform.rotation, RPCGroup);
    8.     var MyGhostBehaviour = MyGhost.AddComponent("Ghost Behaviour");
    9.     MyGhostBehaviour.CopyTarget = transform;
    10.     MyGhostBehaviour.SetName(gameObject.name);
    11. }
    12.  
    and this is the "Ghost Behaviour" script:
    Code (csharp):
    1.  
    2.  
    3. var CopyTarget:Transform;
    4.  
    5. function Update() {
    6.     if (CopyTarget) {
    7.         transform.position = CopyTarget.position;
    8.         transform.rotation = CopyTarget.rotation;
    9.     }
    10. }
    11.  
    12. function SetName(newname:String) {
    13.     networkView.RPC("NetworkSetName", RPCMode.All, newname);
    14. }
    15. @RPC
    16. function NetworkSetName(newname:String) {
    17.     gameObject.name = newname;
    18. }
    19.  
    because - surprise - Unity networking uses the local transform, not the global one. So if you just parent the ghost to your real object on the server and think that its movement will carry over to the clients, you'll find it doesn't. So you explicitly need to set it, yeah for intuitive! Sorry, that had to be out, this is the first time I'm underwhelmed by Unity.

    Anyways, in this setup the actual object (that only exists server-side) has all the behaviours, colliders, rigidbody, etc. while the only thing the ghost has is the mesh renderer.

    So far, it works for me, but I'm really surprised there isn't a better way to do something that I can't believe isn't basically the most common case.
     
  4. PrimeDerektive

    PrimeDerektive

    Joined:
    Dec 13, 2009
    Posts:
    3,090
    I'm a bit confused as to when this would be more useful than just Network.Instantiating the same object for both the host and the clients and just disabling the components and features you don't want to be running on the "ghost" versions on the clients; i would think managing separate objects would be more cumbersome.
     
  5. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    I agree with Legend411.... I just disable all non-needed elements at instantiation on clients. Takes almost no overhead on client machine and keeps everything nice and clean.
     
  6. Tom163

    Tom163

    Joined:
    Nov 30, 2007
    Posts:
    1,290
    It also means troubleshooting hell if you add components and forget to add them to the "kill this on clients" list. And if you use something like UnitySteer, plus some AI code, plus some visual effects, you easily have 10+ components on your objects. And you still have to set up all the RPC communication.
    It's not as if I hadn't tried that approach, in fact, here's the script I wrote for that approach:

    Code (csharp):
    1.  
    2. var ComponentsToDisableOnClients:Component[];
    3.  
    4. function OnNetworkInstantiate (info : NetworkMessageInfo) {
    5.     if (Network.isClient) {
    6.         for (var Component in ComponentsToDisableOnClients) {
    7.             Destroy(Component);
    8.         }
    9.     }
    10. }
    11.  
    The approach I now have is certainly not perfect. I'm still unhappy with it, I would still think it can't possibly be this complicated to tell Unity "the client should SEE this, but nothing else", but apparently it is.
     
  7. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    If all you want to do is see the GO on the clients, could you just have this:
    OnNetworkInstantiate()

    {
    if (network.isclient)
    {
    DisableAllComponentsButTheMeshRenderer();
    }
    }

    You could put this in a class:

    class GhostObject : Monoobject (or whatever the base unity object is, I forget)
    {
    void OnNetworkInstantiate()
    {
    if client, disable all but mesh renderer.
    }
    }

    Then, on your future objects, just inherit from GhostObject in your script instead of the mono object.
    Would that work?
     
  8. Tom163

    Tom163

    Joined:
    Nov 30, 2007
    Posts:
    1,290
    That's an interesting idea. I'll have to check if an equivalent of the transform-children trick works to get all components of an object.