# (2D) Simple AI is not working when Object pooling is used

I am building a top down 2D plane game for practice. Here, enemy has an AI script attached. AI script does the following :

1. Determine whether the object is spawned to the left or right of screen.
2. If spawned to the left, rotate object by -90, move till a certain x position is reached, then turn down.
1. If spawned to the right, rotate object by 90, move till a certain x position is reached, then turn down.

This works when I'm using Instantiate - Destroy method. But causes inconsistent behavior when I implement object pooling. Inconsistent behavior as in, 1>Planes are spawned, 2>Instead of moving a certain distance horizontally, they just turn down and move downwards. Sometimes they behave as intended, but other times, inconsistenct behavior.

Here is the AI script:

public class AI : MonoBehaviour
{
float angle;
Vector2 vel, turnVel;
Rigidbody2D rb;
bool leftSpawn, rightSpawn;

void OnEnable ()
{
transform.rotation = Quaternion.Euler (Vector3.zero);
rb = GetComponent<Rigidbody2D> ();
turnVel = new Vector2 (0, -0.5f);
if (transform.position.x < 0) {
leftSpawn = true;
transform.Rotate (0, 0, -90);
vel = new Vector2 (1, 0);
}

if (transform.position.x > 0) {
rightSpawn = true;
transform.Rotate (0, 0, 90);
vel = new Vector2 (-1, 0);
}
}

void Update ()
{

if (leftSpawn && transform.position.x < -0.8f)
rb.velocity = vel;

if (rightSpawn && transform.position.x > 0.8f) {
rb.velocity = vel;
}

if (rightSpawn && transform.position.x <= 0.8f) {
rb.velocity = turnVel;
angle = Mathf.LerpAngle (90, 180, Time.time * 0.9f);
transform.eulerAngles = new Vector3 (0, 0, angle);
}

if (leftSpawn && transform.position.x > -0.8f) {
rb.velocity = turnVel;
angle = Mathf.LerpAngle (-90, -180, Time.time * 0.9f);
transform.eulerAngles = new Vector3 (0, 0, angle);
}
}
}


Here is the spawning script that doesn't use Pooling. This works fine.

public class EnemySpawner : MonoBehaviour
{
int turn = 0;
public GameObject BF, JU, cam, Hawker, Blenheim;

void Start ()
{
InvokeRepeating ("SpawnEnemy", 0, 3);
}

void SpawnEnemy ()
{

turn = Random.Range (1, 5);

if (turn == 1)
SpawnBF ();
else if (turn == 2)
SpawnJU ();
else if (turn == 3)
SpawnHawker ();
else if (turn == 4)
SpawnBlenheim ();
}

void SpawnHawker ()
{

float x = -4.47f, y;

y = cam.transform.position.y + 4.59f;

Vector3 pos = new Vector3 (x, y, 1);

GameObject obj = (GameObject)Instantiate (Hawker, pos, Quaternion.identity);

}

void SpawnBlenheim ()
{
float x = 4.39f, y;

y = cam.transform.position.y + 4.59f;

Vector3 pos = new Vector3 (x, y, 1);

GameObject obj = (GameObject)Instantiate (Blenheim, pos, Quaternion.identity);
}

void SpawnBF ()
{
float x = -4.47f, y;

y = cam.transform.position.y + 4.59f;

Vector3 pos = new Vector3 (x, y, 1);

GameObject obj = (GameObject)Instantiate (BF, pos, Quaternion.identity);

}

void SpawnJU ()
{
float x = 4.39f, y;

y = cam.transform.position.y + 4.59f;

Vector3 pos = new Vector3 (x, y, 1);

GameObject obj = (GameObject)Instantiate (JU, pos, Quaternion.identity);
}
}


This is the script that pools Enemy planes:

public class EnemiesPooler : MonoBehaviour
{
public static EnemiesPooler current;
public GameObject BF, JU, Hawker, Blenheim;
int pooledAmount = 10;
List<GameObject> BFPlane;
List<GameObject> JUPlane;
List<GameObject> HawkerPlane;
List<GameObject> BlenheimPlane;

void Start ()
{
BFPlane = new List<GameObject> ();
JUPlane = new List<GameObject> ();
HawkerPlane = new List<GameObject> ();
BlenheimPlane=new List<GameObject>();
current = this;

for (int i=0; i<pooledAmount; i++) {
GameObject bf = (GameObject)Instantiate (BF,Vector3.zero,Quaternion.identity);
bf.SetActive (false);
BFPlane.Add (bf);

GameObject ju = (GameObject)Instantiate (JU,Vector3.zero,Quaternion.identity);
ju.SetActive (false);
JUPlane.Add (ju);

GameObject hawker = (GameObject)Instantiate (Hawker,Vector3.zero,Quaternion.identity);
hawker.SetActive (false);
HawkerPlane.Add (hawker);

GameObject blenheim = (GameObject)Instantiate (Blenheim,Vector3.zero,Quaternion.identity);
blenheim.SetActive (false);
BlenheimPlane.Add (blenheim);

}

}

public GameObject GetBF ()
{
for (int i=0; i<BFPlane.Count; i++) {
if (!BFPlane [i].activeInHierarchy)
return BFPlane [i];
}

return null;
}

public GameObject GetJU ()
{
for (int i=0; i<JUPlane.Count; i++)
if (!JUPlane [i].activeInHierarchy) {
return JUPlane [i];
}
return null;
}

public GameObject GetHawker ()
{
for (int i=0; i<HawkerPlane.Count; i++)
if (!HawkerPlane [i].activeInHierarchy) {
return HawkerPlane [i];
}
return null;
}

public GameObject GetBlenheim ()
{
for (int i=0; i<BlenheimPlane.Count; i++)
if (!BlenheimPlane [i].activeInHierarchy) {
return BlenheimPlane [i];
}
return null;
}
}


Here is the script that spawns using Object Pooling. This is the one causing inconsistent behavior.

public class PoolTest : MonoBehaviour {
int turn=0;
float left=-4.47f,right=4.39f,yPos=4.59f,zpos=1,y,rotClockwise=270,rotCounterClockwise=-270;
public Transform cam;

// Use this for initialization
void Start () {
InvokeRepeating ("SpawnEnemy",0,3);

}

void SpawnEnemy()
{

turn = Random.Range (1, 5);

if(turn == 1)
SpawnBF ();
else if (turn == 2)
SpawnJU ();
else if (turn == 3)
SpawnHawker ();
else if (turn == 4)
SpawnBlenheim ();
}

void SpawnBF()
{

int spawnSide;
Vector3 pos;
float rot;

y = cam.position.y + yPos;
spawnSide = Random.Range (0,2);
if (spawnSide == 0) {

pos = new Vector3 (left, y, zpos);

}
else {
pos = new Vector3 (right, y, zpos);

}

GameObject obj=EnemiesPooler.current.GetBF();
if (obj == null)
return;

obj.transform.position=pos;
obj.SetActive (true);
}

void SpawnJU()
{

int spawnSide;
Vector3 pos;

spawnSide = Random.Range (0,2);
if (spawnSide == 0)
pos = new Vector3 (left, y, zpos);
else
pos = new Vector3 (right,y,zpos);

GameObject obj=EnemiesPooler.current.GetJU();
if (obj == null)
return;
obj.transform.position=pos;
obj.SetActive (true);

}

void SpawnHawker()
{

int spawnSide;
Vector3 pos;

spawnSide = Random.Range (0,2);
if (spawnSide == 0)
pos = new Vector3 (left, y, zpos);
else
pos = new Vector3 (right,y,zpos);

GameObject obj=EnemiesPooler.current.GetHawker();
if (obj == null)
return;

obj.transform.position=pos;
obj.SetActive (true);

}

void SpawnBlenheim()
{

int spawnSide;
Vector3 pos;

spawnSide = Random.Range (0,2);
if (spawnSide == 0) {
pos = new Vector3 (left, y, zpos);
}
else
pos = new Vector3 (right,y,zpos);

GameObject obj=EnemiesPooler.current.GetBlenheim();
if (obj == null)
return;

obj.transform.position=pos;
obj.SetActive (true);

}

void Update () {
y = cam.position.y + yPos;
}
}


I have spent a lot of time trying to figure out what is causing this inconsistency. But, couldn't find any solution. Please help me. Thanks.

## 2 Answers

The trick about pooling complex/customizable objects is to reset their state before returning to a pool. So when you fetch from a pool, fetched object always be the same as instantiated one. I don't see any code that return object to pool.

I may suppose that when you init your pooler component some of AI get working before you disable objects. Between this two lines:

        GameObject bf = (GameObject)Instantiate (BF,Vector3.zero,Quaternion.identity);
bf.SetActive (false);


Right after Instantiation of bf object bf.OnEnable() method was called causing the object to do some code and rotate. After that bf.SetActive (false) is called, but it's too late.

To check this out, add some debug code to your EnemiesPooler.Start() method:

void Start ()
{
BFPlane = new List<GameObject> ();
JUPlane = new List<GameObject> ();
HawkerPlane = new List<GameObject> ();
BlenheimPlane=new List<GameObject>();
current = this;

for (int i=0; i<pooledAmount; i++) {
Debug.Log("Instantiation"); // here
GameObject bf = (GameObject)Instantiate (BF,Vector3.zero,Quaternion.identity);
Debug.Log("Instantiated. Deactivation"); // here
bf.SetActive (false);
Debug.Log("Deactivated"); // and here
BFPlane.Add (bf);

GameObject ju = (GameObject)Instantiate (JU,Vector3.zero,Quaternion.identity);
ju.SetActive (false);
JUPlane.Add (ju);

GameObject hawker = (GameObject)Instantiate (Hawker,Vector3.zero,Quaternion.identity);
hawker.SetActive (false);
HawkerPlane.Add (hawker);

GameObject blenheim = (GameObject)Instantiate (Blenheim,Vector3.zero,Quaternion.identity);
blenheim.SetActive (false);
BlenheimPlane.Add (blenheim);

}

}


And in the AI.OnEnable() method:

void OnEnable ()
{
transform.rotation = Quaternion.Euler (Vector3.zero);
rb = GetComponent<Rigidbody2D> ();
turnVel = new Vector2 (0, -0.5f);
if (transform.position.x < 0) {
leftSpawn = true;
transform.Rotate (0, 0, -90);
Debug.Log("Turned -90*"); // here
vel = new Vector2 (1, 0);
}

if (transform.position.x > 0) {
rightSpawn = true;
transform.Rotate (0, 0, 90);
Debug.Log("Turned 90*"); // and here
vel = new Vector2 (-1, 0);
}
}


After that run your code and you will see sequence of messages in the console. I suppose that between 'Instantiation' and 'Deactivated' will be 'Turned' message.

After some thinking, I decided to use the EnemySpawner script only to fetch planes. I moved the positioning logic to the AI script, so positioning, rotation and movement would be up to the spawned plane.