Search Unity

Is trying to simulate Network.Instantiate worth the labor ?

Discussion in 'Multiplayer' started by AkilaeTribe, Aug 9, 2010.

  1. AkilaeTribe

    AkilaeTribe

    Joined:
    Jul 4, 2010
    Posts:
    1,149
    Greetings,

    In my thirst for knowledge, I wondered today more seriously if Network.Instantiate could also be done by a RPC buffered (because Network.Instantiate is itself a RPC buffered...that's what they say, at least !).

    So, during two hours, I tried to modify the already existing M2H tutorial, removing the Network.Instantiate line, and trying some odd things.

    Obviously, I must have screwed up somewhere because it do not work (instantiating a simple cube, and allowing only the owner to move it).

    So, the questions are :

    - As the title says, it is worth the try ? If yes, for which reasons ?

    - As far as my tests brought me, I have some reasons to think that the owner of gameObjects instantiated by Instantiate, with similar Network View IDs is...the server.
    Am I right ?
     
  2. appels

    appels

    Joined:
    Jun 25, 2010
    Posts:
    2,687
    not sure what you mean by this but...
    the owner of the gameobject is the one who instantiated it. a server can instantiate a GO and send an RPC to a client telling him to control it.
     
  3. AkilaeTribe

    AkilaeTribe

    Joined:
    Jul 4, 2010
    Posts:
    1,149
    The owner (networkView.owner) of a game object instantiated by function Network.Instantiate is the user who executed the function, yes.

    But, for example :

    Code (csharp):
    1. // go has a Network View
    2. var go : GameObject;
    3.  
    4. @RPC
    5. function NewPlayer (nvid : NetworkViewID) {
    6.     var clone : GameObject = Instantiate (go, transform.position, transform.rotation);
    7.     clone.networkView.viewID = nvid;
    8. }
    9.  
    10. networkView.RPC ("NewPlayer", RPCMode.AllBuffered, Network.AllocateViewID ());
    Who is the owner (networkView.owner) ?
     
  4. appels

    appels

    Joined:
    Jun 25, 2010
    Posts:
    2,687
    i'm not a 100% sure but i think it's still the GO that executes the instantiate that is the owner.
    you can find out by using NetworkViewID.owner.
     
  5. AkilaeTribe

    AkilaeTribe

    Joined:
    Jul 4, 2010
    Posts:
    1,149
    GameObject Spawner
    Has a Network View. State synchronization = Reliable Delta Compressed. Observed = Spawner (ScriptSpawn)
    Has ScriptSpawn component.

    GameObject NetworkObject
    Has a Network View. State synchronization = Reliable Delta Compressed. Observed = NetworkObject (ScriptObject)
    Has ScriptObject component.
    Has Box collider component.
    Has Rigidbody component.

    ScriptSpawn.js
    Code (csharp):
    1. // VARIABLES
    2. public var userPrefab : GameObject;
    3.  
    4. // EVENTS
    5. function OnServerInitialized () // A USER BECOMES SERVER
    6. {
    7.     var name : String = GenerateRandomName ();
    8.     var nvid : NetworkViewID = Network.AllocateViewID ();
    9.     networkView.RPC ("SpawnUser", RPCMode.AllBuffered, name, nvid);
    10. }
    11.  
    12. function OnConnectedToServer () // A USER CONNECTS TO THE SERVER
    13. {
    14.     var name : String = GenerateRandomName ();
    15.     var nvid : NetworkViewID = Network.AllocateViewID ();
    16.     networkView.RPC ("SpawnUser", RPCMode.AllBuffered, name, nvid);
    17. }
    18.  
    19. function OnPlayerDisconnected (player: NetworkPlayer) // A USER DISCONNECTS FROM SERVER
    20. {
    21.     Debug.Log("Clean up after player " + player);
    22.     Network.RemoveRPCs(player);
    23.     Network.DestroyPlayerObjects(player); // DO NOT WORK, MUST FIND SOMETHING ELSE
    24. }
    25.  
    26. function OnDisconnectedFromServer () // A USER STOPS THE CONNECTION
    27. {
    28.     Application.LoadLevel ("Network Ownership Menu");
    29. }
    30.  
    31. // FUNCTIONS RPC
    32. @RPC
    33. function SpawnUser (name : String, nvid : NetworkViewID, info : NetworkMessageInfo)
    34. {
    35.     var clone : GameObject;
    36.     clone = Instantiate (userPrefab, transform.position, transform.rotation);
    37.     clone.name += name;
    38.     clone.networkView.viewID = nvid;
    39.     Debug.Log ("Clone created:" + clone.name);
    40. }
    41.  
    42. // FUNCTIONS
    43. function GenerateRandomName ()
    44. {
    45.     return "Clone_" + Random.Range (1,1000);
    46. }
    ScriptObject.js
    Code (csharp):
    1. private var screenWidth : float;
    2. private var screenHeight : float;
    3.  
    4. private var showText : boolean = false;
    5.  
    6. function Start ()
    7. {
    8.     screenWidth = Screen.width;
    9.     screenHeight = Screen.height;
    10. }
    11.  
    12. function OnMouseEnter ()
    13. {
    14.     showText = true;
    15. }
    16.  
    17. function OnMouseExit ()
    18. {
    19.     showText = false;
    20. }
    21.  
    22. function OnGUI ()
    23. {
    24.     if (showText)
    25.     {
    26.         var mousePos : Vector3;
    27.         mousePos = Input.mousePosition;
    28.        
    29.         var textToDisplay : String;
    30.         textToDisplay = "Object:" + gameObject.name + " // NetworkPlayer:" + networkView.owner + " // Network View ID:" + networkView.viewID;
    31.        
    32.         GUI.Label (Rect (mousePos.x + 15, screenHeight - mousePos.y, 300, 100), textToDisplay);
    33.     }
    34. }
    35.  
    36. function OnSerializeNetworkView (stream : BitStream, info : NetworkMessageInfo)
    37. {
    38.     var position : Vector3;
    39.     var rotation : Quaternion;
    40.    
    41.     if (stream.isWriting)
    42.     {
    43.         position = transform.position;
    44.         rotation = transform.rotation;
    45.         stream.Serialize (position);
    46.         stream.Serialize (rotation);
    47.     }
    48.     else
    49.     {
    50.         stream.Serialize (position);
    51.         stream.Serialize (rotation);
    52.         transform.position = position;
    53.         transform.rotation = rotation;
    54.     }
    55. }
    When a user initialize the server, the prefab NetworkObject (a cube) is instantiated.

    But when a client connects to the server, there are troubles. Unity running in background stay with one cube, the client (windows stand alone) has two cubes.

    ScriptObject shows that the common gameObject in both server and client (at least the cube with same name) do NOT have the same Network View ID.

    What is wrong ?

    The path to simulate Network.Instantiate is really tough (feel free to tell me it is worthless, if it really is the case, I would nearly glady give up !)...
     
  6. AkilaeTribe

    AkilaeTribe

    Joined:
    Jul 4, 2010
    Posts:
    1,149
    After getting strange errors on the stand alone, I deleted it rebuilt and rebuilt it. I renamed the function OnConnectedToServer to OnPlayerConnected. It works (server and client have both two cubes).

    But it do not works when the client is the one who do the buffered RPC call ?! It only works for authoritative servers then ?

    Anyway, I am trying to figure a way to remove these RPC buffered and the object upon disconnection. Network.DestroyPlayerObjects do not work because it is not instantiated by Network.Instantiate...
     
  7. appels

    appels

    Joined:
    Jun 25, 2010
    Posts:
    2,687
    no, it should work in a non auth setup
     
  8. AkilaeTribe

    AkilaeTribe

    Joined:
    Jul 4, 2010
    Posts:
    1,149
    Code (csharp):
    1. // VARIABLES
    2. public var userPrefab : GameObject;
    3.  
    4. // EVENTS
    5. function OnServerInitialized () // A USER BECOMES SERVER
    6. {
    7.     var name : String = GenerateRandomName ();
    8.     var nvid : NetworkViewID = Network.AllocateViewID ();
    9.     networkView.RPC ("SpawnUser", RPCMode.AllBuffered, name, nvid);
    10. }
    11.  
    12. // FUNCTIONS RPC
    13. @RPC
    14. function SpawnUser (name : String, nvid : NetworkViewID, info : NetworkMessageInfo)
    15. {
    16.     var clone : GameObject;
    17.     clone = Instantiate (userPrefab, transform.position, transform.rotation);
    18.     clone.name += name;
    19.     clone.networkView.viewID = nvid;
    20.     Debug.Log ("Clone created:" + clone.name);
    21. }
    22.  
    23. // FUNCTIONS
    24. function GenerateRandomName ()
    25. {
    26.     return "Clone_" + Random.Range (1,1000);
    27. }
    Simpler than that, you can't.

    After getting rid of my stand alone exe AGAIN (seriously, why deleting the previous build seems to debug the project), I end up with something. But why do I get infinite LogWarning messages in the console ?

    I am not even using a custom GUISkin (on my prefab object, there is a GUI.Label using mouse position to display the NetworkPlayer and the NetworkViewID but that's all).
     
  9. AkilaeTribe

    AkilaeTribe

    Joined:
    Jul 4, 2010
    Posts:
    1,149
    I doubt there are people interested, but for future readers...

    Code (csharp):
    1. // VARIABLES
    2. public var userPrefab : GameObject;
    3. public var playerList : ArrayList = new ArrayList ();
    4.  
    5. // EVENTS
    6. function OnServerInitialized () // A USER BECOMES SERVER
    7. {
    8.     var name : String = GenerateRandomName ();
    9.     var nvid : NetworkViewID = Network.AllocateViewID ();
    10.     networkView.RPC ("SpawnUser", RPCMode.AllBuffered, name, nvid);
    11. }
    12.  
    13. //~ function OnPlayerConnected (player : NetworkPlayer) // DO NOT WORK
    14. function OnConnectedToServer () // A USER CONNECTS TO THE SERVER
    15. {
    16.     var name : String = GenerateRandomName ();
    17.     var nvid : NetworkViewID = Network.AllocateViewID ();
    18.     networkView.RPC ("SpawnUser", RPCMode.AllBuffered, name, nvid);
    19. }
    20.  
    21.  
    22. function OnPlayerDisconnected (player : NetworkPlayer) // A USER DISCONNECTS FROM SERVER
    23. {
    24.     Debug.Log ("Clean up after player " + player);
    25.     Network.RemoveRPCs (player);
    26.     networkView.RPC ("DeletePlayerData", RPCMode.All, player);
    27. }
    28.  
    29. function OnDisconnectedFromServer () // A USER STOPS THE CONNECTION
    30. {
    31.     Application.LoadLevel ("Network Ownership Menu");
    32. }
    33.  
    34. // FUNCTIONS RPC
    35. @RPC
    36. function DeletePlayerData (player : NetworkPlayer)
    37. {
    38.     for (var playerData : PlayerData in playerList)
    39.     {
    40.         if (playerData.player == player)
    41.         {
    42.             if (playerData.object)
    43.                 Network.Destroy (playerData.object);
    44.             playerList.Remove (playerData);
    45.         }
    46.     }
    47. }
    48.  
    49. @RPC
    50. function SpawnUser (name : String, nvid : NetworkViewID, info : NetworkMessageInfo)
    51. {
    52.     var player : NetworkPlayer = info.sender;
    53.     var object : GameObject = GenerateCube (name, nvid);
    54.     var playerData : PlayerData = GeneratePlayerData (player, object);
    55.     if (Network.isServer)
    56.         playerList.Add (playerData);
    57. }
    58.  
    59. // FUNCTIONS
    60. function GenerateCube (name : String, nvid : NetworkViewID)
    61. {
    62.     var object : GameObject;
    63.     object = Instantiate (userPrefab, transform.position, transform.rotation);
    64.     object.name += name;
    65.     object.networkView.viewID = nvid;
    66.     Debug.Log ("Cube created:" + object.name);
    67.     return object;
    68. }
    69.  
    70. function GeneratePlayerData (player : NetworkPlayer, object : GameObject)
    71. {
    72.     var newPlayerData : PlayerData = new PlayerData ();
    73.     newPlayerData.player = player;
    74.     newPlayerData.object = object;
    75.     return newPlayerData;
    76. }
    77.  
    78. function GenerateRandomName ()
    79. {
    80.     return "Cube_" + Random.Range (1,1000);
    81. }
    82.  
    83. // CLASS
    84. class PlayerData
    85. {
    86.     var player : NetworkPlayer;
    87.     var object : GameObject;
    88. }
    It works for non authoritative servers. I haven't tried yet for authoritative servers. Except a strange error when a client disconnects :

    It stills works.
     
  10. Eagle32

    Eagle32

    Joined:
    Jun 20, 2010
    Posts:
    89
    I assume you figured it out but, the owner of a NetworkView is the system that used Network.AllocateViewID() to create that views view id.

    So if a client/server calls Network.AllocateViewID() and assigns that view id to a to a NetworkView and then sends an RPC to the other clients/server that tells them to assign that view id to a NetworkView the client/server that called AllocateViewID() to create the view id is the owner.

    With regards to your last error message.

    You can't modify a collection while using an iterator to iterate over it.

    Code (csharp):
    1.  for (var playerData : PlayerData in playerList)
    2.    {
    3.       if (playerData.player == player)
    4.       {
    5.          if (playerData.object)
    6.             Network.Destroy (playerData.object);
    7.          playerList.Remove (playerData); <---- This is no good
    8.       }
    9.    }
    You need to refactor that section so you don't try to remove the playerData from the collection while iterating over it.

    Unity is catching the Exception, but the Exception will be causing the method to stop executing and the playerData won't be being removed.
     
  11. AkilaeTribe

    AkilaeTribe

    Joined:
    Jul 4, 2010
    Posts:
    1,149
    In other words, Network.AllocateViewID also give ownership ? It is not written anywhere...

    Anyway, I made a test.

    Code (csharp):
    1. function OnConnectedToServer () // A USER CONNECTS TO THE SERVER
    2. {
    3.     networkView.RPC ("SendNVID ", RPCMode.Server);
    4. }
    5.  
    6. @RPC
    7. function SendNVID (info : NetworkMessageInfo)
    8. {
    9.     var nvid : NetworkViewID;
    10.     nvid = Network.AllocateViewID ();
    11.     networkView.RPC ("GetNVID ", info.sender, nvid);
    12. }
    13.  
    14. @RPC
    15. function GetNVID (nvid : NetworkViewID)
    16. {
    17.     var name : String = GenerateRandomName ();
    18.     var object : GameObject = GenerateCube (name, nvid);
    19.     networkView.RPC ("SpawnUser", RPCMode.Others, name, nvid);
    20. }
    21.  
    22. @RPC
    23. function SpawnUser (name : String, nvid : NetworkViewID, info : NetworkMessageInfo)
    24. {
    25.     var object : GameObject = GenerateCube (name, nvid);
    26.     if (Network.isServer)
    27.     {
    28.         var player : NetworkPlayer = info.sender;
    29.         var playerData : PlayerData = GeneratePlayerData (player, object);
    30.         playerList.Add (playerData);
    31.     }
    32. }
    Whoever assign the ID first (in my example, the client assign first, then others do), the owner will still be the user who called Network.AllocateViewID.

    You can't modify a collection while using an iterator to iterate over it.

    Should I make a temporary ArrayList, where I stock instead elements to KEEP, and in the end, assign mainList = new ArrayList (temporaryArrayList) , something like that ?
     
  12. Eagle32

    Eagle32

    Joined:
    Jun 20, 2010
    Posts:
    89
    You could do that, or you could use the temporary list to store the items you want to delete. Then after you've finished making the temp list, iterate over that and remove each item in that list from the origional list.

    Or, and I don't know how, or if, you can do this in UnityScript. You can use a Predicate function delegate with the RemoveAll method like so in C#..

    Code (csharp):
    1.  
    2. playerList.RemoveAll(delegate(PlayerData playerData)
    3.         {
    4.             Boolean remove = false;
    5.  
    6.             if (playerData.player == player)
    7.             {
    8.                 if (playerData.go)
    9.                     Destroy(playerData.go);
    10.                 remove = true;
    11.             }
    12.  
    13.             return remove;
    14.         });
     
  13. AkilaeTribe

    AkilaeTribe

    Joined:
    Jul 4, 2010
    Posts:
    1,149
    I ended up using the temp array and assigning method.

    Code (csharp):
    1. function DeletePlayerData (player : NetworkPlayer)
    2. {
    3.     var playerListTemp : ArrayList = new ArrayList ();
    4.     for (var playerData : PlayerData in playerList)
    5.     {
    6.         if (playerData.player == player)
    7.         {
    8.             if (playerData.object)
    9.                 Network.Destroy (playerData.object);
    10.         }
    11.         else
    12.         {
    13.             playerListTemp.Add (playerData);
    14.         }
    15.     }
    16.     playerList = new ArrayList (playerListTemp);
    17. }
    Thanks you for your advice ;)
     
  14. Eagle32

    Eagle32

    Joined:
    Jun 20, 2010
    Posts:
    89
    On this subject... has anyone got any ideas on how to completely duplicate the Network.Instantiate function?

    A spawn method per object type you want to be able to spawn works.. but it's not a generalised solution.

    Network.Instantiate takes an object reference, you can't send that object reference over an rpc call.

    The best I've been able to come up with is making my network instantiate function use the resource path and name and sending that over the network. However this requires anything I want to instantiate over the network be located in the resources folder.

    The only other thing i've come up with would involve some king of lookup tables.
     
  15. AkilaeTribe

    AkilaeTribe

    Joined:
    Jul 4, 2010
    Posts:
    1,149
    I was also asking myself the question. What do you mean by object reference ? An ID (the same given by GetInstanceID), so an int ?

    You mean a list of all objects that can be Instantiate, and their ID ?
     
  16. Eagle32

    Eagle32

    Joined:
    Jun 20, 2010
    Posts:
    89
    I just meant that the Network.Instantiate functions first paramenter (the object to instantiate) is an Object and Object isn't a type you can send over a normal RPC call.

    Basically, you'd need a list of prefabs and some value by which to lookup which prefab you want to instantiate. It would not involve GetInstanceID, that's just a unique number for any instance of an object, not useful for this purpose.

    I'm currently just using resource paths and Resource.Load instead, but I was wondering if anyone had a better solution.
     
  17. bigjhnny

    bigjhnny

    Joined:
    Aug 10, 2010
    Posts:
    19
    Using this method in an authoritative server setup, suppose I have one server, and two clients connected. Here are the events in sequence:

    1. server init
    2. client 1 connects, gets networkView A from server
    3. client 2 connects, gets networkView B from server
    4. server says: networkViewA.RPC("saysomething")
    5. server says: networkViewB.RPC("saysomething")

    Question:

    Since we aren't using buffered RPC, would #4 error on client 2 and #5 error on client 1 since each client has only their own networkview instanced?
     
  18. bigjhnny

    bigjhnny

    Joined:
    Aug 10, 2010
    Posts:
    19
     
  19. AkilaeTribe

    AkilaeTribe

    Joined:
    Jul 4, 2010
    Posts:
    1,149
    I guess there would be errors. I wrote this code to test it only with two applications, without having to deal with the buffer.
     
  20. bigjhnny

    bigjhnny

    Joined:
    Aug 10, 2010
    Posts:
    19
    I'll have to really test this. Either way though, it seems like at least a couple of people in the forum aren't using RPCMode.Others and instead use RPC(network_player) for every player.

    There in theory should be a way to use groups to achieve targeted RPCs. But I think those RPCs would have to be unbuffered as SetSendingEnabled does not work for "queued" RPCs.

    Buffered RPCs would be a lot more useful if SetSendingEnabled has an effect on queued RPCs as well.
     
  21. tbryant

    tbryant

    Joined:
    Aug 12, 2009
    Posts:
    17
    I second this line of inquiry - I'm mostly having problems with buffered RPCs for instantiation, in that objects that have been destroyed still instantiate for new players. (they can't be removed with removeRPC(viewID) I learned - http://forum.unity3d.com/viewtopic.php?t=9338)

    I am about to resort to a look up table, I think, but I am a little annoyed by the prospect. I don't like the complexity of some of the other solutions such as 1. making a spawn / destroy for every object (lots of spread out code) or 2. buffering destroy calls so that they occur on the new players side after they are instantiated instantiated (big RPC buffers) or 3. using player groups to remove specific instantiate calls (bookkeeping, possible limits to group numbers?)

    Any advice before I make a big list of every prefab I might instantiate and reference it across RPCs with an int?
     
  22. cerebrate

    cerebrate

    Joined:
    Jan 8, 2010
    Posts:
    261
    I use the Resources load method. It's the easiest thing to do because the strings you use might as well be the 'object' you're wanting to instantiate.

    If you don't want a bunch of stuff in your resources folder, you can also make a generic 'spawn object' that actually references whatever you need spawned.
     
  23. J_P_

    J_P_

    Joined:
    Jan 9, 2010
    Posts:
    1,027
    What I've been doing is just RPC toward a specific function to spawn that specific object. I have a "spawnExplosive" RPC that always spawns an explosion and assigns the correct networkViewID. I can get code example later if you'd like but it's pretty simple.

    Unless you're specifically talking about buffering it -- I haven't tried because I want to avoid having a growing RPC buffer at all costs. I'm just going to tell the new client to instantiate everything that needs instantiating.

    I've read that we're limited to 32 network groups, so yeah having an individual group for each object doesn't seem viable.
     
    Last edited: Oct 6, 2010