ObjectPool de Unity
Fait partie de la section Architecture de jeu dans Unity du Journey Unity de Coddy. Leçon 39 sur 50.
Unity fournit un pool, ObjectPool<T> dans UnityEngine.Pool. Tu lui indiques quoi faire à chaque étape à l’aide de quatre fonctions lambda, et il conserve les objets disponibles ainsi que les compteurs. Cette cheminée émet une bouffée de fumée toutes les 0.2 secondes, et chaque bouffée est remise dans le pool au bout d’une seconde :
using UnityEngine;
using UnityEngine.Pool;
public class Chimney : MonoBehaviour
{
public Puff puffPrefab;
private ObjectPool<Puff> pool;
private float timer;
void Awake()
{
pool = new ObjectPool<Puff>(
createFunc: () => Instantiate(puffPrefab),
actionOnGet: puff => puff.gameObject.SetActive(true),
actionOnRelease: puff => puff.gameObject.SetActive(false),
actionOnDestroy: puff => Destroy(puff.gameObject),
collectionCheck: true,
defaultCapacity: 10,
maxSize: 20);
}
void Update()
{
timer += Time.deltaTime;
if (timer >= 0.2f)
{
timer = 0f;
Puff puff = pool.Get();
puff.transform.position = transform.position + Vector3.up;
puff.Launch(pool);
}
}
}// Puff.cs
private IObjectPool<Puff> pool;
private float age;
public void Launch(IObjectPool<Puff> owner)
{
pool = owner;
age = 0f;
}
void Update()
{
transform.Translate(0f, Time.deltaTime, 0f);
age += Time.deltaTime;
if (age >= 1f) pool.Release(this);
}createFunc s’exécute lorsque Get trouve le pool vide ; actionOnGet s’exécute à chaque Get et actionOnRelease à chaque Release. Lorsqu’une bouffée est remise dans le pool alors que maxSize objets disponibles s’y trouvent déjà, le pool ne la conserve pas : actionOnDestroy la détruit. defaultCapacity correspond uniquement à la capacité initiale de la liste du pool ; aucun objet n’est donc créé au départ. CountAll compte les bouffées créées par le pool et qu’il possède encore, CountActive celles qui sont dans le jeu et CountInactive les objets disponibles. Get a activé la bouffée avant que la cheminée ne la positionne, ce qui convient ici puisque Puff n’a pas de OnEnable qui lit sa position. La bouffée conserve le pool en tant que IObjectPool<Puff>, l’interface implémentée par tous les pools Unity, et Launch réinitialise son âge à chaque utilisation.
Lorsque collectionCheck est activé, remettre dans le pool une bouffée qui s’y trouve déjà déclenche une InvalidOperationException : Trying to release an object that has already been released to the pool. Cette vérification s’exécute uniquement dans l’Editor, car elle parcourt les objets disponibles à chaque Release ; dans une build, la bouffée se retrouverait deux fois dans le pool et deux appels à Get renverraient le même objet. Une cause fréquente est un objet qui continue de s’exécuter après sa remise dans le pool et qui s’y remet une nouvelle fois ; c’est pourquoi actionOnRelease le désactive.
Défi
MoyenFeux d’artifice. Chaque pression sur Espace fait jaillir 8 étincelles du lanceur, et chaque Spark appelle Return(this) du lanceur lorsque sa durée de vie de 1 seconde est écoulée (Spark est prêt). Aujourd’hui, le lanceur instancie et détruit chaque étincelle. Utilise le pool de Unity :
- Dans
Launcher.cs, crée unObjectPool<Spark>dans Awake. Il crée une étincelle en instanciantsparkPrefab, l’active lorsqu’elle est récupérée et la désactive lorsqu’elle revient, puis détruit son objet lorsqu’elle est libérée. - Le pool conserve au maximum 5 étincelles en réserve, donc après une salve de 8, 5 attendent et 3 sont détruites.
- Espace récupère ses 8 étincelles dans le pool au lieu de les instancier, et
Returnlibère l’étincelle dans le pool au lieu de la détruire.
Appuie sur Play, lance quelques salves, puis clique sur Check.
Essayez vous-même
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Pool;
public class Launcher : MonoBehaviour
{
public Spark sparkPrefab;
public int sparks = 8;
void Awake()
{
// Créer le pool : créer, obtenir, libérer et détruire, avec au maximum 5 étincelles en réserve
}
void Update()
{
if (Keyboard.current.spaceKey.wasPressedThisFrame)
{
for (int i = 0; i < sparks; i++)
{
Spark spark = Instantiate(sparkPrefab);
spark.transform.position = transform.position + Vector3.up;
spark.Launch(Direction(i), this);
}
}
}
// Une étincelle arrivée en fin de vie
public void Return(Spark spark)
{
Destroy(spark.gameObject);
}
// Vers l’extérieur et vers le haut, réparties uniformément autour du lanceur
Vector3 Direction(int i)
{
return Quaternion.Euler(0f, i * 360f / sparks, 0f) * new Vector3(0f, 1f, 3f);
}
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Architecture de jeu dans Unity
1Des composants, pas des hiérarchies
Des composants simplesUn script de base pour les ennemisDes composants abstraitsDes composants qui fonctionnent ensembleRécapitulatif : tourelle modulaire4Données sérialisées
Classes sérialisablesListes dans l’inspecteurÉnumérations dans l’inspecteurRécapitulatif : table de butin7Machines à états
Les états sous forme de classesUne machine à étatsTransitionsLes cerveaux des ennemisRécapitulatif : états d’une porte10Projet : Survivant de l’arène
Le hérosTypes d’ennemis2Interfaces dans Unity
IDamageableInterfaces et TryGetComponentGetComponentsRécapitulatif : touche d’interaction5ScriptableObjects
Assets de donnéesStatistiques des armesParamètres partagésLes vagues sous forme de donnéesRécapitulatif : base de données d’objets3Événements
Événements C# dans UnityS’abonner dans OnEnableChamps UnityEventLes événements pilotent l’interface utilisateurRécapitulatif : fenêtre contextuelle de succès6Sauvegarder avec JSON
JsonUtilitySauvegarder le jeuCharger et valeurs par défautRécapitulatif : emplacements de sauvegarde9Réutilisation des objets
Pourquoi mettre les objets en poolUn pool avec une fileObjectPool de UnityRécapitulatif : pool de projectiles