Search code examples
c#unity-game-enginerecursionobject-pooling

Gaps in Endless Runner "Treadmill" with Object Pooling


I am working on a 3D endless runner game, where the player is stationary, and the background/obstacles move around them. I am using object pooling.

The background is made up of multiple variations of a large "City Block" object, which consists of a 3D flat cube object for the ground. Each City Block object has a different assortment of obstacles on the street, but these objects are children of the parent "CityBlock", and so move with it. These city blocks are overlaid on top of each other off of the screen, and there are two that are the default starting ones, lined up and placed in the screen. When activated, they have a script that simply moves them along the x-axis. When they reach a certain point past the player, they reset back to their starting position off-screen.

I create a list of GameObjects, and populate it in the editor with the City Blocks that are in the scene. The list is then randomized, and "activates" the one at index 0, and then increments and moves on to activate the next one.

Each time the end of the list is reached, I re-randomize it, and loop through again. This way, the game should be truly random, instead of looping through the same loop of objects every iteration.

My problem is that I frequently end up with "gaps" in the background "treadmill", which make up the size of an entire City Block, and the only way I have been able to solve this problem is using recursion, calling the method again if it reaches a City Block that is already active. This creates performance issues on the target mobile devices, and doesn't completely stop the problem (after several iterations, the gaps show up again).

I feel like I am too far down a certain rabbit hole, or am having tunnel vision with this, and there must be a better way to randomize the list of game objects and activate them randomly.

public class CityBlockManager : MonoBehaviour {

public List<GameObject> cityBlockList = new List<GameObject>(); //List is populated in-editor, not through script

public int listSize;

public int counter; //Only made public to be seen in editor


//Use this for initialization
//On initialization, will determine the size of the list, randomize it, and active the first member of that list.
void Start () {

    listSize = cityBlockList.Count - 1;

    RandomizeList();

    cityBlockList[0].GetComponent<CityBlockMover>().isActive = true;

}

//Given the list of any size of game objects, will randomize/shuffle it
void RandomizeList()
{
    for (int i = 0; i <= listSize; i++)
    {
        GameObject temp = cityBlockList[i];
        int randomIndex = Random.Range(i, cityBlockList.Count);
        cityBlockList[i] = cityBlockList[randomIndex];
        cityBlockList[randomIndex] = temp;
    }

}

//Keeps track of how many members of the list have been activated, and if it reaches the full list, will randomize the list over again.
//On each call, will activate the next game object in the list.

public void ActivateNext()
{
    counter++;

    if (counter > listSize || cityBlockList[counter] == null)
    {
        counter = 0;
        RandomizeList(); //Re-randomize list, start over


        //Helps ensure there are no gaps in city block "spawning"
        if(cityBlockList[0].GetComponent<CityBlockMover>().isActive != true)
        {
            cityBlockList[0].GetComponent<CityBlockMover>().isActive = true;
            return;
        }
        else
        {
            ActivateNext();
            return;
        }

    }

    if (cityBlockList[counter].GetComponent<CityBlockMover>().isActive == false && cityBlockList[counter] != null)
    {
        cityBlockList[counter].GetComponent<CityBlockMover>().isActive = true;

    }
    else
    {
        counter++;
        if (counter > listSize || !cityBlockList[counter])
        {
            counter = 0;
            RandomizeList();
            if (cityBlockList[0].GetComponent<CityBlockMover>().isActive != true)
            {
                cityBlockList[0].GetComponent<CityBlockMover>().isActive = true;
            }
            else
            {
                ActivateNext();
                return;
            }
        }
    }
}
}

Solution

  • consider using 2 lists, one list of active blocks and one list of inactive blocks, perhaps the problem will become more clear that way.

    also, if you use only 1 list, the RandomizeList(); becomes very inefficient when it contains only a few inactive blocks, it will be called very often before it will randomly give you an inactive block on the first place - that is also the reason to use 2 lists.