Unity InstantiateGameObjects

Unity 2022 – Nouvelle fonction InstantiateGameObjects

Dans la récente version de Unity 2022.3.11, la classe GameObject accueille une nouvelle fonction statique, « InstantiateGameObjects. » Cette fonction facilite l’instanciation d’un nombre défini de GameObjects en utilisant un ID d’instance.

Au moment de la rédaction, cette version a moins d’un mois, et nous allons explorer ensemble si elle surpasse la méthode standard « Instantiate« .

Table des matières

Utilisation de InstantiateGameObjects

En référence à la documentation :

Documentation Unity InstantiateGameObjects

Contrairement à la fonction « Instantiate« , au lieu de passer l’objet à instancier en paramètre, nous devons passer un ID d’instance (qui doit être un ID d’instance de gameObject). Un autre changement est que cette fonction ne renvoie pas de valeur, donc nous devons passer 2 NativeArray<int> en tant que paramètres :

  • Le premier sera remplis avec les IDs d’instance des nouveaux GameObjects
  • Le deuxième avec les IDs d’instance des Transforms des nouveaux GameObjects

InstantiateGameObjects est-il plus rapide ?

Nous allons effectuer un test approximatif pour nous donner une indication de sa vitesse. Pour la nouvelle méthode, nous utiliserons le code suivant :

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();
}

Et comparons-le avec la méthode classique « Instantiate » :

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));
    }
}

Le gameObject de référence est un Cube avec un MonoBehaviour « Tracker » attaché.

Unity Inspector Cube

J’ai exécuté ces méthodes 100 fois; voici les résultats :

Unity InstantiateGameObjects Benchmark

En moyenne, la méthode « GameObject.InstantiateGameObjects » prend 1781 ms pour s’exécuter, tandis que la méthode classique Instantiate prend 1870 ms. Nous pouvons voir que cette nouvelle approche est légèrement plus rapide.

En effet, cette fonction nous offre un léger coup de pouce en termes de performances. Le seul inconvénient est qu’elle ne renvoie que des IDs d’instance. Que pouvons-nous en faire ?

Que faire avec les IDs d’instance ?

Récupérer notre objet avec un ID d’instance

Pour récupérer un objet à partir d’un ID d’instance, nous pouvons utiliser la méthode « Resources.InstanceIDToObjectList« . De plus, depuis Unity 2022.3.11, cette méthode est devenue 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);

Si nous voulons ensuite récupérer notre MonoBehaviour, nous pouvons le caster puis utiliser « GetComponent« :

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

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

Activer/Désactiver les GameObjects avec leur ID d’instance (GameObject.SetGameObjectsActive)

Cette nouvelle version a également ajouté la fonction « GameObject.SetGameObjectsActive« , nous permettant d’activer ou de désactiver des GameObjects en passant un NativeArray en tant que paramètre :

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

// activer tous les GameObjects dans instanceIds
GameObject.SetGameObjectsActive(instanceIds, true);

J’ai comparé cette méthode avec la méthode classique « SetActive« , activant 20000 GameObjects :

// objects est une List<GameObject> avec 20000 GameObjects.

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

Après avoir exécuté ces méthodes 100 fois, j’obtiens un temps d’exécution moyen de 116 ms pour « GameObject.SetGameObjectsActive » et 119 ms pour la méthode classique.

Déplacer des GameObjects vers une autre scène via l’ID d’instance (SceneManager.MoveGameObjectsToScene)

Cette nouvelle version a également introduit SceneManager.MoveGameObjectsToScene, permettant le déplacement de GameObjects vers une autre scène en utilisant un NativeArray<int> :

using UnityEngine.SceneManagement;
using Unity.Collections;

// instanceIds est un NativeArray<int> avec des IDs d'instance

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

Créer un job TransformAccessArray en utilisant les IDs d’instance

TransformAccessArray est une structure utilisée avec un job IJobParallelForTransform. Elle permet d’effectuer la même opération sur tous les transforms (position, rotation et échelle) sur plusieurs threads, offrant de meilleures performances. TransformAccessArray a également été amélioré ; maintenant, nous pouvons passer l’ID d’instance de Transform via sa méthode « Add » :

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

// burst compile pour de meilleures performances
[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;

    // initialiser notre TransformAccessArray
    _transformAccess = new TransformAccessArray(count);

    // Préparer l'instanciation avec 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);

    // Ajouter l'ID d'instance à TransformAccessArray
    for (int i = 0; i < count; i++)
    {
        _transformAccess.Add(transformIds[i]);
    }
    
    _isReady = true;
}

private void Update()
{
    // attendre que tout soit instancié
    if (!_isReady)
    {
        return;
    }

    // créer le job
    var job = new MovementJob()
    {
        deltaTime = Time.deltaTime,
    };

    job.Schedule(_transformAccess);
}

private void OnDestroy()
{
	// n'oubliez pas de disposer
	_transformAccess.Dispose();
	instanceIds.Dispose();
	transformIds.Dispose();
}

Conclusion

Ces nouvelles méthodes offrent de nouvelles possibilités d’utilisation des IDs d’instance. Il est très intéressant de pouvoir instancier une multitude de GameObjects directement et d’effectuer des actions dans un Job sans référence directe. Ces méthodes viennent d’arriver ; nous verrons comment elles évoluent avec le temps.

Merci de m’avoir lu.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *