You don't have access to this class

Keep learning! Join and start boosting your career

Aprovecha el precio especial y haz tu profesión a prueba de IA

Antes: $249

Currency
$209
Suscríbete

Termina en:

1 Días
9 Hrs
38 Min
3 Seg

Detectar coincidencias y destruir caramelos

18/31
Resources

How to detect and remove matching candies in a mobile game?

In the fascinating world of mobile game development, optimizing performance and ensuring smooth gameplay is crucial. One of the most essential mechanics in a Candy Crush-like game is to detect and remove matching candies. This process may sound simple, but it involves a detailed approach to programming to ensure that mobile resources are not saturated and that the game is engaging for users. Let's break down how to accomplish this efficiently.

What is the first thing we need to implement?

The game requires an efficient method for detecting matching candies in rows and columns. This process starts with the creation of a private method that evaluates whether there are matches to remove, returning a boolean value. Here vectors are used to define addresses and search those paths for matches:

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; } return true; } return false;}

How to optimize resources on mobile devices?

A key consideration in mobile gaming is the efficient use of resources to avoid heating up the device or draining the battery quickly. Instead of destroying and reinstantiating candies, it is advisable to simply override their visual component, making "invisible" candies scroll without the need to recreate elements:

  • Mobile Optimization: it is suggested to override visual components of candies instead of destroying them.
  • Resource Light: Leverage existing visual components for other operations.

How to find all possible matches?

A method is needed that scans all addresses for matches, making sure that safety conditions are maintained:

public void FindAllMatches(){ if (GetComponent<SpriteRenderer>().sprite == null) return;
 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; } }}

Where and when should the match method be called?

The logic of the game implies that the match method should be invoked when the user interacts with the candy. This is generally handled in the onMouseDown event, which responds to candy selection and movement:

  • Implementation: Call FindAllMatches() for both the initially selected candy and the one receiving the movement.

What common errors can we find?

We may forget to invoke the find match method, which would prevent alignments from being detected and removed. It is crucial to make sure that the method is called in the right place in the code to maintain the expected functionality. Also, not forgetting to return the expected boolean value can cause errors.

Technical conclusions and next steps

Through an efficient and optimized implementation, matches can be correctly detected in a Candy Dash-like mobile game. This approach not only improves game performance, but also ensures a positive user experience. In future enhancements, consideration should be given to implement the candy cascade to complement this removal and further optimize the gameplay.

Contributions 15

Questions 0

Sort by:

Want to see more contributions, questions and answers from the community?

Muchachos para los que se sienten un poco confundidos, hay que entender los conceptos y las reglas básicas del juego. Por eso, la etapa del Game Design es muy importante porque es la que nos va a guiar a medida del desarrollo sobre lo que tenemos que hacer o no en código.

  1. Principalmente es entender que jugamos con dos objetos siempre, Caramelo A y Caramelo B. El caramelo A es aquel que tu seleccionas de primero y el caramelo B es aquel con el que vas a hacer el cambio. Conociendo esto, casi toda la lógica se mueve en base a ambos.

  2. El caramelo A lo vas a distinguir porque siempre tiene el color sombreado. Cuando esta sombreado quiere decir que es nuestro PreviousSelected.

  3. Las combinaciones de tres caramelos o más se pueden dar por el Caramelo A o por el Caramelo B, de acuerdo a la dirección en la que se mueva.

  4. Importantísimo repasar el concepto de Estructuras de Datos. En este curso las utilizamos bastante, listas, arrays, etc. Un curso que podría recomendar de platzi para eso y donde se puede practicar bastante, es el curso básico de algoritmos.

  5. Ayudense bastante con las herramientas de debugging. En este caso, es bueno apoyarse de la consola y emplear el método Debug.log().

Aquí les dejo una función que conseguí por internet para imprimir el data de un gameObject en la consola de Unity:

    public static void DumpToConsole(object obj)
    {
        var output = JsonUtility.ToJson(obj, true);
        Debug.Log(output);
    }

Así como esta función hay muchísimas más por internet. Nunca duden en buscar cualquier duda que tengan y documentarse 💚

Para los que esten siguiendo la ruta de hacerlo con animaciones dejo el codigo de mi script de candy con un gif del resultado que obtuve.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Candy : MonoBehaviour
{
    private static Color selectedColor = new Color(0.5f, 0.5f, 0.5f, 1.0f);
    private static Candy previousSelected = null;

    private SpriteRenderer spriteRenderer;
    private bool isSelected = false;

    public int Id;

    private Vector2[] adjacentDirections = new Vector2[]
    {
        Vector2.down,
        Vector2.left,
        Vector2.up,
        Vector2.right,
    };

    public Vector3 objective;

    GameObject newCandy;

    private void Awake()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
        objective = Vector3.zero;
    }

    private void OnMouseDown()
    {
        if (spriteRenderer.sprite == null || BoardManager.sharedInstance.isShifting)
        {
            return;
        }

        if (isSelected)
        {
            DeselectCandy();
        }
        else
        {
            if (previousSelected == null)
            {
                SelectCandy();
            }
            else
            {
                if (CanSwipe())
                {
                    SwapSprite(previousSelected.gameObject);
                    previousSelected.Invoke("FindAllMatches", 0.25f);
                    previousSelected.DeselectCandy();
                    Invoke("FindAllMatches", 0.25f);
                }
                else
                {
                    previousSelected.DeselectCandy();
                    SelectCandy();
                }
            }
        }
    }

    private void Update()
    {
        if (objective != Vector3.zero)
        {
            this.transform.position = Vector3.Lerp(this.transform.position, objective, 5 * Time.deltaTime);
        }
    }

    private void SelectCandy()
    {
        isSelected = true;
        spriteRenderer.color = selectedColor;
        previousSelected = gameObject.GetComponent<Candy>();
    }

    private void DeselectCandy()
    {
        isSelected = false;
        spriteRenderer.color = Color.white;
        previousSelected = null;
    }

    public void SwapSprite(GameObject candy)
    {
        if (Id == candy.GetComponent<Candy>().Id)
        {
            return;
        }

        this.objective = candy.transform.position;
        candy.GetComponent<Candy>().objective = this.transform.position;
    }

    private GameObject GetNeighbor(Vector2 direction)
    {
        RaycastHit2D hit = Physics2D.Raycast(this.transform.position, direction);

        if(hit.collider != null)
        {
            return hit.collider.gameObject;
        }
        else
        {
            return null;
        }
    }

    private List<GameObject> GetAllNeighbors()
    {
        List<GameObject> neighbors = new List<GameObject>();

        foreach(Vector2 direction in adjacentDirections)
        {
            neighbors.Add(GetNeighbor(direction));
        }


        return neighbors;
    }

    private bool CanSwipe()
    {
        return GetAllNeighbors().Contains(previousSelected.gameObject);
    }

    //Consulta vecinos en direccion del parametro
    private List<GameObject> FindMatch(Vector2 direction)
    {
        List<GameObject> matchingCandies = new List<GameObject>();
        RaycastHit2D hit = Physics2D.Raycast(this.transform.position, direction);

        int i = 0;

        while (hit.collider != null && hit.collider.gameObject.GetComponent<SpriteRenderer>().sprite == spriteRenderer.sprite)
        {
            matchingCandies.Add(hit.collider.gameObject);
            hit = Physics2D.Raycast(hit.collider.gameObject.transform.position, direction);
        }

        return matchingCandies;
    }

    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<Animator>().SetBool("Combo", true);
                GetComponent<Animator>().SetBool("Combo", true);
            }

            return true;
        }

        return false;
    }

    public void FindAllMatches()
    {
        if (spriteRenderer.sprite == null)
        {
            return;
        }

        bool hMatch = ClearMatch(new Vector2[2]
        {
            Vector2.left,
            Vector2.right,
        });

        bool vMatch = ClearMatch(new Vector2[2]
        {
            Vector2.up,
            Vector2.down,
        });
    }

    public void DeleteCandy()
    {
        //Debug.Log("hey");
        Destroy(this.gameObject);
    }
}

Creo que estamos llevando muy lejos la “optimizacion” para movil a costa de los efectos graficos del juego, esta bien que no te consuma bateria, pero quitandole todo ese magico de animaciones y efectos de transicion vas a hacer que el usuario se aburra demasiado rapido.

muy complejo el candyCrush pff

if (hMatch || vMatch)
            {
                spriteRenderer.sprite = null;

            }

aki se podría cambiar el sprite por un “poder” como destruir una linea en esa cosa

Tengan cuidado con el codigo que escriban dentro del update del script Candy, tomen en cuenta que hay un monton de Caramelos instanciados, corriendo esa linea tantas veces su pc pueda por segundo y al sobrecargar feo a unity probablemente se les desconfigure algo, en mi caso se me desconfiguro la opcion que desmarcamos en la configuracion de fisicas 2D y estuve 5 horas con el juego crasheado hasta que se me ocurrio revisar eso.

estaba esperando con ansias hacer algo como esto pero como que a medida que pasa el curso se vuelve una tortura 😦

Dando una ojeada, creo que seguiré esta ruta. Great!

Intenté hacer el efecto cascada de los caramelos aplicando físicas (RigidBody a los caramelos y un piso para que no se caigan al vacío) pero me temblaban los caramelos y después se me complicaba el efecto swipe. Vamos a ver en la próxima clase como lo solucionan 🚀🚀

Consideraciones de optimización básicas para juegos en móvile.

en mi caso en lugar de usar:

Sprite hittedSprite = hit.collider.GetComponent<SpriteRenderer>().sprite;
while(hit.collider != null && hittedSprite == spriteBackground.sprite) {
	//logica
}

reviso el id que me parece que es mas preciso:

while(hit.collider != null && hit.collider.GetComponent<Candy>.id== this.id) {
	//logica
}

Alguien le sucede que cuando quieren eliminarlos con otro tipo de dulce no se elimina si no que tienen que hacerlo ustedes?

Les el diagrama de flujo que hice para entender como funciona el método de OnMouseDown().
https://drive.google.com/file/d/1ul6Kt1xAnIq-vj68MLzCmbYho8vD7FWU/view?usp=sharing

Muchos detalles aprendidos.

Les comparto el diagrama de flujo que hice para entender como funciona el algoritmo de selección de caramelos OnMouseDown().
https://drive.google.com/file/d/1ul6Kt1xAnIq-vj68MLzCmbYho8vD7FWU/view?usp=sharing