📜 ⬆️ ⬇️

Optimization of 2D applications for mobile devices in Unity3d

Recently, our studio has completed the development of a large update - Captain Antarctica: Endless Run - for devices on iOs. The hard work on updating affected performance, which turned out to be very low on weak devices. I struggled with this for a whole week and achieved at least 30 FPS, as well as a significant reduction in the size of the application. I want to tell you how I did it, well, and how not to do it.
The article is useful to any developers on Unity (not only project managers and technical specialists, but also just programmers, artists and designers), because it affects both optimization on Unity as a whole, and specifically optimization of 2d applications for mobile devices.


Everything's possible!


To begin with, every time I start optimization, I initially do not believe that something else can be optimized, especially if the project has already passed several optimization cycles before. But viewing the official Unity documentation, topics on forums, articles on the Internet makes me think of new possible improvements. Thus, I keep a special list in which the main ideas are written down on what can be optimized in a project on Unity, constantly updating it and first of all referring to it when it comes to optimization. These ideas I want to share with you. I hope this article will help you make your project much quicker.
Immediately indicate that the development was conducted on Unity 3.5.6, the target platform - Apple devices from iPhone 3GS and newer.

Basic rules


To begin with, I will give a few rules that I use when developing and optimizing.
1. Do not optimize in advance .
This golden rule should be familiar to anyone who has ever done optimization. Recall the 80/20 rule : 80% of the benefit comes from 20% of the work .


The numbers are fairly arbitrary, but I meant this: most likely, most of the optimizations that you are going to do at the initial stage of the project will most likely have no effect on the final project as a whole .
')
However, there are a couple of exceptions to this rule, which are especially important when developing for mobile devices, because this rule is more suitable for PC projects. A PC is much more productive than mobile platforms and less limited in resources. So, exceptions:


2. Find what needs to be optimized .
A few years ago I had such a situation: walk through the code, scenes, etc. and optimize everything that is possible, and then look at the performance - this is the error of the novice optimizer .
First you need to find what slows down the system, on which there are performance jumps. This profiler helps a lot. Of course, it is quite conditional and it loads the system a little, but the benefits of it are undeniable! The Unity Pro has a built-in pro-faler, quite comfortable. But if you have a regular Unity, you can use the xCode profiler, or any other suitable one. Profiler helps to find the most loading code, shows the used memory, how much sounds load the system, DrawCall count on the end device and so on. Thus, before optimizing, run the application through the profiler. I think you will learn a lot of new things about your project)


What helped me solve a performance problem?


Even before running through the profiler, it was obvious that the weak point was in the Draw Calls count. On average, the scene produced about 70 DrawCalls, which is fatal for devices on iPad1 and below. Normal for them - 30-40 Draw Calls . You can see the Draw Calls count directly in the editor in the Game-> Stats: window.


The number of Draw Calls shown in the editor is the same as that on the end devices. The profiler confirmed this. In general, it is very useful to watch these statistics, and not only for programmers, but also for designers, to find "tight" places in the game.
In our scenes, the grouping of several Draw Calls for the same material into one Draw Call worked poorly. This is called Dynamic Batching . I started digging on the topic "how to lower the number of Draw Calls and improve their grouping." Below are the basic rules, adhering to which, you can get an acceptable amount of Draw Calls. Here are the ones that helped me a lot:

1. Use atlases to combine multiple textures into one large one .
In fact, it is even more important that sprites / models use not only one common texture, but rather one common material . It is in the number of different materials that the number of Draw Calls is measured (ideally). Therefore, in our project, the used images are always combined into atlases, divided into categories: objects used in all scenes, GUI objects, background and so on. Here is an example of such an atlas:

Such a partition will also be useful in the future for applying various settings to textures. But more about that later.

2. Do not change Transform-> Scale .
Objects with a modified Scale fall into a separate category, increasing the number of Draw Calls. I noticed this when I once again traced through the Draw Call Batching document. That's what it means to reread;) Walking through the scene, I discovered a huge number of such objects:



As a result of eliminating almost all changes, Scale managed to reduce the number of Draw Call almost doubled ! True, these improvements took us a decent time, so it is worth remembering Scale already at the initial stage of level design . And now in Memo to the designers we have written in bold red: Try to avoid changing Scale .

A few more tips (taken, including from the Unity document ), how to reduce the number of Draw Calls:


Performance jumps




The second factor affecting performance is the so-called performance jump. Sometimes the game had “hangs” for 0.5-1 seconds, which of course was unacceptable and directly affected the gameplay. And this was observed even on the latest devices.
And in this case, the profiler helped! Here is a list of rules for reducing performance hops:

1. Try not to use Instantiate () , especially for complex objects .
Performance jumps accounted for mainly the calls to Instantiate () , which created new objects from the subs, or cloned existing ones. Moreover, some objects were very cumbersome, which influenced the time of their creation. Instead, we had to rewrite the system so that the objects were used again. Those. the state of the object after the end of use (or before use) was reduced to the initial one. It also helped to reduce the amount of memory used (since new objects no longer needed new memory) and the number of calls to Destroy () .

2. Minimize the number of calls to Destroy () .
Destroy (especially for large objects) almost always leads to memory manipulations. And it usually deplores performance. This rule is directly related to the rule above, because Instantiate () / Destroy () calls are usually associated. Thus, using objects anew made it unnecessary to destroy them.

3. Minimize gameObject.SetActiveRecursively () calls .
For complex objects, the call can be very long, because it involves not just the activation of objects and their components, but in some cases the loading of the necessary resources.

4. Minimize calls to Object.Find () .
I think it is not necessary to explain that the time of this operation depends on the number of objects on the stage. Functions like GetComponent () are also included here.

5. Minimize calls to Resources.UnloadUnusedAssets () and GC.Collect () .
Unity sometimes resorts to it itself if there is not enough memory to load a new resource or a request comes from the OS to free up unused memory. Thus, the first 2 rules automatically reduce the number of such calls. The best place to call Resources.UnloadUnusedAssets manually - before loading the scene or immediately after it starts. It will also help free up additional memory for the scene, which is sometimes critical. The corresponding performance jump can be hidden, for example, by the boot screen;)

Using the rules above has resulted in the elimination of performance jumps and a much smoother gameplay and image.

Other optimizations


Next, I give other rules that can help you. Most of them I used in the previous stages of optimization.

Scripts


/// <summary> /// Cached transform /// </summary> protected new Transform transform { get { if(cachedTransform == null) cachedTransform = base.transform; return cachedTransform; } } private Transform cachedTransform = null; 


Physics


Animations


Particle system


GUI


Other


Reducing the size of the application


What can it be needed for? Previously, this was done because applications <20Mb in size could be downloaded to iOS via a 3g network. That, in principle, should increase the number of downloads, although I have not seen specific statistics. In connection with the release of iPad3 applications have become "fatter", and the threshold was raised to 50Mb . Do not forget that after uploading the application in the AppStore, it will be increased in size by an average of 4Mb . To check how much the application will weigh after filling in the AppStore in xCode in Organizer-> Archives , a special button Estimate Size has even appeared:


Everything you need to reduce the size of the build is described in the documents Unity:

I will describe here what I used myself. Let's start with what affects performance:

1. Use the correct texture format .
This will also reduce the amount of texture memory used, and thus improve performance. Sometimes it is enough to use the texture format of 16 bits , especially if all the graphics are drawn in just a few colors. Compare:

For monotonous textures, you can only use its gray and alpha components and sculpt the finished object from them using a specially written shader and multiplication by color:

For backs and fuzzy objects you can use compression. Now PVRTC-compression on iOS is quite advanced. It is worth remembering that the more texture - the better its quality after compression . On small textures, the use of compression may be unacceptable.
To make it all make sense, you need to divide objects into groups of the “background” type, GUI, game objects, which I already wrote about. Then for each type you can create your own atlas and use different texture format settings.

2. Use the correct sound format .
I have not thought about it before. The use of compression on sounds allowed me not only to reduce the size of the application, but also the amount of memory used by it. I myself use the following rules:

Other steps to reduce the size of the build follow. Most of the settings are done in Player Settings:


Sources of Inspiration Used


Primarily, Unity documents were used — the most useful resources from the Unity developers themselves. They need to be read in the first place, preferably several times, and after a while again, because they are constantly updated.

Separately, I have already mentioned documents to reduce the size of the application:

Other sources:

Conclusion


The optimization carried out by me allowed us to significantly improve the performance of the game and playability in general. As an added bonus, the size of the application has been reduced;) I hope my article will help you to make your application on Unity even better.
Perhaps someone will be interested to read my previous articles:

Most likely, my next article will be an article on how to simplify work in Unity and minimize the number of errors.
If someone has questions, suggestions, amendments - I am always ready to listen and discuss.

Source: https://habr.com/ru/post/169451/


All Articles