Instantly share code, notes, and snippets.

Embed
What would you like to do?
Object Pooling system for Dot Matrix Dodger, a mobile Android game.
/*
* Tomer Braff
* May 10, 2017
* ObjectPool.cs
*
*
* This is an ObjectPool class/system for "Dot Matrix Dodger", a mobile game for Android.
* Object Pooling systems are fairly ubiquitos in video games as they allow for recycling
* and reuse of commonly used objects in games, especially with games in more memory focuesed
* languages like C++. For example, instead of instantiating and destroying an enemy from
* memory over and over again, the game could intantiate a decent number of enemies, say 100
* or so, and simply activate and deactivate them as needed rather than needing to do costly
* instantiating/destroying processes.
*
* They're practically required for certain types of games like bullet-hell or shmups, which
* this game takes heavy inspiration from.
*
* The way this specific object pool works:
*
* SETTING UP:
* Every Enemy inherets from an "Enemy" class and has its own EnemyType to identify itself.
* In the Unity scene, there is a gameobject with the ObjectPool script attached to it. The
* Object Pool is then filled with premade enemy prefabs for each of the enemy types I have.
*
* Unfortunately because of how Unity handles static classes and whatnot, I had to make this
* a Singleton object. I'd like to figure out a way to get this to work without having to
* instantiate this in the game scene, have it be a static, universal object pool, but for now
* it works.
*
* ON START:
*
* When initialized, the Object Pool goes through the list of enemy prefabs, looks at each
* of the prefab's attached Enemy script, finds that prefab's EnemyType, and create an entry
* in the enemyDictionary with the Key being the EnemyType and it's value being a newly instantiated
* Queue that holds Enemies. Because all enemies inheret from "Enemy", doing a bunch of Queue<[EnemyScript]>
* would be impractical. This way I can create as many different enemy types as I want without having
* to create a new Queue type each time.
*
* It will also place the GameObjects in its own dictionary with the key being the EnemyType.
* This is used to decouple certain things from the enemyPrefabs array, like accidentally having
* duplicates and lets me place prefabs in regardless of order. It is als used for instantiating
* if there isn't enough of a specified enemy to spawn.
*
* DURING GAMEPLAY:
*
* Using a wave script/manager system I made, when an enemy needs to be spawned the wave calls
* the ObjectPool "SpawnEnemy". There are several functions to spawn an enemy for different
* situations, but regardless they all eventually call the original SpawnEnemy function that
* calls the Depool function to dequeue the specified EnemyType from its specialized queue in
* the dictionary (or instantiates a new enemy if it didn't find one), places the enemy in the
* specified location found at a certain angle, and enables the enemy object to view in the game.
*
* To find the positions the enemy should spawn at, there are several "FindPosition" functions
* that take in various parameters to find a point around an origin at a certain radius (since
* this game has a lot to do with circles).
*
* FUTURE CONSIDERATIONS:
*
* I should probably decouple the FindPosition functions into its own class away from the ObjectPool
* since that's not really its particular job. Maybe make that a static class of its own, or
* extend one of Unity's like Transform or something.
*
* SpawnEnemy might also be something that should be decoupled from the Object Pool. This would
* help with generalizing the Object Pool instead of having this be specialized to just enemies.
*
* Generalizing the ObjectPool even more would be great, instead of having this Object Pool be
* specifically only for enemies. Earlier in development, for example, I had little particles
* that I would spawn behind an enemy like a trail, but I had to create an entirely new queue
* for that particle and integrate that everywhere. If I could maybe make an overall object pool,
* and then have a certain "section" of the pool be dedicated for Enemies that might help with
* those kinds of situaations.
*
*/
using UnityEngine;
using System.Collections.Generic;
public class ObjectPool : Singleton<ObjectPool>
{
public GameObject[] enemyPrefabs; // Public array to place the different Enemy prefabs
public uint enemyPool = 500; // Initial size of each
public float radius = 10; // Default radius around a point that an enemy will be spawned at
public Sprite[] enemySprites; // Array for enemy sprites. All sprites are the same shape, just different colors
public int spriteIndex = 0; // So depending on the theme, enemies can be circles, squares, randomized, whatever
#region Dictionaries
// Dictionaries relating to the EnemyType and its Queue, GameObject,
// A dictionary of Queues that contain specific enemies for the pool
private Dictionary<EnemyType, Queue<Enemy>> enemyDictionary = new Dictionary<EnemyType, Queue<Enemy>>();
// A dictionary with EnemyType as keys to the Enemy Prefabs
private Dictionary<EnemyType, GameObject> enemyTypeToObject = new Dictionary<EnemyType, GameObject>();
// A Dictionary with EnemyType as keys to the current number of active enemies of that type in the scene
// Not really used for anything right now, mostly for future consideration.
// Probably could get rid of it though...
private Dictionary<EnemyType, int> activeEnemyNum = new Dictionary<EnemyType, int>();
#endregion
#region Delegates and Events
public delegate void dlgt_Despawn(); // Delegate for the Despawn functions
public static event dlgt_Despawn evnt_DespawnEnemies; // Static Event that all enemies subscribe to for despawning
// Enemy subscribes its "Despawn" function on enable, unsubscribes when disabled //
public delegate void dlgt_ChangeSprite(int spriteIndex); // Delegate for the ChangeSprite function
public static event dlgt_ChangeSprite evnt_ChangeSprite; // Static Event that all enemies subscribe to for changing their sprites
#endregion
// Initialize the Object Pool
private void Awake()
{
InitializeEnemies();
}
private void Start()
{
// Change the enemy sprites to the specified sprite in the enemySprites array
evnt_ChangeSprite(spriteIndex);
// subscribe the "DisableAllEnemies" function here to the GameOver event from the GameState script to handle.
GameState.evnt_OnGameOver += DisableAllEnemies;
}
/// <summary>
/// Depool a specified enemy type from a queue to do as you wish and set it to active
/// Will Instantiate a new GameObject of the enemy type if it can't find one already.
/// </summary>
/// <param name="enemType">The enemy type you wish to spawn</param>
/// <returns>A GameObject from the specified EnemyType queue (or initialized if none found)</returns>
private GameObject DepoolEnemy(EnemyType enemType)
{
activeEnemyNum[enemType]++;
if(enemyDictionary[enemType].Count > 0)
return enemyDictionary[enemType].Dequeue().gameObject;
return Instantiate(enemyTypeToObject[enemType]);
}
/// <summary>
/// Pools the passed enemy back into its specified queue.
/// </summary>
/// <param name="e">The enemy to pool</param>
public void PoolEnemy(Enemy e)
{
if (enemyDictionary.ContainsKey(e.type))
{
enemyDictionary[e.type].Enqueue(e);
activeEnemyNum[e.type]--;
}
}
/// <summary>
/// Initializes the enemy queues/dictionaries for the object pool.
/// Instantiates the enemies in the enemyPrefabs array by the specified enemyPool amount
/// </summary>
private void InitializeEnemies()
{
Enemy e;
EnemyType eType;
GameObject o;
// Go through the enemyPrefabs array one by one
for (int x = 0; x < enemyPrefabs.Length; x++)
{
// Take note of the prefab game object, enemyType
o = enemyPrefabs[x];
eType = o.GetComponent<Enemy>().type;
enemyDictionary.Add(eType, new Queue<Enemy>()); // Add a queue to the enemy dictionary with the specified key of eType
enemyTypeToObject.Add(eType, o); // Add the gameObject to the TypeToObject dictionary
activeEnemyNum.Add(eType, 0); // Initialize the active enemy number for the type
// Instantiate an enemyPool amount of the specified enemy type
for (int a = 0; a < enemyPool; a++)
{
e = Instantiate(o).GetComponent<Enemy>(); // Probably unecessary to GetComponent<Enemy>()
e.gameObject.SetActive(false);
}
}
}
/// <summary>
/// What it says on the tin. Disables all the enemies subscribed to the Despawn Event.
/// This function is subscribed to the onGameOver event from the GameState class.
/// </summary>
private void DisableAllEnemies()
{
evnt_DespawnEnemies();
}
/// <summary>
/// Sets the speed of the enemies
/// </summary>
/// <param name="modifier">The amount to modify the speed of the enemies</param>
public static void SetEnemySpeeds(float modifier)
{
Enemy.speedModifier = modifier;
}
#region Find Position functions
/// <summary>
/// Find position from a SPECIFIED ANGLE around a SPECIFIED ORIGIN at a SPECIFIED RADIUS.
/// </summary>
/// <param name="angle">Angle the point is going to be on in relation to the origin point</param>
/// <param name="origin">Vector2 that we want to find the position around</param>
/// <param name="distToOrg">Distance from the origin point.</param>
/// <returns>Vector2 of the point the given parameters output</returns>
private Vector2 FindPosition(float angle, Vector2 origin, float distToOrg)
{
/* SOH CAH TOA */
float x = (distToOrg * Mathf.Cos(angle * Mathf.Deg2Rad)) + origin.x; // h * cos(angle) = A, plus the X coordinate offset
float y = (distToOrg * Mathf.Sin(angle * Mathf.Deg2Rad)) + origin.y; // h * sin(angle) = O, plus the Y coordinate offset
return new Vector2(x, y);
}
/// <summary>
/// Find position from a RANDOM ANGLE around the origin (0,0). DEFAULT RADIUS of the spawn radius
/// </summary>
/// <returns>Vector2 of the point the given parameters output</returns>
private Vector2 FindPosition()
{
return FindPosition(Random.Range(0, 360), Vector2.zero, radius);
}
/// <summary>
/// Find position from a SPECIFIED ANGLE around the origin (0,0). DEFAULT RADIUS of the spawn radius
/// </summary>
/// <param name="angle">Angle the point is going to be on in relation to the origin point</param>
/// <returns>Vector2 of the point the given parameters output</returns>
private Vector2 FindPosition(float angle)
{
return FindPosition(angle, Vector2.zero, radius);
}
/// <summary>
/// Find position from a RANDOM ANGLE around a SPECIFIED ORIGIN. DEFAULT RADIUS of the spawn radius
/// </summary>
/// <param name="origin">Vector2 that we want to find the position around</param>
/// <returns>Vector2 of the point the given parameters output</returns>
private Vector2 FindPosition(Vector2 origin)
{
return FindPosition(Random.Range(0,360), origin, radius);
}
/// <summary>
/// Find position from a SPECIFIED ANGLE around the origin (0,0) point at a SPECIFIED RADIUS
/// </summary>
/// <param name="angle">Angle the point is going to be on in relation to the origin point</param>
/// <param name="distToOrg">Distance from the origin point.</param>
/// <returns>Vector2 of the point the given parameters output</returns>
private Vector2 FindPosition(float angle, float distToOrg)
{
return FindPosition(angle, Vector2.zero, distToOrg);
}
/// <summary>
/// Find position from a SPECIFIED ANGLE around a SPECIFIED ORIGIN. DEFAULT RADIUS of the spawn radius
/// </summary>
/// <param name="angle">Angle the point is going to be on in relation to the origin point</param>
/// <param name="origin">Vector2 that we want to find the position around</param>
/// <returns>Vector2 of the point the given parameters output</returns>
private Vector2 FindPosition(float angle, Vector2 origin)
{
return FindPosition(angle, origin, radius);
}
/// <summary>
/// Find position from a RANDOM ANGLE around a SPECIFIED ORIGIN at a SPECIFIED DISTANCE.
/// </summary>
/// <param name="origin">Vector2 that we want to find the position around</param>
/// <param name="distToOrg">Distance from the origin point.</param>
/// <returns>Vector2 of the point the given parameters output</returns>
private Vector2 FindPosition(Vector2 origin, float distToOrg)
{
return FindPosition(Random.Range(0,360), origin, distToOrg);
}
#endregion
#region Spawn Enemy functions
/// <summary>
/// Spawn an enemy at a SPECIFIED POSITION, facing a SPECIFIED ANGLE.
/// </summary>
/// <param name="enemyType">The type of enemy to spawn</param>
/// <param name="enemyPos">The position the enemy should be spawned in</param>
/// <param name="facingAngle">What angle the enemy will face</param>
/// <returns>The Game Object of the enemy that spawned</returns>
private GameObject SpawnEnemy(EnemyType enemyType, Vector2 enemyPos, float facingAngle)
{
GameObject obj = DepoolEnemy(enemyType);
obj.transform.position = enemyPos;
obj.transform.rotation = Quaternion.AngleAxis(facingAngle, Vector3.forward);
obj.SetActive(true);
return obj;
}
/// <summary>
/// Spawn an enemy at a SPECIFIED ANGLE. This will spawn at the DEFAULT RADIUS around the DEFAULT ORIGIN (0,0)
/// assumes the enemy will face towards the origin.
/// </summary>
/// <param name="enemyType">The type of enemy to spawn</param>
/// <param name="spawnAngle">What angle around the origin point the enemy spawns in</param>
/// <returns>The Game Object of the enemy that spawned</returns>
public GameObject SpawnEnemy(EnemyType enemyType, float spawnAngle)
{
return SpawnEnemy(enemyType, FindPosition(spawnAngle, Vector2.zero, radius), spawnAngle + 180);
}
/// <summary>
/// Spawn an enemy at and facing a SPECIFIED ANGLE. This will spawn at the DEFAULT RADIUS around the DEFAULT origin (0,0).
/// </summary>
/// <param name="enemyType">The type of enemy to spawn</param>
/// <param name="spawnAngle">What angle around the origin point the enemy spawns in</param>
/// <param name="facingAngle">What angle the enemy will face</param>
/// <returns>The Game Object of the enemy that spawned</returns>
public GameObject SpawnEnemy(EnemyType enemyType, float spawnAngle, float facingAngle)
{
return SpawnEnemy(enemyType, FindPosition(spawnAngle, Vector2.zero, radius), facingAngle);
}
/// <summary>
/// Spawn an enemy at and facing a SPECIFIED ANGLE, at a SPECIFIED RADIUS around the DEFAULT origin (0,0).
/// </summary>
/// <param name="enemyType">Type of enemy to spawn</param>
/// <param name="spawnAngle">What angle around the origin point the enemy spawns in</param>
/// <param name="facingAngle">What angle the enemy will face</param>
/// <param name="distToOrig">Radius distance to spawn from the origin point</param>
/// <returns>The Game Object of the enemy that spawned</returns>
public GameObject SpawnEnemy(EnemyType enemyType, float spawnAngle, float facingAngle, float distToOrig)
{
return SpawnEnemy(enemyType, FindPosition(spawnAngle, Vector2.zero, distToOrig), facingAngle);
}
/// <summary>
/// Spawn an enemy at and facing a SPECIFIED ANGLE, around a DEFAULT RADIUS around a SPECIFIED ORIGIN.
/// </summary>
/// <param name="enemyType">Type of enemy to spawn</param>
/// <param name="spawnAngle">What angle around the origin point the enemy spawns in</param>
/// <param name="orig">The origin point around which to spawn</param>
/// <param name="facingAngle">What angle the enemy will face</param>
/// <returns>The Game Object of the enemy that spawned</returns>
public GameObject SpawnEnemy(EnemyType enemyType, Vector2 orig, float spawnAngle, float facingAngle)
{
return SpawnEnemy(enemyType, FindPosition(spawnAngle, orig, radius), facingAngle);
}
/// <summary>
/// Spawn an enemy at and facing a SPECIFIED ANGLE at a SPECIFIED RADIUS around a SPECIFIED ORIGIN.
/// </summary>
/// <param name="enemyType">Type of enemy to spawn</param>
/// <param name="orig">The origin point around which to spawn</param>
/// <param name="spawnAngle">What angle around the origin point the enemy spawns in</param>
/// <param name="facingAngle">What angle the enemy will face</param>
/// <param name="distToOrig">Radius distance to spawn from the origin point</param>
/// <returns>The Game Object of the enemy that spawned</returns>
public GameObject SpawnEnemy(EnemyType enemyType, Vector2 orig, float spawnAngle, float facingAngle, float distToOrig)
{
return SpawnEnemy(enemyType, FindPosition(spawnAngle, orig, distToOrig), facingAngle);
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment