Hi, i have an endless game and i noticed that after approximatley 50 stages on mobile or 100 or computer it becomes slower. I don't see that on the profiler so my guess was maybe my script has some problems. Everytime i hit the brick i have a new number that i use.(several times) For example: float Random = random.range(0,1); So my question is: Everytime that i use this command it makes me another float or it reuse it? Maybe it's one of the causes for making my game heavy. And i use destroy and inst all time so my game did not become heavier with objects.
If will create a new float on your stack, which will be rolled back as soon is it goes out of scope. This is automatically done via the compiler. Have you profiled your memory? The symptoms do seem to point towards a memory leak.
Edit: The whole game doesnt become slower, just when i hit the brick.(from time to time it becomes slower) this is the part when i touch the brick: Code (CSharp): public void OnTriggerEnter2D(Collider2D other) { if (other.CompareTag("Ground")) //Open new Brick with option to troophy { if (MyCol.enabled) { transform.SetParent(other.transform); LastPosition.transform.position = transform.position; LastPosition.transform.SetParent(other.transform); LastPosition.transform.position = Vector3.Lerp(LastPosition.transform.position, other.transform.position, 20 * Time.deltaTime); //prevent death rehearsal rb.velocity = new Vector3(0, 0, 0); if (LastBrick != other.gameObject) // If im standing on a new brick, so do something { if (Pitcher >= 1.15f) { AM.Play("Extra"); AM.sounds[1].pitch = 0.95f; Pitcher = 0.95f; CountPitcher = -1; GameObject con = Instantiate(Confetti, transform.position + Vector3.up*10 + Vector3.right*2, Quaternion.identity); Destroy(con, 4); } else { AM.Play("Complete"); } AM.sounds[1].pitch += 0.05f; Pitcher += 0.05f; CountPitcher++; UpdateComboBar(); if (Vibrate) { Handheld.Vibrate(); } FakeHP = 10; // FakeHP its for testing how much times the player on the edge and then push him left. // Invoke("NewDestroy", 3); // after Jump to new brick, destroy the last one. StartCoroutine(LastBrick.GetComponent<BrickText>().SelfDestroy()); BeforeLastBrick = LastBrick; LastBrick = other.gameObject; //Set the current brick BrickLevel++; ObstacleIntensity += 0.01f; BrickLevelText.text = "Level: " + BrickLevel.ToString(); int RandomBrick = Random.RandomRange(0, NextBrick.Length); // Which Brick its gonna be if (RandomBrick == 3 && ObstacleIntensity < 0.3f) { RandomBrick = 0; } // If its not on hard mode so set normal brick instead of moving brick. ExtraDistance = 0; if (other.name == "Big Fat Brick(Clone)" || RandomBrick == 1) { ExtraDistance = 2f; }// If it equals to the big fat brick so add Extra Distance; if (other.name == "Big Fat Brick(Clone)" && RandomBrick == 1) { ExtraDistance = 4f; } float RandomX = Random.Range(3.5f + ExtraDistance, 4.5f + ExtraDistance); //Where the next Brick gonna be float RandomY = Random.Range(-2, 3); //Where the next Brick gonna be Vector3 TheOffset = new Vector3(1.4f * RandomX, 1.4f * RandomY, 0); if (!ChallengeMode) { GameObject NewOne = Instantiate(NextBrick[RandomBrick], LastBrick.transform.position + TheOffset, Quaternion.identity); // Instantiate the brick int RandomTroophy = Random.Range(0, 4); // Chance of 25% to get troophy; if (RandomTroophy == 3) { RandomTroophy = Random.Range(0, Troophy.Length); //Here i use the same random to test what troophy gonna be. GameObject Tro = Instantiate(Troophy[RandomTroophy], LastBrick.transform.position + TheOffset + Vector3.up * 5.9f, Quaternion.identity, NewOne.transform); } if (RandomBrick != 2) { float RandomObstacleChance = Random.RandomRange(ObstacleIntensity, 1); if (RandomObstacleChance > 0.9f) // The chance slightly raise. { int RandomObs = Random.Range(0, Obstacles.Length); int RandomDir = Random.Range(-1, 2); GameObject ObstaclePrefab = Instantiate(Obstacles[RandomObs], LastBrick.transform.position + TheOffset + Vector3.up * 6.3f + (Vector3.right * RandomDir), Quaternion.identity, NewOne.transform); } } } } else { AM.sounds[1].pitch = 1; Pitcher = 1; CountPitcher = 0; UpdateComboBar(); } AM.updateSound(); if (ChallengeMode) { GameManager.ChallengeText.text = "Level: " + BrickLevel + "/" + GameManager.HowManyLevels; } other.GetComponent<Animator>().Play("Whitening"); GameObject Particles = Instantiate(Land, GroundCheck.transform.position + Vector3.up / 3, Quaternion.identity); FakeHP -= 1; if (FakeHP < 0) { rb.velocity = new Vector3(-2, 5, 0); FakeHP = 10; } // If im on the edge and cant get away Destroy(Particles, 2); } }
I would like to point out that Destroy does not unallocated the memory. C# is a managed language, so the garbage collector is what's actually responsible for freeing memory, but it should run fairly often. Notice how your GC memory slowly increases, then drops all at once. What happens if instead of instantiating and destroying objects, you pool them? Also, how many audio sources do you have playing at once? I've noticed that for some reason when I have a lot of audio sources playing at once, my frame rate suffers. I think it's more likely something else that's going on rather than memory. Memory leaks don't really cause slowdown until you have to start using virtual memory. I think you may have to simply comment everything out, make sure you don't experience the slowdown, then bit by bit uncomment the code until you find the offender.
I think i have no choise but to pool like you said. I use maximum 2 sounds together. But more important to me is to understand why, can you give me an example for what is garbage collector and how can i ger rid of it?
That's just some editor allocations. They're ~300 bytes are extremely small, and those allocations won't be there in a full build of the game. "GUI" is the old "immediate mode" gui code that is only used by the editor nowadays. That's a topic that has been discussed to death and back over the years. I'll give you a quick run down, but there are literally tens or even hundreds of forum posts discussing the GC, how to avoid it, what it does, its implications for development and so on... Long story short: You allocate objects in your code either directly by using "new SomeClass()" if "SomeClass" is a reference type, that means "class". If its a "struct" then no direct allocations happen. There are also indirect allocations that can happen sometimes. For example if you do new List<int>() you directly allocate space for the list first. But when you add too many elements (more than 4) then the list resizes it internal array to become twice as big to fit the new data and some more. That's an indirect allocation, because you calling List<>.Add does something that requires more memory.. Another example is strings, which cannot be ever changed. string name = "abc" + someOtherString; // Will allocate a new string that is a combination of both name += "asdasdasdad"; // Will also create a new string, discard the old string, and put the new (combined) string into the variable, thus allocating, and generating garbage (the old string before the modification will be garbage collected). Then you also have "hidden" allocations. Those include "boxing", like this: object x = 5; // will box 5 into an object and advanced stuff like capturing variables into lambda expressions... Some quick hints: - Allocations are not what you are concerned about. Instead you are concerned about allocating stuff that you eventually stop using. For example calling Destory() on some gameobject means all the c# allocations that have happened for it have now "gone to waste" essentially, the GC has to clean that stuff up later... - To keep GC to a minimum you can pre-allocate stuff, use pooling for gameobjects, classes, ... - Some garbage is simply unavoidable (when you don't have the code for some library for example). And other times it would be so complicated or tedious to remedy all generated garbage that it is totally not worth it. - In some situations you are able to just not care. For example some systems in my game are very rarely used and situational (the level and script editor). There it's not worth fixing any wasteful allocations until it becomes a problem since level editting is very slow-paced anyway, and a short 100ms lag every minute or so won't bother anyone anyway.
Wow did not know that. So destroy is actually not expensive just at the moment but also after. I should have known that before i built my game but never too late to learn more. And what can be a solution to the string issue? And lets say i want to make an endless game like this one: It would be much harder to make a pool instead of Instantiate and destroy.
Yes, that's why pooling is so effective, you get an already initialized object that can be changed to whatever you need now. And the GC doesn't have to move a lot of objects around in memory since fewer objects have "died". The string issue has no solutions ultimately. You can work with StringBuilder as much as possible. That's what TextMeshPro does, it is really effective that way since you don't need "actual strings". There's one overload like SetText(StringBuilder) which just copies out the characters in there to an internal representation. And it also has SetText(format, arg1, arg2,...) which has special handling. Why? Because string s = 5.3.ToString(); obviously cannot get a string out of nowhere, it has to be allocated, and once it is not used anymore then it becomes garbage that has to be cleaned up eventually. In my exmaple here it is immediately discarded as garbage after passing it to TextMeshPro!! That's why those two methods are so useful. In the first one you can assemble your own pseudo strings in a stringbuilder, and in the second one you can use a never-changing format string that gets values put into it through arguments (it's optimized so it does NOT do the float.ToString() thing of course). But your question was about the general case, like when you're not working with TextMeshPro, right? In that case there's no general solution. You could immitate what TMP does with your own StringBuilder stuff if that makes sense at that moment. But ultimately every "string += " or .ToString() will always allocate many string fragments. And StringBuilder is one of the possible ways to avoid that (and there are many others)
I have been thinking that i could use this method to reallocate my memory: System.GC.Collect(); But it seems like it has no effect. Why?