Anular sprites en lugar de destruir objetos

Clase 18 de 31Curso de Desarrollo de Videojuegos para Móviles con Unity

Resumen

Domina la detección y limpieza de coincidencias en un juego tipo Candy Crush con Unity y C#. Aquí se construye un flujo claro con clearMatch y findAllMatches, se reduce código duplicado y se aplica una optimización clave en móvil: anular la parte visual en lugar de destruir objetos.

¿Qué resuelve clear match y por qué es clave en móvil?

clearMatch comprueba si, desde el caramelo actual, hay suficientes vecinos en una o varias direcciones para formar un match. Si los hay, anula la componente visual de todos los implicados en lugar de destruirlos.

  • Usa una List temporal para recolectar vecinos coincidentes.
  • Suma colecciones con AddRange en lugar de Add para eficiencia.
  • Compara con el umbral de boardManager.minCandiesToMatch.
  • Anula el sprite con null en cada caramelo afectado.
  • Devuelve true si limpia, false si no hay match.
private bool clearMatch(Vector2[] directions)
{
    List<GameObject> matchingCandies = new List<GameObject>();

    foreach (Vector2 direction in directions)
    {
        matchingCandies.AddRange(findMatch(direction));
    }

    if (matchingCandies.Count >= boardManager.minCandiesToMatch)
    {
        foreach (GameObject candy in matchingCandies)
        {
            candy.GetComponent<SpriteRenderer>().sprite = null; // Optimización móvil.
        }
        return true;
    }
    else
    {
        return false;
    }
}

¿Por qué es óptimo en móvil? Porque evita instanciaciones y destrucciones constantes. Anular el SpriteRenderer hace invisible el caramelo y permite la futura caída en cascada sin penalizar CPU y batería.

¿Cómo buscar todas las coincidencias con find all matches?

findAllMatches coordina la detección en horizontal y vertical. Primero se protege con una guard clause para no comparar si el caramelo ya no tiene imagen.

  • Comprueba horizontalMatch con Vector2.left y Vector2.right.
  • Comprueba verticalMatch con Vector2.up y Vector2.down.
  • Si hay match en cualquiera, anula también el sprite del caramelo central.
public void findAllMatches()
{
    if (renderer.sprite == null) return; // Guard clause.

    bool horizontalMatch = clearMatch(new Vector2[] { Vector2.left, Vector2.right });
    bool verticalMatch   = clearMatch(new Vector2[] { Vector2.up, Vector2.down });

    if (horizontalMatch || verticalMatch)
    {
        GetComponent<SpriteRenderer>().sprite = null; // El centro también desaparece.
    }
}

¿Por qué no mirar direcciones opuestas?

Antes se consultaban vecinos en ambos sentidos dentro del mismo método. Ahora, al separar en horizontal y vertical, ya no hace falta duplicar consultas. El resultado: menos líneas, más claridad y mantenimiento sencillo.

¿Qué patrón de control evita errores?

El retorno explícito de true y false en clearMatch asegura que todas las rutas devuelvan valor. La guard clause en findAllMatches previene comparaciones inválidas si el sprite ya está null.

¿Dónde invocar find all matches tras el swipe?

La invocación correcta ocurre al gestionar el intercambio en onMouseDown. Se llama dos veces para cubrir ambos casos: cuando destruye el caramelo movido y cuando destruye el caramelo previo.

  • Invoca en el caramelo previamente seleccionado antes de deseleccionarlo.
  • Invoca en el caramelo actual tras el intercambio.
  • Ambos son necesarios para no perder matches válidos.
void onMouseDown()
{
    if (canSwipe)
    {
        swipeSprite();
        previousSelected.findAllMatches();   // Caso 1: el anterior provoca el match.
        deselectCandy();                     // Deselección segura.
        this.findAllMatches();               // Caso 2: el actual provoca el match.
    }
}

Sugerencia práctica: comenta la llamada anterior o la posterior a findAllMatches (línea 68 o 70 en el guion original) y verifica cómo se pierden situaciones de destrucción. Así se evidencia por qué ambas llamadas son imprescindibles.

¿Qué resultados verás y qué falta por implementar?

  • Con tres, cuatro o cinco alineados, todos se vuelven invisibles por la lógica de limpieza.
  • Aún no caen en cascada. Falta programar el desplazamiento visual de piezas superiores para ocupar los huecos. No habrá gravedad física; se moverán sprites.

¿Te gustaría compartir cómo modularías la cascada o cómo estructurarías la actualización del tablero tras cada match?