Is trying to simulate Network.Instantiate worth the labor ?

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 ?

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.

The owner (networkView.owner) of a game object instantiated by function Network.Instantiate is the user who executed the function, yes.

But, for example :

// go has a Network View
var go : GameObject;

@RPC
function NewPlayer (nvid : NetworkViewID) {
    var clone : GameObject = Instantiate (go, transform.position, transform.rotation);
    clone.networkView.viewID = nvid;
}

networkView.RPC ("NewPlayer", RPCMode.AllBuffered, Network.AllocateViewID ());

Who is the owner (networkView.owner) ?

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.

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

// VARIABLES
public var userPrefab : GameObject;

// EVENTS
function OnServerInitialized () // A USER BECOMES SERVER
{
	var name : String = GenerateRandomName ();
	var nvid : NetworkViewID = Network.AllocateViewID ();
	networkView.RPC ("SpawnUser", RPCMode.AllBuffered, name, nvid);
}

function OnConnectedToServer () // A USER CONNECTS TO THE SERVER
{
	var name : String = GenerateRandomName ();
	var nvid : NetworkViewID = Network.AllocateViewID ();
	networkView.RPC ("SpawnUser", RPCMode.AllBuffered, name, nvid);
}

function OnPlayerDisconnected (player: NetworkPlayer) // A USER DISCONNECTS FROM SERVER
{
	Debug.Log("Clean up after player " + player);
	Network.RemoveRPCs(player);
	Network.DestroyPlayerObjects(player); // DO NOT WORK, MUST FIND SOMETHING ELSE
}

function OnDisconnectedFromServer () // A USER STOPS THE CONNECTION
{
	Application.LoadLevel ("Network Ownership Menu");
}

// FUNCTIONS RPC
@RPC
function SpawnUser (name : String, nvid : NetworkViewID, info : NetworkMessageInfo)
{
	var clone : GameObject;
	clone = Instantiate (userPrefab, transform.position, transform.rotation);
	clone.name += name;
	clone.networkView.viewID = nvid;
	Debug.Log ("Clone created:" + clone.name);
}

// FUNCTIONS
function GenerateRandomName ()
{
	return "Clone_" + Random.Range (1,1000);
}

ScriptObject.js

private var screenWidth : float;
private var screenHeight : float;

private var showText : boolean = false;

function Start ()
{
	screenWidth = Screen.width;
	screenHeight = Screen.height;
}

function OnMouseEnter ()
{
	showText = true;
}

function OnMouseExit ()
{
	showText = false;
}

function OnGUI ()
{
	if (showText)
	{
		var mousePos : Vector3;
		mousePos = Input.mousePosition;
		
		var textToDisplay : String;
		textToDisplay = "Object:" + gameObject.name + " // NetworkPlayer:" + networkView.owner + " // Network View ID:" + networkView.viewID;
		
		GUI.Label (Rect (mousePos.x + 15, screenHeight - mousePos.y, 300, 100), textToDisplay);
	}
}

function OnSerializeNetworkView (stream : BitStream, info : NetworkMessageInfo)
{
	var position : Vector3;
	var rotation : Quaternion;
	
	if (stream.isWriting)
	{
		position = transform.position;
		rotation = transform.rotation;
		stream.Serialize (position);
		stream.Serialize (rotation);
	}
	else
	{
		stream.Serialize (position);
		stream.Serialize (rotation);
		transform.position = position;
		transform.rotation = rotation;
	}
}

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 !)…

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…

no, it should work in a non auth setup

// VARIABLES
public var userPrefab : GameObject;

// EVENTS
function OnServerInitialized () // A USER BECOMES SERVER
{
	var name : String = GenerateRandomName ();
	var nvid : NetworkViewID = Network.AllocateViewID ();
	networkView.RPC ("SpawnUser", RPCMode.AllBuffered, name, nvid);
}

// FUNCTIONS RPC
@RPC
function SpawnUser (name : String, nvid : NetworkViewID, info : NetworkMessageInfo)
{
	var clone : GameObject;
	clone = Instantiate (userPrefab, transform.position, transform.rotation);
	clone.name += name;
	clone.networkView.viewID = nvid;
	Debug.Log ("Clone created:" + clone.name);
}

// FUNCTIONS
function GenerateRandomName ()
{
	return "Clone_" + Random.Range (1,1000);
}

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).

I doubt there are people interested, but for future readers…

// VARIABLES
public var userPrefab : GameObject;
public var playerList : ArrayList = new ArrayList ();

// EVENTS
function OnServerInitialized () // A USER BECOMES SERVER
{
	var name : String = GenerateRandomName ();
	var nvid : NetworkViewID = Network.AllocateViewID ();
	networkView.RPC ("SpawnUser", RPCMode.AllBuffered, name, nvid);
}

//~ function OnPlayerConnected (player : NetworkPlayer) // DO NOT WORK
function OnConnectedToServer () // A USER CONNECTS TO THE SERVER
{
	var name : String = GenerateRandomName ();
	var nvid : NetworkViewID = Network.AllocateViewID ();
	networkView.RPC ("SpawnUser", RPCMode.AllBuffered, name, nvid);
}

 
function OnPlayerDisconnected (player : NetworkPlayer) // A USER DISCONNECTS FROM SERVER
{
	Debug.Log ("Clean up after player " + player);
	Network.RemoveRPCs (player);
	networkView.RPC ("DeletePlayerData", RPCMode.All, player);
}

function OnDisconnectedFromServer () // A USER STOPS THE CONNECTION
{
	Application.LoadLevel ("Network Ownership Menu");
}

// FUNCTIONS RPC
@RPC
function DeletePlayerData (player : NetworkPlayer)
{
	for (var playerData : PlayerData in playerList)
	{
		if (playerData.player == player)
		{
			if (playerData.object)
				Network.Destroy (playerData.object);
			playerList.Remove (playerData);
		}
	}
}

@RPC
function SpawnUser (name : String, nvid : NetworkViewID, info : NetworkMessageInfo)
{
	var player : NetworkPlayer = info.sender;
	var object : GameObject = GenerateCube (name, nvid);
	var playerData : PlayerData = GeneratePlayerData (player, object);
	if (Network.isServer)
		playerList.Add (playerData);
}

// FUNCTIONS
function GenerateCube (name : String, nvid : NetworkViewID)
{
	var object : GameObject;
	object = Instantiate (userPrefab, transform.position, transform.rotation);
	object.name += name;
	object.networkView.viewID = nvid;
	Debug.Log ("Cube created:" + object.name);
	return object;
}

function GeneratePlayerData (player : NetworkPlayer, object : GameObject)
{
	var newPlayerData : PlayerData = new PlayerData ();
	newPlayerData.player = player;
	newPlayerData.object = object;
	return newPlayerData;
}

function GenerateRandomName ()
{
	return "Cube_" + Random.Range (1,1000);
}

// CLASS
class PlayerData
{
	var player : NetworkPlayer;
	var object : GameObject;
}

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.

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.

 for (var playerData : PlayerData in playerList)
   {
      if (playerData.player == player)
      {
         if (playerData.object)
            Network.Destroy (playerData.object);
         playerList.Remove (playerData); <---- This is no good
      }
   }

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.

In other words, Network.AllocateViewID also give ownership ? It is not written anywhere…

Anyway, I made a test.

function OnConnectedToServer () // A USER CONNECTS TO THE SERVER
{
	networkView.RPC ("SendNVID ", RPCMode.Server);
}

@RPC
function SendNVID (info : NetworkMessageInfo)
{
	var nvid : NetworkViewID;
	nvid = Network.AllocateViewID ();
	networkView.RPC ("GetNVID ", info.sender, nvid);
}

@RPC
function GetNVID (nvid : NetworkViewID)
{
	var name : String = GenerateRandomName ();
	var object : GameObject = GenerateCube (name, nvid);
	networkView.RPC ("SpawnUser", RPCMode.Others, name, nvid);
}

@RPC
function SpawnUser (name : String, nvid : NetworkViewID, info : NetworkMessageInfo)
{
	var object : GameObject = GenerateCube (name, nvid);
	if (Network.isServer)
	{
		var player : NetworkPlayer = info.sender;
		var playerData : PlayerData = GeneratePlayerData (player, object);
		playerList.Add (playerData);
	}
}

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 ?

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#…

playerList.RemoveAll(delegate(PlayerData playerData)
        {
            Boolean remove = false;

            if (playerData.player == player)
            {
                if (playerData.go)
                    Destroy(playerData.go);
                remove = true;
            }

            return remove;
        });

I ended up using the temp array and assigning method.

function DeletePlayerData (player : NetworkPlayer)
{
	var playerListTemp : ArrayList = new ArrayList ();
	for (var playerData : PlayerData in playerList)
	{
		if (playerData.player == player)
		{
			if (playerData.object)
				Network.Destroy (playerData.object);
		}
		else
		{
			playerListTemp.Add (playerData);
		}
	}
	playerList = new ArrayList (playerListTemp);
}

Thanks you for your advice :wink:

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.

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 ?

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.

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?

I guess there would be errors. I wrote this code to test it only with two applications, without having to deal with the buffer.

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.