🎮 UnityDocs

Gestión de memoria y el recolector (GC)

Por qué el recolector de basura provoca tirones y cómo escribir código que apenas genera basura: value types, cachear, evitar concatenar strings y las asignaciones ocultas en el bucle de juego.

📄 ArtículoAvanzado✅ RevisadoUnity 6.0 LTSActualizado 2026-07-15
DificultadConceptual Código Matemáticas
Tiempo📖 Lectura 9 min

Por qué el GC te importa#

C# gestiona la memoria por ti: cuando creas objetos, se reserva memoria; cuando ya nadie los usa, el recolector de basura (GC) la libera. Cómodo, pero con un coste: cuando el GC salta, puede detener el juego unos milisegundos para hacer su limpieza. Si generas mucha basura, esas pausas son frecuentes y se notan como tirones (stutter).

El objetivo: 0 bytes por frame en el bucle de juego

La meta no es "usar poca memoria", sino no asignar memoria cada fotograma. Si tu Update genera 0 bytes de basura, el GC no tiene nada que limpiar durante el juego. Vigila la columna GC Alloc en el Profiler: en el bucle de juego debería estar cerca de 0.

Qué genera basura (y cómo evitarlo)#

1. Crear objetos cada frame#

C#
void Update()
{
    // ❌ new List cada frame = basura constante
    List<Enemigo> cercanos = new List<Enemigo>();
    // ...
}

Reutiliza: crea la lista una vez (campo) y hazle Clear() en vez de crear otra.

C#
private List<Enemigo> cercanos = new List<Enemigo>();
void Update()
{
    cercanos.Clear();   // sin basura
    // ...
}

2. Concatenar strings#

Los string son inmutables: cada + crea uno nuevo. Actualizar un marcador cada frame con "Puntos: " + n genera basura sin parar.

C#
// ❌ Cada frame crea strings nuevos
textoUI.text = "Puntos: " + puntos + " / " + total;

// ✅ Actualiza SOLO cuando cambia el valor, no cada frame
void SumarPuntos(int p) { puntos += p; textoUI.text = "Puntos: " + puntos; }

Para construir strings largos o muy cambiantes, usa StringBuilder.

3. Boxing de structs#

Meter un struct (int, Vector3) donde se espera un object crea una copia en memoria. Ocurre con APIs antiguas o interfaces mal usadas. Ver structs vs clases.

4. LINQ y algunos foreach#

LINQ genera iteradores intermedios: cómodo, pero basura en Update. Para el bucle de juego, for/foreach sobre List<T> (que no asigna) es mejor.

5. Métodos de Unity que asignan#

Algunos devuelven arrays nuevos cada llamada:

C#
// ❌ GetComponents / Find / Physics.RaycastAll devuelven arrays nuevos
var hits = Physics.RaycastAll(ray);

// ✅ Versiones NonAlloc que escriben en un buffer reutilizado
RaycastHit[] buffer = new RaycastHit[16];
int n = Physics.RaycastNonAlloc(ray, buffer);
Busca las variantes NonAlloc

Muchas APIs de física tienen una versión ...NonAlloc (o que reciben un buffer) para no crear arrays. En código que corre a menudo, úsalas. Igual con GetComponentsInChildren que llenan una List que tú pasas.

Cachear: la regla de oro#

  • Guarda referencias (GetComponent, Camera.main, transform) en Awake, no las pidas en Update.
  • Camera.main hace un Find interno: cáchalo.
  • Reutiliza colecciones, buffers y WaitForSeconds (ver corrutinas).

El GC incremental#

Unity puede repartir la limpieza

Unity ofrece un GC incremental (Project Settings → Player) que reparte el trabajo del recolector en varios fotogramas, en vez de una pausa grande. Ayuda a suavizar los tirones, pero no sustituye a generar poca basura: es un parche, no la cura. La cura es no asignar en el bucle de juego.

Un método de trabajo#

🛠️ Decisión de ingeniería
  • 1. Abre el Profiler y mira GC Alloc por frame durante el juego
  • 2. ¿Hay picos de asignación? Baja al método culpable
  • 3. Ataca: reutiliza colecciones, evita strings por frame, usa NonAlloc, saca LINQ del Update
  • 4. Aplica object pooling a lo que se crea/destruye mucho
  • 5. Vuelve a medir: confirma que GC Alloc bajó

Errores frecuentes#

  • Crear listas/arrays nuevos cada frame en vez de reutilizarlos.
  • Concatenar strings en Update (marcadores, timers).
  • Usar LINQ y ...All/Find en bucles calientes.
  • Confiar en el GC incremental como solución en vez de reducir la basura.
  • Olvidar cachear Camera.main.

Ponte a prueba#

📝 Pon a prueba lo aprendido

Resumen#

El GC libera memoria automáticamente, pero sus pausas causan tirones si generas mucha basura. La meta: 0 asignaciones por frame en el bucle de juego. Reutiliza colecciones (Clear() en vez de new), evita concatenar strings cada frame, usa variantes NonAlloc, saca LINQ del Update, cachea referencias y aplica pooling. El GC incremental ayuda, pero la cura es no asignar. Mídelo con GC Alloc en el Profiler.

Fuentes y para profundizar