Network Object Instantiation

devomage

Member
If you look at all 6 references of IsServer() you will find Object Instantiation code and finally this call:
C#:
NetworkObjectPool.NetworkSpawn(m_Object, m_InstantiatedObject, true);

For TNet to work properly, I need the option for complete control over Object Instantiation.

Something like the following would be ideal... and some sort of flag that skips Instantiation code in those 6 scripts.
C#:
NetworkObjectPool.NetworkSpawn(m_Object, position, rotation, true);

This seems to be the only hurdle I've found so far. I can give details if needed.
 
How does TNet do its network spawn? There are a few cases where I need the reference to the spawned object in order to perform some logic on it. This is why for example the spawnedObject is instantiated first on line 1140 and then instantiated over the network on line 1207. We'll need to come up with something that works with all use cases.
 
TNet has a separate function for spawning network objects.

C#:
// example from the UCC TNet add-on
TNet.TNManager.Instantiate(Utility.TNetUtility.LobbyChannel, "SpawnObject", $"Prefabs/{original.name}", false, spawnPosition, spawnRotation.eulerAngles, spawnData);

// TNet's Instantiate() call - for reference
static public void Instantiate (int channelID, string funcName, string path, bool persistent, params object[] objs)
{
     // ...
}

When TNManager.Instantiate() is called, it is called on all clients in the specified channel simultaneously.

C#:
[TNet.RCC]
static GameObject SpawnObject(GameObject prefab, Vector3 pos, Vector3 rot, DataNode spawnData)
{
    Debug.LogError($"SpawnObject: {prefab.name}");

     // the 'path' field in the initial call (can be null) and the and the 'prefab' reference in this function is irrelevant
     // you can create the prefab instance inside this static function by any means possible.
    var go = ObjectPool.Instantiate(prefab);
    var tno = go.GetComponent<TNet.TNObject>();
    go.transform.position = pos;
    go.transform.rotation = Quaternion.Euler(rot);

     // a dataNode can be set like this only in the RCC - otherwise use tno.Set() or tno.Get().
    tno.dataNode = spawnData;

    var spawnDataObject = go.GetCachedComponent<ISpawnDataObject>();
    if (spawnDataObject != null)
    {
        spawnDataObject.ObjectSpawned();
    }

    return go;
}

There are a few restrictions:
  • A GameObject can not be sent to the RCC.
  • All RCC are static
  • RCC can fire before some Awake() functions (IE DemoManager.Awake) which effects RegisterEvent(). Because of this, everything inside the RCC must be static.
  • ExecuteEvent() is not a good idea within the RCC. The tno is not completely initialized until the RCC 'return go' has executed. This is a problem because the unique id has not yet been assigned.

C#:
// from TNetObjectPool.cs
protected override void NetworkSpawnInternal(GameObject original, GameObject instanceObject, bool sceneObject)
{
    if (!m_SpawnableGameObjectIDMap.TryGetValue(original, out var index))
    {
        Debug.LogError($"Error: Unable to spawn {original.name} on the network. Ensure the object has been added to the PunObjectPool.");
        return;
    }

    var spawnPosition = instanceObject.transform.position;
    var spawnRotation = instanceObject.transform.rotation;

    var spawnDataObject = instanceObject.GetCachedComponent<ISpawnDataObject>();
    TNet.DataNode spawnData = null;// spawnData is now a DataNode
    if (spawnDataObject != null)
    {
        spawnData = spawnDataObject.SpawnData();
    }

    if (spawnData == null)
    {
        spawnData = new TNet.DataNode("SpawnData");
    }

    // hack: destroy the bogus instance
    if (ObjectPool.InstantiatedWithPool(instanceObject))
    {
        DestroyInternal(instanceObject);
    }
    else
    {
        Debug.Log($"{instanceObject == null}");
    }

    Debug.LogError($"NetworkSpawnInternal: {original.name} {sceneObject}");

    TNet.TNManager.Instantiate(Utility.PunUtility.LobbyChannel, "SpawnObject", $"Prefabs/{original.name}", false, spawnPosition, spawnRotation.eulerAngles, spawnData);
}
 
Last edited:
Thanks for the details. Can you trigger the RCC on every client besides the one that is doing the calling? With the RCC being static and asynchronous it would make retrieving a reference to that instantiated object tougher than what it is doing now by getting a reference to the instantiated object immediately. If the RCC can be executed on every other client the NetworkSpawn function can have the same parameters and you'd just send the position/rotation of the spawned object.
 
The owner spawns the object and it is duplicated on all connected clients in the same channel.

There are only 2 options I can see. Static reference inside the RCC or a component on the prefab after it is instantiated.
 
Hmm.. so in the case of ShootableWeapon.ProjectileFire how would you change it so the NetworkObjectPool does the spawning without returning a GameObject?
 
Can you trigger the RCC on every client besides the one that is doing the calling?

I haven't tried it yet, but we are in full control of what happens inside the RCC. Simply returning a null for the local player might be ok. I will look into this.

in the case of ShootableWeapon.ProjectileFire how would you change it so the NetworkObjectPool does the spawning without returning a GameObject?

Unless it can provide data and spawn after the fact - I am not sure. The problem is that PUN (and likely Mirror) can simply take over the object - where TNet requires internal instantiation. I will ask the TNet dev about this.
 
The TNet dev suggested that the ObjectPool might have an identifier to the object? The identifier can be passed to the RCC and then use the ObjectPool to find the reference inside the RCC.

Returning a null seems intermittent and an overall bad idea.
 
Last edited:
When the ObjectPool spawns the object it uses the prefab's instance ID to look up which object should be spawned. This instance ID will be different on each client though so it probably doesn't help.

Maybe the Object Identifier system could be used to look up the object that should be spawned instead? In a sense this is similar to the PhotonView concept. With this structure the interface wouldn't need to change since you can just look up the object based on the original prefab Object Identifier.
 
With this structure the interface wouldn't need to change since you can just look up the object based on the original prefab Object Identifier.
I think this was the concept the TNet dev had in mind... as long as you can get a reference to instanceObject (inside the static RCC) on the master client.

I would still need initialization data... unless I could get it from the instanceObject.



Here is a copy of the code suggestion by the TNet dev. You can also find the conversation in the TNet discord channel.

C#:
TNManager.Instantiate(channel, "YourRCC", null, true, <some identifier>, ...);

[RCC]
static GameObject YourRCC (GameObject prefab, <some identifier>)
{
    GameObject go = YourPoolOfObjects.Pop(<some identifier>);
    return go;
}
 
Last edited:
Top