Back To Home
Optimization
19-03-2024
We spend most of our development time in scripting, so it is very important to learn some best practices . We will be focusing on problems surrounding MonoBehaviours, GameObjects, and its related functionality.
We use GetComponent() to get the component of the GO and it comes with 3 different variations: GetComponent(), GetComponent(typeof(T)) and GetComponent(string). The slowest one of these three is GetComponent(string). It is a very expensive call as it uses a string to search. we can use other two variants as these two have very similar performance.
We should not use it very often as it searches in the scene hierarchy, if the hierarchy is very large then it would take much more time to search and get that component making our game run slow.
We should take direct reference of the objects to the script if possible to avoid using this API. If we need to use it then we should use it in the Awake() or Start() function to initialize the variable.
Do not use this API in those function which are being called continuously.
Avoid using Find , SendMessage and BroadcastMessage functions. These all methods uses string search in the scene hierarchy or in GO’s component.
Avoid using Camera.main, Take the direct reference of the camera. Behind the hood it also calls a similar method like Find() which searches for the tag “Main Camera” in whole scene and returns the first GO it finds.
Strings generates a lot of garbage. Wonder why? Strings are just arrays of character or you can say an array of character is a string, and both string and arrays are immutable in nature means when we change our string its gonna create a new string and the old one becomes garbage. Why is that? Strings are just arrays and these arrays are immutable.
Arrays are always mutable, in that you can assign new values to elements of the array. However the length of an array is immutable. If you need to change the array length, you need to allocate a new array, copy over any elements you want to keep, and assign the new array to your variable. Thankfully you can use StringBuilder to avoid or minimise this garbage allocation.
Don’t get components everytime when needed. Get them only once in the starting and cache them, saving us some CPU overhead each time. The cost is a small amount of additional memory consumption, which is very often worth the price.
Whenever a MonoBehaviour component is first instantiated in our scene, Unity will add any defined callbacks to a list of function pointers, which it will call at key moments. However, it is important to realize that Unity will hook into these callbacks even if the function body is empty. The core Unity Engine has no awareness that these function bodies may be empty and only knows that the method has been defined and, therefore, that it must acquire it and then call it when necessary.
Consequently, if we leave empty definitions of these callbacks scattered throughout the code base,then they will waste a small amount of CPU due to the overhead cost of the engine invoking them. So dont leave any unity function empty whether its Start(),Awake(),Update() etc.
Whenever we perform a null check against a GameObject will result in some unnecessary performance overhead.
we should avoid :
if (gameObject != null) { // do work }
if (!System.Object.ReferenceEquals(gameObject, null)) { // do work }
Gameobject has two string properites “name” and “tag”. We should avoid their use during gameplay. It takes additional memory allocation Whenever we use them.
if ( gameObject.tag == “Player”) { // do something }
Instead we should use CompareTag() to compare tags of object. It is well optimized method to do this task and doesnt need extra memory.
It is often a better practice to identify objects by their components and class types and to identify values that do not involve string objects.
C# offers many different data structures to use and we shouldn’t become too accustomed to using the same ones over and over again because they are commonly used everywhere. Pick the correct data structure that is required or most suitable to perform the task.
Unity uses a memory buffer to store parent-child gameobjects. Its more like a dynamic array. Unity attempts to store all Transforms that share the same parent sequentially in memory inside a pre-allocated memory buffer and are sorted by their depth in the Hierarchy window beneath the parent.
if we re-parent GameObject to another one, the parent must fit the new child within its pre-allocated memory buffer as well as sorting all of these Transforms based on the new depth. Also, if the parent has not pre-allocated enough space to fit the new child, then it must expand its buffer to be able to fit the new child, and all of its children, in depth-first order. This could take some time to complete for deep and complex GameObject structures.
If we have a lot of different Prefabs with components that contain a lot of properties that tend to share data, such as game design values such as hit points, strength, and speed, then all of this data will be serialized into every Prefab that uses them. A better approach is to serialize this common data in a ScriptableObject, which they load and use instead. This reduces the amount of data stored within the serialized file for the Prefab and could significantly reduce the loading time of our scenes by avoiding too much repetitive work.
Unity uses serialization both in editor and scripting. In editor it is used for scenes, prefabs, GameObject, ScriptableObject etc. When one of these object types is saved to disk, it is converted into a text file using the Yet Another Markup Language (YAML) format, which can be deserialized back into the original object type at a later time.
In scripting All GameObjects and their properties get serialized when a Prefab or scene is serialized, including private and protected fields and all of their components, as well as child GameObjects and their components and so on.
The more public and serialized-field varaibles we have in our scripts the more time it takes to deserialize them.Reading and deserializing this data from disk at runtime is an a slow process and so all deserialization activity comes with a significant performance cost.
I hope you found this blog post enjoyable!