Address
304 North Cardinal St.
Dorchester Center, MA 02124

Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM

Unity InstantiateGameObjects

Unity 2022.3 – New way to “InstantiateGameObjects”

In the recent release of Unity 2022.3.11, the GameObject class welcomes a new static function, “InstantiateGameObjects.” This function facilitates the instantiation of a defined number of GameObjects using an instance ID.
As of the time of writing, this version is less than a month old, and we will explore together whether it outperforms the standard “Instantiate” method.

Table of Contents

Using InstantiateGameObjects

Referring to the documentation:

Unity Documentation InstantiateGameObjects

Unlike the “Instantiate” function, instead of passing the object to instantiate as a parameter, we must pass an instance ID (which must be a gameObject instance ID).
Another change is that this function doesn’t return a value, so we need to pass 2 NativeArray<int> parameters:

  • The first will be populated with the instance IDs of the new GameObjects
  • The second by the instance IDs of the Transforms of the new GameObjects

Is InstantiateGameObjects Faster?

We will perform an approximate test to give us an indication of its speed. For the new method, we will use the code below:

using Unity.Collections;
using UnityEngine;

public GameObject reference;
private NativeArray<int> instanceIds;
private NativeArray<int> transformIds;

private void NewInstantiate()
{
    int count = 20000;

    instanceIds = new NativeArray<int>(count, Allocator.Persistent);
    transformIds = new NativeArray<int>(count, Allocator.Persistent);

    GameObject.InstantiateGameObjects(reference.GetInstanceID(), count, instanceIds, transformIds);
}

private void OnDestroy()
{
    instanceIds.Dispose();
    transformIds.Dispose();
}

And compare it with the classic “Instantiate” method:

using UnityEngine;
using System.Collections.Generic;

public GameObject reference;

private void OldInstantiate()
{
    int count = 20000;

    private List<Object> _objects = new List<Object>(count);

    for (int i = 0; i < count; i++)
    {
        _objects.Add(GameObject.Instantiate(reference));
    }
}

The reference gameObject is a Cube with a “Tracker” MonoBehaviour attached.

Unity Inspector Cube

I ran these methods 100 times; here are the results:

Unity InstantiateGameObjects Benchmark

On average, the “GameObject.InstantiateGameObjects” method takes 1781ms to execute, while the classic Instantiate method takes 1870ms. We can see that this new approach is slightly faster.

Indeed, this function gives us a small performance boost. The only drawback is that it only returns instanceIds. What can we do with that?

What to Do with Instance IDs?

Retrieve Our Object with an Instance ID

To retrieve an Object from an instance ID, we can use the “Resources.InstanceIDToObjectList” method. Moreover, since Unity 2022.3.11, this method has become ThreadSafe.

int count = 20000;

instanceIds = new NativeArray<int>(count, Allocator.Persistent);
transformIds = new NativeArray<int>(count, Allocator.Persistent);

GameObject.InstantiateGameObjects(reference.GetInstanceID(), count, instanceIds, transformIds);

var objects = new List<Object>(count);

Resources.InstanceIDToObjectList(instanceIds, objects);

If we then want to retrieve our MonoBehaviour, we can cast and then use “GetComponent”:

var trackers = new List<Tracker>(count);

for (int i = 0; i < count; i++)
{
    trackers.Add(((GameObject)objects[i]).GetComponent<Tracker>());
}

Enable/Disable GameObjects with Their Instance ID (GameObject.SetGameObjectsActive)

This new version has also added the “GameObject.SetGameObjectsActive” function, allowing us to activate or deactivate GameObjects by passing a NativeArray as a parameter:

GameObject.InstantiateGameObjects(reference.GetInstanceID(), count, instanceIds, transformIds);

// activate all GameObjects in instanceIds
GameObject.SetGameObjectsActive(instanceIds, true);

I compared this method with the classic “SetActive” method, activating 20000 GameObjects:

// objects is a List<GameObject> with 20000 GameObjects.

for (int i = 0; i < count; i++)
{
    objects[i].SetActive(true);
}

Having run these methods 100 times, I get an average execution time of 116ms for “GameObject.SetGameObjectsActive” and 119ms for the classic method.

Move GameObjects to Another Scene via Instance ID (SceneManager.MoveGameObjectsToScene)

This new version has also introduced SceneManager.MoveGameObjectsToScene, allowing the movement of GameObjects to another Scene using an NativeArray<int>:

using UnityEngine.SceneManagement;
using Unity.Collections;

// instanceIds is a NativeArray<int> with instance IDs

var scene = SceneManager.GetSceneAt(0);
SceneManager.MoveGameObjectsToScene(instanceIds, scene);

Create a Job TransformAccessArray Using Instance IDs

TransformAccessArray is a structure used with a job IJobParallelForTransform. It allows performing the same operation on all transforms (position, rotation, and scale) on multiple threads, offering better performance. TransformAccessArray has also received an improvement; now, we can pass Transform instance ID via its “Add” method:

using UnityEngine.Jobs;
using Unity.Burst;
using UnityEngine.SceneManagement;
using Unity.Collections;
using UnityEngine;

// burst compile for better performance
[BurstCompile]
public struct MovementJob : IJobParallelForTransform
{
    public float deltaTime;

    public void Execute(int index, TransformAccess transformAccess)
    {
        transformAccess.position = transformAccess.position + Vector3.up * deltaTime;
    }
}

public NativeArray<int> instanceIds;
public NativeArray<int> transformIds;

private TransformAccessArray _transformAccess;

private bool _isReady = false;

private void InstantiateAll()
{
    int count = 20000;

    // init our TransformAccessArray
    _transformAccess = new TransformAccessArray(count);

    // Prepare instantiate with InstantiateGameObjects
    instanceIds = new NativeArray<int>(count, Allocator.Persistent);
    transformIds = new NativeArray<int>(count, Allocator.Persistent);

    var scene = SceneManager.GetActiveScene();

    GameObject.InstantiateGameObjects(reference.GetInstanceID(), count, instanceIds, transformIds, scene);

    // Add instanceId to TransformAccessArray
    for (int i = 0; i < count; i++)
    {
        _transformAccess.Add(transformIds[i]);
    }
    
    _isReady = true;
}

private void Update()
{
    // wait all is instantiate
    if (!_isReady)
    {
        return;
    }

    // create job
    var job = new MovementJob()
    {
        deltaTime = Time.deltaTime,
    };

    job.Schedule(_transformAccess);
}

private void OnDestroy()
{
	// dont forget to dispose
	_transformAccess.Dispose();
	instanceIds.Dispose();
	transformIds.Dispose();
}

Conclusion

These new methods offer new possibilities for using instance IDs. It’s very interesting to be able to instantiate a multitude of GameObjects directly and perform actions in a Job without a direct reference. These are methods that have just arrived; we’ll see how they evolve over time.

Thanks you for reading.

Check my other articles about Unity:

Leave a Reply

Your email address will not be published. Required fields are marked *