unity3d
Regroupement d'objets
Recherche…
Pool d'objets
Parfois, lorsque vous créez un jeu, vous devez créer et détruire de nombreux objets du même type, encore et encore. Vous pouvez simplement faire cela en créant un préfabriqué et en l'instanciant / détruisant chaque fois que vous en avez besoin, mais cela est inefficace et peut ralentir votre jeu.
Un moyen de contourner ce problème est le regroupement d'objets. En gros, cela signifie que vous avez un pool (avec ou sans limite de quantité) d’objets que vous allez réutiliser chaque fois que vous le pouvez pour éviter une instanciation ou une destruction inutiles.
Voici un exemple de pool d'objets simple
public class ObjectPool : MonoBehaviour
{
public GameObject prefab;
public int amount = 0;
public bool populateOnStart = true;
public bool growOverAmount = true;
private List<GameObject> pool = new List<GameObject>();
void Start()
{
if (populateOnStart && prefab != null && amount > 0)
{
for (int i = 0; i < amount; i++)
{
var instance = Instantiate(Prefab);
instance.SetActive(false);
pool.Add(instance);
}
}
}
public GameObject Instantiate (Vector3 position, Quaternion rotation)
{
foreach (var item in pool)
{
if (!item.activeInHierarchy)
{
item.transform.position = position;
item.transform.rotation = rotation;
item.SetActive( true );
return item;
}
}
if (growOverAmount)
{
var instance = (GameObject)Instantiate(prefab, position, rotation);
pool.Add(instance);
return instance;
}
return null;
}
}
Voyons d'abord les variables
public GameObject prefab;
public int amount = 0;
public bool populateOnStart = true;
public bool growOverAmount = true;
private List<GameObject> pool = new List<GameObject>();
-
GameObject prefab
: il s'agit du préfabriqué que le pool d'objets utilisera pour instancier de nouveaux objets dans le pool. -
int amount
: C'est le nombre maximum d'éléments pouvant se trouver dans le pool. Si vous souhaitez instancier un autre élément et que le pool a déjà atteint sa limite, un autre élément du pool sera utilisé. -
bool populateOnStart
: vous pouvez choisir de remplir le pool au démarrage ou non. Cela remplira le pool d'instances du préfabriqué de sorte que la première fois que vous appelezInstantiate
vous obtiendrez un objet déjà existant. -
bool growOverAmount
:bool growOverAmount
cette valeur sur true pour permettre au pool de croître chaque fois que le montant demandé est supérieur à un certain montant. Vous n'êtes pas toujours en mesure de prédire avec précision la quantité d'articles à placer dans votre piscine, ce qui ajoutera plus à votre piscine en cas de besoin. -
List<GameObject> pool
: c'est le pool, l'endroit où tous vos objets instanciés / détruits sont stockés.
Maintenant, vérifions la fonction Start
void Start()
{
if (populateOnStart && prefab != null && amount > 0)
{
for (int i = 0; i < amount; i++)
{
var instance = Instantiate(Prefab);
instance.SetActive(false);
pool.Add(instance);
}
}
}
Dans la fonction de démarrage, nous vérifions si nous devons remplir la liste au début et le faire si le prefab
a été défini et que le montant est supérieur à 0 (sinon, nous créerions indéfiniment).
Ceci est juste une simple boucle pour instancier de nouveaux objets et les placer dans le pool. Une chose à laquelle il faut faire attention est que toutes les instances sont inactives. De cette façon, ils ne sont pas encore visibles dans le jeu.
Ensuite, il y a la fonction Instantiate
, qui est la plus grande partie de la magie
public GameObject Instantiate (Vector3 position, Quaternion rotation)
{
foreach (var item in pool)
{
if (!item.activeInHierarchy)
{
item.transform.position = position;
item.transform.rotation = rotation;
item.SetActive(true);
return item;
}
}
if (growOverAmount)
{
var instance = (GameObject)Instantiate(prefab, position, rotation);
pool.Add(instance);
return instance;
}
return null;
}
La Instantiate
fonction ressemble propre de l' unité Instantiate
fonction, à l' exception de la maison préfabriquée a déjà été fournie ci - dessus en tant que membre de la classe.
La première étape de la fonction Instantiate
consiste à vérifier si un objet inactif se trouve actuellement dans le pool. Cela signifie que nous pouvons réutiliser cet objet et le rendre au demandeur. S'il y a un objet inactif dans le pool, nous définissons la position et la rotation, configurez-le pour qu'il soit actif (sinon il pourrait être réutilisé par accident si vous oubliez de l'activer) et renvoyez-le au demandeur.
La deuxième étape ne se produit que s'il n'y a pas d'éléments inactifs dans le pool et que le pool est autorisé à dépasser le montant initial. Ce qui se passe est simple: une autre instance du préfabriqué est créée et ajoutée au pool. Permettre la croissance de la piscine vous aide à avoir la bonne quantité d'objets dans la piscine.
La troisième "étape" ne se produit que s'il n'y a pas d'éléments inactifs dans le pool et que le pool n'est pas autorisé à se développer. Lorsque cela se produit, le demandeur recevra un objet GameObject nul, ce qui signifie que rien n'était disponible et devrait être géré correctement pour empêcher les NullReferenceExceptions
.
Important!
Pour vous assurer que vos objets reviennent dans le pool, vous ne devez pas détruire les objets du jeu. La seule chose à faire est de les désactiver et de les rendre utilisables dans le pool.
Pool d'objets simple
Vous trouverez ci-dessous un exemple de pool d'objets permettant de louer et de renvoyer un type d'objet donné. Pour créer le pool d'objets, une fonction Func pour la fonction de création et une action pour détruire l'objet sont requises pour donner à l'utilisateur une certaine flexibilité. Lors de la demande d'un objet lorsque le pool est vide, un nouvel objet sera créé et, sur demande, lorsque le pool contient des objets, les objets seront supprimés du pool et renvoyés.
Pool d'objets
public class ResourcePool<T> where T : class
{
private readonly List<T> objectPool = new List<T>();
private readonly Action<T> cleanUpAction;
private readonly Func<T> createAction;
public ResourcePool(Action<T> cleanUpAction, Func<T> createAction)
{
this.cleanUpAction = cleanUpAction;
this.createAction = createAction;
}
public void Return(T resource)
{
this.objectPool.Add(resource);
}
private void PurgeSingleResource()
{
var resource = this.Rent();
this.cleanUpAction(resource);
}
public void TrimResourcesBy(int count)
{
count = Math.Min(count, this.objectPool.Count);
for (int i = 0; i < count; i++)
{
this.PurgeSingleResource();
}
}
public T Rent()
{
int count = this.objectPool.Count;
if (count == 0)
{
Debug.Log("Creating new object.");
return this.createAction();
}
else
{
Debug.Log("Retrieving existing object.");
T resource = this.objectPool[count-1];
this.objectPool.RemoveAt(count-1);
return resource;
}
}
}
Utilisation de l'échantillon
public class Test : MonoBehaviour
{
private ResourcePool<GameObject> objectPool;
[SerializeField]
private GameObject enemyPrefab;
void Start()
{
this.objectPool = new ResourcePool<GameObject>(Destroy,() => Instantiate(this.enemyPrefab) );
}
void Update()
{
// To get existing object or create new from pool
var newEnemy = this.objectPool.Rent();
// To return object to pool
this.objectPool.Return(newEnemy);
// In this example the message 'Creating new object' should only be seen on the frame call
// after that the same object in the pool will be returned.
}
}
Un autre pool d'objets simples
Un autre exemple: une arme qui tire des balles.
L'arme agit comme un pool d'objets pour les puces qu'elle crée.
public class Weapon : MonoBehaviour {
// The Bullet prefab that the Weapon will create
public Bullet bulletPrefab;
// This List is our object pool, which starts out empty
private List<Bullet> availableBullets = new List<Bullet>();
// The Transform that will act as the Bullet starting position
public Transform bulletInstantiationPoint;
// To spawn a new Bullet, this method either grabs an available Bullet from the pool,
// otherwise Instantiates a new Bullet
public Bullet CreateBullet () {
Bullet newBullet = null;
// If a Bullet is available in the pool, take the first one and make it active
if (availableBullets.Count > 0) {
newBullet = availableBullets[availableBullets.Count - 1];
// Remove the Bullet from the pool
availableBullets.RemoveAt(availableBullets.Count - 1);
// Set the Bullet's position and make its GameObject active
newBullet.transform.position = bulletInstantiationPoint.position;
newBullet.gameObject.SetActive(true);
}
// If no Bullets are available in the pool, Instantiate a new Bullet
else {
newBullet newObject = Instantiate(bulletPrefab, bulletInstantiationPoint.position, Quaternion.identity);
// Set the Bullet's Weapon so we know which pool to return to later on
newBullet.weapon = this;
}
return newBullet;
}
}
public class Bullet : MonoBehaviour {
public Weapon weapon;
// When Bullet collides with something, rather than Destroying it, we return it to the pool
public void ReturnToPool () {
// Add Bullet to the pool
weapon.availableBullets.Add(this);
// Disable the Bullet's GameObject so it's hidden from view
gameObject.SetActive(false);
}
}