Reducing Lag

Hi All,

My game is basically complete but I built it and put it on my phone the other day and it has some lag issues. I’m quite new to programming but Ive looked around to see what could solve lag. I found that reducing the things in Update help and also reducing texture quality. I think that my problem is with Update because I have a fair amount of things being spawned that are using update.

I am wondering if there is a way to have the same spawning function that I have, yet reduce the amount of things running in update?

Here is one of my spawner scripts: (Ive shortened it so it will be easier to look through)

public GameObject player;
    public GameObject[] bottomLaneObjects;
    public GameObject[] topLaneObjects;

    [Space(35)]
    [Header ("Top Timers")]
    public float lTSpawnStartTimer = 1.0f;
    public float mTSpawnStartTimer = 1.0f;
    public float rTSpawnStartTimer = 1.0f;
    [Space(10)]
    [Header ("Bottom Timers")]
    public float lSpawnStartTimer = 1.0f;
    public float mSpawnStartTimer = 1.0f;
    public float rSpawnStartTimer = 1.0f;
    [Space(25)]
    public float tSpawnRateStart;    
    public float tSpawnRateEnd;
    [Space(10)]
    public float bSpawnRateStart;   
    public float bSpawnRateEnd;
    [Space(25)]
    public float SpawnRange;    
    [Space(10)]
    public float cZValue1;      
    public float cZValue2;
    public float cZValue3;
    [Space(10)]
    public float cYValue1;    
    public float cYValue2;      



    void Start ()
    {
        StartCoroutine (IncreaseCloudRates ());
    }


    void Update ()
    {
        //BOTTOM
        //Left
        lSpawnStartTimer -= Time.deltaTime;
        if (lSpawnStartTimer < 0.01 && GameInit.gameIsPlaying == true)
        {
            BottomLeftSpawn ();
        }
        //Middle
        mSpawnStartTimer -= Time.deltaTime;
        if (mSpawnStartTimer < 0.01 && GameInit.gameIsPlaying == true)
        {
            BottomMiddleSpawn ();
        }
        //Right
        rSpawnStartTimer -= Time.deltaTime;
        if (rSpawnStartTimer < 0.01 && GameInit.gameIsPlaying == true)
        {
            BottomRightSpawn ();
        }

        //TOP
        //Top Left
        lTSpawnStartTimer -= Time.deltaTime;
        if (lTSpawnStartTimer < 0.01 && GameInit.gameIsPlaying == true)
        {
            TopLeftSpawn ();
        }
        //Top Middle
        mTSpawnStartTimer -= Time.deltaTime;
        if (mTSpawnStartTimer < 0.01 && GameInit.gameIsPlaying == true)
        {
            TopMiddleSpawn ();
        }
        //Top Right
        rTSpawnStartTimer -= Time.deltaTime;
        if (rTSpawnStartTimer < 0.01 && GameInit.gameIsPlaying == true)
        {
            TopRightSpawn ();
        }
    }


    //BOTTOM
    void BottomLeftSpawn ()
    {
        Instantiate (bottomLaneObjects [(Random.Range (0, bottomLaneObjects.Length))], new Vector3 (player.transform.position.x + SpawnRange, cYValue1, cZValue1), Quaternion.identity);
        lSpawnStartTimer = Random.Range (bSpawnRateStart, bSpawnRateEnd);
    }
    void BottomMiddleSpawn ()
    {
        //Instantiate (bottomLaneObjects.....
    }
    void BottomRightSpawn ()
    {
        //Instantiate (bottomLaneObjects.....
    }


    //TOP
    void TopLeftSpawn ()
    {
        Instantiate (topLaneObjects [(Random.Range (0, topLaneObjects.Length))], new Vector3 (player.transform.position.x + SpawnRange, cYValue2, cZValue1), Quaternion.identity);
        lTSpawnStartTimer = Random.Range (tSpawnRateStart, tSpawnRateEnd);
    }
    void TopMiddleSpawn ()
    {
        //Instantiate (topLaneObjects.....
    }
    void TopRightSpawn ()
    {
        //Instantiate (topLaneObjects.....
    }

    IEnumerator IncreaseCloudRates ()
    {
        yield return new WaitForSeconds (30);
        if (player.GetComponent<Player_Collisions>().enabled == true)
        {
            tSpawnRateStart -= 1;
            tSpawnRateEnd -= 1;
            bSpawnRateStart -= 0.25f;
            bSpawnRateEnd -= 0.5f;
            Debug.Log ("Cloud Increase I");
        }

        //3 other Spawn Rate increases
    }
}

Thanks in advance for the help! :slight_smile:

If you are spawning tons of objects and each is running it’s own update, you’ll want to consider a manager script that handles updating for you instead of each object doing it’s own update.

Talks a little bit about how the two methods differ.

Ok, so what I’m getting from this is that I need to put my functions in a list and then have that list in update, right?

Or do I need to add a script with protected virtual void Update() {} to act as the “manager script”?

Here is another place that I think that could be causing lag. Each of the objects that are getting spawned have this script on them to destroy them after X amount of time.

    public float timeTillDestroy = 10;


    void Start ()
    {
        if (GameInit.gameIsPlaying == true)
        {
            StartCoroutine (DestroyThis ());
        }
    }
      
IEnumerator DestroyThis ()
    {
        yield return new WaitForSeconds (timeTillDestroy);

        if (GameInit.gameIsPlaying == true)
        {
            Destroy (gameObject);
            Debug.Log ("Destroyed");
        }
    }
}

I used to have this running with an Update function but then switched it to use a coroutine to see if that helped but there was no improvement.

First you should try to get used to profiling your game. That’ll help to understand which parts of your code cause drops in performance. You’ll also get an insight on the amount of garbage that is being collected all the time.

Next thing to check would be whether it’s worth implementing a small pooling system for your objects. It’s not always an enormous benefit, but you appear to permanently instantiate and destroy objects. This might improve this at least a little.

The coroutines seem to be just fine, unless you got actually tons of them running at the same time (we don’t have any numbers of gameobjects in your scenes).

With that pattern you’d have one script that tracks all instances that need to be updated. Either per script type, per group or just everything. Note that you lose the execution order if you update instances of different types with a single manager, unless you take care of that. You’ll also need to remove destroyed instances, take care of disabled instances etc.

You just need a normal Update method in the manager and it should work. However, the scripts to be updated by the manager must then rename the Update method, otherwise it’ll still be called by the engine.

2 Likes

I would say that there is around 35 - 45 objects in the scene at once with about 30 - 40 of those being the objects that are being spawned and deleted after X amount of time.

For the Update Manager, I found an update manager in the asset store that Brathnann was talking about before. Ive been playing around with it, but haven’t succeeded in getting everything hooked up correctly (quite new to programming).

Here is what he left in the “Read Me”

To use this Update manager all you need to do is let your class inherit from OverridableMonoBehaviour

if you use the Awake function you'll have to change it into protected override void Awake()
Also call the OverridableMonoBehaviour.Awake() function on the first line of your Awake function.

You'll have to replace your Update method to public override void UpdateMe()
Once you've done that you'll be able to use this package.

To remove objects from your UpdateManager, all you need to do is all the methods UpdateManager.RemoveSpecificItem or UpdateManager.RemoveSpecificItemAndDestroyIt
These method names should speak for themselves.

If somehow your object does not get added to the Update manage you can still add it by using the UpdateManager.AddItem method

Please keep in mind to not destroy more than 2.000 objects at the same time, this will unfortunately decrease the performance for a few frames.
Adding up to 10.000 objects on your Awake function is possible, but will take some time before your game will actually start.

Here is what I thought should be happing in order to get this manager to work on one of my spawners:

public abstract class TestSpawnerx : OverridableMonoBehaviour
{
//Variables

public override void UpdateMe ()
    {
//BOTTOM
        //Left
        lSpawnStartTimer -= Time.deltaTime;
        if (lSpawnStartTimer < 0.01 && GameInit.gameIsPlaying == true)
        {
            BottomLeftSpawn ();
        }
        //and so on
     }

Edit:

Don’t know if this info is needed, but here is the update manager script in case I need to add something here:

public class UpdateManager : MonoBehaviour
{
    private static UpdateManager instance;

    private int count = 0;
    private OverridableMonoBehaviour[] array;

    public UpdateManager()
    {
        instance = this;
    }

    public void Start ()
    {
        DontDestroyOnLoad (gameObject);
    }

    public static void AddItem(OverridableMonoBehaviour behaviour)
    {
        instance.AddItemToArray(behaviour);
    }

    public static void RemoveSpecificItem(OverridableMonoBehaviour behaviour)
    {
        instance.RemoveSpecificItemFromArray(behaviour);
    }

    public static void RemoveSpecificItemAndDestroyIt(OverridableMonoBehaviour behaviour)
    {
        instance.RemoveSpecificItemFromArray(behaviour);

        Destroy(behaviour.gameObject);
    }

    private void AddItemToArray(OverridableMonoBehaviour behaviour)
    {
        if(array == null)
        {
            array = new OverridableMonoBehaviour[1];
        }
        else
        {
            System.Array.Resize(ref array, array.Length + 1);
        }
        array[array.Length - 1] = behaviour;
        count = array.Length;
    }

    private void RemoveSpecificItemFromArray(OverridableMonoBehaviour behaviour)
    {
        int addAt = 0;
        OverridableMonoBehaviour[] tempArray = new OverridableMonoBehaviour[array.Length - 1];

        for(int i = 0; i < array.Length; i++)
        {
            if(array[i] == null)
            {
                continue;
            }
            else if(array[i] == behaviour)
            {
                continue;
            }
            tempArray[addAt] = array[i];
            addAt++;
        }

        array = new OverridableMonoBehaviour[tempArray.Length];

        for (int i = 0; i < tempArray.Length; i++)
        {
            array[i] = tempArray[i];
        }

        count = array.Length;
    }

    private void Update()
    {
        if (count > 0)
        {
            for (var i = 0; i < array.Length; i++)
            {
                if (array[i] == null)
                {
                    continue;
                }
                array[i].UpdateMe();
            }
        }
    }
}

Rather than creating and destroying objects could you pool them in your game and save on the garbage collection?

I’ve looked into pooling and watched a few tutorials on it. I put together this script from one of them, but it is only pooling one object:

    public static PoolControl poolcontrol;

    public GameObject pooledObject;
    public int pooledAmount = 20;

    List<GameObject> pooledObjects;

   void Awake ()
   {
       poolcontrol = this;
   }

    void Start ()
    {
        //Add objects into the scene
        pooledObjects = new List<GameObject> ();
        for (int i = 0; i < pooledAmount; i++)
        {
            GameObject obj = (GameObject)Instantiate (pooledObject);
            obj.SetActive (false);
            pooledObjects.Add (obj);
        }
    }

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

Instead of just one “public GameObject pooledObject;” i need multiple objects, like an array of them or a list that then go into the pool. Is there a way to do this? It looks like I could create multiple variables for multiple objects and go about things that way, but in my spawner I need to be randomly selecting one of those objects every time I spawn something.

This

This

This.

None of this discussion has any real meaning until you profile it.

1 Like

Sorry for the lack of info, but I did after he recommended it and saw that there were some problems due to graphics. So, I lowered the quality of everything, but still had lag issues. The profiling also showed some major spikes when the Update functions would spawn a fair amount of objects around the same time, hence, why I am trying to learn how to pool my objects. Im very new to this, so if you need more info on the profile, just let me know.

So back to my question that hopefully has a simple answer. Is there a way to put a few different objects into the same pool? Right now, my pooling script only holds one object. I need something like an array or list. Ive tried to replace the singular GameObject variable with an array and also a list, but haven’t gotten it to work (quite new to programming). I’ve looked through a few more tutorials and the docs to see if I could solve the problem, but no luck.

Here is my pooling script again:

public static PoolControl poolcontrol;
    public GameObject pooledObject;   //this needs to be an array/list or something with similar functionality.
    public int pooledAmount = 20;
    List<GameObject> pooledObjects;
   void Awake ()
   {
       poolcontrol = this;
   }
    void Start ()
    {
        //Add objects into the scene
        pooledObjects = new List<GameObject> ();
        for (int i = 0; i < pooledAmount; i++)
        {
            GameObject obj = (GameObject)Instantiate (pooledObject);
            obj.SetActive (false);
            pooledObjects.Add (obj);
        }
    }
    public GameObject GetPooledObject ()
    {
        for (int i = 0; i < pooledObjects.Count; i++)
        {
            if (!pooledObjects [i].activeInHierarchy)
            {
                return pooledObjects [i];
            }
        }
    }

When you say it doesn’t work, what problem are you running into?

If I replace line 2 with

public GameObject[] pooledObject;

I get error an error in line 15. (same thing happens if I use a list)

Error: UnityEngine.GameObject[ ]' cannot be used as type parameter T’ in the generic type or method

Im just not to sure how to go about implementing the array in this line correctly.

Stacks are useful for pooling. Just cache the available objects in a stack. If you need an instance, just ‘pop’ it or create a new one if there is currently no object in the stack. Can be extended to be way more complex and provide more options and functionalities, but just start with the most trivial version. If you use stacks, you’ll avoids the iterations + the availability checks.

Whenever an instance is no longer needed, put it back so that it can be used again.

1 Like

Are you using textures? What resolution are they?

I’m confused. Why do you need a list of pooledObject if you have the list pooledObjects?

If pooledObject is a single item, then you are instantiating a single object. You can’t instantiate a list of objects. You are looping through and creating 20 objects and adding them to a list which I assume was meant to be your object pool.

1 Like

First off, thank you for the help!!

I need to have different objects in the pool. For example, clouds, planes, and UFOs. I really don’t know how to go about this properly, if I need one pool with different objects in them or have multiple pools containing a different object. The dilemma is that I need to be choosing a random object to enable in the pool(s) (Ex. cloud, plane, or ufo).

Here is what I I am trying to do with a pool (this is a snippet of the old way that is causing lag. Not using a pool):

  public GameObject[] bottomLaneObjects;

public void Update ()
    {
        lSpawnStartTimer -= Time.deltaTime;
        if (lSpawnStartTimer < 0.01 && GameInit.gameIsPlaying == true)
        {
            BottomLeftSpawn ();
        }
    }

void BottomLeftSpawn ()
    {                                  //this (below) is where I am choosing a random object in the array to spawn
        Instantiate (bottomLaneObjects [(Random.Range (0, bottomLaneObjects.Length))], new Vector3 (player.transform.position.x + SpawnRange, cYValue1, cZValue1), Quaternion.identity);
        lSpawnStartTimer = Random.Range (bSpawnRateStart, bSpawnRateEnd);
    }

No textures, just a plain white material with a lowered opacity (the objects are clouds).

Thank you for all of your help! :slight_smile: Ive never used stacks or pools before so this is all new to me :face_with_spiral_eyes: (and this is my first game)

I’m looking into stacks right now. From what I’ve seen so far, I haven’t found a way to push a random object in my pool. In my reply above to Brathnann, I explained what I need to be doing with this pool. So my question about the stacks is that is there a way to push a random object in my pool and then pop it? The script in the reply to Brathnann shows the idea of what I would need the stack to do.

Edit: Quick Q on the profiling. Is there a way to profile specific scripts/parts of code? I want to make sure that the spawning is the biggest part of my lag problem in my scripts.

Oh I see. Now the public field with the comment in the other post does make some sense, too. I thought you’re going to work with a single prefab for everything (which would’ve been pretty trivial).

So just to grasp your idea:
You have different lanes, say RightLane, MidLane, LeftLane.
All of them require different prefabs to be spawned, so 3 prefabs for now.
But you want to randomize the objects for each lane as well, so N prefabs to choose from per lane?
And on top of that, everything should get pooled.

Have you tried to enable deep profiling?

1 Like

Your on the right track with how the game works. :stuck_out_tongue:

So here is how the game works. The player is a plane that has 6 lanes total (3 bottom (left, center, right), 3 Top(left, center, right)). The function is very similar to a game like SubwaySurfers, except instead of jumping up, the player moves to the top lane, swipe left and right to move to left, right, center. Clouds and planes are the objects that I am spawning in the lanes and I have a few different cloud prefabs, so this is where my issue with adding different objects to the pool is. And yes, the objects in each lane should be randomly chosen out of the pool.

I enabled deep profiling. I attached my results: (I don’t know if there is a way to look at a period of time rather than one frame)

2985162--222125--Screen Shot 2017-03-07 at 4.01.45 PM.png