Tips and best practices

Article outline

  1. Introduction

  2. Using the Unity Profiler

  3. Script optimization

  4. Memory management

  5. Rendering Optimization

  6. Physics optimization

  7. Conclusion

Introduction

Why is performance optimization important?

Performance optimization is one of the key aspects of game development. It allows you to create a project that will run smoothly and stably on various devices, from powerful gaming PCs to mobile phones. High FPS (frames per second) not only improves the user experience, but can also be a decisive factor in the success of your game on the market.

Brief overview of optimization methods

Performance optimization includes several areas, each of which is important in its own way:

  • Using the Unity Profiler: A performance analysis tool that helps identify bottlenecks.

  • Script optimization: Reduce the load from your C# scripts by improving logic and data caching.

  • Memory management: Efficient use of resources and minimization of garbage collection work.

  • Rendering Optimization: Reducing the number of polygons and using levels of detail (LOD).

  • Physics optimization: Reduced load on Unity's physics engine.

In this article, we'll take a detailed look at each of these areas and provide illustrative code examples so you can immediately put the knowledge you've gained into practice.

Let's move on to examining each of these points in more detail.

Using the Unity Profiler

How to start and use the profiler

The Unity profiler is a powerful tool that allows you to analyze the performance of your game project. It helps you identify bottlenecks and understand which parts of your code or resources are taking up the most time and memory. Here's how to launch and use the profiler:

  1. Launching the profiler:

  2. Main tabs of the profiler:

Analysis of key metrics

Now that we know how to run the profiler, let's look at how to analyze key metrics to improve performance.

  1. CPU Usage:

  2. Memory:

  3. Rendering:

Code examples

Here's a code example that demonstrates optimization by caching components:

// Пример плохого кода: частое обращение к GetComponent в методе Update
public class BadExample : MonoBehaviour
{
    void Update()
    {
        GetComponent<Renderer>().material.color = Color.red;
    }
}

// Пример хорошего кода: кэширование компонента в методе Start
public class GoodExample : MonoBehaviour
{
    private Renderer _renderer;

    void Start()
    {
        _renderer = GetComponent<Renderer>();
    }

    void Update()
    {
        _renderer.material.color = Color.red;
    }
}

Code Notes:

Using the Unity profiler allows you to visually see which aspects of your project need optimization. By analyzing the data provided by the profiler, you can make informed decisions and improve the performance of your game.

Next we will look at script optimization.

Script optimization

Avoiding unnecessary updates

One of the main ways to optimize scripts in Unity is to avoid unnecessary updates. Methods Update, FixedUpdate And LateUpdate are called every frame, which can put a lot of strain on the processor, especially if they contain resource-intensive operations.

Example of bad code:

public class BadUpdateExample : MonoBehaviour
{
    void Update()
    {
        // Проверка состояния каждый кадр
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Jump();
        }
    }

    void Jump()
    {
        // Логика прыжка
    }
}

Example of good code:

public class GoodUpdateExample : MonoBehaviour
{
    void Start()
    {
        // Подписка на событие один раз при запуске
        Input.GetKeyDown(KeyCode.Space) += Jump;
    }

    void Jump()
    {
        // Логика прыжка
    }
}

Code notes:

Using Component Caching

Frequent use of the method GetComponent can significantly reduce performance, since each call to this method requires searching for the component in the object hierarchy. Caching components avoids this problem.

Example of bad code:

public class BadGetComponentExample : MonoBehaviour
{
    void Update()
    {
        GetComponent<Renderer>().material.color = Color.red;
    }
}

Example of good code:

public class GoodGetComponentExample : MonoBehaviour
{
    private Renderer _renderer;

    void Start()
    {
        _renderer = GetComponent<Renderer>();
    }

    void Update()
    {
        _renderer.material.color = Color.red;
    }
}

Code notes:

Example of using events and delegates:

public class EventExample : MonoBehaviour
{
    public delegate void OnJumpAction();
    public static event OnJumpAction OnJump;

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space) && OnJump != null)
        {
            OnJump();
        }
    }
}

public class JumpHandler : MonoBehaviour
{
    void OnEnable()
    {
        EventExample.OnJump += HandleJump;
    }

    void OnDisable()
    {
        EventExample.OnJump -= HandleJump;
    }

    void HandleJump()
    {
        // Логика прыжка
    }
}

Code notes:

Script optimization in Unity includes many methods and techniques that can significantly improve the performance of your project. It is important to analyze each part of the code and apply best practices to achieve the best results.

Memory management

Working with textures and resources

Effective memory management includes optimizing work with textures and resources. Textures can take up a significant amount of memory, especially in projects with high graphics detail.

Texture optimization tips:

  1. Texture sizes:

  2. Mipmap:

  3. Atlas Textures:

Example of texture setup in Unity:

using UnityEngine;

public class TextureOptimization : MonoBehaviour
{
    public Texture2D texture;

    void Start()
    {
        // Пример настройки текстуры для использования Mipmap и сжатия
        texture.filterMode = FilterMode.Bilinear;
        texture.anisoLevel = 4;
        texture.wrapMode = TextureWrapMode.Repeat;
    }
}

Code notes:

Minimizing the work of the garbage collector

Unity's Garbage Collector automatically manages memory, but running it too often can cause lag and stutter in your game. To minimize the Garbage Collector, follow these tips:

  1. Avoid frequent creation and destruction of objects:

  2. Use data structures with predictable memory allocation:

  3. Object Caching:

Example of using object pools:

public class ObjectPooler : MonoBehaviour
{
    public static ObjectPooler Instance;
    public GameObject objectToPool;
    public int amountToPool;

    private List<GameObject> pooledObjects;

    void Awake()
    {
        Instance = this;
    }

    void Start()
    {
        pooledObjects = new List<GameObject>();
        for (int i = 0; i < amountToPool; i++)
        {
            GameObject obj = Instantiate(objectToPool);
            obj.SetActive(false);
            pooledObjects.Add(obj);
        }
    }

    public GameObject GetPooledObject()
    {
        foreach (var obj in pooledObjects)
        {
            if (!obj.activeInHierarchy)
            {
                return obj;
            }
        }

        GameObject newObj = Instantiate(objectToPool);
        newObj.SetActive(false);
        pooledObjects.Add(newObj);
        return newObj;
    }
}

Code notes:

Memory management in Unity is an important aspect of optimization that directly affects the performance of the game. By applying the methods and practices mentioned above, you can significantly reduce the memory load and improve the overall performance of your project.

Rendering Optimization

Reducing the number of polygons

Reducing the number of polygons in a scene is one of the most effective ways to optimize rendering in Unity. Fewer polygons mean less load on the GPU, which is especially important for mobile devices and VR applications.

Tips for reducing polygon count:

  1. Using low poly models:

  2. Removing invisible polygons:

  3. Using normals and textures:

Example of reducing the number of polygons:

using UnityEngine;

public class LowPolyExample : MonoBehaviour
{
    public MeshFilter meshFilter;

    void Start()
    {
        // Применение упрощенной низкополигональной модели
        Mesh lowPolyMesh = new Mesh();
        // Установка вершин, нормалей и треугольников для низкополигональной модели
        // (Примерный код, не учитывающий реальную модель)
        lowPolyMesh.vertices = new Vector3[] { new Vector3(0, 0, 0), new Vector3(0, 1, 0), new Vector3(1, 0, 0) };
        lowPolyMesh.normals = new Vector3[] { Vector3.up, Vector3.up, Vector3.up };
        lowPolyMesh.triangles = new int[] { 0, 1, 2 };
        meshFilter.mesh = lowPolyMesh;
    }
}

Code notes:

Using Levels of Detail (LOD)

Levels of Detail (LOD) allow the number of polygons of models to be dynamically changed depending on the distance to the camera. This allows the load on the GPU to be reduced when rendering objects located far from the camera, without a noticeable decrease in visual quality.

Setting up LOD in Unity:

  1. Creating a LOD group:

  2. Setting up LOD levels:

Example of LOD group setup:

using UnityEngine;

public class LODExample : MonoBehaviour
{
    public GameObject highPolyModel;
    public GameObject mediumPolyModel;
    public GameObject lowPolyModel;

    void Start()
    {
        LODGroup lodGroup = gameObject.AddComponent<LODGroup>();

        LOD[] lods = new LOD[3];

        Renderer[] highPolyRenderers = highPolyModel.GetComponentsInChildren<Renderer>();
        Renderer[] mediumPolyRenderers = mediumPolyModel.GetComponentsInChildren<Renderer>();
        Renderer[] lowPolyRenderers = lowPolyModel.GetComponentsInChildren<Renderer>();

        lods[0] = new LOD(0.5f, highPolyRenderers);
        lods[1] = new LOD(0.3f, mediumPolyRenderers);
        lods[2] = new LOD(0.1f, lowPolyRenderers);

        lodGroup.SetLODs(lods);
        lodGroup.RecalculateBounds();
    }
}

Code notes:

Code examples

Let's look at more complex examples that involve optimizing various aspects of rendering.

An example of using texture atlases:

using UnityEngine;

public class TextureAtlasExample : MonoBehaviour
{
    public Material atlasMaterial;

    void Start()
    {
        // Применение атласной текстуры к материалу
        Renderer renderer = GetComponent<Renderer>();
        renderer.material = atlasMaterial;
    }
}

Code notes:

Optimizing rendering in Unity involves a variety of methods and techniques that can significantly improve the performance of your project. By applying the methods and practices mentioned above, you can significantly reduce the load on the GPU and improve the overall performance of your game.

Physics optimization

Managing the number of physical objects

Unity's physics engine can become a performance bottleneck if there are many physics objects in a scene. Managing the number of physics objects and optimizing their behavior are key steps to improving performance.

Tips for optimizing physical objects:

  1. Reducing the number of objects:

  2. Deactivation of physical objects:

Example of managing the number of physical objects:

using UnityEngine;

public class PhysicsOptimization : MonoBehaviour
{
    public GameObject[] physicalObjects;

    void Update()
    {
        foreach (GameObject obj in physicalObjects)
        {
            if (IsVisible(obj))
            {
                obj.SetActive(true);
            }
            else
            {
                obj.SetActive(false);
            }
        }
    }

    bool IsVisible(GameObject obj)
    {
        Plane[] planes = GeometryUtility.CalculateFrustumPlanes(Camera.main);
        return GeometryUtility.TestPlanesAABB(planes, obj.GetComponent<Collider>().bounds);
    }
}

Code notes:

Using Layers and Colliders

Using layers and properly setting up interactions between colliders can significantly reduce the amount of physics calculations.

Setting up layers and interactions:

  1. Creating layers:

  2. Setting up layer interactions:

Example of using layers and colliders:

using UnityEngine;

public class LayerOptimization : MonoBehaviour
{
    private void Start()
    {
        // Установка слоя для объекта
        gameObject.layer = LayerMask.NameToLayer("Player");

        // Игнорирование столкновений между слоями Player и Environment
        Physics.IgnoreLayerCollision(LayerMask.NameToLayer("Player"), LayerMask.NameToLayer("Environment"));
    }
}

Code notes:

Code examples

Let's look at some additional examples involving optimization of various aspects of physics.

Example of using simple colliders:

using UnityEngine;

public class SimpleColliders : MonoBehaviour
{
    private void Start()
    {
        // Замена сложного MeshCollider на простой BoxCollider
        MeshCollider meshCollider = GetComponent<MeshCollider>();

        if (meshCollider != null)
        {
            Destroy(meshCollider);
            gameObject.AddComponent<BoxCollider>();
        }
    }
}

Code notes:

An example of using Triggers to optimize interactions:

using UnityEngine;

public class TriggerOptimization : MonoBehaviour
{
    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player"))
        {
            // Логика при входе игрока в триггер
        }
    }
}

Code notes:

Physics optimization in Unity includes many methods and techniques that can significantly improve the performance of your project. By applying the above methods and practices, you can significantly reduce the load on the physics engine and improve the overall performance of your game.

Conclusion

Optimizing performance in Unity is a complex but critical process that directly impacts the success of your game project. In this article, we covered key optimization techniques, including using the Unity profiler, script optimization, memory management, rendering improvements, and physics optimization. Here’s a quick summary of each aspect we covered:

  1. Using the Unity Profiler:

  2. Script optimization:

  3. Memory management:

  4. Rendering Optimization:

  5. Physics optimization:

Additional resources for study

If you'd like to deepen your knowledge of performance optimization in Unity, here are some useful resources:

Optimizing performance takes time and effort, but the results are worth it. By applying the methods and techniques discussed in this article, you can create a game that runs smoothly and stably across multiple devices, providing the best user experience. Good luck with your projects, and keep learning and improving in the art of optimization!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *