Beauty of Object Pool Model in Game Development and Design

Links to the original text: https://www.tuicool.com/articles/MNBJJvZ

Principle: Reuse objects from a fixed pool to improve performance and memory usage, rather than releasing them one by one.
When you need to create a large number of repetitive objects and use them frequently, you need to consider using object pools, because repetitive creation and destruction is a process of memory allocation and release, which is easy to generate memory fragments.
Comparing with PC, the memory is scarce on both host and mobile. We all hope that the game can be more stable and not manage memory effectively. At this time, a large number of memory fragments are fatal.
Memory fragmentation means that memory is divided into small chunks instead of the whole chunk. The size of all memory chunks may be large but not usable. For example, if you want to allocate 16 byte memory, if you have 20 byte space, you can allocate it successfully. But if this 20 byte is memory fragmentation, it will be divided into two 10 bytes. Matching failure. Therefore, if there are a large number of memory fragments, theoretically enough available memory will also fail to allocate.
Many game companies run immersion tests, let a game run for days, check whether it crashes to detect memory leaks, and so on, because memory fragmentation produces a devastating result that is a slow process.

The principle of a memory pool is to allocate a large block of memory in advance, generate objects that need to be used frequently, and then release them all until they are not used. A memory pool can be regarded as a collection of reusable objects. A large amount of memory fragmentation can be avoided to some extent.
When creating a memory pool, we generate a specified number of all objects and mark them to distinguish whether they are in use or not, so when we want an object, we just need to get an unused object from the pool, mark it as in use, and mark it back as unused. ” All right. It's like a rental office, which borrows when needed and returns when used up. Reuse objects in this way. Note: When using, remember to initialize the object. The object pool does not need to be released immediately.
Object pools are often used in particle systems to generate particles, bullets, enemies, etc., or sounds that need to be played.
When you need:
1. Frequent creation and destruction of an object
2. Require objects of similar size
3. Possible memory fragmentation
4. Reusable and consumable objects to create and destroy
Do not hesitate to use object pools.
Object pool also has some shortcomings. It may start to generate many objects, but most of them are not used, and a lot of memory is wasted. So we need to control the number of objects to be generated or create a second pool according to the situation. Next, we will talk about dynamic expansion of object pool, which will solve this problem. Another possibility is that the generation is not enough, tragedy, one solution is to force "do not use" one or several objects to tear down the East Wall to make up for the West Wall, the best way to tear down will not be found by the players, and the other is that the dynamic expansion of the object pool will solve this problem. A small pool of objects may also be useless or produce memory fragmentation. In addition, a large amount of memory is allocated at the moment of initialization, which may cause problems.
We need to store the generated objects in the pool to extract and increase recycling. There are several options
1. Arrays, here is not ArrayList or ordinary Object [] such arrays, in memory is continuous storage, index speed is very fast, compared with the use. But this kind of array can not be dynamically expanded, that is, the number of generated objects is constant, accidentally beyond this range will also produce data overflow, and can only store one type of object.
2.ArrayList, a dynamic array, can dynamically expand or store different types of objects, but it needs to be boxed and unpacked (to do a forced conversion) when operating different types of data, which results in performance loss and is not type-safe.
3. List < T >, generic, dynamic expansion, but can not store different types of data, need to develop storage data type T. Safety type, there is no packing and unloading.
In addition to these three basic can also use heap, stack, hash, Dictionary, if you need to find specific objects according to the key can use hash or Dictionary. The code below the blogger is demonstrated with List < T >.
Simple code to generate enemies
First look at the enemy class:

using UnityEngine;
using System.Collections;
public class Enemy : MonoBehaviour {
	public AnimationClip hurtAnimation;
	public AudioClip hertSound;
	public AudioClip dieSound;
	AudioSource audiosouce;
	int Max_HP = 3;
	int Now_HP = 3;
	bool isUsed = false;
	float moveSpeed = 0;
	float act = 0;
	// Use this for initialization
	public void init(bool _isUsed, float _moveSpeed, float _act, Vector3 _scale, Vector3 _pos, Quaternion _rot,int _Max_HP)
	{
		isUsed = _isUsed;
		moveSpeed = _moveSpeed;
		act = _act;
		this.transform.localScale = _scale;
		this.transform.position = _pos;
		this.transform.rotation = _rot;
		Max_HP = _Max_HP;
		Now_HP = _Max_HP;
	}
	public bool getUsed()
	{
		return isUsed;
	}
	void hert()
	{
...Injury management ____________.
	}
	void Die()
	{
		audiosouce.PlayOneShot(dieSound);
		isUsed = false;
	 this.GetComponent<Rigidbody>().useGravity = false;
		this.transform.position = Vector3.zero;
   //	 Destroy(this.gameObject); no destroy object is needed
	}
	void Start () {
		audiosouce = this.GetComponent<AudioSource>();
	}
	void OnTriggerEnter(Collider other)
	{
		  ...hert()..
	}
	// Update is called once per frame
	void Update () {
	...Logic.....
	}
}

The init function is responsible for initializing the enemy. Every time the enemy is born from the pool, it needs to be initialized. Initially, isUsed is marked as false to represent unused and then init () is called to assign parameters in the pool. The getUsed method is the way for the pool to obtain the object state.

A simple target pool for generating enemies:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class EnemyPool : MonoBehaviour
{
	public GameObject Hero;
	public GameObject perfab;
	List<GameObject> enemy = new List<GameObject>();
	int Max_Amount = 10;
	// Use this for initialization
	void Start()
	{
		InvokeRepeating("setEnemy", 1, 10);
		for (int i = 0; i < Max_Amount; i++)
		{
			enemy.Add(Instantiate(perfab, Vector3.left*i*2, Quaternion.identity) as GameObject);
		}
	}
	void setEnemy()
	{
		for (int i = 0; i < Max_Amount; i++)
		{
			if (!enemy[i].GetComponent<Enemy>().getUsed())
			{
  enemy[i].GetComponent<Enemy>().init(true,2,enemy[i].transform.localScale,_pos,Quaternion.identity, 3);
				enemy[i].GetComponent<Rigidbody>().useGravity = true;
				return;
			}
		}
		   print("enemy all busy! create new!");
		addEnemy();
	}
	void addEnemy()
	{
		enemy.Add(Instantiate(perfab, Vector3.zero, Quaternion.identity) as GameObject);
		++Max_Amount;
	}
}

Here the blogger sets to generate an enemy every 10 seconds, which can automatically expand the object pool.

setEnemy() to generate an enemy, in the setEnemy() method, we traverse all enemy objects to find unused enemies, mark them as usage, and initialize enemy attributes, and then successfully "generate" an enemy. There is a way to reduce the consumption of traversal by dividing it into two tables: used tables and unused tables. When creating, the tables are retrieved from unused tables and then put into the tables in use.

The addEnemy() method extends an enemy by using the addEnemy() method of List < T >.

Achieving results:

Keywords: Mobile

Added by vince251 on Fri, 23 Aug 2019 06:19:38 +0300