# How to balance spawn system with multiple objects?

I'm trying to make a top-down endless runner.

I have divided my screen into 3 columns. Right now I have some objects spawned from the bottom of the screen that go up towards the top. I have 3 different types and each of them has a random speed between 4 and 8 and can be spawned at intervals that vary from 0.2f to 2f.

I've noticed though that it may happen that these objects are spawned in a way that it is impossible for the player to dodge them (all the 3 columns are occupied by these obstacles on the same line).

Is there a way to check if this scenario is going to happen in the next wave and to, for instance, destroy one obstacle out of those. How could this be done?

Precaution

What i'm going to write here is not a solution to your problem, in my solution, the movement speed of obstacles is constant. Consider it to be a simplified case of your. I just want to show you a possible way to solve this kind of problem.

First you need to define the distance (the space) between 2 obstacle of 2 different columns that will allow the player to dodge. Let's say:

float safeDistance = 10f;


Next you create an array/list that holds references to the last object spawned on each column, like:

Transform[] lastObstacles = new Transform[3];


Make an event that's fired anytime an obstacle is spawned, update the list

void OnObstacleSpawned (Transform obstacle, int onColumn )
{
lastObstacles [onColumn] = obstacle;
}


Assuming that the obstacles move along the vertical axis OY, the distance from the spawn point to the closest obstacle on given column can be easily calculated by

float DistanceSpawnPointToLastObstacle(int column)
{
if( lastObstacles[column] == null ) return float.MaxValue;
return Mathf.Abs(lastObstacles[column].position.y - spawnPoint.position.y);
}


or between two obstacles

float DistanceBetweenObstacles(int column01, int columnt02)
{
if( lastObstacles[column01] == null ) return float.MaxValue;
if( lastObstacles[columnt02] == null ) return float.MaxValue;
return Mathf.Abs(lastObstacles[column01].position.y  - lastObstacles[columnt02].position.y);
}


And now make a condition check function that gives the answer that you want to know ;)

bool ShouldISpawnObstalce(int onColumn)
{
switch (onColumn)
{
case (0):
// there is already space to dodge
if (DistanceBetweenObstacles(1, 2) > safeDistance) return true;
// if not then check if there would be enough space between obstacles on column 0 and on column 1
if (DistanceSpawnPointToLastObstacle(1) > safeDistance) return true;
break;
case (1):
if (DistanceSpawnPointToLastObstacle(0) > safeDistance) return true;
if (DistanceSpawnPointToLastObstacle(2) > safeDistance) return true;
break;
case (2):
if (DistanceBetweenObstacles(0, 1) > safeDistance) return true;
if (DistanceSpawnPointToLastObstacle(1) > safeDistance) return true;
break;

}
return false;
}


Hope this somehow helps.

Edit 01: The whole code block:

// assign in Inspector, spawn points for column 0-1-2
public Transform[] spawnPoints;

public Transform[] obstacles;

private Transform[] lastObstacles = new Transform[3];
private float safeDistance = 10f;

public void SpawnObstacle()
{
int randomColumn = RandomFunction();

if(ShouldISpawnObstalce(randomColumn))
{
int randomObstacleType = RandomFuntion02();
var obstacleToSpawn = obstacles[randomObstacleType];
var spawnedObject = Instantiate(obstacleToSpawn);

// do set-up/ initialization for obstacle here
spawnedObject.position = spawnPoints[randomColumn].position;

//
OnObstacleSpawned(spawnedObject, randomColumn); // you can make an event here for further use
}
}

void OnObstacleSpawned(Transform obstacle, int onColumn)
{
lastObstacles[onColumn] = obstacle;
}

float DistanceSpawnPointToLastObstacle(int column)
{
if (lastObstacles[column] == null) return float.MaxValue;
return Mathf.Abs(lastObstacles[column].position.y - spawnPoints[column].position.y);
}

float DistanceBetweenObstacles(int column01, int columnt02)
{
if (lastObstacles[column01] == null) return float.MaxValue;
if (lastObstacles[columnt02] == null) return float.MaxValue;
return Mathf.Abs(lastObstacles[column01].position.y - lastObstacles[columnt02].position.y);
}

bool ShouldISpawnObstalce(int onColumn)
{
switch (onColumn)
{
case (0):
// there is already space to dodge
if (DistanceBetweenObstacles(1, 2) > safeDistance) return true;
// if not then check if there would be enough space between obstacles on column 0 and on column 1
if (DistanceSpawnPointToLastObstacle(1) > safeDistance) return true;
break;
case (1):
if (DistanceSpawnPointToLastObstacle(0) > safeDistance) return true;
if (DistanceSpawnPointToLastObstacle(2) > safeDistance) return true;
break;
case (2):
if (DistanceBetweenObstacles(0, 1) > safeDistance) return true;
if (DistanceSpawnPointToLastObstacle(1) > safeDistance) return true;
break;

}
return false;
}


RandomFunction of course should return a valid value, like an int from 0 to 3 (exclusive) in case of random column number or an int from 0 to obstacleToSpawn.Length (exclusive) in case of random obstacle choice. Its implementation is all up to you
And dont forget to assign the needed references in inspector.

• Hi, sorry for late response. I'm trying to use your code (thanks for that by the way!) but i keep having this error: NullReferenceExeption and it is referring to the line in which i set the float "DistanceBetweenObstacles". It looks like the lastObstacles array is null (I do call the OnObstacleSpawned every time i spawn one, so it shouldn't be). Do you have any idea why? – Jules Sep 22 '16 at 16:21
• That funtion returns distance between the last obstacles of 2 columns that lie next to each other, but if you haven't spawned anythin on a column yet then we will get a null exception when try to access the last obstacle on that column – tc07 Sep 22 '16 at 16:29
• Just make a null check. If No obstacle has been recorded yet on given column theb space check should return true – tc07 Sep 22 '16 at 16:33
• so i added this just after the switch statement: if (lastObstacles == null) {return true;}. I wouldn't know where else to put it. now i get an error in play mode which says that lastobstacles is null. I understand what you said before about not having spawned anything yet, but now it should (i think) work right? my code to spawn is this: if (ShoudISpawnObstacle (randomSpawnPoint) == true) { Transform obstacle = Instantiate (obstacles [randomObstacle], spawnPoints [randomSpawnPoint].position, Quaternion.identity) as Transform; OnObstacleSpawned (obstacle, randomSpawnPoint); } – Jules Sep 22 '16 at 17:02
• I hope it's not too tedious to read the code that way – Jules Sep 22 '16 at 17:03

At any given moment, you know each obstacle's position and speed and so you can calculate the start and end times of when that obstacle would collide with the player if the player happens to be in the column.

Whenever an obstacle spawns, check all obstacles in the other two columns to see if their start time and end time overlaps the new obstacle's start and end time. If both of the other lanes have at least one obstacle with overlapping start and end times then do not use the new obstacle.

• sorry for late response. I had though about your idea but i didn't/don't know how to put it in action. Mainly I didn't know how to keep track of all the obstacles, but I think s76's idea of columns and arrays could be a nice way to go about doing what you suggested too. Or do you know any better way to use what you said? thank you. – Jules Sep 22 '16 at 16:26
• You could definitely go with s76's idea of storing them in separate data structures as they are spawned or you could create a tag for each column and tag each obstacle as it's created and then use GameObject.FindGameObjectWithTag() when you need to retrieve them. The first option is a little more efficient, but the second is easier. – spectacularbob Sep 22 '16 at 17:30