Otimização de Performance em Jogos: Como Garantir 60 FPS Estáveis

Otimização de performance para jogos 60 FPS

Guia completo de otimização de performance: profiling, técnicas avançadas, GPU/CPU optimization e estratégias para alcançar 60 FPS

Otimização de Performance em Jogos: Como Garantir 60 FPS Estáveis

Introdução: A Importância dos 60 FPS

Performance é fundamental para a experiência do jogador. A diferença entre 30 e 60 FPS pode determinar o sucesso ou fracasso de um jogo, especialmente em gêneros competitivos. Este guia abrangente explorará técnicas avançadas de otimização, ferramentas de profiling e estratégias para alcançar e manter 60 FPS estáveis em diferentes plataformas.

Por Que 60 FPS é o Padrão?

60 FPS oferece fluidez visual, menor input lag e melhor responsividade. Para jogos competitivos, é essencial. Para experiências imersivas, é transformador. Dominar a otimização para alcançar este objetivo consistentemente é uma habilidade crucial para todo desenvolvedor.

Profiling: Identificando Gargalos

Ferramentas de Profiling

using UnityEngine;
using UnityEngine.Profiling;
using System.Diagnostics;

public class PerformanceProfiler : MonoBehaviour
{
    private float deltaTime = 0.0f;
    private Recorder drawCallRecorder;
    private Recorder setPassRecorder;
    private Recorder triangleRecorder;
    private Recorder vertexRecorder;

    void Start()
    {
        // Inicializar recorders do Unity Profiler
        drawCallRecorder = Recorder.Get("Draw Calls Count");
        setPassRecorder = Recorder.Get("SetPass Calls Count");
        triangleRecorder = Recorder.Get("Triangles Count");
        vertexRecorder = Recorder.Get("Vertices Count");

        drawCallRecorder.enabled = true;
        setPassRecorder.enabled = true;
        triangleRecorder.enabled = true;
        vertexRecorder.enabled = true;
    }

    void Update()
    {
        // Calcular FPS
        deltaTime += (Time.unscaledDeltaTime - deltaTime) * 0.1f;

        // Custom profiling markers
        using (new ProfilerMarker("MySystem.Update").Auto())
        {
            UpdateGameSystems();
        }
    }

    void OnGUI()
    {
        if (Application.isEditor || UnityEngine.Debug.isDebugBuild)
        {
            DisplayPerformanceStats();
        }
    }

    void DisplayPerformanceStats()
    {
        int w = Screen.width, h = Screen.height;
        GUIStyle style = new GUIStyle();

        Rect rect = new Rect(10, 10, w, h * 2 / 100);
        style.alignment = TextAnchor.UpperLeft;
        style.fontSize = h * 2 / 75;
        style.normal.textColor = Color.white;

        float msec = deltaTime * 1000.0f;
        float fps = 1.0f / deltaTime;
        string text = string.Format(
            "FPS: {0:0.} ({1:0.0} ms)\n" +
            "Draw Calls: {2}\n" +
            "SetPass Calls: {3}\n" +
            "Triangles: {4:0,0}\n" +
            "Vertices: {5:0,0}\n" +
            "Memory: {6:0} MB",
            fps, msec,
            drawCallRecorder.sampleBlockCount,
            setPassRecorder.sampleBlockCount,
            triangleRecorder.sampleBlockCount,
            vertexRecorder.sampleBlockCount,
            GC.GetTotalMemory(false) / 1048576
        );

        GUI.Label(rect, text, style);
    }
}

Análise de Frame Budget

# Sistema de análise de frame budget
class FrameBudgetAnalyzer:
    def __init__(self, target_fps=60):
        self.target_fps = target_fps
        self.frame_time_ms = 1000 / target_fps  # 16.67ms para 60 FPS

    def analyze_frame_breakdown(self):
        """Breakdown típico de um frame a 60 FPS"""

        budget = {
            "cpu": {
                "gameplay_logic": 3.0,  # ms
                "physics": 2.5,
                "ai": 2.0,
                "animation": 1.5,
                "scripting": 1.0,
                "audio": 0.5,
                "input": 0.3,
                "other": 1.2,
                "total": 12.0
            },
            "gpu": {
                "geometry": 3.0,
                "shadows": 2.5,
                "lighting": 2.0,
                "post_processing": 2.0,
                "particles": 1.5,
                "ui": 1.0,
                "total": 12.0
            },
            "vsync_overhead": 1.0,
            "system": 3.67
        }

        total = sum([
            budget["cpu"]["total"],
            budget["gpu"]["total"],
            budget["vsync_overhead"],
            budget["system"]
        ])

        return {
            "budget": budget,
            "total_ms": total,
            "within_budget": total <= self.frame_time_ms,
            "headroom": self.frame_time_ms - total
        }

    def identify_bottleneck(self, frame_data):
        """Identificar se é CPU ou GPU bound"""

        if frame_data["gpu_time"] > frame_data["cpu_time"]:
            return {
                "bottleneck": "GPU",
                "severity": frame_data["gpu_time"] / self.frame_time_ms,
                "recommendations": [
                    "Reduzir qualidade de sombras",
                    "Diminuir resolução de renderização",
                    "Otimizar shaders",
                    "Reduzir post-processing"
                ]
            }
        else:
            return {
                "bottleneck": "CPU",
                "severity": frame_data["cpu_time"] / self.frame_time_ms,
                "recommendations": [
                    "Otimizar scripts",
                    "Reduzir física complexa",
                    "Implementar LOD para AI",
                    "Use object pooling"
                ]
            }

Otimização de CPU

Gestão Eficiente de GameObjects

public class CPUOptimization : MonoBehaviour
{
    // Object Pooling para reduzir GC e instantiation
    public class ObjectPool<T> where T : Component
    {
        private Queue<T> pool = new Queue<T>();
        private GameObject prefab;
        private Transform parent;
        private int initialSize;

        public ObjectPool(GameObject prefab, int initialSize, Transform parent = null)
        {
            this.prefab = prefab;
            this.initialSize = initialSize;
            this.parent = parent;

            // Pre-warm pool
            for (int i = 0; i < initialSize; i++)
            {
                T obj = GameObject.Instantiate(prefab, parent).GetComponent<T>();
                obj.gameObject.SetActive(false);
                pool.Enqueue(obj);
            }
        }

        public T Get()
        {
            T obj;

            if (pool.Count > 0)
            {
                obj = pool.Dequeue();
            }
            else
            {
                obj = GameObject.Instantiate(prefab, parent).GetComponent<T>();
            }

            obj.gameObject.SetActive(true);
            return obj;
        }

        public void Return(T obj)
        {
            obj.gameObject.SetActive(false);
            pool.Enqueue(obj);
        }
    }

    // Spatial Hashing para otimizar detecção de colisão
    public class SpatialHashGrid
    {
        private Dictionary<int, List<GameObject>> grid;
        private float cellSize;

        public SpatialHashGrid(float cellSize)
        {
            this.cellSize = cellSize;
            this.grid = new Dictionary<int, List<GameObject>>();
        }

        private int GetCellKey(Vector3 position)
        {
            int x = Mathf.FloorToInt(position.x / cellSize);
            int z = Mathf.FloorToInt(position.z / cellSize);
            return x * 73856093 ^ z * 19349663; // Hash function
        }

        public void Insert(GameObject obj)
        {
            int key = GetCellKey(obj.transform.position);

            if (!grid.ContainsKey(key))
            {
                grid[key] = new List<GameObject>();
            }

            grid[key].Add(obj);
        }

        public List<GameObject> GetNearby(Vector3 position, float radius)
        {
            List<GameObject> nearby = new List<GameObject>();
            int cellRadius = Mathf.CeilToInt(radius / cellSize);

            for (int x = -cellRadius; x <= cellRadius; x++)
            {
                for (int z = -cellRadius; z <= cellRadius; z++)
                {
                    Vector3 cellPos = position + new Vector3(x * cellSize, 0, z * cellSize);
                    int key = GetCellKey(cellPos);

                    if (grid.ContainsKey(key))
                    {
                        nearby.AddRange(grid[key]);
                    }
                }
            }

            return nearby;
        }
    }
}

Otimização de Scripts

// Técnicas de otimização de código JavaScript/Unity
class ScriptOptimization {
  constructor() {
    // Cache de referências
    this.cachedTransform = null
    this.cachedRenderer = null
    this.cachedRigidbody = null
  }

  // RUIM: Busca componente toda vez
  badUpdate() {
    let position = this.gameObject.GetComponent('Transform').position
    let renderer = this.gameObject.GetComponent('Renderer')
    // ... operações
  }

  // BOM: Cache de componentes
  start() {
    this.cachedTransform = this.transform
    this.cachedRenderer = this.GetComponent('Renderer')
    this.cachedRigidbody = this.GetComponent('Rigidbody')
  }

  goodUpdate() {
    let position = this.cachedTransform.position
    // ... operações com componentes cacheados
  }

  // Otimização de loops
  optimizeLoops() {
    const array = new Array(10000)

    // RUIM: Length calculado toda iteração
    for (let i = 0; i < array.length; i++) {
      // processo
    }

    // BOM: Length cacheado
    const length = array.length
    for (let i = 0; i < length; i++) {
      // processo
    }

    // MELHOR: Loop reverso (quando ordem não importa)
    for (let i = array.length - 1; i >= 0; i--) {
      // processo
    }
  }

  // Evitar alocações desnecessárias
  avoidAllocations() {
    // RUIM: Cria novo array toda vez
    function badGetNeighbors() {
      return [1, 2, 3, 4, 5]
    }

    // BOM: Reutiliza array
    const neighborsCache = new Array(5)
    function goodGetNeighbors() {
      neighborsCache[0] = 1
      neighborsCache[1] = 2
      // ... preenche array
      return neighborsCache
    }
  }
}

Threading e Jobs System

using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;

// Job System para cálculos paralelos
[BurstCompile]
public struct ParallelCalculationJob : IJobParallelFor
{
    [ReadOnly] public NativeArray<float3> positions;
    [ReadOnly] public float3 targetPosition;
    public NativeArray<float> distances;

    public void Execute(int index)
    {
        distances[index] = math.distance(positions[index], targetPosition);
    }
}

public class JobSystemOptimization : MonoBehaviour
{
    private NativeArray<float3> positions;
    private NativeArray<float> distances;

    void Start()
    {
        int entityCount = 10000;
        positions = new NativeArray<float3>(entityCount, Allocator.Persistent);
        distances = new NativeArray<float>(entityCount, Allocator.Persistent);

        // Inicializar posições
        for (int i = 0; i < entityCount; i++)
        {
            positions[i] = new float3(
                UnityEngine.Random.Range(-100f, 100f),
                0,
                UnityEngine.Random.Range(-100f, 100f)
            );
        }
    }

    void Update()
    {
        // Criar e agendar job
        var job = new ParallelCalculationJob
        {
            positions = positions,
            targetPosition = float3.zero,
            distances = distances
        };

        // Executar em paralelo com batch size de 32
        JobHandle handle = job.Schedule(positions.Length, 32);

        // Fazer outro trabalho enquanto job executa
        DoOtherWork();

        // Esperar conclusão quando necessário
        handle.Complete();

        // Usar resultados
        ProcessDistances();
    }

    void OnDestroy()
    {
        positions.Dispose();
        distances.Dispose();
    }
}

Otimização de GPU

Batching e Draw Calls

public class GPUOptimization : MonoBehaviour
{
    // Static Batching para objetos estáticos
    void SetupStaticBatching()
    {
        // Marcar objetos como static no Inspector
        // Unity automaticamente faz batch

        // Ou programaticamente
        GameObject[] staticObjects = GameObject.FindGameObjectsWithTag("Static");
        StaticBatchingUtility.Combine(staticObjects, gameObject);
    }

    // Dynamic Batching settings
    void ConfigureDynamicBatching()
    {
        // Requisitos para dynamic batching:
        // - Menos de 900 vértices
        // - Mesmo material
        // - Sem lightmaps diferentes
        // - Sem multi-pass shaders

        // Configurar no Player Settings
        // Edit -> Project Settings -> Player -> Other Settings
    }

    // GPU Instancing para múltiplas cópias
    public class GPUInstancingRenderer : MonoBehaviour
    {
        public Mesh mesh;
        public Material material;
        private Matrix4x4[] matrices;
        private MaterialPropertyBlock propertyBlock;

        void Start()
        {
            // Material deve ter "Enable GPU Instancing" marcado
            material.enableInstancing = true;

            matrices = new Matrix4x4[1023]; // Máximo por draw call
            propertyBlock = new MaterialPropertyBlock();

            // Criar matrizes para instâncias
            for (int i = 0; i < matrices.Length; i++)
            {
                Vector3 position = new Vector3(
                    Random.Range(-50f, 50f),
                    0,
                    Random.Range(-50f, 50f)
                );

                Quaternion rotation = Quaternion.Euler(0, Random.Range(0, 360), 0);
                Vector3 scale = Vector3.one;

                matrices[i] = Matrix4x4.TRS(position, rotation, scale);
            }
        }

        void Update()
        {
            // Renderizar todas as instâncias em um draw call
            Graphics.DrawMeshInstanced(mesh, 0, material, matrices, matrices.Length, propertyBlock);
        }
    }
}

Otimização de Shaders

// Shader otimizado para mobile
Shader "Optimized/MobileShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color ("Color", Color) = (1,1,1,1)
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // Otimizações
            #pragma multi_compile_instancing
            #pragma multi_compile_fog
            // Remover features não usadas
            #pragma skip_variants SHADOWS_SOFT
            #pragma skip_variants DIRLIGHTMAP_COMBINED

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            UNITY_INSTANCING_BUFFER_START(Props)
                UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
            UNITY_INSTANCING_BUFFER_END(Props)

            v2f vert (appdata v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o);

                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o, o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i);

                // Sampling simples
                fixed4 col = tex2D(_MainTex, i.uv);
                col *= UNITY_ACCESS_INSTANCED_PROP(Props, _Color);

                // Apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);

                return col;
            }
            ENDCG
        }
    }

    // Fallback para hardware mais fraco
    FallBack "Mobile/Diffuse"
}

LOD (Level of Detail)

public class LODOptimization : MonoBehaviour
{
    public class DynamicLODSystem
    {
        public GameObject[] lodModels; // Do maior para menor detalhe
        public float[] lodDistances = { 10f, 25f, 50f, 100f };
        private Camera mainCamera;
        private int currentLOD = -1;

        void Start()
        {
            mainCamera = Camera.main;
        }

        void Update()
        {
            float distance = Vector3.Distance(
                transform.position,
                mainCamera.transform.position
            );

            int newLOD = CalculateLOD(distance);

            if (newLOD != currentLOD)
            {
                SwitchLOD(newLOD);
                currentLOD = newLOD;
            }
        }

        int CalculateLOD(float distance)
        {
            for (int i = 0; i < lodDistances.Length; i++)
            {
                if (distance < lodDistances[i])
                {
                    return i;
                }
            }
            return lodModels.Length - 1; // Menor LOD
        }

        void SwitchLOD(int lodLevel)
        {
            // Desativar todos
            foreach (var model in lodModels)
            {
                model.SetActive(false);
            }

            // Ativar LOD correto
            if (lodLevel < lodModels.Length)
            {
                lodModels[lodLevel].SetActive(true);
            }
        }
    }

    // LOD para terreno
    public class TerrainLOD
    {
        public void OptimizeTerrain(Terrain terrain)
        {
            // Ajustar pixel error baseado em distância
            terrain.heightmapPixelError = 5; // 1-200, menor = mais detalhe

            // Reduzir detalhe de vegetação distante
            terrain.detailObjectDistance = 80; // Distância máxima para detalhes

            // Billboarding para árvores distantes
            terrain.treeBillboardDistance = 50;

            // Reduzir resolução de sombras para terreno
            terrain.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.TwoSided;
        }
    }
}

Otimização de Memória

Gestão de Texturas e Assets

# Sistema de gestão de memória para assets
class MemoryOptimization:
    def __init__(self):
        self.texture_budget_mb = 512  # Mobile
        self.mesh_budget_mb = 256
        self.audio_budget_mb = 128

    def calculate_texture_memory(self, width, height, format):
        """Calcular memória usada por textura"""

        bytes_per_pixel = {
            "RGBA32": 4,
            "RGB24": 3,
            "RGB565": 2,
            "PVRTC4": 0.5,
            "ETC2": 0.5,
            "ASTC4x4": 1,
            "DXT1": 0.5,
            "DXT5": 1
        }

        bpp = bytes_per_pixel.get(format, 4)
        size_bytes = width * height * bpp

        # Considerar mipmaps (adiciona ~33%)
        with_mipmaps = size_bytes * 1.33

        return {
            "format": format,
            "resolution": f"{width}x{height}",
            "memory_mb": with_mipmaps / (1024 * 1024),
            "with_mipmaps": True
        }

    def optimize_texture_settings(self, platform):
        """Configurações otimizadas por plataforma"""

        settings = {
            "mobile": {
                "max_resolution": 1024,
                "compression": "PVRTC4" if platform == "ios" else "ETC2",
                "aniso_level": 2,
                "mipmaps": True,
                "read_write": False  # Economiza 50% memória
            },
            "pc": {
                "max_resolution": 2048,
                "compression": "DXT5",
                "aniso_level": 8,
                "mipmaps": True,
                "read_write": False
            },
            "console": {
                "max_resolution": 4096,
                "compression": "BC7",
                "aniso_level": 16,
                "mipmaps": True,
                "read_write": False
            }
        }

        return settings.get(platform, settings["pc"])

    def audio_optimization(self):
        """Otimização de memória de áudio"""

        return {
            "music": {
                "format": "Vorbis",
                "quality": 0.4,  # 0-1
                "load_type": "Streaming",
                "compression": "Compressed"
            },
            "sfx_short": {
                "format": "ADPCM",
                "load_type": "Decompress on load",
                "compression": "Compressed"
            },
            "sfx_frequent": {
                "format": "PCM",
                "load_type": "Load in memory",
                "compression": "None"  # Para baixa latência
            },
            "voice": {
                "format": "MP3",
                "quality": 0.5,
                "load_type": "Streaming",
                "compression": "Compressed"
            }
        }

Garbage Collection

public class GCOptimization : MonoBehaviour
{
    // Evitar alocações no Update
    private Vector3[] positionsCache;
    private List<GameObject> visibleObjects;
    private StringBuilder stringBuilder;

    void Start()
    {
        // Pre-alocar coleções
        positionsCache = new Vector3[100];
        visibleObjects = new List<GameObject>(100);
        stringBuilder = new StringBuilder(256);
    }

    // RUIM: Cria lixo toda frame
    void BadUpdate()
    {
        string status = "Health: " + health + " Score: " + score;
        Vector3[] positions = new Vector3[enemyCount];
        List<GameObject> visible = new List<GameObject>();
    }

    // BOM: Reutiliza objetos
    void GoodUpdate()
    {
        // Reutilizar StringBuilder
        stringBuilder.Clear();
        stringBuilder.Append("Health: ");
        stringBuilder.Append(health);
        stringBuilder.Append(" Score: ");
        stringBuilder.Append(score);

        // Reutilizar array
        for (int i = 0; i < enemyCount && i < positionsCache.Length; i++)
        {
            positionsCache[i] = GetEnemyPosition(i);
        }

        // Clear e reutilizar lista
        visibleObjects.Clear();
        // ... adicionar objetos visíveis
    }

    // Controle manual de GC
    public class GCManager : MonoBehaviour
    {
        private float gcTimer = 0f;
        private const float GC_INTERVAL = 30f; // GC a cada 30 segundos

        void Update()
        {
            gcTimer += Time.deltaTime;

            if (gcTimer >= GC_INTERVAL)
            {
                // Forçar GC em momento controlado (ex: entre níveis)
                if (IsGoodTimeForGC())
                {
                    System.GC.Collect();
                    gcTimer = 0f;
                }
            }
        }

        bool IsGoodTimeForGC()
        {
            // Durante loading, menu, ou momentos não-críticos
            return GameState.IsLoading || GameState.IsPaused;
        }
    }
}

Otimização Multi-plataforma

Adaptive Quality

public class AdaptiveQuality : MonoBehaviour
{
    private float targetFrameTime = 16.67f; // 60 FPS
    private float[] frameTimes = new float[30];
    private int frameIndex = 0;
    private int currentQualityLevel;

    public class QualityPreset
    {
        public int shadowQuality;
        public float shadowDistance;
        public int textureQuality;
        public int anisotropicFiltering;
        public bool softParticles;
        public int antiAliasing;
        public float lodBias;
        public int maxLODLevel;
        public float renderScale;
    }

    private Dictionary<int, QualityPreset> qualityPresets = new Dictionary<int, QualityPreset>
    {
        [0] = new QualityPreset // Ultra Low
        {
            shadowQuality = 0,
            shadowDistance = 0,
            textureQuality = 2,
            anisotropicFiltering = 0,
            softParticles = false,
            antiAliasing = 0,
            lodBias = 0.3f,
            maxLODLevel = 3,
            renderScale = 0.5f
        },
        [1] = new QualityPreset // Low
        {
            shadowQuality = 1,
            shadowDistance = 20,
            textureQuality = 1,
            anisotropicFiltering = 0,
            softParticles = false,
            antiAliasing = 0,
            lodBias = 0.5f,
            maxLODLevel = 2,
            renderScale = 0.75f
        },
        [2] = new QualityPreset // Medium
        {
            shadowQuality = 2,
            shadowDistance = 40,
            textureQuality = 0,
            anisotropicFiltering = 2,
            softParticles = true,
            antiAliasing = 2,
            lodBias = 1.0f,
            maxLODLevel = 1,
            renderScale = 1.0f
        },
        [3] = new QualityPreset // High
        {
            shadowQuality = 3,
            shadowDistance = 100,
            textureQuality = 0,
            anisotropicFiltering = 8,
            softParticles = true,
            antiAliasing = 4,
            lodBias = 1.5f,
            maxLODLevel = 0,
            renderScale = 1.0f
        }
    };

    void Update()
    {
        // Registrar frame time
        frameTimes[frameIndex] = Time.deltaTime * 1000f;
        frameIndex = (frameIndex + 1) % frameTimes.Length;

        // Ajustar qualidade a cada segundo
        if (frameIndex == 0)
        {
            AdjustQuality();
        }
    }

    void AdjustQuality()
    {
        float avgFrameTime = 0f;
        foreach (float time in frameTimes)
        {
            avgFrameTime += time;
        }
        avgFrameTime /= frameTimes.Length;

        // Se está muito abaixo do target, aumentar qualidade
        if (avgFrameTime < targetFrameTime * 0.8f && currentQualityLevel < 3)
        {
            currentQualityLevel++;
            ApplyQualityPreset(currentQualityLevel);
        }
        // Se está acima do target, reduzir qualidade
        else if (avgFrameTime > targetFrameTime * 1.1f && currentQualityLevel > 0)
        {
            currentQualityLevel--;
            ApplyQualityPreset(currentQualityLevel);
        }
    }

    void ApplyQualityPreset(int level)
    {
        var preset = qualityPresets[level];

        QualitySettings.shadows = (ShadowQuality)preset.shadowQuality;
        QualitySettings.shadowDistance = preset.shadowDistance;
        QualitySettings.masterTextureLimit = preset.textureQuality;
        QualitySettings.anisotropicFiltering = (AnisotropicFiltering)preset.anisotropicFiltering;
        QualitySettings.softParticles = preset.softParticles;
        QualitySettings.antiAliasing = preset.antiAliasing;
        QualitySettings.lodBias = preset.lodBias;
        QualitySettings.maximumLODLevel = preset.maxLODLevel;

        // Ajustar render scale
        UnityEngine.Rendering.Universal.UniversalRenderPipelineAsset urpAsset =
            (UnityEngine.Rendering.Universal.UniversalRenderPipelineAsset)QualitySettings.renderPipeline;
        if (urpAsset != null)
        {
            urpAsset.renderScale = preset.renderScale;
        }

        UnityEngine.Debug.Log($"Quality adjusted to level {level}");
    }
}

Ferramentas e Métricas

Dashboard de Performance

// Sistema de monitoramento de performance em tempo real
class PerformanceDashboard {
  constructor() {
    this.metrics = {
      fps: [],
      frameTime: [],
      drawCalls: [],
      triangles: [],
      memoryUsage: [],
      gcCollects: [],
    }

    this.thresholds = {
      fps: { min: 55, target: 60, max: 120 },
      frameTime: { min: 8.33, target: 16.67, max: 33.33 },
      drawCalls: { min: 50, target: 100, max: 300 },
      triangles: { min: 50000, target: 100000, max: 500000 },
    }
  }

  collectMetrics() {
    const current = {
      fps: this.calculateFPS(),
      frameTime: performance.now() - this.lastFrame,
      drawCalls: this.getDrawCalls(),
      triangles: this.getTriangleCount(),
      memoryUsage: this.getMemoryUsage(),
      gcCollects: this.getGCCount(),
    }

    // Manter histórico dos últimos 60 frames
    for (let metric in this.metrics) {
      this.metrics[metric].push(current[metric])
      if (this.metrics[metric].length > 60) {
        this.metrics[metric].shift()
      }
    }

    return current
  }

  analyzePerformance() {
    const analysis = {
      status: 'Good',
      issues: [],
      recommendations: [],
    }

    // Analisar FPS
    const avgFPS = this.average(this.metrics.fps)
    if (avgFPS < this.thresholds.fps.min) {
      analysis.status = 'Critical'
      analysis.issues.push(`FPS baixo: ${avgFPS.toFixed(1)}`)
      analysis.recommendations.push('Reduzir qualidade gráfica')
    }

    // Analisar Draw Calls
    const avgDrawCalls = this.average(this.metrics.drawCalls)
    if (avgDrawCalls > this.thresholds.drawCalls.max) {
      analysis.issues.push(`Draw calls alto: ${avgDrawCalls}`)
      analysis.recommendations.push('Implementar batching')
    }

    // Detectar spikes
    const spikes = this.detectSpikes(this.metrics.frameTime)
    if (spikes.length > 0) {
      analysis.issues.push(`${spikes.length} frame spikes detectados`)
      analysis.recommendations.push('Investigar operações síncronas')
    }

    return analysis
  }

  detectSpikes(data, threshold = 2) {
    const avg = this.average(data)
    return data.filter((value) => value > avg * threshold)
  }

  average(arr) {
    return arr.reduce((a, b) => a + b, 0) / arr.length
  }
}

Recursos e Ferramentas

Ferramentas de Profiling

  • Unity Profiler: Análise completa built-in
  • Frame Debugger: Visualizar draw calls
  • RenderDoc: GPU debugging detalhado
  • Intel VTune: CPU profiling avançado
  • NVIDIA Nsight: GPU profiling NVIDIA

Benchmarking Tools

  • Unity Performance Testing: Framework de testes
  • Benchmark.NET: Micro-benchmarking
  • PerfView: Análise de performance .NET
  • Chrome DevTools: Para WebGL games

Conclusão

Alcançar 60 FPS estáveis requer disciplina, ferramentas adequadas e otimização contínua. Não existe bala de prata - é necessário perfilar, identificar gargalos e aplicar técnicas específicas para cada situação. A chave é estabelecer um orçamento de performance desde o início e monitorar constantemente.


⚡ Domine Performance em Games! Aprenda técnicas avançadas de otimização com profissionais da indústria. Teste vocacional gratuito →


Próximos Passos

Comece estabelecendo métricas de performance claras. Implemente profiling desde o início do desenvolvimento. Teste em hardware real, especialmente dispositivos mais fracos. Lembre-se: é mais fácil manter boa performance do que recuperá-la depois.


🚀 Curso de Otimização Avançada! Torne-se especialista em performance de jogos. Inscreva-se agora →