1

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;
            }
        }
    }
}
}
A.Crane
  • 435
  • 1
  • 3
  • 12
  • 1
    suggestions: 1) use `int randomIndex = Random.Range(i+1, cityBlockList.Count);` so you can't swap `i` with `i` - it will be slightly more efficient. 2) use `Debug.Log` to output the list order and every index that `isActive` and also log every call to `RandomizeList();` 3) consider using 2 lists, one list of active blocks and one list of inactive blocks, perhaps the problem will become more clear that way – Jinjinov Jul 26 '18 at 07:07
  • 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 – Jinjinov Jul 26 '18 at 07:14
  • 1) I understand the logic you are going with, though unfortunately, simply changing that line introduced more gaps. I will attempt 2) and 3) and report back! – A.Crane Jul 26 '18 at 20:15
  • Just coming back to confirm that your solution worked, and that I was also dealing with a pesky off-by-one error on top of that that had to do with the size of the list. I would choose your answer as correct, but it is a comment and not an answer. – A.Crane Aug 22 '18 at 20:27
  • i am glad that you solved your problem – Jinjinov Aug 23 '18 at 07:20

2 Answers2

0

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.

Jinjinov
  • 2,554
  • 4
  • 26
  • 45
0

I was able to fix this issue when it occurred for my infinite runner project by making sure the script that moves my environment was called before default time in Project Settings > Script Execution order.

This is because my script that tracked the position of my roads and instantiated new ones was running before my move environment script. Resulting in a stale position when Unity actually does the instantiation.